Skip to main content

Document

Struct Document 

Source
pub struct Document { /* private fields */ }
Expand description

A Word document (.docx file).

This is the main entry point for reading, creating, and modifying DOCX documents.

Implementations§

Source§

impl Document

Source

pub fn new() -> Self

Create a new, empty document with default page setup and styles.

Examples found in repository?
examples/convert_html_md.rs (line 15)
3fn main() {
4    let doc = Document::open("samples/feature_showcase.docx").expect("Failed to open document");
5
6    let html = doc.to_html();
7    std::fs::write("/tmp/feature_showcase.html", &html).expect("Failed to write HTML");
8    println!("HTML: {} bytes -> /tmp/feature_showcase.html", html.len());
9
10    let md = doc.to_markdown();
11    std::fs::write("/tmp/feature_showcase.md", &md).expect("Failed to write Markdown");
12    println!("Markdown: {} bytes -> /tmp/feature_showcase.md", md.len());
13
14    // Simple document
15    let mut simple = Document::new();
16    simple.add_paragraph("Hello, World!");
17    let html = simple.to_html();
18    println!("\n--- Simple HTML ---\n{html}");
19
20    let md = simple.to_markdown();
21    println!("--- Simple Markdown ---\n{md}");
22}
More examples
Hide additional examples
examples/generate_pdf.rs (line 9)
7fn main() {
8    // Test 1: Simple document
9    let doc = Document::new();
10    match doc.to_pdf() {
11        Ok(bytes) => {
12            std::fs::write("/tmp/rdocx_simple.pdf", &bytes).unwrap();
13            println!("Simple PDF: {} bytes -> /tmp/rdocx_simple.pdf", bytes.len());
14        }
15        Err(e) => println!("Simple PDF failed: {e}"),
16    }
17
18    // Test 2: Document with content
19    let mut doc = Document::new();
20    doc.set_title("Test PDF Document");
21    doc.set_author("rdocx-pdf");
22    doc.add_paragraph("Chapter 1: Introduction")
23        .style("Heading1");
24    doc.add_paragraph(
25        "This is a test document generated by rdocx and rendered to PDF. \
26         It demonstrates text rendering with proper font shaping and pagination.",
27    );
28    doc.add_paragraph("Section 1.1").style("Heading2");
29    doc.add_paragraph("More content in a sub-section.");
30
31    {
32        let mut table = doc.add_table(2, 3);
33        for r in 0..2 {
34            for c in 0..3 {
35                if let Some(mut cell) = table.cell(r, c) {
36                    cell.set_text(&format!("R{}C{}", r + 1, c + 1));
37                }
38            }
39        }
40    }
41
42    doc.add_paragraph("After the table.");
43
44    match doc.to_pdf() {
45        Ok(bytes) => {
46            std::fs::write("/tmp/rdocx_content.pdf", &bytes).unwrap();
47            println!(
48                "Content PDF: {} bytes -> /tmp/rdocx_content.pdf",
49                bytes.len()
50            );
51        }
52        Err(e) => println!("Content PDF failed: {e}"),
53    }
54
55    // Test 3: From feature_showcase.docx
56    let showcase_path = concat!(
57        env!("CARGO_MANIFEST_DIR"),
58        "/../../samples/feature_showcase.docx"
59    );
60    match Document::open(showcase_path) {
61        Ok(doc) => match doc.to_pdf() {
62            Ok(bytes) => {
63                std::fs::write("/tmp/rdocx_showcase.pdf", &bytes).unwrap();
64                println!(
65                    "Showcase PDF: {} bytes -> /tmp/rdocx_showcase.pdf",
66                    bytes.len()
67                );
68            }
69            Err(e) => println!("Showcase PDF failed: {e}"),
70        },
71        Err(e) => println!("Failed to open showcase: {e}"),
72    }
73}
examples/template_replace.rs (line 44)
43fn create_template(path: &Path) {
44    let mut doc = Document::new();
45    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
46    doc.set_margins(
47        Length::inches(1.0),
48        Length::inches(1.0),
49        Length::inches(1.0),
50        Length::inches(1.0),
51    );
52
53    doc.set_header("{{company_name}} — Confidential");
54    doc.set_footer("Prepared by {{author_name}} on {{date}}");
55
56    // ── Title ──
57    doc.add_paragraph("{{company_name}}")
58        .style("Heading1")
59        .alignment(Alignment::Center);
60
61    doc.add_paragraph("Project Proposal")
62        .alignment(Alignment::Center);
63
64    doc.add_paragraph("");
65
66    // ── Summary section ──
67    doc.add_paragraph("Executive Summary").style("Heading2");
68
69    doc.add_paragraph(
70        "This proposal outlines the {{project_name}} project for {{company_name}}. \
71         The primary contact is {{contact_name}} ({{contact_email}}). \
72         The proposed start date is {{start_date}} with an estimated duration of {{duration}}.",
73    );
74
75    doc.add_paragraph("");
76
77    // ── Cross-run placeholder (bold label + normal value) ──
78    doc.add_paragraph("Key Details").style("Heading2");
79
80    {
81        let mut p = doc.add_paragraph("");
82        p.add_run("Project: ").bold(true);
83        p.add_run("{{project_name}}");
84    }
85    {
86        let mut p = doc.add_paragraph("");
87        p.add_run("Budget: ").bold(true);
88        p.add_run("{{budget}}");
89    }
90    {
91        let mut p = doc.add_paragraph("");
92        p.add_run("Status: ").bold(true);
93        p.add_run("{{status}}");
94    }
95
96    doc.add_paragraph("");
97
98    // ── Table with placeholders ──
99    doc.add_paragraph("Team Members").style("Heading2");
100
101    {
102        let mut tbl = doc.add_table(4, 3);
103        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
104
105        // Header row
106        for col in 0..3 {
107            tbl.cell(0, col).unwrap().shading("2E75B6");
108        }
109        tbl.cell(0, 0).unwrap().set_text("Name");
110        tbl.cell(0, 1).unwrap().set_text("Role");
111        tbl.cell(0, 2).unwrap().set_text("Email");
112
113        tbl.cell(1, 0).unwrap().set_text("{{member1_name}}");
114        tbl.cell(1, 1).unwrap().set_text("{{member1_role}}");
115        tbl.cell(1, 2).unwrap().set_text("{{member1_email}}");
116
117        tbl.cell(2, 0).unwrap().set_text("{{member2_name}}");
118        tbl.cell(2, 1).unwrap().set_text("{{member2_role}}");
119        tbl.cell(2, 2).unwrap().set_text("{{member2_email}}");
120
121        tbl.cell(3, 0).unwrap().set_text("{{member3_name}}");
122        tbl.cell(3, 1).unwrap().set_text("{{member3_role}}");
123        tbl.cell(3, 2).unwrap().set_text("{{member3_email}}");
124    }
125
126    doc.add_paragraph("");
127
128    // ── Deliverables section ──
129    doc.add_paragraph("Deliverables").style("Heading2");
130
131    doc.add_paragraph("INSERTION_POINT");
132
133    doc.add_paragraph("");
134
135    // ── Signature block ──
136    doc.add_paragraph("Acceptance").style("Heading2");
137
138    doc.add_paragraph(
139        "By signing below, {{company_name}} agrees to the terms outlined in this proposal.",
140    );
141
142    {
143        let mut tbl = doc.add_table(2, 2);
144        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
145        tbl.cell(0, 0)
146            .unwrap()
147            .set_text("Customer: ___________________");
148        tbl.cell(0, 1)
149            .unwrap()
150            .set_text("Provider: ___________________");
151        tbl.cell(1, 0).unwrap().set_text("Date: {{date}}");
152        tbl.cell(1, 1).unwrap().set_text("Date: {{date}}");
153    }
154
155    doc.set_title("{{company_name}} — Project Proposal Template");
156    doc.set_author("Template Generator");
157
158    doc.save(path).unwrap();
159}
examples/header_banner.rs (line 30)
29fn generate_header_banner_doc(path: &Path) {
30    let mut doc = Document::new();
31
32    // Page setup with extra top margin for the banner
33    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
34    doc.set_margins(
35        Length::twips(2292), // top — extra tall for header banner
36        Length::twips(1440), // right
37        Length::twips(1440), // bottom
38        Length::twips(1440), // left
39    );
40    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
41
42    // Generate a simple logo image (white text on transparent background)
43    let logo_img = create_logo_png(220, 48);
44
45    // ── Dark blue banner ──
46    let banner = build_header_banner_xml(
47        "rId1",
48        &BannerOpts {
49            bg_color: "1A3C6E",
50            banner_width: 7772400, // full page width in EMU (~8.5")
51            banner_height: 969026, // banner height in EMU (~1.06")
52            logo_width: 2011680,   // logo display width (~2.2")
53            logo_height: 438912,   // logo display height (~0.48")
54            logo_x_offset: 295125, // left padding
55            logo_y_offset: 265057, // vertical centering
56        },
57    );
58
59    doc.set_raw_header_with_images(
60        banner.clone(),
61        &[("rId1", &logo_img, "logo.png")],
62        rdocx_oxml::header_footer::HdrFtrType::Default,
63    );
64
65    // Use a different first page header (same banner, different color)
66    doc.set_different_first_page(true);
67    let first_page_banner = build_header_banner_xml(
68        "rId1",
69        &BannerOpts {
70            bg_color: "2E75B6", // lighter blue for cover
71            banner_width: 7772400,
72            banner_height: 969026,
73            logo_width: 2011680,
74            logo_height: 438912,
75            logo_x_offset: 295125,
76            logo_y_offset: 265057,
77        },
78    );
79    doc.set_raw_header_with_images(
80        first_page_banner,
81        &[("rId1", &logo_img, "logo.png")],
82        rdocx_oxml::header_footer::HdrFtrType::First,
83    );
84
85    // Footer
86    doc.set_footer("Confidential — Internal Use Only");
87
88    // ── Page 1: Cover ──
89    doc.add_paragraph("Company Report").style("Heading1");
90
91    doc.add_paragraph(
92        "This document demonstrates a custom header banner built with DrawingML \
93         group shapes. The banner uses a colored rectangle with a logo image overlaid, \
94         positioned at the top of each page.",
95    );
96
97    doc.add_paragraph("");
98
99    doc.add_paragraph("How the Header Banner Works")
100        .style("Heading2");
101
102    doc.add_paragraph(
103        "The header banner is built using set_raw_header_with_images(), which \
104         accepts raw XML and a list of (rel_id, image_data, filename) tuples. \
105         The XML uses a DrawingML group shape (wpg:wgp) containing:",
106    );
107
108    doc.add_bullet_list_item(
109        "A wps:wsp rectangle shape with a solid color fill (the background bar)",
110        0,
111    );
112    doc.add_bullet_list_item(
113        "A pic:pic image element positioned within the group (the logo)",
114        0,
115    );
116    doc.add_bullet_list_item(
117        "The group is wrapped in a wp:anchor element for absolute page positioning",
118        0,
119    );
120
121    doc.add_paragraph("");
122
123    doc.add_paragraph("Customization").style("Heading2");
124
125    doc.add_paragraph(
126        "All dimensions are in EMU (English Metric Units) where 914400 EMU = 1 inch. \
127         You can customize:",
128    );
129
130    doc.add_bullet_list_item("bg_color — any hex color for the rectangle background", 0);
131    doc.add_bullet_list_item("banner_width / banner_height — size of the full banner", 0);
132    doc.add_bullet_list_item("logo_width / logo_height — display size of the logo", 0);
133    doc.add_bullet_list_item(
134        "logo_x_offset / logo_y_offset — logo position within the banner",
135        0,
136    );
137
138    doc.add_paragraph("");
139
140    doc.add_paragraph("Different First Page").style("Heading2");
141
142    doc.add_paragraph(
143        "This page uses a lighter blue banner (first page header). \
144         Subsequent pages use a darker blue banner (default header). \
145         Use set_different_first_page(true) to enable this.",
146    );
147
148    // ── Page 2 ──
149    doc.add_paragraph("").page_break_before(true);
150
151    doc.add_paragraph("Second Page").style("Heading1");
152
153    doc.add_paragraph(
154        "This page shows the default header banner (dark blue). The first page \
155         had a lighter blue banner because we set a different first-page header.",
156    );
157
158    doc.add_paragraph("");
159
160    doc.add_paragraph(
161        "The banner repeats on every page because it is placed in the header part. \
162         You can have different banners for default, first-page, and even-page headers.",
163    );
164
165    doc.set_title("Header Banner Example");
166    doc.set_author("rdocx");
167
168    doc.save(path).unwrap();
169}
examples/styled_tables.rs (line 25)
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}
examples/generate_all_samples.rs (line 98)
97fn generate_feature_showcase(_samples_dir: &Path) -> Document {
98    let mut doc = Document::new();
99
100    // ── Page Setup & Metadata ──
101    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
102    doc.set_margins(
103        Length::inches(1.0),
104        Length::inches(1.0),
105        Length::inches(1.0),
106        Length::inches(1.0),
107    );
108    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
109    doc.set_gutter(Length::twips(0));
110    doc.set_title("rdocx Feature Showcase");
111    doc.set_author("rdocx Sample Generator");
112    doc.set_subject("Comprehensive feature demonstration");
113    doc.set_keywords("rdocx, docx, rust, sample, showcase");
114
115    // Headers & Footers with different first page
116    doc.set_different_first_page(true);
117    doc.set_first_page_header("rdocx");
118    doc.set_first_page_footer("Feature Showcase — Cover Page");
119    doc.set_header("rdocx Feature Showcase");
120    doc.set_footer("Generated by rdocx");
121
122    // ── COVER PAGE ──
123    let bg = create_sample_png(612, 792, [20, 45, 90]);
124    doc.add_background_image(&bg, "cover_bg.png");
125
126    for _ in 0..3 {
127        doc.add_paragraph("");
128    }
129    {
130        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
131        p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
132    }
133    {
134        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
135        p.add_run("Complete Feature Showcase")
136            .size(28.0)
137            .color("FFFFFF")
138            .italic(true);
139    }
140    doc.add_paragraph("");
141    {
142        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
143        p.add_run("Every feature of the rdocx Rust library")
144            .size(13.0)
145            .color("B0C4DE");
146    }
147    {
148        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
149        p.add_run("demonstrated in a single document.")
150            .size(13.0)
151            .color("B0C4DE");
152    }
153
154    // ── TABLE OF CONTENTS ──
155    doc.add_paragraph("").page_break_before(true);
156    doc.insert_toc(doc.content_count(), 3);
157
158    // ── SECTION 1: TEXT FORMATTING ──
159    doc.add_paragraph("").page_break_before(true);
160    doc.add_paragraph("1. Text Formatting").style("Heading1");
161
162    // Paragraph Alignment
163    doc.add_paragraph("1.1 Paragraph Alignment")
164        .style("Heading2");
165    doc.add_paragraph("Left-aligned paragraph (default).")
166        .alignment(Alignment::Left);
167    doc.add_paragraph("Center-aligned paragraph.")
168        .alignment(Alignment::Center);
169    doc.add_paragraph("Right-aligned paragraph.")
170        .alignment(Alignment::Right);
171    doc.add_paragraph(
172        "Justified paragraph — this text is long enough to demonstrate how justified alignment \
173         distributes extra space across word gaps so lines fill the full width of the text area.",
174    )
175    .alignment(Alignment::Justify);
176
177    doc.add_paragraph("");
178
179    // Run Formatting
180    doc.add_paragraph("1.2 Run Formatting").style("Heading2");
181    {
182        let mut p = doc.add_paragraph("");
183        p.add_run("Bold").bold(true);
184        p.add_run(" | ");
185        p.add_run("Italic").italic(true);
186        p.add_run(" | ");
187        p.add_run("Bold Italic").bold(true).italic(true);
188        p.add_run(" | ");
189        p.add_run("Underline").underline(true);
190        p.add_run(" | ");
191        p.add_run("Strikethrough").strike(true);
192        p.add_run(" | ");
193        p.add_run("Double Strike").double_strike(true);
194    }
195    {
196        let mut p = doc.add_paragraph("");
197        p.add_run("Red").color("FF0000");
198        p.add_run(" | ");
199        p.add_run("Blue").color("0000FF");
200        p.add_run(" | ");
201        p.add_run("Green").color("00AA00");
202        p.add_run(" | ");
203        p.add_run("Highlighted").highlight("FFFF00");
204    }
205    {
206        let mut p = doc.add_paragraph("");
207        p.add_run("8pt").size(8.0);
208        p.add_run(" | ");
209        p.add_run("11pt").size(11.0);
210        p.add_run(" | ");
211        p.add_run("16pt").size(16.0);
212        p.add_run(" | ");
213        p.add_run("24pt").size(24.0);
214    }
215    {
216        let mut p = doc.add_paragraph("");
217        p.add_run("Arial").font("Arial");
218        p.add_run(" | ");
219        p.add_run("Times New Roman").font("Times New Roman");
220        p.add_run(" | ");
221        p.add_run("Courier New").font("Courier New");
222    }
223    {
224        let mut p = doc.add_paragraph("");
225        p.add_run("H");
226        p.add_run("2").subscript();
227        p.add_run("O (subscript) | E=mc");
228        p.add_run("2").superscript();
229        p.add_run(" (superscript)");
230    }
231    {
232        let mut p = doc.add_paragraph("");
233        p.add_run("ALL CAPS").all_caps(true);
234        p.add_run(" | ");
235        p.add_run("Small Caps").small_caps(true);
236        p.add_run(" | ");
237        p.add_run("Expanded").character_spacing(Length::twips(40));
238        p.add_run(" | ");
239        p.add_run("Hidden text (not visible)").hidden(true);
240    }
241
242    doc.add_paragraph("");
243
244    // Underline Styles
245    doc.add_paragraph("1.3 Underline Styles").style("Heading2");
246    {
247        let mut p = doc.add_paragraph("");
248        p.add_run("Single ")
249            .underline_style(rdocx::UnderlineStyle::Single);
250        p.add_run("Double ")
251            .underline_style(rdocx::UnderlineStyle::Double);
252        p.add_run("Thick ")
253            .underline_style(rdocx::UnderlineStyle::Thick);
254        p.add_run("Dotted ")
255            .underline_style(rdocx::UnderlineStyle::Dotted);
256        p.add_run("Dash ")
257            .underline_style(rdocx::UnderlineStyle::Dash);
258        p.add_run("Wave ")
259            .underline_style(rdocx::UnderlineStyle::Wave);
260    }
261
262    // ── SECTION 2: PARAGRAPH FORMATTING ──
263    doc.add_paragraph("").page_break_before(true);
264    doc.add_paragraph("2. Paragraph Formatting")
265        .style("Heading1");
266
267    doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
268    doc.add_paragraph("Paragraph with light green shading")
269        .shading("E2EFDA");
270    doc.add_paragraph("Paragraph with bottom border")
271        .border_bottom(BorderStyle::Single, 6, "2E75B6");
272    doc.add_paragraph("Paragraph with all borders (red)")
273        .border_all(BorderStyle::Single, 4, "FF0000");
274
275    doc.add_paragraph("2.2 Indentation").style("Heading2");
276    doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
277        .indent_left(Length::inches(1.0))
278        .hanging_indent(Length::inches(0.5));
279    doc.add_paragraph("0.5-inch first-line indent on this paragraph")
280        .first_line_indent(Length::inches(0.5));
281    doc.add_paragraph("Both left and right indent (0.75 inches each)")
282        .indent_left(Length::inches(0.75))
283        .indent_right(Length::inches(0.75));
284
285    doc.add_paragraph("2.3 Spacing & Line Height")
286        .style("Heading2");
287    doc.add_paragraph("Extra space before (24pt) and after (12pt)")
288        .space_before(Length::pt(24.0))
289        .space_after(Length::pt(12.0));
290    doc.add_paragraph(
291        "Double line spacing paragraph. This text should have extra vertical space between \
292         lines to demonstrate line_spacing_multiple(2.0).",
293    )
294    .line_spacing_multiple(2.0);
295    doc.add_paragraph("Exact 20pt line spacing (fixed height).")
296        .line_spacing(20.0);
297
298    doc.add_paragraph("2.4 Pagination Controls")
299        .style("Heading2");
300    doc.add_paragraph("keep_with_next — this paragraph stays with the next")
301        .keep_with_next(true);
302    doc.add_paragraph("(This paragraph was kept with the one above.)");
303    doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
304        .keep_together(true);
305    doc.add_paragraph("widow_control — prevents widow/orphan lines")
306        .widow_control(true);
307
308    // ── SECTION 3: LISTS ──
309    doc.add_paragraph("").page_break_before(true);
310    doc.add_paragraph("3. Lists").style("Heading1");
311
312    doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
313    doc.add_bullet_list_item("First item", 0);
314    doc.add_bullet_list_item("Second item", 0);
315    doc.add_bullet_list_item("Nested level 1", 1);
316    doc.add_bullet_list_item("Nested level 2", 2);
317    doc.add_bullet_list_item("Back to level 1", 1);
318    doc.add_bullet_list_item("Third item", 0);
319
320    doc.add_paragraph("");
321
322    doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
323    doc.add_numbered_list_item("First numbered item", 0);
324    doc.add_numbered_list_item("Second numbered item", 0);
325    doc.add_numbered_list_item("Sub-item A", 1);
326    doc.add_numbered_list_item("Sub-item B", 1);
327    doc.add_numbered_list_item("Third numbered item", 0);
328
329    // ── SECTION 4: TAB STOPS ──
330    doc.add_paragraph("");
331    doc.add_paragraph("4. Tab Stops").style("Heading1");
332
333    doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
334    doc.add_paragraph("Left\tCenter\tRight\tDecimal")
335        .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
336        .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
337        .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
338        .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
339
340    doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
341    doc.add_paragraph("Item\t\tPrice")
342        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
343        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
344        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
345    doc.add_paragraph("Widget\t\t$19.99")
346        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
347        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
348        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
349    doc.add_paragraph("Gadget\t\t$249.50")
350        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
351        .add_tab_stop_with_leader(
352            TabAlignment::Right,
353            Length::inches(4.0),
354            TabLeader::Underscore,
355        )
356        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
357
358    // ── SECTION 5: TABLES ──
359    doc.add_paragraph("").page_break_before(true);
360    doc.add_paragraph("5. Tables").style("Heading1");
361
362    doc.add_paragraph("5.1 Basic Table").style("Heading2");
363    {
364        let mut tbl = doc
365            .add_table(4, 3)
366            .borders(BorderStyle::Single, 4, "000000");
367        for col in 0..3 {
368            tbl.cell(0, col).unwrap().shading("2E75B6");
369        }
370        tbl.cell(0, 0).unwrap().set_text("Name");
371        tbl.cell(0, 1).unwrap().set_text("Role");
372        tbl.cell(0, 2).unwrap().set_text("Location");
373        tbl.cell(1, 0).unwrap().set_text("Walter White");
374        tbl.cell(1, 1).unwrap().set_text("CEO");
375        tbl.cell(1, 2).unwrap().set_text("Albuquerque");
376        tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
377        tbl.cell(2, 1).unwrap().set_text("CTO");
378        tbl.cell(2, 2).unwrap().set_text("Remote");
379        tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
380        tbl.cell(3, 1).unwrap().set_text("Security");
381        tbl.cell(3, 2).unwrap().set_text("Washington");
382    }
383
384    doc.add_paragraph("");
385
386    doc.add_paragraph("5.2 Column Span & Cell Shading")
387        .style("Heading2");
388    {
389        let mut tbl = doc
390            .add_table(3, 4)
391            .borders(BorderStyle::Single, 4, "000000")
392            .width_pct(100.0);
393        tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
394        tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
395        tbl.cell(1, 0).unwrap().set_text("Region");
396        tbl.cell(1, 0).unwrap().shading("D6E4F0");
397        tbl.cell(1, 1).unwrap().set_text("Q1");
398        tbl.cell(1, 1).unwrap().shading("D6E4F0");
399        tbl.cell(1, 2).unwrap().set_text("Q2");
400        tbl.cell(1, 2).unwrap().shading("D6E4F0");
401        tbl.cell(1, 3).unwrap().set_text("Total");
402        tbl.cell(1, 3).unwrap().shading("D6E4F0");
403        tbl.cell(2, 0).unwrap().set_text("Americas");
404        tbl.cell(2, 1).unwrap().set_text("$2.4M");
405        tbl.cell(2, 2).unwrap().set_text("$2.7M");
406        tbl.cell(2, 3).unwrap().set_text("$5.1M");
407    }
408
409    doc.add_paragraph("");
410
411    doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
412    {
413        let mut tbl = doc
414            .add_table(4, 3)
415            .borders(BorderStyle::Single, 4, "000000");
416        tbl.cell(0, 0).unwrap().set_text("Category");
417        tbl.cell(0, 0).unwrap().shading("E2EFDA");
418        tbl.cell(0, 1).unwrap().set_text("Item");
419        tbl.cell(0, 1).unwrap().shading("E2EFDA");
420        tbl.cell(0, 2).unwrap().set_text("Price");
421        tbl.cell(0, 2).unwrap().shading("E2EFDA");
422        tbl.cell(1, 0).unwrap().set_text("Hardware");
423        tbl.cell(1, 0).unwrap().v_merge_restart();
424        tbl.cell(1, 1).unwrap().set_text("Laptop");
425        tbl.cell(1, 2).unwrap().set_text("$1,200");
426        tbl.cell(2, 0).unwrap().v_merge_continue();
427        tbl.cell(2, 1).unwrap().set_text("Monitor");
428        tbl.cell(2, 2).unwrap().set_text("$450");
429        tbl.cell(3, 0).unwrap().set_text("Software");
430        tbl.cell(3, 1).unwrap().set_text("IDE License");
431        tbl.cell(3, 2).unwrap().set_text("$200/yr");
432    }
433
434    doc.add_paragraph("");
435
436    doc.add_paragraph("5.4 Nested Table").style("Heading2");
437    {
438        let mut tbl = doc
439            .add_table(2, 2)
440            .borders(BorderStyle::Single, 6, "2E75B6");
441        tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
442        tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
443        tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
444        {
445            let mut cell = tbl.cell(1, 1).unwrap();
446            cell.set_text("Nested table below:");
447            let mut nested = cell
448                .add_table(2, 2)
449                .borders(BorderStyle::Single, 2, "FF6600");
450            nested.cell(0, 0).unwrap().set_text("A");
451            nested.cell(0, 1).unwrap().set_text("B");
452            nested.cell(1, 0).unwrap().set_text("C");
453            nested.cell(1, 1).unwrap().set_text("D");
454        }
455    }
456
457    doc.add_paragraph("");
458
459    doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
460        .style("Heading2");
461    {
462        let mut tbl = doc
463            .add_table(2, 3)
464            .borders(BorderStyle::Single, 4, "666666");
465        tbl.row(0).unwrap().height(Length::pt(50.0)).header();
466        tbl.cell(0, 0).unwrap().set_text("Top");
467        tbl.cell(0, 0)
468            .unwrap()
469            .vertical_alignment(VerticalAlignment::Top)
470            .shading("FFF2CC");
471        tbl.cell(0, 1).unwrap().set_text("Center");
472        tbl.cell(0, 1)
473            .unwrap()
474            .vertical_alignment(VerticalAlignment::Center)
475            .shading("D9E2F3");
476        tbl.cell(0, 2).unwrap().set_text("Bottom");
477        tbl.cell(0, 2)
478            .unwrap()
479            .vertical_alignment(VerticalAlignment::Bottom)
480            .shading("E2EFDA");
481        tbl.row(1).unwrap().cant_split();
482        tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
483        tbl.cell(1, 0).unwrap().no_wrap();
484        tbl.cell(1, 1).unwrap().set_text("Fixed width");
485        tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
486        tbl.cell(1, 2).unwrap().set_text("Normal");
487    }
488
489    // ── SECTION 6: IMAGES ──
490    doc.add_paragraph("").page_break_before(true);
491    doc.add_paragraph("6. Images").style("Heading1");
492
493    doc.add_paragraph("6.1 Inline Image").style("Heading2");
494    doc.add_paragraph("A blue gradient image below:");
495    let img = create_sample_png(200, 50, [0, 80, 200]);
496    doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
497
498    doc.add_paragraph("");
499    doc.add_paragraph("6.2 Header Image").style("Heading2");
500    let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
501    doc.set_header_image(
502        &hdr_img,
503        "header_logo.png",
504        Length::inches(2.0),
505        Length::inches(0.2),
506    );
507    doc.add_paragraph("The header now contains an inline image (check the top of this page).");
508
509    doc.add_paragraph("");
510    doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
511
512    // ── SECTION 7: CONTENT MANIPULATION ──
513    doc.add_paragraph("").page_break_before(true);
514    doc.add_paragraph("7. Content Manipulation")
515        .style("Heading1");
516
517    doc.add_paragraph("7.1 Placeholder Replacement")
518        .style("Heading2");
519    doc.add_paragraph("Customer: {{customer}}");
520    doc.add_paragraph("Date: {{date}}");
521    doc.add_paragraph("Reference: {{ref_number}}");
522    {
523        let mut tbl = doc
524            .add_table(2, 2)
525            .borders(BorderStyle::Single, 4, "000000");
526        tbl.cell(0, 0).unwrap().set_text("Project");
527        tbl.cell(0, 0).unwrap().shading("D6E4F0");
528        tbl.cell(0, 1).unwrap().set_text("{{project}}");
529        tbl.cell(1, 0).unwrap().set_text("Status");
530        tbl.cell(1, 0).unwrap().shading("D6E4F0");
531        tbl.cell(1, 1).unwrap().set_text("{{status}}");
532    }
533
534    let mut replacements = HashMap::new();
535    replacements.insert("{{customer}}", "Tensorbee Inc.");
536    replacements.insert("{{date}}", "February 22, 2026");
537    replacements.insert("{{ref_number}}", "TB-2026-001");
538    replacements.insert("{{project}}", "Infrastructure Upgrade");
539    replacements.insert("{{status}}", "In Progress");
540    let n = doc.replace_all(&replacements);
541    doc.add_paragraph(&format!("({n} placeholders replaced above)"));
542
543    doc.add_paragraph("");
544
545    doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
546    doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
547    let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
548    doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
549
550    doc.add_paragraph("");
551
552    doc.add_paragraph("7.3 Content Insertion").style("Heading2");
553    doc.add_paragraph("Section A: First content.");
554    doc.add_paragraph("Section C: Third content.");
555    if let Some(idx) = doc.find_content_index("Section C") {
556        doc.insert_paragraph(
557            idx,
558            "Section B: Inserted between A and C via find_content_index().",
559        );
560    }
561
562    // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
563    doc.add_paragraph("").section_break(SectionBreak::NextPage);
564    doc.add_paragraph("8. Section Breaks & Orientation")
565        .style("Heading1");
566    doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
567
568    {
569        let mut tbl = doc
570            .add_table(3, 7)
571            .borders(BorderStyle::Single, 4, "2E75B6");
572        let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
573        for (c, h) in headers.iter().enumerate() {
574            tbl.cell(0, c).unwrap().set_text(h);
575            tbl.cell(0, c).unwrap().shading("2E75B6");
576        }
577        let data = [
578            [
579                "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
580            ],
581            [
582                "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
583            ],
584        ];
585        for (r, row) in data.iter().enumerate() {
586            for (c, v) in row.iter().enumerate() {
587                tbl.cell(r + 1, c).unwrap().set_text(v);
588            }
589        }
590    }
591
592    doc.add_paragraph("")
593        .section_break(SectionBreak::NextPage)
594        .section_landscape();
595
596    // ── SECTION 9: CUSTOM STYLES ──
597    doc.add_paragraph("9. Custom Styles").style("Heading1");
598    doc.add_style(
599        StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
600            .based_on("Normal")
601            .paragraph_properties(rdocx_oxml::properties::CT_PPr {
602                shading: Some(rdocx_oxml::properties::CT_Shd {
603                    val: "clear".to_string(),
604                    color: None,
605                    fill: Some("FFF2CC".to_string()),
606                }),
607                ..Default::default()
608            })
609            .run_properties(rdocx_oxml::properties::CT_RPr {
610                bold: Some(true),
611                color: Some("C45911".to_string()),
612                ..Default::default()
613            }),
614    );
615    doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
616        .style("CustomHighlight");
617
618    doc.add_paragraph("");
619
620    // ── SECTION 10: DOCUMENT INTELLIGENCE ──
621    doc.add_paragraph("10. Document Intelligence API")
622        .style("Heading1");
623
624    let wc = doc.word_count();
625    let hc = doc.headings().len();
626    let ic = doc.images().len();
627    let lc = doc.links().len();
628    doc.add_paragraph(&format!("Word count: {wc}"));
629    doc.add_paragraph(&format!("Heading count: {hc}"));
630    doc.add_paragraph(&format!("Image count: {ic}"));
631    doc.add_paragraph(&format!("Link count: {lc}"));
632
633    let outline = doc.document_outline();
634    doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
635
636    let issues = doc.audit_accessibility();
637    doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
638    for issue in &issues {
639        doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
640    }
641
642    // ── SECTION 11: DOCUMENT MERGING ──
643    doc.add_paragraph("");
644    doc.add_paragraph("11. Document Merging").style("Heading1");
645    {
646        let mut other = Document::new();
647        other.add_paragraph("This paragraph was merged from another document using append().");
648        doc.append(&other);
649    }
650
651    doc.add_paragraph("");
652
653    // ── SUMMARY ──
654    doc.add_paragraph("Summary of Demonstrated Features")
655        .style("Heading1");
656    let features = [
657        "Page setup: size, margins, header/footer distance, gutter",
658        "Document metadata: title, author, subject, keywords",
659        "Headers and footers: text, images, different first page",
660        "Background images (full-page behind text)",
661        "Table of Contents generation with bookmarks",
662        "Text formatting: bold, italic, underline styles, strike, color, size, font",
663        "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
664        "Paragraph formatting: alignment, borders, shading, spacing, indentation",
665        "Pagination controls: keep-with-next, keep-together, widow control",
666        "Bullet and numbered lists with nesting",
667        "Tab stops with dot/underscore/hyphen leaders",
668        "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
669        "Row properties: header rows, exact height, cant-split",
670        "Cell properties: width, no-wrap, vertical alignment",
671        "Inline images and header images",
672        "Placeholder replacement (single and batch)",
673        "Regex-based find and replace",
674        "Content insertion at specific positions",
675        "Section breaks with mixed portrait/landscape",
676        "Custom paragraph and character styles",
677        "Document intelligence: word count, headings, outline, images, links",
678        "Accessibility audit",
679        "Document merging (append)",
680        "Export: DOCX, PDF, HTML, Markdown",
681    ];
682    for f in &features {
683        doc.add_bullet_list_item(f, 0);
684    }
685    doc.add_paragraph("");
686    doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
687        .alignment(Alignment::Center)
688        .shading("E2EFDA")
689        .border_all(BorderStyle::Single, 2, "00AA00");
690
691    doc
692}
693
694// =============================================================================
695// 2. PROPOSAL DOCUMENT — Deep navy + gold scheme
696// =============================================================================
697fn generate_proposal(_samples_dir: &Path) -> Document {
698    let mut doc = Document::new();
699
700    // Colors: Navy #1B2A4A, Gold #C5922E, Light #F4F1EB
701    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
702    doc.set_margins(
703        Length::inches(1.0),
704        Length::inches(1.0),
705        Length::inches(1.0),
706        Length::inches(1.0),
707    );
708    doc.set_title("AI Platform Modernization Proposal");
709    doc.set_author("Walter White");
710    doc.set_subject("Technology Proposal");
711    doc.set_keywords("proposal, AI, modernization, Tensorbee");
712
713    // Banner header
714    let logo_img = create_logo_png(220, 48);
715    let banner = build_header_banner_xml(
716        "rId1",
717        &BannerOpts {
718            bg_color: "1B2A4A",
719            banner_width: 7772400,
720            banner_height: 914400,
721            logo_width: 2011680,
722            logo_height: 438912,
723            logo_x_offset: 295125,
724            logo_y_offset: 237744,
725        },
726    );
727    doc.set_raw_header_with_images(
728        banner,
729        &[("rId1", &logo_img, "logo.png")],
730        rdocx_oxml::header_footer::HdrFtrType::Default,
731    );
732    doc.set_different_first_page(true);
733    let cover_banner = build_header_banner_xml(
734        "rId1",
735        &BannerOpts {
736            bg_color: "C5922E",
737            banner_width: 7772400,
738            banner_height: 914400,
739            logo_width: 2011680,
740            logo_height: 438912,
741            logo_x_offset: 295125,
742            logo_y_offset: 237744,
743        },
744    );
745    doc.set_raw_header_with_images(
746        cover_banner,
747        &[("rId1", &logo_img, "logo.png")],
748        rdocx_oxml::header_footer::HdrFtrType::First,
749    );
750    doc.set_margins(
751        Length::twips(2292),
752        Length::twips(1440),
753        Length::twips(1440),
754        Length::twips(1440),
755    );
756    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
757    doc.set_footer("Tensorbee — Confidential");
758
759    // Custom styles
760    doc.add_style(
761        StyleBuilder::paragraph("ProposalTitle", "Proposal Title")
762            .based_on("Normal")
763            .run_properties(rdocx_oxml::properties::CT_RPr {
764                bold: Some(true),
765                font_ascii: Some("Georgia".to_string()),
766                font_hansi: Some("Georgia".to_string()),
767                sz: Some(rdocx_oxml::HalfPoint(56)), // half-points → 28pt
768                color: Some("1B2A4A".to_string()),
769                ..Default::default()
770            }),
771    );
772
773    // ── Cover Page ──
774    for _ in 0..4 {
775        doc.add_paragraph("");
776    }
777    doc.add_paragraph("AI Platform Modernization")
778        .style("ProposalTitle")
779        .alignment(Alignment::Center);
780    doc.add_paragraph("")
781        .alignment(Alignment::Center)
782        .border_bottom(BorderStyle::Single, 8, "C5922E");
783    {
784        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
785        p.add_run("Prepared for Global Financial Corp.")
786            .size(14.0)
787            .color("666666")
788            .italic(true);
789    }
790    {
791        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
792        p.add_run("Prepared by: Walter White, CEO — Tensorbee")
793            .size(12.0)
794            .color("888888");
795    }
796    {
797        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
798        p.add_run("February 22, 2026").size(12.0).color("888888");
799    }
800
801    // ── TOC ──
802    doc.add_paragraph("").page_break_before(true);
803    doc.add_paragraph("Table of Contents").style("Heading1");
804    // We'll add a manual-style TOC since the insert_toc goes at a specific position
805    doc.insert_toc(doc.content_count(), 2);
806
807    // ── Executive Summary ──
808    doc.add_paragraph("").page_break_before(true);
809    doc.add_paragraph("Executive Summary").style("Heading1");
810    doc.add_paragraph(
811        "Tensorbee proposes a comprehensive modernization of Global Financial Corp's AI platform, \
812         transitioning from legacy batch-processing systems to a real-time inference architecture. \
813         This transformation will reduce model deployment time from weeks to hours, improve \
814         prediction accuracy by an estimated 23%, and deliver $4.2M in annual operational savings.",
815    )
816    .first_line_indent(Length::inches(0.3));
817    doc.add_paragraph(
818        "The project spans three phases over 18 months, with a total investment of $2.8M. \
819         Tensorbee brings deep expertise in ML infrastructure, having successfully completed \
820         similar transformations for three Fortune 500 financial institutions.",
821    )
822    .first_line_indent(Length::inches(0.3));
823
824    // Key metrics table
825    doc.add_paragraph("");
826    {
827        let mut tbl = doc
828            .add_table(5, 2)
829            .borders(BorderStyle::Single, 4, "1B2A4A")
830            .width_pct(80.0);
831        tbl.cell(0, 0).unwrap().set_text("Key Metric");
832        tbl.cell(0, 0).unwrap().shading("1B2A4A");
833        tbl.cell(0, 1).unwrap().set_text("Value");
834        tbl.cell(0, 1).unwrap().shading("1B2A4A");
835        let rows = [
836            ("Total Investment", "$2.8M"),
837            ("Annual Savings", "$4.2M"),
838            ("ROI (Year 1)", "150%"),
839            ("Timeline", "18 months"),
840        ];
841        for (i, (k, v)) in rows.iter().enumerate() {
842            tbl.cell(i + 1, 0).unwrap().set_text(k);
843            if i % 2 == 0 {
844                tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
845            }
846            tbl.cell(i + 1, 1).unwrap().set_text(v);
847            if i % 2 == 0 {
848                tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
849            }
850        }
851    }
852
853    // ── Problem Statement ──
854    doc.add_paragraph("").page_break_before(true);
855    doc.add_paragraph("Problem Statement").style("Heading1");
856    doc.add_paragraph(
857        "Global Financial Corp's current AI infrastructure faces three critical challenges:",
858    );
859    doc.add_numbered_list_item("Deployment Latency — New models take 3-4 weeks to deploy to production, compared to the industry benchmark of 2-3 days.", 0);
860    doc.add_numbered_list_item("Scalability Constraints — The batch processing architecture cannot handle real-time inference demands during peak trading hours.", 0);
861    doc.add_numbered_list_item("Technical Debt — Legacy Python 2.7 codebases and manual deployment scripts create reliability risks and slow iteration speed.", 0);
862
863    // ── Proposed Solution ──
864    doc.add_paragraph("").page_break_before(true);
865    doc.add_paragraph("Proposed Solution").style("Heading1");
866
867    doc.add_paragraph("Phase 1: Foundation (Months 1-6)")
868        .style("Heading2");
869    doc.add_bullet_list_item("Deploy Kubernetes-based ML serving infrastructure", 0);
870    doc.add_bullet_list_item("Implement CI/CD pipeline for model deployment", 0);
871    doc.add_bullet_list_item("Migrate top 5 production models to new platform", 0);
872
873    doc.add_paragraph("Phase 2: Expansion (Months 7-12)")
874        .style("Heading2");
875    doc.add_bullet_list_item(
876        "Real-time feature engineering pipeline (Apache Kafka + Flink)",
877        0,
878    );
879    doc.add_bullet_list_item("A/B testing framework for model variants", 0);
880    doc.add_bullet_list_item("Automated model monitoring and drift detection", 0);
881
882    doc.add_paragraph("Phase 3: Optimization (Months 13-18)")
883        .style("Heading2");
884    doc.add_bullet_list_item("GPU inference optimization (TensorRT/ONNX Runtime)", 0);
885    doc.add_bullet_list_item("Multi-region deployment for latency reduction", 0);
886    doc.add_bullet_list_item(
887        "Self-service model deployment portal for data science teams",
888        0,
889    );
890
891    // ── Budget ──
892    doc.add_paragraph("Budget Breakdown").style("Heading1");
893    {
894        let mut tbl = doc
895            .add_table(6, 3)
896            .borders(BorderStyle::Single, 4, "1B2A4A")
897            .width_pct(100.0);
898        tbl.cell(0, 0).unwrap().set_text("Category");
899        tbl.cell(0, 0).unwrap().shading("1B2A4A");
900        tbl.cell(0, 1).unwrap().set_text("Cost");
901        tbl.cell(0, 1).unwrap().shading("1B2A4A");
902        tbl.cell(0, 2).unwrap().set_text("Notes");
903        tbl.cell(0, 2).unwrap().shading("1B2A4A");
904        let items = [
905            (
906                "Infrastructure",
907                "$450,000",
908                "Cloud compute + GPU instances",
909            ),
910            ("Engineering Services", "$1,600,000", "12 FTE x 18 months"),
911            (
912                "Software Licenses",
913                "$350,000",
914                "Kafka, monitoring, CI/CD tools",
915            ),
916            (
917                "Training & Enablement",
918                "$200,000",
919                "Team training, documentation",
920            ),
921            ("Contingency (10%)", "$200,000", "Risk buffer"),
922        ];
923        for (i, (cat, cost, note)) in items.iter().enumerate() {
924            tbl.cell(i + 1, 0).unwrap().set_text(cat);
925            tbl.cell(i + 1, 1).unwrap().set_text(cost);
926            tbl.cell(i + 1, 2).unwrap().set_text(note);
927            if i % 2 == 0 {
928                tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
929                tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
930                tbl.cell(i + 1, 2).unwrap().shading("F4F1EB");
931            }
932        }
933    }
934
935    // ── Closing ──
936    doc.add_paragraph("");
937    doc.add_paragraph("Next Steps").style("Heading1");
938    doc.add_numbered_list_item(
939        "Schedule technical deep-dive with GFC engineering team (Week 1)",
940        0,
941    );
942    doc.add_numbered_list_item("Finalize scope and sign SOW (Week 2-3)", 0);
943    doc.add_numbered_list_item("Kick off Phase 1 with joint planning session (Week 4)", 0);
944
945    doc.add_paragraph("");
946    {
947        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
948        p.add_run("Walter White")
949            .bold(true)
950            .size(14.0)
951            .color("1B2A4A");
952    }
953    {
954        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
955        p.add_run("CEO, Tensorbee | walter@tensorbee.com")
956            .size(10.0)
957            .color("888888");
958    }
959
960    doc
961}
962
963// =============================================================================
964// 3. QUOTE / BILL OF MATERIALS — Teal + orange scheme
965// =============================================================================
966fn generate_quote(_samples_dir: &Path) -> Document {
967    let mut doc = Document::new();
968
969    // Colors: Teal #008B8B, Dark #1A3C3C, Orange #E07020, Light #F0F8F8
970    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
971    doc.set_margins(
972        Length::inches(0.75),
973        Length::inches(0.75),
974        Length::inches(0.75),
975        Length::inches(0.75),
976    );
977    doc.set_title("Quotation QT-2026-0147");
978    doc.set_author("Walter White");
979    doc.set_keywords("quote, BOM, Tensorbee");
980
981    doc.set_header("Tensorbee — Quotation");
982    doc.set_footer("QT-2026-0147 | Page");
983
984    // ── Header Block ──
985    {
986        let mut p = doc.add_paragraph("").alignment(Alignment::Left);
987        p.add_run("TENSORBEE")
988            .bold(true)
989            .size(28.0)
990            .color("008B8B")
991            .font("Helvetica");
992    }
993    doc.add_paragraph("123 Innovation Drive, Suite 400")
994        .alignment(Alignment::Left);
995    doc.add_paragraph("San Francisco, CA 94105 | +1 (415) 555-0199")
996        .alignment(Alignment::Left);
997    doc.add_paragraph("")
998        .border_bottom(BorderStyle::Single, 8, "008B8B");
999
1000    // Quote meta
1001    doc.add_paragraph("");
1002    {
1003        let mut tbl = doc.add_table(4, 4).width_pct(100.0);
1004        tbl.cell(0, 0).unwrap().set_text("QUOTATION");
1005        tbl.cell(0, 0).unwrap().shading("008B8B").grid_span(2);
1006        tbl.cell(0, 2).unwrap().set_text("DATE");
1007        tbl.cell(0, 2).unwrap().shading("008B8B");
1008        tbl.cell(0, 3).unwrap().set_text("VALID UNTIL");
1009        tbl.cell(0, 3).unwrap().shading("008B8B");
1010        tbl.cell(1, 0).unwrap().set_text("Quote #:");
1011        tbl.cell(1, 1).unwrap().set_text("QT-2026-0147");
1012        tbl.cell(1, 2).unwrap().set_text("Feb 22, 2026");
1013        tbl.cell(1, 3).unwrap().set_text("Mar 22, 2026");
1014        tbl.cell(2, 0).unwrap().set_text("Prepared by:");
1015        tbl.cell(2, 1).unwrap().set_text("Walter White");
1016        tbl.cell(2, 2).unwrap().set_text("Payment:");
1017        tbl.cell(2, 3).unwrap().set_text("Net 30");
1018        tbl.cell(3, 0).unwrap().set_text("Customer:");
1019        tbl.cell(3, 1).unwrap().set_text("Meridian Dynamics LLC");
1020        tbl.cell(3, 2).unwrap().set_text("Currency:");
1021        tbl.cell(3, 3).unwrap().set_text("USD");
1022    }
1023
1024    doc.add_paragraph("");
1025
1026    // ── Bill of Materials ──
1027    {
1028        let mut p = doc.add_paragraph("");
1029        p.add_run("Bill of Materials")
1030            .bold(true)
1031            .size(16.0)
1032            .color("1A3C3C");
1033    }
1034    doc.add_paragraph("");
1035
1036    {
1037        let mut tbl = doc
1038            .add_table(10, 6)
1039            .borders(BorderStyle::Single, 2, "008B8B")
1040            .width_pct(100.0)
1041            .layout_fixed();
1042        // Headers
1043        let hdrs = [
1044            "#",
1045            "Part Number",
1046            "Description",
1047            "Qty",
1048            "Unit Price",
1049            "Total",
1050        ];
1051        for (c, h) in hdrs.iter().enumerate() {
1052            tbl.cell(0, c).unwrap().set_text(h);
1053            tbl.cell(0, c).unwrap().shading("008B8B");
1054        }
1055
1056        let items: Vec<(&str, &str, &str, &str, &str)> = vec![
1057            (
1058                "TB-GPU-A100",
1059                "NVIDIA A100 80GB GPU",
1060                "4",
1061                "$12,500.00",
1062                "$50,000.00",
1063            ),
1064            (
1065                "TB-SRV-R750",
1066                "Dell R750xa Server Chassis",
1067                "2",
1068                "$8,200.00",
1069                "$16,400.00",
1070            ),
1071            (
1072                "TB-RAM-256G",
1073                "256GB DDR5 ECC Memory Module",
1074                "8",
1075                "$890.00",
1076                "$7,120.00",
1077            ),
1078            (
1079                "TB-SSD-3840",
1080                "3.84TB NVMe U.2 SSD",
1081                "8",
1082                "$1,150.00",
1083                "$9,200.00",
1084            ),
1085            (
1086                "TB-NET-CX7",
1087                "ConnectX-7 200GbE NIC",
1088                "4",
1089                "$1,800.00",
1090                "$7,200.00",
1091            ),
1092            (
1093                "TB-SW-48P",
1094                "48-Port 100GbE Switch",
1095                "1",
1096                "$22,000.00",
1097                "$22,000.00",
1098            ),
1099            (
1100                "TB-CAB-RACK",
1101                "42U Server Rack + PDU",
1102                "1",
1103                "$4,500.00",
1104                "$4,500.00",
1105            ),
1106            (
1107                "TB-SVC-INST",
1108                "Installation & Configuration",
1109                "1",
1110                "$8,500.00",
1111                "$8,500.00",
1112            ),
1113            (
1114                "TB-SVC-SUPP",
1115                "3-Year Premium Support",
1116                "1",
1117                "$15,000.00",
1118                "$15,000.00",
1119            ),
1120        ];
1121
1122        for (i, (pn, desc, qty, unit, total)) in items.iter().enumerate() {
1123            let row = i + 1;
1124            tbl.cell(row, 0).unwrap().set_text(&format!("{}", i + 1));
1125            tbl.cell(row, 1).unwrap().set_text(pn);
1126            tbl.cell(row, 2).unwrap().set_text(desc);
1127            tbl.cell(row, 3).unwrap().set_text(qty);
1128            tbl.cell(row, 4).unwrap().set_text(unit);
1129            tbl.cell(row, 5).unwrap().set_text(total);
1130            if i % 2 == 0 {
1131                for c in 0..6 {
1132                    tbl.cell(row, c).unwrap().shading("F0F8F8");
1133                }
1134            }
1135        }
1136    }
1137
1138    doc.add_paragraph("");
1139
1140    // ── Totals ──
1141    {
1142        let mut tbl = doc
1143            .add_table(4, 2)
1144            .borders(BorderStyle::Single, 2, "008B8B")
1145            .width(Length::inches(3.5))
1146            .alignment(Alignment::Right);
1147        tbl.cell(0, 0).unwrap().set_text("Subtotal");
1148        tbl.cell(0, 1).unwrap().set_text("$139,920.00");
1149        tbl.cell(1, 0).unwrap().set_text("Shipping & Handling");
1150        tbl.cell(1, 1).unwrap().set_text("$2,500.00");
1151        tbl.cell(2, 0).unwrap().set_text("Tax (8.625%)");
1152        tbl.cell(2, 1).unwrap().set_text("$12,068.10");
1153        tbl.cell(3, 0).unwrap().set_text("TOTAL");
1154        tbl.cell(3, 0).unwrap().shading("E07020");
1155        tbl.cell(3, 1).unwrap().set_text("$154,488.10");
1156        tbl.cell(3, 1).unwrap().shading("E07020");
1157    }
1158
1159    doc.add_paragraph("");
1160
1161    // ── Terms & Conditions ──
1162    {
1163        let mut p = doc.add_paragraph("");
1164        p.add_run("Terms & Conditions")
1165            .bold(true)
1166            .size(14.0)
1167            .color("1A3C3C");
1168    }
1169    doc.add_numbered_list_item(
1170        "This quotation is valid for 30 calendar days from the date of issue.",
1171        0,
1172    );
1173    doc.add_numbered_list_item(
1174        "All prices are in USD and exclusive of applicable taxes unless stated.",
1175        0,
1176    );
1177    doc.add_numbered_list_item("Standard lead time is 4-6 weeks from PO receipt.", 0);
1178    doc.add_numbered_list_item(
1179        "Payment terms: Net 30 from invoice date. 2% discount for payment within 10 days.",
1180        0,
1181    );
1182    doc.add_numbered_list_item(
1183        "Warranty: 3-year manufacturer warranty on all hardware components.",
1184        0,
1185    );
1186    doc.add_numbered_list_item(
1187        "Returns subject to 15% restocking fee if initiated after 14 days.",
1188        0,
1189    );
1190
1191    doc.add_paragraph("");
1192    doc.add_paragraph("")
1193        .border_bottom(BorderStyle::Single, 4, "008B8B");
1194    doc.add_paragraph("");
1195    {
1196        let mut p = doc.add_paragraph("");
1197        p.add_run("Accepted by: ").bold(true);
1198        p.add_run("___________________________ Date: ___________");
1199    }
1200    {
1201        let mut p = doc.add_paragraph("");
1202        p.add_run("Print Name: ").bold(true);
1203        p.add_run("___________________________ Title: ___________");
1204    }
1205
1206    doc
1207}
1208
1209// =============================================================================
1210// 4. INVOICE — Crimson + charcoal scheme
1211// =============================================================================
1212fn generate_invoice(_samples_dir: &Path) -> Document {
1213    let mut doc = Document::new();
1214
1215    // Colors: Crimson #B22222, Charcoal #333333, Light #FAF0F0
1216    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1217    doc.set_margins(
1218        Length::inches(0.75),
1219        Length::inches(0.75),
1220        Length::inches(0.75),
1221        Length::inches(0.75),
1222    );
1223    doc.set_title("Invoice INV-2026-0392");
1224    doc.set_author("Walter White");
1225    doc.set_keywords("invoice, Tensorbee");
1226
1227    doc.set_footer("Tensorbee — Thank you for your business!");
1228
1229    // ── Company & Invoice Header ──
1230    {
1231        let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1232        // Company name left, INVOICE right
1233        {
1234            let mut cell = tbl.cell(0, 0).unwrap();
1235            let mut p = cell.add_paragraph("");
1236            p.add_run("TENSORBEE")
1237                .bold(true)
1238                .size(32.0)
1239                .color("B22222")
1240                .font("Helvetica");
1241        }
1242        {
1243            let mut cell = tbl.cell(0, 1).unwrap();
1244            let mut p = cell.add_paragraph("");
1245            p.add_run("INVOICE").bold(true).size(32.0).color("333333");
1246        }
1247        tbl.cell(1, 0)
1248            .unwrap()
1249            .set_text("123 Innovation Drive, Suite 400");
1250        tbl.cell(1, 1).unwrap().set_text("Invoice #: INV-2026-0392");
1251        tbl.cell(2, 0).unwrap().set_text("San Francisco, CA 94105");
1252        tbl.cell(2, 1).unwrap().set_text("Date: February 22, 2026");
1253        tbl.cell(3, 0).unwrap().set_text("walter@tensorbee.com");
1254        tbl.cell(3, 1).unwrap().set_text("Due Date: March 24, 2026");
1255    }
1256
1257    doc.add_paragraph("")
1258        .border_bottom(BorderStyle::Thick, 8, "B22222");
1259    doc.add_paragraph("");
1260
1261    // ── Bill To / Ship To ──
1262    {
1263        let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1264        tbl.cell(0, 0).unwrap().set_text("BILL TO");
1265        tbl.cell(0, 0).unwrap().shading("B22222");
1266        tbl.cell(0, 1).unwrap().set_text("SHIP TO");
1267        tbl.cell(0, 1).unwrap().shading("B22222");
1268        tbl.cell(1, 0).unwrap().set_text("Meridian Dynamics LLC");
1269        tbl.cell(1, 1).unwrap().set_text("Meridian Dynamics LLC");
1270        tbl.cell(2, 0).unwrap().set_text("456 Enterprise Blvd");
1271        tbl.cell(2, 1).unwrap().set_text("Attn: Server Room B");
1272        tbl.cell(3, 0).unwrap().set_text("Austin, TX 78701");
1273        tbl.cell(3, 1)
1274            .unwrap()
1275            .set_text("456 Enterprise Blvd, Austin, TX 78701");
1276    }
1277
1278    doc.add_paragraph("");
1279
1280    // ── Line Items ──
1281    {
1282        let mut tbl = doc
1283            .add_table(8, 5)
1284            .borders(BorderStyle::Single, 2, "B22222")
1285            .width_pct(100.0);
1286        let hdrs = ["Description", "Qty", "Unit Price", "Tax", "Amount"];
1287        for (c, h) in hdrs.iter().enumerate() {
1288            tbl.cell(0, c).unwrap().set_text(h);
1289            tbl.cell(0, c).unwrap().shading("B22222");
1290        }
1291        let items = [
1292            (
1293                "ML Infrastructure Setup — Phase 1",
1294                "1",
1295                "$45,000.00",
1296                "$3,881.25",
1297                "$48,881.25",
1298            ),
1299            (
1300                "Data Pipeline Development (160 hrs)",
1301                "160",
1302                "$225.00",
1303                "$3,105.00",
1304                "$39,105.00",
1305            ),
1306            (
1307                "GPU Cluster Configuration",
1308                "1",
1309                "$12,000.00",
1310                "$1,035.00",
1311                "$13,035.00",
1312            ),
1313            (
1314                "API Gateway Implementation",
1315                "1",
1316                "$18,500.00",
1317                "$1,595.63",
1318                "$20,095.63",
1319            ),
1320            (
1321                "Load Testing & QA (80 hrs)",
1322                "80",
1323                "$195.00",
1324                "$1,345.50",
1325                "$16,945.50",
1326            ),
1327            (
1328                "Documentation & Training",
1329                "1",
1330                "$8,000.00",
1331                "$690.00",
1332                "$8,690.00",
1333            ),
1334            (
1335                "Project Management (3 months)",
1336                "3",
1337                "$6,500.00",
1338                "$1,679.63",
1339                "$21,179.63",
1340            ),
1341        ];
1342        for (i, (desc, qty, unit, tax, amt)) in items.iter().enumerate() {
1343            let r = i + 1;
1344            tbl.cell(r, 0).unwrap().set_text(desc);
1345            tbl.cell(r, 1).unwrap().set_text(qty);
1346            tbl.cell(r, 2).unwrap().set_text(unit);
1347            tbl.cell(r, 3).unwrap().set_text(tax);
1348            tbl.cell(r, 4).unwrap().set_text(amt);
1349            if i % 2 == 0 {
1350                for c in 0..5 {
1351                    tbl.cell(r, c).unwrap().shading("FAF0F0");
1352                }
1353            }
1354        }
1355    }
1356
1357    doc.add_paragraph("");
1358
1359    // ── Totals ──
1360    {
1361        let mut tbl = doc
1362            .add_table(4, 2)
1363            .borders(BorderStyle::Single, 2, "B22222")
1364            .width(Length::inches(3.0))
1365            .alignment(Alignment::Right);
1366        tbl.cell(0, 0).unwrap().set_text("Subtotal");
1367        tbl.cell(0, 1).unwrap().set_text("$154,600.00");
1368        tbl.cell(1, 0).unwrap().set_text("Tax (8.625%)");
1369        tbl.cell(1, 1).unwrap().set_text("$13,334.25");
1370        tbl.cell(2, 0).unwrap().set_text("Discount (5%)");
1371        tbl.cell(2, 1).unwrap().set_text("-$7,730.00");
1372        tbl.cell(3, 0).unwrap().set_text("AMOUNT DUE");
1373        tbl.cell(3, 0).unwrap().shading("B22222");
1374        tbl.cell(3, 1).unwrap().set_text("$160,204.25");
1375        tbl.cell(3, 1).unwrap().shading("B22222");
1376    }
1377
1378    doc.add_paragraph("");
1379    doc.add_paragraph("");
1380
1381    // ── Payment Details ──
1382    {
1383        let mut p = doc.add_paragraph("");
1384        p.add_run("Payment Details")
1385            .bold(true)
1386            .size(14.0)
1387            .color("333333");
1388    }
1389    doc.add_paragraph("Bank: Silicon Valley Bank")
1390        .indent_left(Length::inches(0.3));
1391    doc.add_paragraph("Account: Tensorbee Inc. — 0847-2953-1120")
1392        .indent_left(Length::inches(0.3));
1393    doc.add_paragraph("Routing: 121140399")
1394        .indent_left(Length::inches(0.3));
1395    doc.add_paragraph("Swift: SVBKUS6S")
1396        .indent_left(Length::inches(0.3));
1397
1398    doc.add_paragraph("");
1399
1400    doc.add_paragraph("Please include invoice number INV-2026-0392 in the payment reference.")
1401        .shading("FAF0F0")
1402        .border_all(BorderStyle::Single, 2, "B22222");
1403
1404    doc
1405}
1406
1407// =============================================================================
1408// 5. REPORT — Forest green + earth tones, with images & hierarchical sections
1409// =============================================================================
1410fn generate_report(_samples_dir: &Path) -> Document {
1411    let mut doc = Document::new();
1412
1413    // Colors: Forest #2D5016, Sage #6B8E23, Earth #8B7355, Cream #FFFAF0
1414    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1415    doc.set_margins(
1416        Length::inches(1.0),
1417        Length::inches(1.0),
1418        Length::inches(1.0),
1419        Length::inches(1.0),
1420    );
1421    doc.set_title("Q4 2025 Environmental Impact Report");
1422    doc.set_author("Walter White");
1423    doc.set_subject("Quarterly Environmental Report");
1424    doc.set_keywords("environment, sustainability, report, Tensorbee");
1425
1426    doc.set_different_first_page(true);
1427    doc.set_first_page_header("");
1428    doc.set_header("Tensorbee — Q4 2025 Environmental Impact Report");
1429    doc.set_footer("CONFIDENTIAL — Page");
1430
1431    // ── Cover ──
1432    let cover_bg = create_sample_png(612, 792, [20, 50, 15]);
1433    doc.add_background_image(&cover_bg, "report_cover.png");
1434
1435    for _ in 0..5 {
1436        doc.add_paragraph("");
1437    }
1438    {
1439        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1440        p.add_run("Q4 2025")
1441            .bold(true)
1442            .size(48.0)
1443            .color("FFFFFF")
1444            .font("Georgia");
1445    }
1446    {
1447        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1448        p.add_run("Environmental Impact Report")
1449            .size(24.0)
1450            .color("90EE90")
1451            .font("Georgia")
1452            .italic(true);
1453    }
1454    doc.add_paragraph("");
1455    {
1456        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1457        p.add_run("Tensorbee — Sustainability Division")
1458            .size(14.0)
1459            .color("C0C0C0");
1460    }
1461    {
1462        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1463        p.add_run("Prepared by Walter White, Chief Sustainability Officer")
1464            .size(11.0)
1465            .color("AAAAAA");
1466    }
1467
1468    // ── TOC ──
1469    doc.add_paragraph("").page_break_before(true);
1470    doc.insert_toc(doc.content_count(), 3);
1471
1472    // ── Section 1: Executive Overview ──
1473    doc.add_paragraph("").page_break_before(true);
1474    doc.add_paragraph("Executive Overview").style("Heading1");
1475    doc.add_paragraph(
1476        "This report presents Tensorbee's environmental performance for Q4 2025. Our \
1477         sustainability initiatives have yielded a 34% reduction in carbon emissions compared \
1478         to Q4 2024, exceeding our target of 25%. Key achievements include the transition to \
1479         100% renewable energy in our primary data centers and the launch of our carbon offset \
1480         marketplace.",
1481    )
1482    .first_line_indent(Length::inches(0.3));
1483
1484    // Image: performance chart
1485    let chart_img = create_chart_png(400, 200);
1486    doc.add_paragraph("");
1487    doc.add_picture(
1488        &chart_img,
1489        "performance_chart.png",
1490        Length::inches(5.0),
1491        Length::inches(2.5),
1492    )
1493    .alignment(Alignment::Center);
1494    {
1495        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1496        p.add_run("Figure 1: Quarterly Carbon Emissions (tonnes CO2e)")
1497            .italic(true)
1498            .size(9.0)
1499            .color("666666");
1500    }
1501
1502    // ── Section 2: Energy Consumption ──
1503    doc.add_paragraph("").page_break_before(true);
1504    doc.add_paragraph("Energy Consumption").style("Heading1");
1505
1506    doc.add_paragraph("Data Center Operations")
1507        .style("Heading2");
1508    doc.add_paragraph(
1509        "Our three primary data centers consumed a combined 4.2 GWh in Q4 2025, \
1510         a 12% reduction from Q3 2025 driven by improved cooling efficiency and \
1511         server consolidation.",
1512    );
1513
1514    // Data table
1515    {
1516        let mut tbl = doc
1517            .add_table(5, 4)
1518            .borders(BorderStyle::Single, 4, "2D5016")
1519            .width_pct(100.0);
1520        let hdrs = ["Data Center", "Capacity (MW)", "Usage (GWh)", "PUE"];
1521        for (c, h) in hdrs.iter().enumerate() {
1522            tbl.cell(0, c).unwrap().set_text(h);
1523            tbl.cell(0, c).unwrap().shading("2D5016");
1524        }
1525        let rows = [
1526            ("San Francisco (Primary)", "3.2", "1.8", "1.12"),
1527            ("Dublin (EU)", "2.1", "1.2", "1.18"),
1528            ("Singapore (APAC)", "1.8", "1.2", "1.24"),
1529            ("TOTAL", "7.1", "4.2", "1.17 avg"),
1530        ];
1531        for (i, (dc, cap, use_, pue)) in rows.iter().enumerate() {
1532            tbl.cell(i + 1, 0).unwrap().set_text(dc);
1533            tbl.cell(i + 1, 1).unwrap().set_text(cap);
1534            tbl.cell(i + 1, 2).unwrap().set_text(use_);
1535            tbl.cell(i + 1, 3).unwrap().set_text(pue);
1536            if i == 3 {
1537                for c in 0..4 {
1538                    tbl.cell(i + 1, c).unwrap().shading("E2EFDA");
1539                }
1540            }
1541        }
1542    }
1543
1544    doc.add_paragraph("");
1545
1546    doc.add_paragraph("Renewable Energy Mix").style("Heading2");
1547    doc.add_paragraph("Breakdown of energy sources across all facilities:");
1548
1549    doc.add_bullet_list_item("Solar PV: 42% (1.76 GWh)", 0);
1550    doc.add_bullet_list_item("Wind Power Purchase Agreements: 38% (1.60 GWh)", 0);
1551    doc.add_bullet_list_item("Hydroelectric: 12% (0.50 GWh)", 0);
1552    doc.add_bullet_list_item("Grid (non-renewable): 8% (0.34 GWh)", 0);
1553
1554    // ── Section 3: Water & Waste ──
1555    doc.add_paragraph("Water & Waste Management")
1556        .style("Heading1");
1557
1558    doc.add_paragraph("Water Usage").style("Heading2");
1559    doc.add_paragraph(
1560        "Total water consumption was 12.4 million gallons, a 15% reduction from Q3. \
1561         Our closed-loop cooling systems now recycle 78% of water used in cooling operations.",
1562    );
1563
1564    doc.add_paragraph("Waste Reduction").style("Heading2");
1565    doc.add_paragraph("E-waste management results for Q4:")
1566        .keep_with_next(true);
1567    {
1568        let mut tbl = doc
1569            .add_table(4, 3)
1570            .borders(BorderStyle::Single, 2, "6B8E23")
1571            .width_pct(80.0);
1572        tbl.cell(0, 0).unwrap().set_text("Category");
1573        tbl.cell(0, 0).unwrap().shading("6B8E23");
1574        tbl.cell(0, 1).unwrap().set_text("Weight (kg)");
1575        tbl.cell(0, 1).unwrap().shading("6B8E23");
1576        tbl.cell(0, 2).unwrap().set_text("Recycled %");
1577        tbl.cell(0, 2).unwrap().shading("6B8E23");
1578        let rows = [
1579            ("Server Hardware", "2,450", "94%"),
1580            ("Networking Equipment", "820", "91%"),
1581            ("Storage Media", "340", "99%"),
1582        ];
1583        for (i, (cat, wt, pct)) in rows.iter().enumerate() {
1584            tbl.cell(i + 1, 0).unwrap().set_text(cat);
1585            tbl.cell(i + 1, 1).unwrap().set_text(wt);
1586            tbl.cell(i + 1, 2).unwrap().set_text(pct);
1587        }
1588    }
1589
1590    // ── Section 4: Initiatives ──
1591    doc.add_paragraph("").page_break_before(true);
1592    doc.add_paragraph("Q1 2026 Initiatives").style("Heading1");
1593
1594    doc.add_paragraph("Planned Programs").style("Heading2");
1595    doc.add_numbered_list_item(
1596        "Deploy on-site battery storage at SF data center (2 MWh capacity)",
1597        0,
1598    );
1599    doc.add_numbered_list_item("Pilot immersion cooling in Dublin facility", 0);
1600    doc.add_numbered_list_item("Launch employee commute carbon offset program", 0);
1601    doc.add_numbered_list_item("Achieve ISO 14001 certification for Singapore facility", 0);
1602
1603    doc.add_paragraph("Investment Targets").style("Heading2");
1604    doc.add_paragraph("Sustainability CapEx allocation for FY2026:")
1605        .keep_with_next(true);
1606    {
1607        let mut tbl = doc
1608            .add_table(5, 2)
1609            .borders(BorderStyle::Single, 4, "2D5016");
1610        tbl.cell(0, 0).unwrap().set_text("Initiative");
1611        tbl.cell(0, 0).unwrap().shading("2D5016");
1612        tbl.cell(0, 1).unwrap().set_text("Budget");
1613        tbl.cell(0, 1).unwrap().shading("2D5016");
1614        let rows = [
1615            ("Battery Storage", "$1.2M"),
1616            ("Immersion Cooling Pilot", "$800K"),
1617            ("Solar Panel Expansion", "$2.1M"),
1618            ("Carbon Credits", "$500K"),
1619        ];
1620        for (i, (init, budget)) in rows.iter().enumerate() {
1621            tbl.cell(i + 1, 0).unwrap().set_text(init);
1622            tbl.cell(i + 1, 1).unwrap().set_text(budget);
1623            if i % 2 == 0 {
1624                tbl.cell(i + 1, 0).unwrap().shading("FFFAF0");
1625                tbl.cell(i + 1, 1).unwrap().shading("FFFAF0");
1626            }
1627        }
1628    }
1629
1630    // Image: sustainability roadmap
1631    let roadmap_img = create_sample_png(500, 100, [40, 80, 30]);
1632    doc.add_paragraph("");
1633    doc.add_picture(
1634        &roadmap_img,
1635        "roadmap.png",
1636        Length::inches(6.0),
1637        Length::inches(1.2),
1638    )
1639    .alignment(Alignment::Center);
1640    {
1641        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1642        p.add_run("Figure 2: 2026 Sustainability Roadmap")
1643            .italic(true)
1644            .size(9.0)
1645            .color("666666");
1646    }
1647
1648    doc.add_paragraph("");
1649    doc.add_paragraph(
1650        "For questions about this report, contact Walter White at sustainability@tensorbee.com.",
1651    )
1652    .shading("FFFAF0")
1653    .border_all(BorderStyle::Single, 2, "2D5016");
1654
1655    doc
1656}
1657
1658// =============================================================================
1659// 6. LETTER — Slate blue + silver scheme
1660// =============================================================================
1661fn generate_letter(_samples_dir: &Path) -> Document {
1662    let mut doc = Document::new();
1663
1664    // Colors: Slate #4A5568, Blue accent #3182CE
1665    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1666    doc.set_margins(
1667        Length::inches(1.25),
1668        Length::inches(1.25),
1669        Length::inches(1.25),
1670        Length::inches(1.25),
1671    );
1672    doc.set_title("Business Letter");
1673    doc.set_author("Walter White");
1674
1675    // Banner header using raw XML
1676    let logo_img = create_logo_png(220, 48);
1677    let banner = build_header_banner_xml(
1678        "rId1",
1679        &BannerOpts {
1680            bg_color: "4A5568",
1681            banner_width: 7772400,
1682            banner_height: 731520, // ~0.8"
1683            logo_width: 2011680,
1684            logo_height: 438912,
1685            logo_x_offset: 295125,
1686            logo_y_offset: 146304,
1687        },
1688    );
1689    doc.set_raw_header_with_images(
1690        banner,
1691        &[("rId1", &logo_img, "logo.png")],
1692        rdocx_oxml::header_footer::HdrFtrType::Default,
1693    );
1694    doc.set_margins(
1695        Length::twips(2000),
1696        Length::twips(1800),
1697        Length::twips(1440),
1698        Length::twips(1800),
1699    );
1700    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
1701
1702    // Footer with contact
1703    doc.set_footer(
1704        "Tensorbee | 123 Innovation Drive, Suite 400 | San Francisco, CA 94105 | tensorbee.com",
1705    );
1706
1707    // ── Sender Address ──
1708    doc.add_paragraph("Walter White");
1709    doc.add_paragraph("Chief Executive Officer");
1710    doc.add_paragraph("Tensorbee");
1711    doc.add_paragraph("123 Innovation Drive, Suite 400");
1712    doc.add_paragraph("San Francisco, CA 94105");
1713    doc.add_paragraph("");
1714    doc.add_paragraph("February 22, 2026");
1715
1716    doc.add_paragraph("");
1717
1718    // ── Recipient ──
1719    doc.add_paragraph("Ms. Sarah Chen");
1720    doc.add_paragraph("VP of Engineering");
1721    doc.add_paragraph("Meridian Dynamics LLC");
1722    doc.add_paragraph("456 Enterprise Boulevard");
1723    doc.add_paragraph("Austin, TX 78701");
1724
1725    doc.add_paragraph("");
1726
1727    // ── Subject ──
1728    {
1729        let mut p = doc.add_paragraph("");
1730        p.add_run("Re: Strategic Technology Partnership — Phase 2 Expansion")
1731            .bold(true);
1732    }
1733
1734    doc.add_paragraph("");
1735
1736    // ── Body ──
1737    doc.add_paragraph("Dear Ms. Chen,");
1738    doc.add_paragraph("");
1739
1740    doc.add_paragraph(
1741        "I am writing to express Tensorbee's enthusiasm for expanding our strategic technology \
1742         partnership with Meridian Dynamics. Following the successful completion of Phase 1, \
1743         which delivered a 40% improvement in your ML inference pipeline throughput, we believe \
1744         the foundation is firmly established for an ambitious Phase 2 engagement.",
1745    )
1746    .first_line_indent(Length::inches(0.5));
1747
1748    doc.add_paragraph(
1749        "During our recent executive review, your team highlighted three priority areas for \
1750         the next phase of collaboration:",
1751    )
1752    .first_line_indent(Length::inches(0.5));
1753
1754    doc.add_numbered_list_item(
1755        "Real-time anomaly detection for your financial transaction monitoring system, \
1756         targeting sub-100ms latency at 50,000 transactions per second.",
1757        0,
1758    );
1759    doc.add_numbered_list_item(
1760        "Federated learning infrastructure to enable model training across your distributed \
1761         data centers without centralizing sensitive financial data.",
1762        0,
1763    );
1764    doc.add_numbered_list_item(
1765        "MLOps automation to reduce your model deployment cycle from the current 5 days \
1766         to under 4 hours.",
1767        0,
1768    );
1769
1770    doc.add_paragraph(
1771        "Our engineering team has prepared a detailed technical proposal addressing each of \
1772         these areas. We have allocated a dedicated team of eight senior engineers, led by \
1773         our CTO, to ensure continuity with the Phase 1 team your organization has already \
1774         built a productive working relationship with.",
1775    )
1776    .first_line_indent(Length::inches(0.5));
1777
1778    doc.add_paragraph(
1779        "I would welcome the opportunity to present our Phase 2 proposal in person. My \
1780         assistant will reach out to coordinate a meeting at your convenience during the \
1781         first week of March.",
1782    )
1783    .first_line_indent(Length::inches(0.5));
1784
1785    doc.add_paragraph("");
1786
1787    doc.add_paragraph("Warm regards,");
1788    doc.add_paragraph("");
1789    doc.add_paragraph("");
1790
1791    // Signature line
1792    doc.add_paragraph("")
1793        .border_bottom(BorderStyle::Single, 4, "4A5568");
1794    {
1795        let mut p = doc.add_paragraph("");
1796        p.add_run("Walter White").bold(true).size(12.0);
1797    }
1798    {
1799        let mut p = doc.add_paragraph("");
1800        p.add_run("Chief Executive Officer, Tensorbee")
1801            .italic(true)
1802            .size(10.0)
1803            .color("666666");
1804    }
1805    {
1806        let mut p = doc.add_paragraph("");
1807        p.add_run("walter@tensorbee.com | +1 (415) 555-0199")
1808            .size(10.0)
1809            .color("888888");
1810    }
1811
1812    doc
1813}
1814
1815// =============================================================================
1816// 7. EMPLOYMENT CONTRACT — Purple + charcoal scheme
1817// =============================================================================
1818fn generate_contract(_samples_dir: &Path) -> Document {
1819    let mut doc = Document::new();
1820
1821    // Colors: Purple #5B2C6F, Plum #8E44AD, Gray #2C3E50
1822    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1823    doc.set_margins(
1824        Length::inches(1.25),
1825        Length::inches(1.0),
1826        Length::inches(1.0),
1827        Length::inches(1.0),
1828    );
1829    doc.set_title("Employment Agreement");
1830    doc.set_author("Tensorbee HR Department");
1831    doc.set_subject("Employment Contract — Walter White");
1832    doc.set_keywords("employment, contract, agreement, Tensorbee");
1833
1834    doc.set_header("TENSORBEE — Employment Agreement");
1835    doc.set_footer("Employment Agreement — Walter White — Page");
1836
1837    // ── Title Block ──
1838    doc.add_paragraph("")
1839        .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1840    {
1841        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1842        p.add_run("EMPLOYMENT AGREEMENT")
1843            .bold(true)
1844            .size(24.0)
1845            .color("5B2C6F")
1846            .font("Georgia");
1847    }
1848    doc.add_paragraph("")
1849        .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1850    doc.add_paragraph("");
1851
1852    // ── Parties ──
1853    doc.add_paragraph(
1854        "This Employment Agreement (\"Agreement\") is entered into as of February 22, 2026 \
1855         (\"Effective Date\"), by and between:",
1856    );
1857
1858    doc.add_paragraph("");
1859
1860    {
1861        let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1862        p.add_run("EMPLOYER: ").bold(true);
1863        p.add_run(
1864            "Tensorbee, Inc., a Delaware corporation with its principal place of business at \
1865                   123 Innovation Drive, Suite 400, San Francisco, CA 94105 (\"Company\")",
1866        );
1867    }
1868    doc.add_paragraph("");
1869    {
1870        let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1871        p.add_run("EMPLOYEE: ").bold(true);
1872        p.add_run(
1873            "Walter White, residing at 308 Negra Arroyo Lane, Albuquerque, NM 87104 (\"Employee\")",
1874        );
1875    }
1876
1877    doc.add_paragraph("");
1878    doc.add_paragraph("The Company and Employee are collectively referred to as the \"Parties.\"");
1879    doc.add_paragraph("");
1880
1881    // ── Article 1: Position and Duties ──
1882    doc.add_paragraph("Article 1 — Position and Duties")
1883        .style("Heading1");
1884    {
1885        let mut p = doc.add_paragraph("");
1886        p.add_run("1.1 ").bold(true);
1887        p.add_run("The Company hereby employs the Employee as ");
1888        p.add_run("Chief Executive Officer (CEO)")
1889            .bold(true)
1890            .italic(true);
1891        p.add_run(", reporting directly to the Board of Directors.");
1892    }
1893    {
1894        let mut p = doc.add_paragraph("");
1895        p.add_run("1.2 ").bold(true);
1896        p.add_run("The Employee shall devote full working time, attention, and best efforts to \
1897                   the performance of duties as reasonably assigned by the Board, including but not limited to:");
1898    }
1899    doc.add_bullet_list_item(
1900        "Setting and executing the Company's strategic vision and business plan",
1901        0,
1902    );
1903    doc.add_bullet_list_item(
1904        "Overseeing all operations, engineering, and commercial activities",
1905        0,
1906    );
1907    doc.add_bullet_list_item(
1908        "Representing the Company to investors, customers, and the public",
1909        0,
1910    );
1911    doc.add_bullet_list_item("Recruiting, developing, and retaining key talent", 0);
1912
1913    {
1914        let mut p = doc.add_paragraph("");
1915        p.add_run("1.3 ").bold(true);
1916        p.add_run(
1917            "The Employee's primary work location shall be the Company's San Francisco \
1918                   headquarters, with reasonable travel as required by business needs.",
1919        );
1920    }
1921
1922    // ── Article 2: Compensation ──
1923    doc.add_paragraph("Article 2 — Compensation and Benefits")
1924        .style("Heading1");
1925    {
1926        let mut p = doc.add_paragraph("");
1927        p.add_run("2.1 Base Salary. ").bold(true);
1928        p.add_run("The Company shall pay the Employee an annual base salary of ");
1929        p.add_run("$375,000.00").bold(true);
1930        p.add_run(" (Three Hundred Seventy-Five Thousand Dollars), payable in accordance with the \
1931                   Company's standard payroll schedule, less applicable withholdings and deductions.");
1932    }
1933    {
1934        let mut p = doc.add_paragraph("");
1935        p.add_run("2.2 Annual Bonus. ").bold(true);
1936        p.add_run("The Employee shall be eligible for an annual performance bonus of up to ");
1937        p.add_run("40%").bold(true);
1938        p.add_run(
1939            " of base salary, based on achievement of mutually agreed performance objectives.",
1940        );
1941    }
1942    {
1943        let mut p = doc.add_paragraph("");
1944        p.add_run("2.3 Equity. ").bold(true);
1945        p.add_run("Subject to Board approval, the Employee shall receive a stock option grant of ");
1946        p.add_run("500,000 shares").bold(true);
1947        p.add_run(
1948            " of the Company's common stock, vesting over four (4) years with a one-year cliff, \
1949                   at an exercise price equal to the fair market value on the date of grant.",
1950        );
1951    }
1952
1953    // Compensation summary table
1954    doc.add_paragraph("");
1955    {
1956        let mut tbl = doc
1957            .add_table(5, 2)
1958            .borders(BorderStyle::Single, 4, "5B2C6F")
1959            .width_pct(70.0)
1960            .alignment(Alignment::Center);
1961        tbl.cell(0, 0).unwrap().set_text("Compensation Element");
1962        tbl.cell(0, 0).unwrap().shading("5B2C6F");
1963        tbl.cell(0, 1).unwrap().set_text("Value");
1964        tbl.cell(0, 1).unwrap().shading("5B2C6F");
1965        let items = [
1966            ("Base Salary", "$375,000/year"),
1967            ("Target Bonus", "Up to 40% ($150,000)"),
1968            ("Equity Grant", "500,000 shares (4yr vest)"),
1969            ("Total Target Comp", "$525,000 + equity"),
1970        ];
1971        for (i, (elem, val)) in items.iter().enumerate() {
1972            tbl.cell(i + 1, 0).unwrap().set_text(elem);
1973            tbl.cell(i + 1, 1).unwrap().set_text(val);
1974            if i == 3 {
1975                tbl.cell(i + 1, 0).unwrap().shading("E8DAEF");
1976                tbl.cell(i + 1, 1).unwrap().shading("E8DAEF");
1977            }
1978        }
1979    }
1980
1981    // ── Article 3: Benefits ──
1982    doc.add_paragraph("").page_break_before(true);
1983    doc.add_paragraph("Article 3 — Benefits").style("Heading1");
1984    {
1985        let mut p = doc.add_paragraph("");
1986        p.add_run("3.1 ").bold(true);
1987        p.add_run(
1988            "The Employee shall be entitled to participate in all benefit programs \
1989                   generally available to senior executives, including:",
1990        );
1991    }
1992    doc.add_bullet_list_item(
1993        "Medical, dental, and vision insurance (100% premium coverage for employee and dependents)",
1994        0,
1995    );
1996    doc.add_bullet_list_item("401(k) retirement plan with 6% company match", 0);
1997    doc.add_bullet_list_item("Life insurance and long-term disability coverage", 0);
1998    doc.add_bullet_list_item("Annual professional development allowance of $10,000", 0);
1999
2000    {
2001        let mut p = doc.add_paragraph("");
2002        p.add_run("3.2 Paid Time Off. ").bold(true);
2003        p.add_run(
2004            "The Employee shall receive 25 days of paid vacation per year, plus Company holidays, \
2005                   accruing on a monthly basis.",
2006        );
2007    }
2008
2009    // ── Article 4: Term and Termination ──
2010    doc.add_paragraph("Article 4 — Term and Termination")
2011        .style("Heading1");
2012    {
2013        let mut p = doc.add_paragraph("");
2014        p.add_run("4.1 At-Will Employment. ").bold(true);
2015        p.add_run(
2016            "This Agreement is for at-will employment and may be terminated by either Party \
2017                   at any time, with or without cause, subject to the notice provisions herein.",
2018        );
2019    }
2020    {
2021        let mut p = doc.add_paragraph("");
2022        p.add_run("4.2 Notice Period. ").bold(true);
2023        p.add_run("Either Party shall provide ");
2024        p.add_run("ninety (90) days").bold(true);
2025        p.add_run(" written notice of termination, or pay in lieu thereof.");
2026    }
2027    {
2028        let mut p = doc.add_paragraph("");
2029        p.add_run("4.3 Severance. ").bold(true);
2030        p.add_run("In the event of termination by the Company without Cause, the Employee shall \
2031                   receive (a) twelve (12) months of base salary continuation, (b) pro-rata bonus \
2032                   for the year of termination, and (c) twelve (12) months of COBRA premium coverage.");
2033    }
2034
2035    // ── Article 5: Confidentiality ──
2036    doc.add_paragraph("Article 5 — Confidentiality and IP")
2037        .style("Heading1");
2038    {
2039        let mut p = doc.add_paragraph("");
2040        p.add_run("5.1 ").bold(true);
2041        p.add_run(
2042            "The Employee agrees to maintain strict confidentiality of all proprietary \
2043                   information, trade secrets, and business plans of the Company during and after \
2044                   employment.",
2045        );
2046    }
2047    {
2048        let mut p = doc.add_paragraph("");
2049        p.add_run("5.2 ").bold(true);
2050        p.add_run(
2051            "All intellectual property, inventions, and works of authorship created by the \
2052                   Employee during the term of employment and within the scope of duties shall be \
2053                   the sole and exclusive property of the Company.",
2054        );
2055    }
2056
2057    // ── Article 6: Non-Compete ──
2058    doc.add_paragraph("Article 6 — Non-Competition")
2059        .style("Heading1");
2060    {
2061        let mut p = doc.add_paragraph("");
2062        p.add_run("6.1 ").bold(true);
2063        p.add_run("For a period of twelve (12) months following termination, the Employee agrees \
2064                   not to engage in any business that directly competes with the Company's core \
2065                   business of AI infrastructure and ML operations services, within the United States.");
2066    }
2067
2068    // ── Article 7: General Provisions ──
2069    doc.add_paragraph("Article 7 — General Provisions")
2070        .style("Heading1");
2071    {
2072        let mut p = doc.add_paragraph("");
2073        p.add_run("7.1 Governing Law. ").bold(true);
2074        p.add_run(
2075            "This Agreement shall be governed by and construed in accordance with the laws of \
2076                   the State of California.",
2077        );
2078    }
2079    {
2080        let mut p = doc.add_paragraph("");
2081        p.add_run("7.2 Entire Agreement. ").bold(true);
2082        p.add_run(
2083            "This Agreement constitutes the entire agreement between the Parties and supersedes \
2084                   all prior negotiations, representations, and agreements.",
2085        );
2086    }
2087    {
2088        let mut p = doc.add_paragraph("");
2089        p.add_run("7.3 Amendment. ").bold(true);
2090        p.add_run(
2091            "This Agreement may only be amended by a written instrument signed by both Parties.",
2092        );
2093    }
2094
2095    // ── Signature Block ──
2096    doc.add_paragraph("").page_break_before(true);
2097    doc.add_paragraph("IN WITNESS WHEREOF, the Parties have executed this Employment Agreement as of the Effective Date.")
2098        .space_after(Length::pt(24.0));
2099
2100    // Signature table
2101    {
2102        let mut tbl = doc.add_table(6, 2).width_pct(100.0);
2103        tbl.cell(0, 0).unwrap().set_text("FOR THE COMPANY:");
2104        tbl.cell(0, 0).unwrap().shading("5B2C6F");
2105        tbl.cell(0, 1).unwrap().set_text("EMPLOYEE:");
2106        tbl.cell(0, 1).unwrap().shading("5B2C6F");
2107
2108        tbl.cell(1, 0).unwrap().set_text("");
2109        tbl.cell(1, 1).unwrap().set_text("");
2110        tbl.row(1).unwrap().height(Length::pt(40.0));
2111
2112        tbl.cell(2, 0)
2113            .unwrap()
2114            .set_text("Signature: ___________________________");
2115        tbl.cell(2, 1)
2116            .unwrap()
2117            .set_text("Signature: ___________________________");
2118        tbl.cell(3, 0)
2119            .unwrap()
2120            .set_text("Name: Board Representative");
2121        tbl.cell(3, 1).unwrap().set_text("Name: Walter White");
2122        tbl.cell(4, 0)
2123            .unwrap()
2124            .set_text("Title: Chair, Board of Directors");
2125        tbl.cell(4, 1)
2126            .unwrap()
2127            .set_text("Title: Chief Executive Officer");
2128        tbl.cell(5, 0).unwrap().set_text("Date: _______________");
2129        tbl.cell(5, 1).unwrap().set_text("Date: _______________");
2130    }
2131
2132    doc
2133}
Source

pub fn open<P: AsRef<Path>>(path: P) -> Result<Self>

Open a document from a file path.

Examples found in repository?
examples/convert_html_md.rs (line 4)
3fn main() {
4    let doc = Document::open("samples/feature_showcase.docx").expect("Failed to open document");
5
6    let html = doc.to_html();
7    std::fs::write("/tmp/feature_showcase.html", &html).expect("Failed to write HTML");
8    println!("HTML: {} bytes -> /tmp/feature_showcase.html", html.len());
9
10    let md = doc.to_markdown();
11    std::fs::write("/tmp/feature_showcase.md", &md).expect("Failed to write Markdown");
12    println!("Markdown: {} bytes -> /tmp/feature_showcase.md", md.len());
13
14    // Simple document
15    let mut simple = Document::new();
16    simple.add_paragraph("Hello, World!");
17    let html = simple.to_html();
18    println!("\n--- Simple HTML ---\n{html}");
19
20    let md = simple.to_markdown();
21    println!("--- Simple Markdown ---\n{md}");
22}
More examples
Hide additional examples
examples/generate_pdf.rs (line 60)
7fn main() {
8    // Test 1: Simple document
9    let doc = Document::new();
10    match doc.to_pdf() {
11        Ok(bytes) => {
12            std::fs::write("/tmp/rdocx_simple.pdf", &bytes).unwrap();
13            println!("Simple PDF: {} bytes -> /tmp/rdocx_simple.pdf", bytes.len());
14        }
15        Err(e) => println!("Simple PDF failed: {e}"),
16    }
17
18    // Test 2: Document with content
19    let mut doc = Document::new();
20    doc.set_title("Test PDF Document");
21    doc.set_author("rdocx-pdf");
22    doc.add_paragraph("Chapter 1: Introduction")
23        .style("Heading1");
24    doc.add_paragraph(
25        "This is a test document generated by rdocx and rendered to PDF. \
26         It demonstrates text rendering with proper font shaping and pagination.",
27    );
28    doc.add_paragraph("Section 1.1").style("Heading2");
29    doc.add_paragraph("More content in a sub-section.");
30
31    {
32        let mut table = doc.add_table(2, 3);
33        for r in 0..2 {
34            for c in 0..3 {
35                if let Some(mut cell) = table.cell(r, c) {
36                    cell.set_text(&format!("R{}C{}", r + 1, c + 1));
37                }
38            }
39        }
40    }
41
42    doc.add_paragraph("After the table.");
43
44    match doc.to_pdf() {
45        Ok(bytes) => {
46            std::fs::write("/tmp/rdocx_content.pdf", &bytes).unwrap();
47            println!(
48                "Content PDF: {} bytes -> /tmp/rdocx_content.pdf",
49                bytes.len()
50            );
51        }
52        Err(e) => println!("Content PDF failed: {e}"),
53    }
54
55    // Test 3: From feature_showcase.docx
56    let showcase_path = concat!(
57        env!("CARGO_MANIFEST_DIR"),
58        "/../../samples/feature_showcase.docx"
59    );
60    match Document::open(showcase_path) {
61        Ok(doc) => match doc.to_pdf() {
62            Ok(bytes) => {
63                std::fs::write("/tmp/rdocx_showcase.pdf", &bytes).unwrap();
64                println!(
65                    "Showcase PDF: {} bytes -> /tmp/rdocx_showcase.pdf",
66                    bytes.len()
67                );
68            }
69            Err(e) => println!("Showcase PDF failed: {e}"),
70        },
71        Err(e) => println!("Failed to open showcase: {e}"),
72    }
73}
examples/template_replace.rs (line 163)
162fn fill_template(template_path: &Path, output_path: &Path) {
163    let mut doc = Document::open(template_path).unwrap();
164
165    // ── Batch replacement ──
166    let mut replacements = HashMap::new();
167    replacements.insert("{{company_name}}", "Riverside Medical Center");
168    replacements.insert("{{project_name}}", "Network Security Upgrade");
169    replacements.insert("{{contact_name}}", "Dr. Sarah Chen");
170    replacements.insert("{{contact_email}}", "s.chen@riverside.org");
171    replacements.insert("{{start_date}}", "March 1, 2026");
172    replacements.insert("{{duration}}", "12 weeks");
173    replacements.insert("{{budget}}", "$185,000");
174    replacements.insert("{{status}}", "Pending Approval");
175    replacements.insert("{{author_name}}", "James Wilson");
176    replacements.insert("{{date}}", "February 22, 2026");
177
178    // Team members
179    replacements.insert("{{member1_name}}", "James Wilson");
180    replacements.insert("{{member1_role}}", "Project Lead");
181    replacements.insert("{{member1_email}}", "j.wilson@provider.com");
182    replacements.insert("{{member2_name}}", "Maria Garcia");
183    replacements.insert("{{member2_role}}", "Security Architect");
184    replacements.insert("{{member2_email}}", "m.garcia@provider.com");
185    replacements.insert("{{member3_name}}", "David Park");
186    replacements.insert("{{member3_role}}", "Network Engineer");
187    replacements.insert("{{member3_email}}", "d.park@provider.com");
188
189    let count = doc.replace_all(&replacements);
190    println!("  Replaced {} placeholders", count);
191
192    // ── Insert deliverables at the insertion point ──
193    if let Some(idx) = doc.find_content_index("INSERTION_POINT") {
194        // Remove the placeholder paragraph
195        doc.remove_content(idx);
196
197        // Insert deliverables list
198        doc.insert_paragraph(idx, "The following deliverables are included:");
199
200        // Insert a deliverables table
201        let mut tbl = doc.insert_table(idx + 1, 5, 3);
202        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
203
204        for col in 0..3 {
205            tbl.cell(0, col).unwrap().shading("E2EFDA");
206        }
207        tbl.cell(0, 0).unwrap().set_text("Phase");
208        tbl.cell(0, 1).unwrap().set_text("Description");
209        tbl.cell(0, 2).unwrap().set_text("Timeline");
210
211        tbl.cell(1, 0).unwrap().set_text("1. Discovery");
212        tbl.cell(1, 1)
213            .unwrap()
214            .set_text("Network assessment and asset inventory");
215        tbl.cell(1, 2).unwrap().set_text("Weeks 1-3");
216
217        tbl.cell(2, 0).unwrap().set_text("2. Design");
218        tbl.cell(2, 1)
219            .unwrap()
220            .set_text("Security architecture and policy design");
221        tbl.cell(2, 2).unwrap().set_text("Weeks 4-6");
222
223        tbl.cell(3, 0).unwrap().set_text("3. Implementation");
224        tbl.cell(3, 1)
225            .unwrap()
226            .set_text("Deploy monitoring and access controls");
227        tbl.cell(3, 2).unwrap().set_text("Weeks 7-10");
228
229        tbl.cell(4, 0).unwrap().set_text("4. Validation");
230        tbl.cell(4, 1)
231            .unwrap()
232            .set_text("Testing, training, and handover");
233        tbl.cell(4, 2).unwrap().set_text("Weeks 11-12");
234
235        println!("  Inserted deliverables table at position {}", idx);
236    }
237
238    // ── Update metadata ──
239    doc.set_title("Riverside Medical Center — Network Security Upgrade Proposal");
240    doc.set_author("James Wilson");
241    doc.set_subject("Project Proposal");
242    doc.set_keywords("security, network, medical, proposal");
243
244    doc.save(output_path).unwrap();
245}
Source

pub fn from_bytes(bytes: &[u8]) -> Result<Self>

Open a document from bytes.

Source

pub fn save<P: AsRef<Path>>(&mut self, path: P) -> Result<()>

Save the document to a file path.

Examples found in repository?
examples/generate_all_samples.rs (line 63)
61fn export_all(dir: &Path, name: &str, mut doc: Document) {
62    let docx_path = dir.join(format!("{name}.docx"));
63    doc.save(&docx_path).unwrap();
64    println!("  {name}.docx");
65
66    let png = doc
67        .render_page_to_png_deterministic(0, 150.0)
68        .unwrap()
69        .expect("every generated sample should have a first page");
70    let png_path = dir.join(format!("{name}.png"));
71    std::fs::write(&png_path, &png).unwrap();
72    println!("  {name}.png ({} bytes)", png.len());
73
74    match doc.to_pdf_deterministic() {
75        Ok(pdf) => {
76            let pdf_path = dir.join(format!("{name}.pdf"));
77            std::fs::write(&pdf_path, &pdf).unwrap();
78            println!("  {name}.pdf ({} bytes)", pdf.len());
79        }
80        Err(e) => println!("  {name}.pdf — skipped: {e}"),
81    }
82
83    let html = doc.to_html();
84    let html_path = dir.join(format!("{name}.html"));
85    std::fs::write(&html_path, &html).unwrap();
86    println!("  {name}.html ({} bytes)", html.len());
87
88    let md = doc.to_markdown();
89    let md_path = dir.join(format!("{name}.md"));
90    std::fs::write(&md_path, &md).unwrap();
91    println!("  {name}.md ({} bytes)", md.len());
92}
More examples
Hide additional examples
examples/template_replace.rs (line 158)
43fn create_template(path: &Path) {
44    let mut doc = Document::new();
45    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
46    doc.set_margins(
47        Length::inches(1.0),
48        Length::inches(1.0),
49        Length::inches(1.0),
50        Length::inches(1.0),
51    );
52
53    doc.set_header("{{company_name}} — Confidential");
54    doc.set_footer("Prepared by {{author_name}} on {{date}}");
55
56    // ── Title ──
57    doc.add_paragraph("{{company_name}}")
58        .style("Heading1")
59        .alignment(Alignment::Center);
60
61    doc.add_paragraph("Project Proposal")
62        .alignment(Alignment::Center);
63
64    doc.add_paragraph("");
65
66    // ── Summary section ──
67    doc.add_paragraph("Executive Summary").style("Heading2");
68
69    doc.add_paragraph(
70        "This proposal outlines the {{project_name}} project for {{company_name}}. \
71         The primary contact is {{contact_name}} ({{contact_email}}). \
72         The proposed start date is {{start_date}} with an estimated duration of {{duration}}.",
73    );
74
75    doc.add_paragraph("");
76
77    // ── Cross-run placeholder (bold label + normal value) ──
78    doc.add_paragraph("Key Details").style("Heading2");
79
80    {
81        let mut p = doc.add_paragraph("");
82        p.add_run("Project: ").bold(true);
83        p.add_run("{{project_name}}");
84    }
85    {
86        let mut p = doc.add_paragraph("");
87        p.add_run("Budget: ").bold(true);
88        p.add_run("{{budget}}");
89    }
90    {
91        let mut p = doc.add_paragraph("");
92        p.add_run("Status: ").bold(true);
93        p.add_run("{{status}}");
94    }
95
96    doc.add_paragraph("");
97
98    // ── Table with placeholders ──
99    doc.add_paragraph("Team Members").style("Heading2");
100
101    {
102        let mut tbl = doc.add_table(4, 3);
103        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
104
105        // Header row
106        for col in 0..3 {
107            tbl.cell(0, col).unwrap().shading("2E75B6");
108        }
109        tbl.cell(0, 0).unwrap().set_text("Name");
110        tbl.cell(0, 1).unwrap().set_text("Role");
111        tbl.cell(0, 2).unwrap().set_text("Email");
112
113        tbl.cell(1, 0).unwrap().set_text("{{member1_name}}");
114        tbl.cell(1, 1).unwrap().set_text("{{member1_role}}");
115        tbl.cell(1, 2).unwrap().set_text("{{member1_email}}");
116
117        tbl.cell(2, 0).unwrap().set_text("{{member2_name}}");
118        tbl.cell(2, 1).unwrap().set_text("{{member2_role}}");
119        tbl.cell(2, 2).unwrap().set_text("{{member2_email}}");
120
121        tbl.cell(3, 0).unwrap().set_text("{{member3_name}}");
122        tbl.cell(3, 1).unwrap().set_text("{{member3_role}}");
123        tbl.cell(3, 2).unwrap().set_text("{{member3_email}}");
124    }
125
126    doc.add_paragraph("");
127
128    // ── Deliverables section ──
129    doc.add_paragraph("Deliverables").style("Heading2");
130
131    doc.add_paragraph("INSERTION_POINT");
132
133    doc.add_paragraph("");
134
135    // ── Signature block ──
136    doc.add_paragraph("Acceptance").style("Heading2");
137
138    doc.add_paragraph(
139        "By signing below, {{company_name}} agrees to the terms outlined in this proposal.",
140    );
141
142    {
143        let mut tbl = doc.add_table(2, 2);
144        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
145        tbl.cell(0, 0)
146            .unwrap()
147            .set_text("Customer: ___________________");
148        tbl.cell(0, 1)
149            .unwrap()
150            .set_text("Provider: ___________________");
151        tbl.cell(1, 0).unwrap().set_text("Date: {{date}}");
152        tbl.cell(1, 1).unwrap().set_text("Date: {{date}}");
153    }
154
155    doc.set_title("{{company_name}} — Project Proposal Template");
156    doc.set_author("Template Generator");
157
158    doc.save(path).unwrap();
159}
160
161/// Open the template, replace all placeholders, insert content, and save.
162fn fill_template(template_path: &Path, output_path: &Path) {
163    let mut doc = Document::open(template_path).unwrap();
164
165    // ── Batch replacement ──
166    let mut replacements = HashMap::new();
167    replacements.insert("{{company_name}}", "Riverside Medical Center");
168    replacements.insert("{{project_name}}", "Network Security Upgrade");
169    replacements.insert("{{contact_name}}", "Dr. Sarah Chen");
170    replacements.insert("{{contact_email}}", "s.chen@riverside.org");
171    replacements.insert("{{start_date}}", "March 1, 2026");
172    replacements.insert("{{duration}}", "12 weeks");
173    replacements.insert("{{budget}}", "$185,000");
174    replacements.insert("{{status}}", "Pending Approval");
175    replacements.insert("{{author_name}}", "James Wilson");
176    replacements.insert("{{date}}", "February 22, 2026");
177
178    // Team members
179    replacements.insert("{{member1_name}}", "James Wilson");
180    replacements.insert("{{member1_role}}", "Project Lead");
181    replacements.insert("{{member1_email}}", "j.wilson@provider.com");
182    replacements.insert("{{member2_name}}", "Maria Garcia");
183    replacements.insert("{{member2_role}}", "Security Architect");
184    replacements.insert("{{member2_email}}", "m.garcia@provider.com");
185    replacements.insert("{{member3_name}}", "David Park");
186    replacements.insert("{{member3_role}}", "Network Engineer");
187    replacements.insert("{{member3_email}}", "d.park@provider.com");
188
189    let count = doc.replace_all(&replacements);
190    println!("  Replaced {} placeholders", count);
191
192    // ── Insert deliverables at the insertion point ──
193    if let Some(idx) = doc.find_content_index("INSERTION_POINT") {
194        // Remove the placeholder paragraph
195        doc.remove_content(idx);
196
197        // Insert deliverables list
198        doc.insert_paragraph(idx, "The following deliverables are included:");
199
200        // Insert a deliverables table
201        let mut tbl = doc.insert_table(idx + 1, 5, 3);
202        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
203
204        for col in 0..3 {
205            tbl.cell(0, col).unwrap().shading("E2EFDA");
206        }
207        tbl.cell(0, 0).unwrap().set_text("Phase");
208        tbl.cell(0, 1).unwrap().set_text("Description");
209        tbl.cell(0, 2).unwrap().set_text("Timeline");
210
211        tbl.cell(1, 0).unwrap().set_text("1. Discovery");
212        tbl.cell(1, 1)
213            .unwrap()
214            .set_text("Network assessment and asset inventory");
215        tbl.cell(1, 2).unwrap().set_text("Weeks 1-3");
216
217        tbl.cell(2, 0).unwrap().set_text("2. Design");
218        tbl.cell(2, 1)
219            .unwrap()
220            .set_text("Security architecture and policy design");
221        tbl.cell(2, 2).unwrap().set_text("Weeks 4-6");
222
223        tbl.cell(3, 0).unwrap().set_text("3. Implementation");
224        tbl.cell(3, 1)
225            .unwrap()
226            .set_text("Deploy monitoring and access controls");
227        tbl.cell(3, 2).unwrap().set_text("Weeks 7-10");
228
229        tbl.cell(4, 0).unwrap().set_text("4. Validation");
230        tbl.cell(4, 1)
231            .unwrap()
232            .set_text("Testing, training, and handover");
233        tbl.cell(4, 2).unwrap().set_text("Weeks 11-12");
234
235        println!("  Inserted deliverables table at position {}", idx);
236    }
237
238    // ── Update metadata ──
239    doc.set_title("Riverside Medical Center — Network Security Upgrade Proposal");
240    doc.set_author("James Wilson");
241    doc.set_subject("Project Proposal");
242    doc.set_keywords("security, network, medical, proposal");
243
244    doc.save(output_path).unwrap();
245}
examples/header_banner.rs (line 168)
29fn generate_header_banner_doc(path: &Path) {
30    let mut doc = Document::new();
31
32    // Page setup with extra top margin for the banner
33    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
34    doc.set_margins(
35        Length::twips(2292), // top — extra tall for header banner
36        Length::twips(1440), // right
37        Length::twips(1440), // bottom
38        Length::twips(1440), // left
39    );
40    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
41
42    // Generate a simple logo image (white text on transparent background)
43    let logo_img = create_logo_png(220, 48);
44
45    // ── Dark blue banner ──
46    let banner = build_header_banner_xml(
47        "rId1",
48        &BannerOpts {
49            bg_color: "1A3C6E",
50            banner_width: 7772400, // full page width in EMU (~8.5")
51            banner_height: 969026, // banner height in EMU (~1.06")
52            logo_width: 2011680,   // logo display width (~2.2")
53            logo_height: 438912,   // logo display height (~0.48")
54            logo_x_offset: 295125, // left padding
55            logo_y_offset: 265057, // vertical centering
56        },
57    );
58
59    doc.set_raw_header_with_images(
60        banner.clone(),
61        &[("rId1", &logo_img, "logo.png")],
62        rdocx_oxml::header_footer::HdrFtrType::Default,
63    );
64
65    // Use a different first page header (same banner, different color)
66    doc.set_different_first_page(true);
67    let first_page_banner = build_header_banner_xml(
68        "rId1",
69        &BannerOpts {
70            bg_color: "2E75B6", // lighter blue for cover
71            banner_width: 7772400,
72            banner_height: 969026,
73            logo_width: 2011680,
74            logo_height: 438912,
75            logo_x_offset: 295125,
76            logo_y_offset: 265057,
77        },
78    );
79    doc.set_raw_header_with_images(
80        first_page_banner,
81        &[("rId1", &logo_img, "logo.png")],
82        rdocx_oxml::header_footer::HdrFtrType::First,
83    );
84
85    // Footer
86    doc.set_footer("Confidential — Internal Use Only");
87
88    // ── Page 1: Cover ──
89    doc.add_paragraph("Company Report").style("Heading1");
90
91    doc.add_paragraph(
92        "This document demonstrates a custom header banner built with DrawingML \
93         group shapes. The banner uses a colored rectangle with a logo image overlaid, \
94         positioned at the top of each page.",
95    );
96
97    doc.add_paragraph("");
98
99    doc.add_paragraph("How the Header Banner Works")
100        .style("Heading2");
101
102    doc.add_paragraph(
103        "The header banner is built using set_raw_header_with_images(), which \
104         accepts raw XML and a list of (rel_id, image_data, filename) tuples. \
105         The XML uses a DrawingML group shape (wpg:wgp) containing:",
106    );
107
108    doc.add_bullet_list_item(
109        "A wps:wsp rectangle shape with a solid color fill (the background bar)",
110        0,
111    );
112    doc.add_bullet_list_item(
113        "A pic:pic image element positioned within the group (the logo)",
114        0,
115    );
116    doc.add_bullet_list_item(
117        "The group is wrapped in a wp:anchor element for absolute page positioning",
118        0,
119    );
120
121    doc.add_paragraph("");
122
123    doc.add_paragraph("Customization").style("Heading2");
124
125    doc.add_paragraph(
126        "All dimensions are in EMU (English Metric Units) where 914400 EMU = 1 inch. \
127         You can customize:",
128    );
129
130    doc.add_bullet_list_item("bg_color — any hex color for the rectangle background", 0);
131    doc.add_bullet_list_item("banner_width / banner_height — size of the full banner", 0);
132    doc.add_bullet_list_item("logo_width / logo_height — display size of the logo", 0);
133    doc.add_bullet_list_item(
134        "logo_x_offset / logo_y_offset — logo position within the banner",
135        0,
136    );
137
138    doc.add_paragraph("");
139
140    doc.add_paragraph("Different First Page").style("Heading2");
141
142    doc.add_paragraph(
143        "This page uses a lighter blue banner (first page header). \
144         Subsequent pages use a darker blue banner (default header). \
145         Use set_different_first_page(true) to enable this.",
146    );
147
148    // ── Page 2 ──
149    doc.add_paragraph("").page_break_before(true);
150
151    doc.add_paragraph("Second Page").style("Heading1");
152
153    doc.add_paragraph(
154        "This page shows the default header banner (dark blue). The first page \
155         had a lighter blue banner because we set a different first-page header.",
156    );
157
158    doc.add_paragraph("");
159
160    doc.add_paragraph(
161        "The banner repeats on every page because it is placed in the header part. \
162         You can have different banners for default, first-page, and even-page headers.",
163    );
164
165    doc.set_title("Header Banner Example");
166    doc.set_author("rdocx");
167
168    doc.save(path).unwrap();
169}
examples/styled_tables.rs (line 448)
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}
Source

pub fn to_bytes(&mut self) -> Result<Vec<u8>>

Save the document to a byte vector.

Source

pub fn paragraphs(&self) -> Vec<ParagraphRef<'_>>

Get immutable references to all paragraphs.

Source

pub fn paragraph(&self, index: usize) -> Option<ParagraphRef<'_>>

Get an immutable reference to a paragraph by index (among paragraphs only).

Source

pub fn footnotes(&self) -> Vec<(i32, String)>

All footnotes as (id, plain text), in file order.

Separator entries are excluded. They live in the same stream and are retained by the model so a round trip preserves them, but they are not notes and never were part of this listing.

Source

pub fn add_footnote(&mut self, text: &str) -> i32

Add a footnote with the given text; returns its id. Pair with Paragraph::add_footnote_ref to reference it from the body.

Source

pub fn add_paragraph(&mut self, text: &str) -> Paragraph<'_>

Add a paragraph with the given text and return a mutable reference.

Examples found in repository?
examples/convert_html_md.rs (line 16)
3fn main() {
4    let doc = Document::open("samples/feature_showcase.docx").expect("Failed to open document");
5
6    let html = doc.to_html();
7    std::fs::write("/tmp/feature_showcase.html", &html).expect("Failed to write HTML");
8    println!("HTML: {} bytes -> /tmp/feature_showcase.html", html.len());
9
10    let md = doc.to_markdown();
11    std::fs::write("/tmp/feature_showcase.md", &md).expect("Failed to write Markdown");
12    println!("Markdown: {} bytes -> /tmp/feature_showcase.md", md.len());
13
14    // Simple document
15    let mut simple = Document::new();
16    simple.add_paragraph("Hello, World!");
17    let html = simple.to_html();
18    println!("\n--- Simple HTML ---\n{html}");
19
20    let md = simple.to_markdown();
21    println!("--- Simple Markdown ---\n{md}");
22}
More examples
Hide additional examples
examples/generate_pdf.rs (line 22)
7fn main() {
8    // Test 1: Simple document
9    let doc = Document::new();
10    match doc.to_pdf() {
11        Ok(bytes) => {
12            std::fs::write("/tmp/rdocx_simple.pdf", &bytes).unwrap();
13            println!("Simple PDF: {} bytes -> /tmp/rdocx_simple.pdf", bytes.len());
14        }
15        Err(e) => println!("Simple PDF failed: {e}"),
16    }
17
18    // Test 2: Document with content
19    let mut doc = Document::new();
20    doc.set_title("Test PDF Document");
21    doc.set_author("rdocx-pdf");
22    doc.add_paragraph("Chapter 1: Introduction")
23        .style("Heading1");
24    doc.add_paragraph(
25        "This is a test document generated by rdocx and rendered to PDF. \
26         It demonstrates text rendering with proper font shaping and pagination.",
27    );
28    doc.add_paragraph("Section 1.1").style("Heading2");
29    doc.add_paragraph("More content in a sub-section.");
30
31    {
32        let mut table = doc.add_table(2, 3);
33        for r in 0..2 {
34            for c in 0..3 {
35                if let Some(mut cell) = table.cell(r, c) {
36                    cell.set_text(&format!("R{}C{}", r + 1, c + 1));
37                }
38            }
39        }
40    }
41
42    doc.add_paragraph("After the table.");
43
44    match doc.to_pdf() {
45        Ok(bytes) => {
46            std::fs::write("/tmp/rdocx_content.pdf", &bytes).unwrap();
47            println!(
48                "Content PDF: {} bytes -> /tmp/rdocx_content.pdf",
49                bytes.len()
50            );
51        }
52        Err(e) => println!("Content PDF failed: {e}"),
53    }
54
55    // Test 3: From feature_showcase.docx
56    let showcase_path = concat!(
57        env!("CARGO_MANIFEST_DIR"),
58        "/../../samples/feature_showcase.docx"
59    );
60    match Document::open(showcase_path) {
61        Ok(doc) => match doc.to_pdf() {
62            Ok(bytes) => {
63                std::fs::write("/tmp/rdocx_showcase.pdf", &bytes).unwrap();
64                println!(
65                    "Showcase PDF: {} bytes -> /tmp/rdocx_showcase.pdf",
66                    bytes.len()
67                );
68            }
69            Err(e) => println!("Showcase PDF failed: {e}"),
70        },
71        Err(e) => println!("Failed to open showcase: {e}"),
72    }
73}
examples/template_replace.rs (line 57)
43fn create_template(path: &Path) {
44    let mut doc = Document::new();
45    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
46    doc.set_margins(
47        Length::inches(1.0),
48        Length::inches(1.0),
49        Length::inches(1.0),
50        Length::inches(1.0),
51    );
52
53    doc.set_header("{{company_name}} — Confidential");
54    doc.set_footer("Prepared by {{author_name}} on {{date}}");
55
56    // ── Title ──
57    doc.add_paragraph("{{company_name}}")
58        .style("Heading1")
59        .alignment(Alignment::Center);
60
61    doc.add_paragraph("Project Proposal")
62        .alignment(Alignment::Center);
63
64    doc.add_paragraph("");
65
66    // ── Summary section ──
67    doc.add_paragraph("Executive Summary").style("Heading2");
68
69    doc.add_paragraph(
70        "This proposal outlines the {{project_name}} project for {{company_name}}. \
71         The primary contact is {{contact_name}} ({{contact_email}}). \
72         The proposed start date is {{start_date}} with an estimated duration of {{duration}}.",
73    );
74
75    doc.add_paragraph("");
76
77    // ── Cross-run placeholder (bold label + normal value) ──
78    doc.add_paragraph("Key Details").style("Heading2");
79
80    {
81        let mut p = doc.add_paragraph("");
82        p.add_run("Project: ").bold(true);
83        p.add_run("{{project_name}}");
84    }
85    {
86        let mut p = doc.add_paragraph("");
87        p.add_run("Budget: ").bold(true);
88        p.add_run("{{budget}}");
89    }
90    {
91        let mut p = doc.add_paragraph("");
92        p.add_run("Status: ").bold(true);
93        p.add_run("{{status}}");
94    }
95
96    doc.add_paragraph("");
97
98    // ── Table with placeholders ──
99    doc.add_paragraph("Team Members").style("Heading2");
100
101    {
102        let mut tbl = doc.add_table(4, 3);
103        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
104
105        // Header row
106        for col in 0..3 {
107            tbl.cell(0, col).unwrap().shading("2E75B6");
108        }
109        tbl.cell(0, 0).unwrap().set_text("Name");
110        tbl.cell(0, 1).unwrap().set_text("Role");
111        tbl.cell(0, 2).unwrap().set_text("Email");
112
113        tbl.cell(1, 0).unwrap().set_text("{{member1_name}}");
114        tbl.cell(1, 1).unwrap().set_text("{{member1_role}}");
115        tbl.cell(1, 2).unwrap().set_text("{{member1_email}}");
116
117        tbl.cell(2, 0).unwrap().set_text("{{member2_name}}");
118        tbl.cell(2, 1).unwrap().set_text("{{member2_role}}");
119        tbl.cell(2, 2).unwrap().set_text("{{member2_email}}");
120
121        tbl.cell(3, 0).unwrap().set_text("{{member3_name}}");
122        tbl.cell(3, 1).unwrap().set_text("{{member3_role}}");
123        tbl.cell(3, 2).unwrap().set_text("{{member3_email}}");
124    }
125
126    doc.add_paragraph("");
127
128    // ── Deliverables section ──
129    doc.add_paragraph("Deliverables").style("Heading2");
130
131    doc.add_paragraph("INSERTION_POINT");
132
133    doc.add_paragraph("");
134
135    // ── Signature block ──
136    doc.add_paragraph("Acceptance").style("Heading2");
137
138    doc.add_paragraph(
139        "By signing below, {{company_name}} agrees to the terms outlined in this proposal.",
140    );
141
142    {
143        let mut tbl = doc.add_table(2, 2);
144        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
145        tbl.cell(0, 0)
146            .unwrap()
147            .set_text("Customer: ___________________");
148        tbl.cell(0, 1)
149            .unwrap()
150            .set_text("Provider: ___________________");
151        tbl.cell(1, 0).unwrap().set_text("Date: {{date}}");
152        tbl.cell(1, 1).unwrap().set_text("Date: {{date}}");
153    }
154
155    doc.set_title("{{company_name}} — Project Proposal Template");
156    doc.set_author("Template Generator");
157
158    doc.save(path).unwrap();
159}
examples/header_banner.rs (line 89)
29fn generate_header_banner_doc(path: &Path) {
30    let mut doc = Document::new();
31
32    // Page setup with extra top margin for the banner
33    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
34    doc.set_margins(
35        Length::twips(2292), // top — extra tall for header banner
36        Length::twips(1440), // right
37        Length::twips(1440), // bottom
38        Length::twips(1440), // left
39    );
40    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
41
42    // Generate a simple logo image (white text on transparent background)
43    let logo_img = create_logo_png(220, 48);
44
45    // ── Dark blue banner ──
46    let banner = build_header_banner_xml(
47        "rId1",
48        &BannerOpts {
49            bg_color: "1A3C6E",
50            banner_width: 7772400, // full page width in EMU (~8.5")
51            banner_height: 969026, // banner height in EMU (~1.06")
52            logo_width: 2011680,   // logo display width (~2.2")
53            logo_height: 438912,   // logo display height (~0.48")
54            logo_x_offset: 295125, // left padding
55            logo_y_offset: 265057, // vertical centering
56        },
57    );
58
59    doc.set_raw_header_with_images(
60        banner.clone(),
61        &[("rId1", &logo_img, "logo.png")],
62        rdocx_oxml::header_footer::HdrFtrType::Default,
63    );
64
65    // Use a different first page header (same banner, different color)
66    doc.set_different_first_page(true);
67    let first_page_banner = build_header_banner_xml(
68        "rId1",
69        &BannerOpts {
70            bg_color: "2E75B6", // lighter blue for cover
71            banner_width: 7772400,
72            banner_height: 969026,
73            logo_width: 2011680,
74            logo_height: 438912,
75            logo_x_offset: 295125,
76            logo_y_offset: 265057,
77        },
78    );
79    doc.set_raw_header_with_images(
80        first_page_banner,
81        &[("rId1", &logo_img, "logo.png")],
82        rdocx_oxml::header_footer::HdrFtrType::First,
83    );
84
85    // Footer
86    doc.set_footer("Confidential — Internal Use Only");
87
88    // ── Page 1: Cover ──
89    doc.add_paragraph("Company Report").style("Heading1");
90
91    doc.add_paragraph(
92        "This document demonstrates a custom header banner built with DrawingML \
93         group shapes. The banner uses a colored rectangle with a logo image overlaid, \
94         positioned at the top of each page.",
95    );
96
97    doc.add_paragraph("");
98
99    doc.add_paragraph("How the Header Banner Works")
100        .style("Heading2");
101
102    doc.add_paragraph(
103        "The header banner is built using set_raw_header_with_images(), which \
104         accepts raw XML and a list of (rel_id, image_data, filename) tuples. \
105         The XML uses a DrawingML group shape (wpg:wgp) containing:",
106    );
107
108    doc.add_bullet_list_item(
109        "A wps:wsp rectangle shape with a solid color fill (the background bar)",
110        0,
111    );
112    doc.add_bullet_list_item(
113        "A pic:pic image element positioned within the group (the logo)",
114        0,
115    );
116    doc.add_bullet_list_item(
117        "The group is wrapped in a wp:anchor element for absolute page positioning",
118        0,
119    );
120
121    doc.add_paragraph("");
122
123    doc.add_paragraph("Customization").style("Heading2");
124
125    doc.add_paragraph(
126        "All dimensions are in EMU (English Metric Units) where 914400 EMU = 1 inch. \
127         You can customize:",
128    );
129
130    doc.add_bullet_list_item("bg_color — any hex color for the rectangle background", 0);
131    doc.add_bullet_list_item("banner_width / banner_height — size of the full banner", 0);
132    doc.add_bullet_list_item("logo_width / logo_height — display size of the logo", 0);
133    doc.add_bullet_list_item(
134        "logo_x_offset / logo_y_offset — logo position within the banner",
135        0,
136    );
137
138    doc.add_paragraph("");
139
140    doc.add_paragraph("Different First Page").style("Heading2");
141
142    doc.add_paragraph(
143        "This page uses a lighter blue banner (first page header). \
144         Subsequent pages use a darker blue banner (default header). \
145         Use set_different_first_page(true) to enable this.",
146    );
147
148    // ── Page 2 ──
149    doc.add_paragraph("").page_break_before(true);
150
151    doc.add_paragraph("Second Page").style("Heading1");
152
153    doc.add_paragraph(
154        "This page shows the default header banner (dark blue). The first page \
155         had a lighter blue banner because we set a different first-page header.",
156    );
157
158    doc.add_paragraph("");
159
160    doc.add_paragraph(
161        "The banner repeats on every page because it is placed in the header part. \
162         You can have different banners for default, first-page, and even-page headers.",
163    );
164
165    doc.set_title("Header Banner Example");
166    doc.set_author("rdocx");
167
168    doc.save(path).unwrap();
169}
examples/styled_tables.rs (line 34)
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}
examples/generate_all_samples.rs (line 127)
97fn generate_feature_showcase(_samples_dir: &Path) -> Document {
98    let mut doc = Document::new();
99
100    // ── Page Setup & Metadata ──
101    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
102    doc.set_margins(
103        Length::inches(1.0),
104        Length::inches(1.0),
105        Length::inches(1.0),
106        Length::inches(1.0),
107    );
108    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
109    doc.set_gutter(Length::twips(0));
110    doc.set_title("rdocx Feature Showcase");
111    doc.set_author("rdocx Sample Generator");
112    doc.set_subject("Comprehensive feature demonstration");
113    doc.set_keywords("rdocx, docx, rust, sample, showcase");
114
115    // Headers & Footers with different first page
116    doc.set_different_first_page(true);
117    doc.set_first_page_header("rdocx");
118    doc.set_first_page_footer("Feature Showcase — Cover Page");
119    doc.set_header("rdocx Feature Showcase");
120    doc.set_footer("Generated by rdocx");
121
122    // ── COVER PAGE ──
123    let bg = create_sample_png(612, 792, [20, 45, 90]);
124    doc.add_background_image(&bg, "cover_bg.png");
125
126    for _ in 0..3 {
127        doc.add_paragraph("");
128    }
129    {
130        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
131        p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
132    }
133    {
134        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
135        p.add_run("Complete Feature Showcase")
136            .size(28.0)
137            .color("FFFFFF")
138            .italic(true);
139    }
140    doc.add_paragraph("");
141    {
142        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
143        p.add_run("Every feature of the rdocx Rust library")
144            .size(13.0)
145            .color("B0C4DE");
146    }
147    {
148        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
149        p.add_run("demonstrated in a single document.")
150            .size(13.0)
151            .color("B0C4DE");
152    }
153
154    // ── TABLE OF CONTENTS ──
155    doc.add_paragraph("").page_break_before(true);
156    doc.insert_toc(doc.content_count(), 3);
157
158    // ── SECTION 1: TEXT FORMATTING ──
159    doc.add_paragraph("").page_break_before(true);
160    doc.add_paragraph("1. Text Formatting").style("Heading1");
161
162    // Paragraph Alignment
163    doc.add_paragraph("1.1 Paragraph Alignment")
164        .style("Heading2");
165    doc.add_paragraph("Left-aligned paragraph (default).")
166        .alignment(Alignment::Left);
167    doc.add_paragraph("Center-aligned paragraph.")
168        .alignment(Alignment::Center);
169    doc.add_paragraph("Right-aligned paragraph.")
170        .alignment(Alignment::Right);
171    doc.add_paragraph(
172        "Justified paragraph — this text is long enough to demonstrate how justified alignment \
173         distributes extra space across word gaps so lines fill the full width of the text area.",
174    )
175    .alignment(Alignment::Justify);
176
177    doc.add_paragraph("");
178
179    // Run Formatting
180    doc.add_paragraph("1.2 Run Formatting").style("Heading2");
181    {
182        let mut p = doc.add_paragraph("");
183        p.add_run("Bold").bold(true);
184        p.add_run(" | ");
185        p.add_run("Italic").italic(true);
186        p.add_run(" | ");
187        p.add_run("Bold Italic").bold(true).italic(true);
188        p.add_run(" | ");
189        p.add_run("Underline").underline(true);
190        p.add_run(" | ");
191        p.add_run("Strikethrough").strike(true);
192        p.add_run(" | ");
193        p.add_run("Double Strike").double_strike(true);
194    }
195    {
196        let mut p = doc.add_paragraph("");
197        p.add_run("Red").color("FF0000");
198        p.add_run(" | ");
199        p.add_run("Blue").color("0000FF");
200        p.add_run(" | ");
201        p.add_run("Green").color("00AA00");
202        p.add_run(" | ");
203        p.add_run("Highlighted").highlight("FFFF00");
204    }
205    {
206        let mut p = doc.add_paragraph("");
207        p.add_run("8pt").size(8.0);
208        p.add_run(" | ");
209        p.add_run("11pt").size(11.0);
210        p.add_run(" | ");
211        p.add_run("16pt").size(16.0);
212        p.add_run(" | ");
213        p.add_run("24pt").size(24.0);
214    }
215    {
216        let mut p = doc.add_paragraph("");
217        p.add_run("Arial").font("Arial");
218        p.add_run(" | ");
219        p.add_run("Times New Roman").font("Times New Roman");
220        p.add_run(" | ");
221        p.add_run("Courier New").font("Courier New");
222    }
223    {
224        let mut p = doc.add_paragraph("");
225        p.add_run("H");
226        p.add_run("2").subscript();
227        p.add_run("O (subscript) | E=mc");
228        p.add_run("2").superscript();
229        p.add_run(" (superscript)");
230    }
231    {
232        let mut p = doc.add_paragraph("");
233        p.add_run("ALL CAPS").all_caps(true);
234        p.add_run(" | ");
235        p.add_run("Small Caps").small_caps(true);
236        p.add_run(" | ");
237        p.add_run("Expanded").character_spacing(Length::twips(40));
238        p.add_run(" | ");
239        p.add_run("Hidden text (not visible)").hidden(true);
240    }
241
242    doc.add_paragraph("");
243
244    // Underline Styles
245    doc.add_paragraph("1.3 Underline Styles").style("Heading2");
246    {
247        let mut p = doc.add_paragraph("");
248        p.add_run("Single ")
249            .underline_style(rdocx::UnderlineStyle::Single);
250        p.add_run("Double ")
251            .underline_style(rdocx::UnderlineStyle::Double);
252        p.add_run("Thick ")
253            .underline_style(rdocx::UnderlineStyle::Thick);
254        p.add_run("Dotted ")
255            .underline_style(rdocx::UnderlineStyle::Dotted);
256        p.add_run("Dash ")
257            .underline_style(rdocx::UnderlineStyle::Dash);
258        p.add_run("Wave ")
259            .underline_style(rdocx::UnderlineStyle::Wave);
260    }
261
262    // ── SECTION 2: PARAGRAPH FORMATTING ──
263    doc.add_paragraph("").page_break_before(true);
264    doc.add_paragraph("2. Paragraph Formatting")
265        .style("Heading1");
266
267    doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
268    doc.add_paragraph("Paragraph with light green shading")
269        .shading("E2EFDA");
270    doc.add_paragraph("Paragraph with bottom border")
271        .border_bottom(BorderStyle::Single, 6, "2E75B6");
272    doc.add_paragraph("Paragraph with all borders (red)")
273        .border_all(BorderStyle::Single, 4, "FF0000");
274
275    doc.add_paragraph("2.2 Indentation").style("Heading2");
276    doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
277        .indent_left(Length::inches(1.0))
278        .hanging_indent(Length::inches(0.5));
279    doc.add_paragraph("0.5-inch first-line indent on this paragraph")
280        .first_line_indent(Length::inches(0.5));
281    doc.add_paragraph("Both left and right indent (0.75 inches each)")
282        .indent_left(Length::inches(0.75))
283        .indent_right(Length::inches(0.75));
284
285    doc.add_paragraph("2.3 Spacing & Line Height")
286        .style("Heading2");
287    doc.add_paragraph("Extra space before (24pt) and after (12pt)")
288        .space_before(Length::pt(24.0))
289        .space_after(Length::pt(12.0));
290    doc.add_paragraph(
291        "Double line spacing paragraph. This text should have extra vertical space between \
292         lines to demonstrate line_spacing_multiple(2.0).",
293    )
294    .line_spacing_multiple(2.0);
295    doc.add_paragraph("Exact 20pt line spacing (fixed height).")
296        .line_spacing(20.0);
297
298    doc.add_paragraph("2.4 Pagination Controls")
299        .style("Heading2");
300    doc.add_paragraph("keep_with_next — this paragraph stays with the next")
301        .keep_with_next(true);
302    doc.add_paragraph("(This paragraph was kept with the one above.)");
303    doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
304        .keep_together(true);
305    doc.add_paragraph("widow_control — prevents widow/orphan lines")
306        .widow_control(true);
307
308    // ── SECTION 3: LISTS ──
309    doc.add_paragraph("").page_break_before(true);
310    doc.add_paragraph("3. Lists").style("Heading1");
311
312    doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
313    doc.add_bullet_list_item("First item", 0);
314    doc.add_bullet_list_item("Second item", 0);
315    doc.add_bullet_list_item("Nested level 1", 1);
316    doc.add_bullet_list_item("Nested level 2", 2);
317    doc.add_bullet_list_item("Back to level 1", 1);
318    doc.add_bullet_list_item("Third item", 0);
319
320    doc.add_paragraph("");
321
322    doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
323    doc.add_numbered_list_item("First numbered item", 0);
324    doc.add_numbered_list_item("Second numbered item", 0);
325    doc.add_numbered_list_item("Sub-item A", 1);
326    doc.add_numbered_list_item("Sub-item B", 1);
327    doc.add_numbered_list_item("Third numbered item", 0);
328
329    // ── SECTION 4: TAB STOPS ──
330    doc.add_paragraph("");
331    doc.add_paragraph("4. Tab Stops").style("Heading1");
332
333    doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
334    doc.add_paragraph("Left\tCenter\tRight\tDecimal")
335        .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
336        .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
337        .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
338        .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
339
340    doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
341    doc.add_paragraph("Item\t\tPrice")
342        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
343        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
344        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
345    doc.add_paragraph("Widget\t\t$19.99")
346        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
347        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
348        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
349    doc.add_paragraph("Gadget\t\t$249.50")
350        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
351        .add_tab_stop_with_leader(
352            TabAlignment::Right,
353            Length::inches(4.0),
354            TabLeader::Underscore,
355        )
356        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
357
358    // ── SECTION 5: TABLES ──
359    doc.add_paragraph("").page_break_before(true);
360    doc.add_paragraph("5. Tables").style("Heading1");
361
362    doc.add_paragraph("5.1 Basic Table").style("Heading2");
363    {
364        let mut tbl = doc
365            .add_table(4, 3)
366            .borders(BorderStyle::Single, 4, "000000");
367        for col in 0..3 {
368            tbl.cell(0, col).unwrap().shading("2E75B6");
369        }
370        tbl.cell(0, 0).unwrap().set_text("Name");
371        tbl.cell(0, 1).unwrap().set_text("Role");
372        tbl.cell(0, 2).unwrap().set_text("Location");
373        tbl.cell(1, 0).unwrap().set_text("Walter White");
374        tbl.cell(1, 1).unwrap().set_text("CEO");
375        tbl.cell(1, 2).unwrap().set_text("Albuquerque");
376        tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
377        tbl.cell(2, 1).unwrap().set_text("CTO");
378        tbl.cell(2, 2).unwrap().set_text("Remote");
379        tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
380        tbl.cell(3, 1).unwrap().set_text("Security");
381        tbl.cell(3, 2).unwrap().set_text("Washington");
382    }
383
384    doc.add_paragraph("");
385
386    doc.add_paragraph("5.2 Column Span & Cell Shading")
387        .style("Heading2");
388    {
389        let mut tbl = doc
390            .add_table(3, 4)
391            .borders(BorderStyle::Single, 4, "000000")
392            .width_pct(100.0);
393        tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
394        tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
395        tbl.cell(1, 0).unwrap().set_text("Region");
396        tbl.cell(1, 0).unwrap().shading("D6E4F0");
397        tbl.cell(1, 1).unwrap().set_text("Q1");
398        tbl.cell(1, 1).unwrap().shading("D6E4F0");
399        tbl.cell(1, 2).unwrap().set_text("Q2");
400        tbl.cell(1, 2).unwrap().shading("D6E4F0");
401        tbl.cell(1, 3).unwrap().set_text("Total");
402        tbl.cell(1, 3).unwrap().shading("D6E4F0");
403        tbl.cell(2, 0).unwrap().set_text("Americas");
404        tbl.cell(2, 1).unwrap().set_text("$2.4M");
405        tbl.cell(2, 2).unwrap().set_text("$2.7M");
406        tbl.cell(2, 3).unwrap().set_text("$5.1M");
407    }
408
409    doc.add_paragraph("");
410
411    doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
412    {
413        let mut tbl = doc
414            .add_table(4, 3)
415            .borders(BorderStyle::Single, 4, "000000");
416        tbl.cell(0, 0).unwrap().set_text("Category");
417        tbl.cell(0, 0).unwrap().shading("E2EFDA");
418        tbl.cell(0, 1).unwrap().set_text("Item");
419        tbl.cell(0, 1).unwrap().shading("E2EFDA");
420        tbl.cell(0, 2).unwrap().set_text("Price");
421        tbl.cell(0, 2).unwrap().shading("E2EFDA");
422        tbl.cell(1, 0).unwrap().set_text("Hardware");
423        tbl.cell(1, 0).unwrap().v_merge_restart();
424        tbl.cell(1, 1).unwrap().set_text("Laptop");
425        tbl.cell(1, 2).unwrap().set_text("$1,200");
426        tbl.cell(2, 0).unwrap().v_merge_continue();
427        tbl.cell(2, 1).unwrap().set_text("Monitor");
428        tbl.cell(2, 2).unwrap().set_text("$450");
429        tbl.cell(3, 0).unwrap().set_text("Software");
430        tbl.cell(3, 1).unwrap().set_text("IDE License");
431        tbl.cell(3, 2).unwrap().set_text("$200/yr");
432    }
433
434    doc.add_paragraph("");
435
436    doc.add_paragraph("5.4 Nested Table").style("Heading2");
437    {
438        let mut tbl = doc
439            .add_table(2, 2)
440            .borders(BorderStyle::Single, 6, "2E75B6");
441        tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
442        tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
443        tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
444        {
445            let mut cell = tbl.cell(1, 1).unwrap();
446            cell.set_text("Nested table below:");
447            let mut nested = cell
448                .add_table(2, 2)
449                .borders(BorderStyle::Single, 2, "FF6600");
450            nested.cell(0, 0).unwrap().set_text("A");
451            nested.cell(0, 1).unwrap().set_text("B");
452            nested.cell(1, 0).unwrap().set_text("C");
453            nested.cell(1, 1).unwrap().set_text("D");
454        }
455    }
456
457    doc.add_paragraph("");
458
459    doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
460        .style("Heading2");
461    {
462        let mut tbl = doc
463            .add_table(2, 3)
464            .borders(BorderStyle::Single, 4, "666666");
465        tbl.row(0).unwrap().height(Length::pt(50.0)).header();
466        tbl.cell(0, 0).unwrap().set_text("Top");
467        tbl.cell(0, 0)
468            .unwrap()
469            .vertical_alignment(VerticalAlignment::Top)
470            .shading("FFF2CC");
471        tbl.cell(0, 1).unwrap().set_text("Center");
472        tbl.cell(0, 1)
473            .unwrap()
474            .vertical_alignment(VerticalAlignment::Center)
475            .shading("D9E2F3");
476        tbl.cell(0, 2).unwrap().set_text("Bottom");
477        tbl.cell(0, 2)
478            .unwrap()
479            .vertical_alignment(VerticalAlignment::Bottom)
480            .shading("E2EFDA");
481        tbl.row(1).unwrap().cant_split();
482        tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
483        tbl.cell(1, 0).unwrap().no_wrap();
484        tbl.cell(1, 1).unwrap().set_text("Fixed width");
485        tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
486        tbl.cell(1, 2).unwrap().set_text("Normal");
487    }
488
489    // ── SECTION 6: IMAGES ──
490    doc.add_paragraph("").page_break_before(true);
491    doc.add_paragraph("6. Images").style("Heading1");
492
493    doc.add_paragraph("6.1 Inline Image").style("Heading2");
494    doc.add_paragraph("A blue gradient image below:");
495    let img = create_sample_png(200, 50, [0, 80, 200]);
496    doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
497
498    doc.add_paragraph("");
499    doc.add_paragraph("6.2 Header Image").style("Heading2");
500    let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
501    doc.set_header_image(
502        &hdr_img,
503        "header_logo.png",
504        Length::inches(2.0),
505        Length::inches(0.2),
506    );
507    doc.add_paragraph("The header now contains an inline image (check the top of this page).");
508
509    doc.add_paragraph("");
510    doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
511
512    // ── SECTION 7: CONTENT MANIPULATION ──
513    doc.add_paragraph("").page_break_before(true);
514    doc.add_paragraph("7. Content Manipulation")
515        .style("Heading1");
516
517    doc.add_paragraph("7.1 Placeholder Replacement")
518        .style("Heading2");
519    doc.add_paragraph("Customer: {{customer}}");
520    doc.add_paragraph("Date: {{date}}");
521    doc.add_paragraph("Reference: {{ref_number}}");
522    {
523        let mut tbl = doc
524            .add_table(2, 2)
525            .borders(BorderStyle::Single, 4, "000000");
526        tbl.cell(0, 0).unwrap().set_text("Project");
527        tbl.cell(0, 0).unwrap().shading("D6E4F0");
528        tbl.cell(0, 1).unwrap().set_text("{{project}}");
529        tbl.cell(1, 0).unwrap().set_text("Status");
530        tbl.cell(1, 0).unwrap().shading("D6E4F0");
531        tbl.cell(1, 1).unwrap().set_text("{{status}}");
532    }
533
534    let mut replacements = HashMap::new();
535    replacements.insert("{{customer}}", "Tensorbee Inc.");
536    replacements.insert("{{date}}", "February 22, 2026");
537    replacements.insert("{{ref_number}}", "TB-2026-001");
538    replacements.insert("{{project}}", "Infrastructure Upgrade");
539    replacements.insert("{{status}}", "In Progress");
540    let n = doc.replace_all(&replacements);
541    doc.add_paragraph(&format!("({n} placeholders replaced above)"));
542
543    doc.add_paragraph("");
544
545    doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
546    doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
547    let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
548    doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
549
550    doc.add_paragraph("");
551
552    doc.add_paragraph("7.3 Content Insertion").style("Heading2");
553    doc.add_paragraph("Section A: First content.");
554    doc.add_paragraph("Section C: Third content.");
555    if let Some(idx) = doc.find_content_index("Section C") {
556        doc.insert_paragraph(
557            idx,
558            "Section B: Inserted between A and C via find_content_index().",
559        );
560    }
561
562    // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
563    doc.add_paragraph("").section_break(SectionBreak::NextPage);
564    doc.add_paragraph("8. Section Breaks & Orientation")
565        .style("Heading1");
566    doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
567
568    {
569        let mut tbl = doc
570            .add_table(3, 7)
571            .borders(BorderStyle::Single, 4, "2E75B6");
572        let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
573        for (c, h) in headers.iter().enumerate() {
574            tbl.cell(0, c).unwrap().set_text(h);
575            tbl.cell(0, c).unwrap().shading("2E75B6");
576        }
577        let data = [
578            [
579                "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
580            ],
581            [
582                "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
583            ],
584        ];
585        for (r, row) in data.iter().enumerate() {
586            for (c, v) in row.iter().enumerate() {
587                tbl.cell(r + 1, c).unwrap().set_text(v);
588            }
589        }
590    }
591
592    doc.add_paragraph("")
593        .section_break(SectionBreak::NextPage)
594        .section_landscape();
595
596    // ── SECTION 9: CUSTOM STYLES ──
597    doc.add_paragraph("9. Custom Styles").style("Heading1");
598    doc.add_style(
599        StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
600            .based_on("Normal")
601            .paragraph_properties(rdocx_oxml::properties::CT_PPr {
602                shading: Some(rdocx_oxml::properties::CT_Shd {
603                    val: "clear".to_string(),
604                    color: None,
605                    fill: Some("FFF2CC".to_string()),
606                }),
607                ..Default::default()
608            })
609            .run_properties(rdocx_oxml::properties::CT_RPr {
610                bold: Some(true),
611                color: Some("C45911".to_string()),
612                ..Default::default()
613            }),
614    );
615    doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
616        .style("CustomHighlight");
617
618    doc.add_paragraph("");
619
620    // ── SECTION 10: DOCUMENT INTELLIGENCE ──
621    doc.add_paragraph("10. Document Intelligence API")
622        .style("Heading1");
623
624    let wc = doc.word_count();
625    let hc = doc.headings().len();
626    let ic = doc.images().len();
627    let lc = doc.links().len();
628    doc.add_paragraph(&format!("Word count: {wc}"));
629    doc.add_paragraph(&format!("Heading count: {hc}"));
630    doc.add_paragraph(&format!("Image count: {ic}"));
631    doc.add_paragraph(&format!("Link count: {lc}"));
632
633    let outline = doc.document_outline();
634    doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
635
636    let issues = doc.audit_accessibility();
637    doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
638    for issue in &issues {
639        doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
640    }
641
642    // ── SECTION 11: DOCUMENT MERGING ──
643    doc.add_paragraph("");
644    doc.add_paragraph("11. Document Merging").style("Heading1");
645    {
646        let mut other = Document::new();
647        other.add_paragraph("This paragraph was merged from another document using append().");
648        doc.append(&other);
649    }
650
651    doc.add_paragraph("");
652
653    // ── SUMMARY ──
654    doc.add_paragraph("Summary of Demonstrated Features")
655        .style("Heading1");
656    let features = [
657        "Page setup: size, margins, header/footer distance, gutter",
658        "Document metadata: title, author, subject, keywords",
659        "Headers and footers: text, images, different first page",
660        "Background images (full-page behind text)",
661        "Table of Contents generation with bookmarks",
662        "Text formatting: bold, italic, underline styles, strike, color, size, font",
663        "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
664        "Paragraph formatting: alignment, borders, shading, spacing, indentation",
665        "Pagination controls: keep-with-next, keep-together, widow control",
666        "Bullet and numbered lists with nesting",
667        "Tab stops with dot/underscore/hyphen leaders",
668        "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
669        "Row properties: header rows, exact height, cant-split",
670        "Cell properties: width, no-wrap, vertical alignment",
671        "Inline images and header images",
672        "Placeholder replacement (single and batch)",
673        "Regex-based find and replace",
674        "Content insertion at specific positions",
675        "Section breaks with mixed portrait/landscape",
676        "Custom paragraph and character styles",
677        "Document intelligence: word count, headings, outline, images, links",
678        "Accessibility audit",
679        "Document merging (append)",
680        "Export: DOCX, PDF, HTML, Markdown",
681    ];
682    for f in &features {
683        doc.add_bullet_list_item(f, 0);
684    }
685    doc.add_paragraph("");
686    doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
687        .alignment(Alignment::Center)
688        .shading("E2EFDA")
689        .border_all(BorderStyle::Single, 2, "00AA00");
690
691    doc
692}
693
694// =============================================================================
695// 2. PROPOSAL DOCUMENT — Deep navy + gold scheme
696// =============================================================================
697fn generate_proposal(_samples_dir: &Path) -> Document {
698    let mut doc = Document::new();
699
700    // Colors: Navy #1B2A4A, Gold #C5922E, Light #F4F1EB
701    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
702    doc.set_margins(
703        Length::inches(1.0),
704        Length::inches(1.0),
705        Length::inches(1.0),
706        Length::inches(1.0),
707    );
708    doc.set_title("AI Platform Modernization Proposal");
709    doc.set_author("Walter White");
710    doc.set_subject("Technology Proposal");
711    doc.set_keywords("proposal, AI, modernization, Tensorbee");
712
713    // Banner header
714    let logo_img = create_logo_png(220, 48);
715    let banner = build_header_banner_xml(
716        "rId1",
717        &BannerOpts {
718            bg_color: "1B2A4A",
719            banner_width: 7772400,
720            banner_height: 914400,
721            logo_width: 2011680,
722            logo_height: 438912,
723            logo_x_offset: 295125,
724            logo_y_offset: 237744,
725        },
726    );
727    doc.set_raw_header_with_images(
728        banner,
729        &[("rId1", &logo_img, "logo.png")],
730        rdocx_oxml::header_footer::HdrFtrType::Default,
731    );
732    doc.set_different_first_page(true);
733    let cover_banner = build_header_banner_xml(
734        "rId1",
735        &BannerOpts {
736            bg_color: "C5922E",
737            banner_width: 7772400,
738            banner_height: 914400,
739            logo_width: 2011680,
740            logo_height: 438912,
741            logo_x_offset: 295125,
742            logo_y_offset: 237744,
743        },
744    );
745    doc.set_raw_header_with_images(
746        cover_banner,
747        &[("rId1", &logo_img, "logo.png")],
748        rdocx_oxml::header_footer::HdrFtrType::First,
749    );
750    doc.set_margins(
751        Length::twips(2292),
752        Length::twips(1440),
753        Length::twips(1440),
754        Length::twips(1440),
755    );
756    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
757    doc.set_footer("Tensorbee — Confidential");
758
759    // Custom styles
760    doc.add_style(
761        StyleBuilder::paragraph("ProposalTitle", "Proposal Title")
762            .based_on("Normal")
763            .run_properties(rdocx_oxml::properties::CT_RPr {
764                bold: Some(true),
765                font_ascii: Some("Georgia".to_string()),
766                font_hansi: Some("Georgia".to_string()),
767                sz: Some(rdocx_oxml::HalfPoint(56)), // half-points → 28pt
768                color: Some("1B2A4A".to_string()),
769                ..Default::default()
770            }),
771    );
772
773    // ── Cover Page ──
774    for _ in 0..4 {
775        doc.add_paragraph("");
776    }
777    doc.add_paragraph("AI Platform Modernization")
778        .style("ProposalTitle")
779        .alignment(Alignment::Center);
780    doc.add_paragraph("")
781        .alignment(Alignment::Center)
782        .border_bottom(BorderStyle::Single, 8, "C5922E");
783    {
784        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
785        p.add_run("Prepared for Global Financial Corp.")
786            .size(14.0)
787            .color("666666")
788            .italic(true);
789    }
790    {
791        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
792        p.add_run("Prepared by: Walter White, CEO — Tensorbee")
793            .size(12.0)
794            .color("888888");
795    }
796    {
797        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
798        p.add_run("February 22, 2026").size(12.0).color("888888");
799    }
800
801    // ── TOC ──
802    doc.add_paragraph("").page_break_before(true);
803    doc.add_paragraph("Table of Contents").style("Heading1");
804    // We'll add a manual-style TOC since the insert_toc goes at a specific position
805    doc.insert_toc(doc.content_count(), 2);
806
807    // ── Executive Summary ──
808    doc.add_paragraph("").page_break_before(true);
809    doc.add_paragraph("Executive Summary").style("Heading1");
810    doc.add_paragraph(
811        "Tensorbee proposes a comprehensive modernization of Global Financial Corp's AI platform, \
812         transitioning from legacy batch-processing systems to a real-time inference architecture. \
813         This transformation will reduce model deployment time from weeks to hours, improve \
814         prediction accuracy by an estimated 23%, and deliver $4.2M in annual operational savings.",
815    )
816    .first_line_indent(Length::inches(0.3));
817    doc.add_paragraph(
818        "The project spans three phases over 18 months, with a total investment of $2.8M. \
819         Tensorbee brings deep expertise in ML infrastructure, having successfully completed \
820         similar transformations for three Fortune 500 financial institutions.",
821    )
822    .first_line_indent(Length::inches(0.3));
823
824    // Key metrics table
825    doc.add_paragraph("");
826    {
827        let mut tbl = doc
828            .add_table(5, 2)
829            .borders(BorderStyle::Single, 4, "1B2A4A")
830            .width_pct(80.0);
831        tbl.cell(0, 0).unwrap().set_text("Key Metric");
832        tbl.cell(0, 0).unwrap().shading("1B2A4A");
833        tbl.cell(0, 1).unwrap().set_text("Value");
834        tbl.cell(0, 1).unwrap().shading("1B2A4A");
835        let rows = [
836            ("Total Investment", "$2.8M"),
837            ("Annual Savings", "$4.2M"),
838            ("ROI (Year 1)", "150%"),
839            ("Timeline", "18 months"),
840        ];
841        for (i, (k, v)) in rows.iter().enumerate() {
842            tbl.cell(i + 1, 0).unwrap().set_text(k);
843            if i % 2 == 0 {
844                tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
845            }
846            tbl.cell(i + 1, 1).unwrap().set_text(v);
847            if i % 2 == 0 {
848                tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
849            }
850        }
851    }
852
853    // ── Problem Statement ──
854    doc.add_paragraph("").page_break_before(true);
855    doc.add_paragraph("Problem Statement").style("Heading1");
856    doc.add_paragraph(
857        "Global Financial Corp's current AI infrastructure faces three critical challenges:",
858    );
859    doc.add_numbered_list_item("Deployment Latency — New models take 3-4 weeks to deploy to production, compared to the industry benchmark of 2-3 days.", 0);
860    doc.add_numbered_list_item("Scalability Constraints — The batch processing architecture cannot handle real-time inference demands during peak trading hours.", 0);
861    doc.add_numbered_list_item("Technical Debt — Legacy Python 2.7 codebases and manual deployment scripts create reliability risks and slow iteration speed.", 0);
862
863    // ── Proposed Solution ──
864    doc.add_paragraph("").page_break_before(true);
865    doc.add_paragraph("Proposed Solution").style("Heading1");
866
867    doc.add_paragraph("Phase 1: Foundation (Months 1-6)")
868        .style("Heading2");
869    doc.add_bullet_list_item("Deploy Kubernetes-based ML serving infrastructure", 0);
870    doc.add_bullet_list_item("Implement CI/CD pipeline for model deployment", 0);
871    doc.add_bullet_list_item("Migrate top 5 production models to new platform", 0);
872
873    doc.add_paragraph("Phase 2: Expansion (Months 7-12)")
874        .style("Heading2");
875    doc.add_bullet_list_item(
876        "Real-time feature engineering pipeline (Apache Kafka + Flink)",
877        0,
878    );
879    doc.add_bullet_list_item("A/B testing framework for model variants", 0);
880    doc.add_bullet_list_item("Automated model monitoring and drift detection", 0);
881
882    doc.add_paragraph("Phase 3: Optimization (Months 13-18)")
883        .style("Heading2");
884    doc.add_bullet_list_item("GPU inference optimization (TensorRT/ONNX Runtime)", 0);
885    doc.add_bullet_list_item("Multi-region deployment for latency reduction", 0);
886    doc.add_bullet_list_item(
887        "Self-service model deployment portal for data science teams",
888        0,
889    );
890
891    // ── Budget ──
892    doc.add_paragraph("Budget Breakdown").style("Heading1");
893    {
894        let mut tbl = doc
895            .add_table(6, 3)
896            .borders(BorderStyle::Single, 4, "1B2A4A")
897            .width_pct(100.0);
898        tbl.cell(0, 0).unwrap().set_text("Category");
899        tbl.cell(0, 0).unwrap().shading("1B2A4A");
900        tbl.cell(0, 1).unwrap().set_text("Cost");
901        tbl.cell(0, 1).unwrap().shading("1B2A4A");
902        tbl.cell(0, 2).unwrap().set_text("Notes");
903        tbl.cell(0, 2).unwrap().shading("1B2A4A");
904        let items = [
905            (
906                "Infrastructure",
907                "$450,000",
908                "Cloud compute + GPU instances",
909            ),
910            ("Engineering Services", "$1,600,000", "12 FTE x 18 months"),
911            (
912                "Software Licenses",
913                "$350,000",
914                "Kafka, monitoring, CI/CD tools",
915            ),
916            (
917                "Training & Enablement",
918                "$200,000",
919                "Team training, documentation",
920            ),
921            ("Contingency (10%)", "$200,000", "Risk buffer"),
922        ];
923        for (i, (cat, cost, note)) in items.iter().enumerate() {
924            tbl.cell(i + 1, 0).unwrap().set_text(cat);
925            tbl.cell(i + 1, 1).unwrap().set_text(cost);
926            tbl.cell(i + 1, 2).unwrap().set_text(note);
927            if i % 2 == 0 {
928                tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
929                tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
930                tbl.cell(i + 1, 2).unwrap().shading("F4F1EB");
931            }
932        }
933    }
934
935    // ── Closing ──
936    doc.add_paragraph("");
937    doc.add_paragraph("Next Steps").style("Heading1");
938    doc.add_numbered_list_item(
939        "Schedule technical deep-dive with GFC engineering team (Week 1)",
940        0,
941    );
942    doc.add_numbered_list_item("Finalize scope and sign SOW (Week 2-3)", 0);
943    doc.add_numbered_list_item("Kick off Phase 1 with joint planning session (Week 4)", 0);
944
945    doc.add_paragraph("");
946    {
947        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
948        p.add_run("Walter White")
949            .bold(true)
950            .size(14.0)
951            .color("1B2A4A");
952    }
953    {
954        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
955        p.add_run("CEO, Tensorbee | walter@tensorbee.com")
956            .size(10.0)
957            .color("888888");
958    }
959
960    doc
961}
962
963// =============================================================================
964// 3. QUOTE / BILL OF MATERIALS — Teal + orange scheme
965// =============================================================================
966fn generate_quote(_samples_dir: &Path) -> Document {
967    let mut doc = Document::new();
968
969    // Colors: Teal #008B8B, Dark #1A3C3C, Orange #E07020, Light #F0F8F8
970    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
971    doc.set_margins(
972        Length::inches(0.75),
973        Length::inches(0.75),
974        Length::inches(0.75),
975        Length::inches(0.75),
976    );
977    doc.set_title("Quotation QT-2026-0147");
978    doc.set_author("Walter White");
979    doc.set_keywords("quote, BOM, Tensorbee");
980
981    doc.set_header("Tensorbee — Quotation");
982    doc.set_footer("QT-2026-0147 | Page");
983
984    // ── Header Block ──
985    {
986        let mut p = doc.add_paragraph("").alignment(Alignment::Left);
987        p.add_run("TENSORBEE")
988            .bold(true)
989            .size(28.0)
990            .color("008B8B")
991            .font("Helvetica");
992    }
993    doc.add_paragraph("123 Innovation Drive, Suite 400")
994        .alignment(Alignment::Left);
995    doc.add_paragraph("San Francisco, CA 94105 | +1 (415) 555-0199")
996        .alignment(Alignment::Left);
997    doc.add_paragraph("")
998        .border_bottom(BorderStyle::Single, 8, "008B8B");
999
1000    // Quote meta
1001    doc.add_paragraph("");
1002    {
1003        let mut tbl = doc.add_table(4, 4).width_pct(100.0);
1004        tbl.cell(0, 0).unwrap().set_text("QUOTATION");
1005        tbl.cell(0, 0).unwrap().shading("008B8B").grid_span(2);
1006        tbl.cell(0, 2).unwrap().set_text("DATE");
1007        tbl.cell(0, 2).unwrap().shading("008B8B");
1008        tbl.cell(0, 3).unwrap().set_text("VALID UNTIL");
1009        tbl.cell(0, 3).unwrap().shading("008B8B");
1010        tbl.cell(1, 0).unwrap().set_text("Quote #:");
1011        tbl.cell(1, 1).unwrap().set_text("QT-2026-0147");
1012        tbl.cell(1, 2).unwrap().set_text("Feb 22, 2026");
1013        tbl.cell(1, 3).unwrap().set_text("Mar 22, 2026");
1014        tbl.cell(2, 0).unwrap().set_text("Prepared by:");
1015        tbl.cell(2, 1).unwrap().set_text("Walter White");
1016        tbl.cell(2, 2).unwrap().set_text("Payment:");
1017        tbl.cell(2, 3).unwrap().set_text("Net 30");
1018        tbl.cell(3, 0).unwrap().set_text("Customer:");
1019        tbl.cell(3, 1).unwrap().set_text("Meridian Dynamics LLC");
1020        tbl.cell(3, 2).unwrap().set_text("Currency:");
1021        tbl.cell(3, 3).unwrap().set_text("USD");
1022    }
1023
1024    doc.add_paragraph("");
1025
1026    // ── Bill of Materials ──
1027    {
1028        let mut p = doc.add_paragraph("");
1029        p.add_run("Bill of Materials")
1030            .bold(true)
1031            .size(16.0)
1032            .color("1A3C3C");
1033    }
1034    doc.add_paragraph("");
1035
1036    {
1037        let mut tbl = doc
1038            .add_table(10, 6)
1039            .borders(BorderStyle::Single, 2, "008B8B")
1040            .width_pct(100.0)
1041            .layout_fixed();
1042        // Headers
1043        let hdrs = [
1044            "#",
1045            "Part Number",
1046            "Description",
1047            "Qty",
1048            "Unit Price",
1049            "Total",
1050        ];
1051        for (c, h) in hdrs.iter().enumerate() {
1052            tbl.cell(0, c).unwrap().set_text(h);
1053            tbl.cell(0, c).unwrap().shading("008B8B");
1054        }
1055
1056        let items: Vec<(&str, &str, &str, &str, &str)> = vec![
1057            (
1058                "TB-GPU-A100",
1059                "NVIDIA A100 80GB GPU",
1060                "4",
1061                "$12,500.00",
1062                "$50,000.00",
1063            ),
1064            (
1065                "TB-SRV-R750",
1066                "Dell R750xa Server Chassis",
1067                "2",
1068                "$8,200.00",
1069                "$16,400.00",
1070            ),
1071            (
1072                "TB-RAM-256G",
1073                "256GB DDR5 ECC Memory Module",
1074                "8",
1075                "$890.00",
1076                "$7,120.00",
1077            ),
1078            (
1079                "TB-SSD-3840",
1080                "3.84TB NVMe U.2 SSD",
1081                "8",
1082                "$1,150.00",
1083                "$9,200.00",
1084            ),
1085            (
1086                "TB-NET-CX7",
1087                "ConnectX-7 200GbE NIC",
1088                "4",
1089                "$1,800.00",
1090                "$7,200.00",
1091            ),
1092            (
1093                "TB-SW-48P",
1094                "48-Port 100GbE Switch",
1095                "1",
1096                "$22,000.00",
1097                "$22,000.00",
1098            ),
1099            (
1100                "TB-CAB-RACK",
1101                "42U Server Rack + PDU",
1102                "1",
1103                "$4,500.00",
1104                "$4,500.00",
1105            ),
1106            (
1107                "TB-SVC-INST",
1108                "Installation & Configuration",
1109                "1",
1110                "$8,500.00",
1111                "$8,500.00",
1112            ),
1113            (
1114                "TB-SVC-SUPP",
1115                "3-Year Premium Support",
1116                "1",
1117                "$15,000.00",
1118                "$15,000.00",
1119            ),
1120        ];
1121
1122        for (i, (pn, desc, qty, unit, total)) in items.iter().enumerate() {
1123            let row = i + 1;
1124            tbl.cell(row, 0).unwrap().set_text(&format!("{}", i + 1));
1125            tbl.cell(row, 1).unwrap().set_text(pn);
1126            tbl.cell(row, 2).unwrap().set_text(desc);
1127            tbl.cell(row, 3).unwrap().set_text(qty);
1128            tbl.cell(row, 4).unwrap().set_text(unit);
1129            tbl.cell(row, 5).unwrap().set_text(total);
1130            if i % 2 == 0 {
1131                for c in 0..6 {
1132                    tbl.cell(row, c).unwrap().shading("F0F8F8");
1133                }
1134            }
1135        }
1136    }
1137
1138    doc.add_paragraph("");
1139
1140    // ── Totals ──
1141    {
1142        let mut tbl = doc
1143            .add_table(4, 2)
1144            .borders(BorderStyle::Single, 2, "008B8B")
1145            .width(Length::inches(3.5))
1146            .alignment(Alignment::Right);
1147        tbl.cell(0, 0).unwrap().set_text("Subtotal");
1148        tbl.cell(0, 1).unwrap().set_text("$139,920.00");
1149        tbl.cell(1, 0).unwrap().set_text("Shipping & Handling");
1150        tbl.cell(1, 1).unwrap().set_text("$2,500.00");
1151        tbl.cell(2, 0).unwrap().set_text("Tax (8.625%)");
1152        tbl.cell(2, 1).unwrap().set_text("$12,068.10");
1153        tbl.cell(3, 0).unwrap().set_text("TOTAL");
1154        tbl.cell(3, 0).unwrap().shading("E07020");
1155        tbl.cell(3, 1).unwrap().set_text("$154,488.10");
1156        tbl.cell(3, 1).unwrap().shading("E07020");
1157    }
1158
1159    doc.add_paragraph("");
1160
1161    // ── Terms & Conditions ──
1162    {
1163        let mut p = doc.add_paragraph("");
1164        p.add_run("Terms & Conditions")
1165            .bold(true)
1166            .size(14.0)
1167            .color("1A3C3C");
1168    }
1169    doc.add_numbered_list_item(
1170        "This quotation is valid for 30 calendar days from the date of issue.",
1171        0,
1172    );
1173    doc.add_numbered_list_item(
1174        "All prices are in USD and exclusive of applicable taxes unless stated.",
1175        0,
1176    );
1177    doc.add_numbered_list_item("Standard lead time is 4-6 weeks from PO receipt.", 0);
1178    doc.add_numbered_list_item(
1179        "Payment terms: Net 30 from invoice date. 2% discount for payment within 10 days.",
1180        0,
1181    );
1182    doc.add_numbered_list_item(
1183        "Warranty: 3-year manufacturer warranty on all hardware components.",
1184        0,
1185    );
1186    doc.add_numbered_list_item(
1187        "Returns subject to 15% restocking fee if initiated after 14 days.",
1188        0,
1189    );
1190
1191    doc.add_paragraph("");
1192    doc.add_paragraph("")
1193        .border_bottom(BorderStyle::Single, 4, "008B8B");
1194    doc.add_paragraph("");
1195    {
1196        let mut p = doc.add_paragraph("");
1197        p.add_run("Accepted by: ").bold(true);
1198        p.add_run("___________________________ Date: ___________");
1199    }
1200    {
1201        let mut p = doc.add_paragraph("");
1202        p.add_run("Print Name: ").bold(true);
1203        p.add_run("___________________________ Title: ___________");
1204    }
1205
1206    doc
1207}
1208
1209// =============================================================================
1210// 4. INVOICE — Crimson + charcoal scheme
1211// =============================================================================
1212fn generate_invoice(_samples_dir: &Path) -> Document {
1213    let mut doc = Document::new();
1214
1215    // Colors: Crimson #B22222, Charcoal #333333, Light #FAF0F0
1216    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1217    doc.set_margins(
1218        Length::inches(0.75),
1219        Length::inches(0.75),
1220        Length::inches(0.75),
1221        Length::inches(0.75),
1222    );
1223    doc.set_title("Invoice INV-2026-0392");
1224    doc.set_author("Walter White");
1225    doc.set_keywords("invoice, Tensorbee");
1226
1227    doc.set_footer("Tensorbee — Thank you for your business!");
1228
1229    // ── Company & Invoice Header ──
1230    {
1231        let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1232        // Company name left, INVOICE right
1233        {
1234            let mut cell = tbl.cell(0, 0).unwrap();
1235            let mut p = cell.add_paragraph("");
1236            p.add_run("TENSORBEE")
1237                .bold(true)
1238                .size(32.0)
1239                .color("B22222")
1240                .font("Helvetica");
1241        }
1242        {
1243            let mut cell = tbl.cell(0, 1).unwrap();
1244            let mut p = cell.add_paragraph("");
1245            p.add_run("INVOICE").bold(true).size(32.0).color("333333");
1246        }
1247        tbl.cell(1, 0)
1248            .unwrap()
1249            .set_text("123 Innovation Drive, Suite 400");
1250        tbl.cell(1, 1).unwrap().set_text("Invoice #: INV-2026-0392");
1251        tbl.cell(2, 0).unwrap().set_text("San Francisco, CA 94105");
1252        tbl.cell(2, 1).unwrap().set_text("Date: February 22, 2026");
1253        tbl.cell(3, 0).unwrap().set_text("walter@tensorbee.com");
1254        tbl.cell(3, 1).unwrap().set_text("Due Date: March 24, 2026");
1255    }
1256
1257    doc.add_paragraph("")
1258        .border_bottom(BorderStyle::Thick, 8, "B22222");
1259    doc.add_paragraph("");
1260
1261    // ── Bill To / Ship To ──
1262    {
1263        let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1264        tbl.cell(0, 0).unwrap().set_text("BILL TO");
1265        tbl.cell(0, 0).unwrap().shading("B22222");
1266        tbl.cell(0, 1).unwrap().set_text("SHIP TO");
1267        tbl.cell(0, 1).unwrap().shading("B22222");
1268        tbl.cell(1, 0).unwrap().set_text("Meridian Dynamics LLC");
1269        tbl.cell(1, 1).unwrap().set_text("Meridian Dynamics LLC");
1270        tbl.cell(2, 0).unwrap().set_text("456 Enterprise Blvd");
1271        tbl.cell(2, 1).unwrap().set_text("Attn: Server Room B");
1272        tbl.cell(3, 0).unwrap().set_text("Austin, TX 78701");
1273        tbl.cell(3, 1)
1274            .unwrap()
1275            .set_text("456 Enterprise Blvd, Austin, TX 78701");
1276    }
1277
1278    doc.add_paragraph("");
1279
1280    // ── Line Items ──
1281    {
1282        let mut tbl = doc
1283            .add_table(8, 5)
1284            .borders(BorderStyle::Single, 2, "B22222")
1285            .width_pct(100.0);
1286        let hdrs = ["Description", "Qty", "Unit Price", "Tax", "Amount"];
1287        for (c, h) in hdrs.iter().enumerate() {
1288            tbl.cell(0, c).unwrap().set_text(h);
1289            tbl.cell(0, c).unwrap().shading("B22222");
1290        }
1291        let items = [
1292            (
1293                "ML Infrastructure Setup — Phase 1",
1294                "1",
1295                "$45,000.00",
1296                "$3,881.25",
1297                "$48,881.25",
1298            ),
1299            (
1300                "Data Pipeline Development (160 hrs)",
1301                "160",
1302                "$225.00",
1303                "$3,105.00",
1304                "$39,105.00",
1305            ),
1306            (
1307                "GPU Cluster Configuration",
1308                "1",
1309                "$12,000.00",
1310                "$1,035.00",
1311                "$13,035.00",
1312            ),
1313            (
1314                "API Gateway Implementation",
1315                "1",
1316                "$18,500.00",
1317                "$1,595.63",
1318                "$20,095.63",
1319            ),
1320            (
1321                "Load Testing & QA (80 hrs)",
1322                "80",
1323                "$195.00",
1324                "$1,345.50",
1325                "$16,945.50",
1326            ),
1327            (
1328                "Documentation & Training",
1329                "1",
1330                "$8,000.00",
1331                "$690.00",
1332                "$8,690.00",
1333            ),
1334            (
1335                "Project Management (3 months)",
1336                "3",
1337                "$6,500.00",
1338                "$1,679.63",
1339                "$21,179.63",
1340            ),
1341        ];
1342        for (i, (desc, qty, unit, tax, amt)) in items.iter().enumerate() {
1343            let r = i + 1;
1344            tbl.cell(r, 0).unwrap().set_text(desc);
1345            tbl.cell(r, 1).unwrap().set_text(qty);
1346            tbl.cell(r, 2).unwrap().set_text(unit);
1347            tbl.cell(r, 3).unwrap().set_text(tax);
1348            tbl.cell(r, 4).unwrap().set_text(amt);
1349            if i % 2 == 0 {
1350                for c in 0..5 {
1351                    tbl.cell(r, c).unwrap().shading("FAF0F0");
1352                }
1353            }
1354        }
1355    }
1356
1357    doc.add_paragraph("");
1358
1359    // ── Totals ──
1360    {
1361        let mut tbl = doc
1362            .add_table(4, 2)
1363            .borders(BorderStyle::Single, 2, "B22222")
1364            .width(Length::inches(3.0))
1365            .alignment(Alignment::Right);
1366        tbl.cell(0, 0).unwrap().set_text("Subtotal");
1367        tbl.cell(0, 1).unwrap().set_text("$154,600.00");
1368        tbl.cell(1, 0).unwrap().set_text("Tax (8.625%)");
1369        tbl.cell(1, 1).unwrap().set_text("$13,334.25");
1370        tbl.cell(2, 0).unwrap().set_text("Discount (5%)");
1371        tbl.cell(2, 1).unwrap().set_text("-$7,730.00");
1372        tbl.cell(3, 0).unwrap().set_text("AMOUNT DUE");
1373        tbl.cell(3, 0).unwrap().shading("B22222");
1374        tbl.cell(3, 1).unwrap().set_text("$160,204.25");
1375        tbl.cell(3, 1).unwrap().shading("B22222");
1376    }
1377
1378    doc.add_paragraph("");
1379    doc.add_paragraph("");
1380
1381    // ── Payment Details ──
1382    {
1383        let mut p = doc.add_paragraph("");
1384        p.add_run("Payment Details")
1385            .bold(true)
1386            .size(14.0)
1387            .color("333333");
1388    }
1389    doc.add_paragraph("Bank: Silicon Valley Bank")
1390        .indent_left(Length::inches(0.3));
1391    doc.add_paragraph("Account: Tensorbee Inc. — 0847-2953-1120")
1392        .indent_left(Length::inches(0.3));
1393    doc.add_paragraph("Routing: 121140399")
1394        .indent_left(Length::inches(0.3));
1395    doc.add_paragraph("Swift: SVBKUS6S")
1396        .indent_left(Length::inches(0.3));
1397
1398    doc.add_paragraph("");
1399
1400    doc.add_paragraph("Please include invoice number INV-2026-0392 in the payment reference.")
1401        .shading("FAF0F0")
1402        .border_all(BorderStyle::Single, 2, "B22222");
1403
1404    doc
1405}
1406
1407// =============================================================================
1408// 5. REPORT — Forest green + earth tones, with images & hierarchical sections
1409// =============================================================================
1410fn generate_report(_samples_dir: &Path) -> Document {
1411    let mut doc = Document::new();
1412
1413    // Colors: Forest #2D5016, Sage #6B8E23, Earth #8B7355, Cream #FFFAF0
1414    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1415    doc.set_margins(
1416        Length::inches(1.0),
1417        Length::inches(1.0),
1418        Length::inches(1.0),
1419        Length::inches(1.0),
1420    );
1421    doc.set_title("Q4 2025 Environmental Impact Report");
1422    doc.set_author("Walter White");
1423    doc.set_subject("Quarterly Environmental Report");
1424    doc.set_keywords("environment, sustainability, report, Tensorbee");
1425
1426    doc.set_different_first_page(true);
1427    doc.set_first_page_header("");
1428    doc.set_header("Tensorbee — Q4 2025 Environmental Impact Report");
1429    doc.set_footer("CONFIDENTIAL — Page");
1430
1431    // ── Cover ──
1432    let cover_bg = create_sample_png(612, 792, [20, 50, 15]);
1433    doc.add_background_image(&cover_bg, "report_cover.png");
1434
1435    for _ in 0..5 {
1436        doc.add_paragraph("");
1437    }
1438    {
1439        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1440        p.add_run("Q4 2025")
1441            .bold(true)
1442            .size(48.0)
1443            .color("FFFFFF")
1444            .font("Georgia");
1445    }
1446    {
1447        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1448        p.add_run("Environmental Impact Report")
1449            .size(24.0)
1450            .color("90EE90")
1451            .font("Georgia")
1452            .italic(true);
1453    }
1454    doc.add_paragraph("");
1455    {
1456        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1457        p.add_run("Tensorbee — Sustainability Division")
1458            .size(14.0)
1459            .color("C0C0C0");
1460    }
1461    {
1462        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1463        p.add_run("Prepared by Walter White, Chief Sustainability Officer")
1464            .size(11.0)
1465            .color("AAAAAA");
1466    }
1467
1468    // ── TOC ──
1469    doc.add_paragraph("").page_break_before(true);
1470    doc.insert_toc(doc.content_count(), 3);
1471
1472    // ── Section 1: Executive Overview ──
1473    doc.add_paragraph("").page_break_before(true);
1474    doc.add_paragraph("Executive Overview").style("Heading1");
1475    doc.add_paragraph(
1476        "This report presents Tensorbee's environmental performance for Q4 2025. Our \
1477         sustainability initiatives have yielded a 34% reduction in carbon emissions compared \
1478         to Q4 2024, exceeding our target of 25%. Key achievements include the transition to \
1479         100% renewable energy in our primary data centers and the launch of our carbon offset \
1480         marketplace.",
1481    )
1482    .first_line_indent(Length::inches(0.3));
1483
1484    // Image: performance chart
1485    let chart_img = create_chart_png(400, 200);
1486    doc.add_paragraph("");
1487    doc.add_picture(
1488        &chart_img,
1489        "performance_chart.png",
1490        Length::inches(5.0),
1491        Length::inches(2.5),
1492    )
1493    .alignment(Alignment::Center);
1494    {
1495        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1496        p.add_run("Figure 1: Quarterly Carbon Emissions (tonnes CO2e)")
1497            .italic(true)
1498            .size(9.0)
1499            .color("666666");
1500    }
1501
1502    // ── Section 2: Energy Consumption ──
1503    doc.add_paragraph("").page_break_before(true);
1504    doc.add_paragraph("Energy Consumption").style("Heading1");
1505
1506    doc.add_paragraph("Data Center Operations")
1507        .style("Heading2");
1508    doc.add_paragraph(
1509        "Our three primary data centers consumed a combined 4.2 GWh in Q4 2025, \
1510         a 12% reduction from Q3 2025 driven by improved cooling efficiency and \
1511         server consolidation.",
1512    );
1513
1514    // Data table
1515    {
1516        let mut tbl = doc
1517            .add_table(5, 4)
1518            .borders(BorderStyle::Single, 4, "2D5016")
1519            .width_pct(100.0);
1520        let hdrs = ["Data Center", "Capacity (MW)", "Usage (GWh)", "PUE"];
1521        for (c, h) in hdrs.iter().enumerate() {
1522            tbl.cell(0, c).unwrap().set_text(h);
1523            tbl.cell(0, c).unwrap().shading("2D5016");
1524        }
1525        let rows = [
1526            ("San Francisco (Primary)", "3.2", "1.8", "1.12"),
1527            ("Dublin (EU)", "2.1", "1.2", "1.18"),
1528            ("Singapore (APAC)", "1.8", "1.2", "1.24"),
1529            ("TOTAL", "7.1", "4.2", "1.17 avg"),
1530        ];
1531        for (i, (dc, cap, use_, pue)) in rows.iter().enumerate() {
1532            tbl.cell(i + 1, 0).unwrap().set_text(dc);
1533            tbl.cell(i + 1, 1).unwrap().set_text(cap);
1534            tbl.cell(i + 1, 2).unwrap().set_text(use_);
1535            tbl.cell(i + 1, 3).unwrap().set_text(pue);
1536            if i == 3 {
1537                for c in 0..4 {
1538                    tbl.cell(i + 1, c).unwrap().shading("E2EFDA");
1539                }
1540            }
1541        }
1542    }
1543
1544    doc.add_paragraph("");
1545
1546    doc.add_paragraph("Renewable Energy Mix").style("Heading2");
1547    doc.add_paragraph("Breakdown of energy sources across all facilities:");
1548
1549    doc.add_bullet_list_item("Solar PV: 42% (1.76 GWh)", 0);
1550    doc.add_bullet_list_item("Wind Power Purchase Agreements: 38% (1.60 GWh)", 0);
1551    doc.add_bullet_list_item("Hydroelectric: 12% (0.50 GWh)", 0);
1552    doc.add_bullet_list_item("Grid (non-renewable): 8% (0.34 GWh)", 0);
1553
1554    // ── Section 3: Water & Waste ──
1555    doc.add_paragraph("Water & Waste Management")
1556        .style("Heading1");
1557
1558    doc.add_paragraph("Water Usage").style("Heading2");
1559    doc.add_paragraph(
1560        "Total water consumption was 12.4 million gallons, a 15% reduction from Q3. \
1561         Our closed-loop cooling systems now recycle 78% of water used in cooling operations.",
1562    );
1563
1564    doc.add_paragraph("Waste Reduction").style("Heading2");
1565    doc.add_paragraph("E-waste management results for Q4:")
1566        .keep_with_next(true);
1567    {
1568        let mut tbl = doc
1569            .add_table(4, 3)
1570            .borders(BorderStyle::Single, 2, "6B8E23")
1571            .width_pct(80.0);
1572        tbl.cell(0, 0).unwrap().set_text("Category");
1573        tbl.cell(0, 0).unwrap().shading("6B8E23");
1574        tbl.cell(0, 1).unwrap().set_text("Weight (kg)");
1575        tbl.cell(0, 1).unwrap().shading("6B8E23");
1576        tbl.cell(0, 2).unwrap().set_text("Recycled %");
1577        tbl.cell(0, 2).unwrap().shading("6B8E23");
1578        let rows = [
1579            ("Server Hardware", "2,450", "94%"),
1580            ("Networking Equipment", "820", "91%"),
1581            ("Storage Media", "340", "99%"),
1582        ];
1583        for (i, (cat, wt, pct)) in rows.iter().enumerate() {
1584            tbl.cell(i + 1, 0).unwrap().set_text(cat);
1585            tbl.cell(i + 1, 1).unwrap().set_text(wt);
1586            tbl.cell(i + 1, 2).unwrap().set_text(pct);
1587        }
1588    }
1589
1590    // ── Section 4: Initiatives ──
1591    doc.add_paragraph("").page_break_before(true);
1592    doc.add_paragraph("Q1 2026 Initiatives").style("Heading1");
1593
1594    doc.add_paragraph("Planned Programs").style("Heading2");
1595    doc.add_numbered_list_item(
1596        "Deploy on-site battery storage at SF data center (2 MWh capacity)",
1597        0,
1598    );
1599    doc.add_numbered_list_item("Pilot immersion cooling in Dublin facility", 0);
1600    doc.add_numbered_list_item("Launch employee commute carbon offset program", 0);
1601    doc.add_numbered_list_item("Achieve ISO 14001 certification for Singapore facility", 0);
1602
1603    doc.add_paragraph("Investment Targets").style("Heading2");
1604    doc.add_paragraph("Sustainability CapEx allocation for FY2026:")
1605        .keep_with_next(true);
1606    {
1607        let mut tbl = doc
1608            .add_table(5, 2)
1609            .borders(BorderStyle::Single, 4, "2D5016");
1610        tbl.cell(0, 0).unwrap().set_text("Initiative");
1611        tbl.cell(0, 0).unwrap().shading("2D5016");
1612        tbl.cell(0, 1).unwrap().set_text("Budget");
1613        tbl.cell(0, 1).unwrap().shading("2D5016");
1614        let rows = [
1615            ("Battery Storage", "$1.2M"),
1616            ("Immersion Cooling Pilot", "$800K"),
1617            ("Solar Panel Expansion", "$2.1M"),
1618            ("Carbon Credits", "$500K"),
1619        ];
1620        for (i, (init, budget)) in rows.iter().enumerate() {
1621            tbl.cell(i + 1, 0).unwrap().set_text(init);
1622            tbl.cell(i + 1, 1).unwrap().set_text(budget);
1623            if i % 2 == 0 {
1624                tbl.cell(i + 1, 0).unwrap().shading("FFFAF0");
1625                tbl.cell(i + 1, 1).unwrap().shading("FFFAF0");
1626            }
1627        }
1628    }
1629
1630    // Image: sustainability roadmap
1631    let roadmap_img = create_sample_png(500, 100, [40, 80, 30]);
1632    doc.add_paragraph("");
1633    doc.add_picture(
1634        &roadmap_img,
1635        "roadmap.png",
1636        Length::inches(6.0),
1637        Length::inches(1.2),
1638    )
1639    .alignment(Alignment::Center);
1640    {
1641        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1642        p.add_run("Figure 2: 2026 Sustainability Roadmap")
1643            .italic(true)
1644            .size(9.0)
1645            .color("666666");
1646    }
1647
1648    doc.add_paragraph("");
1649    doc.add_paragraph(
1650        "For questions about this report, contact Walter White at sustainability@tensorbee.com.",
1651    )
1652    .shading("FFFAF0")
1653    .border_all(BorderStyle::Single, 2, "2D5016");
1654
1655    doc
1656}
1657
1658// =============================================================================
1659// 6. LETTER — Slate blue + silver scheme
1660// =============================================================================
1661fn generate_letter(_samples_dir: &Path) -> Document {
1662    let mut doc = Document::new();
1663
1664    // Colors: Slate #4A5568, Blue accent #3182CE
1665    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1666    doc.set_margins(
1667        Length::inches(1.25),
1668        Length::inches(1.25),
1669        Length::inches(1.25),
1670        Length::inches(1.25),
1671    );
1672    doc.set_title("Business Letter");
1673    doc.set_author("Walter White");
1674
1675    // Banner header using raw XML
1676    let logo_img = create_logo_png(220, 48);
1677    let banner = build_header_banner_xml(
1678        "rId1",
1679        &BannerOpts {
1680            bg_color: "4A5568",
1681            banner_width: 7772400,
1682            banner_height: 731520, // ~0.8"
1683            logo_width: 2011680,
1684            logo_height: 438912,
1685            logo_x_offset: 295125,
1686            logo_y_offset: 146304,
1687        },
1688    );
1689    doc.set_raw_header_with_images(
1690        banner,
1691        &[("rId1", &logo_img, "logo.png")],
1692        rdocx_oxml::header_footer::HdrFtrType::Default,
1693    );
1694    doc.set_margins(
1695        Length::twips(2000),
1696        Length::twips(1800),
1697        Length::twips(1440),
1698        Length::twips(1800),
1699    );
1700    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
1701
1702    // Footer with contact
1703    doc.set_footer(
1704        "Tensorbee | 123 Innovation Drive, Suite 400 | San Francisco, CA 94105 | tensorbee.com",
1705    );
1706
1707    // ── Sender Address ──
1708    doc.add_paragraph("Walter White");
1709    doc.add_paragraph("Chief Executive Officer");
1710    doc.add_paragraph("Tensorbee");
1711    doc.add_paragraph("123 Innovation Drive, Suite 400");
1712    doc.add_paragraph("San Francisco, CA 94105");
1713    doc.add_paragraph("");
1714    doc.add_paragraph("February 22, 2026");
1715
1716    doc.add_paragraph("");
1717
1718    // ── Recipient ──
1719    doc.add_paragraph("Ms. Sarah Chen");
1720    doc.add_paragraph("VP of Engineering");
1721    doc.add_paragraph("Meridian Dynamics LLC");
1722    doc.add_paragraph("456 Enterprise Boulevard");
1723    doc.add_paragraph("Austin, TX 78701");
1724
1725    doc.add_paragraph("");
1726
1727    // ── Subject ──
1728    {
1729        let mut p = doc.add_paragraph("");
1730        p.add_run("Re: Strategic Technology Partnership — Phase 2 Expansion")
1731            .bold(true);
1732    }
1733
1734    doc.add_paragraph("");
1735
1736    // ── Body ──
1737    doc.add_paragraph("Dear Ms. Chen,");
1738    doc.add_paragraph("");
1739
1740    doc.add_paragraph(
1741        "I am writing to express Tensorbee's enthusiasm for expanding our strategic technology \
1742         partnership with Meridian Dynamics. Following the successful completion of Phase 1, \
1743         which delivered a 40% improvement in your ML inference pipeline throughput, we believe \
1744         the foundation is firmly established for an ambitious Phase 2 engagement.",
1745    )
1746    .first_line_indent(Length::inches(0.5));
1747
1748    doc.add_paragraph(
1749        "During our recent executive review, your team highlighted three priority areas for \
1750         the next phase of collaboration:",
1751    )
1752    .first_line_indent(Length::inches(0.5));
1753
1754    doc.add_numbered_list_item(
1755        "Real-time anomaly detection for your financial transaction monitoring system, \
1756         targeting sub-100ms latency at 50,000 transactions per second.",
1757        0,
1758    );
1759    doc.add_numbered_list_item(
1760        "Federated learning infrastructure to enable model training across your distributed \
1761         data centers without centralizing sensitive financial data.",
1762        0,
1763    );
1764    doc.add_numbered_list_item(
1765        "MLOps automation to reduce your model deployment cycle from the current 5 days \
1766         to under 4 hours.",
1767        0,
1768    );
1769
1770    doc.add_paragraph(
1771        "Our engineering team has prepared a detailed technical proposal addressing each of \
1772         these areas. We have allocated a dedicated team of eight senior engineers, led by \
1773         our CTO, to ensure continuity with the Phase 1 team your organization has already \
1774         built a productive working relationship with.",
1775    )
1776    .first_line_indent(Length::inches(0.5));
1777
1778    doc.add_paragraph(
1779        "I would welcome the opportunity to present our Phase 2 proposal in person. My \
1780         assistant will reach out to coordinate a meeting at your convenience during the \
1781         first week of March.",
1782    )
1783    .first_line_indent(Length::inches(0.5));
1784
1785    doc.add_paragraph("");
1786
1787    doc.add_paragraph("Warm regards,");
1788    doc.add_paragraph("");
1789    doc.add_paragraph("");
1790
1791    // Signature line
1792    doc.add_paragraph("")
1793        .border_bottom(BorderStyle::Single, 4, "4A5568");
1794    {
1795        let mut p = doc.add_paragraph("");
1796        p.add_run("Walter White").bold(true).size(12.0);
1797    }
1798    {
1799        let mut p = doc.add_paragraph("");
1800        p.add_run("Chief Executive Officer, Tensorbee")
1801            .italic(true)
1802            .size(10.0)
1803            .color("666666");
1804    }
1805    {
1806        let mut p = doc.add_paragraph("");
1807        p.add_run("walter@tensorbee.com | +1 (415) 555-0199")
1808            .size(10.0)
1809            .color("888888");
1810    }
1811
1812    doc
1813}
1814
1815// =============================================================================
1816// 7. EMPLOYMENT CONTRACT — Purple + charcoal scheme
1817// =============================================================================
1818fn generate_contract(_samples_dir: &Path) -> Document {
1819    let mut doc = Document::new();
1820
1821    // Colors: Purple #5B2C6F, Plum #8E44AD, Gray #2C3E50
1822    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1823    doc.set_margins(
1824        Length::inches(1.25),
1825        Length::inches(1.0),
1826        Length::inches(1.0),
1827        Length::inches(1.0),
1828    );
1829    doc.set_title("Employment Agreement");
1830    doc.set_author("Tensorbee HR Department");
1831    doc.set_subject("Employment Contract — Walter White");
1832    doc.set_keywords("employment, contract, agreement, Tensorbee");
1833
1834    doc.set_header("TENSORBEE — Employment Agreement");
1835    doc.set_footer("Employment Agreement — Walter White — Page");
1836
1837    // ── Title Block ──
1838    doc.add_paragraph("")
1839        .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1840    {
1841        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1842        p.add_run("EMPLOYMENT AGREEMENT")
1843            .bold(true)
1844            .size(24.0)
1845            .color("5B2C6F")
1846            .font("Georgia");
1847    }
1848    doc.add_paragraph("")
1849        .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1850    doc.add_paragraph("");
1851
1852    // ── Parties ──
1853    doc.add_paragraph(
1854        "This Employment Agreement (\"Agreement\") is entered into as of February 22, 2026 \
1855         (\"Effective Date\"), by and between:",
1856    );
1857
1858    doc.add_paragraph("");
1859
1860    {
1861        let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1862        p.add_run("EMPLOYER: ").bold(true);
1863        p.add_run(
1864            "Tensorbee, Inc., a Delaware corporation with its principal place of business at \
1865                   123 Innovation Drive, Suite 400, San Francisco, CA 94105 (\"Company\")",
1866        );
1867    }
1868    doc.add_paragraph("");
1869    {
1870        let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1871        p.add_run("EMPLOYEE: ").bold(true);
1872        p.add_run(
1873            "Walter White, residing at 308 Negra Arroyo Lane, Albuquerque, NM 87104 (\"Employee\")",
1874        );
1875    }
1876
1877    doc.add_paragraph("");
1878    doc.add_paragraph("The Company and Employee are collectively referred to as the \"Parties.\"");
1879    doc.add_paragraph("");
1880
1881    // ── Article 1: Position and Duties ──
1882    doc.add_paragraph("Article 1 — Position and Duties")
1883        .style("Heading1");
1884    {
1885        let mut p = doc.add_paragraph("");
1886        p.add_run("1.1 ").bold(true);
1887        p.add_run("The Company hereby employs the Employee as ");
1888        p.add_run("Chief Executive Officer (CEO)")
1889            .bold(true)
1890            .italic(true);
1891        p.add_run(", reporting directly to the Board of Directors.");
1892    }
1893    {
1894        let mut p = doc.add_paragraph("");
1895        p.add_run("1.2 ").bold(true);
1896        p.add_run("The Employee shall devote full working time, attention, and best efforts to \
1897                   the performance of duties as reasonably assigned by the Board, including but not limited to:");
1898    }
1899    doc.add_bullet_list_item(
1900        "Setting and executing the Company's strategic vision and business plan",
1901        0,
1902    );
1903    doc.add_bullet_list_item(
1904        "Overseeing all operations, engineering, and commercial activities",
1905        0,
1906    );
1907    doc.add_bullet_list_item(
1908        "Representing the Company to investors, customers, and the public",
1909        0,
1910    );
1911    doc.add_bullet_list_item("Recruiting, developing, and retaining key talent", 0);
1912
1913    {
1914        let mut p = doc.add_paragraph("");
1915        p.add_run("1.3 ").bold(true);
1916        p.add_run(
1917            "The Employee's primary work location shall be the Company's San Francisco \
1918                   headquarters, with reasonable travel as required by business needs.",
1919        );
1920    }
1921
1922    // ── Article 2: Compensation ──
1923    doc.add_paragraph("Article 2 — Compensation and Benefits")
1924        .style("Heading1");
1925    {
1926        let mut p = doc.add_paragraph("");
1927        p.add_run("2.1 Base Salary. ").bold(true);
1928        p.add_run("The Company shall pay the Employee an annual base salary of ");
1929        p.add_run("$375,000.00").bold(true);
1930        p.add_run(" (Three Hundred Seventy-Five Thousand Dollars), payable in accordance with the \
1931                   Company's standard payroll schedule, less applicable withholdings and deductions.");
1932    }
1933    {
1934        let mut p = doc.add_paragraph("");
1935        p.add_run("2.2 Annual Bonus. ").bold(true);
1936        p.add_run("The Employee shall be eligible for an annual performance bonus of up to ");
1937        p.add_run("40%").bold(true);
1938        p.add_run(
1939            " of base salary, based on achievement of mutually agreed performance objectives.",
1940        );
1941    }
1942    {
1943        let mut p = doc.add_paragraph("");
1944        p.add_run("2.3 Equity. ").bold(true);
1945        p.add_run("Subject to Board approval, the Employee shall receive a stock option grant of ");
1946        p.add_run("500,000 shares").bold(true);
1947        p.add_run(
1948            " of the Company's common stock, vesting over four (4) years with a one-year cliff, \
1949                   at an exercise price equal to the fair market value on the date of grant.",
1950        );
1951    }
1952
1953    // Compensation summary table
1954    doc.add_paragraph("");
1955    {
1956        let mut tbl = doc
1957            .add_table(5, 2)
1958            .borders(BorderStyle::Single, 4, "5B2C6F")
1959            .width_pct(70.0)
1960            .alignment(Alignment::Center);
1961        tbl.cell(0, 0).unwrap().set_text("Compensation Element");
1962        tbl.cell(0, 0).unwrap().shading("5B2C6F");
1963        tbl.cell(0, 1).unwrap().set_text("Value");
1964        tbl.cell(0, 1).unwrap().shading("5B2C6F");
1965        let items = [
1966            ("Base Salary", "$375,000/year"),
1967            ("Target Bonus", "Up to 40% ($150,000)"),
1968            ("Equity Grant", "500,000 shares (4yr vest)"),
1969            ("Total Target Comp", "$525,000 + equity"),
1970        ];
1971        for (i, (elem, val)) in items.iter().enumerate() {
1972            tbl.cell(i + 1, 0).unwrap().set_text(elem);
1973            tbl.cell(i + 1, 1).unwrap().set_text(val);
1974            if i == 3 {
1975                tbl.cell(i + 1, 0).unwrap().shading("E8DAEF");
1976                tbl.cell(i + 1, 1).unwrap().shading("E8DAEF");
1977            }
1978        }
1979    }
1980
1981    // ── Article 3: Benefits ──
1982    doc.add_paragraph("").page_break_before(true);
1983    doc.add_paragraph("Article 3 — Benefits").style("Heading1");
1984    {
1985        let mut p = doc.add_paragraph("");
1986        p.add_run("3.1 ").bold(true);
1987        p.add_run(
1988            "The Employee shall be entitled to participate in all benefit programs \
1989                   generally available to senior executives, including:",
1990        );
1991    }
1992    doc.add_bullet_list_item(
1993        "Medical, dental, and vision insurance (100% premium coverage for employee and dependents)",
1994        0,
1995    );
1996    doc.add_bullet_list_item("401(k) retirement plan with 6% company match", 0);
1997    doc.add_bullet_list_item("Life insurance and long-term disability coverage", 0);
1998    doc.add_bullet_list_item("Annual professional development allowance of $10,000", 0);
1999
2000    {
2001        let mut p = doc.add_paragraph("");
2002        p.add_run("3.2 Paid Time Off. ").bold(true);
2003        p.add_run(
2004            "The Employee shall receive 25 days of paid vacation per year, plus Company holidays, \
2005                   accruing on a monthly basis.",
2006        );
2007    }
2008
2009    // ── Article 4: Term and Termination ──
2010    doc.add_paragraph("Article 4 — Term and Termination")
2011        .style("Heading1");
2012    {
2013        let mut p = doc.add_paragraph("");
2014        p.add_run("4.1 At-Will Employment. ").bold(true);
2015        p.add_run(
2016            "This Agreement is for at-will employment and may be terminated by either Party \
2017                   at any time, with or without cause, subject to the notice provisions herein.",
2018        );
2019    }
2020    {
2021        let mut p = doc.add_paragraph("");
2022        p.add_run("4.2 Notice Period. ").bold(true);
2023        p.add_run("Either Party shall provide ");
2024        p.add_run("ninety (90) days").bold(true);
2025        p.add_run(" written notice of termination, or pay in lieu thereof.");
2026    }
2027    {
2028        let mut p = doc.add_paragraph("");
2029        p.add_run("4.3 Severance. ").bold(true);
2030        p.add_run("In the event of termination by the Company without Cause, the Employee shall \
2031                   receive (a) twelve (12) months of base salary continuation, (b) pro-rata bonus \
2032                   for the year of termination, and (c) twelve (12) months of COBRA premium coverage.");
2033    }
2034
2035    // ── Article 5: Confidentiality ──
2036    doc.add_paragraph("Article 5 — Confidentiality and IP")
2037        .style("Heading1");
2038    {
2039        let mut p = doc.add_paragraph("");
2040        p.add_run("5.1 ").bold(true);
2041        p.add_run(
2042            "The Employee agrees to maintain strict confidentiality of all proprietary \
2043                   information, trade secrets, and business plans of the Company during and after \
2044                   employment.",
2045        );
2046    }
2047    {
2048        let mut p = doc.add_paragraph("");
2049        p.add_run("5.2 ").bold(true);
2050        p.add_run(
2051            "All intellectual property, inventions, and works of authorship created by the \
2052                   Employee during the term of employment and within the scope of duties shall be \
2053                   the sole and exclusive property of the Company.",
2054        );
2055    }
2056
2057    // ── Article 6: Non-Compete ──
2058    doc.add_paragraph("Article 6 — Non-Competition")
2059        .style("Heading1");
2060    {
2061        let mut p = doc.add_paragraph("");
2062        p.add_run("6.1 ").bold(true);
2063        p.add_run("For a period of twelve (12) months following termination, the Employee agrees \
2064                   not to engage in any business that directly competes with the Company's core \
2065                   business of AI infrastructure and ML operations services, within the United States.");
2066    }
2067
2068    // ── Article 7: General Provisions ──
2069    doc.add_paragraph("Article 7 — General Provisions")
2070        .style("Heading1");
2071    {
2072        let mut p = doc.add_paragraph("");
2073        p.add_run("7.1 Governing Law. ").bold(true);
2074        p.add_run(
2075            "This Agreement shall be governed by and construed in accordance with the laws of \
2076                   the State of California.",
2077        );
2078    }
2079    {
2080        let mut p = doc.add_paragraph("");
2081        p.add_run("7.2 Entire Agreement. ").bold(true);
2082        p.add_run(
2083            "This Agreement constitutes the entire agreement between the Parties and supersedes \
2084                   all prior negotiations, representations, and agreements.",
2085        );
2086    }
2087    {
2088        let mut p = doc.add_paragraph("");
2089        p.add_run("7.3 Amendment. ").bold(true);
2090        p.add_run(
2091            "This Agreement may only be amended by a written instrument signed by both Parties.",
2092        );
2093    }
2094
2095    // ── Signature Block ──
2096    doc.add_paragraph("").page_break_before(true);
2097    doc.add_paragraph("IN WITNESS WHEREOF, the Parties have executed this Employment Agreement as of the Effective Date.")
2098        .space_after(Length::pt(24.0));
2099
2100    // Signature table
2101    {
2102        let mut tbl = doc.add_table(6, 2).width_pct(100.0);
2103        tbl.cell(0, 0).unwrap().set_text("FOR THE COMPANY:");
2104        tbl.cell(0, 0).unwrap().shading("5B2C6F");
2105        tbl.cell(0, 1).unwrap().set_text("EMPLOYEE:");
2106        tbl.cell(0, 1).unwrap().shading("5B2C6F");
2107
2108        tbl.cell(1, 0).unwrap().set_text("");
2109        tbl.cell(1, 1).unwrap().set_text("");
2110        tbl.row(1).unwrap().height(Length::pt(40.0));
2111
2112        tbl.cell(2, 0)
2113            .unwrap()
2114            .set_text("Signature: ___________________________");
2115        tbl.cell(2, 1)
2116            .unwrap()
2117            .set_text("Signature: ___________________________");
2118        tbl.cell(3, 0)
2119            .unwrap()
2120            .set_text("Name: Board Representative");
2121        tbl.cell(3, 1).unwrap().set_text("Name: Walter White");
2122        tbl.cell(4, 0)
2123            .unwrap()
2124            .set_text("Title: Chair, Board of Directors");
2125        tbl.cell(4, 1)
2126            .unwrap()
2127            .set_text("Title: Chief Executive Officer");
2128        tbl.cell(5, 0).unwrap().set_text("Date: _______________");
2129        tbl.cell(5, 1).unwrap().set_text("Date: _______________");
2130    }
2131
2132    doc
2133}
Source

pub fn paragraph_count(&self) -> usize

Get the number of paragraphs.

Source

pub fn text(&self) -> String

Get the plain text of body paragraphs and table cells in document order.

Source

pub fn paragraph_mut(&mut self, index: usize) -> Option<Paragraph<'_>>

Get a mutable reference to a paragraph by index (among paragraphs only).

Source

pub fn tables(&self) -> Vec<TableRef<'_>>

Get immutable references to all tables.

Source

pub fn table(&self, index: usize) -> Option<TableRef<'_>>

Get an immutable table by index among tables only.

Source

pub fn table_mut(&mut self, index: usize) -> Option<Table<'_>>

Get a mutable table by index among tables only.

Source

pub fn add_table(&mut self, rows: usize, cols: usize) -> Table<'_>

Add a table with the specified number of rows and columns. Returns a mutable reference for further configuration.

Examples found in repository?
examples/generate_pdf.rs (line 32)
7fn main() {
8    // Test 1: Simple document
9    let doc = Document::new();
10    match doc.to_pdf() {
11        Ok(bytes) => {
12            std::fs::write("/tmp/rdocx_simple.pdf", &bytes).unwrap();
13            println!("Simple PDF: {} bytes -> /tmp/rdocx_simple.pdf", bytes.len());
14        }
15        Err(e) => println!("Simple PDF failed: {e}"),
16    }
17
18    // Test 2: Document with content
19    let mut doc = Document::new();
20    doc.set_title("Test PDF Document");
21    doc.set_author("rdocx-pdf");
22    doc.add_paragraph("Chapter 1: Introduction")
23        .style("Heading1");
24    doc.add_paragraph(
25        "This is a test document generated by rdocx and rendered to PDF. \
26         It demonstrates text rendering with proper font shaping and pagination.",
27    );
28    doc.add_paragraph("Section 1.1").style("Heading2");
29    doc.add_paragraph("More content in a sub-section.");
30
31    {
32        let mut table = doc.add_table(2, 3);
33        for r in 0..2 {
34            for c in 0..3 {
35                if let Some(mut cell) = table.cell(r, c) {
36                    cell.set_text(&format!("R{}C{}", r + 1, c + 1));
37                }
38            }
39        }
40    }
41
42    doc.add_paragraph("After the table.");
43
44    match doc.to_pdf() {
45        Ok(bytes) => {
46            std::fs::write("/tmp/rdocx_content.pdf", &bytes).unwrap();
47            println!(
48                "Content PDF: {} bytes -> /tmp/rdocx_content.pdf",
49                bytes.len()
50            );
51        }
52        Err(e) => println!("Content PDF failed: {e}"),
53    }
54
55    // Test 3: From feature_showcase.docx
56    let showcase_path = concat!(
57        env!("CARGO_MANIFEST_DIR"),
58        "/../../samples/feature_showcase.docx"
59    );
60    match Document::open(showcase_path) {
61        Ok(doc) => match doc.to_pdf() {
62            Ok(bytes) => {
63                std::fs::write("/tmp/rdocx_showcase.pdf", &bytes).unwrap();
64                println!(
65                    "Showcase PDF: {} bytes -> /tmp/rdocx_showcase.pdf",
66                    bytes.len()
67                );
68            }
69            Err(e) => println!("Showcase PDF failed: {e}"),
70        },
71        Err(e) => println!("Failed to open showcase: {e}"),
72    }
73}
More examples
Hide additional examples
examples/template_replace.rs (line 102)
43fn create_template(path: &Path) {
44    let mut doc = Document::new();
45    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
46    doc.set_margins(
47        Length::inches(1.0),
48        Length::inches(1.0),
49        Length::inches(1.0),
50        Length::inches(1.0),
51    );
52
53    doc.set_header("{{company_name}} — Confidential");
54    doc.set_footer("Prepared by {{author_name}} on {{date}}");
55
56    // ── Title ──
57    doc.add_paragraph("{{company_name}}")
58        .style("Heading1")
59        .alignment(Alignment::Center);
60
61    doc.add_paragraph("Project Proposal")
62        .alignment(Alignment::Center);
63
64    doc.add_paragraph("");
65
66    // ── Summary section ──
67    doc.add_paragraph("Executive Summary").style("Heading2");
68
69    doc.add_paragraph(
70        "This proposal outlines the {{project_name}} project for {{company_name}}. \
71         The primary contact is {{contact_name}} ({{contact_email}}). \
72         The proposed start date is {{start_date}} with an estimated duration of {{duration}}.",
73    );
74
75    doc.add_paragraph("");
76
77    // ── Cross-run placeholder (bold label + normal value) ──
78    doc.add_paragraph("Key Details").style("Heading2");
79
80    {
81        let mut p = doc.add_paragraph("");
82        p.add_run("Project: ").bold(true);
83        p.add_run("{{project_name}}");
84    }
85    {
86        let mut p = doc.add_paragraph("");
87        p.add_run("Budget: ").bold(true);
88        p.add_run("{{budget}}");
89    }
90    {
91        let mut p = doc.add_paragraph("");
92        p.add_run("Status: ").bold(true);
93        p.add_run("{{status}}");
94    }
95
96    doc.add_paragraph("");
97
98    // ── Table with placeholders ──
99    doc.add_paragraph("Team Members").style("Heading2");
100
101    {
102        let mut tbl = doc.add_table(4, 3);
103        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
104
105        // Header row
106        for col in 0..3 {
107            tbl.cell(0, col).unwrap().shading("2E75B6");
108        }
109        tbl.cell(0, 0).unwrap().set_text("Name");
110        tbl.cell(0, 1).unwrap().set_text("Role");
111        tbl.cell(0, 2).unwrap().set_text("Email");
112
113        tbl.cell(1, 0).unwrap().set_text("{{member1_name}}");
114        tbl.cell(1, 1).unwrap().set_text("{{member1_role}}");
115        tbl.cell(1, 2).unwrap().set_text("{{member1_email}}");
116
117        tbl.cell(2, 0).unwrap().set_text("{{member2_name}}");
118        tbl.cell(2, 1).unwrap().set_text("{{member2_role}}");
119        tbl.cell(2, 2).unwrap().set_text("{{member2_email}}");
120
121        tbl.cell(3, 0).unwrap().set_text("{{member3_name}}");
122        tbl.cell(3, 1).unwrap().set_text("{{member3_role}}");
123        tbl.cell(3, 2).unwrap().set_text("{{member3_email}}");
124    }
125
126    doc.add_paragraph("");
127
128    // ── Deliverables section ──
129    doc.add_paragraph("Deliverables").style("Heading2");
130
131    doc.add_paragraph("INSERTION_POINT");
132
133    doc.add_paragraph("");
134
135    // ── Signature block ──
136    doc.add_paragraph("Acceptance").style("Heading2");
137
138    doc.add_paragraph(
139        "By signing below, {{company_name}} agrees to the terms outlined in this proposal.",
140    );
141
142    {
143        let mut tbl = doc.add_table(2, 2);
144        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
145        tbl.cell(0, 0)
146            .unwrap()
147            .set_text("Customer: ___________________");
148        tbl.cell(0, 1)
149            .unwrap()
150            .set_text("Provider: ___________________");
151        tbl.cell(1, 0).unwrap().set_text("Date: {{date}}");
152        tbl.cell(1, 1).unwrap().set_text("Date: {{date}}");
153    }
154
155    doc.set_title("{{company_name}} — Project Proposal Template");
156    doc.set_author("Template Generator");
157
158    doc.save(path).unwrap();
159}
examples/styled_tables.rs (line 46)
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}
examples/generate_all_samples.rs (line 365)
97fn generate_feature_showcase(_samples_dir: &Path) -> Document {
98    let mut doc = Document::new();
99
100    // ── Page Setup & Metadata ──
101    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
102    doc.set_margins(
103        Length::inches(1.0),
104        Length::inches(1.0),
105        Length::inches(1.0),
106        Length::inches(1.0),
107    );
108    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
109    doc.set_gutter(Length::twips(0));
110    doc.set_title("rdocx Feature Showcase");
111    doc.set_author("rdocx Sample Generator");
112    doc.set_subject("Comprehensive feature demonstration");
113    doc.set_keywords("rdocx, docx, rust, sample, showcase");
114
115    // Headers & Footers with different first page
116    doc.set_different_first_page(true);
117    doc.set_first_page_header("rdocx");
118    doc.set_first_page_footer("Feature Showcase — Cover Page");
119    doc.set_header("rdocx Feature Showcase");
120    doc.set_footer("Generated by rdocx");
121
122    // ── COVER PAGE ──
123    let bg = create_sample_png(612, 792, [20, 45, 90]);
124    doc.add_background_image(&bg, "cover_bg.png");
125
126    for _ in 0..3 {
127        doc.add_paragraph("");
128    }
129    {
130        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
131        p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
132    }
133    {
134        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
135        p.add_run("Complete Feature Showcase")
136            .size(28.0)
137            .color("FFFFFF")
138            .italic(true);
139    }
140    doc.add_paragraph("");
141    {
142        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
143        p.add_run("Every feature of the rdocx Rust library")
144            .size(13.0)
145            .color("B0C4DE");
146    }
147    {
148        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
149        p.add_run("demonstrated in a single document.")
150            .size(13.0)
151            .color("B0C4DE");
152    }
153
154    // ── TABLE OF CONTENTS ──
155    doc.add_paragraph("").page_break_before(true);
156    doc.insert_toc(doc.content_count(), 3);
157
158    // ── SECTION 1: TEXT FORMATTING ──
159    doc.add_paragraph("").page_break_before(true);
160    doc.add_paragraph("1. Text Formatting").style("Heading1");
161
162    // Paragraph Alignment
163    doc.add_paragraph("1.1 Paragraph Alignment")
164        .style("Heading2");
165    doc.add_paragraph("Left-aligned paragraph (default).")
166        .alignment(Alignment::Left);
167    doc.add_paragraph("Center-aligned paragraph.")
168        .alignment(Alignment::Center);
169    doc.add_paragraph("Right-aligned paragraph.")
170        .alignment(Alignment::Right);
171    doc.add_paragraph(
172        "Justified paragraph — this text is long enough to demonstrate how justified alignment \
173         distributes extra space across word gaps so lines fill the full width of the text area.",
174    )
175    .alignment(Alignment::Justify);
176
177    doc.add_paragraph("");
178
179    // Run Formatting
180    doc.add_paragraph("1.2 Run Formatting").style("Heading2");
181    {
182        let mut p = doc.add_paragraph("");
183        p.add_run("Bold").bold(true);
184        p.add_run(" | ");
185        p.add_run("Italic").italic(true);
186        p.add_run(" | ");
187        p.add_run("Bold Italic").bold(true).italic(true);
188        p.add_run(" | ");
189        p.add_run("Underline").underline(true);
190        p.add_run(" | ");
191        p.add_run("Strikethrough").strike(true);
192        p.add_run(" | ");
193        p.add_run("Double Strike").double_strike(true);
194    }
195    {
196        let mut p = doc.add_paragraph("");
197        p.add_run("Red").color("FF0000");
198        p.add_run(" | ");
199        p.add_run("Blue").color("0000FF");
200        p.add_run(" | ");
201        p.add_run("Green").color("00AA00");
202        p.add_run(" | ");
203        p.add_run("Highlighted").highlight("FFFF00");
204    }
205    {
206        let mut p = doc.add_paragraph("");
207        p.add_run("8pt").size(8.0);
208        p.add_run(" | ");
209        p.add_run("11pt").size(11.0);
210        p.add_run(" | ");
211        p.add_run("16pt").size(16.0);
212        p.add_run(" | ");
213        p.add_run("24pt").size(24.0);
214    }
215    {
216        let mut p = doc.add_paragraph("");
217        p.add_run("Arial").font("Arial");
218        p.add_run(" | ");
219        p.add_run("Times New Roman").font("Times New Roman");
220        p.add_run(" | ");
221        p.add_run("Courier New").font("Courier New");
222    }
223    {
224        let mut p = doc.add_paragraph("");
225        p.add_run("H");
226        p.add_run("2").subscript();
227        p.add_run("O (subscript) | E=mc");
228        p.add_run("2").superscript();
229        p.add_run(" (superscript)");
230    }
231    {
232        let mut p = doc.add_paragraph("");
233        p.add_run("ALL CAPS").all_caps(true);
234        p.add_run(" | ");
235        p.add_run("Small Caps").small_caps(true);
236        p.add_run(" | ");
237        p.add_run("Expanded").character_spacing(Length::twips(40));
238        p.add_run(" | ");
239        p.add_run("Hidden text (not visible)").hidden(true);
240    }
241
242    doc.add_paragraph("");
243
244    // Underline Styles
245    doc.add_paragraph("1.3 Underline Styles").style("Heading2");
246    {
247        let mut p = doc.add_paragraph("");
248        p.add_run("Single ")
249            .underline_style(rdocx::UnderlineStyle::Single);
250        p.add_run("Double ")
251            .underline_style(rdocx::UnderlineStyle::Double);
252        p.add_run("Thick ")
253            .underline_style(rdocx::UnderlineStyle::Thick);
254        p.add_run("Dotted ")
255            .underline_style(rdocx::UnderlineStyle::Dotted);
256        p.add_run("Dash ")
257            .underline_style(rdocx::UnderlineStyle::Dash);
258        p.add_run("Wave ")
259            .underline_style(rdocx::UnderlineStyle::Wave);
260    }
261
262    // ── SECTION 2: PARAGRAPH FORMATTING ──
263    doc.add_paragraph("").page_break_before(true);
264    doc.add_paragraph("2. Paragraph Formatting")
265        .style("Heading1");
266
267    doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
268    doc.add_paragraph("Paragraph with light green shading")
269        .shading("E2EFDA");
270    doc.add_paragraph("Paragraph with bottom border")
271        .border_bottom(BorderStyle::Single, 6, "2E75B6");
272    doc.add_paragraph("Paragraph with all borders (red)")
273        .border_all(BorderStyle::Single, 4, "FF0000");
274
275    doc.add_paragraph("2.2 Indentation").style("Heading2");
276    doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
277        .indent_left(Length::inches(1.0))
278        .hanging_indent(Length::inches(0.5));
279    doc.add_paragraph("0.5-inch first-line indent on this paragraph")
280        .first_line_indent(Length::inches(0.5));
281    doc.add_paragraph("Both left and right indent (0.75 inches each)")
282        .indent_left(Length::inches(0.75))
283        .indent_right(Length::inches(0.75));
284
285    doc.add_paragraph("2.3 Spacing & Line Height")
286        .style("Heading2");
287    doc.add_paragraph("Extra space before (24pt) and after (12pt)")
288        .space_before(Length::pt(24.0))
289        .space_after(Length::pt(12.0));
290    doc.add_paragraph(
291        "Double line spacing paragraph. This text should have extra vertical space between \
292         lines to demonstrate line_spacing_multiple(2.0).",
293    )
294    .line_spacing_multiple(2.0);
295    doc.add_paragraph("Exact 20pt line spacing (fixed height).")
296        .line_spacing(20.0);
297
298    doc.add_paragraph("2.4 Pagination Controls")
299        .style("Heading2");
300    doc.add_paragraph("keep_with_next — this paragraph stays with the next")
301        .keep_with_next(true);
302    doc.add_paragraph("(This paragraph was kept with the one above.)");
303    doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
304        .keep_together(true);
305    doc.add_paragraph("widow_control — prevents widow/orphan lines")
306        .widow_control(true);
307
308    // ── SECTION 3: LISTS ──
309    doc.add_paragraph("").page_break_before(true);
310    doc.add_paragraph("3. Lists").style("Heading1");
311
312    doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
313    doc.add_bullet_list_item("First item", 0);
314    doc.add_bullet_list_item("Second item", 0);
315    doc.add_bullet_list_item("Nested level 1", 1);
316    doc.add_bullet_list_item("Nested level 2", 2);
317    doc.add_bullet_list_item("Back to level 1", 1);
318    doc.add_bullet_list_item("Third item", 0);
319
320    doc.add_paragraph("");
321
322    doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
323    doc.add_numbered_list_item("First numbered item", 0);
324    doc.add_numbered_list_item("Second numbered item", 0);
325    doc.add_numbered_list_item("Sub-item A", 1);
326    doc.add_numbered_list_item("Sub-item B", 1);
327    doc.add_numbered_list_item("Third numbered item", 0);
328
329    // ── SECTION 4: TAB STOPS ──
330    doc.add_paragraph("");
331    doc.add_paragraph("4. Tab Stops").style("Heading1");
332
333    doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
334    doc.add_paragraph("Left\tCenter\tRight\tDecimal")
335        .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
336        .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
337        .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
338        .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
339
340    doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
341    doc.add_paragraph("Item\t\tPrice")
342        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
343        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
344        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
345    doc.add_paragraph("Widget\t\t$19.99")
346        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
347        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
348        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
349    doc.add_paragraph("Gadget\t\t$249.50")
350        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
351        .add_tab_stop_with_leader(
352            TabAlignment::Right,
353            Length::inches(4.0),
354            TabLeader::Underscore,
355        )
356        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
357
358    // ── SECTION 5: TABLES ──
359    doc.add_paragraph("").page_break_before(true);
360    doc.add_paragraph("5. Tables").style("Heading1");
361
362    doc.add_paragraph("5.1 Basic Table").style("Heading2");
363    {
364        let mut tbl = doc
365            .add_table(4, 3)
366            .borders(BorderStyle::Single, 4, "000000");
367        for col in 0..3 {
368            tbl.cell(0, col).unwrap().shading("2E75B6");
369        }
370        tbl.cell(0, 0).unwrap().set_text("Name");
371        tbl.cell(0, 1).unwrap().set_text("Role");
372        tbl.cell(0, 2).unwrap().set_text("Location");
373        tbl.cell(1, 0).unwrap().set_text("Walter White");
374        tbl.cell(1, 1).unwrap().set_text("CEO");
375        tbl.cell(1, 2).unwrap().set_text("Albuquerque");
376        tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
377        tbl.cell(2, 1).unwrap().set_text("CTO");
378        tbl.cell(2, 2).unwrap().set_text("Remote");
379        tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
380        tbl.cell(3, 1).unwrap().set_text("Security");
381        tbl.cell(3, 2).unwrap().set_text("Washington");
382    }
383
384    doc.add_paragraph("");
385
386    doc.add_paragraph("5.2 Column Span & Cell Shading")
387        .style("Heading2");
388    {
389        let mut tbl = doc
390            .add_table(3, 4)
391            .borders(BorderStyle::Single, 4, "000000")
392            .width_pct(100.0);
393        tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
394        tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
395        tbl.cell(1, 0).unwrap().set_text("Region");
396        tbl.cell(1, 0).unwrap().shading("D6E4F0");
397        tbl.cell(1, 1).unwrap().set_text("Q1");
398        tbl.cell(1, 1).unwrap().shading("D6E4F0");
399        tbl.cell(1, 2).unwrap().set_text("Q2");
400        tbl.cell(1, 2).unwrap().shading("D6E4F0");
401        tbl.cell(1, 3).unwrap().set_text("Total");
402        tbl.cell(1, 3).unwrap().shading("D6E4F0");
403        tbl.cell(2, 0).unwrap().set_text("Americas");
404        tbl.cell(2, 1).unwrap().set_text("$2.4M");
405        tbl.cell(2, 2).unwrap().set_text("$2.7M");
406        tbl.cell(2, 3).unwrap().set_text("$5.1M");
407    }
408
409    doc.add_paragraph("");
410
411    doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
412    {
413        let mut tbl = doc
414            .add_table(4, 3)
415            .borders(BorderStyle::Single, 4, "000000");
416        tbl.cell(0, 0).unwrap().set_text("Category");
417        tbl.cell(0, 0).unwrap().shading("E2EFDA");
418        tbl.cell(0, 1).unwrap().set_text("Item");
419        tbl.cell(0, 1).unwrap().shading("E2EFDA");
420        tbl.cell(0, 2).unwrap().set_text("Price");
421        tbl.cell(0, 2).unwrap().shading("E2EFDA");
422        tbl.cell(1, 0).unwrap().set_text("Hardware");
423        tbl.cell(1, 0).unwrap().v_merge_restart();
424        tbl.cell(1, 1).unwrap().set_text("Laptop");
425        tbl.cell(1, 2).unwrap().set_text("$1,200");
426        tbl.cell(2, 0).unwrap().v_merge_continue();
427        tbl.cell(2, 1).unwrap().set_text("Monitor");
428        tbl.cell(2, 2).unwrap().set_text("$450");
429        tbl.cell(3, 0).unwrap().set_text("Software");
430        tbl.cell(3, 1).unwrap().set_text("IDE License");
431        tbl.cell(3, 2).unwrap().set_text("$200/yr");
432    }
433
434    doc.add_paragraph("");
435
436    doc.add_paragraph("5.4 Nested Table").style("Heading2");
437    {
438        let mut tbl = doc
439            .add_table(2, 2)
440            .borders(BorderStyle::Single, 6, "2E75B6");
441        tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
442        tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
443        tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
444        {
445            let mut cell = tbl.cell(1, 1).unwrap();
446            cell.set_text("Nested table below:");
447            let mut nested = cell
448                .add_table(2, 2)
449                .borders(BorderStyle::Single, 2, "FF6600");
450            nested.cell(0, 0).unwrap().set_text("A");
451            nested.cell(0, 1).unwrap().set_text("B");
452            nested.cell(1, 0).unwrap().set_text("C");
453            nested.cell(1, 1).unwrap().set_text("D");
454        }
455    }
456
457    doc.add_paragraph("");
458
459    doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
460        .style("Heading2");
461    {
462        let mut tbl = doc
463            .add_table(2, 3)
464            .borders(BorderStyle::Single, 4, "666666");
465        tbl.row(0).unwrap().height(Length::pt(50.0)).header();
466        tbl.cell(0, 0).unwrap().set_text("Top");
467        tbl.cell(0, 0)
468            .unwrap()
469            .vertical_alignment(VerticalAlignment::Top)
470            .shading("FFF2CC");
471        tbl.cell(0, 1).unwrap().set_text("Center");
472        tbl.cell(0, 1)
473            .unwrap()
474            .vertical_alignment(VerticalAlignment::Center)
475            .shading("D9E2F3");
476        tbl.cell(0, 2).unwrap().set_text("Bottom");
477        tbl.cell(0, 2)
478            .unwrap()
479            .vertical_alignment(VerticalAlignment::Bottom)
480            .shading("E2EFDA");
481        tbl.row(1).unwrap().cant_split();
482        tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
483        tbl.cell(1, 0).unwrap().no_wrap();
484        tbl.cell(1, 1).unwrap().set_text("Fixed width");
485        tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
486        tbl.cell(1, 2).unwrap().set_text("Normal");
487    }
488
489    // ── SECTION 6: IMAGES ──
490    doc.add_paragraph("").page_break_before(true);
491    doc.add_paragraph("6. Images").style("Heading1");
492
493    doc.add_paragraph("6.1 Inline Image").style("Heading2");
494    doc.add_paragraph("A blue gradient image below:");
495    let img = create_sample_png(200, 50, [0, 80, 200]);
496    doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
497
498    doc.add_paragraph("");
499    doc.add_paragraph("6.2 Header Image").style("Heading2");
500    let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
501    doc.set_header_image(
502        &hdr_img,
503        "header_logo.png",
504        Length::inches(2.0),
505        Length::inches(0.2),
506    );
507    doc.add_paragraph("The header now contains an inline image (check the top of this page).");
508
509    doc.add_paragraph("");
510    doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
511
512    // ── SECTION 7: CONTENT MANIPULATION ──
513    doc.add_paragraph("").page_break_before(true);
514    doc.add_paragraph("7. Content Manipulation")
515        .style("Heading1");
516
517    doc.add_paragraph("7.1 Placeholder Replacement")
518        .style("Heading2");
519    doc.add_paragraph("Customer: {{customer}}");
520    doc.add_paragraph("Date: {{date}}");
521    doc.add_paragraph("Reference: {{ref_number}}");
522    {
523        let mut tbl = doc
524            .add_table(2, 2)
525            .borders(BorderStyle::Single, 4, "000000");
526        tbl.cell(0, 0).unwrap().set_text("Project");
527        tbl.cell(0, 0).unwrap().shading("D6E4F0");
528        tbl.cell(0, 1).unwrap().set_text("{{project}}");
529        tbl.cell(1, 0).unwrap().set_text("Status");
530        tbl.cell(1, 0).unwrap().shading("D6E4F0");
531        tbl.cell(1, 1).unwrap().set_text("{{status}}");
532    }
533
534    let mut replacements = HashMap::new();
535    replacements.insert("{{customer}}", "Tensorbee Inc.");
536    replacements.insert("{{date}}", "February 22, 2026");
537    replacements.insert("{{ref_number}}", "TB-2026-001");
538    replacements.insert("{{project}}", "Infrastructure Upgrade");
539    replacements.insert("{{status}}", "In Progress");
540    let n = doc.replace_all(&replacements);
541    doc.add_paragraph(&format!("({n} placeholders replaced above)"));
542
543    doc.add_paragraph("");
544
545    doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
546    doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
547    let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
548    doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
549
550    doc.add_paragraph("");
551
552    doc.add_paragraph("7.3 Content Insertion").style("Heading2");
553    doc.add_paragraph("Section A: First content.");
554    doc.add_paragraph("Section C: Third content.");
555    if let Some(idx) = doc.find_content_index("Section C") {
556        doc.insert_paragraph(
557            idx,
558            "Section B: Inserted between A and C via find_content_index().",
559        );
560    }
561
562    // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
563    doc.add_paragraph("").section_break(SectionBreak::NextPage);
564    doc.add_paragraph("8. Section Breaks & Orientation")
565        .style("Heading1");
566    doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
567
568    {
569        let mut tbl = doc
570            .add_table(3, 7)
571            .borders(BorderStyle::Single, 4, "2E75B6");
572        let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
573        for (c, h) in headers.iter().enumerate() {
574            tbl.cell(0, c).unwrap().set_text(h);
575            tbl.cell(0, c).unwrap().shading("2E75B6");
576        }
577        let data = [
578            [
579                "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
580            ],
581            [
582                "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
583            ],
584        ];
585        for (r, row) in data.iter().enumerate() {
586            for (c, v) in row.iter().enumerate() {
587                tbl.cell(r + 1, c).unwrap().set_text(v);
588            }
589        }
590    }
591
592    doc.add_paragraph("")
593        .section_break(SectionBreak::NextPage)
594        .section_landscape();
595
596    // ── SECTION 9: CUSTOM STYLES ──
597    doc.add_paragraph("9. Custom Styles").style("Heading1");
598    doc.add_style(
599        StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
600            .based_on("Normal")
601            .paragraph_properties(rdocx_oxml::properties::CT_PPr {
602                shading: Some(rdocx_oxml::properties::CT_Shd {
603                    val: "clear".to_string(),
604                    color: None,
605                    fill: Some("FFF2CC".to_string()),
606                }),
607                ..Default::default()
608            })
609            .run_properties(rdocx_oxml::properties::CT_RPr {
610                bold: Some(true),
611                color: Some("C45911".to_string()),
612                ..Default::default()
613            }),
614    );
615    doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
616        .style("CustomHighlight");
617
618    doc.add_paragraph("");
619
620    // ── SECTION 10: DOCUMENT INTELLIGENCE ──
621    doc.add_paragraph("10. Document Intelligence API")
622        .style("Heading1");
623
624    let wc = doc.word_count();
625    let hc = doc.headings().len();
626    let ic = doc.images().len();
627    let lc = doc.links().len();
628    doc.add_paragraph(&format!("Word count: {wc}"));
629    doc.add_paragraph(&format!("Heading count: {hc}"));
630    doc.add_paragraph(&format!("Image count: {ic}"));
631    doc.add_paragraph(&format!("Link count: {lc}"));
632
633    let outline = doc.document_outline();
634    doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
635
636    let issues = doc.audit_accessibility();
637    doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
638    for issue in &issues {
639        doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
640    }
641
642    // ── SECTION 11: DOCUMENT MERGING ──
643    doc.add_paragraph("");
644    doc.add_paragraph("11. Document Merging").style("Heading1");
645    {
646        let mut other = Document::new();
647        other.add_paragraph("This paragraph was merged from another document using append().");
648        doc.append(&other);
649    }
650
651    doc.add_paragraph("");
652
653    // ── SUMMARY ──
654    doc.add_paragraph("Summary of Demonstrated Features")
655        .style("Heading1");
656    let features = [
657        "Page setup: size, margins, header/footer distance, gutter",
658        "Document metadata: title, author, subject, keywords",
659        "Headers and footers: text, images, different first page",
660        "Background images (full-page behind text)",
661        "Table of Contents generation with bookmarks",
662        "Text formatting: bold, italic, underline styles, strike, color, size, font",
663        "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
664        "Paragraph formatting: alignment, borders, shading, spacing, indentation",
665        "Pagination controls: keep-with-next, keep-together, widow control",
666        "Bullet and numbered lists with nesting",
667        "Tab stops with dot/underscore/hyphen leaders",
668        "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
669        "Row properties: header rows, exact height, cant-split",
670        "Cell properties: width, no-wrap, vertical alignment",
671        "Inline images and header images",
672        "Placeholder replacement (single and batch)",
673        "Regex-based find and replace",
674        "Content insertion at specific positions",
675        "Section breaks with mixed portrait/landscape",
676        "Custom paragraph and character styles",
677        "Document intelligence: word count, headings, outline, images, links",
678        "Accessibility audit",
679        "Document merging (append)",
680        "Export: DOCX, PDF, HTML, Markdown",
681    ];
682    for f in &features {
683        doc.add_bullet_list_item(f, 0);
684    }
685    doc.add_paragraph("");
686    doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
687        .alignment(Alignment::Center)
688        .shading("E2EFDA")
689        .border_all(BorderStyle::Single, 2, "00AA00");
690
691    doc
692}
693
694// =============================================================================
695// 2. PROPOSAL DOCUMENT — Deep navy + gold scheme
696// =============================================================================
697fn generate_proposal(_samples_dir: &Path) -> Document {
698    let mut doc = Document::new();
699
700    // Colors: Navy #1B2A4A, Gold #C5922E, Light #F4F1EB
701    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
702    doc.set_margins(
703        Length::inches(1.0),
704        Length::inches(1.0),
705        Length::inches(1.0),
706        Length::inches(1.0),
707    );
708    doc.set_title("AI Platform Modernization Proposal");
709    doc.set_author("Walter White");
710    doc.set_subject("Technology Proposal");
711    doc.set_keywords("proposal, AI, modernization, Tensorbee");
712
713    // Banner header
714    let logo_img = create_logo_png(220, 48);
715    let banner = build_header_banner_xml(
716        "rId1",
717        &BannerOpts {
718            bg_color: "1B2A4A",
719            banner_width: 7772400,
720            banner_height: 914400,
721            logo_width: 2011680,
722            logo_height: 438912,
723            logo_x_offset: 295125,
724            logo_y_offset: 237744,
725        },
726    );
727    doc.set_raw_header_with_images(
728        banner,
729        &[("rId1", &logo_img, "logo.png")],
730        rdocx_oxml::header_footer::HdrFtrType::Default,
731    );
732    doc.set_different_first_page(true);
733    let cover_banner = build_header_banner_xml(
734        "rId1",
735        &BannerOpts {
736            bg_color: "C5922E",
737            banner_width: 7772400,
738            banner_height: 914400,
739            logo_width: 2011680,
740            logo_height: 438912,
741            logo_x_offset: 295125,
742            logo_y_offset: 237744,
743        },
744    );
745    doc.set_raw_header_with_images(
746        cover_banner,
747        &[("rId1", &logo_img, "logo.png")],
748        rdocx_oxml::header_footer::HdrFtrType::First,
749    );
750    doc.set_margins(
751        Length::twips(2292),
752        Length::twips(1440),
753        Length::twips(1440),
754        Length::twips(1440),
755    );
756    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
757    doc.set_footer("Tensorbee — Confidential");
758
759    // Custom styles
760    doc.add_style(
761        StyleBuilder::paragraph("ProposalTitle", "Proposal Title")
762            .based_on("Normal")
763            .run_properties(rdocx_oxml::properties::CT_RPr {
764                bold: Some(true),
765                font_ascii: Some("Georgia".to_string()),
766                font_hansi: Some("Georgia".to_string()),
767                sz: Some(rdocx_oxml::HalfPoint(56)), // half-points → 28pt
768                color: Some("1B2A4A".to_string()),
769                ..Default::default()
770            }),
771    );
772
773    // ── Cover Page ──
774    for _ in 0..4 {
775        doc.add_paragraph("");
776    }
777    doc.add_paragraph("AI Platform Modernization")
778        .style("ProposalTitle")
779        .alignment(Alignment::Center);
780    doc.add_paragraph("")
781        .alignment(Alignment::Center)
782        .border_bottom(BorderStyle::Single, 8, "C5922E");
783    {
784        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
785        p.add_run("Prepared for Global Financial Corp.")
786            .size(14.0)
787            .color("666666")
788            .italic(true);
789    }
790    {
791        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
792        p.add_run("Prepared by: Walter White, CEO — Tensorbee")
793            .size(12.0)
794            .color("888888");
795    }
796    {
797        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
798        p.add_run("February 22, 2026").size(12.0).color("888888");
799    }
800
801    // ── TOC ──
802    doc.add_paragraph("").page_break_before(true);
803    doc.add_paragraph("Table of Contents").style("Heading1");
804    // We'll add a manual-style TOC since the insert_toc goes at a specific position
805    doc.insert_toc(doc.content_count(), 2);
806
807    // ── Executive Summary ──
808    doc.add_paragraph("").page_break_before(true);
809    doc.add_paragraph("Executive Summary").style("Heading1");
810    doc.add_paragraph(
811        "Tensorbee proposes a comprehensive modernization of Global Financial Corp's AI platform, \
812         transitioning from legacy batch-processing systems to a real-time inference architecture. \
813         This transformation will reduce model deployment time from weeks to hours, improve \
814         prediction accuracy by an estimated 23%, and deliver $4.2M in annual operational savings.",
815    )
816    .first_line_indent(Length::inches(0.3));
817    doc.add_paragraph(
818        "The project spans three phases over 18 months, with a total investment of $2.8M. \
819         Tensorbee brings deep expertise in ML infrastructure, having successfully completed \
820         similar transformations for three Fortune 500 financial institutions.",
821    )
822    .first_line_indent(Length::inches(0.3));
823
824    // Key metrics table
825    doc.add_paragraph("");
826    {
827        let mut tbl = doc
828            .add_table(5, 2)
829            .borders(BorderStyle::Single, 4, "1B2A4A")
830            .width_pct(80.0);
831        tbl.cell(0, 0).unwrap().set_text("Key Metric");
832        tbl.cell(0, 0).unwrap().shading("1B2A4A");
833        tbl.cell(0, 1).unwrap().set_text("Value");
834        tbl.cell(0, 1).unwrap().shading("1B2A4A");
835        let rows = [
836            ("Total Investment", "$2.8M"),
837            ("Annual Savings", "$4.2M"),
838            ("ROI (Year 1)", "150%"),
839            ("Timeline", "18 months"),
840        ];
841        for (i, (k, v)) in rows.iter().enumerate() {
842            tbl.cell(i + 1, 0).unwrap().set_text(k);
843            if i % 2 == 0 {
844                tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
845            }
846            tbl.cell(i + 1, 1).unwrap().set_text(v);
847            if i % 2 == 0 {
848                tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
849            }
850        }
851    }
852
853    // ── Problem Statement ──
854    doc.add_paragraph("").page_break_before(true);
855    doc.add_paragraph("Problem Statement").style("Heading1");
856    doc.add_paragraph(
857        "Global Financial Corp's current AI infrastructure faces three critical challenges:",
858    );
859    doc.add_numbered_list_item("Deployment Latency — New models take 3-4 weeks to deploy to production, compared to the industry benchmark of 2-3 days.", 0);
860    doc.add_numbered_list_item("Scalability Constraints — The batch processing architecture cannot handle real-time inference demands during peak trading hours.", 0);
861    doc.add_numbered_list_item("Technical Debt — Legacy Python 2.7 codebases and manual deployment scripts create reliability risks and slow iteration speed.", 0);
862
863    // ── Proposed Solution ──
864    doc.add_paragraph("").page_break_before(true);
865    doc.add_paragraph("Proposed Solution").style("Heading1");
866
867    doc.add_paragraph("Phase 1: Foundation (Months 1-6)")
868        .style("Heading2");
869    doc.add_bullet_list_item("Deploy Kubernetes-based ML serving infrastructure", 0);
870    doc.add_bullet_list_item("Implement CI/CD pipeline for model deployment", 0);
871    doc.add_bullet_list_item("Migrate top 5 production models to new platform", 0);
872
873    doc.add_paragraph("Phase 2: Expansion (Months 7-12)")
874        .style("Heading2");
875    doc.add_bullet_list_item(
876        "Real-time feature engineering pipeline (Apache Kafka + Flink)",
877        0,
878    );
879    doc.add_bullet_list_item("A/B testing framework for model variants", 0);
880    doc.add_bullet_list_item("Automated model monitoring and drift detection", 0);
881
882    doc.add_paragraph("Phase 3: Optimization (Months 13-18)")
883        .style("Heading2");
884    doc.add_bullet_list_item("GPU inference optimization (TensorRT/ONNX Runtime)", 0);
885    doc.add_bullet_list_item("Multi-region deployment for latency reduction", 0);
886    doc.add_bullet_list_item(
887        "Self-service model deployment portal for data science teams",
888        0,
889    );
890
891    // ── Budget ──
892    doc.add_paragraph("Budget Breakdown").style("Heading1");
893    {
894        let mut tbl = doc
895            .add_table(6, 3)
896            .borders(BorderStyle::Single, 4, "1B2A4A")
897            .width_pct(100.0);
898        tbl.cell(0, 0).unwrap().set_text("Category");
899        tbl.cell(0, 0).unwrap().shading("1B2A4A");
900        tbl.cell(0, 1).unwrap().set_text("Cost");
901        tbl.cell(0, 1).unwrap().shading("1B2A4A");
902        tbl.cell(0, 2).unwrap().set_text("Notes");
903        tbl.cell(0, 2).unwrap().shading("1B2A4A");
904        let items = [
905            (
906                "Infrastructure",
907                "$450,000",
908                "Cloud compute + GPU instances",
909            ),
910            ("Engineering Services", "$1,600,000", "12 FTE x 18 months"),
911            (
912                "Software Licenses",
913                "$350,000",
914                "Kafka, monitoring, CI/CD tools",
915            ),
916            (
917                "Training & Enablement",
918                "$200,000",
919                "Team training, documentation",
920            ),
921            ("Contingency (10%)", "$200,000", "Risk buffer"),
922        ];
923        for (i, (cat, cost, note)) in items.iter().enumerate() {
924            tbl.cell(i + 1, 0).unwrap().set_text(cat);
925            tbl.cell(i + 1, 1).unwrap().set_text(cost);
926            tbl.cell(i + 1, 2).unwrap().set_text(note);
927            if i % 2 == 0 {
928                tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
929                tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
930                tbl.cell(i + 1, 2).unwrap().shading("F4F1EB");
931            }
932        }
933    }
934
935    // ── Closing ──
936    doc.add_paragraph("");
937    doc.add_paragraph("Next Steps").style("Heading1");
938    doc.add_numbered_list_item(
939        "Schedule technical deep-dive with GFC engineering team (Week 1)",
940        0,
941    );
942    doc.add_numbered_list_item("Finalize scope and sign SOW (Week 2-3)", 0);
943    doc.add_numbered_list_item("Kick off Phase 1 with joint planning session (Week 4)", 0);
944
945    doc.add_paragraph("");
946    {
947        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
948        p.add_run("Walter White")
949            .bold(true)
950            .size(14.0)
951            .color("1B2A4A");
952    }
953    {
954        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
955        p.add_run("CEO, Tensorbee | walter@tensorbee.com")
956            .size(10.0)
957            .color("888888");
958    }
959
960    doc
961}
962
963// =============================================================================
964// 3. QUOTE / BILL OF MATERIALS — Teal + orange scheme
965// =============================================================================
966fn generate_quote(_samples_dir: &Path) -> Document {
967    let mut doc = Document::new();
968
969    // Colors: Teal #008B8B, Dark #1A3C3C, Orange #E07020, Light #F0F8F8
970    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
971    doc.set_margins(
972        Length::inches(0.75),
973        Length::inches(0.75),
974        Length::inches(0.75),
975        Length::inches(0.75),
976    );
977    doc.set_title("Quotation QT-2026-0147");
978    doc.set_author("Walter White");
979    doc.set_keywords("quote, BOM, Tensorbee");
980
981    doc.set_header("Tensorbee — Quotation");
982    doc.set_footer("QT-2026-0147 | Page");
983
984    // ── Header Block ──
985    {
986        let mut p = doc.add_paragraph("").alignment(Alignment::Left);
987        p.add_run("TENSORBEE")
988            .bold(true)
989            .size(28.0)
990            .color("008B8B")
991            .font("Helvetica");
992    }
993    doc.add_paragraph("123 Innovation Drive, Suite 400")
994        .alignment(Alignment::Left);
995    doc.add_paragraph("San Francisco, CA 94105 | +1 (415) 555-0199")
996        .alignment(Alignment::Left);
997    doc.add_paragraph("")
998        .border_bottom(BorderStyle::Single, 8, "008B8B");
999
1000    // Quote meta
1001    doc.add_paragraph("");
1002    {
1003        let mut tbl = doc.add_table(4, 4).width_pct(100.0);
1004        tbl.cell(0, 0).unwrap().set_text("QUOTATION");
1005        tbl.cell(0, 0).unwrap().shading("008B8B").grid_span(2);
1006        tbl.cell(0, 2).unwrap().set_text("DATE");
1007        tbl.cell(0, 2).unwrap().shading("008B8B");
1008        tbl.cell(0, 3).unwrap().set_text("VALID UNTIL");
1009        tbl.cell(0, 3).unwrap().shading("008B8B");
1010        tbl.cell(1, 0).unwrap().set_text("Quote #:");
1011        tbl.cell(1, 1).unwrap().set_text("QT-2026-0147");
1012        tbl.cell(1, 2).unwrap().set_text("Feb 22, 2026");
1013        tbl.cell(1, 3).unwrap().set_text("Mar 22, 2026");
1014        tbl.cell(2, 0).unwrap().set_text("Prepared by:");
1015        tbl.cell(2, 1).unwrap().set_text("Walter White");
1016        tbl.cell(2, 2).unwrap().set_text("Payment:");
1017        tbl.cell(2, 3).unwrap().set_text("Net 30");
1018        tbl.cell(3, 0).unwrap().set_text("Customer:");
1019        tbl.cell(3, 1).unwrap().set_text("Meridian Dynamics LLC");
1020        tbl.cell(3, 2).unwrap().set_text("Currency:");
1021        tbl.cell(3, 3).unwrap().set_text("USD");
1022    }
1023
1024    doc.add_paragraph("");
1025
1026    // ── Bill of Materials ──
1027    {
1028        let mut p = doc.add_paragraph("");
1029        p.add_run("Bill of Materials")
1030            .bold(true)
1031            .size(16.0)
1032            .color("1A3C3C");
1033    }
1034    doc.add_paragraph("");
1035
1036    {
1037        let mut tbl = doc
1038            .add_table(10, 6)
1039            .borders(BorderStyle::Single, 2, "008B8B")
1040            .width_pct(100.0)
1041            .layout_fixed();
1042        // Headers
1043        let hdrs = [
1044            "#",
1045            "Part Number",
1046            "Description",
1047            "Qty",
1048            "Unit Price",
1049            "Total",
1050        ];
1051        for (c, h) in hdrs.iter().enumerate() {
1052            tbl.cell(0, c).unwrap().set_text(h);
1053            tbl.cell(0, c).unwrap().shading("008B8B");
1054        }
1055
1056        let items: Vec<(&str, &str, &str, &str, &str)> = vec![
1057            (
1058                "TB-GPU-A100",
1059                "NVIDIA A100 80GB GPU",
1060                "4",
1061                "$12,500.00",
1062                "$50,000.00",
1063            ),
1064            (
1065                "TB-SRV-R750",
1066                "Dell R750xa Server Chassis",
1067                "2",
1068                "$8,200.00",
1069                "$16,400.00",
1070            ),
1071            (
1072                "TB-RAM-256G",
1073                "256GB DDR5 ECC Memory Module",
1074                "8",
1075                "$890.00",
1076                "$7,120.00",
1077            ),
1078            (
1079                "TB-SSD-3840",
1080                "3.84TB NVMe U.2 SSD",
1081                "8",
1082                "$1,150.00",
1083                "$9,200.00",
1084            ),
1085            (
1086                "TB-NET-CX7",
1087                "ConnectX-7 200GbE NIC",
1088                "4",
1089                "$1,800.00",
1090                "$7,200.00",
1091            ),
1092            (
1093                "TB-SW-48P",
1094                "48-Port 100GbE Switch",
1095                "1",
1096                "$22,000.00",
1097                "$22,000.00",
1098            ),
1099            (
1100                "TB-CAB-RACK",
1101                "42U Server Rack + PDU",
1102                "1",
1103                "$4,500.00",
1104                "$4,500.00",
1105            ),
1106            (
1107                "TB-SVC-INST",
1108                "Installation & Configuration",
1109                "1",
1110                "$8,500.00",
1111                "$8,500.00",
1112            ),
1113            (
1114                "TB-SVC-SUPP",
1115                "3-Year Premium Support",
1116                "1",
1117                "$15,000.00",
1118                "$15,000.00",
1119            ),
1120        ];
1121
1122        for (i, (pn, desc, qty, unit, total)) in items.iter().enumerate() {
1123            let row = i + 1;
1124            tbl.cell(row, 0).unwrap().set_text(&format!("{}", i + 1));
1125            tbl.cell(row, 1).unwrap().set_text(pn);
1126            tbl.cell(row, 2).unwrap().set_text(desc);
1127            tbl.cell(row, 3).unwrap().set_text(qty);
1128            tbl.cell(row, 4).unwrap().set_text(unit);
1129            tbl.cell(row, 5).unwrap().set_text(total);
1130            if i % 2 == 0 {
1131                for c in 0..6 {
1132                    tbl.cell(row, c).unwrap().shading("F0F8F8");
1133                }
1134            }
1135        }
1136    }
1137
1138    doc.add_paragraph("");
1139
1140    // ── Totals ──
1141    {
1142        let mut tbl = doc
1143            .add_table(4, 2)
1144            .borders(BorderStyle::Single, 2, "008B8B")
1145            .width(Length::inches(3.5))
1146            .alignment(Alignment::Right);
1147        tbl.cell(0, 0).unwrap().set_text("Subtotal");
1148        tbl.cell(0, 1).unwrap().set_text("$139,920.00");
1149        tbl.cell(1, 0).unwrap().set_text("Shipping & Handling");
1150        tbl.cell(1, 1).unwrap().set_text("$2,500.00");
1151        tbl.cell(2, 0).unwrap().set_text("Tax (8.625%)");
1152        tbl.cell(2, 1).unwrap().set_text("$12,068.10");
1153        tbl.cell(3, 0).unwrap().set_text("TOTAL");
1154        tbl.cell(3, 0).unwrap().shading("E07020");
1155        tbl.cell(3, 1).unwrap().set_text("$154,488.10");
1156        tbl.cell(3, 1).unwrap().shading("E07020");
1157    }
1158
1159    doc.add_paragraph("");
1160
1161    // ── Terms & Conditions ──
1162    {
1163        let mut p = doc.add_paragraph("");
1164        p.add_run("Terms & Conditions")
1165            .bold(true)
1166            .size(14.0)
1167            .color("1A3C3C");
1168    }
1169    doc.add_numbered_list_item(
1170        "This quotation is valid for 30 calendar days from the date of issue.",
1171        0,
1172    );
1173    doc.add_numbered_list_item(
1174        "All prices are in USD and exclusive of applicable taxes unless stated.",
1175        0,
1176    );
1177    doc.add_numbered_list_item("Standard lead time is 4-6 weeks from PO receipt.", 0);
1178    doc.add_numbered_list_item(
1179        "Payment terms: Net 30 from invoice date. 2% discount for payment within 10 days.",
1180        0,
1181    );
1182    doc.add_numbered_list_item(
1183        "Warranty: 3-year manufacturer warranty on all hardware components.",
1184        0,
1185    );
1186    doc.add_numbered_list_item(
1187        "Returns subject to 15% restocking fee if initiated after 14 days.",
1188        0,
1189    );
1190
1191    doc.add_paragraph("");
1192    doc.add_paragraph("")
1193        .border_bottom(BorderStyle::Single, 4, "008B8B");
1194    doc.add_paragraph("");
1195    {
1196        let mut p = doc.add_paragraph("");
1197        p.add_run("Accepted by: ").bold(true);
1198        p.add_run("___________________________ Date: ___________");
1199    }
1200    {
1201        let mut p = doc.add_paragraph("");
1202        p.add_run("Print Name: ").bold(true);
1203        p.add_run("___________________________ Title: ___________");
1204    }
1205
1206    doc
1207}
1208
1209// =============================================================================
1210// 4. INVOICE — Crimson + charcoal scheme
1211// =============================================================================
1212fn generate_invoice(_samples_dir: &Path) -> Document {
1213    let mut doc = Document::new();
1214
1215    // Colors: Crimson #B22222, Charcoal #333333, Light #FAF0F0
1216    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1217    doc.set_margins(
1218        Length::inches(0.75),
1219        Length::inches(0.75),
1220        Length::inches(0.75),
1221        Length::inches(0.75),
1222    );
1223    doc.set_title("Invoice INV-2026-0392");
1224    doc.set_author("Walter White");
1225    doc.set_keywords("invoice, Tensorbee");
1226
1227    doc.set_footer("Tensorbee — Thank you for your business!");
1228
1229    // ── Company & Invoice Header ──
1230    {
1231        let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1232        // Company name left, INVOICE right
1233        {
1234            let mut cell = tbl.cell(0, 0).unwrap();
1235            let mut p = cell.add_paragraph("");
1236            p.add_run("TENSORBEE")
1237                .bold(true)
1238                .size(32.0)
1239                .color("B22222")
1240                .font("Helvetica");
1241        }
1242        {
1243            let mut cell = tbl.cell(0, 1).unwrap();
1244            let mut p = cell.add_paragraph("");
1245            p.add_run("INVOICE").bold(true).size(32.0).color("333333");
1246        }
1247        tbl.cell(1, 0)
1248            .unwrap()
1249            .set_text("123 Innovation Drive, Suite 400");
1250        tbl.cell(1, 1).unwrap().set_text("Invoice #: INV-2026-0392");
1251        tbl.cell(2, 0).unwrap().set_text("San Francisco, CA 94105");
1252        tbl.cell(2, 1).unwrap().set_text("Date: February 22, 2026");
1253        tbl.cell(3, 0).unwrap().set_text("walter@tensorbee.com");
1254        tbl.cell(3, 1).unwrap().set_text("Due Date: March 24, 2026");
1255    }
1256
1257    doc.add_paragraph("")
1258        .border_bottom(BorderStyle::Thick, 8, "B22222");
1259    doc.add_paragraph("");
1260
1261    // ── Bill To / Ship To ──
1262    {
1263        let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1264        tbl.cell(0, 0).unwrap().set_text("BILL TO");
1265        tbl.cell(0, 0).unwrap().shading("B22222");
1266        tbl.cell(0, 1).unwrap().set_text("SHIP TO");
1267        tbl.cell(0, 1).unwrap().shading("B22222");
1268        tbl.cell(1, 0).unwrap().set_text("Meridian Dynamics LLC");
1269        tbl.cell(1, 1).unwrap().set_text("Meridian Dynamics LLC");
1270        tbl.cell(2, 0).unwrap().set_text("456 Enterprise Blvd");
1271        tbl.cell(2, 1).unwrap().set_text("Attn: Server Room B");
1272        tbl.cell(3, 0).unwrap().set_text("Austin, TX 78701");
1273        tbl.cell(3, 1)
1274            .unwrap()
1275            .set_text("456 Enterprise Blvd, Austin, TX 78701");
1276    }
1277
1278    doc.add_paragraph("");
1279
1280    // ── Line Items ──
1281    {
1282        let mut tbl = doc
1283            .add_table(8, 5)
1284            .borders(BorderStyle::Single, 2, "B22222")
1285            .width_pct(100.0);
1286        let hdrs = ["Description", "Qty", "Unit Price", "Tax", "Amount"];
1287        for (c, h) in hdrs.iter().enumerate() {
1288            tbl.cell(0, c).unwrap().set_text(h);
1289            tbl.cell(0, c).unwrap().shading("B22222");
1290        }
1291        let items = [
1292            (
1293                "ML Infrastructure Setup — Phase 1",
1294                "1",
1295                "$45,000.00",
1296                "$3,881.25",
1297                "$48,881.25",
1298            ),
1299            (
1300                "Data Pipeline Development (160 hrs)",
1301                "160",
1302                "$225.00",
1303                "$3,105.00",
1304                "$39,105.00",
1305            ),
1306            (
1307                "GPU Cluster Configuration",
1308                "1",
1309                "$12,000.00",
1310                "$1,035.00",
1311                "$13,035.00",
1312            ),
1313            (
1314                "API Gateway Implementation",
1315                "1",
1316                "$18,500.00",
1317                "$1,595.63",
1318                "$20,095.63",
1319            ),
1320            (
1321                "Load Testing & QA (80 hrs)",
1322                "80",
1323                "$195.00",
1324                "$1,345.50",
1325                "$16,945.50",
1326            ),
1327            (
1328                "Documentation & Training",
1329                "1",
1330                "$8,000.00",
1331                "$690.00",
1332                "$8,690.00",
1333            ),
1334            (
1335                "Project Management (3 months)",
1336                "3",
1337                "$6,500.00",
1338                "$1,679.63",
1339                "$21,179.63",
1340            ),
1341        ];
1342        for (i, (desc, qty, unit, tax, amt)) in items.iter().enumerate() {
1343            let r = i + 1;
1344            tbl.cell(r, 0).unwrap().set_text(desc);
1345            tbl.cell(r, 1).unwrap().set_text(qty);
1346            tbl.cell(r, 2).unwrap().set_text(unit);
1347            tbl.cell(r, 3).unwrap().set_text(tax);
1348            tbl.cell(r, 4).unwrap().set_text(amt);
1349            if i % 2 == 0 {
1350                for c in 0..5 {
1351                    tbl.cell(r, c).unwrap().shading("FAF0F0");
1352                }
1353            }
1354        }
1355    }
1356
1357    doc.add_paragraph("");
1358
1359    // ── Totals ──
1360    {
1361        let mut tbl = doc
1362            .add_table(4, 2)
1363            .borders(BorderStyle::Single, 2, "B22222")
1364            .width(Length::inches(3.0))
1365            .alignment(Alignment::Right);
1366        tbl.cell(0, 0).unwrap().set_text("Subtotal");
1367        tbl.cell(0, 1).unwrap().set_text("$154,600.00");
1368        tbl.cell(1, 0).unwrap().set_text("Tax (8.625%)");
1369        tbl.cell(1, 1).unwrap().set_text("$13,334.25");
1370        tbl.cell(2, 0).unwrap().set_text("Discount (5%)");
1371        tbl.cell(2, 1).unwrap().set_text("-$7,730.00");
1372        tbl.cell(3, 0).unwrap().set_text("AMOUNT DUE");
1373        tbl.cell(3, 0).unwrap().shading("B22222");
1374        tbl.cell(3, 1).unwrap().set_text("$160,204.25");
1375        tbl.cell(3, 1).unwrap().shading("B22222");
1376    }
1377
1378    doc.add_paragraph("");
1379    doc.add_paragraph("");
1380
1381    // ── Payment Details ──
1382    {
1383        let mut p = doc.add_paragraph("");
1384        p.add_run("Payment Details")
1385            .bold(true)
1386            .size(14.0)
1387            .color("333333");
1388    }
1389    doc.add_paragraph("Bank: Silicon Valley Bank")
1390        .indent_left(Length::inches(0.3));
1391    doc.add_paragraph("Account: Tensorbee Inc. — 0847-2953-1120")
1392        .indent_left(Length::inches(0.3));
1393    doc.add_paragraph("Routing: 121140399")
1394        .indent_left(Length::inches(0.3));
1395    doc.add_paragraph("Swift: SVBKUS6S")
1396        .indent_left(Length::inches(0.3));
1397
1398    doc.add_paragraph("");
1399
1400    doc.add_paragraph("Please include invoice number INV-2026-0392 in the payment reference.")
1401        .shading("FAF0F0")
1402        .border_all(BorderStyle::Single, 2, "B22222");
1403
1404    doc
1405}
1406
1407// =============================================================================
1408// 5. REPORT — Forest green + earth tones, with images & hierarchical sections
1409// =============================================================================
1410fn generate_report(_samples_dir: &Path) -> Document {
1411    let mut doc = Document::new();
1412
1413    // Colors: Forest #2D5016, Sage #6B8E23, Earth #8B7355, Cream #FFFAF0
1414    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1415    doc.set_margins(
1416        Length::inches(1.0),
1417        Length::inches(1.0),
1418        Length::inches(1.0),
1419        Length::inches(1.0),
1420    );
1421    doc.set_title("Q4 2025 Environmental Impact Report");
1422    doc.set_author("Walter White");
1423    doc.set_subject("Quarterly Environmental Report");
1424    doc.set_keywords("environment, sustainability, report, Tensorbee");
1425
1426    doc.set_different_first_page(true);
1427    doc.set_first_page_header("");
1428    doc.set_header("Tensorbee — Q4 2025 Environmental Impact Report");
1429    doc.set_footer("CONFIDENTIAL — Page");
1430
1431    // ── Cover ──
1432    let cover_bg = create_sample_png(612, 792, [20, 50, 15]);
1433    doc.add_background_image(&cover_bg, "report_cover.png");
1434
1435    for _ in 0..5 {
1436        doc.add_paragraph("");
1437    }
1438    {
1439        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1440        p.add_run("Q4 2025")
1441            .bold(true)
1442            .size(48.0)
1443            .color("FFFFFF")
1444            .font("Georgia");
1445    }
1446    {
1447        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1448        p.add_run("Environmental Impact Report")
1449            .size(24.0)
1450            .color("90EE90")
1451            .font("Georgia")
1452            .italic(true);
1453    }
1454    doc.add_paragraph("");
1455    {
1456        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1457        p.add_run("Tensorbee — Sustainability Division")
1458            .size(14.0)
1459            .color("C0C0C0");
1460    }
1461    {
1462        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1463        p.add_run("Prepared by Walter White, Chief Sustainability Officer")
1464            .size(11.0)
1465            .color("AAAAAA");
1466    }
1467
1468    // ── TOC ──
1469    doc.add_paragraph("").page_break_before(true);
1470    doc.insert_toc(doc.content_count(), 3);
1471
1472    // ── Section 1: Executive Overview ──
1473    doc.add_paragraph("").page_break_before(true);
1474    doc.add_paragraph("Executive Overview").style("Heading1");
1475    doc.add_paragraph(
1476        "This report presents Tensorbee's environmental performance for Q4 2025. Our \
1477         sustainability initiatives have yielded a 34% reduction in carbon emissions compared \
1478         to Q4 2024, exceeding our target of 25%. Key achievements include the transition to \
1479         100% renewable energy in our primary data centers and the launch of our carbon offset \
1480         marketplace.",
1481    )
1482    .first_line_indent(Length::inches(0.3));
1483
1484    // Image: performance chart
1485    let chart_img = create_chart_png(400, 200);
1486    doc.add_paragraph("");
1487    doc.add_picture(
1488        &chart_img,
1489        "performance_chart.png",
1490        Length::inches(5.0),
1491        Length::inches(2.5),
1492    )
1493    .alignment(Alignment::Center);
1494    {
1495        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1496        p.add_run("Figure 1: Quarterly Carbon Emissions (tonnes CO2e)")
1497            .italic(true)
1498            .size(9.0)
1499            .color("666666");
1500    }
1501
1502    // ── Section 2: Energy Consumption ──
1503    doc.add_paragraph("").page_break_before(true);
1504    doc.add_paragraph("Energy Consumption").style("Heading1");
1505
1506    doc.add_paragraph("Data Center Operations")
1507        .style("Heading2");
1508    doc.add_paragraph(
1509        "Our three primary data centers consumed a combined 4.2 GWh in Q4 2025, \
1510         a 12% reduction from Q3 2025 driven by improved cooling efficiency and \
1511         server consolidation.",
1512    );
1513
1514    // Data table
1515    {
1516        let mut tbl = doc
1517            .add_table(5, 4)
1518            .borders(BorderStyle::Single, 4, "2D5016")
1519            .width_pct(100.0);
1520        let hdrs = ["Data Center", "Capacity (MW)", "Usage (GWh)", "PUE"];
1521        for (c, h) in hdrs.iter().enumerate() {
1522            tbl.cell(0, c).unwrap().set_text(h);
1523            tbl.cell(0, c).unwrap().shading("2D5016");
1524        }
1525        let rows = [
1526            ("San Francisco (Primary)", "3.2", "1.8", "1.12"),
1527            ("Dublin (EU)", "2.1", "1.2", "1.18"),
1528            ("Singapore (APAC)", "1.8", "1.2", "1.24"),
1529            ("TOTAL", "7.1", "4.2", "1.17 avg"),
1530        ];
1531        for (i, (dc, cap, use_, pue)) in rows.iter().enumerate() {
1532            tbl.cell(i + 1, 0).unwrap().set_text(dc);
1533            tbl.cell(i + 1, 1).unwrap().set_text(cap);
1534            tbl.cell(i + 1, 2).unwrap().set_text(use_);
1535            tbl.cell(i + 1, 3).unwrap().set_text(pue);
1536            if i == 3 {
1537                for c in 0..4 {
1538                    tbl.cell(i + 1, c).unwrap().shading("E2EFDA");
1539                }
1540            }
1541        }
1542    }
1543
1544    doc.add_paragraph("");
1545
1546    doc.add_paragraph("Renewable Energy Mix").style("Heading2");
1547    doc.add_paragraph("Breakdown of energy sources across all facilities:");
1548
1549    doc.add_bullet_list_item("Solar PV: 42% (1.76 GWh)", 0);
1550    doc.add_bullet_list_item("Wind Power Purchase Agreements: 38% (1.60 GWh)", 0);
1551    doc.add_bullet_list_item("Hydroelectric: 12% (0.50 GWh)", 0);
1552    doc.add_bullet_list_item("Grid (non-renewable): 8% (0.34 GWh)", 0);
1553
1554    // ── Section 3: Water & Waste ──
1555    doc.add_paragraph("Water & Waste Management")
1556        .style("Heading1");
1557
1558    doc.add_paragraph("Water Usage").style("Heading2");
1559    doc.add_paragraph(
1560        "Total water consumption was 12.4 million gallons, a 15% reduction from Q3. \
1561         Our closed-loop cooling systems now recycle 78% of water used in cooling operations.",
1562    );
1563
1564    doc.add_paragraph("Waste Reduction").style("Heading2");
1565    doc.add_paragraph("E-waste management results for Q4:")
1566        .keep_with_next(true);
1567    {
1568        let mut tbl = doc
1569            .add_table(4, 3)
1570            .borders(BorderStyle::Single, 2, "6B8E23")
1571            .width_pct(80.0);
1572        tbl.cell(0, 0).unwrap().set_text("Category");
1573        tbl.cell(0, 0).unwrap().shading("6B8E23");
1574        tbl.cell(0, 1).unwrap().set_text("Weight (kg)");
1575        tbl.cell(0, 1).unwrap().shading("6B8E23");
1576        tbl.cell(0, 2).unwrap().set_text("Recycled %");
1577        tbl.cell(0, 2).unwrap().shading("6B8E23");
1578        let rows = [
1579            ("Server Hardware", "2,450", "94%"),
1580            ("Networking Equipment", "820", "91%"),
1581            ("Storage Media", "340", "99%"),
1582        ];
1583        for (i, (cat, wt, pct)) in rows.iter().enumerate() {
1584            tbl.cell(i + 1, 0).unwrap().set_text(cat);
1585            tbl.cell(i + 1, 1).unwrap().set_text(wt);
1586            tbl.cell(i + 1, 2).unwrap().set_text(pct);
1587        }
1588    }
1589
1590    // ── Section 4: Initiatives ──
1591    doc.add_paragraph("").page_break_before(true);
1592    doc.add_paragraph("Q1 2026 Initiatives").style("Heading1");
1593
1594    doc.add_paragraph("Planned Programs").style("Heading2");
1595    doc.add_numbered_list_item(
1596        "Deploy on-site battery storage at SF data center (2 MWh capacity)",
1597        0,
1598    );
1599    doc.add_numbered_list_item("Pilot immersion cooling in Dublin facility", 0);
1600    doc.add_numbered_list_item("Launch employee commute carbon offset program", 0);
1601    doc.add_numbered_list_item("Achieve ISO 14001 certification for Singapore facility", 0);
1602
1603    doc.add_paragraph("Investment Targets").style("Heading2");
1604    doc.add_paragraph("Sustainability CapEx allocation for FY2026:")
1605        .keep_with_next(true);
1606    {
1607        let mut tbl = doc
1608            .add_table(5, 2)
1609            .borders(BorderStyle::Single, 4, "2D5016");
1610        tbl.cell(0, 0).unwrap().set_text("Initiative");
1611        tbl.cell(0, 0).unwrap().shading("2D5016");
1612        tbl.cell(0, 1).unwrap().set_text("Budget");
1613        tbl.cell(0, 1).unwrap().shading("2D5016");
1614        let rows = [
1615            ("Battery Storage", "$1.2M"),
1616            ("Immersion Cooling Pilot", "$800K"),
1617            ("Solar Panel Expansion", "$2.1M"),
1618            ("Carbon Credits", "$500K"),
1619        ];
1620        for (i, (init, budget)) in rows.iter().enumerate() {
1621            tbl.cell(i + 1, 0).unwrap().set_text(init);
1622            tbl.cell(i + 1, 1).unwrap().set_text(budget);
1623            if i % 2 == 0 {
1624                tbl.cell(i + 1, 0).unwrap().shading("FFFAF0");
1625                tbl.cell(i + 1, 1).unwrap().shading("FFFAF0");
1626            }
1627        }
1628    }
1629
1630    // Image: sustainability roadmap
1631    let roadmap_img = create_sample_png(500, 100, [40, 80, 30]);
1632    doc.add_paragraph("");
1633    doc.add_picture(
1634        &roadmap_img,
1635        "roadmap.png",
1636        Length::inches(6.0),
1637        Length::inches(1.2),
1638    )
1639    .alignment(Alignment::Center);
1640    {
1641        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1642        p.add_run("Figure 2: 2026 Sustainability Roadmap")
1643            .italic(true)
1644            .size(9.0)
1645            .color("666666");
1646    }
1647
1648    doc.add_paragraph("");
1649    doc.add_paragraph(
1650        "For questions about this report, contact Walter White at sustainability@tensorbee.com.",
1651    )
1652    .shading("FFFAF0")
1653    .border_all(BorderStyle::Single, 2, "2D5016");
1654
1655    doc
1656}
1657
1658// =============================================================================
1659// 6. LETTER — Slate blue + silver scheme
1660// =============================================================================
1661fn generate_letter(_samples_dir: &Path) -> Document {
1662    let mut doc = Document::new();
1663
1664    // Colors: Slate #4A5568, Blue accent #3182CE
1665    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1666    doc.set_margins(
1667        Length::inches(1.25),
1668        Length::inches(1.25),
1669        Length::inches(1.25),
1670        Length::inches(1.25),
1671    );
1672    doc.set_title("Business Letter");
1673    doc.set_author("Walter White");
1674
1675    // Banner header using raw XML
1676    let logo_img = create_logo_png(220, 48);
1677    let banner = build_header_banner_xml(
1678        "rId1",
1679        &BannerOpts {
1680            bg_color: "4A5568",
1681            banner_width: 7772400,
1682            banner_height: 731520, // ~0.8"
1683            logo_width: 2011680,
1684            logo_height: 438912,
1685            logo_x_offset: 295125,
1686            logo_y_offset: 146304,
1687        },
1688    );
1689    doc.set_raw_header_with_images(
1690        banner,
1691        &[("rId1", &logo_img, "logo.png")],
1692        rdocx_oxml::header_footer::HdrFtrType::Default,
1693    );
1694    doc.set_margins(
1695        Length::twips(2000),
1696        Length::twips(1800),
1697        Length::twips(1440),
1698        Length::twips(1800),
1699    );
1700    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
1701
1702    // Footer with contact
1703    doc.set_footer(
1704        "Tensorbee | 123 Innovation Drive, Suite 400 | San Francisco, CA 94105 | tensorbee.com",
1705    );
1706
1707    // ── Sender Address ──
1708    doc.add_paragraph("Walter White");
1709    doc.add_paragraph("Chief Executive Officer");
1710    doc.add_paragraph("Tensorbee");
1711    doc.add_paragraph("123 Innovation Drive, Suite 400");
1712    doc.add_paragraph("San Francisco, CA 94105");
1713    doc.add_paragraph("");
1714    doc.add_paragraph("February 22, 2026");
1715
1716    doc.add_paragraph("");
1717
1718    // ── Recipient ──
1719    doc.add_paragraph("Ms. Sarah Chen");
1720    doc.add_paragraph("VP of Engineering");
1721    doc.add_paragraph("Meridian Dynamics LLC");
1722    doc.add_paragraph("456 Enterprise Boulevard");
1723    doc.add_paragraph("Austin, TX 78701");
1724
1725    doc.add_paragraph("");
1726
1727    // ── Subject ──
1728    {
1729        let mut p = doc.add_paragraph("");
1730        p.add_run("Re: Strategic Technology Partnership — Phase 2 Expansion")
1731            .bold(true);
1732    }
1733
1734    doc.add_paragraph("");
1735
1736    // ── Body ──
1737    doc.add_paragraph("Dear Ms. Chen,");
1738    doc.add_paragraph("");
1739
1740    doc.add_paragraph(
1741        "I am writing to express Tensorbee's enthusiasm for expanding our strategic technology \
1742         partnership with Meridian Dynamics. Following the successful completion of Phase 1, \
1743         which delivered a 40% improvement in your ML inference pipeline throughput, we believe \
1744         the foundation is firmly established for an ambitious Phase 2 engagement.",
1745    )
1746    .first_line_indent(Length::inches(0.5));
1747
1748    doc.add_paragraph(
1749        "During our recent executive review, your team highlighted three priority areas for \
1750         the next phase of collaboration:",
1751    )
1752    .first_line_indent(Length::inches(0.5));
1753
1754    doc.add_numbered_list_item(
1755        "Real-time anomaly detection for your financial transaction monitoring system, \
1756         targeting sub-100ms latency at 50,000 transactions per second.",
1757        0,
1758    );
1759    doc.add_numbered_list_item(
1760        "Federated learning infrastructure to enable model training across your distributed \
1761         data centers without centralizing sensitive financial data.",
1762        0,
1763    );
1764    doc.add_numbered_list_item(
1765        "MLOps automation to reduce your model deployment cycle from the current 5 days \
1766         to under 4 hours.",
1767        0,
1768    );
1769
1770    doc.add_paragraph(
1771        "Our engineering team has prepared a detailed technical proposal addressing each of \
1772         these areas. We have allocated a dedicated team of eight senior engineers, led by \
1773         our CTO, to ensure continuity with the Phase 1 team your organization has already \
1774         built a productive working relationship with.",
1775    )
1776    .first_line_indent(Length::inches(0.5));
1777
1778    doc.add_paragraph(
1779        "I would welcome the opportunity to present our Phase 2 proposal in person. My \
1780         assistant will reach out to coordinate a meeting at your convenience during the \
1781         first week of March.",
1782    )
1783    .first_line_indent(Length::inches(0.5));
1784
1785    doc.add_paragraph("");
1786
1787    doc.add_paragraph("Warm regards,");
1788    doc.add_paragraph("");
1789    doc.add_paragraph("");
1790
1791    // Signature line
1792    doc.add_paragraph("")
1793        .border_bottom(BorderStyle::Single, 4, "4A5568");
1794    {
1795        let mut p = doc.add_paragraph("");
1796        p.add_run("Walter White").bold(true).size(12.0);
1797    }
1798    {
1799        let mut p = doc.add_paragraph("");
1800        p.add_run("Chief Executive Officer, Tensorbee")
1801            .italic(true)
1802            .size(10.0)
1803            .color("666666");
1804    }
1805    {
1806        let mut p = doc.add_paragraph("");
1807        p.add_run("walter@tensorbee.com | +1 (415) 555-0199")
1808            .size(10.0)
1809            .color("888888");
1810    }
1811
1812    doc
1813}
1814
1815// =============================================================================
1816// 7. EMPLOYMENT CONTRACT — Purple + charcoal scheme
1817// =============================================================================
1818fn generate_contract(_samples_dir: &Path) -> Document {
1819    let mut doc = Document::new();
1820
1821    // Colors: Purple #5B2C6F, Plum #8E44AD, Gray #2C3E50
1822    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1823    doc.set_margins(
1824        Length::inches(1.25),
1825        Length::inches(1.0),
1826        Length::inches(1.0),
1827        Length::inches(1.0),
1828    );
1829    doc.set_title("Employment Agreement");
1830    doc.set_author("Tensorbee HR Department");
1831    doc.set_subject("Employment Contract — Walter White");
1832    doc.set_keywords("employment, contract, agreement, Tensorbee");
1833
1834    doc.set_header("TENSORBEE — Employment Agreement");
1835    doc.set_footer("Employment Agreement — Walter White — Page");
1836
1837    // ── Title Block ──
1838    doc.add_paragraph("")
1839        .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1840    {
1841        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1842        p.add_run("EMPLOYMENT AGREEMENT")
1843            .bold(true)
1844            .size(24.0)
1845            .color("5B2C6F")
1846            .font("Georgia");
1847    }
1848    doc.add_paragraph("")
1849        .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1850    doc.add_paragraph("");
1851
1852    // ── Parties ──
1853    doc.add_paragraph(
1854        "This Employment Agreement (\"Agreement\") is entered into as of February 22, 2026 \
1855         (\"Effective Date\"), by and between:",
1856    );
1857
1858    doc.add_paragraph("");
1859
1860    {
1861        let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1862        p.add_run("EMPLOYER: ").bold(true);
1863        p.add_run(
1864            "Tensorbee, Inc., a Delaware corporation with its principal place of business at \
1865                   123 Innovation Drive, Suite 400, San Francisco, CA 94105 (\"Company\")",
1866        );
1867    }
1868    doc.add_paragraph("");
1869    {
1870        let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1871        p.add_run("EMPLOYEE: ").bold(true);
1872        p.add_run(
1873            "Walter White, residing at 308 Negra Arroyo Lane, Albuquerque, NM 87104 (\"Employee\")",
1874        );
1875    }
1876
1877    doc.add_paragraph("");
1878    doc.add_paragraph("The Company and Employee are collectively referred to as the \"Parties.\"");
1879    doc.add_paragraph("");
1880
1881    // ── Article 1: Position and Duties ──
1882    doc.add_paragraph("Article 1 — Position and Duties")
1883        .style("Heading1");
1884    {
1885        let mut p = doc.add_paragraph("");
1886        p.add_run("1.1 ").bold(true);
1887        p.add_run("The Company hereby employs the Employee as ");
1888        p.add_run("Chief Executive Officer (CEO)")
1889            .bold(true)
1890            .italic(true);
1891        p.add_run(", reporting directly to the Board of Directors.");
1892    }
1893    {
1894        let mut p = doc.add_paragraph("");
1895        p.add_run("1.2 ").bold(true);
1896        p.add_run("The Employee shall devote full working time, attention, and best efforts to \
1897                   the performance of duties as reasonably assigned by the Board, including but not limited to:");
1898    }
1899    doc.add_bullet_list_item(
1900        "Setting and executing the Company's strategic vision and business plan",
1901        0,
1902    );
1903    doc.add_bullet_list_item(
1904        "Overseeing all operations, engineering, and commercial activities",
1905        0,
1906    );
1907    doc.add_bullet_list_item(
1908        "Representing the Company to investors, customers, and the public",
1909        0,
1910    );
1911    doc.add_bullet_list_item("Recruiting, developing, and retaining key talent", 0);
1912
1913    {
1914        let mut p = doc.add_paragraph("");
1915        p.add_run("1.3 ").bold(true);
1916        p.add_run(
1917            "The Employee's primary work location shall be the Company's San Francisco \
1918                   headquarters, with reasonable travel as required by business needs.",
1919        );
1920    }
1921
1922    // ── Article 2: Compensation ──
1923    doc.add_paragraph("Article 2 — Compensation and Benefits")
1924        .style("Heading1");
1925    {
1926        let mut p = doc.add_paragraph("");
1927        p.add_run("2.1 Base Salary. ").bold(true);
1928        p.add_run("The Company shall pay the Employee an annual base salary of ");
1929        p.add_run("$375,000.00").bold(true);
1930        p.add_run(" (Three Hundred Seventy-Five Thousand Dollars), payable in accordance with the \
1931                   Company's standard payroll schedule, less applicable withholdings and deductions.");
1932    }
1933    {
1934        let mut p = doc.add_paragraph("");
1935        p.add_run("2.2 Annual Bonus. ").bold(true);
1936        p.add_run("The Employee shall be eligible for an annual performance bonus of up to ");
1937        p.add_run("40%").bold(true);
1938        p.add_run(
1939            " of base salary, based on achievement of mutually agreed performance objectives.",
1940        );
1941    }
1942    {
1943        let mut p = doc.add_paragraph("");
1944        p.add_run("2.3 Equity. ").bold(true);
1945        p.add_run("Subject to Board approval, the Employee shall receive a stock option grant of ");
1946        p.add_run("500,000 shares").bold(true);
1947        p.add_run(
1948            " of the Company's common stock, vesting over four (4) years with a one-year cliff, \
1949                   at an exercise price equal to the fair market value on the date of grant.",
1950        );
1951    }
1952
1953    // Compensation summary table
1954    doc.add_paragraph("");
1955    {
1956        let mut tbl = doc
1957            .add_table(5, 2)
1958            .borders(BorderStyle::Single, 4, "5B2C6F")
1959            .width_pct(70.0)
1960            .alignment(Alignment::Center);
1961        tbl.cell(0, 0).unwrap().set_text("Compensation Element");
1962        tbl.cell(0, 0).unwrap().shading("5B2C6F");
1963        tbl.cell(0, 1).unwrap().set_text("Value");
1964        tbl.cell(0, 1).unwrap().shading("5B2C6F");
1965        let items = [
1966            ("Base Salary", "$375,000/year"),
1967            ("Target Bonus", "Up to 40% ($150,000)"),
1968            ("Equity Grant", "500,000 shares (4yr vest)"),
1969            ("Total Target Comp", "$525,000 + equity"),
1970        ];
1971        for (i, (elem, val)) in items.iter().enumerate() {
1972            tbl.cell(i + 1, 0).unwrap().set_text(elem);
1973            tbl.cell(i + 1, 1).unwrap().set_text(val);
1974            if i == 3 {
1975                tbl.cell(i + 1, 0).unwrap().shading("E8DAEF");
1976                tbl.cell(i + 1, 1).unwrap().shading("E8DAEF");
1977            }
1978        }
1979    }
1980
1981    // ── Article 3: Benefits ──
1982    doc.add_paragraph("").page_break_before(true);
1983    doc.add_paragraph("Article 3 — Benefits").style("Heading1");
1984    {
1985        let mut p = doc.add_paragraph("");
1986        p.add_run("3.1 ").bold(true);
1987        p.add_run(
1988            "The Employee shall be entitled to participate in all benefit programs \
1989                   generally available to senior executives, including:",
1990        );
1991    }
1992    doc.add_bullet_list_item(
1993        "Medical, dental, and vision insurance (100% premium coverage for employee and dependents)",
1994        0,
1995    );
1996    doc.add_bullet_list_item("401(k) retirement plan with 6% company match", 0);
1997    doc.add_bullet_list_item("Life insurance and long-term disability coverage", 0);
1998    doc.add_bullet_list_item("Annual professional development allowance of $10,000", 0);
1999
2000    {
2001        let mut p = doc.add_paragraph("");
2002        p.add_run("3.2 Paid Time Off. ").bold(true);
2003        p.add_run(
2004            "The Employee shall receive 25 days of paid vacation per year, plus Company holidays, \
2005                   accruing on a monthly basis.",
2006        );
2007    }
2008
2009    // ── Article 4: Term and Termination ──
2010    doc.add_paragraph("Article 4 — Term and Termination")
2011        .style("Heading1");
2012    {
2013        let mut p = doc.add_paragraph("");
2014        p.add_run("4.1 At-Will Employment. ").bold(true);
2015        p.add_run(
2016            "This Agreement is for at-will employment and may be terminated by either Party \
2017                   at any time, with or without cause, subject to the notice provisions herein.",
2018        );
2019    }
2020    {
2021        let mut p = doc.add_paragraph("");
2022        p.add_run("4.2 Notice Period. ").bold(true);
2023        p.add_run("Either Party shall provide ");
2024        p.add_run("ninety (90) days").bold(true);
2025        p.add_run(" written notice of termination, or pay in lieu thereof.");
2026    }
2027    {
2028        let mut p = doc.add_paragraph("");
2029        p.add_run("4.3 Severance. ").bold(true);
2030        p.add_run("In the event of termination by the Company without Cause, the Employee shall \
2031                   receive (a) twelve (12) months of base salary continuation, (b) pro-rata bonus \
2032                   for the year of termination, and (c) twelve (12) months of COBRA premium coverage.");
2033    }
2034
2035    // ── Article 5: Confidentiality ──
2036    doc.add_paragraph("Article 5 — Confidentiality and IP")
2037        .style("Heading1");
2038    {
2039        let mut p = doc.add_paragraph("");
2040        p.add_run("5.1 ").bold(true);
2041        p.add_run(
2042            "The Employee agrees to maintain strict confidentiality of all proprietary \
2043                   information, trade secrets, and business plans of the Company during and after \
2044                   employment.",
2045        );
2046    }
2047    {
2048        let mut p = doc.add_paragraph("");
2049        p.add_run("5.2 ").bold(true);
2050        p.add_run(
2051            "All intellectual property, inventions, and works of authorship created by the \
2052                   Employee during the term of employment and within the scope of duties shall be \
2053                   the sole and exclusive property of the Company.",
2054        );
2055    }
2056
2057    // ── Article 6: Non-Compete ──
2058    doc.add_paragraph("Article 6 — Non-Competition")
2059        .style("Heading1");
2060    {
2061        let mut p = doc.add_paragraph("");
2062        p.add_run("6.1 ").bold(true);
2063        p.add_run("For a period of twelve (12) months following termination, the Employee agrees \
2064                   not to engage in any business that directly competes with the Company's core \
2065                   business of AI infrastructure and ML operations services, within the United States.");
2066    }
2067
2068    // ── Article 7: General Provisions ──
2069    doc.add_paragraph("Article 7 — General Provisions")
2070        .style("Heading1");
2071    {
2072        let mut p = doc.add_paragraph("");
2073        p.add_run("7.1 Governing Law. ").bold(true);
2074        p.add_run(
2075            "This Agreement shall be governed by and construed in accordance with the laws of \
2076                   the State of California.",
2077        );
2078    }
2079    {
2080        let mut p = doc.add_paragraph("");
2081        p.add_run("7.2 Entire Agreement. ").bold(true);
2082        p.add_run(
2083            "This Agreement constitutes the entire agreement between the Parties and supersedes \
2084                   all prior negotiations, representations, and agreements.",
2085        );
2086    }
2087    {
2088        let mut p = doc.add_paragraph("");
2089        p.add_run("7.3 Amendment. ").bold(true);
2090        p.add_run(
2091            "This Agreement may only be amended by a written instrument signed by both Parties.",
2092        );
2093    }
2094
2095    // ── Signature Block ──
2096    doc.add_paragraph("").page_break_before(true);
2097    doc.add_paragraph("IN WITNESS WHEREOF, the Parties have executed this Employment Agreement as of the Effective Date.")
2098        .space_after(Length::pt(24.0));
2099
2100    // Signature table
2101    {
2102        let mut tbl = doc.add_table(6, 2).width_pct(100.0);
2103        tbl.cell(0, 0).unwrap().set_text("FOR THE COMPANY:");
2104        tbl.cell(0, 0).unwrap().shading("5B2C6F");
2105        tbl.cell(0, 1).unwrap().set_text("EMPLOYEE:");
2106        tbl.cell(0, 1).unwrap().shading("5B2C6F");
2107
2108        tbl.cell(1, 0).unwrap().set_text("");
2109        tbl.cell(1, 1).unwrap().set_text("");
2110        tbl.row(1).unwrap().height(Length::pt(40.0));
2111
2112        tbl.cell(2, 0)
2113            .unwrap()
2114            .set_text("Signature: ___________________________");
2115        tbl.cell(2, 1)
2116            .unwrap()
2117            .set_text("Signature: ___________________________");
2118        tbl.cell(3, 0)
2119            .unwrap()
2120            .set_text("Name: Board Representative");
2121        tbl.cell(3, 1).unwrap().set_text("Name: Walter White");
2122        tbl.cell(4, 0)
2123            .unwrap()
2124            .set_text("Title: Chair, Board of Directors");
2125        tbl.cell(4, 1)
2126            .unwrap()
2127            .set_text("Title: Chief Executive Officer");
2128        tbl.cell(5, 0).unwrap().set_text("Date: _______________");
2129        tbl.cell(5, 1).unwrap().set_text("Date: _______________");
2130    }
2131
2132    doc
2133}
Source

pub fn table_count(&self) -> usize

Get the number of tables.

Source

pub fn content_count(&self) -> usize

Get the number of body content elements (paragraphs + tables).

Examples found in repository?
examples/generate_all_samples.rs (line 156)
97fn generate_feature_showcase(_samples_dir: &Path) -> Document {
98    let mut doc = Document::new();
99
100    // ── Page Setup & Metadata ──
101    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
102    doc.set_margins(
103        Length::inches(1.0),
104        Length::inches(1.0),
105        Length::inches(1.0),
106        Length::inches(1.0),
107    );
108    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
109    doc.set_gutter(Length::twips(0));
110    doc.set_title("rdocx Feature Showcase");
111    doc.set_author("rdocx Sample Generator");
112    doc.set_subject("Comprehensive feature demonstration");
113    doc.set_keywords("rdocx, docx, rust, sample, showcase");
114
115    // Headers & Footers with different first page
116    doc.set_different_first_page(true);
117    doc.set_first_page_header("rdocx");
118    doc.set_first_page_footer("Feature Showcase — Cover Page");
119    doc.set_header("rdocx Feature Showcase");
120    doc.set_footer("Generated by rdocx");
121
122    // ── COVER PAGE ──
123    let bg = create_sample_png(612, 792, [20, 45, 90]);
124    doc.add_background_image(&bg, "cover_bg.png");
125
126    for _ in 0..3 {
127        doc.add_paragraph("");
128    }
129    {
130        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
131        p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
132    }
133    {
134        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
135        p.add_run("Complete Feature Showcase")
136            .size(28.0)
137            .color("FFFFFF")
138            .italic(true);
139    }
140    doc.add_paragraph("");
141    {
142        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
143        p.add_run("Every feature of the rdocx Rust library")
144            .size(13.0)
145            .color("B0C4DE");
146    }
147    {
148        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
149        p.add_run("demonstrated in a single document.")
150            .size(13.0)
151            .color("B0C4DE");
152    }
153
154    // ── TABLE OF CONTENTS ──
155    doc.add_paragraph("").page_break_before(true);
156    doc.insert_toc(doc.content_count(), 3);
157
158    // ── SECTION 1: TEXT FORMATTING ──
159    doc.add_paragraph("").page_break_before(true);
160    doc.add_paragraph("1. Text Formatting").style("Heading1");
161
162    // Paragraph Alignment
163    doc.add_paragraph("1.1 Paragraph Alignment")
164        .style("Heading2");
165    doc.add_paragraph("Left-aligned paragraph (default).")
166        .alignment(Alignment::Left);
167    doc.add_paragraph("Center-aligned paragraph.")
168        .alignment(Alignment::Center);
169    doc.add_paragraph("Right-aligned paragraph.")
170        .alignment(Alignment::Right);
171    doc.add_paragraph(
172        "Justified paragraph — this text is long enough to demonstrate how justified alignment \
173         distributes extra space across word gaps so lines fill the full width of the text area.",
174    )
175    .alignment(Alignment::Justify);
176
177    doc.add_paragraph("");
178
179    // Run Formatting
180    doc.add_paragraph("1.2 Run Formatting").style("Heading2");
181    {
182        let mut p = doc.add_paragraph("");
183        p.add_run("Bold").bold(true);
184        p.add_run(" | ");
185        p.add_run("Italic").italic(true);
186        p.add_run(" | ");
187        p.add_run("Bold Italic").bold(true).italic(true);
188        p.add_run(" | ");
189        p.add_run("Underline").underline(true);
190        p.add_run(" | ");
191        p.add_run("Strikethrough").strike(true);
192        p.add_run(" | ");
193        p.add_run("Double Strike").double_strike(true);
194    }
195    {
196        let mut p = doc.add_paragraph("");
197        p.add_run("Red").color("FF0000");
198        p.add_run(" | ");
199        p.add_run("Blue").color("0000FF");
200        p.add_run(" | ");
201        p.add_run("Green").color("00AA00");
202        p.add_run(" | ");
203        p.add_run("Highlighted").highlight("FFFF00");
204    }
205    {
206        let mut p = doc.add_paragraph("");
207        p.add_run("8pt").size(8.0);
208        p.add_run(" | ");
209        p.add_run("11pt").size(11.0);
210        p.add_run(" | ");
211        p.add_run("16pt").size(16.0);
212        p.add_run(" | ");
213        p.add_run("24pt").size(24.0);
214    }
215    {
216        let mut p = doc.add_paragraph("");
217        p.add_run("Arial").font("Arial");
218        p.add_run(" | ");
219        p.add_run("Times New Roman").font("Times New Roman");
220        p.add_run(" | ");
221        p.add_run("Courier New").font("Courier New");
222    }
223    {
224        let mut p = doc.add_paragraph("");
225        p.add_run("H");
226        p.add_run("2").subscript();
227        p.add_run("O (subscript) | E=mc");
228        p.add_run("2").superscript();
229        p.add_run(" (superscript)");
230    }
231    {
232        let mut p = doc.add_paragraph("");
233        p.add_run("ALL CAPS").all_caps(true);
234        p.add_run(" | ");
235        p.add_run("Small Caps").small_caps(true);
236        p.add_run(" | ");
237        p.add_run("Expanded").character_spacing(Length::twips(40));
238        p.add_run(" | ");
239        p.add_run("Hidden text (not visible)").hidden(true);
240    }
241
242    doc.add_paragraph("");
243
244    // Underline Styles
245    doc.add_paragraph("1.3 Underline Styles").style("Heading2");
246    {
247        let mut p = doc.add_paragraph("");
248        p.add_run("Single ")
249            .underline_style(rdocx::UnderlineStyle::Single);
250        p.add_run("Double ")
251            .underline_style(rdocx::UnderlineStyle::Double);
252        p.add_run("Thick ")
253            .underline_style(rdocx::UnderlineStyle::Thick);
254        p.add_run("Dotted ")
255            .underline_style(rdocx::UnderlineStyle::Dotted);
256        p.add_run("Dash ")
257            .underline_style(rdocx::UnderlineStyle::Dash);
258        p.add_run("Wave ")
259            .underline_style(rdocx::UnderlineStyle::Wave);
260    }
261
262    // ── SECTION 2: PARAGRAPH FORMATTING ──
263    doc.add_paragraph("").page_break_before(true);
264    doc.add_paragraph("2. Paragraph Formatting")
265        .style("Heading1");
266
267    doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
268    doc.add_paragraph("Paragraph with light green shading")
269        .shading("E2EFDA");
270    doc.add_paragraph("Paragraph with bottom border")
271        .border_bottom(BorderStyle::Single, 6, "2E75B6");
272    doc.add_paragraph("Paragraph with all borders (red)")
273        .border_all(BorderStyle::Single, 4, "FF0000");
274
275    doc.add_paragraph("2.2 Indentation").style("Heading2");
276    doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
277        .indent_left(Length::inches(1.0))
278        .hanging_indent(Length::inches(0.5));
279    doc.add_paragraph("0.5-inch first-line indent on this paragraph")
280        .first_line_indent(Length::inches(0.5));
281    doc.add_paragraph("Both left and right indent (0.75 inches each)")
282        .indent_left(Length::inches(0.75))
283        .indent_right(Length::inches(0.75));
284
285    doc.add_paragraph("2.3 Spacing & Line Height")
286        .style("Heading2");
287    doc.add_paragraph("Extra space before (24pt) and after (12pt)")
288        .space_before(Length::pt(24.0))
289        .space_after(Length::pt(12.0));
290    doc.add_paragraph(
291        "Double line spacing paragraph. This text should have extra vertical space between \
292         lines to demonstrate line_spacing_multiple(2.0).",
293    )
294    .line_spacing_multiple(2.0);
295    doc.add_paragraph("Exact 20pt line spacing (fixed height).")
296        .line_spacing(20.0);
297
298    doc.add_paragraph("2.4 Pagination Controls")
299        .style("Heading2");
300    doc.add_paragraph("keep_with_next — this paragraph stays with the next")
301        .keep_with_next(true);
302    doc.add_paragraph("(This paragraph was kept with the one above.)");
303    doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
304        .keep_together(true);
305    doc.add_paragraph("widow_control — prevents widow/orphan lines")
306        .widow_control(true);
307
308    // ── SECTION 3: LISTS ──
309    doc.add_paragraph("").page_break_before(true);
310    doc.add_paragraph("3. Lists").style("Heading1");
311
312    doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
313    doc.add_bullet_list_item("First item", 0);
314    doc.add_bullet_list_item("Second item", 0);
315    doc.add_bullet_list_item("Nested level 1", 1);
316    doc.add_bullet_list_item("Nested level 2", 2);
317    doc.add_bullet_list_item("Back to level 1", 1);
318    doc.add_bullet_list_item("Third item", 0);
319
320    doc.add_paragraph("");
321
322    doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
323    doc.add_numbered_list_item("First numbered item", 0);
324    doc.add_numbered_list_item("Second numbered item", 0);
325    doc.add_numbered_list_item("Sub-item A", 1);
326    doc.add_numbered_list_item("Sub-item B", 1);
327    doc.add_numbered_list_item("Third numbered item", 0);
328
329    // ── SECTION 4: TAB STOPS ──
330    doc.add_paragraph("");
331    doc.add_paragraph("4. Tab Stops").style("Heading1");
332
333    doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
334    doc.add_paragraph("Left\tCenter\tRight\tDecimal")
335        .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
336        .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
337        .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
338        .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
339
340    doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
341    doc.add_paragraph("Item\t\tPrice")
342        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
343        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
344        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
345    doc.add_paragraph("Widget\t\t$19.99")
346        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
347        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
348        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
349    doc.add_paragraph("Gadget\t\t$249.50")
350        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
351        .add_tab_stop_with_leader(
352            TabAlignment::Right,
353            Length::inches(4.0),
354            TabLeader::Underscore,
355        )
356        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
357
358    // ── SECTION 5: TABLES ──
359    doc.add_paragraph("").page_break_before(true);
360    doc.add_paragraph("5. Tables").style("Heading1");
361
362    doc.add_paragraph("5.1 Basic Table").style("Heading2");
363    {
364        let mut tbl = doc
365            .add_table(4, 3)
366            .borders(BorderStyle::Single, 4, "000000");
367        for col in 0..3 {
368            tbl.cell(0, col).unwrap().shading("2E75B6");
369        }
370        tbl.cell(0, 0).unwrap().set_text("Name");
371        tbl.cell(0, 1).unwrap().set_text("Role");
372        tbl.cell(0, 2).unwrap().set_text("Location");
373        tbl.cell(1, 0).unwrap().set_text("Walter White");
374        tbl.cell(1, 1).unwrap().set_text("CEO");
375        tbl.cell(1, 2).unwrap().set_text("Albuquerque");
376        tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
377        tbl.cell(2, 1).unwrap().set_text("CTO");
378        tbl.cell(2, 2).unwrap().set_text("Remote");
379        tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
380        tbl.cell(3, 1).unwrap().set_text("Security");
381        tbl.cell(3, 2).unwrap().set_text("Washington");
382    }
383
384    doc.add_paragraph("");
385
386    doc.add_paragraph("5.2 Column Span & Cell Shading")
387        .style("Heading2");
388    {
389        let mut tbl = doc
390            .add_table(3, 4)
391            .borders(BorderStyle::Single, 4, "000000")
392            .width_pct(100.0);
393        tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
394        tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
395        tbl.cell(1, 0).unwrap().set_text("Region");
396        tbl.cell(1, 0).unwrap().shading("D6E4F0");
397        tbl.cell(1, 1).unwrap().set_text("Q1");
398        tbl.cell(1, 1).unwrap().shading("D6E4F0");
399        tbl.cell(1, 2).unwrap().set_text("Q2");
400        tbl.cell(1, 2).unwrap().shading("D6E4F0");
401        tbl.cell(1, 3).unwrap().set_text("Total");
402        tbl.cell(1, 3).unwrap().shading("D6E4F0");
403        tbl.cell(2, 0).unwrap().set_text("Americas");
404        tbl.cell(2, 1).unwrap().set_text("$2.4M");
405        tbl.cell(2, 2).unwrap().set_text("$2.7M");
406        tbl.cell(2, 3).unwrap().set_text("$5.1M");
407    }
408
409    doc.add_paragraph("");
410
411    doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
412    {
413        let mut tbl = doc
414            .add_table(4, 3)
415            .borders(BorderStyle::Single, 4, "000000");
416        tbl.cell(0, 0).unwrap().set_text("Category");
417        tbl.cell(0, 0).unwrap().shading("E2EFDA");
418        tbl.cell(0, 1).unwrap().set_text("Item");
419        tbl.cell(0, 1).unwrap().shading("E2EFDA");
420        tbl.cell(0, 2).unwrap().set_text("Price");
421        tbl.cell(0, 2).unwrap().shading("E2EFDA");
422        tbl.cell(1, 0).unwrap().set_text("Hardware");
423        tbl.cell(1, 0).unwrap().v_merge_restart();
424        tbl.cell(1, 1).unwrap().set_text("Laptop");
425        tbl.cell(1, 2).unwrap().set_text("$1,200");
426        tbl.cell(2, 0).unwrap().v_merge_continue();
427        tbl.cell(2, 1).unwrap().set_text("Monitor");
428        tbl.cell(2, 2).unwrap().set_text("$450");
429        tbl.cell(3, 0).unwrap().set_text("Software");
430        tbl.cell(3, 1).unwrap().set_text("IDE License");
431        tbl.cell(3, 2).unwrap().set_text("$200/yr");
432    }
433
434    doc.add_paragraph("");
435
436    doc.add_paragraph("5.4 Nested Table").style("Heading2");
437    {
438        let mut tbl = doc
439            .add_table(2, 2)
440            .borders(BorderStyle::Single, 6, "2E75B6");
441        tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
442        tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
443        tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
444        {
445            let mut cell = tbl.cell(1, 1).unwrap();
446            cell.set_text("Nested table below:");
447            let mut nested = cell
448                .add_table(2, 2)
449                .borders(BorderStyle::Single, 2, "FF6600");
450            nested.cell(0, 0).unwrap().set_text("A");
451            nested.cell(0, 1).unwrap().set_text("B");
452            nested.cell(1, 0).unwrap().set_text("C");
453            nested.cell(1, 1).unwrap().set_text("D");
454        }
455    }
456
457    doc.add_paragraph("");
458
459    doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
460        .style("Heading2");
461    {
462        let mut tbl = doc
463            .add_table(2, 3)
464            .borders(BorderStyle::Single, 4, "666666");
465        tbl.row(0).unwrap().height(Length::pt(50.0)).header();
466        tbl.cell(0, 0).unwrap().set_text("Top");
467        tbl.cell(0, 0)
468            .unwrap()
469            .vertical_alignment(VerticalAlignment::Top)
470            .shading("FFF2CC");
471        tbl.cell(0, 1).unwrap().set_text("Center");
472        tbl.cell(0, 1)
473            .unwrap()
474            .vertical_alignment(VerticalAlignment::Center)
475            .shading("D9E2F3");
476        tbl.cell(0, 2).unwrap().set_text("Bottom");
477        tbl.cell(0, 2)
478            .unwrap()
479            .vertical_alignment(VerticalAlignment::Bottom)
480            .shading("E2EFDA");
481        tbl.row(1).unwrap().cant_split();
482        tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
483        tbl.cell(1, 0).unwrap().no_wrap();
484        tbl.cell(1, 1).unwrap().set_text("Fixed width");
485        tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
486        tbl.cell(1, 2).unwrap().set_text("Normal");
487    }
488
489    // ── SECTION 6: IMAGES ──
490    doc.add_paragraph("").page_break_before(true);
491    doc.add_paragraph("6. Images").style("Heading1");
492
493    doc.add_paragraph("6.1 Inline Image").style("Heading2");
494    doc.add_paragraph("A blue gradient image below:");
495    let img = create_sample_png(200, 50, [0, 80, 200]);
496    doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
497
498    doc.add_paragraph("");
499    doc.add_paragraph("6.2 Header Image").style("Heading2");
500    let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
501    doc.set_header_image(
502        &hdr_img,
503        "header_logo.png",
504        Length::inches(2.0),
505        Length::inches(0.2),
506    );
507    doc.add_paragraph("The header now contains an inline image (check the top of this page).");
508
509    doc.add_paragraph("");
510    doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
511
512    // ── SECTION 7: CONTENT MANIPULATION ──
513    doc.add_paragraph("").page_break_before(true);
514    doc.add_paragraph("7. Content Manipulation")
515        .style("Heading1");
516
517    doc.add_paragraph("7.1 Placeholder Replacement")
518        .style("Heading2");
519    doc.add_paragraph("Customer: {{customer}}");
520    doc.add_paragraph("Date: {{date}}");
521    doc.add_paragraph("Reference: {{ref_number}}");
522    {
523        let mut tbl = doc
524            .add_table(2, 2)
525            .borders(BorderStyle::Single, 4, "000000");
526        tbl.cell(0, 0).unwrap().set_text("Project");
527        tbl.cell(0, 0).unwrap().shading("D6E4F0");
528        tbl.cell(0, 1).unwrap().set_text("{{project}}");
529        tbl.cell(1, 0).unwrap().set_text("Status");
530        tbl.cell(1, 0).unwrap().shading("D6E4F0");
531        tbl.cell(1, 1).unwrap().set_text("{{status}}");
532    }
533
534    let mut replacements = HashMap::new();
535    replacements.insert("{{customer}}", "Tensorbee Inc.");
536    replacements.insert("{{date}}", "February 22, 2026");
537    replacements.insert("{{ref_number}}", "TB-2026-001");
538    replacements.insert("{{project}}", "Infrastructure Upgrade");
539    replacements.insert("{{status}}", "In Progress");
540    let n = doc.replace_all(&replacements);
541    doc.add_paragraph(&format!("({n} placeholders replaced above)"));
542
543    doc.add_paragraph("");
544
545    doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
546    doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
547    let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
548    doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
549
550    doc.add_paragraph("");
551
552    doc.add_paragraph("7.3 Content Insertion").style("Heading2");
553    doc.add_paragraph("Section A: First content.");
554    doc.add_paragraph("Section C: Third content.");
555    if let Some(idx) = doc.find_content_index("Section C") {
556        doc.insert_paragraph(
557            idx,
558            "Section B: Inserted between A and C via find_content_index().",
559        );
560    }
561
562    // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
563    doc.add_paragraph("").section_break(SectionBreak::NextPage);
564    doc.add_paragraph("8. Section Breaks & Orientation")
565        .style("Heading1");
566    doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
567
568    {
569        let mut tbl = doc
570            .add_table(3, 7)
571            .borders(BorderStyle::Single, 4, "2E75B6");
572        let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
573        for (c, h) in headers.iter().enumerate() {
574            tbl.cell(0, c).unwrap().set_text(h);
575            tbl.cell(0, c).unwrap().shading("2E75B6");
576        }
577        let data = [
578            [
579                "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
580            ],
581            [
582                "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
583            ],
584        ];
585        for (r, row) in data.iter().enumerate() {
586            for (c, v) in row.iter().enumerate() {
587                tbl.cell(r + 1, c).unwrap().set_text(v);
588            }
589        }
590    }
591
592    doc.add_paragraph("")
593        .section_break(SectionBreak::NextPage)
594        .section_landscape();
595
596    // ── SECTION 9: CUSTOM STYLES ──
597    doc.add_paragraph("9. Custom Styles").style("Heading1");
598    doc.add_style(
599        StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
600            .based_on("Normal")
601            .paragraph_properties(rdocx_oxml::properties::CT_PPr {
602                shading: Some(rdocx_oxml::properties::CT_Shd {
603                    val: "clear".to_string(),
604                    color: None,
605                    fill: Some("FFF2CC".to_string()),
606                }),
607                ..Default::default()
608            })
609            .run_properties(rdocx_oxml::properties::CT_RPr {
610                bold: Some(true),
611                color: Some("C45911".to_string()),
612                ..Default::default()
613            }),
614    );
615    doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
616        .style("CustomHighlight");
617
618    doc.add_paragraph("");
619
620    // ── SECTION 10: DOCUMENT INTELLIGENCE ──
621    doc.add_paragraph("10. Document Intelligence API")
622        .style("Heading1");
623
624    let wc = doc.word_count();
625    let hc = doc.headings().len();
626    let ic = doc.images().len();
627    let lc = doc.links().len();
628    doc.add_paragraph(&format!("Word count: {wc}"));
629    doc.add_paragraph(&format!("Heading count: {hc}"));
630    doc.add_paragraph(&format!("Image count: {ic}"));
631    doc.add_paragraph(&format!("Link count: {lc}"));
632
633    let outline = doc.document_outline();
634    doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
635
636    let issues = doc.audit_accessibility();
637    doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
638    for issue in &issues {
639        doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
640    }
641
642    // ── SECTION 11: DOCUMENT MERGING ──
643    doc.add_paragraph("");
644    doc.add_paragraph("11. Document Merging").style("Heading1");
645    {
646        let mut other = Document::new();
647        other.add_paragraph("This paragraph was merged from another document using append().");
648        doc.append(&other);
649    }
650
651    doc.add_paragraph("");
652
653    // ── SUMMARY ──
654    doc.add_paragraph("Summary of Demonstrated Features")
655        .style("Heading1");
656    let features = [
657        "Page setup: size, margins, header/footer distance, gutter",
658        "Document metadata: title, author, subject, keywords",
659        "Headers and footers: text, images, different first page",
660        "Background images (full-page behind text)",
661        "Table of Contents generation with bookmarks",
662        "Text formatting: bold, italic, underline styles, strike, color, size, font",
663        "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
664        "Paragraph formatting: alignment, borders, shading, spacing, indentation",
665        "Pagination controls: keep-with-next, keep-together, widow control",
666        "Bullet and numbered lists with nesting",
667        "Tab stops with dot/underscore/hyphen leaders",
668        "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
669        "Row properties: header rows, exact height, cant-split",
670        "Cell properties: width, no-wrap, vertical alignment",
671        "Inline images and header images",
672        "Placeholder replacement (single and batch)",
673        "Regex-based find and replace",
674        "Content insertion at specific positions",
675        "Section breaks with mixed portrait/landscape",
676        "Custom paragraph and character styles",
677        "Document intelligence: word count, headings, outline, images, links",
678        "Accessibility audit",
679        "Document merging (append)",
680        "Export: DOCX, PDF, HTML, Markdown",
681    ];
682    for f in &features {
683        doc.add_bullet_list_item(f, 0);
684    }
685    doc.add_paragraph("");
686    doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
687        .alignment(Alignment::Center)
688        .shading("E2EFDA")
689        .border_all(BorderStyle::Single, 2, "00AA00");
690
691    doc
692}
693
694// =============================================================================
695// 2. PROPOSAL DOCUMENT — Deep navy + gold scheme
696// =============================================================================
697fn generate_proposal(_samples_dir: &Path) -> Document {
698    let mut doc = Document::new();
699
700    // Colors: Navy #1B2A4A, Gold #C5922E, Light #F4F1EB
701    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
702    doc.set_margins(
703        Length::inches(1.0),
704        Length::inches(1.0),
705        Length::inches(1.0),
706        Length::inches(1.0),
707    );
708    doc.set_title("AI Platform Modernization Proposal");
709    doc.set_author("Walter White");
710    doc.set_subject("Technology Proposal");
711    doc.set_keywords("proposal, AI, modernization, Tensorbee");
712
713    // Banner header
714    let logo_img = create_logo_png(220, 48);
715    let banner = build_header_banner_xml(
716        "rId1",
717        &BannerOpts {
718            bg_color: "1B2A4A",
719            banner_width: 7772400,
720            banner_height: 914400,
721            logo_width: 2011680,
722            logo_height: 438912,
723            logo_x_offset: 295125,
724            logo_y_offset: 237744,
725        },
726    );
727    doc.set_raw_header_with_images(
728        banner,
729        &[("rId1", &logo_img, "logo.png")],
730        rdocx_oxml::header_footer::HdrFtrType::Default,
731    );
732    doc.set_different_first_page(true);
733    let cover_banner = build_header_banner_xml(
734        "rId1",
735        &BannerOpts {
736            bg_color: "C5922E",
737            banner_width: 7772400,
738            banner_height: 914400,
739            logo_width: 2011680,
740            logo_height: 438912,
741            logo_x_offset: 295125,
742            logo_y_offset: 237744,
743        },
744    );
745    doc.set_raw_header_with_images(
746        cover_banner,
747        &[("rId1", &logo_img, "logo.png")],
748        rdocx_oxml::header_footer::HdrFtrType::First,
749    );
750    doc.set_margins(
751        Length::twips(2292),
752        Length::twips(1440),
753        Length::twips(1440),
754        Length::twips(1440),
755    );
756    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
757    doc.set_footer("Tensorbee — Confidential");
758
759    // Custom styles
760    doc.add_style(
761        StyleBuilder::paragraph("ProposalTitle", "Proposal Title")
762            .based_on("Normal")
763            .run_properties(rdocx_oxml::properties::CT_RPr {
764                bold: Some(true),
765                font_ascii: Some("Georgia".to_string()),
766                font_hansi: Some("Georgia".to_string()),
767                sz: Some(rdocx_oxml::HalfPoint(56)), // half-points → 28pt
768                color: Some("1B2A4A".to_string()),
769                ..Default::default()
770            }),
771    );
772
773    // ── Cover Page ──
774    for _ in 0..4 {
775        doc.add_paragraph("");
776    }
777    doc.add_paragraph("AI Platform Modernization")
778        .style("ProposalTitle")
779        .alignment(Alignment::Center);
780    doc.add_paragraph("")
781        .alignment(Alignment::Center)
782        .border_bottom(BorderStyle::Single, 8, "C5922E");
783    {
784        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
785        p.add_run("Prepared for Global Financial Corp.")
786            .size(14.0)
787            .color("666666")
788            .italic(true);
789    }
790    {
791        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
792        p.add_run("Prepared by: Walter White, CEO — Tensorbee")
793            .size(12.0)
794            .color("888888");
795    }
796    {
797        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
798        p.add_run("February 22, 2026").size(12.0).color("888888");
799    }
800
801    // ── TOC ──
802    doc.add_paragraph("").page_break_before(true);
803    doc.add_paragraph("Table of Contents").style("Heading1");
804    // We'll add a manual-style TOC since the insert_toc goes at a specific position
805    doc.insert_toc(doc.content_count(), 2);
806
807    // ── Executive Summary ──
808    doc.add_paragraph("").page_break_before(true);
809    doc.add_paragraph("Executive Summary").style("Heading1");
810    doc.add_paragraph(
811        "Tensorbee proposes a comprehensive modernization of Global Financial Corp's AI platform, \
812         transitioning from legacy batch-processing systems to a real-time inference architecture. \
813         This transformation will reduce model deployment time from weeks to hours, improve \
814         prediction accuracy by an estimated 23%, and deliver $4.2M in annual operational savings.",
815    )
816    .first_line_indent(Length::inches(0.3));
817    doc.add_paragraph(
818        "The project spans three phases over 18 months, with a total investment of $2.8M. \
819         Tensorbee brings deep expertise in ML infrastructure, having successfully completed \
820         similar transformations for three Fortune 500 financial institutions.",
821    )
822    .first_line_indent(Length::inches(0.3));
823
824    // Key metrics table
825    doc.add_paragraph("");
826    {
827        let mut tbl = doc
828            .add_table(5, 2)
829            .borders(BorderStyle::Single, 4, "1B2A4A")
830            .width_pct(80.0);
831        tbl.cell(0, 0).unwrap().set_text("Key Metric");
832        tbl.cell(0, 0).unwrap().shading("1B2A4A");
833        tbl.cell(0, 1).unwrap().set_text("Value");
834        tbl.cell(0, 1).unwrap().shading("1B2A4A");
835        let rows = [
836            ("Total Investment", "$2.8M"),
837            ("Annual Savings", "$4.2M"),
838            ("ROI (Year 1)", "150%"),
839            ("Timeline", "18 months"),
840        ];
841        for (i, (k, v)) in rows.iter().enumerate() {
842            tbl.cell(i + 1, 0).unwrap().set_text(k);
843            if i % 2 == 0 {
844                tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
845            }
846            tbl.cell(i + 1, 1).unwrap().set_text(v);
847            if i % 2 == 0 {
848                tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
849            }
850        }
851    }
852
853    // ── Problem Statement ──
854    doc.add_paragraph("").page_break_before(true);
855    doc.add_paragraph("Problem Statement").style("Heading1");
856    doc.add_paragraph(
857        "Global Financial Corp's current AI infrastructure faces three critical challenges:",
858    );
859    doc.add_numbered_list_item("Deployment Latency — New models take 3-4 weeks to deploy to production, compared to the industry benchmark of 2-3 days.", 0);
860    doc.add_numbered_list_item("Scalability Constraints — The batch processing architecture cannot handle real-time inference demands during peak trading hours.", 0);
861    doc.add_numbered_list_item("Technical Debt — Legacy Python 2.7 codebases and manual deployment scripts create reliability risks and slow iteration speed.", 0);
862
863    // ── Proposed Solution ──
864    doc.add_paragraph("").page_break_before(true);
865    doc.add_paragraph("Proposed Solution").style("Heading1");
866
867    doc.add_paragraph("Phase 1: Foundation (Months 1-6)")
868        .style("Heading2");
869    doc.add_bullet_list_item("Deploy Kubernetes-based ML serving infrastructure", 0);
870    doc.add_bullet_list_item("Implement CI/CD pipeline for model deployment", 0);
871    doc.add_bullet_list_item("Migrate top 5 production models to new platform", 0);
872
873    doc.add_paragraph("Phase 2: Expansion (Months 7-12)")
874        .style("Heading2");
875    doc.add_bullet_list_item(
876        "Real-time feature engineering pipeline (Apache Kafka + Flink)",
877        0,
878    );
879    doc.add_bullet_list_item("A/B testing framework for model variants", 0);
880    doc.add_bullet_list_item("Automated model monitoring and drift detection", 0);
881
882    doc.add_paragraph("Phase 3: Optimization (Months 13-18)")
883        .style("Heading2");
884    doc.add_bullet_list_item("GPU inference optimization (TensorRT/ONNX Runtime)", 0);
885    doc.add_bullet_list_item("Multi-region deployment for latency reduction", 0);
886    doc.add_bullet_list_item(
887        "Self-service model deployment portal for data science teams",
888        0,
889    );
890
891    // ── Budget ──
892    doc.add_paragraph("Budget Breakdown").style("Heading1");
893    {
894        let mut tbl = doc
895            .add_table(6, 3)
896            .borders(BorderStyle::Single, 4, "1B2A4A")
897            .width_pct(100.0);
898        tbl.cell(0, 0).unwrap().set_text("Category");
899        tbl.cell(0, 0).unwrap().shading("1B2A4A");
900        tbl.cell(0, 1).unwrap().set_text("Cost");
901        tbl.cell(0, 1).unwrap().shading("1B2A4A");
902        tbl.cell(0, 2).unwrap().set_text("Notes");
903        tbl.cell(0, 2).unwrap().shading("1B2A4A");
904        let items = [
905            (
906                "Infrastructure",
907                "$450,000",
908                "Cloud compute + GPU instances",
909            ),
910            ("Engineering Services", "$1,600,000", "12 FTE x 18 months"),
911            (
912                "Software Licenses",
913                "$350,000",
914                "Kafka, monitoring, CI/CD tools",
915            ),
916            (
917                "Training & Enablement",
918                "$200,000",
919                "Team training, documentation",
920            ),
921            ("Contingency (10%)", "$200,000", "Risk buffer"),
922        ];
923        for (i, (cat, cost, note)) in items.iter().enumerate() {
924            tbl.cell(i + 1, 0).unwrap().set_text(cat);
925            tbl.cell(i + 1, 1).unwrap().set_text(cost);
926            tbl.cell(i + 1, 2).unwrap().set_text(note);
927            if i % 2 == 0 {
928                tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
929                tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
930                tbl.cell(i + 1, 2).unwrap().shading("F4F1EB");
931            }
932        }
933    }
934
935    // ── Closing ──
936    doc.add_paragraph("");
937    doc.add_paragraph("Next Steps").style("Heading1");
938    doc.add_numbered_list_item(
939        "Schedule technical deep-dive with GFC engineering team (Week 1)",
940        0,
941    );
942    doc.add_numbered_list_item("Finalize scope and sign SOW (Week 2-3)", 0);
943    doc.add_numbered_list_item("Kick off Phase 1 with joint planning session (Week 4)", 0);
944
945    doc.add_paragraph("");
946    {
947        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
948        p.add_run("Walter White")
949            .bold(true)
950            .size(14.0)
951            .color("1B2A4A");
952    }
953    {
954        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
955        p.add_run("CEO, Tensorbee | walter@tensorbee.com")
956            .size(10.0)
957            .color("888888");
958    }
959
960    doc
961}
962
963// =============================================================================
964// 3. QUOTE / BILL OF MATERIALS — Teal + orange scheme
965// =============================================================================
966fn generate_quote(_samples_dir: &Path) -> Document {
967    let mut doc = Document::new();
968
969    // Colors: Teal #008B8B, Dark #1A3C3C, Orange #E07020, Light #F0F8F8
970    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
971    doc.set_margins(
972        Length::inches(0.75),
973        Length::inches(0.75),
974        Length::inches(0.75),
975        Length::inches(0.75),
976    );
977    doc.set_title("Quotation QT-2026-0147");
978    doc.set_author("Walter White");
979    doc.set_keywords("quote, BOM, Tensorbee");
980
981    doc.set_header("Tensorbee — Quotation");
982    doc.set_footer("QT-2026-0147 | Page");
983
984    // ── Header Block ──
985    {
986        let mut p = doc.add_paragraph("").alignment(Alignment::Left);
987        p.add_run("TENSORBEE")
988            .bold(true)
989            .size(28.0)
990            .color("008B8B")
991            .font("Helvetica");
992    }
993    doc.add_paragraph("123 Innovation Drive, Suite 400")
994        .alignment(Alignment::Left);
995    doc.add_paragraph("San Francisco, CA 94105 | +1 (415) 555-0199")
996        .alignment(Alignment::Left);
997    doc.add_paragraph("")
998        .border_bottom(BorderStyle::Single, 8, "008B8B");
999
1000    // Quote meta
1001    doc.add_paragraph("");
1002    {
1003        let mut tbl = doc.add_table(4, 4).width_pct(100.0);
1004        tbl.cell(0, 0).unwrap().set_text("QUOTATION");
1005        tbl.cell(0, 0).unwrap().shading("008B8B").grid_span(2);
1006        tbl.cell(0, 2).unwrap().set_text("DATE");
1007        tbl.cell(0, 2).unwrap().shading("008B8B");
1008        tbl.cell(0, 3).unwrap().set_text("VALID UNTIL");
1009        tbl.cell(0, 3).unwrap().shading("008B8B");
1010        tbl.cell(1, 0).unwrap().set_text("Quote #:");
1011        tbl.cell(1, 1).unwrap().set_text("QT-2026-0147");
1012        tbl.cell(1, 2).unwrap().set_text("Feb 22, 2026");
1013        tbl.cell(1, 3).unwrap().set_text("Mar 22, 2026");
1014        tbl.cell(2, 0).unwrap().set_text("Prepared by:");
1015        tbl.cell(2, 1).unwrap().set_text("Walter White");
1016        tbl.cell(2, 2).unwrap().set_text("Payment:");
1017        tbl.cell(2, 3).unwrap().set_text("Net 30");
1018        tbl.cell(3, 0).unwrap().set_text("Customer:");
1019        tbl.cell(3, 1).unwrap().set_text("Meridian Dynamics LLC");
1020        tbl.cell(3, 2).unwrap().set_text("Currency:");
1021        tbl.cell(3, 3).unwrap().set_text("USD");
1022    }
1023
1024    doc.add_paragraph("");
1025
1026    // ── Bill of Materials ──
1027    {
1028        let mut p = doc.add_paragraph("");
1029        p.add_run("Bill of Materials")
1030            .bold(true)
1031            .size(16.0)
1032            .color("1A3C3C");
1033    }
1034    doc.add_paragraph("");
1035
1036    {
1037        let mut tbl = doc
1038            .add_table(10, 6)
1039            .borders(BorderStyle::Single, 2, "008B8B")
1040            .width_pct(100.0)
1041            .layout_fixed();
1042        // Headers
1043        let hdrs = [
1044            "#",
1045            "Part Number",
1046            "Description",
1047            "Qty",
1048            "Unit Price",
1049            "Total",
1050        ];
1051        for (c, h) in hdrs.iter().enumerate() {
1052            tbl.cell(0, c).unwrap().set_text(h);
1053            tbl.cell(0, c).unwrap().shading("008B8B");
1054        }
1055
1056        let items: Vec<(&str, &str, &str, &str, &str)> = vec![
1057            (
1058                "TB-GPU-A100",
1059                "NVIDIA A100 80GB GPU",
1060                "4",
1061                "$12,500.00",
1062                "$50,000.00",
1063            ),
1064            (
1065                "TB-SRV-R750",
1066                "Dell R750xa Server Chassis",
1067                "2",
1068                "$8,200.00",
1069                "$16,400.00",
1070            ),
1071            (
1072                "TB-RAM-256G",
1073                "256GB DDR5 ECC Memory Module",
1074                "8",
1075                "$890.00",
1076                "$7,120.00",
1077            ),
1078            (
1079                "TB-SSD-3840",
1080                "3.84TB NVMe U.2 SSD",
1081                "8",
1082                "$1,150.00",
1083                "$9,200.00",
1084            ),
1085            (
1086                "TB-NET-CX7",
1087                "ConnectX-7 200GbE NIC",
1088                "4",
1089                "$1,800.00",
1090                "$7,200.00",
1091            ),
1092            (
1093                "TB-SW-48P",
1094                "48-Port 100GbE Switch",
1095                "1",
1096                "$22,000.00",
1097                "$22,000.00",
1098            ),
1099            (
1100                "TB-CAB-RACK",
1101                "42U Server Rack + PDU",
1102                "1",
1103                "$4,500.00",
1104                "$4,500.00",
1105            ),
1106            (
1107                "TB-SVC-INST",
1108                "Installation & Configuration",
1109                "1",
1110                "$8,500.00",
1111                "$8,500.00",
1112            ),
1113            (
1114                "TB-SVC-SUPP",
1115                "3-Year Premium Support",
1116                "1",
1117                "$15,000.00",
1118                "$15,000.00",
1119            ),
1120        ];
1121
1122        for (i, (pn, desc, qty, unit, total)) in items.iter().enumerate() {
1123            let row = i + 1;
1124            tbl.cell(row, 0).unwrap().set_text(&format!("{}", i + 1));
1125            tbl.cell(row, 1).unwrap().set_text(pn);
1126            tbl.cell(row, 2).unwrap().set_text(desc);
1127            tbl.cell(row, 3).unwrap().set_text(qty);
1128            tbl.cell(row, 4).unwrap().set_text(unit);
1129            tbl.cell(row, 5).unwrap().set_text(total);
1130            if i % 2 == 0 {
1131                for c in 0..6 {
1132                    tbl.cell(row, c).unwrap().shading("F0F8F8");
1133                }
1134            }
1135        }
1136    }
1137
1138    doc.add_paragraph("");
1139
1140    // ── Totals ──
1141    {
1142        let mut tbl = doc
1143            .add_table(4, 2)
1144            .borders(BorderStyle::Single, 2, "008B8B")
1145            .width(Length::inches(3.5))
1146            .alignment(Alignment::Right);
1147        tbl.cell(0, 0).unwrap().set_text("Subtotal");
1148        tbl.cell(0, 1).unwrap().set_text("$139,920.00");
1149        tbl.cell(1, 0).unwrap().set_text("Shipping & Handling");
1150        tbl.cell(1, 1).unwrap().set_text("$2,500.00");
1151        tbl.cell(2, 0).unwrap().set_text("Tax (8.625%)");
1152        tbl.cell(2, 1).unwrap().set_text("$12,068.10");
1153        tbl.cell(3, 0).unwrap().set_text("TOTAL");
1154        tbl.cell(3, 0).unwrap().shading("E07020");
1155        tbl.cell(3, 1).unwrap().set_text("$154,488.10");
1156        tbl.cell(3, 1).unwrap().shading("E07020");
1157    }
1158
1159    doc.add_paragraph("");
1160
1161    // ── Terms & Conditions ──
1162    {
1163        let mut p = doc.add_paragraph("");
1164        p.add_run("Terms & Conditions")
1165            .bold(true)
1166            .size(14.0)
1167            .color("1A3C3C");
1168    }
1169    doc.add_numbered_list_item(
1170        "This quotation is valid for 30 calendar days from the date of issue.",
1171        0,
1172    );
1173    doc.add_numbered_list_item(
1174        "All prices are in USD and exclusive of applicable taxes unless stated.",
1175        0,
1176    );
1177    doc.add_numbered_list_item("Standard lead time is 4-6 weeks from PO receipt.", 0);
1178    doc.add_numbered_list_item(
1179        "Payment terms: Net 30 from invoice date. 2% discount for payment within 10 days.",
1180        0,
1181    );
1182    doc.add_numbered_list_item(
1183        "Warranty: 3-year manufacturer warranty on all hardware components.",
1184        0,
1185    );
1186    doc.add_numbered_list_item(
1187        "Returns subject to 15% restocking fee if initiated after 14 days.",
1188        0,
1189    );
1190
1191    doc.add_paragraph("");
1192    doc.add_paragraph("")
1193        .border_bottom(BorderStyle::Single, 4, "008B8B");
1194    doc.add_paragraph("");
1195    {
1196        let mut p = doc.add_paragraph("");
1197        p.add_run("Accepted by: ").bold(true);
1198        p.add_run("___________________________ Date: ___________");
1199    }
1200    {
1201        let mut p = doc.add_paragraph("");
1202        p.add_run("Print Name: ").bold(true);
1203        p.add_run("___________________________ Title: ___________");
1204    }
1205
1206    doc
1207}
1208
1209// =============================================================================
1210// 4. INVOICE — Crimson + charcoal scheme
1211// =============================================================================
1212fn generate_invoice(_samples_dir: &Path) -> Document {
1213    let mut doc = Document::new();
1214
1215    // Colors: Crimson #B22222, Charcoal #333333, Light #FAF0F0
1216    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1217    doc.set_margins(
1218        Length::inches(0.75),
1219        Length::inches(0.75),
1220        Length::inches(0.75),
1221        Length::inches(0.75),
1222    );
1223    doc.set_title("Invoice INV-2026-0392");
1224    doc.set_author("Walter White");
1225    doc.set_keywords("invoice, Tensorbee");
1226
1227    doc.set_footer("Tensorbee — Thank you for your business!");
1228
1229    // ── Company & Invoice Header ──
1230    {
1231        let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1232        // Company name left, INVOICE right
1233        {
1234            let mut cell = tbl.cell(0, 0).unwrap();
1235            let mut p = cell.add_paragraph("");
1236            p.add_run("TENSORBEE")
1237                .bold(true)
1238                .size(32.0)
1239                .color("B22222")
1240                .font("Helvetica");
1241        }
1242        {
1243            let mut cell = tbl.cell(0, 1).unwrap();
1244            let mut p = cell.add_paragraph("");
1245            p.add_run("INVOICE").bold(true).size(32.0).color("333333");
1246        }
1247        tbl.cell(1, 0)
1248            .unwrap()
1249            .set_text("123 Innovation Drive, Suite 400");
1250        tbl.cell(1, 1).unwrap().set_text("Invoice #: INV-2026-0392");
1251        tbl.cell(2, 0).unwrap().set_text("San Francisco, CA 94105");
1252        tbl.cell(2, 1).unwrap().set_text("Date: February 22, 2026");
1253        tbl.cell(3, 0).unwrap().set_text("walter@tensorbee.com");
1254        tbl.cell(3, 1).unwrap().set_text("Due Date: March 24, 2026");
1255    }
1256
1257    doc.add_paragraph("")
1258        .border_bottom(BorderStyle::Thick, 8, "B22222");
1259    doc.add_paragraph("");
1260
1261    // ── Bill To / Ship To ──
1262    {
1263        let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1264        tbl.cell(0, 0).unwrap().set_text("BILL TO");
1265        tbl.cell(0, 0).unwrap().shading("B22222");
1266        tbl.cell(0, 1).unwrap().set_text("SHIP TO");
1267        tbl.cell(0, 1).unwrap().shading("B22222");
1268        tbl.cell(1, 0).unwrap().set_text("Meridian Dynamics LLC");
1269        tbl.cell(1, 1).unwrap().set_text("Meridian Dynamics LLC");
1270        tbl.cell(2, 0).unwrap().set_text("456 Enterprise Blvd");
1271        tbl.cell(2, 1).unwrap().set_text("Attn: Server Room B");
1272        tbl.cell(3, 0).unwrap().set_text("Austin, TX 78701");
1273        tbl.cell(3, 1)
1274            .unwrap()
1275            .set_text("456 Enterprise Blvd, Austin, TX 78701");
1276    }
1277
1278    doc.add_paragraph("");
1279
1280    // ── Line Items ──
1281    {
1282        let mut tbl = doc
1283            .add_table(8, 5)
1284            .borders(BorderStyle::Single, 2, "B22222")
1285            .width_pct(100.0);
1286        let hdrs = ["Description", "Qty", "Unit Price", "Tax", "Amount"];
1287        for (c, h) in hdrs.iter().enumerate() {
1288            tbl.cell(0, c).unwrap().set_text(h);
1289            tbl.cell(0, c).unwrap().shading("B22222");
1290        }
1291        let items = [
1292            (
1293                "ML Infrastructure Setup — Phase 1",
1294                "1",
1295                "$45,000.00",
1296                "$3,881.25",
1297                "$48,881.25",
1298            ),
1299            (
1300                "Data Pipeline Development (160 hrs)",
1301                "160",
1302                "$225.00",
1303                "$3,105.00",
1304                "$39,105.00",
1305            ),
1306            (
1307                "GPU Cluster Configuration",
1308                "1",
1309                "$12,000.00",
1310                "$1,035.00",
1311                "$13,035.00",
1312            ),
1313            (
1314                "API Gateway Implementation",
1315                "1",
1316                "$18,500.00",
1317                "$1,595.63",
1318                "$20,095.63",
1319            ),
1320            (
1321                "Load Testing & QA (80 hrs)",
1322                "80",
1323                "$195.00",
1324                "$1,345.50",
1325                "$16,945.50",
1326            ),
1327            (
1328                "Documentation & Training",
1329                "1",
1330                "$8,000.00",
1331                "$690.00",
1332                "$8,690.00",
1333            ),
1334            (
1335                "Project Management (3 months)",
1336                "3",
1337                "$6,500.00",
1338                "$1,679.63",
1339                "$21,179.63",
1340            ),
1341        ];
1342        for (i, (desc, qty, unit, tax, amt)) in items.iter().enumerate() {
1343            let r = i + 1;
1344            tbl.cell(r, 0).unwrap().set_text(desc);
1345            tbl.cell(r, 1).unwrap().set_text(qty);
1346            tbl.cell(r, 2).unwrap().set_text(unit);
1347            tbl.cell(r, 3).unwrap().set_text(tax);
1348            tbl.cell(r, 4).unwrap().set_text(amt);
1349            if i % 2 == 0 {
1350                for c in 0..5 {
1351                    tbl.cell(r, c).unwrap().shading("FAF0F0");
1352                }
1353            }
1354        }
1355    }
1356
1357    doc.add_paragraph("");
1358
1359    // ── Totals ──
1360    {
1361        let mut tbl = doc
1362            .add_table(4, 2)
1363            .borders(BorderStyle::Single, 2, "B22222")
1364            .width(Length::inches(3.0))
1365            .alignment(Alignment::Right);
1366        tbl.cell(0, 0).unwrap().set_text("Subtotal");
1367        tbl.cell(0, 1).unwrap().set_text("$154,600.00");
1368        tbl.cell(1, 0).unwrap().set_text("Tax (8.625%)");
1369        tbl.cell(1, 1).unwrap().set_text("$13,334.25");
1370        tbl.cell(2, 0).unwrap().set_text("Discount (5%)");
1371        tbl.cell(2, 1).unwrap().set_text("-$7,730.00");
1372        tbl.cell(3, 0).unwrap().set_text("AMOUNT DUE");
1373        tbl.cell(3, 0).unwrap().shading("B22222");
1374        tbl.cell(3, 1).unwrap().set_text("$160,204.25");
1375        tbl.cell(3, 1).unwrap().shading("B22222");
1376    }
1377
1378    doc.add_paragraph("");
1379    doc.add_paragraph("");
1380
1381    // ── Payment Details ──
1382    {
1383        let mut p = doc.add_paragraph("");
1384        p.add_run("Payment Details")
1385            .bold(true)
1386            .size(14.0)
1387            .color("333333");
1388    }
1389    doc.add_paragraph("Bank: Silicon Valley Bank")
1390        .indent_left(Length::inches(0.3));
1391    doc.add_paragraph("Account: Tensorbee Inc. — 0847-2953-1120")
1392        .indent_left(Length::inches(0.3));
1393    doc.add_paragraph("Routing: 121140399")
1394        .indent_left(Length::inches(0.3));
1395    doc.add_paragraph("Swift: SVBKUS6S")
1396        .indent_left(Length::inches(0.3));
1397
1398    doc.add_paragraph("");
1399
1400    doc.add_paragraph("Please include invoice number INV-2026-0392 in the payment reference.")
1401        .shading("FAF0F0")
1402        .border_all(BorderStyle::Single, 2, "B22222");
1403
1404    doc
1405}
1406
1407// =============================================================================
1408// 5. REPORT — Forest green + earth tones, with images & hierarchical sections
1409// =============================================================================
1410fn generate_report(_samples_dir: &Path) -> Document {
1411    let mut doc = Document::new();
1412
1413    // Colors: Forest #2D5016, Sage #6B8E23, Earth #8B7355, Cream #FFFAF0
1414    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1415    doc.set_margins(
1416        Length::inches(1.0),
1417        Length::inches(1.0),
1418        Length::inches(1.0),
1419        Length::inches(1.0),
1420    );
1421    doc.set_title("Q4 2025 Environmental Impact Report");
1422    doc.set_author("Walter White");
1423    doc.set_subject("Quarterly Environmental Report");
1424    doc.set_keywords("environment, sustainability, report, Tensorbee");
1425
1426    doc.set_different_first_page(true);
1427    doc.set_first_page_header("");
1428    doc.set_header("Tensorbee — Q4 2025 Environmental Impact Report");
1429    doc.set_footer("CONFIDENTIAL — Page");
1430
1431    // ── Cover ──
1432    let cover_bg = create_sample_png(612, 792, [20, 50, 15]);
1433    doc.add_background_image(&cover_bg, "report_cover.png");
1434
1435    for _ in 0..5 {
1436        doc.add_paragraph("");
1437    }
1438    {
1439        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1440        p.add_run("Q4 2025")
1441            .bold(true)
1442            .size(48.0)
1443            .color("FFFFFF")
1444            .font("Georgia");
1445    }
1446    {
1447        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1448        p.add_run("Environmental Impact Report")
1449            .size(24.0)
1450            .color("90EE90")
1451            .font("Georgia")
1452            .italic(true);
1453    }
1454    doc.add_paragraph("");
1455    {
1456        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1457        p.add_run("Tensorbee — Sustainability Division")
1458            .size(14.0)
1459            .color("C0C0C0");
1460    }
1461    {
1462        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1463        p.add_run("Prepared by Walter White, Chief Sustainability Officer")
1464            .size(11.0)
1465            .color("AAAAAA");
1466    }
1467
1468    // ── TOC ──
1469    doc.add_paragraph("").page_break_before(true);
1470    doc.insert_toc(doc.content_count(), 3);
1471
1472    // ── Section 1: Executive Overview ──
1473    doc.add_paragraph("").page_break_before(true);
1474    doc.add_paragraph("Executive Overview").style("Heading1");
1475    doc.add_paragraph(
1476        "This report presents Tensorbee's environmental performance for Q4 2025. Our \
1477         sustainability initiatives have yielded a 34% reduction in carbon emissions compared \
1478         to Q4 2024, exceeding our target of 25%. Key achievements include the transition to \
1479         100% renewable energy in our primary data centers and the launch of our carbon offset \
1480         marketplace.",
1481    )
1482    .first_line_indent(Length::inches(0.3));
1483
1484    // Image: performance chart
1485    let chart_img = create_chart_png(400, 200);
1486    doc.add_paragraph("");
1487    doc.add_picture(
1488        &chart_img,
1489        "performance_chart.png",
1490        Length::inches(5.0),
1491        Length::inches(2.5),
1492    )
1493    .alignment(Alignment::Center);
1494    {
1495        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1496        p.add_run("Figure 1: Quarterly Carbon Emissions (tonnes CO2e)")
1497            .italic(true)
1498            .size(9.0)
1499            .color("666666");
1500    }
1501
1502    // ── Section 2: Energy Consumption ──
1503    doc.add_paragraph("").page_break_before(true);
1504    doc.add_paragraph("Energy Consumption").style("Heading1");
1505
1506    doc.add_paragraph("Data Center Operations")
1507        .style("Heading2");
1508    doc.add_paragraph(
1509        "Our three primary data centers consumed a combined 4.2 GWh in Q4 2025, \
1510         a 12% reduction from Q3 2025 driven by improved cooling efficiency and \
1511         server consolidation.",
1512    );
1513
1514    // Data table
1515    {
1516        let mut tbl = doc
1517            .add_table(5, 4)
1518            .borders(BorderStyle::Single, 4, "2D5016")
1519            .width_pct(100.0);
1520        let hdrs = ["Data Center", "Capacity (MW)", "Usage (GWh)", "PUE"];
1521        for (c, h) in hdrs.iter().enumerate() {
1522            tbl.cell(0, c).unwrap().set_text(h);
1523            tbl.cell(0, c).unwrap().shading("2D5016");
1524        }
1525        let rows = [
1526            ("San Francisco (Primary)", "3.2", "1.8", "1.12"),
1527            ("Dublin (EU)", "2.1", "1.2", "1.18"),
1528            ("Singapore (APAC)", "1.8", "1.2", "1.24"),
1529            ("TOTAL", "7.1", "4.2", "1.17 avg"),
1530        ];
1531        for (i, (dc, cap, use_, pue)) in rows.iter().enumerate() {
1532            tbl.cell(i + 1, 0).unwrap().set_text(dc);
1533            tbl.cell(i + 1, 1).unwrap().set_text(cap);
1534            tbl.cell(i + 1, 2).unwrap().set_text(use_);
1535            tbl.cell(i + 1, 3).unwrap().set_text(pue);
1536            if i == 3 {
1537                for c in 0..4 {
1538                    tbl.cell(i + 1, c).unwrap().shading("E2EFDA");
1539                }
1540            }
1541        }
1542    }
1543
1544    doc.add_paragraph("");
1545
1546    doc.add_paragraph("Renewable Energy Mix").style("Heading2");
1547    doc.add_paragraph("Breakdown of energy sources across all facilities:");
1548
1549    doc.add_bullet_list_item("Solar PV: 42% (1.76 GWh)", 0);
1550    doc.add_bullet_list_item("Wind Power Purchase Agreements: 38% (1.60 GWh)", 0);
1551    doc.add_bullet_list_item("Hydroelectric: 12% (0.50 GWh)", 0);
1552    doc.add_bullet_list_item("Grid (non-renewable): 8% (0.34 GWh)", 0);
1553
1554    // ── Section 3: Water & Waste ──
1555    doc.add_paragraph("Water & Waste Management")
1556        .style("Heading1");
1557
1558    doc.add_paragraph("Water Usage").style("Heading2");
1559    doc.add_paragraph(
1560        "Total water consumption was 12.4 million gallons, a 15% reduction from Q3. \
1561         Our closed-loop cooling systems now recycle 78% of water used in cooling operations.",
1562    );
1563
1564    doc.add_paragraph("Waste Reduction").style("Heading2");
1565    doc.add_paragraph("E-waste management results for Q4:")
1566        .keep_with_next(true);
1567    {
1568        let mut tbl = doc
1569            .add_table(4, 3)
1570            .borders(BorderStyle::Single, 2, "6B8E23")
1571            .width_pct(80.0);
1572        tbl.cell(0, 0).unwrap().set_text("Category");
1573        tbl.cell(0, 0).unwrap().shading("6B8E23");
1574        tbl.cell(0, 1).unwrap().set_text("Weight (kg)");
1575        tbl.cell(0, 1).unwrap().shading("6B8E23");
1576        tbl.cell(0, 2).unwrap().set_text("Recycled %");
1577        tbl.cell(0, 2).unwrap().shading("6B8E23");
1578        let rows = [
1579            ("Server Hardware", "2,450", "94%"),
1580            ("Networking Equipment", "820", "91%"),
1581            ("Storage Media", "340", "99%"),
1582        ];
1583        for (i, (cat, wt, pct)) in rows.iter().enumerate() {
1584            tbl.cell(i + 1, 0).unwrap().set_text(cat);
1585            tbl.cell(i + 1, 1).unwrap().set_text(wt);
1586            tbl.cell(i + 1, 2).unwrap().set_text(pct);
1587        }
1588    }
1589
1590    // ── Section 4: Initiatives ──
1591    doc.add_paragraph("").page_break_before(true);
1592    doc.add_paragraph("Q1 2026 Initiatives").style("Heading1");
1593
1594    doc.add_paragraph("Planned Programs").style("Heading2");
1595    doc.add_numbered_list_item(
1596        "Deploy on-site battery storage at SF data center (2 MWh capacity)",
1597        0,
1598    );
1599    doc.add_numbered_list_item("Pilot immersion cooling in Dublin facility", 0);
1600    doc.add_numbered_list_item("Launch employee commute carbon offset program", 0);
1601    doc.add_numbered_list_item("Achieve ISO 14001 certification for Singapore facility", 0);
1602
1603    doc.add_paragraph("Investment Targets").style("Heading2");
1604    doc.add_paragraph("Sustainability CapEx allocation for FY2026:")
1605        .keep_with_next(true);
1606    {
1607        let mut tbl = doc
1608            .add_table(5, 2)
1609            .borders(BorderStyle::Single, 4, "2D5016");
1610        tbl.cell(0, 0).unwrap().set_text("Initiative");
1611        tbl.cell(0, 0).unwrap().shading("2D5016");
1612        tbl.cell(0, 1).unwrap().set_text("Budget");
1613        tbl.cell(0, 1).unwrap().shading("2D5016");
1614        let rows = [
1615            ("Battery Storage", "$1.2M"),
1616            ("Immersion Cooling Pilot", "$800K"),
1617            ("Solar Panel Expansion", "$2.1M"),
1618            ("Carbon Credits", "$500K"),
1619        ];
1620        for (i, (init, budget)) in rows.iter().enumerate() {
1621            tbl.cell(i + 1, 0).unwrap().set_text(init);
1622            tbl.cell(i + 1, 1).unwrap().set_text(budget);
1623            if i % 2 == 0 {
1624                tbl.cell(i + 1, 0).unwrap().shading("FFFAF0");
1625                tbl.cell(i + 1, 1).unwrap().shading("FFFAF0");
1626            }
1627        }
1628    }
1629
1630    // Image: sustainability roadmap
1631    let roadmap_img = create_sample_png(500, 100, [40, 80, 30]);
1632    doc.add_paragraph("");
1633    doc.add_picture(
1634        &roadmap_img,
1635        "roadmap.png",
1636        Length::inches(6.0),
1637        Length::inches(1.2),
1638    )
1639    .alignment(Alignment::Center);
1640    {
1641        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1642        p.add_run("Figure 2: 2026 Sustainability Roadmap")
1643            .italic(true)
1644            .size(9.0)
1645            .color("666666");
1646    }
1647
1648    doc.add_paragraph("");
1649    doc.add_paragraph(
1650        "For questions about this report, contact Walter White at sustainability@tensorbee.com.",
1651    )
1652    .shading("FFFAF0")
1653    .border_all(BorderStyle::Single, 2, "2D5016");
1654
1655    doc
1656}
Source

pub fn insert_paragraph(&mut self, index: usize, text: &str) -> Paragraph<'_>

Insert a paragraph at the given body index.

Returns a mutable Paragraph for further configuration.

§Panics

Panics if index > content_count(). (Unlike Self::insert_document and Self::insert_toc, which clamp an out-of-range index to the end.)

Examples found in repository?
examples/template_replace.rs (line 198)
162fn fill_template(template_path: &Path, output_path: &Path) {
163    let mut doc = Document::open(template_path).unwrap();
164
165    // ── Batch replacement ──
166    let mut replacements = HashMap::new();
167    replacements.insert("{{company_name}}", "Riverside Medical Center");
168    replacements.insert("{{project_name}}", "Network Security Upgrade");
169    replacements.insert("{{contact_name}}", "Dr. Sarah Chen");
170    replacements.insert("{{contact_email}}", "s.chen@riverside.org");
171    replacements.insert("{{start_date}}", "March 1, 2026");
172    replacements.insert("{{duration}}", "12 weeks");
173    replacements.insert("{{budget}}", "$185,000");
174    replacements.insert("{{status}}", "Pending Approval");
175    replacements.insert("{{author_name}}", "James Wilson");
176    replacements.insert("{{date}}", "February 22, 2026");
177
178    // Team members
179    replacements.insert("{{member1_name}}", "James Wilson");
180    replacements.insert("{{member1_role}}", "Project Lead");
181    replacements.insert("{{member1_email}}", "j.wilson@provider.com");
182    replacements.insert("{{member2_name}}", "Maria Garcia");
183    replacements.insert("{{member2_role}}", "Security Architect");
184    replacements.insert("{{member2_email}}", "m.garcia@provider.com");
185    replacements.insert("{{member3_name}}", "David Park");
186    replacements.insert("{{member3_role}}", "Network Engineer");
187    replacements.insert("{{member3_email}}", "d.park@provider.com");
188
189    let count = doc.replace_all(&replacements);
190    println!("  Replaced {} placeholders", count);
191
192    // ── Insert deliverables at the insertion point ──
193    if let Some(idx) = doc.find_content_index("INSERTION_POINT") {
194        // Remove the placeholder paragraph
195        doc.remove_content(idx);
196
197        // Insert deliverables list
198        doc.insert_paragraph(idx, "The following deliverables are included:");
199
200        // Insert a deliverables table
201        let mut tbl = doc.insert_table(idx + 1, 5, 3);
202        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
203
204        for col in 0..3 {
205            tbl.cell(0, col).unwrap().shading("E2EFDA");
206        }
207        tbl.cell(0, 0).unwrap().set_text("Phase");
208        tbl.cell(0, 1).unwrap().set_text("Description");
209        tbl.cell(0, 2).unwrap().set_text("Timeline");
210
211        tbl.cell(1, 0).unwrap().set_text("1. Discovery");
212        tbl.cell(1, 1)
213            .unwrap()
214            .set_text("Network assessment and asset inventory");
215        tbl.cell(1, 2).unwrap().set_text("Weeks 1-3");
216
217        tbl.cell(2, 0).unwrap().set_text("2. Design");
218        tbl.cell(2, 1)
219            .unwrap()
220            .set_text("Security architecture and policy design");
221        tbl.cell(2, 2).unwrap().set_text("Weeks 4-6");
222
223        tbl.cell(3, 0).unwrap().set_text("3. Implementation");
224        tbl.cell(3, 1)
225            .unwrap()
226            .set_text("Deploy monitoring and access controls");
227        tbl.cell(3, 2).unwrap().set_text("Weeks 7-10");
228
229        tbl.cell(4, 0).unwrap().set_text("4. Validation");
230        tbl.cell(4, 1)
231            .unwrap()
232            .set_text("Testing, training, and handover");
233        tbl.cell(4, 2).unwrap().set_text("Weeks 11-12");
234
235        println!("  Inserted deliverables table at position {}", idx);
236    }
237
238    // ── Update metadata ──
239    doc.set_title("Riverside Medical Center — Network Security Upgrade Proposal");
240    doc.set_author("James Wilson");
241    doc.set_subject("Project Proposal");
242    doc.set_keywords("security, network, medical, proposal");
243
244    doc.save(output_path).unwrap();
245}
More examples
Hide additional examples
examples/generate_all_samples.rs (lines 556-559)
97fn generate_feature_showcase(_samples_dir: &Path) -> Document {
98    let mut doc = Document::new();
99
100    // ── Page Setup & Metadata ──
101    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
102    doc.set_margins(
103        Length::inches(1.0),
104        Length::inches(1.0),
105        Length::inches(1.0),
106        Length::inches(1.0),
107    );
108    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
109    doc.set_gutter(Length::twips(0));
110    doc.set_title("rdocx Feature Showcase");
111    doc.set_author("rdocx Sample Generator");
112    doc.set_subject("Comprehensive feature demonstration");
113    doc.set_keywords("rdocx, docx, rust, sample, showcase");
114
115    // Headers & Footers with different first page
116    doc.set_different_first_page(true);
117    doc.set_first_page_header("rdocx");
118    doc.set_first_page_footer("Feature Showcase — Cover Page");
119    doc.set_header("rdocx Feature Showcase");
120    doc.set_footer("Generated by rdocx");
121
122    // ── COVER PAGE ──
123    let bg = create_sample_png(612, 792, [20, 45, 90]);
124    doc.add_background_image(&bg, "cover_bg.png");
125
126    for _ in 0..3 {
127        doc.add_paragraph("");
128    }
129    {
130        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
131        p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
132    }
133    {
134        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
135        p.add_run("Complete Feature Showcase")
136            .size(28.0)
137            .color("FFFFFF")
138            .italic(true);
139    }
140    doc.add_paragraph("");
141    {
142        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
143        p.add_run("Every feature of the rdocx Rust library")
144            .size(13.0)
145            .color("B0C4DE");
146    }
147    {
148        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
149        p.add_run("demonstrated in a single document.")
150            .size(13.0)
151            .color("B0C4DE");
152    }
153
154    // ── TABLE OF CONTENTS ──
155    doc.add_paragraph("").page_break_before(true);
156    doc.insert_toc(doc.content_count(), 3);
157
158    // ── SECTION 1: TEXT FORMATTING ──
159    doc.add_paragraph("").page_break_before(true);
160    doc.add_paragraph("1. Text Formatting").style("Heading1");
161
162    // Paragraph Alignment
163    doc.add_paragraph("1.1 Paragraph Alignment")
164        .style("Heading2");
165    doc.add_paragraph("Left-aligned paragraph (default).")
166        .alignment(Alignment::Left);
167    doc.add_paragraph("Center-aligned paragraph.")
168        .alignment(Alignment::Center);
169    doc.add_paragraph("Right-aligned paragraph.")
170        .alignment(Alignment::Right);
171    doc.add_paragraph(
172        "Justified paragraph — this text is long enough to demonstrate how justified alignment \
173         distributes extra space across word gaps so lines fill the full width of the text area.",
174    )
175    .alignment(Alignment::Justify);
176
177    doc.add_paragraph("");
178
179    // Run Formatting
180    doc.add_paragraph("1.2 Run Formatting").style("Heading2");
181    {
182        let mut p = doc.add_paragraph("");
183        p.add_run("Bold").bold(true);
184        p.add_run(" | ");
185        p.add_run("Italic").italic(true);
186        p.add_run(" | ");
187        p.add_run("Bold Italic").bold(true).italic(true);
188        p.add_run(" | ");
189        p.add_run("Underline").underline(true);
190        p.add_run(" | ");
191        p.add_run("Strikethrough").strike(true);
192        p.add_run(" | ");
193        p.add_run("Double Strike").double_strike(true);
194    }
195    {
196        let mut p = doc.add_paragraph("");
197        p.add_run("Red").color("FF0000");
198        p.add_run(" | ");
199        p.add_run("Blue").color("0000FF");
200        p.add_run(" | ");
201        p.add_run("Green").color("00AA00");
202        p.add_run(" | ");
203        p.add_run("Highlighted").highlight("FFFF00");
204    }
205    {
206        let mut p = doc.add_paragraph("");
207        p.add_run("8pt").size(8.0);
208        p.add_run(" | ");
209        p.add_run("11pt").size(11.0);
210        p.add_run(" | ");
211        p.add_run("16pt").size(16.0);
212        p.add_run(" | ");
213        p.add_run("24pt").size(24.0);
214    }
215    {
216        let mut p = doc.add_paragraph("");
217        p.add_run("Arial").font("Arial");
218        p.add_run(" | ");
219        p.add_run("Times New Roman").font("Times New Roman");
220        p.add_run(" | ");
221        p.add_run("Courier New").font("Courier New");
222    }
223    {
224        let mut p = doc.add_paragraph("");
225        p.add_run("H");
226        p.add_run("2").subscript();
227        p.add_run("O (subscript) | E=mc");
228        p.add_run("2").superscript();
229        p.add_run(" (superscript)");
230    }
231    {
232        let mut p = doc.add_paragraph("");
233        p.add_run("ALL CAPS").all_caps(true);
234        p.add_run(" | ");
235        p.add_run("Small Caps").small_caps(true);
236        p.add_run(" | ");
237        p.add_run("Expanded").character_spacing(Length::twips(40));
238        p.add_run(" | ");
239        p.add_run("Hidden text (not visible)").hidden(true);
240    }
241
242    doc.add_paragraph("");
243
244    // Underline Styles
245    doc.add_paragraph("1.3 Underline Styles").style("Heading2");
246    {
247        let mut p = doc.add_paragraph("");
248        p.add_run("Single ")
249            .underline_style(rdocx::UnderlineStyle::Single);
250        p.add_run("Double ")
251            .underline_style(rdocx::UnderlineStyle::Double);
252        p.add_run("Thick ")
253            .underline_style(rdocx::UnderlineStyle::Thick);
254        p.add_run("Dotted ")
255            .underline_style(rdocx::UnderlineStyle::Dotted);
256        p.add_run("Dash ")
257            .underline_style(rdocx::UnderlineStyle::Dash);
258        p.add_run("Wave ")
259            .underline_style(rdocx::UnderlineStyle::Wave);
260    }
261
262    // ── SECTION 2: PARAGRAPH FORMATTING ──
263    doc.add_paragraph("").page_break_before(true);
264    doc.add_paragraph("2. Paragraph Formatting")
265        .style("Heading1");
266
267    doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
268    doc.add_paragraph("Paragraph with light green shading")
269        .shading("E2EFDA");
270    doc.add_paragraph("Paragraph with bottom border")
271        .border_bottom(BorderStyle::Single, 6, "2E75B6");
272    doc.add_paragraph("Paragraph with all borders (red)")
273        .border_all(BorderStyle::Single, 4, "FF0000");
274
275    doc.add_paragraph("2.2 Indentation").style("Heading2");
276    doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
277        .indent_left(Length::inches(1.0))
278        .hanging_indent(Length::inches(0.5));
279    doc.add_paragraph("0.5-inch first-line indent on this paragraph")
280        .first_line_indent(Length::inches(0.5));
281    doc.add_paragraph("Both left and right indent (0.75 inches each)")
282        .indent_left(Length::inches(0.75))
283        .indent_right(Length::inches(0.75));
284
285    doc.add_paragraph("2.3 Spacing & Line Height")
286        .style("Heading2");
287    doc.add_paragraph("Extra space before (24pt) and after (12pt)")
288        .space_before(Length::pt(24.0))
289        .space_after(Length::pt(12.0));
290    doc.add_paragraph(
291        "Double line spacing paragraph. This text should have extra vertical space between \
292         lines to demonstrate line_spacing_multiple(2.0).",
293    )
294    .line_spacing_multiple(2.0);
295    doc.add_paragraph("Exact 20pt line spacing (fixed height).")
296        .line_spacing(20.0);
297
298    doc.add_paragraph("2.4 Pagination Controls")
299        .style("Heading2");
300    doc.add_paragraph("keep_with_next — this paragraph stays with the next")
301        .keep_with_next(true);
302    doc.add_paragraph("(This paragraph was kept with the one above.)");
303    doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
304        .keep_together(true);
305    doc.add_paragraph("widow_control — prevents widow/orphan lines")
306        .widow_control(true);
307
308    // ── SECTION 3: LISTS ──
309    doc.add_paragraph("").page_break_before(true);
310    doc.add_paragraph("3. Lists").style("Heading1");
311
312    doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
313    doc.add_bullet_list_item("First item", 0);
314    doc.add_bullet_list_item("Second item", 0);
315    doc.add_bullet_list_item("Nested level 1", 1);
316    doc.add_bullet_list_item("Nested level 2", 2);
317    doc.add_bullet_list_item("Back to level 1", 1);
318    doc.add_bullet_list_item("Third item", 0);
319
320    doc.add_paragraph("");
321
322    doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
323    doc.add_numbered_list_item("First numbered item", 0);
324    doc.add_numbered_list_item("Second numbered item", 0);
325    doc.add_numbered_list_item("Sub-item A", 1);
326    doc.add_numbered_list_item("Sub-item B", 1);
327    doc.add_numbered_list_item("Third numbered item", 0);
328
329    // ── SECTION 4: TAB STOPS ──
330    doc.add_paragraph("");
331    doc.add_paragraph("4. Tab Stops").style("Heading1");
332
333    doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
334    doc.add_paragraph("Left\tCenter\tRight\tDecimal")
335        .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
336        .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
337        .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
338        .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
339
340    doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
341    doc.add_paragraph("Item\t\tPrice")
342        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
343        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
344        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
345    doc.add_paragraph("Widget\t\t$19.99")
346        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
347        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
348        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
349    doc.add_paragraph("Gadget\t\t$249.50")
350        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
351        .add_tab_stop_with_leader(
352            TabAlignment::Right,
353            Length::inches(4.0),
354            TabLeader::Underscore,
355        )
356        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
357
358    // ── SECTION 5: TABLES ──
359    doc.add_paragraph("").page_break_before(true);
360    doc.add_paragraph("5. Tables").style("Heading1");
361
362    doc.add_paragraph("5.1 Basic Table").style("Heading2");
363    {
364        let mut tbl = doc
365            .add_table(4, 3)
366            .borders(BorderStyle::Single, 4, "000000");
367        for col in 0..3 {
368            tbl.cell(0, col).unwrap().shading("2E75B6");
369        }
370        tbl.cell(0, 0).unwrap().set_text("Name");
371        tbl.cell(0, 1).unwrap().set_text("Role");
372        tbl.cell(0, 2).unwrap().set_text("Location");
373        tbl.cell(1, 0).unwrap().set_text("Walter White");
374        tbl.cell(1, 1).unwrap().set_text("CEO");
375        tbl.cell(1, 2).unwrap().set_text("Albuquerque");
376        tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
377        tbl.cell(2, 1).unwrap().set_text("CTO");
378        tbl.cell(2, 2).unwrap().set_text("Remote");
379        tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
380        tbl.cell(3, 1).unwrap().set_text("Security");
381        tbl.cell(3, 2).unwrap().set_text("Washington");
382    }
383
384    doc.add_paragraph("");
385
386    doc.add_paragraph("5.2 Column Span & Cell Shading")
387        .style("Heading2");
388    {
389        let mut tbl = doc
390            .add_table(3, 4)
391            .borders(BorderStyle::Single, 4, "000000")
392            .width_pct(100.0);
393        tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
394        tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
395        tbl.cell(1, 0).unwrap().set_text("Region");
396        tbl.cell(1, 0).unwrap().shading("D6E4F0");
397        tbl.cell(1, 1).unwrap().set_text("Q1");
398        tbl.cell(1, 1).unwrap().shading("D6E4F0");
399        tbl.cell(1, 2).unwrap().set_text("Q2");
400        tbl.cell(1, 2).unwrap().shading("D6E4F0");
401        tbl.cell(1, 3).unwrap().set_text("Total");
402        tbl.cell(1, 3).unwrap().shading("D6E4F0");
403        tbl.cell(2, 0).unwrap().set_text("Americas");
404        tbl.cell(2, 1).unwrap().set_text("$2.4M");
405        tbl.cell(2, 2).unwrap().set_text("$2.7M");
406        tbl.cell(2, 3).unwrap().set_text("$5.1M");
407    }
408
409    doc.add_paragraph("");
410
411    doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
412    {
413        let mut tbl = doc
414            .add_table(4, 3)
415            .borders(BorderStyle::Single, 4, "000000");
416        tbl.cell(0, 0).unwrap().set_text("Category");
417        tbl.cell(0, 0).unwrap().shading("E2EFDA");
418        tbl.cell(0, 1).unwrap().set_text("Item");
419        tbl.cell(0, 1).unwrap().shading("E2EFDA");
420        tbl.cell(0, 2).unwrap().set_text("Price");
421        tbl.cell(0, 2).unwrap().shading("E2EFDA");
422        tbl.cell(1, 0).unwrap().set_text("Hardware");
423        tbl.cell(1, 0).unwrap().v_merge_restart();
424        tbl.cell(1, 1).unwrap().set_text("Laptop");
425        tbl.cell(1, 2).unwrap().set_text("$1,200");
426        tbl.cell(2, 0).unwrap().v_merge_continue();
427        tbl.cell(2, 1).unwrap().set_text("Monitor");
428        tbl.cell(2, 2).unwrap().set_text("$450");
429        tbl.cell(3, 0).unwrap().set_text("Software");
430        tbl.cell(3, 1).unwrap().set_text("IDE License");
431        tbl.cell(3, 2).unwrap().set_text("$200/yr");
432    }
433
434    doc.add_paragraph("");
435
436    doc.add_paragraph("5.4 Nested Table").style("Heading2");
437    {
438        let mut tbl = doc
439            .add_table(2, 2)
440            .borders(BorderStyle::Single, 6, "2E75B6");
441        tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
442        tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
443        tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
444        {
445            let mut cell = tbl.cell(1, 1).unwrap();
446            cell.set_text("Nested table below:");
447            let mut nested = cell
448                .add_table(2, 2)
449                .borders(BorderStyle::Single, 2, "FF6600");
450            nested.cell(0, 0).unwrap().set_text("A");
451            nested.cell(0, 1).unwrap().set_text("B");
452            nested.cell(1, 0).unwrap().set_text("C");
453            nested.cell(1, 1).unwrap().set_text("D");
454        }
455    }
456
457    doc.add_paragraph("");
458
459    doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
460        .style("Heading2");
461    {
462        let mut tbl = doc
463            .add_table(2, 3)
464            .borders(BorderStyle::Single, 4, "666666");
465        tbl.row(0).unwrap().height(Length::pt(50.0)).header();
466        tbl.cell(0, 0).unwrap().set_text("Top");
467        tbl.cell(0, 0)
468            .unwrap()
469            .vertical_alignment(VerticalAlignment::Top)
470            .shading("FFF2CC");
471        tbl.cell(0, 1).unwrap().set_text("Center");
472        tbl.cell(0, 1)
473            .unwrap()
474            .vertical_alignment(VerticalAlignment::Center)
475            .shading("D9E2F3");
476        tbl.cell(0, 2).unwrap().set_text("Bottom");
477        tbl.cell(0, 2)
478            .unwrap()
479            .vertical_alignment(VerticalAlignment::Bottom)
480            .shading("E2EFDA");
481        tbl.row(1).unwrap().cant_split();
482        tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
483        tbl.cell(1, 0).unwrap().no_wrap();
484        tbl.cell(1, 1).unwrap().set_text("Fixed width");
485        tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
486        tbl.cell(1, 2).unwrap().set_text("Normal");
487    }
488
489    // ── SECTION 6: IMAGES ──
490    doc.add_paragraph("").page_break_before(true);
491    doc.add_paragraph("6. Images").style("Heading1");
492
493    doc.add_paragraph("6.1 Inline Image").style("Heading2");
494    doc.add_paragraph("A blue gradient image below:");
495    let img = create_sample_png(200, 50, [0, 80, 200]);
496    doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
497
498    doc.add_paragraph("");
499    doc.add_paragraph("6.2 Header Image").style("Heading2");
500    let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
501    doc.set_header_image(
502        &hdr_img,
503        "header_logo.png",
504        Length::inches(2.0),
505        Length::inches(0.2),
506    );
507    doc.add_paragraph("The header now contains an inline image (check the top of this page).");
508
509    doc.add_paragraph("");
510    doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
511
512    // ── SECTION 7: CONTENT MANIPULATION ──
513    doc.add_paragraph("").page_break_before(true);
514    doc.add_paragraph("7. Content Manipulation")
515        .style("Heading1");
516
517    doc.add_paragraph("7.1 Placeholder Replacement")
518        .style("Heading2");
519    doc.add_paragraph("Customer: {{customer}}");
520    doc.add_paragraph("Date: {{date}}");
521    doc.add_paragraph("Reference: {{ref_number}}");
522    {
523        let mut tbl = doc
524            .add_table(2, 2)
525            .borders(BorderStyle::Single, 4, "000000");
526        tbl.cell(0, 0).unwrap().set_text("Project");
527        tbl.cell(0, 0).unwrap().shading("D6E4F0");
528        tbl.cell(0, 1).unwrap().set_text("{{project}}");
529        tbl.cell(1, 0).unwrap().set_text("Status");
530        tbl.cell(1, 0).unwrap().shading("D6E4F0");
531        tbl.cell(1, 1).unwrap().set_text("{{status}}");
532    }
533
534    let mut replacements = HashMap::new();
535    replacements.insert("{{customer}}", "Tensorbee Inc.");
536    replacements.insert("{{date}}", "February 22, 2026");
537    replacements.insert("{{ref_number}}", "TB-2026-001");
538    replacements.insert("{{project}}", "Infrastructure Upgrade");
539    replacements.insert("{{status}}", "In Progress");
540    let n = doc.replace_all(&replacements);
541    doc.add_paragraph(&format!("({n} placeholders replaced above)"));
542
543    doc.add_paragraph("");
544
545    doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
546    doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
547    let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
548    doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
549
550    doc.add_paragraph("");
551
552    doc.add_paragraph("7.3 Content Insertion").style("Heading2");
553    doc.add_paragraph("Section A: First content.");
554    doc.add_paragraph("Section C: Third content.");
555    if let Some(idx) = doc.find_content_index("Section C") {
556        doc.insert_paragraph(
557            idx,
558            "Section B: Inserted between A and C via find_content_index().",
559        );
560    }
561
562    // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
563    doc.add_paragraph("").section_break(SectionBreak::NextPage);
564    doc.add_paragraph("8. Section Breaks & Orientation")
565        .style("Heading1");
566    doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
567
568    {
569        let mut tbl = doc
570            .add_table(3, 7)
571            .borders(BorderStyle::Single, 4, "2E75B6");
572        let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
573        for (c, h) in headers.iter().enumerate() {
574            tbl.cell(0, c).unwrap().set_text(h);
575            tbl.cell(0, c).unwrap().shading("2E75B6");
576        }
577        let data = [
578            [
579                "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
580            ],
581            [
582                "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
583            ],
584        ];
585        for (r, row) in data.iter().enumerate() {
586            for (c, v) in row.iter().enumerate() {
587                tbl.cell(r + 1, c).unwrap().set_text(v);
588            }
589        }
590    }
591
592    doc.add_paragraph("")
593        .section_break(SectionBreak::NextPage)
594        .section_landscape();
595
596    // ── SECTION 9: CUSTOM STYLES ──
597    doc.add_paragraph("9. Custom Styles").style("Heading1");
598    doc.add_style(
599        StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
600            .based_on("Normal")
601            .paragraph_properties(rdocx_oxml::properties::CT_PPr {
602                shading: Some(rdocx_oxml::properties::CT_Shd {
603                    val: "clear".to_string(),
604                    color: None,
605                    fill: Some("FFF2CC".to_string()),
606                }),
607                ..Default::default()
608            })
609            .run_properties(rdocx_oxml::properties::CT_RPr {
610                bold: Some(true),
611                color: Some("C45911".to_string()),
612                ..Default::default()
613            }),
614    );
615    doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
616        .style("CustomHighlight");
617
618    doc.add_paragraph("");
619
620    // ── SECTION 10: DOCUMENT INTELLIGENCE ──
621    doc.add_paragraph("10. Document Intelligence API")
622        .style("Heading1");
623
624    let wc = doc.word_count();
625    let hc = doc.headings().len();
626    let ic = doc.images().len();
627    let lc = doc.links().len();
628    doc.add_paragraph(&format!("Word count: {wc}"));
629    doc.add_paragraph(&format!("Heading count: {hc}"));
630    doc.add_paragraph(&format!("Image count: {ic}"));
631    doc.add_paragraph(&format!("Link count: {lc}"));
632
633    let outline = doc.document_outline();
634    doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
635
636    let issues = doc.audit_accessibility();
637    doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
638    for issue in &issues {
639        doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
640    }
641
642    // ── SECTION 11: DOCUMENT MERGING ──
643    doc.add_paragraph("");
644    doc.add_paragraph("11. Document Merging").style("Heading1");
645    {
646        let mut other = Document::new();
647        other.add_paragraph("This paragraph was merged from another document using append().");
648        doc.append(&other);
649    }
650
651    doc.add_paragraph("");
652
653    // ── SUMMARY ──
654    doc.add_paragraph("Summary of Demonstrated Features")
655        .style("Heading1");
656    let features = [
657        "Page setup: size, margins, header/footer distance, gutter",
658        "Document metadata: title, author, subject, keywords",
659        "Headers and footers: text, images, different first page",
660        "Background images (full-page behind text)",
661        "Table of Contents generation with bookmarks",
662        "Text formatting: bold, italic, underline styles, strike, color, size, font",
663        "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
664        "Paragraph formatting: alignment, borders, shading, spacing, indentation",
665        "Pagination controls: keep-with-next, keep-together, widow control",
666        "Bullet and numbered lists with nesting",
667        "Tab stops with dot/underscore/hyphen leaders",
668        "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
669        "Row properties: header rows, exact height, cant-split",
670        "Cell properties: width, no-wrap, vertical alignment",
671        "Inline images and header images",
672        "Placeholder replacement (single and batch)",
673        "Regex-based find and replace",
674        "Content insertion at specific positions",
675        "Section breaks with mixed portrait/landscape",
676        "Custom paragraph and character styles",
677        "Document intelligence: word count, headings, outline, images, links",
678        "Accessibility audit",
679        "Document merging (append)",
680        "Export: DOCX, PDF, HTML, Markdown",
681    ];
682    for f in &features {
683        doc.add_bullet_list_item(f, 0);
684    }
685    doc.add_paragraph("");
686    doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
687        .alignment(Alignment::Center)
688        .shading("E2EFDA")
689        .border_all(BorderStyle::Single, 2, "00AA00");
690
691    doc
692}
Source

pub fn insert_table( &mut self, index: usize, rows: usize, cols: usize, ) -> Table<'_>

Insert a table at the given body index.

Returns a mutable Table for further configuration. A cols of 0 produces a table with no columns rather than panicking.

§Panics

Panics if index > content_count(). (Unlike Self::insert_document and Self::insert_toc, which clamp an out-of-range index to the end.)

Examples found in repository?
examples/template_replace.rs (line 201)
162fn fill_template(template_path: &Path, output_path: &Path) {
163    let mut doc = Document::open(template_path).unwrap();
164
165    // ── Batch replacement ──
166    let mut replacements = HashMap::new();
167    replacements.insert("{{company_name}}", "Riverside Medical Center");
168    replacements.insert("{{project_name}}", "Network Security Upgrade");
169    replacements.insert("{{contact_name}}", "Dr. Sarah Chen");
170    replacements.insert("{{contact_email}}", "s.chen@riverside.org");
171    replacements.insert("{{start_date}}", "March 1, 2026");
172    replacements.insert("{{duration}}", "12 weeks");
173    replacements.insert("{{budget}}", "$185,000");
174    replacements.insert("{{status}}", "Pending Approval");
175    replacements.insert("{{author_name}}", "James Wilson");
176    replacements.insert("{{date}}", "February 22, 2026");
177
178    // Team members
179    replacements.insert("{{member1_name}}", "James Wilson");
180    replacements.insert("{{member1_role}}", "Project Lead");
181    replacements.insert("{{member1_email}}", "j.wilson@provider.com");
182    replacements.insert("{{member2_name}}", "Maria Garcia");
183    replacements.insert("{{member2_role}}", "Security Architect");
184    replacements.insert("{{member2_email}}", "m.garcia@provider.com");
185    replacements.insert("{{member3_name}}", "David Park");
186    replacements.insert("{{member3_role}}", "Network Engineer");
187    replacements.insert("{{member3_email}}", "d.park@provider.com");
188
189    let count = doc.replace_all(&replacements);
190    println!("  Replaced {} placeholders", count);
191
192    // ── Insert deliverables at the insertion point ──
193    if let Some(idx) = doc.find_content_index("INSERTION_POINT") {
194        // Remove the placeholder paragraph
195        doc.remove_content(idx);
196
197        // Insert deliverables list
198        doc.insert_paragraph(idx, "The following deliverables are included:");
199
200        // Insert a deliverables table
201        let mut tbl = doc.insert_table(idx + 1, 5, 3);
202        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
203
204        for col in 0..3 {
205            tbl.cell(0, col).unwrap().shading("E2EFDA");
206        }
207        tbl.cell(0, 0).unwrap().set_text("Phase");
208        tbl.cell(0, 1).unwrap().set_text("Description");
209        tbl.cell(0, 2).unwrap().set_text("Timeline");
210
211        tbl.cell(1, 0).unwrap().set_text("1. Discovery");
212        tbl.cell(1, 1)
213            .unwrap()
214            .set_text("Network assessment and asset inventory");
215        tbl.cell(1, 2).unwrap().set_text("Weeks 1-3");
216
217        tbl.cell(2, 0).unwrap().set_text("2. Design");
218        tbl.cell(2, 1)
219            .unwrap()
220            .set_text("Security architecture and policy design");
221        tbl.cell(2, 2).unwrap().set_text("Weeks 4-6");
222
223        tbl.cell(3, 0).unwrap().set_text("3. Implementation");
224        tbl.cell(3, 1)
225            .unwrap()
226            .set_text("Deploy monitoring and access controls");
227        tbl.cell(3, 2).unwrap().set_text("Weeks 7-10");
228
229        tbl.cell(4, 0).unwrap().set_text("4. Validation");
230        tbl.cell(4, 1)
231            .unwrap()
232            .set_text("Testing, training, and handover");
233        tbl.cell(4, 2).unwrap().set_text("Weeks 11-12");
234
235        println!("  Inserted deliverables table at position {}", idx);
236    }
237
238    // ── Update metadata ──
239    doc.set_title("Riverside Medical Center — Network Security Upgrade Proposal");
240    doc.set_author("James Wilson");
241    doc.set_subject("Project Proposal");
242    doc.set_keywords("security, network, medical, proposal");
243
244    doc.save(output_path).unwrap();
245}
Source

pub fn find_content_index(&self, text: &str) -> Option<usize>

Find the body content index of the first paragraph containing the given text.

Examples found in repository?
examples/template_replace.rs (line 193)
162fn fill_template(template_path: &Path, output_path: &Path) {
163    let mut doc = Document::open(template_path).unwrap();
164
165    // ── Batch replacement ──
166    let mut replacements = HashMap::new();
167    replacements.insert("{{company_name}}", "Riverside Medical Center");
168    replacements.insert("{{project_name}}", "Network Security Upgrade");
169    replacements.insert("{{contact_name}}", "Dr. Sarah Chen");
170    replacements.insert("{{contact_email}}", "s.chen@riverside.org");
171    replacements.insert("{{start_date}}", "March 1, 2026");
172    replacements.insert("{{duration}}", "12 weeks");
173    replacements.insert("{{budget}}", "$185,000");
174    replacements.insert("{{status}}", "Pending Approval");
175    replacements.insert("{{author_name}}", "James Wilson");
176    replacements.insert("{{date}}", "February 22, 2026");
177
178    // Team members
179    replacements.insert("{{member1_name}}", "James Wilson");
180    replacements.insert("{{member1_role}}", "Project Lead");
181    replacements.insert("{{member1_email}}", "j.wilson@provider.com");
182    replacements.insert("{{member2_name}}", "Maria Garcia");
183    replacements.insert("{{member2_role}}", "Security Architect");
184    replacements.insert("{{member2_email}}", "m.garcia@provider.com");
185    replacements.insert("{{member3_name}}", "David Park");
186    replacements.insert("{{member3_role}}", "Network Engineer");
187    replacements.insert("{{member3_email}}", "d.park@provider.com");
188
189    let count = doc.replace_all(&replacements);
190    println!("  Replaced {} placeholders", count);
191
192    // ── Insert deliverables at the insertion point ──
193    if let Some(idx) = doc.find_content_index("INSERTION_POINT") {
194        // Remove the placeholder paragraph
195        doc.remove_content(idx);
196
197        // Insert deliverables list
198        doc.insert_paragraph(idx, "The following deliverables are included:");
199
200        // Insert a deliverables table
201        let mut tbl = doc.insert_table(idx + 1, 5, 3);
202        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
203
204        for col in 0..3 {
205            tbl.cell(0, col).unwrap().shading("E2EFDA");
206        }
207        tbl.cell(0, 0).unwrap().set_text("Phase");
208        tbl.cell(0, 1).unwrap().set_text("Description");
209        tbl.cell(0, 2).unwrap().set_text("Timeline");
210
211        tbl.cell(1, 0).unwrap().set_text("1. Discovery");
212        tbl.cell(1, 1)
213            .unwrap()
214            .set_text("Network assessment and asset inventory");
215        tbl.cell(1, 2).unwrap().set_text("Weeks 1-3");
216
217        tbl.cell(2, 0).unwrap().set_text("2. Design");
218        tbl.cell(2, 1)
219            .unwrap()
220            .set_text("Security architecture and policy design");
221        tbl.cell(2, 2).unwrap().set_text("Weeks 4-6");
222
223        tbl.cell(3, 0).unwrap().set_text("3. Implementation");
224        tbl.cell(3, 1)
225            .unwrap()
226            .set_text("Deploy monitoring and access controls");
227        tbl.cell(3, 2).unwrap().set_text("Weeks 7-10");
228
229        tbl.cell(4, 0).unwrap().set_text("4. Validation");
230        tbl.cell(4, 1)
231            .unwrap()
232            .set_text("Testing, training, and handover");
233        tbl.cell(4, 2).unwrap().set_text("Weeks 11-12");
234
235        println!("  Inserted deliverables table at position {}", idx);
236    }
237
238    // ── Update metadata ──
239    doc.set_title("Riverside Medical Center — Network Security Upgrade Proposal");
240    doc.set_author("James Wilson");
241    doc.set_subject("Project Proposal");
242    doc.set_keywords("security, network, medical, proposal");
243
244    doc.save(output_path).unwrap();
245}
More examples
Hide additional examples
examples/generate_all_samples.rs (line 555)
97fn generate_feature_showcase(_samples_dir: &Path) -> Document {
98    let mut doc = Document::new();
99
100    // ── Page Setup & Metadata ──
101    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
102    doc.set_margins(
103        Length::inches(1.0),
104        Length::inches(1.0),
105        Length::inches(1.0),
106        Length::inches(1.0),
107    );
108    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
109    doc.set_gutter(Length::twips(0));
110    doc.set_title("rdocx Feature Showcase");
111    doc.set_author("rdocx Sample Generator");
112    doc.set_subject("Comprehensive feature demonstration");
113    doc.set_keywords("rdocx, docx, rust, sample, showcase");
114
115    // Headers & Footers with different first page
116    doc.set_different_first_page(true);
117    doc.set_first_page_header("rdocx");
118    doc.set_first_page_footer("Feature Showcase — Cover Page");
119    doc.set_header("rdocx Feature Showcase");
120    doc.set_footer("Generated by rdocx");
121
122    // ── COVER PAGE ──
123    let bg = create_sample_png(612, 792, [20, 45, 90]);
124    doc.add_background_image(&bg, "cover_bg.png");
125
126    for _ in 0..3 {
127        doc.add_paragraph("");
128    }
129    {
130        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
131        p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
132    }
133    {
134        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
135        p.add_run("Complete Feature Showcase")
136            .size(28.0)
137            .color("FFFFFF")
138            .italic(true);
139    }
140    doc.add_paragraph("");
141    {
142        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
143        p.add_run("Every feature of the rdocx Rust library")
144            .size(13.0)
145            .color("B0C4DE");
146    }
147    {
148        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
149        p.add_run("demonstrated in a single document.")
150            .size(13.0)
151            .color("B0C4DE");
152    }
153
154    // ── TABLE OF CONTENTS ──
155    doc.add_paragraph("").page_break_before(true);
156    doc.insert_toc(doc.content_count(), 3);
157
158    // ── SECTION 1: TEXT FORMATTING ──
159    doc.add_paragraph("").page_break_before(true);
160    doc.add_paragraph("1. Text Formatting").style("Heading1");
161
162    // Paragraph Alignment
163    doc.add_paragraph("1.1 Paragraph Alignment")
164        .style("Heading2");
165    doc.add_paragraph("Left-aligned paragraph (default).")
166        .alignment(Alignment::Left);
167    doc.add_paragraph("Center-aligned paragraph.")
168        .alignment(Alignment::Center);
169    doc.add_paragraph("Right-aligned paragraph.")
170        .alignment(Alignment::Right);
171    doc.add_paragraph(
172        "Justified paragraph — this text is long enough to demonstrate how justified alignment \
173         distributes extra space across word gaps so lines fill the full width of the text area.",
174    )
175    .alignment(Alignment::Justify);
176
177    doc.add_paragraph("");
178
179    // Run Formatting
180    doc.add_paragraph("1.2 Run Formatting").style("Heading2");
181    {
182        let mut p = doc.add_paragraph("");
183        p.add_run("Bold").bold(true);
184        p.add_run(" | ");
185        p.add_run("Italic").italic(true);
186        p.add_run(" | ");
187        p.add_run("Bold Italic").bold(true).italic(true);
188        p.add_run(" | ");
189        p.add_run("Underline").underline(true);
190        p.add_run(" | ");
191        p.add_run("Strikethrough").strike(true);
192        p.add_run(" | ");
193        p.add_run("Double Strike").double_strike(true);
194    }
195    {
196        let mut p = doc.add_paragraph("");
197        p.add_run("Red").color("FF0000");
198        p.add_run(" | ");
199        p.add_run("Blue").color("0000FF");
200        p.add_run(" | ");
201        p.add_run("Green").color("00AA00");
202        p.add_run(" | ");
203        p.add_run("Highlighted").highlight("FFFF00");
204    }
205    {
206        let mut p = doc.add_paragraph("");
207        p.add_run("8pt").size(8.0);
208        p.add_run(" | ");
209        p.add_run("11pt").size(11.0);
210        p.add_run(" | ");
211        p.add_run("16pt").size(16.0);
212        p.add_run(" | ");
213        p.add_run("24pt").size(24.0);
214    }
215    {
216        let mut p = doc.add_paragraph("");
217        p.add_run("Arial").font("Arial");
218        p.add_run(" | ");
219        p.add_run("Times New Roman").font("Times New Roman");
220        p.add_run(" | ");
221        p.add_run("Courier New").font("Courier New");
222    }
223    {
224        let mut p = doc.add_paragraph("");
225        p.add_run("H");
226        p.add_run("2").subscript();
227        p.add_run("O (subscript) | E=mc");
228        p.add_run("2").superscript();
229        p.add_run(" (superscript)");
230    }
231    {
232        let mut p = doc.add_paragraph("");
233        p.add_run("ALL CAPS").all_caps(true);
234        p.add_run(" | ");
235        p.add_run("Small Caps").small_caps(true);
236        p.add_run(" | ");
237        p.add_run("Expanded").character_spacing(Length::twips(40));
238        p.add_run(" | ");
239        p.add_run("Hidden text (not visible)").hidden(true);
240    }
241
242    doc.add_paragraph("");
243
244    // Underline Styles
245    doc.add_paragraph("1.3 Underline Styles").style("Heading2");
246    {
247        let mut p = doc.add_paragraph("");
248        p.add_run("Single ")
249            .underline_style(rdocx::UnderlineStyle::Single);
250        p.add_run("Double ")
251            .underline_style(rdocx::UnderlineStyle::Double);
252        p.add_run("Thick ")
253            .underline_style(rdocx::UnderlineStyle::Thick);
254        p.add_run("Dotted ")
255            .underline_style(rdocx::UnderlineStyle::Dotted);
256        p.add_run("Dash ")
257            .underline_style(rdocx::UnderlineStyle::Dash);
258        p.add_run("Wave ")
259            .underline_style(rdocx::UnderlineStyle::Wave);
260    }
261
262    // ── SECTION 2: PARAGRAPH FORMATTING ──
263    doc.add_paragraph("").page_break_before(true);
264    doc.add_paragraph("2. Paragraph Formatting")
265        .style("Heading1");
266
267    doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
268    doc.add_paragraph("Paragraph with light green shading")
269        .shading("E2EFDA");
270    doc.add_paragraph("Paragraph with bottom border")
271        .border_bottom(BorderStyle::Single, 6, "2E75B6");
272    doc.add_paragraph("Paragraph with all borders (red)")
273        .border_all(BorderStyle::Single, 4, "FF0000");
274
275    doc.add_paragraph("2.2 Indentation").style("Heading2");
276    doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
277        .indent_left(Length::inches(1.0))
278        .hanging_indent(Length::inches(0.5));
279    doc.add_paragraph("0.5-inch first-line indent on this paragraph")
280        .first_line_indent(Length::inches(0.5));
281    doc.add_paragraph("Both left and right indent (0.75 inches each)")
282        .indent_left(Length::inches(0.75))
283        .indent_right(Length::inches(0.75));
284
285    doc.add_paragraph("2.3 Spacing & Line Height")
286        .style("Heading2");
287    doc.add_paragraph("Extra space before (24pt) and after (12pt)")
288        .space_before(Length::pt(24.0))
289        .space_after(Length::pt(12.0));
290    doc.add_paragraph(
291        "Double line spacing paragraph. This text should have extra vertical space between \
292         lines to demonstrate line_spacing_multiple(2.0).",
293    )
294    .line_spacing_multiple(2.0);
295    doc.add_paragraph("Exact 20pt line spacing (fixed height).")
296        .line_spacing(20.0);
297
298    doc.add_paragraph("2.4 Pagination Controls")
299        .style("Heading2");
300    doc.add_paragraph("keep_with_next — this paragraph stays with the next")
301        .keep_with_next(true);
302    doc.add_paragraph("(This paragraph was kept with the one above.)");
303    doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
304        .keep_together(true);
305    doc.add_paragraph("widow_control — prevents widow/orphan lines")
306        .widow_control(true);
307
308    // ── SECTION 3: LISTS ──
309    doc.add_paragraph("").page_break_before(true);
310    doc.add_paragraph("3. Lists").style("Heading1");
311
312    doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
313    doc.add_bullet_list_item("First item", 0);
314    doc.add_bullet_list_item("Second item", 0);
315    doc.add_bullet_list_item("Nested level 1", 1);
316    doc.add_bullet_list_item("Nested level 2", 2);
317    doc.add_bullet_list_item("Back to level 1", 1);
318    doc.add_bullet_list_item("Third item", 0);
319
320    doc.add_paragraph("");
321
322    doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
323    doc.add_numbered_list_item("First numbered item", 0);
324    doc.add_numbered_list_item("Second numbered item", 0);
325    doc.add_numbered_list_item("Sub-item A", 1);
326    doc.add_numbered_list_item("Sub-item B", 1);
327    doc.add_numbered_list_item("Third numbered item", 0);
328
329    // ── SECTION 4: TAB STOPS ──
330    doc.add_paragraph("");
331    doc.add_paragraph("4. Tab Stops").style("Heading1");
332
333    doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
334    doc.add_paragraph("Left\tCenter\tRight\tDecimal")
335        .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
336        .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
337        .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
338        .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
339
340    doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
341    doc.add_paragraph("Item\t\tPrice")
342        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
343        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
344        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
345    doc.add_paragraph("Widget\t\t$19.99")
346        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
347        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
348        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
349    doc.add_paragraph("Gadget\t\t$249.50")
350        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
351        .add_tab_stop_with_leader(
352            TabAlignment::Right,
353            Length::inches(4.0),
354            TabLeader::Underscore,
355        )
356        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
357
358    // ── SECTION 5: TABLES ──
359    doc.add_paragraph("").page_break_before(true);
360    doc.add_paragraph("5. Tables").style("Heading1");
361
362    doc.add_paragraph("5.1 Basic Table").style("Heading2");
363    {
364        let mut tbl = doc
365            .add_table(4, 3)
366            .borders(BorderStyle::Single, 4, "000000");
367        for col in 0..3 {
368            tbl.cell(0, col).unwrap().shading("2E75B6");
369        }
370        tbl.cell(0, 0).unwrap().set_text("Name");
371        tbl.cell(0, 1).unwrap().set_text("Role");
372        tbl.cell(0, 2).unwrap().set_text("Location");
373        tbl.cell(1, 0).unwrap().set_text("Walter White");
374        tbl.cell(1, 1).unwrap().set_text("CEO");
375        tbl.cell(1, 2).unwrap().set_text("Albuquerque");
376        tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
377        tbl.cell(2, 1).unwrap().set_text("CTO");
378        tbl.cell(2, 2).unwrap().set_text("Remote");
379        tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
380        tbl.cell(3, 1).unwrap().set_text("Security");
381        tbl.cell(3, 2).unwrap().set_text("Washington");
382    }
383
384    doc.add_paragraph("");
385
386    doc.add_paragraph("5.2 Column Span & Cell Shading")
387        .style("Heading2");
388    {
389        let mut tbl = doc
390            .add_table(3, 4)
391            .borders(BorderStyle::Single, 4, "000000")
392            .width_pct(100.0);
393        tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
394        tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
395        tbl.cell(1, 0).unwrap().set_text("Region");
396        tbl.cell(1, 0).unwrap().shading("D6E4F0");
397        tbl.cell(1, 1).unwrap().set_text("Q1");
398        tbl.cell(1, 1).unwrap().shading("D6E4F0");
399        tbl.cell(1, 2).unwrap().set_text("Q2");
400        tbl.cell(1, 2).unwrap().shading("D6E4F0");
401        tbl.cell(1, 3).unwrap().set_text("Total");
402        tbl.cell(1, 3).unwrap().shading("D6E4F0");
403        tbl.cell(2, 0).unwrap().set_text("Americas");
404        tbl.cell(2, 1).unwrap().set_text("$2.4M");
405        tbl.cell(2, 2).unwrap().set_text("$2.7M");
406        tbl.cell(2, 3).unwrap().set_text("$5.1M");
407    }
408
409    doc.add_paragraph("");
410
411    doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
412    {
413        let mut tbl = doc
414            .add_table(4, 3)
415            .borders(BorderStyle::Single, 4, "000000");
416        tbl.cell(0, 0).unwrap().set_text("Category");
417        tbl.cell(0, 0).unwrap().shading("E2EFDA");
418        tbl.cell(0, 1).unwrap().set_text("Item");
419        tbl.cell(0, 1).unwrap().shading("E2EFDA");
420        tbl.cell(0, 2).unwrap().set_text("Price");
421        tbl.cell(0, 2).unwrap().shading("E2EFDA");
422        tbl.cell(1, 0).unwrap().set_text("Hardware");
423        tbl.cell(1, 0).unwrap().v_merge_restart();
424        tbl.cell(1, 1).unwrap().set_text("Laptop");
425        tbl.cell(1, 2).unwrap().set_text("$1,200");
426        tbl.cell(2, 0).unwrap().v_merge_continue();
427        tbl.cell(2, 1).unwrap().set_text("Monitor");
428        tbl.cell(2, 2).unwrap().set_text("$450");
429        tbl.cell(3, 0).unwrap().set_text("Software");
430        tbl.cell(3, 1).unwrap().set_text("IDE License");
431        tbl.cell(3, 2).unwrap().set_text("$200/yr");
432    }
433
434    doc.add_paragraph("");
435
436    doc.add_paragraph("5.4 Nested Table").style("Heading2");
437    {
438        let mut tbl = doc
439            .add_table(2, 2)
440            .borders(BorderStyle::Single, 6, "2E75B6");
441        tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
442        tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
443        tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
444        {
445            let mut cell = tbl.cell(1, 1).unwrap();
446            cell.set_text("Nested table below:");
447            let mut nested = cell
448                .add_table(2, 2)
449                .borders(BorderStyle::Single, 2, "FF6600");
450            nested.cell(0, 0).unwrap().set_text("A");
451            nested.cell(0, 1).unwrap().set_text("B");
452            nested.cell(1, 0).unwrap().set_text("C");
453            nested.cell(1, 1).unwrap().set_text("D");
454        }
455    }
456
457    doc.add_paragraph("");
458
459    doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
460        .style("Heading2");
461    {
462        let mut tbl = doc
463            .add_table(2, 3)
464            .borders(BorderStyle::Single, 4, "666666");
465        tbl.row(0).unwrap().height(Length::pt(50.0)).header();
466        tbl.cell(0, 0).unwrap().set_text("Top");
467        tbl.cell(0, 0)
468            .unwrap()
469            .vertical_alignment(VerticalAlignment::Top)
470            .shading("FFF2CC");
471        tbl.cell(0, 1).unwrap().set_text("Center");
472        tbl.cell(0, 1)
473            .unwrap()
474            .vertical_alignment(VerticalAlignment::Center)
475            .shading("D9E2F3");
476        tbl.cell(0, 2).unwrap().set_text("Bottom");
477        tbl.cell(0, 2)
478            .unwrap()
479            .vertical_alignment(VerticalAlignment::Bottom)
480            .shading("E2EFDA");
481        tbl.row(1).unwrap().cant_split();
482        tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
483        tbl.cell(1, 0).unwrap().no_wrap();
484        tbl.cell(1, 1).unwrap().set_text("Fixed width");
485        tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
486        tbl.cell(1, 2).unwrap().set_text("Normal");
487    }
488
489    // ── SECTION 6: IMAGES ──
490    doc.add_paragraph("").page_break_before(true);
491    doc.add_paragraph("6. Images").style("Heading1");
492
493    doc.add_paragraph("6.1 Inline Image").style("Heading2");
494    doc.add_paragraph("A blue gradient image below:");
495    let img = create_sample_png(200, 50, [0, 80, 200]);
496    doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
497
498    doc.add_paragraph("");
499    doc.add_paragraph("6.2 Header Image").style("Heading2");
500    let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
501    doc.set_header_image(
502        &hdr_img,
503        "header_logo.png",
504        Length::inches(2.0),
505        Length::inches(0.2),
506    );
507    doc.add_paragraph("The header now contains an inline image (check the top of this page).");
508
509    doc.add_paragraph("");
510    doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
511
512    // ── SECTION 7: CONTENT MANIPULATION ──
513    doc.add_paragraph("").page_break_before(true);
514    doc.add_paragraph("7. Content Manipulation")
515        .style("Heading1");
516
517    doc.add_paragraph("7.1 Placeholder Replacement")
518        .style("Heading2");
519    doc.add_paragraph("Customer: {{customer}}");
520    doc.add_paragraph("Date: {{date}}");
521    doc.add_paragraph("Reference: {{ref_number}}");
522    {
523        let mut tbl = doc
524            .add_table(2, 2)
525            .borders(BorderStyle::Single, 4, "000000");
526        tbl.cell(0, 0).unwrap().set_text("Project");
527        tbl.cell(0, 0).unwrap().shading("D6E4F0");
528        tbl.cell(0, 1).unwrap().set_text("{{project}}");
529        tbl.cell(1, 0).unwrap().set_text("Status");
530        tbl.cell(1, 0).unwrap().shading("D6E4F0");
531        tbl.cell(1, 1).unwrap().set_text("{{status}}");
532    }
533
534    let mut replacements = HashMap::new();
535    replacements.insert("{{customer}}", "Tensorbee Inc.");
536    replacements.insert("{{date}}", "February 22, 2026");
537    replacements.insert("{{ref_number}}", "TB-2026-001");
538    replacements.insert("{{project}}", "Infrastructure Upgrade");
539    replacements.insert("{{status}}", "In Progress");
540    let n = doc.replace_all(&replacements);
541    doc.add_paragraph(&format!("({n} placeholders replaced above)"));
542
543    doc.add_paragraph("");
544
545    doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
546    doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
547    let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
548    doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
549
550    doc.add_paragraph("");
551
552    doc.add_paragraph("7.3 Content Insertion").style("Heading2");
553    doc.add_paragraph("Section A: First content.");
554    doc.add_paragraph("Section C: Third content.");
555    if let Some(idx) = doc.find_content_index("Section C") {
556        doc.insert_paragraph(
557            idx,
558            "Section B: Inserted between A and C via find_content_index().",
559        );
560    }
561
562    // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
563    doc.add_paragraph("").section_break(SectionBreak::NextPage);
564    doc.add_paragraph("8. Section Breaks & Orientation")
565        .style("Heading1");
566    doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
567
568    {
569        let mut tbl = doc
570            .add_table(3, 7)
571            .borders(BorderStyle::Single, 4, "2E75B6");
572        let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
573        for (c, h) in headers.iter().enumerate() {
574            tbl.cell(0, c).unwrap().set_text(h);
575            tbl.cell(0, c).unwrap().shading("2E75B6");
576        }
577        let data = [
578            [
579                "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
580            ],
581            [
582                "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
583            ],
584        ];
585        for (r, row) in data.iter().enumerate() {
586            for (c, v) in row.iter().enumerate() {
587                tbl.cell(r + 1, c).unwrap().set_text(v);
588            }
589        }
590    }
591
592    doc.add_paragraph("")
593        .section_break(SectionBreak::NextPage)
594        .section_landscape();
595
596    // ── SECTION 9: CUSTOM STYLES ──
597    doc.add_paragraph("9. Custom Styles").style("Heading1");
598    doc.add_style(
599        StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
600            .based_on("Normal")
601            .paragraph_properties(rdocx_oxml::properties::CT_PPr {
602                shading: Some(rdocx_oxml::properties::CT_Shd {
603                    val: "clear".to_string(),
604                    color: None,
605                    fill: Some("FFF2CC".to_string()),
606                }),
607                ..Default::default()
608            })
609            .run_properties(rdocx_oxml::properties::CT_RPr {
610                bold: Some(true),
611                color: Some("C45911".to_string()),
612                ..Default::default()
613            }),
614    );
615    doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
616        .style("CustomHighlight");
617
618    doc.add_paragraph("");
619
620    // ── SECTION 10: DOCUMENT INTELLIGENCE ──
621    doc.add_paragraph("10. Document Intelligence API")
622        .style("Heading1");
623
624    let wc = doc.word_count();
625    let hc = doc.headings().len();
626    let ic = doc.images().len();
627    let lc = doc.links().len();
628    doc.add_paragraph(&format!("Word count: {wc}"));
629    doc.add_paragraph(&format!("Heading count: {hc}"));
630    doc.add_paragraph(&format!("Image count: {ic}"));
631    doc.add_paragraph(&format!("Link count: {lc}"));
632
633    let outline = doc.document_outline();
634    doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
635
636    let issues = doc.audit_accessibility();
637    doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
638    for issue in &issues {
639        doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
640    }
641
642    // ── SECTION 11: DOCUMENT MERGING ──
643    doc.add_paragraph("");
644    doc.add_paragraph("11. Document Merging").style("Heading1");
645    {
646        let mut other = Document::new();
647        other.add_paragraph("This paragraph was merged from another document using append().");
648        doc.append(&other);
649    }
650
651    doc.add_paragraph("");
652
653    // ── SUMMARY ──
654    doc.add_paragraph("Summary of Demonstrated Features")
655        .style("Heading1");
656    let features = [
657        "Page setup: size, margins, header/footer distance, gutter",
658        "Document metadata: title, author, subject, keywords",
659        "Headers and footers: text, images, different first page",
660        "Background images (full-page behind text)",
661        "Table of Contents generation with bookmarks",
662        "Text formatting: bold, italic, underline styles, strike, color, size, font",
663        "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
664        "Paragraph formatting: alignment, borders, shading, spacing, indentation",
665        "Pagination controls: keep-with-next, keep-together, widow control",
666        "Bullet and numbered lists with nesting",
667        "Tab stops with dot/underscore/hyphen leaders",
668        "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
669        "Row properties: header rows, exact height, cant-split",
670        "Cell properties: width, no-wrap, vertical alignment",
671        "Inline images and header images",
672        "Placeholder replacement (single and batch)",
673        "Regex-based find and replace",
674        "Content insertion at specific positions",
675        "Section breaks with mixed portrait/landscape",
676        "Custom paragraph and character styles",
677        "Document intelligence: word count, headings, outline, images, links",
678        "Accessibility audit",
679        "Document merging (append)",
680        "Export: DOCX, PDF, HTML, Markdown",
681    ];
682    for f in &features {
683        doc.add_bullet_list_item(f, 0);
684    }
685    doc.add_paragraph("");
686    doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
687        .alignment(Alignment::Center)
688        .shading("E2EFDA")
689        .border_all(BorderStyle::Single, 2, "00AA00");
690
691    doc
692}
Source

pub fn remove_content(&mut self, index: usize) -> bool

Remove the content at the given body index.

Returns true if an element was removed, false if the index was out of bounds.

Examples found in repository?
examples/template_replace.rs (line 195)
162fn fill_template(template_path: &Path, output_path: &Path) {
163    let mut doc = Document::open(template_path).unwrap();
164
165    // ── Batch replacement ──
166    let mut replacements = HashMap::new();
167    replacements.insert("{{company_name}}", "Riverside Medical Center");
168    replacements.insert("{{project_name}}", "Network Security Upgrade");
169    replacements.insert("{{contact_name}}", "Dr. Sarah Chen");
170    replacements.insert("{{contact_email}}", "s.chen@riverside.org");
171    replacements.insert("{{start_date}}", "March 1, 2026");
172    replacements.insert("{{duration}}", "12 weeks");
173    replacements.insert("{{budget}}", "$185,000");
174    replacements.insert("{{status}}", "Pending Approval");
175    replacements.insert("{{author_name}}", "James Wilson");
176    replacements.insert("{{date}}", "February 22, 2026");
177
178    // Team members
179    replacements.insert("{{member1_name}}", "James Wilson");
180    replacements.insert("{{member1_role}}", "Project Lead");
181    replacements.insert("{{member1_email}}", "j.wilson@provider.com");
182    replacements.insert("{{member2_name}}", "Maria Garcia");
183    replacements.insert("{{member2_role}}", "Security Architect");
184    replacements.insert("{{member2_email}}", "m.garcia@provider.com");
185    replacements.insert("{{member3_name}}", "David Park");
186    replacements.insert("{{member3_role}}", "Network Engineer");
187    replacements.insert("{{member3_email}}", "d.park@provider.com");
188
189    let count = doc.replace_all(&replacements);
190    println!("  Replaced {} placeholders", count);
191
192    // ── Insert deliverables at the insertion point ──
193    if let Some(idx) = doc.find_content_index("INSERTION_POINT") {
194        // Remove the placeholder paragraph
195        doc.remove_content(idx);
196
197        // Insert deliverables list
198        doc.insert_paragraph(idx, "The following deliverables are included:");
199
200        // Insert a deliverables table
201        let mut tbl = doc.insert_table(idx + 1, 5, 3);
202        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
203
204        for col in 0..3 {
205            tbl.cell(0, col).unwrap().shading("E2EFDA");
206        }
207        tbl.cell(0, 0).unwrap().set_text("Phase");
208        tbl.cell(0, 1).unwrap().set_text("Description");
209        tbl.cell(0, 2).unwrap().set_text("Timeline");
210
211        tbl.cell(1, 0).unwrap().set_text("1. Discovery");
212        tbl.cell(1, 1)
213            .unwrap()
214            .set_text("Network assessment and asset inventory");
215        tbl.cell(1, 2).unwrap().set_text("Weeks 1-3");
216
217        tbl.cell(2, 0).unwrap().set_text("2. Design");
218        tbl.cell(2, 1)
219            .unwrap()
220            .set_text("Security architecture and policy design");
221        tbl.cell(2, 2).unwrap().set_text("Weeks 4-6");
222
223        tbl.cell(3, 0).unwrap().set_text("3. Implementation");
224        tbl.cell(3, 1)
225            .unwrap()
226            .set_text("Deploy monitoring and access controls");
227        tbl.cell(3, 2).unwrap().set_text("Weeks 7-10");
228
229        tbl.cell(4, 0).unwrap().set_text("4. Validation");
230        tbl.cell(4, 1)
231            .unwrap()
232            .set_text("Testing, training, and handover");
233        tbl.cell(4, 2).unwrap().set_text("Weeks 11-12");
234
235        println!("  Inserted deliverables table at position {}", idx);
236    }
237
238    // ── Update metadata ──
239    doc.set_title("Riverside Medical Center — Network Security Upgrade Proposal");
240    doc.set_author("James Wilson");
241    doc.set_subject("Project Proposal");
242    doc.set_keywords("security, network, medical, proposal");
243
244    doc.save(output_path).unwrap();
245}
Source

pub fn add_picture( &mut self, image_data: &[u8], image_filename: &str, width: Length, height: Length, ) -> Paragraph<'_>

Add an inline image to the document.

Embeds the image data (PNG, JPEG, etc.) into the package and adds a paragraph containing the image. Returns a mutable reference to the paragraph for further configuration.

width and height specify the display size.

Examples found in repository?
examples/generate_all_samples.rs (line 496)
97fn generate_feature_showcase(_samples_dir: &Path) -> Document {
98    let mut doc = Document::new();
99
100    // ── Page Setup & Metadata ──
101    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
102    doc.set_margins(
103        Length::inches(1.0),
104        Length::inches(1.0),
105        Length::inches(1.0),
106        Length::inches(1.0),
107    );
108    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
109    doc.set_gutter(Length::twips(0));
110    doc.set_title("rdocx Feature Showcase");
111    doc.set_author("rdocx Sample Generator");
112    doc.set_subject("Comprehensive feature demonstration");
113    doc.set_keywords("rdocx, docx, rust, sample, showcase");
114
115    // Headers & Footers with different first page
116    doc.set_different_first_page(true);
117    doc.set_first_page_header("rdocx");
118    doc.set_first_page_footer("Feature Showcase — Cover Page");
119    doc.set_header("rdocx Feature Showcase");
120    doc.set_footer("Generated by rdocx");
121
122    // ── COVER PAGE ──
123    let bg = create_sample_png(612, 792, [20, 45, 90]);
124    doc.add_background_image(&bg, "cover_bg.png");
125
126    for _ in 0..3 {
127        doc.add_paragraph("");
128    }
129    {
130        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
131        p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
132    }
133    {
134        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
135        p.add_run("Complete Feature Showcase")
136            .size(28.0)
137            .color("FFFFFF")
138            .italic(true);
139    }
140    doc.add_paragraph("");
141    {
142        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
143        p.add_run("Every feature of the rdocx Rust library")
144            .size(13.0)
145            .color("B0C4DE");
146    }
147    {
148        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
149        p.add_run("demonstrated in a single document.")
150            .size(13.0)
151            .color("B0C4DE");
152    }
153
154    // ── TABLE OF CONTENTS ──
155    doc.add_paragraph("").page_break_before(true);
156    doc.insert_toc(doc.content_count(), 3);
157
158    // ── SECTION 1: TEXT FORMATTING ──
159    doc.add_paragraph("").page_break_before(true);
160    doc.add_paragraph("1. Text Formatting").style("Heading1");
161
162    // Paragraph Alignment
163    doc.add_paragraph("1.1 Paragraph Alignment")
164        .style("Heading2");
165    doc.add_paragraph("Left-aligned paragraph (default).")
166        .alignment(Alignment::Left);
167    doc.add_paragraph("Center-aligned paragraph.")
168        .alignment(Alignment::Center);
169    doc.add_paragraph("Right-aligned paragraph.")
170        .alignment(Alignment::Right);
171    doc.add_paragraph(
172        "Justified paragraph — this text is long enough to demonstrate how justified alignment \
173         distributes extra space across word gaps so lines fill the full width of the text area.",
174    )
175    .alignment(Alignment::Justify);
176
177    doc.add_paragraph("");
178
179    // Run Formatting
180    doc.add_paragraph("1.2 Run Formatting").style("Heading2");
181    {
182        let mut p = doc.add_paragraph("");
183        p.add_run("Bold").bold(true);
184        p.add_run(" | ");
185        p.add_run("Italic").italic(true);
186        p.add_run(" | ");
187        p.add_run("Bold Italic").bold(true).italic(true);
188        p.add_run(" | ");
189        p.add_run("Underline").underline(true);
190        p.add_run(" | ");
191        p.add_run("Strikethrough").strike(true);
192        p.add_run(" | ");
193        p.add_run("Double Strike").double_strike(true);
194    }
195    {
196        let mut p = doc.add_paragraph("");
197        p.add_run("Red").color("FF0000");
198        p.add_run(" | ");
199        p.add_run("Blue").color("0000FF");
200        p.add_run(" | ");
201        p.add_run("Green").color("00AA00");
202        p.add_run(" | ");
203        p.add_run("Highlighted").highlight("FFFF00");
204    }
205    {
206        let mut p = doc.add_paragraph("");
207        p.add_run("8pt").size(8.0);
208        p.add_run(" | ");
209        p.add_run("11pt").size(11.0);
210        p.add_run(" | ");
211        p.add_run("16pt").size(16.0);
212        p.add_run(" | ");
213        p.add_run("24pt").size(24.0);
214    }
215    {
216        let mut p = doc.add_paragraph("");
217        p.add_run("Arial").font("Arial");
218        p.add_run(" | ");
219        p.add_run("Times New Roman").font("Times New Roman");
220        p.add_run(" | ");
221        p.add_run("Courier New").font("Courier New");
222    }
223    {
224        let mut p = doc.add_paragraph("");
225        p.add_run("H");
226        p.add_run("2").subscript();
227        p.add_run("O (subscript) | E=mc");
228        p.add_run("2").superscript();
229        p.add_run(" (superscript)");
230    }
231    {
232        let mut p = doc.add_paragraph("");
233        p.add_run("ALL CAPS").all_caps(true);
234        p.add_run(" | ");
235        p.add_run("Small Caps").small_caps(true);
236        p.add_run(" | ");
237        p.add_run("Expanded").character_spacing(Length::twips(40));
238        p.add_run(" | ");
239        p.add_run("Hidden text (not visible)").hidden(true);
240    }
241
242    doc.add_paragraph("");
243
244    // Underline Styles
245    doc.add_paragraph("1.3 Underline Styles").style("Heading2");
246    {
247        let mut p = doc.add_paragraph("");
248        p.add_run("Single ")
249            .underline_style(rdocx::UnderlineStyle::Single);
250        p.add_run("Double ")
251            .underline_style(rdocx::UnderlineStyle::Double);
252        p.add_run("Thick ")
253            .underline_style(rdocx::UnderlineStyle::Thick);
254        p.add_run("Dotted ")
255            .underline_style(rdocx::UnderlineStyle::Dotted);
256        p.add_run("Dash ")
257            .underline_style(rdocx::UnderlineStyle::Dash);
258        p.add_run("Wave ")
259            .underline_style(rdocx::UnderlineStyle::Wave);
260    }
261
262    // ── SECTION 2: PARAGRAPH FORMATTING ──
263    doc.add_paragraph("").page_break_before(true);
264    doc.add_paragraph("2. Paragraph Formatting")
265        .style("Heading1");
266
267    doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
268    doc.add_paragraph("Paragraph with light green shading")
269        .shading("E2EFDA");
270    doc.add_paragraph("Paragraph with bottom border")
271        .border_bottom(BorderStyle::Single, 6, "2E75B6");
272    doc.add_paragraph("Paragraph with all borders (red)")
273        .border_all(BorderStyle::Single, 4, "FF0000");
274
275    doc.add_paragraph("2.2 Indentation").style("Heading2");
276    doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
277        .indent_left(Length::inches(1.0))
278        .hanging_indent(Length::inches(0.5));
279    doc.add_paragraph("0.5-inch first-line indent on this paragraph")
280        .first_line_indent(Length::inches(0.5));
281    doc.add_paragraph("Both left and right indent (0.75 inches each)")
282        .indent_left(Length::inches(0.75))
283        .indent_right(Length::inches(0.75));
284
285    doc.add_paragraph("2.3 Spacing & Line Height")
286        .style("Heading2");
287    doc.add_paragraph("Extra space before (24pt) and after (12pt)")
288        .space_before(Length::pt(24.0))
289        .space_after(Length::pt(12.0));
290    doc.add_paragraph(
291        "Double line spacing paragraph. This text should have extra vertical space between \
292         lines to demonstrate line_spacing_multiple(2.0).",
293    )
294    .line_spacing_multiple(2.0);
295    doc.add_paragraph("Exact 20pt line spacing (fixed height).")
296        .line_spacing(20.0);
297
298    doc.add_paragraph("2.4 Pagination Controls")
299        .style("Heading2");
300    doc.add_paragraph("keep_with_next — this paragraph stays with the next")
301        .keep_with_next(true);
302    doc.add_paragraph("(This paragraph was kept with the one above.)");
303    doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
304        .keep_together(true);
305    doc.add_paragraph("widow_control — prevents widow/orphan lines")
306        .widow_control(true);
307
308    // ── SECTION 3: LISTS ──
309    doc.add_paragraph("").page_break_before(true);
310    doc.add_paragraph("3. Lists").style("Heading1");
311
312    doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
313    doc.add_bullet_list_item("First item", 0);
314    doc.add_bullet_list_item("Second item", 0);
315    doc.add_bullet_list_item("Nested level 1", 1);
316    doc.add_bullet_list_item("Nested level 2", 2);
317    doc.add_bullet_list_item("Back to level 1", 1);
318    doc.add_bullet_list_item("Third item", 0);
319
320    doc.add_paragraph("");
321
322    doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
323    doc.add_numbered_list_item("First numbered item", 0);
324    doc.add_numbered_list_item("Second numbered item", 0);
325    doc.add_numbered_list_item("Sub-item A", 1);
326    doc.add_numbered_list_item("Sub-item B", 1);
327    doc.add_numbered_list_item("Third numbered item", 0);
328
329    // ── SECTION 4: TAB STOPS ──
330    doc.add_paragraph("");
331    doc.add_paragraph("4. Tab Stops").style("Heading1");
332
333    doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
334    doc.add_paragraph("Left\tCenter\tRight\tDecimal")
335        .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
336        .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
337        .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
338        .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
339
340    doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
341    doc.add_paragraph("Item\t\tPrice")
342        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
343        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
344        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
345    doc.add_paragraph("Widget\t\t$19.99")
346        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
347        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
348        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
349    doc.add_paragraph("Gadget\t\t$249.50")
350        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
351        .add_tab_stop_with_leader(
352            TabAlignment::Right,
353            Length::inches(4.0),
354            TabLeader::Underscore,
355        )
356        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
357
358    // ── SECTION 5: TABLES ──
359    doc.add_paragraph("").page_break_before(true);
360    doc.add_paragraph("5. Tables").style("Heading1");
361
362    doc.add_paragraph("5.1 Basic Table").style("Heading2");
363    {
364        let mut tbl = doc
365            .add_table(4, 3)
366            .borders(BorderStyle::Single, 4, "000000");
367        for col in 0..3 {
368            tbl.cell(0, col).unwrap().shading("2E75B6");
369        }
370        tbl.cell(0, 0).unwrap().set_text("Name");
371        tbl.cell(0, 1).unwrap().set_text("Role");
372        tbl.cell(0, 2).unwrap().set_text("Location");
373        tbl.cell(1, 0).unwrap().set_text("Walter White");
374        tbl.cell(1, 1).unwrap().set_text("CEO");
375        tbl.cell(1, 2).unwrap().set_text("Albuquerque");
376        tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
377        tbl.cell(2, 1).unwrap().set_text("CTO");
378        tbl.cell(2, 2).unwrap().set_text("Remote");
379        tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
380        tbl.cell(3, 1).unwrap().set_text("Security");
381        tbl.cell(3, 2).unwrap().set_text("Washington");
382    }
383
384    doc.add_paragraph("");
385
386    doc.add_paragraph("5.2 Column Span & Cell Shading")
387        .style("Heading2");
388    {
389        let mut tbl = doc
390            .add_table(3, 4)
391            .borders(BorderStyle::Single, 4, "000000")
392            .width_pct(100.0);
393        tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
394        tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
395        tbl.cell(1, 0).unwrap().set_text("Region");
396        tbl.cell(1, 0).unwrap().shading("D6E4F0");
397        tbl.cell(1, 1).unwrap().set_text("Q1");
398        tbl.cell(1, 1).unwrap().shading("D6E4F0");
399        tbl.cell(1, 2).unwrap().set_text("Q2");
400        tbl.cell(1, 2).unwrap().shading("D6E4F0");
401        tbl.cell(1, 3).unwrap().set_text("Total");
402        tbl.cell(1, 3).unwrap().shading("D6E4F0");
403        tbl.cell(2, 0).unwrap().set_text("Americas");
404        tbl.cell(2, 1).unwrap().set_text("$2.4M");
405        tbl.cell(2, 2).unwrap().set_text("$2.7M");
406        tbl.cell(2, 3).unwrap().set_text("$5.1M");
407    }
408
409    doc.add_paragraph("");
410
411    doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
412    {
413        let mut tbl = doc
414            .add_table(4, 3)
415            .borders(BorderStyle::Single, 4, "000000");
416        tbl.cell(0, 0).unwrap().set_text("Category");
417        tbl.cell(0, 0).unwrap().shading("E2EFDA");
418        tbl.cell(0, 1).unwrap().set_text("Item");
419        tbl.cell(0, 1).unwrap().shading("E2EFDA");
420        tbl.cell(0, 2).unwrap().set_text("Price");
421        tbl.cell(0, 2).unwrap().shading("E2EFDA");
422        tbl.cell(1, 0).unwrap().set_text("Hardware");
423        tbl.cell(1, 0).unwrap().v_merge_restart();
424        tbl.cell(1, 1).unwrap().set_text("Laptop");
425        tbl.cell(1, 2).unwrap().set_text("$1,200");
426        tbl.cell(2, 0).unwrap().v_merge_continue();
427        tbl.cell(2, 1).unwrap().set_text("Monitor");
428        tbl.cell(2, 2).unwrap().set_text("$450");
429        tbl.cell(3, 0).unwrap().set_text("Software");
430        tbl.cell(3, 1).unwrap().set_text("IDE License");
431        tbl.cell(3, 2).unwrap().set_text("$200/yr");
432    }
433
434    doc.add_paragraph("");
435
436    doc.add_paragraph("5.4 Nested Table").style("Heading2");
437    {
438        let mut tbl = doc
439            .add_table(2, 2)
440            .borders(BorderStyle::Single, 6, "2E75B6");
441        tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
442        tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
443        tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
444        {
445            let mut cell = tbl.cell(1, 1).unwrap();
446            cell.set_text("Nested table below:");
447            let mut nested = cell
448                .add_table(2, 2)
449                .borders(BorderStyle::Single, 2, "FF6600");
450            nested.cell(0, 0).unwrap().set_text("A");
451            nested.cell(0, 1).unwrap().set_text("B");
452            nested.cell(1, 0).unwrap().set_text("C");
453            nested.cell(1, 1).unwrap().set_text("D");
454        }
455    }
456
457    doc.add_paragraph("");
458
459    doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
460        .style("Heading2");
461    {
462        let mut tbl = doc
463            .add_table(2, 3)
464            .borders(BorderStyle::Single, 4, "666666");
465        tbl.row(0).unwrap().height(Length::pt(50.0)).header();
466        tbl.cell(0, 0).unwrap().set_text("Top");
467        tbl.cell(0, 0)
468            .unwrap()
469            .vertical_alignment(VerticalAlignment::Top)
470            .shading("FFF2CC");
471        tbl.cell(0, 1).unwrap().set_text("Center");
472        tbl.cell(0, 1)
473            .unwrap()
474            .vertical_alignment(VerticalAlignment::Center)
475            .shading("D9E2F3");
476        tbl.cell(0, 2).unwrap().set_text("Bottom");
477        tbl.cell(0, 2)
478            .unwrap()
479            .vertical_alignment(VerticalAlignment::Bottom)
480            .shading("E2EFDA");
481        tbl.row(1).unwrap().cant_split();
482        tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
483        tbl.cell(1, 0).unwrap().no_wrap();
484        tbl.cell(1, 1).unwrap().set_text("Fixed width");
485        tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
486        tbl.cell(1, 2).unwrap().set_text("Normal");
487    }
488
489    // ── SECTION 6: IMAGES ──
490    doc.add_paragraph("").page_break_before(true);
491    doc.add_paragraph("6. Images").style("Heading1");
492
493    doc.add_paragraph("6.1 Inline Image").style("Heading2");
494    doc.add_paragraph("A blue gradient image below:");
495    let img = create_sample_png(200, 50, [0, 80, 200]);
496    doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
497
498    doc.add_paragraph("");
499    doc.add_paragraph("6.2 Header Image").style("Heading2");
500    let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
501    doc.set_header_image(
502        &hdr_img,
503        "header_logo.png",
504        Length::inches(2.0),
505        Length::inches(0.2),
506    );
507    doc.add_paragraph("The header now contains an inline image (check the top of this page).");
508
509    doc.add_paragraph("");
510    doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
511
512    // ── SECTION 7: CONTENT MANIPULATION ──
513    doc.add_paragraph("").page_break_before(true);
514    doc.add_paragraph("7. Content Manipulation")
515        .style("Heading1");
516
517    doc.add_paragraph("7.1 Placeholder Replacement")
518        .style("Heading2");
519    doc.add_paragraph("Customer: {{customer}}");
520    doc.add_paragraph("Date: {{date}}");
521    doc.add_paragraph("Reference: {{ref_number}}");
522    {
523        let mut tbl = doc
524            .add_table(2, 2)
525            .borders(BorderStyle::Single, 4, "000000");
526        tbl.cell(0, 0).unwrap().set_text("Project");
527        tbl.cell(0, 0).unwrap().shading("D6E4F0");
528        tbl.cell(0, 1).unwrap().set_text("{{project}}");
529        tbl.cell(1, 0).unwrap().set_text("Status");
530        tbl.cell(1, 0).unwrap().shading("D6E4F0");
531        tbl.cell(1, 1).unwrap().set_text("{{status}}");
532    }
533
534    let mut replacements = HashMap::new();
535    replacements.insert("{{customer}}", "Tensorbee Inc.");
536    replacements.insert("{{date}}", "February 22, 2026");
537    replacements.insert("{{ref_number}}", "TB-2026-001");
538    replacements.insert("{{project}}", "Infrastructure Upgrade");
539    replacements.insert("{{status}}", "In Progress");
540    let n = doc.replace_all(&replacements);
541    doc.add_paragraph(&format!("({n} placeholders replaced above)"));
542
543    doc.add_paragraph("");
544
545    doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
546    doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
547    let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
548    doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
549
550    doc.add_paragraph("");
551
552    doc.add_paragraph("7.3 Content Insertion").style("Heading2");
553    doc.add_paragraph("Section A: First content.");
554    doc.add_paragraph("Section C: Third content.");
555    if let Some(idx) = doc.find_content_index("Section C") {
556        doc.insert_paragraph(
557            idx,
558            "Section B: Inserted between A and C via find_content_index().",
559        );
560    }
561
562    // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
563    doc.add_paragraph("").section_break(SectionBreak::NextPage);
564    doc.add_paragraph("8. Section Breaks & Orientation")
565        .style("Heading1");
566    doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
567
568    {
569        let mut tbl = doc
570            .add_table(3, 7)
571            .borders(BorderStyle::Single, 4, "2E75B6");
572        let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
573        for (c, h) in headers.iter().enumerate() {
574            tbl.cell(0, c).unwrap().set_text(h);
575            tbl.cell(0, c).unwrap().shading("2E75B6");
576        }
577        let data = [
578            [
579                "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
580            ],
581            [
582                "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
583            ],
584        ];
585        for (r, row) in data.iter().enumerate() {
586            for (c, v) in row.iter().enumerate() {
587                tbl.cell(r + 1, c).unwrap().set_text(v);
588            }
589        }
590    }
591
592    doc.add_paragraph("")
593        .section_break(SectionBreak::NextPage)
594        .section_landscape();
595
596    // ── SECTION 9: CUSTOM STYLES ──
597    doc.add_paragraph("9. Custom Styles").style("Heading1");
598    doc.add_style(
599        StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
600            .based_on("Normal")
601            .paragraph_properties(rdocx_oxml::properties::CT_PPr {
602                shading: Some(rdocx_oxml::properties::CT_Shd {
603                    val: "clear".to_string(),
604                    color: None,
605                    fill: Some("FFF2CC".to_string()),
606                }),
607                ..Default::default()
608            })
609            .run_properties(rdocx_oxml::properties::CT_RPr {
610                bold: Some(true),
611                color: Some("C45911".to_string()),
612                ..Default::default()
613            }),
614    );
615    doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
616        .style("CustomHighlight");
617
618    doc.add_paragraph("");
619
620    // ── SECTION 10: DOCUMENT INTELLIGENCE ──
621    doc.add_paragraph("10. Document Intelligence API")
622        .style("Heading1");
623
624    let wc = doc.word_count();
625    let hc = doc.headings().len();
626    let ic = doc.images().len();
627    let lc = doc.links().len();
628    doc.add_paragraph(&format!("Word count: {wc}"));
629    doc.add_paragraph(&format!("Heading count: {hc}"));
630    doc.add_paragraph(&format!("Image count: {ic}"));
631    doc.add_paragraph(&format!("Link count: {lc}"));
632
633    let outline = doc.document_outline();
634    doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
635
636    let issues = doc.audit_accessibility();
637    doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
638    for issue in &issues {
639        doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
640    }
641
642    // ── SECTION 11: DOCUMENT MERGING ──
643    doc.add_paragraph("");
644    doc.add_paragraph("11. Document Merging").style("Heading1");
645    {
646        let mut other = Document::new();
647        other.add_paragraph("This paragraph was merged from another document using append().");
648        doc.append(&other);
649    }
650
651    doc.add_paragraph("");
652
653    // ── SUMMARY ──
654    doc.add_paragraph("Summary of Demonstrated Features")
655        .style("Heading1");
656    let features = [
657        "Page setup: size, margins, header/footer distance, gutter",
658        "Document metadata: title, author, subject, keywords",
659        "Headers and footers: text, images, different first page",
660        "Background images (full-page behind text)",
661        "Table of Contents generation with bookmarks",
662        "Text formatting: bold, italic, underline styles, strike, color, size, font",
663        "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
664        "Paragraph formatting: alignment, borders, shading, spacing, indentation",
665        "Pagination controls: keep-with-next, keep-together, widow control",
666        "Bullet and numbered lists with nesting",
667        "Tab stops with dot/underscore/hyphen leaders",
668        "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
669        "Row properties: header rows, exact height, cant-split",
670        "Cell properties: width, no-wrap, vertical alignment",
671        "Inline images and header images",
672        "Placeholder replacement (single and batch)",
673        "Regex-based find and replace",
674        "Content insertion at specific positions",
675        "Section breaks with mixed portrait/landscape",
676        "Custom paragraph and character styles",
677        "Document intelligence: word count, headings, outline, images, links",
678        "Accessibility audit",
679        "Document merging (append)",
680        "Export: DOCX, PDF, HTML, Markdown",
681    ];
682    for f in &features {
683        doc.add_bullet_list_item(f, 0);
684    }
685    doc.add_paragraph("");
686    doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
687        .alignment(Alignment::Center)
688        .shading("E2EFDA")
689        .border_all(BorderStyle::Single, 2, "00AA00");
690
691    doc
692}
693
694// =============================================================================
695// 2. PROPOSAL DOCUMENT — Deep navy + gold scheme
696// =============================================================================
697fn generate_proposal(_samples_dir: &Path) -> Document {
698    let mut doc = Document::new();
699
700    // Colors: Navy #1B2A4A, Gold #C5922E, Light #F4F1EB
701    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
702    doc.set_margins(
703        Length::inches(1.0),
704        Length::inches(1.0),
705        Length::inches(1.0),
706        Length::inches(1.0),
707    );
708    doc.set_title("AI Platform Modernization Proposal");
709    doc.set_author("Walter White");
710    doc.set_subject("Technology Proposal");
711    doc.set_keywords("proposal, AI, modernization, Tensorbee");
712
713    // Banner header
714    let logo_img = create_logo_png(220, 48);
715    let banner = build_header_banner_xml(
716        "rId1",
717        &BannerOpts {
718            bg_color: "1B2A4A",
719            banner_width: 7772400,
720            banner_height: 914400,
721            logo_width: 2011680,
722            logo_height: 438912,
723            logo_x_offset: 295125,
724            logo_y_offset: 237744,
725        },
726    );
727    doc.set_raw_header_with_images(
728        banner,
729        &[("rId1", &logo_img, "logo.png")],
730        rdocx_oxml::header_footer::HdrFtrType::Default,
731    );
732    doc.set_different_first_page(true);
733    let cover_banner = build_header_banner_xml(
734        "rId1",
735        &BannerOpts {
736            bg_color: "C5922E",
737            banner_width: 7772400,
738            banner_height: 914400,
739            logo_width: 2011680,
740            logo_height: 438912,
741            logo_x_offset: 295125,
742            logo_y_offset: 237744,
743        },
744    );
745    doc.set_raw_header_with_images(
746        cover_banner,
747        &[("rId1", &logo_img, "logo.png")],
748        rdocx_oxml::header_footer::HdrFtrType::First,
749    );
750    doc.set_margins(
751        Length::twips(2292),
752        Length::twips(1440),
753        Length::twips(1440),
754        Length::twips(1440),
755    );
756    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
757    doc.set_footer("Tensorbee — Confidential");
758
759    // Custom styles
760    doc.add_style(
761        StyleBuilder::paragraph("ProposalTitle", "Proposal Title")
762            .based_on("Normal")
763            .run_properties(rdocx_oxml::properties::CT_RPr {
764                bold: Some(true),
765                font_ascii: Some("Georgia".to_string()),
766                font_hansi: Some("Georgia".to_string()),
767                sz: Some(rdocx_oxml::HalfPoint(56)), // half-points → 28pt
768                color: Some("1B2A4A".to_string()),
769                ..Default::default()
770            }),
771    );
772
773    // ── Cover Page ──
774    for _ in 0..4 {
775        doc.add_paragraph("");
776    }
777    doc.add_paragraph("AI Platform Modernization")
778        .style("ProposalTitle")
779        .alignment(Alignment::Center);
780    doc.add_paragraph("")
781        .alignment(Alignment::Center)
782        .border_bottom(BorderStyle::Single, 8, "C5922E");
783    {
784        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
785        p.add_run("Prepared for Global Financial Corp.")
786            .size(14.0)
787            .color("666666")
788            .italic(true);
789    }
790    {
791        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
792        p.add_run("Prepared by: Walter White, CEO — Tensorbee")
793            .size(12.0)
794            .color("888888");
795    }
796    {
797        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
798        p.add_run("February 22, 2026").size(12.0).color("888888");
799    }
800
801    // ── TOC ──
802    doc.add_paragraph("").page_break_before(true);
803    doc.add_paragraph("Table of Contents").style("Heading1");
804    // We'll add a manual-style TOC since the insert_toc goes at a specific position
805    doc.insert_toc(doc.content_count(), 2);
806
807    // ── Executive Summary ──
808    doc.add_paragraph("").page_break_before(true);
809    doc.add_paragraph("Executive Summary").style("Heading1");
810    doc.add_paragraph(
811        "Tensorbee proposes a comprehensive modernization of Global Financial Corp's AI platform, \
812         transitioning from legacy batch-processing systems to a real-time inference architecture. \
813         This transformation will reduce model deployment time from weeks to hours, improve \
814         prediction accuracy by an estimated 23%, and deliver $4.2M in annual operational savings.",
815    )
816    .first_line_indent(Length::inches(0.3));
817    doc.add_paragraph(
818        "The project spans three phases over 18 months, with a total investment of $2.8M. \
819         Tensorbee brings deep expertise in ML infrastructure, having successfully completed \
820         similar transformations for three Fortune 500 financial institutions.",
821    )
822    .first_line_indent(Length::inches(0.3));
823
824    // Key metrics table
825    doc.add_paragraph("");
826    {
827        let mut tbl = doc
828            .add_table(5, 2)
829            .borders(BorderStyle::Single, 4, "1B2A4A")
830            .width_pct(80.0);
831        tbl.cell(0, 0).unwrap().set_text("Key Metric");
832        tbl.cell(0, 0).unwrap().shading("1B2A4A");
833        tbl.cell(0, 1).unwrap().set_text("Value");
834        tbl.cell(0, 1).unwrap().shading("1B2A4A");
835        let rows = [
836            ("Total Investment", "$2.8M"),
837            ("Annual Savings", "$4.2M"),
838            ("ROI (Year 1)", "150%"),
839            ("Timeline", "18 months"),
840        ];
841        for (i, (k, v)) in rows.iter().enumerate() {
842            tbl.cell(i + 1, 0).unwrap().set_text(k);
843            if i % 2 == 0 {
844                tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
845            }
846            tbl.cell(i + 1, 1).unwrap().set_text(v);
847            if i % 2 == 0 {
848                tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
849            }
850        }
851    }
852
853    // ── Problem Statement ──
854    doc.add_paragraph("").page_break_before(true);
855    doc.add_paragraph("Problem Statement").style("Heading1");
856    doc.add_paragraph(
857        "Global Financial Corp's current AI infrastructure faces three critical challenges:",
858    );
859    doc.add_numbered_list_item("Deployment Latency — New models take 3-4 weeks to deploy to production, compared to the industry benchmark of 2-3 days.", 0);
860    doc.add_numbered_list_item("Scalability Constraints — The batch processing architecture cannot handle real-time inference demands during peak trading hours.", 0);
861    doc.add_numbered_list_item("Technical Debt — Legacy Python 2.7 codebases and manual deployment scripts create reliability risks and slow iteration speed.", 0);
862
863    // ── Proposed Solution ──
864    doc.add_paragraph("").page_break_before(true);
865    doc.add_paragraph("Proposed Solution").style("Heading1");
866
867    doc.add_paragraph("Phase 1: Foundation (Months 1-6)")
868        .style("Heading2");
869    doc.add_bullet_list_item("Deploy Kubernetes-based ML serving infrastructure", 0);
870    doc.add_bullet_list_item("Implement CI/CD pipeline for model deployment", 0);
871    doc.add_bullet_list_item("Migrate top 5 production models to new platform", 0);
872
873    doc.add_paragraph("Phase 2: Expansion (Months 7-12)")
874        .style("Heading2");
875    doc.add_bullet_list_item(
876        "Real-time feature engineering pipeline (Apache Kafka + Flink)",
877        0,
878    );
879    doc.add_bullet_list_item("A/B testing framework for model variants", 0);
880    doc.add_bullet_list_item("Automated model monitoring and drift detection", 0);
881
882    doc.add_paragraph("Phase 3: Optimization (Months 13-18)")
883        .style("Heading2");
884    doc.add_bullet_list_item("GPU inference optimization (TensorRT/ONNX Runtime)", 0);
885    doc.add_bullet_list_item("Multi-region deployment for latency reduction", 0);
886    doc.add_bullet_list_item(
887        "Self-service model deployment portal for data science teams",
888        0,
889    );
890
891    // ── Budget ──
892    doc.add_paragraph("Budget Breakdown").style("Heading1");
893    {
894        let mut tbl = doc
895            .add_table(6, 3)
896            .borders(BorderStyle::Single, 4, "1B2A4A")
897            .width_pct(100.0);
898        tbl.cell(0, 0).unwrap().set_text("Category");
899        tbl.cell(0, 0).unwrap().shading("1B2A4A");
900        tbl.cell(0, 1).unwrap().set_text("Cost");
901        tbl.cell(0, 1).unwrap().shading("1B2A4A");
902        tbl.cell(0, 2).unwrap().set_text("Notes");
903        tbl.cell(0, 2).unwrap().shading("1B2A4A");
904        let items = [
905            (
906                "Infrastructure",
907                "$450,000",
908                "Cloud compute + GPU instances",
909            ),
910            ("Engineering Services", "$1,600,000", "12 FTE x 18 months"),
911            (
912                "Software Licenses",
913                "$350,000",
914                "Kafka, monitoring, CI/CD tools",
915            ),
916            (
917                "Training & Enablement",
918                "$200,000",
919                "Team training, documentation",
920            ),
921            ("Contingency (10%)", "$200,000", "Risk buffer"),
922        ];
923        for (i, (cat, cost, note)) in items.iter().enumerate() {
924            tbl.cell(i + 1, 0).unwrap().set_text(cat);
925            tbl.cell(i + 1, 1).unwrap().set_text(cost);
926            tbl.cell(i + 1, 2).unwrap().set_text(note);
927            if i % 2 == 0 {
928                tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
929                tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
930                tbl.cell(i + 1, 2).unwrap().shading("F4F1EB");
931            }
932        }
933    }
934
935    // ── Closing ──
936    doc.add_paragraph("");
937    doc.add_paragraph("Next Steps").style("Heading1");
938    doc.add_numbered_list_item(
939        "Schedule technical deep-dive with GFC engineering team (Week 1)",
940        0,
941    );
942    doc.add_numbered_list_item("Finalize scope and sign SOW (Week 2-3)", 0);
943    doc.add_numbered_list_item("Kick off Phase 1 with joint planning session (Week 4)", 0);
944
945    doc.add_paragraph("");
946    {
947        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
948        p.add_run("Walter White")
949            .bold(true)
950            .size(14.0)
951            .color("1B2A4A");
952    }
953    {
954        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
955        p.add_run("CEO, Tensorbee | walter@tensorbee.com")
956            .size(10.0)
957            .color("888888");
958    }
959
960    doc
961}
962
963// =============================================================================
964// 3. QUOTE / BILL OF MATERIALS — Teal + orange scheme
965// =============================================================================
966fn generate_quote(_samples_dir: &Path) -> Document {
967    let mut doc = Document::new();
968
969    // Colors: Teal #008B8B, Dark #1A3C3C, Orange #E07020, Light #F0F8F8
970    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
971    doc.set_margins(
972        Length::inches(0.75),
973        Length::inches(0.75),
974        Length::inches(0.75),
975        Length::inches(0.75),
976    );
977    doc.set_title("Quotation QT-2026-0147");
978    doc.set_author("Walter White");
979    doc.set_keywords("quote, BOM, Tensorbee");
980
981    doc.set_header("Tensorbee — Quotation");
982    doc.set_footer("QT-2026-0147 | Page");
983
984    // ── Header Block ──
985    {
986        let mut p = doc.add_paragraph("").alignment(Alignment::Left);
987        p.add_run("TENSORBEE")
988            .bold(true)
989            .size(28.0)
990            .color("008B8B")
991            .font("Helvetica");
992    }
993    doc.add_paragraph("123 Innovation Drive, Suite 400")
994        .alignment(Alignment::Left);
995    doc.add_paragraph("San Francisco, CA 94105 | +1 (415) 555-0199")
996        .alignment(Alignment::Left);
997    doc.add_paragraph("")
998        .border_bottom(BorderStyle::Single, 8, "008B8B");
999
1000    // Quote meta
1001    doc.add_paragraph("");
1002    {
1003        let mut tbl = doc.add_table(4, 4).width_pct(100.0);
1004        tbl.cell(0, 0).unwrap().set_text("QUOTATION");
1005        tbl.cell(0, 0).unwrap().shading("008B8B").grid_span(2);
1006        tbl.cell(0, 2).unwrap().set_text("DATE");
1007        tbl.cell(0, 2).unwrap().shading("008B8B");
1008        tbl.cell(0, 3).unwrap().set_text("VALID UNTIL");
1009        tbl.cell(0, 3).unwrap().shading("008B8B");
1010        tbl.cell(1, 0).unwrap().set_text("Quote #:");
1011        tbl.cell(1, 1).unwrap().set_text("QT-2026-0147");
1012        tbl.cell(1, 2).unwrap().set_text("Feb 22, 2026");
1013        tbl.cell(1, 3).unwrap().set_text("Mar 22, 2026");
1014        tbl.cell(2, 0).unwrap().set_text("Prepared by:");
1015        tbl.cell(2, 1).unwrap().set_text("Walter White");
1016        tbl.cell(2, 2).unwrap().set_text("Payment:");
1017        tbl.cell(2, 3).unwrap().set_text("Net 30");
1018        tbl.cell(3, 0).unwrap().set_text("Customer:");
1019        tbl.cell(3, 1).unwrap().set_text("Meridian Dynamics LLC");
1020        tbl.cell(3, 2).unwrap().set_text("Currency:");
1021        tbl.cell(3, 3).unwrap().set_text("USD");
1022    }
1023
1024    doc.add_paragraph("");
1025
1026    // ── Bill of Materials ──
1027    {
1028        let mut p = doc.add_paragraph("");
1029        p.add_run("Bill of Materials")
1030            .bold(true)
1031            .size(16.0)
1032            .color("1A3C3C");
1033    }
1034    doc.add_paragraph("");
1035
1036    {
1037        let mut tbl = doc
1038            .add_table(10, 6)
1039            .borders(BorderStyle::Single, 2, "008B8B")
1040            .width_pct(100.0)
1041            .layout_fixed();
1042        // Headers
1043        let hdrs = [
1044            "#",
1045            "Part Number",
1046            "Description",
1047            "Qty",
1048            "Unit Price",
1049            "Total",
1050        ];
1051        for (c, h) in hdrs.iter().enumerate() {
1052            tbl.cell(0, c).unwrap().set_text(h);
1053            tbl.cell(0, c).unwrap().shading("008B8B");
1054        }
1055
1056        let items: Vec<(&str, &str, &str, &str, &str)> = vec![
1057            (
1058                "TB-GPU-A100",
1059                "NVIDIA A100 80GB GPU",
1060                "4",
1061                "$12,500.00",
1062                "$50,000.00",
1063            ),
1064            (
1065                "TB-SRV-R750",
1066                "Dell R750xa Server Chassis",
1067                "2",
1068                "$8,200.00",
1069                "$16,400.00",
1070            ),
1071            (
1072                "TB-RAM-256G",
1073                "256GB DDR5 ECC Memory Module",
1074                "8",
1075                "$890.00",
1076                "$7,120.00",
1077            ),
1078            (
1079                "TB-SSD-3840",
1080                "3.84TB NVMe U.2 SSD",
1081                "8",
1082                "$1,150.00",
1083                "$9,200.00",
1084            ),
1085            (
1086                "TB-NET-CX7",
1087                "ConnectX-7 200GbE NIC",
1088                "4",
1089                "$1,800.00",
1090                "$7,200.00",
1091            ),
1092            (
1093                "TB-SW-48P",
1094                "48-Port 100GbE Switch",
1095                "1",
1096                "$22,000.00",
1097                "$22,000.00",
1098            ),
1099            (
1100                "TB-CAB-RACK",
1101                "42U Server Rack + PDU",
1102                "1",
1103                "$4,500.00",
1104                "$4,500.00",
1105            ),
1106            (
1107                "TB-SVC-INST",
1108                "Installation & Configuration",
1109                "1",
1110                "$8,500.00",
1111                "$8,500.00",
1112            ),
1113            (
1114                "TB-SVC-SUPP",
1115                "3-Year Premium Support",
1116                "1",
1117                "$15,000.00",
1118                "$15,000.00",
1119            ),
1120        ];
1121
1122        for (i, (pn, desc, qty, unit, total)) in items.iter().enumerate() {
1123            let row = i + 1;
1124            tbl.cell(row, 0).unwrap().set_text(&format!("{}", i + 1));
1125            tbl.cell(row, 1).unwrap().set_text(pn);
1126            tbl.cell(row, 2).unwrap().set_text(desc);
1127            tbl.cell(row, 3).unwrap().set_text(qty);
1128            tbl.cell(row, 4).unwrap().set_text(unit);
1129            tbl.cell(row, 5).unwrap().set_text(total);
1130            if i % 2 == 0 {
1131                for c in 0..6 {
1132                    tbl.cell(row, c).unwrap().shading("F0F8F8");
1133                }
1134            }
1135        }
1136    }
1137
1138    doc.add_paragraph("");
1139
1140    // ── Totals ──
1141    {
1142        let mut tbl = doc
1143            .add_table(4, 2)
1144            .borders(BorderStyle::Single, 2, "008B8B")
1145            .width(Length::inches(3.5))
1146            .alignment(Alignment::Right);
1147        tbl.cell(0, 0).unwrap().set_text("Subtotal");
1148        tbl.cell(0, 1).unwrap().set_text("$139,920.00");
1149        tbl.cell(1, 0).unwrap().set_text("Shipping & Handling");
1150        tbl.cell(1, 1).unwrap().set_text("$2,500.00");
1151        tbl.cell(2, 0).unwrap().set_text("Tax (8.625%)");
1152        tbl.cell(2, 1).unwrap().set_text("$12,068.10");
1153        tbl.cell(3, 0).unwrap().set_text("TOTAL");
1154        tbl.cell(3, 0).unwrap().shading("E07020");
1155        tbl.cell(3, 1).unwrap().set_text("$154,488.10");
1156        tbl.cell(3, 1).unwrap().shading("E07020");
1157    }
1158
1159    doc.add_paragraph("");
1160
1161    // ── Terms & Conditions ──
1162    {
1163        let mut p = doc.add_paragraph("");
1164        p.add_run("Terms & Conditions")
1165            .bold(true)
1166            .size(14.0)
1167            .color("1A3C3C");
1168    }
1169    doc.add_numbered_list_item(
1170        "This quotation is valid for 30 calendar days from the date of issue.",
1171        0,
1172    );
1173    doc.add_numbered_list_item(
1174        "All prices are in USD and exclusive of applicable taxes unless stated.",
1175        0,
1176    );
1177    doc.add_numbered_list_item("Standard lead time is 4-6 weeks from PO receipt.", 0);
1178    doc.add_numbered_list_item(
1179        "Payment terms: Net 30 from invoice date. 2% discount for payment within 10 days.",
1180        0,
1181    );
1182    doc.add_numbered_list_item(
1183        "Warranty: 3-year manufacturer warranty on all hardware components.",
1184        0,
1185    );
1186    doc.add_numbered_list_item(
1187        "Returns subject to 15% restocking fee if initiated after 14 days.",
1188        0,
1189    );
1190
1191    doc.add_paragraph("");
1192    doc.add_paragraph("")
1193        .border_bottom(BorderStyle::Single, 4, "008B8B");
1194    doc.add_paragraph("");
1195    {
1196        let mut p = doc.add_paragraph("");
1197        p.add_run("Accepted by: ").bold(true);
1198        p.add_run("___________________________ Date: ___________");
1199    }
1200    {
1201        let mut p = doc.add_paragraph("");
1202        p.add_run("Print Name: ").bold(true);
1203        p.add_run("___________________________ Title: ___________");
1204    }
1205
1206    doc
1207}
1208
1209// =============================================================================
1210// 4. INVOICE — Crimson + charcoal scheme
1211// =============================================================================
1212fn generate_invoice(_samples_dir: &Path) -> Document {
1213    let mut doc = Document::new();
1214
1215    // Colors: Crimson #B22222, Charcoal #333333, Light #FAF0F0
1216    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1217    doc.set_margins(
1218        Length::inches(0.75),
1219        Length::inches(0.75),
1220        Length::inches(0.75),
1221        Length::inches(0.75),
1222    );
1223    doc.set_title("Invoice INV-2026-0392");
1224    doc.set_author("Walter White");
1225    doc.set_keywords("invoice, Tensorbee");
1226
1227    doc.set_footer("Tensorbee — Thank you for your business!");
1228
1229    // ── Company & Invoice Header ──
1230    {
1231        let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1232        // Company name left, INVOICE right
1233        {
1234            let mut cell = tbl.cell(0, 0).unwrap();
1235            let mut p = cell.add_paragraph("");
1236            p.add_run("TENSORBEE")
1237                .bold(true)
1238                .size(32.0)
1239                .color("B22222")
1240                .font("Helvetica");
1241        }
1242        {
1243            let mut cell = tbl.cell(0, 1).unwrap();
1244            let mut p = cell.add_paragraph("");
1245            p.add_run("INVOICE").bold(true).size(32.0).color("333333");
1246        }
1247        tbl.cell(1, 0)
1248            .unwrap()
1249            .set_text("123 Innovation Drive, Suite 400");
1250        tbl.cell(1, 1).unwrap().set_text("Invoice #: INV-2026-0392");
1251        tbl.cell(2, 0).unwrap().set_text("San Francisco, CA 94105");
1252        tbl.cell(2, 1).unwrap().set_text("Date: February 22, 2026");
1253        tbl.cell(3, 0).unwrap().set_text("walter@tensorbee.com");
1254        tbl.cell(3, 1).unwrap().set_text("Due Date: March 24, 2026");
1255    }
1256
1257    doc.add_paragraph("")
1258        .border_bottom(BorderStyle::Thick, 8, "B22222");
1259    doc.add_paragraph("");
1260
1261    // ── Bill To / Ship To ──
1262    {
1263        let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1264        tbl.cell(0, 0).unwrap().set_text("BILL TO");
1265        tbl.cell(0, 0).unwrap().shading("B22222");
1266        tbl.cell(0, 1).unwrap().set_text("SHIP TO");
1267        tbl.cell(0, 1).unwrap().shading("B22222");
1268        tbl.cell(1, 0).unwrap().set_text("Meridian Dynamics LLC");
1269        tbl.cell(1, 1).unwrap().set_text("Meridian Dynamics LLC");
1270        tbl.cell(2, 0).unwrap().set_text("456 Enterprise Blvd");
1271        tbl.cell(2, 1).unwrap().set_text("Attn: Server Room B");
1272        tbl.cell(3, 0).unwrap().set_text("Austin, TX 78701");
1273        tbl.cell(3, 1)
1274            .unwrap()
1275            .set_text("456 Enterprise Blvd, Austin, TX 78701");
1276    }
1277
1278    doc.add_paragraph("");
1279
1280    // ── Line Items ──
1281    {
1282        let mut tbl = doc
1283            .add_table(8, 5)
1284            .borders(BorderStyle::Single, 2, "B22222")
1285            .width_pct(100.0);
1286        let hdrs = ["Description", "Qty", "Unit Price", "Tax", "Amount"];
1287        for (c, h) in hdrs.iter().enumerate() {
1288            tbl.cell(0, c).unwrap().set_text(h);
1289            tbl.cell(0, c).unwrap().shading("B22222");
1290        }
1291        let items = [
1292            (
1293                "ML Infrastructure Setup — Phase 1",
1294                "1",
1295                "$45,000.00",
1296                "$3,881.25",
1297                "$48,881.25",
1298            ),
1299            (
1300                "Data Pipeline Development (160 hrs)",
1301                "160",
1302                "$225.00",
1303                "$3,105.00",
1304                "$39,105.00",
1305            ),
1306            (
1307                "GPU Cluster Configuration",
1308                "1",
1309                "$12,000.00",
1310                "$1,035.00",
1311                "$13,035.00",
1312            ),
1313            (
1314                "API Gateway Implementation",
1315                "1",
1316                "$18,500.00",
1317                "$1,595.63",
1318                "$20,095.63",
1319            ),
1320            (
1321                "Load Testing & QA (80 hrs)",
1322                "80",
1323                "$195.00",
1324                "$1,345.50",
1325                "$16,945.50",
1326            ),
1327            (
1328                "Documentation & Training",
1329                "1",
1330                "$8,000.00",
1331                "$690.00",
1332                "$8,690.00",
1333            ),
1334            (
1335                "Project Management (3 months)",
1336                "3",
1337                "$6,500.00",
1338                "$1,679.63",
1339                "$21,179.63",
1340            ),
1341        ];
1342        for (i, (desc, qty, unit, tax, amt)) in items.iter().enumerate() {
1343            let r = i + 1;
1344            tbl.cell(r, 0).unwrap().set_text(desc);
1345            tbl.cell(r, 1).unwrap().set_text(qty);
1346            tbl.cell(r, 2).unwrap().set_text(unit);
1347            tbl.cell(r, 3).unwrap().set_text(tax);
1348            tbl.cell(r, 4).unwrap().set_text(amt);
1349            if i % 2 == 0 {
1350                for c in 0..5 {
1351                    tbl.cell(r, c).unwrap().shading("FAF0F0");
1352                }
1353            }
1354        }
1355    }
1356
1357    doc.add_paragraph("");
1358
1359    // ── Totals ──
1360    {
1361        let mut tbl = doc
1362            .add_table(4, 2)
1363            .borders(BorderStyle::Single, 2, "B22222")
1364            .width(Length::inches(3.0))
1365            .alignment(Alignment::Right);
1366        tbl.cell(0, 0).unwrap().set_text("Subtotal");
1367        tbl.cell(0, 1).unwrap().set_text("$154,600.00");
1368        tbl.cell(1, 0).unwrap().set_text("Tax (8.625%)");
1369        tbl.cell(1, 1).unwrap().set_text("$13,334.25");
1370        tbl.cell(2, 0).unwrap().set_text("Discount (5%)");
1371        tbl.cell(2, 1).unwrap().set_text("-$7,730.00");
1372        tbl.cell(3, 0).unwrap().set_text("AMOUNT DUE");
1373        tbl.cell(3, 0).unwrap().shading("B22222");
1374        tbl.cell(3, 1).unwrap().set_text("$160,204.25");
1375        tbl.cell(3, 1).unwrap().shading("B22222");
1376    }
1377
1378    doc.add_paragraph("");
1379    doc.add_paragraph("");
1380
1381    // ── Payment Details ──
1382    {
1383        let mut p = doc.add_paragraph("");
1384        p.add_run("Payment Details")
1385            .bold(true)
1386            .size(14.0)
1387            .color("333333");
1388    }
1389    doc.add_paragraph("Bank: Silicon Valley Bank")
1390        .indent_left(Length::inches(0.3));
1391    doc.add_paragraph("Account: Tensorbee Inc. — 0847-2953-1120")
1392        .indent_left(Length::inches(0.3));
1393    doc.add_paragraph("Routing: 121140399")
1394        .indent_left(Length::inches(0.3));
1395    doc.add_paragraph("Swift: SVBKUS6S")
1396        .indent_left(Length::inches(0.3));
1397
1398    doc.add_paragraph("");
1399
1400    doc.add_paragraph("Please include invoice number INV-2026-0392 in the payment reference.")
1401        .shading("FAF0F0")
1402        .border_all(BorderStyle::Single, 2, "B22222");
1403
1404    doc
1405}
1406
1407// =============================================================================
1408// 5. REPORT — Forest green + earth tones, with images & hierarchical sections
1409// =============================================================================
1410fn generate_report(_samples_dir: &Path) -> Document {
1411    let mut doc = Document::new();
1412
1413    // Colors: Forest #2D5016, Sage #6B8E23, Earth #8B7355, Cream #FFFAF0
1414    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1415    doc.set_margins(
1416        Length::inches(1.0),
1417        Length::inches(1.0),
1418        Length::inches(1.0),
1419        Length::inches(1.0),
1420    );
1421    doc.set_title("Q4 2025 Environmental Impact Report");
1422    doc.set_author("Walter White");
1423    doc.set_subject("Quarterly Environmental Report");
1424    doc.set_keywords("environment, sustainability, report, Tensorbee");
1425
1426    doc.set_different_first_page(true);
1427    doc.set_first_page_header("");
1428    doc.set_header("Tensorbee — Q4 2025 Environmental Impact Report");
1429    doc.set_footer("CONFIDENTIAL — Page");
1430
1431    // ── Cover ──
1432    let cover_bg = create_sample_png(612, 792, [20, 50, 15]);
1433    doc.add_background_image(&cover_bg, "report_cover.png");
1434
1435    for _ in 0..5 {
1436        doc.add_paragraph("");
1437    }
1438    {
1439        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1440        p.add_run("Q4 2025")
1441            .bold(true)
1442            .size(48.0)
1443            .color("FFFFFF")
1444            .font("Georgia");
1445    }
1446    {
1447        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1448        p.add_run("Environmental Impact Report")
1449            .size(24.0)
1450            .color("90EE90")
1451            .font("Georgia")
1452            .italic(true);
1453    }
1454    doc.add_paragraph("");
1455    {
1456        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1457        p.add_run("Tensorbee — Sustainability Division")
1458            .size(14.0)
1459            .color("C0C0C0");
1460    }
1461    {
1462        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1463        p.add_run("Prepared by Walter White, Chief Sustainability Officer")
1464            .size(11.0)
1465            .color("AAAAAA");
1466    }
1467
1468    // ── TOC ──
1469    doc.add_paragraph("").page_break_before(true);
1470    doc.insert_toc(doc.content_count(), 3);
1471
1472    // ── Section 1: Executive Overview ──
1473    doc.add_paragraph("").page_break_before(true);
1474    doc.add_paragraph("Executive Overview").style("Heading1");
1475    doc.add_paragraph(
1476        "This report presents Tensorbee's environmental performance for Q4 2025. Our \
1477         sustainability initiatives have yielded a 34% reduction in carbon emissions compared \
1478         to Q4 2024, exceeding our target of 25%. Key achievements include the transition to \
1479         100% renewable energy in our primary data centers and the launch of our carbon offset \
1480         marketplace.",
1481    )
1482    .first_line_indent(Length::inches(0.3));
1483
1484    // Image: performance chart
1485    let chart_img = create_chart_png(400, 200);
1486    doc.add_paragraph("");
1487    doc.add_picture(
1488        &chart_img,
1489        "performance_chart.png",
1490        Length::inches(5.0),
1491        Length::inches(2.5),
1492    )
1493    .alignment(Alignment::Center);
1494    {
1495        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1496        p.add_run("Figure 1: Quarterly Carbon Emissions (tonnes CO2e)")
1497            .italic(true)
1498            .size(9.0)
1499            .color("666666");
1500    }
1501
1502    // ── Section 2: Energy Consumption ──
1503    doc.add_paragraph("").page_break_before(true);
1504    doc.add_paragraph("Energy Consumption").style("Heading1");
1505
1506    doc.add_paragraph("Data Center Operations")
1507        .style("Heading2");
1508    doc.add_paragraph(
1509        "Our three primary data centers consumed a combined 4.2 GWh in Q4 2025, \
1510         a 12% reduction from Q3 2025 driven by improved cooling efficiency and \
1511         server consolidation.",
1512    );
1513
1514    // Data table
1515    {
1516        let mut tbl = doc
1517            .add_table(5, 4)
1518            .borders(BorderStyle::Single, 4, "2D5016")
1519            .width_pct(100.0);
1520        let hdrs = ["Data Center", "Capacity (MW)", "Usage (GWh)", "PUE"];
1521        for (c, h) in hdrs.iter().enumerate() {
1522            tbl.cell(0, c).unwrap().set_text(h);
1523            tbl.cell(0, c).unwrap().shading("2D5016");
1524        }
1525        let rows = [
1526            ("San Francisco (Primary)", "3.2", "1.8", "1.12"),
1527            ("Dublin (EU)", "2.1", "1.2", "1.18"),
1528            ("Singapore (APAC)", "1.8", "1.2", "1.24"),
1529            ("TOTAL", "7.1", "4.2", "1.17 avg"),
1530        ];
1531        for (i, (dc, cap, use_, pue)) in rows.iter().enumerate() {
1532            tbl.cell(i + 1, 0).unwrap().set_text(dc);
1533            tbl.cell(i + 1, 1).unwrap().set_text(cap);
1534            tbl.cell(i + 1, 2).unwrap().set_text(use_);
1535            tbl.cell(i + 1, 3).unwrap().set_text(pue);
1536            if i == 3 {
1537                for c in 0..4 {
1538                    tbl.cell(i + 1, c).unwrap().shading("E2EFDA");
1539                }
1540            }
1541        }
1542    }
1543
1544    doc.add_paragraph("");
1545
1546    doc.add_paragraph("Renewable Energy Mix").style("Heading2");
1547    doc.add_paragraph("Breakdown of energy sources across all facilities:");
1548
1549    doc.add_bullet_list_item("Solar PV: 42% (1.76 GWh)", 0);
1550    doc.add_bullet_list_item("Wind Power Purchase Agreements: 38% (1.60 GWh)", 0);
1551    doc.add_bullet_list_item("Hydroelectric: 12% (0.50 GWh)", 0);
1552    doc.add_bullet_list_item("Grid (non-renewable): 8% (0.34 GWh)", 0);
1553
1554    // ── Section 3: Water & Waste ──
1555    doc.add_paragraph("Water & Waste Management")
1556        .style("Heading1");
1557
1558    doc.add_paragraph("Water Usage").style("Heading2");
1559    doc.add_paragraph(
1560        "Total water consumption was 12.4 million gallons, a 15% reduction from Q3. \
1561         Our closed-loop cooling systems now recycle 78% of water used in cooling operations.",
1562    );
1563
1564    doc.add_paragraph("Waste Reduction").style("Heading2");
1565    doc.add_paragraph("E-waste management results for Q4:")
1566        .keep_with_next(true);
1567    {
1568        let mut tbl = doc
1569            .add_table(4, 3)
1570            .borders(BorderStyle::Single, 2, "6B8E23")
1571            .width_pct(80.0);
1572        tbl.cell(0, 0).unwrap().set_text("Category");
1573        tbl.cell(0, 0).unwrap().shading("6B8E23");
1574        tbl.cell(0, 1).unwrap().set_text("Weight (kg)");
1575        tbl.cell(0, 1).unwrap().shading("6B8E23");
1576        tbl.cell(0, 2).unwrap().set_text("Recycled %");
1577        tbl.cell(0, 2).unwrap().shading("6B8E23");
1578        let rows = [
1579            ("Server Hardware", "2,450", "94%"),
1580            ("Networking Equipment", "820", "91%"),
1581            ("Storage Media", "340", "99%"),
1582        ];
1583        for (i, (cat, wt, pct)) in rows.iter().enumerate() {
1584            tbl.cell(i + 1, 0).unwrap().set_text(cat);
1585            tbl.cell(i + 1, 1).unwrap().set_text(wt);
1586            tbl.cell(i + 1, 2).unwrap().set_text(pct);
1587        }
1588    }
1589
1590    // ── Section 4: Initiatives ──
1591    doc.add_paragraph("").page_break_before(true);
1592    doc.add_paragraph("Q1 2026 Initiatives").style("Heading1");
1593
1594    doc.add_paragraph("Planned Programs").style("Heading2");
1595    doc.add_numbered_list_item(
1596        "Deploy on-site battery storage at SF data center (2 MWh capacity)",
1597        0,
1598    );
1599    doc.add_numbered_list_item("Pilot immersion cooling in Dublin facility", 0);
1600    doc.add_numbered_list_item("Launch employee commute carbon offset program", 0);
1601    doc.add_numbered_list_item("Achieve ISO 14001 certification for Singapore facility", 0);
1602
1603    doc.add_paragraph("Investment Targets").style("Heading2");
1604    doc.add_paragraph("Sustainability CapEx allocation for FY2026:")
1605        .keep_with_next(true);
1606    {
1607        let mut tbl = doc
1608            .add_table(5, 2)
1609            .borders(BorderStyle::Single, 4, "2D5016");
1610        tbl.cell(0, 0).unwrap().set_text("Initiative");
1611        tbl.cell(0, 0).unwrap().shading("2D5016");
1612        tbl.cell(0, 1).unwrap().set_text("Budget");
1613        tbl.cell(0, 1).unwrap().shading("2D5016");
1614        let rows = [
1615            ("Battery Storage", "$1.2M"),
1616            ("Immersion Cooling Pilot", "$800K"),
1617            ("Solar Panel Expansion", "$2.1M"),
1618            ("Carbon Credits", "$500K"),
1619        ];
1620        for (i, (init, budget)) in rows.iter().enumerate() {
1621            tbl.cell(i + 1, 0).unwrap().set_text(init);
1622            tbl.cell(i + 1, 1).unwrap().set_text(budget);
1623            if i % 2 == 0 {
1624                tbl.cell(i + 1, 0).unwrap().shading("FFFAF0");
1625                tbl.cell(i + 1, 1).unwrap().shading("FFFAF0");
1626            }
1627        }
1628    }
1629
1630    // Image: sustainability roadmap
1631    let roadmap_img = create_sample_png(500, 100, [40, 80, 30]);
1632    doc.add_paragraph("");
1633    doc.add_picture(
1634        &roadmap_img,
1635        "roadmap.png",
1636        Length::inches(6.0),
1637        Length::inches(1.2),
1638    )
1639    .alignment(Alignment::Center);
1640    {
1641        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1642        p.add_run("Figure 2: 2026 Sustainability Roadmap")
1643            .italic(true)
1644            .size(9.0)
1645            .color("666666");
1646    }
1647
1648    doc.add_paragraph("");
1649    doc.add_paragraph(
1650        "For questions about this report, contact Walter White at sustainability@tensorbee.com.",
1651    )
1652    .shading("FFFAF0")
1653    .border_all(BorderStyle::Single, 2, "2D5016");
1654
1655    doc
1656}
Source

pub fn add_picture_auto( &mut self, image_data: &[u8], image_filename: &str, ) -> Result<Paragraph<'_>>

Add an inline image at its native size using 72 DPI when none is declared.

Returns an error without changing the document when the image dimensions cannot be determined.

Source

pub fn add_background_image( &mut self, image_data: &[u8], image_filename: &str, ) -> Paragraph<'_>

Add a full-page background image behind text.

The image is placed at position (0,0) relative to the page with dimensions matching the page size from section properties. It is inserted at the beginning of the document body so it renders behind all other content.

Examples found in repository?
examples/generate_all_samples.rs (line 124)
97fn generate_feature_showcase(_samples_dir: &Path) -> Document {
98    let mut doc = Document::new();
99
100    // ── Page Setup & Metadata ──
101    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
102    doc.set_margins(
103        Length::inches(1.0),
104        Length::inches(1.0),
105        Length::inches(1.0),
106        Length::inches(1.0),
107    );
108    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
109    doc.set_gutter(Length::twips(0));
110    doc.set_title("rdocx Feature Showcase");
111    doc.set_author("rdocx Sample Generator");
112    doc.set_subject("Comprehensive feature demonstration");
113    doc.set_keywords("rdocx, docx, rust, sample, showcase");
114
115    // Headers & Footers with different first page
116    doc.set_different_first_page(true);
117    doc.set_first_page_header("rdocx");
118    doc.set_first_page_footer("Feature Showcase — Cover Page");
119    doc.set_header("rdocx Feature Showcase");
120    doc.set_footer("Generated by rdocx");
121
122    // ── COVER PAGE ──
123    let bg = create_sample_png(612, 792, [20, 45, 90]);
124    doc.add_background_image(&bg, "cover_bg.png");
125
126    for _ in 0..3 {
127        doc.add_paragraph("");
128    }
129    {
130        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
131        p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
132    }
133    {
134        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
135        p.add_run("Complete Feature Showcase")
136            .size(28.0)
137            .color("FFFFFF")
138            .italic(true);
139    }
140    doc.add_paragraph("");
141    {
142        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
143        p.add_run("Every feature of the rdocx Rust library")
144            .size(13.0)
145            .color("B0C4DE");
146    }
147    {
148        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
149        p.add_run("demonstrated in a single document.")
150            .size(13.0)
151            .color("B0C4DE");
152    }
153
154    // ── TABLE OF CONTENTS ──
155    doc.add_paragraph("").page_break_before(true);
156    doc.insert_toc(doc.content_count(), 3);
157
158    // ── SECTION 1: TEXT FORMATTING ──
159    doc.add_paragraph("").page_break_before(true);
160    doc.add_paragraph("1. Text Formatting").style("Heading1");
161
162    // Paragraph Alignment
163    doc.add_paragraph("1.1 Paragraph Alignment")
164        .style("Heading2");
165    doc.add_paragraph("Left-aligned paragraph (default).")
166        .alignment(Alignment::Left);
167    doc.add_paragraph("Center-aligned paragraph.")
168        .alignment(Alignment::Center);
169    doc.add_paragraph("Right-aligned paragraph.")
170        .alignment(Alignment::Right);
171    doc.add_paragraph(
172        "Justified paragraph — this text is long enough to demonstrate how justified alignment \
173         distributes extra space across word gaps so lines fill the full width of the text area.",
174    )
175    .alignment(Alignment::Justify);
176
177    doc.add_paragraph("");
178
179    // Run Formatting
180    doc.add_paragraph("1.2 Run Formatting").style("Heading2");
181    {
182        let mut p = doc.add_paragraph("");
183        p.add_run("Bold").bold(true);
184        p.add_run(" | ");
185        p.add_run("Italic").italic(true);
186        p.add_run(" | ");
187        p.add_run("Bold Italic").bold(true).italic(true);
188        p.add_run(" | ");
189        p.add_run("Underline").underline(true);
190        p.add_run(" | ");
191        p.add_run("Strikethrough").strike(true);
192        p.add_run(" | ");
193        p.add_run("Double Strike").double_strike(true);
194    }
195    {
196        let mut p = doc.add_paragraph("");
197        p.add_run("Red").color("FF0000");
198        p.add_run(" | ");
199        p.add_run("Blue").color("0000FF");
200        p.add_run(" | ");
201        p.add_run("Green").color("00AA00");
202        p.add_run(" | ");
203        p.add_run("Highlighted").highlight("FFFF00");
204    }
205    {
206        let mut p = doc.add_paragraph("");
207        p.add_run("8pt").size(8.0);
208        p.add_run(" | ");
209        p.add_run("11pt").size(11.0);
210        p.add_run(" | ");
211        p.add_run("16pt").size(16.0);
212        p.add_run(" | ");
213        p.add_run("24pt").size(24.0);
214    }
215    {
216        let mut p = doc.add_paragraph("");
217        p.add_run("Arial").font("Arial");
218        p.add_run(" | ");
219        p.add_run("Times New Roman").font("Times New Roman");
220        p.add_run(" | ");
221        p.add_run("Courier New").font("Courier New");
222    }
223    {
224        let mut p = doc.add_paragraph("");
225        p.add_run("H");
226        p.add_run("2").subscript();
227        p.add_run("O (subscript) | E=mc");
228        p.add_run("2").superscript();
229        p.add_run(" (superscript)");
230    }
231    {
232        let mut p = doc.add_paragraph("");
233        p.add_run("ALL CAPS").all_caps(true);
234        p.add_run(" | ");
235        p.add_run("Small Caps").small_caps(true);
236        p.add_run(" | ");
237        p.add_run("Expanded").character_spacing(Length::twips(40));
238        p.add_run(" | ");
239        p.add_run("Hidden text (not visible)").hidden(true);
240    }
241
242    doc.add_paragraph("");
243
244    // Underline Styles
245    doc.add_paragraph("1.3 Underline Styles").style("Heading2");
246    {
247        let mut p = doc.add_paragraph("");
248        p.add_run("Single ")
249            .underline_style(rdocx::UnderlineStyle::Single);
250        p.add_run("Double ")
251            .underline_style(rdocx::UnderlineStyle::Double);
252        p.add_run("Thick ")
253            .underline_style(rdocx::UnderlineStyle::Thick);
254        p.add_run("Dotted ")
255            .underline_style(rdocx::UnderlineStyle::Dotted);
256        p.add_run("Dash ")
257            .underline_style(rdocx::UnderlineStyle::Dash);
258        p.add_run("Wave ")
259            .underline_style(rdocx::UnderlineStyle::Wave);
260    }
261
262    // ── SECTION 2: PARAGRAPH FORMATTING ──
263    doc.add_paragraph("").page_break_before(true);
264    doc.add_paragraph("2. Paragraph Formatting")
265        .style("Heading1");
266
267    doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
268    doc.add_paragraph("Paragraph with light green shading")
269        .shading("E2EFDA");
270    doc.add_paragraph("Paragraph with bottom border")
271        .border_bottom(BorderStyle::Single, 6, "2E75B6");
272    doc.add_paragraph("Paragraph with all borders (red)")
273        .border_all(BorderStyle::Single, 4, "FF0000");
274
275    doc.add_paragraph("2.2 Indentation").style("Heading2");
276    doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
277        .indent_left(Length::inches(1.0))
278        .hanging_indent(Length::inches(0.5));
279    doc.add_paragraph("0.5-inch first-line indent on this paragraph")
280        .first_line_indent(Length::inches(0.5));
281    doc.add_paragraph("Both left and right indent (0.75 inches each)")
282        .indent_left(Length::inches(0.75))
283        .indent_right(Length::inches(0.75));
284
285    doc.add_paragraph("2.3 Spacing & Line Height")
286        .style("Heading2");
287    doc.add_paragraph("Extra space before (24pt) and after (12pt)")
288        .space_before(Length::pt(24.0))
289        .space_after(Length::pt(12.0));
290    doc.add_paragraph(
291        "Double line spacing paragraph. This text should have extra vertical space between \
292         lines to demonstrate line_spacing_multiple(2.0).",
293    )
294    .line_spacing_multiple(2.0);
295    doc.add_paragraph("Exact 20pt line spacing (fixed height).")
296        .line_spacing(20.0);
297
298    doc.add_paragraph("2.4 Pagination Controls")
299        .style("Heading2");
300    doc.add_paragraph("keep_with_next — this paragraph stays with the next")
301        .keep_with_next(true);
302    doc.add_paragraph("(This paragraph was kept with the one above.)");
303    doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
304        .keep_together(true);
305    doc.add_paragraph("widow_control — prevents widow/orphan lines")
306        .widow_control(true);
307
308    // ── SECTION 3: LISTS ──
309    doc.add_paragraph("").page_break_before(true);
310    doc.add_paragraph("3. Lists").style("Heading1");
311
312    doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
313    doc.add_bullet_list_item("First item", 0);
314    doc.add_bullet_list_item("Second item", 0);
315    doc.add_bullet_list_item("Nested level 1", 1);
316    doc.add_bullet_list_item("Nested level 2", 2);
317    doc.add_bullet_list_item("Back to level 1", 1);
318    doc.add_bullet_list_item("Third item", 0);
319
320    doc.add_paragraph("");
321
322    doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
323    doc.add_numbered_list_item("First numbered item", 0);
324    doc.add_numbered_list_item("Second numbered item", 0);
325    doc.add_numbered_list_item("Sub-item A", 1);
326    doc.add_numbered_list_item("Sub-item B", 1);
327    doc.add_numbered_list_item("Third numbered item", 0);
328
329    // ── SECTION 4: TAB STOPS ──
330    doc.add_paragraph("");
331    doc.add_paragraph("4. Tab Stops").style("Heading1");
332
333    doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
334    doc.add_paragraph("Left\tCenter\tRight\tDecimal")
335        .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
336        .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
337        .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
338        .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
339
340    doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
341    doc.add_paragraph("Item\t\tPrice")
342        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
343        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
344        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
345    doc.add_paragraph("Widget\t\t$19.99")
346        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
347        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
348        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
349    doc.add_paragraph("Gadget\t\t$249.50")
350        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
351        .add_tab_stop_with_leader(
352            TabAlignment::Right,
353            Length::inches(4.0),
354            TabLeader::Underscore,
355        )
356        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
357
358    // ── SECTION 5: TABLES ──
359    doc.add_paragraph("").page_break_before(true);
360    doc.add_paragraph("5. Tables").style("Heading1");
361
362    doc.add_paragraph("5.1 Basic Table").style("Heading2");
363    {
364        let mut tbl = doc
365            .add_table(4, 3)
366            .borders(BorderStyle::Single, 4, "000000");
367        for col in 0..3 {
368            tbl.cell(0, col).unwrap().shading("2E75B6");
369        }
370        tbl.cell(0, 0).unwrap().set_text("Name");
371        tbl.cell(0, 1).unwrap().set_text("Role");
372        tbl.cell(0, 2).unwrap().set_text("Location");
373        tbl.cell(1, 0).unwrap().set_text("Walter White");
374        tbl.cell(1, 1).unwrap().set_text("CEO");
375        tbl.cell(1, 2).unwrap().set_text("Albuquerque");
376        tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
377        tbl.cell(2, 1).unwrap().set_text("CTO");
378        tbl.cell(2, 2).unwrap().set_text("Remote");
379        tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
380        tbl.cell(3, 1).unwrap().set_text("Security");
381        tbl.cell(3, 2).unwrap().set_text("Washington");
382    }
383
384    doc.add_paragraph("");
385
386    doc.add_paragraph("5.2 Column Span & Cell Shading")
387        .style("Heading2");
388    {
389        let mut tbl = doc
390            .add_table(3, 4)
391            .borders(BorderStyle::Single, 4, "000000")
392            .width_pct(100.0);
393        tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
394        tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
395        tbl.cell(1, 0).unwrap().set_text("Region");
396        tbl.cell(1, 0).unwrap().shading("D6E4F0");
397        tbl.cell(1, 1).unwrap().set_text("Q1");
398        tbl.cell(1, 1).unwrap().shading("D6E4F0");
399        tbl.cell(1, 2).unwrap().set_text("Q2");
400        tbl.cell(1, 2).unwrap().shading("D6E4F0");
401        tbl.cell(1, 3).unwrap().set_text("Total");
402        tbl.cell(1, 3).unwrap().shading("D6E4F0");
403        tbl.cell(2, 0).unwrap().set_text("Americas");
404        tbl.cell(2, 1).unwrap().set_text("$2.4M");
405        tbl.cell(2, 2).unwrap().set_text("$2.7M");
406        tbl.cell(2, 3).unwrap().set_text("$5.1M");
407    }
408
409    doc.add_paragraph("");
410
411    doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
412    {
413        let mut tbl = doc
414            .add_table(4, 3)
415            .borders(BorderStyle::Single, 4, "000000");
416        tbl.cell(0, 0).unwrap().set_text("Category");
417        tbl.cell(0, 0).unwrap().shading("E2EFDA");
418        tbl.cell(0, 1).unwrap().set_text("Item");
419        tbl.cell(0, 1).unwrap().shading("E2EFDA");
420        tbl.cell(0, 2).unwrap().set_text("Price");
421        tbl.cell(0, 2).unwrap().shading("E2EFDA");
422        tbl.cell(1, 0).unwrap().set_text("Hardware");
423        tbl.cell(1, 0).unwrap().v_merge_restart();
424        tbl.cell(1, 1).unwrap().set_text("Laptop");
425        tbl.cell(1, 2).unwrap().set_text("$1,200");
426        tbl.cell(2, 0).unwrap().v_merge_continue();
427        tbl.cell(2, 1).unwrap().set_text("Monitor");
428        tbl.cell(2, 2).unwrap().set_text("$450");
429        tbl.cell(3, 0).unwrap().set_text("Software");
430        tbl.cell(3, 1).unwrap().set_text("IDE License");
431        tbl.cell(3, 2).unwrap().set_text("$200/yr");
432    }
433
434    doc.add_paragraph("");
435
436    doc.add_paragraph("5.4 Nested Table").style("Heading2");
437    {
438        let mut tbl = doc
439            .add_table(2, 2)
440            .borders(BorderStyle::Single, 6, "2E75B6");
441        tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
442        tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
443        tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
444        {
445            let mut cell = tbl.cell(1, 1).unwrap();
446            cell.set_text("Nested table below:");
447            let mut nested = cell
448                .add_table(2, 2)
449                .borders(BorderStyle::Single, 2, "FF6600");
450            nested.cell(0, 0).unwrap().set_text("A");
451            nested.cell(0, 1).unwrap().set_text("B");
452            nested.cell(1, 0).unwrap().set_text("C");
453            nested.cell(1, 1).unwrap().set_text("D");
454        }
455    }
456
457    doc.add_paragraph("");
458
459    doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
460        .style("Heading2");
461    {
462        let mut tbl = doc
463            .add_table(2, 3)
464            .borders(BorderStyle::Single, 4, "666666");
465        tbl.row(0).unwrap().height(Length::pt(50.0)).header();
466        tbl.cell(0, 0).unwrap().set_text("Top");
467        tbl.cell(0, 0)
468            .unwrap()
469            .vertical_alignment(VerticalAlignment::Top)
470            .shading("FFF2CC");
471        tbl.cell(0, 1).unwrap().set_text("Center");
472        tbl.cell(0, 1)
473            .unwrap()
474            .vertical_alignment(VerticalAlignment::Center)
475            .shading("D9E2F3");
476        tbl.cell(0, 2).unwrap().set_text("Bottom");
477        tbl.cell(0, 2)
478            .unwrap()
479            .vertical_alignment(VerticalAlignment::Bottom)
480            .shading("E2EFDA");
481        tbl.row(1).unwrap().cant_split();
482        tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
483        tbl.cell(1, 0).unwrap().no_wrap();
484        tbl.cell(1, 1).unwrap().set_text("Fixed width");
485        tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
486        tbl.cell(1, 2).unwrap().set_text("Normal");
487    }
488
489    // ── SECTION 6: IMAGES ──
490    doc.add_paragraph("").page_break_before(true);
491    doc.add_paragraph("6. Images").style("Heading1");
492
493    doc.add_paragraph("6.1 Inline Image").style("Heading2");
494    doc.add_paragraph("A blue gradient image below:");
495    let img = create_sample_png(200, 50, [0, 80, 200]);
496    doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
497
498    doc.add_paragraph("");
499    doc.add_paragraph("6.2 Header Image").style("Heading2");
500    let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
501    doc.set_header_image(
502        &hdr_img,
503        "header_logo.png",
504        Length::inches(2.0),
505        Length::inches(0.2),
506    );
507    doc.add_paragraph("The header now contains an inline image (check the top of this page).");
508
509    doc.add_paragraph("");
510    doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
511
512    // ── SECTION 7: CONTENT MANIPULATION ──
513    doc.add_paragraph("").page_break_before(true);
514    doc.add_paragraph("7. Content Manipulation")
515        .style("Heading1");
516
517    doc.add_paragraph("7.1 Placeholder Replacement")
518        .style("Heading2");
519    doc.add_paragraph("Customer: {{customer}}");
520    doc.add_paragraph("Date: {{date}}");
521    doc.add_paragraph("Reference: {{ref_number}}");
522    {
523        let mut tbl = doc
524            .add_table(2, 2)
525            .borders(BorderStyle::Single, 4, "000000");
526        tbl.cell(0, 0).unwrap().set_text("Project");
527        tbl.cell(0, 0).unwrap().shading("D6E4F0");
528        tbl.cell(0, 1).unwrap().set_text("{{project}}");
529        tbl.cell(1, 0).unwrap().set_text("Status");
530        tbl.cell(1, 0).unwrap().shading("D6E4F0");
531        tbl.cell(1, 1).unwrap().set_text("{{status}}");
532    }
533
534    let mut replacements = HashMap::new();
535    replacements.insert("{{customer}}", "Tensorbee Inc.");
536    replacements.insert("{{date}}", "February 22, 2026");
537    replacements.insert("{{ref_number}}", "TB-2026-001");
538    replacements.insert("{{project}}", "Infrastructure Upgrade");
539    replacements.insert("{{status}}", "In Progress");
540    let n = doc.replace_all(&replacements);
541    doc.add_paragraph(&format!("({n} placeholders replaced above)"));
542
543    doc.add_paragraph("");
544
545    doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
546    doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
547    let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
548    doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
549
550    doc.add_paragraph("");
551
552    doc.add_paragraph("7.3 Content Insertion").style("Heading2");
553    doc.add_paragraph("Section A: First content.");
554    doc.add_paragraph("Section C: Third content.");
555    if let Some(idx) = doc.find_content_index("Section C") {
556        doc.insert_paragraph(
557            idx,
558            "Section B: Inserted between A and C via find_content_index().",
559        );
560    }
561
562    // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
563    doc.add_paragraph("").section_break(SectionBreak::NextPage);
564    doc.add_paragraph("8. Section Breaks & Orientation")
565        .style("Heading1");
566    doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
567
568    {
569        let mut tbl = doc
570            .add_table(3, 7)
571            .borders(BorderStyle::Single, 4, "2E75B6");
572        let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
573        for (c, h) in headers.iter().enumerate() {
574            tbl.cell(0, c).unwrap().set_text(h);
575            tbl.cell(0, c).unwrap().shading("2E75B6");
576        }
577        let data = [
578            [
579                "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
580            ],
581            [
582                "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
583            ],
584        ];
585        for (r, row) in data.iter().enumerate() {
586            for (c, v) in row.iter().enumerate() {
587                tbl.cell(r + 1, c).unwrap().set_text(v);
588            }
589        }
590    }
591
592    doc.add_paragraph("")
593        .section_break(SectionBreak::NextPage)
594        .section_landscape();
595
596    // ── SECTION 9: CUSTOM STYLES ──
597    doc.add_paragraph("9. Custom Styles").style("Heading1");
598    doc.add_style(
599        StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
600            .based_on("Normal")
601            .paragraph_properties(rdocx_oxml::properties::CT_PPr {
602                shading: Some(rdocx_oxml::properties::CT_Shd {
603                    val: "clear".to_string(),
604                    color: None,
605                    fill: Some("FFF2CC".to_string()),
606                }),
607                ..Default::default()
608            })
609            .run_properties(rdocx_oxml::properties::CT_RPr {
610                bold: Some(true),
611                color: Some("C45911".to_string()),
612                ..Default::default()
613            }),
614    );
615    doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
616        .style("CustomHighlight");
617
618    doc.add_paragraph("");
619
620    // ── SECTION 10: DOCUMENT INTELLIGENCE ──
621    doc.add_paragraph("10. Document Intelligence API")
622        .style("Heading1");
623
624    let wc = doc.word_count();
625    let hc = doc.headings().len();
626    let ic = doc.images().len();
627    let lc = doc.links().len();
628    doc.add_paragraph(&format!("Word count: {wc}"));
629    doc.add_paragraph(&format!("Heading count: {hc}"));
630    doc.add_paragraph(&format!("Image count: {ic}"));
631    doc.add_paragraph(&format!("Link count: {lc}"));
632
633    let outline = doc.document_outline();
634    doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
635
636    let issues = doc.audit_accessibility();
637    doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
638    for issue in &issues {
639        doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
640    }
641
642    // ── SECTION 11: DOCUMENT MERGING ──
643    doc.add_paragraph("");
644    doc.add_paragraph("11. Document Merging").style("Heading1");
645    {
646        let mut other = Document::new();
647        other.add_paragraph("This paragraph was merged from another document using append().");
648        doc.append(&other);
649    }
650
651    doc.add_paragraph("");
652
653    // ── SUMMARY ──
654    doc.add_paragraph("Summary of Demonstrated Features")
655        .style("Heading1");
656    let features = [
657        "Page setup: size, margins, header/footer distance, gutter",
658        "Document metadata: title, author, subject, keywords",
659        "Headers and footers: text, images, different first page",
660        "Background images (full-page behind text)",
661        "Table of Contents generation with bookmarks",
662        "Text formatting: bold, italic, underline styles, strike, color, size, font",
663        "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
664        "Paragraph formatting: alignment, borders, shading, spacing, indentation",
665        "Pagination controls: keep-with-next, keep-together, widow control",
666        "Bullet and numbered lists with nesting",
667        "Tab stops with dot/underscore/hyphen leaders",
668        "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
669        "Row properties: header rows, exact height, cant-split",
670        "Cell properties: width, no-wrap, vertical alignment",
671        "Inline images and header images",
672        "Placeholder replacement (single and batch)",
673        "Regex-based find and replace",
674        "Content insertion at specific positions",
675        "Section breaks with mixed portrait/landscape",
676        "Custom paragraph and character styles",
677        "Document intelligence: word count, headings, outline, images, links",
678        "Accessibility audit",
679        "Document merging (append)",
680        "Export: DOCX, PDF, HTML, Markdown",
681    ];
682    for f in &features {
683        doc.add_bullet_list_item(f, 0);
684    }
685    doc.add_paragraph("");
686    doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
687        .alignment(Alignment::Center)
688        .shading("E2EFDA")
689        .border_all(BorderStyle::Single, 2, "00AA00");
690
691    doc
692}
693
694// =============================================================================
695// 2. PROPOSAL DOCUMENT — Deep navy + gold scheme
696// =============================================================================
697fn generate_proposal(_samples_dir: &Path) -> Document {
698    let mut doc = Document::new();
699
700    // Colors: Navy #1B2A4A, Gold #C5922E, Light #F4F1EB
701    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
702    doc.set_margins(
703        Length::inches(1.0),
704        Length::inches(1.0),
705        Length::inches(1.0),
706        Length::inches(1.0),
707    );
708    doc.set_title("AI Platform Modernization Proposal");
709    doc.set_author("Walter White");
710    doc.set_subject("Technology Proposal");
711    doc.set_keywords("proposal, AI, modernization, Tensorbee");
712
713    // Banner header
714    let logo_img = create_logo_png(220, 48);
715    let banner = build_header_banner_xml(
716        "rId1",
717        &BannerOpts {
718            bg_color: "1B2A4A",
719            banner_width: 7772400,
720            banner_height: 914400,
721            logo_width: 2011680,
722            logo_height: 438912,
723            logo_x_offset: 295125,
724            logo_y_offset: 237744,
725        },
726    );
727    doc.set_raw_header_with_images(
728        banner,
729        &[("rId1", &logo_img, "logo.png")],
730        rdocx_oxml::header_footer::HdrFtrType::Default,
731    );
732    doc.set_different_first_page(true);
733    let cover_banner = build_header_banner_xml(
734        "rId1",
735        &BannerOpts {
736            bg_color: "C5922E",
737            banner_width: 7772400,
738            banner_height: 914400,
739            logo_width: 2011680,
740            logo_height: 438912,
741            logo_x_offset: 295125,
742            logo_y_offset: 237744,
743        },
744    );
745    doc.set_raw_header_with_images(
746        cover_banner,
747        &[("rId1", &logo_img, "logo.png")],
748        rdocx_oxml::header_footer::HdrFtrType::First,
749    );
750    doc.set_margins(
751        Length::twips(2292),
752        Length::twips(1440),
753        Length::twips(1440),
754        Length::twips(1440),
755    );
756    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
757    doc.set_footer("Tensorbee — Confidential");
758
759    // Custom styles
760    doc.add_style(
761        StyleBuilder::paragraph("ProposalTitle", "Proposal Title")
762            .based_on("Normal")
763            .run_properties(rdocx_oxml::properties::CT_RPr {
764                bold: Some(true),
765                font_ascii: Some("Georgia".to_string()),
766                font_hansi: Some("Georgia".to_string()),
767                sz: Some(rdocx_oxml::HalfPoint(56)), // half-points → 28pt
768                color: Some("1B2A4A".to_string()),
769                ..Default::default()
770            }),
771    );
772
773    // ── Cover Page ──
774    for _ in 0..4 {
775        doc.add_paragraph("");
776    }
777    doc.add_paragraph("AI Platform Modernization")
778        .style("ProposalTitle")
779        .alignment(Alignment::Center);
780    doc.add_paragraph("")
781        .alignment(Alignment::Center)
782        .border_bottom(BorderStyle::Single, 8, "C5922E");
783    {
784        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
785        p.add_run("Prepared for Global Financial Corp.")
786            .size(14.0)
787            .color("666666")
788            .italic(true);
789    }
790    {
791        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
792        p.add_run("Prepared by: Walter White, CEO — Tensorbee")
793            .size(12.0)
794            .color("888888");
795    }
796    {
797        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
798        p.add_run("February 22, 2026").size(12.0).color("888888");
799    }
800
801    // ── TOC ──
802    doc.add_paragraph("").page_break_before(true);
803    doc.add_paragraph("Table of Contents").style("Heading1");
804    // We'll add a manual-style TOC since the insert_toc goes at a specific position
805    doc.insert_toc(doc.content_count(), 2);
806
807    // ── Executive Summary ──
808    doc.add_paragraph("").page_break_before(true);
809    doc.add_paragraph("Executive Summary").style("Heading1");
810    doc.add_paragraph(
811        "Tensorbee proposes a comprehensive modernization of Global Financial Corp's AI platform, \
812         transitioning from legacy batch-processing systems to a real-time inference architecture. \
813         This transformation will reduce model deployment time from weeks to hours, improve \
814         prediction accuracy by an estimated 23%, and deliver $4.2M in annual operational savings.",
815    )
816    .first_line_indent(Length::inches(0.3));
817    doc.add_paragraph(
818        "The project spans three phases over 18 months, with a total investment of $2.8M. \
819         Tensorbee brings deep expertise in ML infrastructure, having successfully completed \
820         similar transformations for three Fortune 500 financial institutions.",
821    )
822    .first_line_indent(Length::inches(0.3));
823
824    // Key metrics table
825    doc.add_paragraph("");
826    {
827        let mut tbl = doc
828            .add_table(5, 2)
829            .borders(BorderStyle::Single, 4, "1B2A4A")
830            .width_pct(80.0);
831        tbl.cell(0, 0).unwrap().set_text("Key Metric");
832        tbl.cell(0, 0).unwrap().shading("1B2A4A");
833        tbl.cell(0, 1).unwrap().set_text("Value");
834        tbl.cell(0, 1).unwrap().shading("1B2A4A");
835        let rows = [
836            ("Total Investment", "$2.8M"),
837            ("Annual Savings", "$4.2M"),
838            ("ROI (Year 1)", "150%"),
839            ("Timeline", "18 months"),
840        ];
841        for (i, (k, v)) in rows.iter().enumerate() {
842            tbl.cell(i + 1, 0).unwrap().set_text(k);
843            if i % 2 == 0 {
844                tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
845            }
846            tbl.cell(i + 1, 1).unwrap().set_text(v);
847            if i % 2 == 0 {
848                tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
849            }
850        }
851    }
852
853    // ── Problem Statement ──
854    doc.add_paragraph("").page_break_before(true);
855    doc.add_paragraph("Problem Statement").style("Heading1");
856    doc.add_paragraph(
857        "Global Financial Corp's current AI infrastructure faces three critical challenges:",
858    );
859    doc.add_numbered_list_item("Deployment Latency — New models take 3-4 weeks to deploy to production, compared to the industry benchmark of 2-3 days.", 0);
860    doc.add_numbered_list_item("Scalability Constraints — The batch processing architecture cannot handle real-time inference demands during peak trading hours.", 0);
861    doc.add_numbered_list_item("Technical Debt — Legacy Python 2.7 codebases and manual deployment scripts create reliability risks and slow iteration speed.", 0);
862
863    // ── Proposed Solution ──
864    doc.add_paragraph("").page_break_before(true);
865    doc.add_paragraph("Proposed Solution").style("Heading1");
866
867    doc.add_paragraph("Phase 1: Foundation (Months 1-6)")
868        .style("Heading2");
869    doc.add_bullet_list_item("Deploy Kubernetes-based ML serving infrastructure", 0);
870    doc.add_bullet_list_item("Implement CI/CD pipeline for model deployment", 0);
871    doc.add_bullet_list_item("Migrate top 5 production models to new platform", 0);
872
873    doc.add_paragraph("Phase 2: Expansion (Months 7-12)")
874        .style("Heading2");
875    doc.add_bullet_list_item(
876        "Real-time feature engineering pipeline (Apache Kafka + Flink)",
877        0,
878    );
879    doc.add_bullet_list_item("A/B testing framework for model variants", 0);
880    doc.add_bullet_list_item("Automated model monitoring and drift detection", 0);
881
882    doc.add_paragraph("Phase 3: Optimization (Months 13-18)")
883        .style("Heading2");
884    doc.add_bullet_list_item("GPU inference optimization (TensorRT/ONNX Runtime)", 0);
885    doc.add_bullet_list_item("Multi-region deployment for latency reduction", 0);
886    doc.add_bullet_list_item(
887        "Self-service model deployment portal for data science teams",
888        0,
889    );
890
891    // ── Budget ──
892    doc.add_paragraph("Budget Breakdown").style("Heading1");
893    {
894        let mut tbl = doc
895            .add_table(6, 3)
896            .borders(BorderStyle::Single, 4, "1B2A4A")
897            .width_pct(100.0);
898        tbl.cell(0, 0).unwrap().set_text("Category");
899        tbl.cell(0, 0).unwrap().shading("1B2A4A");
900        tbl.cell(0, 1).unwrap().set_text("Cost");
901        tbl.cell(0, 1).unwrap().shading("1B2A4A");
902        tbl.cell(0, 2).unwrap().set_text("Notes");
903        tbl.cell(0, 2).unwrap().shading("1B2A4A");
904        let items = [
905            (
906                "Infrastructure",
907                "$450,000",
908                "Cloud compute + GPU instances",
909            ),
910            ("Engineering Services", "$1,600,000", "12 FTE x 18 months"),
911            (
912                "Software Licenses",
913                "$350,000",
914                "Kafka, monitoring, CI/CD tools",
915            ),
916            (
917                "Training & Enablement",
918                "$200,000",
919                "Team training, documentation",
920            ),
921            ("Contingency (10%)", "$200,000", "Risk buffer"),
922        ];
923        for (i, (cat, cost, note)) in items.iter().enumerate() {
924            tbl.cell(i + 1, 0).unwrap().set_text(cat);
925            tbl.cell(i + 1, 1).unwrap().set_text(cost);
926            tbl.cell(i + 1, 2).unwrap().set_text(note);
927            if i % 2 == 0 {
928                tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
929                tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
930                tbl.cell(i + 1, 2).unwrap().shading("F4F1EB");
931            }
932        }
933    }
934
935    // ── Closing ──
936    doc.add_paragraph("");
937    doc.add_paragraph("Next Steps").style("Heading1");
938    doc.add_numbered_list_item(
939        "Schedule technical deep-dive with GFC engineering team (Week 1)",
940        0,
941    );
942    doc.add_numbered_list_item("Finalize scope and sign SOW (Week 2-3)", 0);
943    doc.add_numbered_list_item("Kick off Phase 1 with joint planning session (Week 4)", 0);
944
945    doc.add_paragraph("");
946    {
947        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
948        p.add_run("Walter White")
949            .bold(true)
950            .size(14.0)
951            .color("1B2A4A");
952    }
953    {
954        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
955        p.add_run("CEO, Tensorbee | walter@tensorbee.com")
956            .size(10.0)
957            .color("888888");
958    }
959
960    doc
961}
962
963// =============================================================================
964// 3. QUOTE / BILL OF MATERIALS — Teal + orange scheme
965// =============================================================================
966fn generate_quote(_samples_dir: &Path) -> Document {
967    let mut doc = Document::new();
968
969    // Colors: Teal #008B8B, Dark #1A3C3C, Orange #E07020, Light #F0F8F8
970    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
971    doc.set_margins(
972        Length::inches(0.75),
973        Length::inches(0.75),
974        Length::inches(0.75),
975        Length::inches(0.75),
976    );
977    doc.set_title("Quotation QT-2026-0147");
978    doc.set_author("Walter White");
979    doc.set_keywords("quote, BOM, Tensorbee");
980
981    doc.set_header("Tensorbee — Quotation");
982    doc.set_footer("QT-2026-0147 | Page");
983
984    // ── Header Block ──
985    {
986        let mut p = doc.add_paragraph("").alignment(Alignment::Left);
987        p.add_run("TENSORBEE")
988            .bold(true)
989            .size(28.0)
990            .color("008B8B")
991            .font("Helvetica");
992    }
993    doc.add_paragraph("123 Innovation Drive, Suite 400")
994        .alignment(Alignment::Left);
995    doc.add_paragraph("San Francisco, CA 94105 | +1 (415) 555-0199")
996        .alignment(Alignment::Left);
997    doc.add_paragraph("")
998        .border_bottom(BorderStyle::Single, 8, "008B8B");
999
1000    // Quote meta
1001    doc.add_paragraph("");
1002    {
1003        let mut tbl = doc.add_table(4, 4).width_pct(100.0);
1004        tbl.cell(0, 0).unwrap().set_text("QUOTATION");
1005        tbl.cell(0, 0).unwrap().shading("008B8B").grid_span(2);
1006        tbl.cell(0, 2).unwrap().set_text("DATE");
1007        tbl.cell(0, 2).unwrap().shading("008B8B");
1008        tbl.cell(0, 3).unwrap().set_text("VALID UNTIL");
1009        tbl.cell(0, 3).unwrap().shading("008B8B");
1010        tbl.cell(1, 0).unwrap().set_text("Quote #:");
1011        tbl.cell(1, 1).unwrap().set_text("QT-2026-0147");
1012        tbl.cell(1, 2).unwrap().set_text("Feb 22, 2026");
1013        tbl.cell(1, 3).unwrap().set_text("Mar 22, 2026");
1014        tbl.cell(2, 0).unwrap().set_text("Prepared by:");
1015        tbl.cell(2, 1).unwrap().set_text("Walter White");
1016        tbl.cell(2, 2).unwrap().set_text("Payment:");
1017        tbl.cell(2, 3).unwrap().set_text("Net 30");
1018        tbl.cell(3, 0).unwrap().set_text("Customer:");
1019        tbl.cell(3, 1).unwrap().set_text("Meridian Dynamics LLC");
1020        tbl.cell(3, 2).unwrap().set_text("Currency:");
1021        tbl.cell(3, 3).unwrap().set_text("USD");
1022    }
1023
1024    doc.add_paragraph("");
1025
1026    // ── Bill of Materials ──
1027    {
1028        let mut p = doc.add_paragraph("");
1029        p.add_run("Bill of Materials")
1030            .bold(true)
1031            .size(16.0)
1032            .color("1A3C3C");
1033    }
1034    doc.add_paragraph("");
1035
1036    {
1037        let mut tbl = doc
1038            .add_table(10, 6)
1039            .borders(BorderStyle::Single, 2, "008B8B")
1040            .width_pct(100.0)
1041            .layout_fixed();
1042        // Headers
1043        let hdrs = [
1044            "#",
1045            "Part Number",
1046            "Description",
1047            "Qty",
1048            "Unit Price",
1049            "Total",
1050        ];
1051        for (c, h) in hdrs.iter().enumerate() {
1052            tbl.cell(0, c).unwrap().set_text(h);
1053            tbl.cell(0, c).unwrap().shading("008B8B");
1054        }
1055
1056        let items: Vec<(&str, &str, &str, &str, &str)> = vec![
1057            (
1058                "TB-GPU-A100",
1059                "NVIDIA A100 80GB GPU",
1060                "4",
1061                "$12,500.00",
1062                "$50,000.00",
1063            ),
1064            (
1065                "TB-SRV-R750",
1066                "Dell R750xa Server Chassis",
1067                "2",
1068                "$8,200.00",
1069                "$16,400.00",
1070            ),
1071            (
1072                "TB-RAM-256G",
1073                "256GB DDR5 ECC Memory Module",
1074                "8",
1075                "$890.00",
1076                "$7,120.00",
1077            ),
1078            (
1079                "TB-SSD-3840",
1080                "3.84TB NVMe U.2 SSD",
1081                "8",
1082                "$1,150.00",
1083                "$9,200.00",
1084            ),
1085            (
1086                "TB-NET-CX7",
1087                "ConnectX-7 200GbE NIC",
1088                "4",
1089                "$1,800.00",
1090                "$7,200.00",
1091            ),
1092            (
1093                "TB-SW-48P",
1094                "48-Port 100GbE Switch",
1095                "1",
1096                "$22,000.00",
1097                "$22,000.00",
1098            ),
1099            (
1100                "TB-CAB-RACK",
1101                "42U Server Rack + PDU",
1102                "1",
1103                "$4,500.00",
1104                "$4,500.00",
1105            ),
1106            (
1107                "TB-SVC-INST",
1108                "Installation & Configuration",
1109                "1",
1110                "$8,500.00",
1111                "$8,500.00",
1112            ),
1113            (
1114                "TB-SVC-SUPP",
1115                "3-Year Premium Support",
1116                "1",
1117                "$15,000.00",
1118                "$15,000.00",
1119            ),
1120        ];
1121
1122        for (i, (pn, desc, qty, unit, total)) in items.iter().enumerate() {
1123            let row = i + 1;
1124            tbl.cell(row, 0).unwrap().set_text(&format!("{}", i + 1));
1125            tbl.cell(row, 1).unwrap().set_text(pn);
1126            tbl.cell(row, 2).unwrap().set_text(desc);
1127            tbl.cell(row, 3).unwrap().set_text(qty);
1128            tbl.cell(row, 4).unwrap().set_text(unit);
1129            tbl.cell(row, 5).unwrap().set_text(total);
1130            if i % 2 == 0 {
1131                for c in 0..6 {
1132                    tbl.cell(row, c).unwrap().shading("F0F8F8");
1133                }
1134            }
1135        }
1136    }
1137
1138    doc.add_paragraph("");
1139
1140    // ── Totals ──
1141    {
1142        let mut tbl = doc
1143            .add_table(4, 2)
1144            .borders(BorderStyle::Single, 2, "008B8B")
1145            .width(Length::inches(3.5))
1146            .alignment(Alignment::Right);
1147        tbl.cell(0, 0).unwrap().set_text("Subtotal");
1148        tbl.cell(0, 1).unwrap().set_text("$139,920.00");
1149        tbl.cell(1, 0).unwrap().set_text("Shipping & Handling");
1150        tbl.cell(1, 1).unwrap().set_text("$2,500.00");
1151        tbl.cell(2, 0).unwrap().set_text("Tax (8.625%)");
1152        tbl.cell(2, 1).unwrap().set_text("$12,068.10");
1153        tbl.cell(3, 0).unwrap().set_text("TOTAL");
1154        tbl.cell(3, 0).unwrap().shading("E07020");
1155        tbl.cell(3, 1).unwrap().set_text("$154,488.10");
1156        tbl.cell(3, 1).unwrap().shading("E07020");
1157    }
1158
1159    doc.add_paragraph("");
1160
1161    // ── Terms & Conditions ──
1162    {
1163        let mut p = doc.add_paragraph("");
1164        p.add_run("Terms & Conditions")
1165            .bold(true)
1166            .size(14.0)
1167            .color("1A3C3C");
1168    }
1169    doc.add_numbered_list_item(
1170        "This quotation is valid for 30 calendar days from the date of issue.",
1171        0,
1172    );
1173    doc.add_numbered_list_item(
1174        "All prices are in USD and exclusive of applicable taxes unless stated.",
1175        0,
1176    );
1177    doc.add_numbered_list_item("Standard lead time is 4-6 weeks from PO receipt.", 0);
1178    doc.add_numbered_list_item(
1179        "Payment terms: Net 30 from invoice date. 2% discount for payment within 10 days.",
1180        0,
1181    );
1182    doc.add_numbered_list_item(
1183        "Warranty: 3-year manufacturer warranty on all hardware components.",
1184        0,
1185    );
1186    doc.add_numbered_list_item(
1187        "Returns subject to 15% restocking fee if initiated after 14 days.",
1188        0,
1189    );
1190
1191    doc.add_paragraph("");
1192    doc.add_paragraph("")
1193        .border_bottom(BorderStyle::Single, 4, "008B8B");
1194    doc.add_paragraph("");
1195    {
1196        let mut p = doc.add_paragraph("");
1197        p.add_run("Accepted by: ").bold(true);
1198        p.add_run("___________________________ Date: ___________");
1199    }
1200    {
1201        let mut p = doc.add_paragraph("");
1202        p.add_run("Print Name: ").bold(true);
1203        p.add_run("___________________________ Title: ___________");
1204    }
1205
1206    doc
1207}
1208
1209// =============================================================================
1210// 4. INVOICE — Crimson + charcoal scheme
1211// =============================================================================
1212fn generate_invoice(_samples_dir: &Path) -> Document {
1213    let mut doc = Document::new();
1214
1215    // Colors: Crimson #B22222, Charcoal #333333, Light #FAF0F0
1216    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1217    doc.set_margins(
1218        Length::inches(0.75),
1219        Length::inches(0.75),
1220        Length::inches(0.75),
1221        Length::inches(0.75),
1222    );
1223    doc.set_title("Invoice INV-2026-0392");
1224    doc.set_author("Walter White");
1225    doc.set_keywords("invoice, Tensorbee");
1226
1227    doc.set_footer("Tensorbee — Thank you for your business!");
1228
1229    // ── Company & Invoice Header ──
1230    {
1231        let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1232        // Company name left, INVOICE right
1233        {
1234            let mut cell = tbl.cell(0, 0).unwrap();
1235            let mut p = cell.add_paragraph("");
1236            p.add_run("TENSORBEE")
1237                .bold(true)
1238                .size(32.0)
1239                .color("B22222")
1240                .font("Helvetica");
1241        }
1242        {
1243            let mut cell = tbl.cell(0, 1).unwrap();
1244            let mut p = cell.add_paragraph("");
1245            p.add_run("INVOICE").bold(true).size(32.0).color("333333");
1246        }
1247        tbl.cell(1, 0)
1248            .unwrap()
1249            .set_text("123 Innovation Drive, Suite 400");
1250        tbl.cell(1, 1).unwrap().set_text("Invoice #: INV-2026-0392");
1251        tbl.cell(2, 0).unwrap().set_text("San Francisco, CA 94105");
1252        tbl.cell(2, 1).unwrap().set_text("Date: February 22, 2026");
1253        tbl.cell(3, 0).unwrap().set_text("walter@tensorbee.com");
1254        tbl.cell(3, 1).unwrap().set_text("Due Date: March 24, 2026");
1255    }
1256
1257    doc.add_paragraph("")
1258        .border_bottom(BorderStyle::Thick, 8, "B22222");
1259    doc.add_paragraph("");
1260
1261    // ── Bill To / Ship To ──
1262    {
1263        let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1264        tbl.cell(0, 0).unwrap().set_text("BILL TO");
1265        tbl.cell(0, 0).unwrap().shading("B22222");
1266        tbl.cell(0, 1).unwrap().set_text("SHIP TO");
1267        tbl.cell(0, 1).unwrap().shading("B22222");
1268        tbl.cell(1, 0).unwrap().set_text("Meridian Dynamics LLC");
1269        tbl.cell(1, 1).unwrap().set_text("Meridian Dynamics LLC");
1270        tbl.cell(2, 0).unwrap().set_text("456 Enterprise Blvd");
1271        tbl.cell(2, 1).unwrap().set_text("Attn: Server Room B");
1272        tbl.cell(3, 0).unwrap().set_text("Austin, TX 78701");
1273        tbl.cell(3, 1)
1274            .unwrap()
1275            .set_text("456 Enterprise Blvd, Austin, TX 78701");
1276    }
1277
1278    doc.add_paragraph("");
1279
1280    // ── Line Items ──
1281    {
1282        let mut tbl = doc
1283            .add_table(8, 5)
1284            .borders(BorderStyle::Single, 2, "B22222")
1285            .width_pct(100.0);
1286        let hdrs = ["Description", "Qty", "Unit Price", "Tax", "Amount"];
1287        for (c, h) in hdrs.iter().enumerate() {
1288            tbl.cell(0, c).unwrap().set_text(h);
1289            tbl.cell(0, c).unwrap().shading("B22222");
1290        }
1291        let items = [
1292            (
1293                "ML Infrastructure Setup — Phase 1",
1294                "1",
1295                "$45,000.00",
1296                "$3,881.25",
1297                "$48,881.25",
1298            ),
1299            (
1300                "Data Pipeline Development (160 hrs)",
1301                "160",
1302                "$225.00",
1303                "$3,105.00",
1304                "$39,105.00",
1305            ),
1306            (
1307                "GPU Cluster Configuration",
1308                "1",
1309                "$12,000.00",
1310                "$1,035.00",
1311                "$13,035.00",
1312            ),
1313            (
1314                "API Gateway Implementation",
1315                "1",
1316                "$18,500.00",
1317                "$1,595.63",
1318                "$20,095.63",
1319            ),
1320            (
1321                "Load Testing & QA (80 hrs)",
1322                "80",
1323                "$195.00",
1324                "$1,345.50",
1325                "$16,945.50",
1326            ),
1327            (
1328                "Documentation & Training",
1329                "1",
1330                "$8,000.00",
1331                "$690.00",
1332                "$8,690.00",
1333            ),
1334            (
1335                "Project Management (3 months)",
1336                "3",
1337                "$6,500.00",
1338                "$1,679.63",
1339                "$21,179.63",
1340            ),
1341        ];
1342        for (i, (desc, qty, unit, tax, amt)) in items.iter().enumerate() {
1343            let r = i + 1;
1344            tbl.cell(r, 0).unwrap().set_text(desc);
1345            tbl.cell(r, 1).unwrap().set_text(qty);
1346            tbl.cell(r, 2).unwrap().set_text(unit);
1347            tbl.cell(r, 3).unwrap().set_text(tax);
1348            tbl.cell(r, 4).unwrap().set_text(amt);
1349            if i % 2 == 0 {
1350                for c in 0..5 {
1351                    tbl.cell(r, c).unwrap().shading("FAF0F0");
1352                }
1353            }
1354        }
1355    }
1356
1357    doc.add_paragraph("");
1358
1359    // ── Totals ──
1360    {
1361        let mut tbl = doc
1362            .add_table(4, 2)
1363            .borders(BorderStyle::Single, 2, "B22222")
1364            .width(Length::inches(3.0))
1365            .alignment(Alignment::Right);
1366        tbl.cell(0, 0).unwrap().set_text("Subtotal");
1367        tbl.cell(0, 1).unwrap().set_text("$154,600.00");
1368        tbl.cell(1, 0).unwrap().set_text("Tax (8.625%)");
1369        tbl.cell(1, 1).unwrap().set_text("$13,334.25");
1370        tbl.cell(2, 0).unwrap().set_text("Discount (5%)");
1371        tbl.cell(2, 1).unwrap().set_text("-$7,730.00");
1372        tbl.cell(3, 0).unwrap().set_text("AMOUNT DUE");
1373        tbl.cell(3, 0).unwrap().shading("B22222");
1374        tbl.cell(3, 1).unwrap().set_text("$160,204.25");
1375        tbl.cell(3, 1).unwrap().shading("B22222");
1376    }
1377
1378    doc.add_paragraph("");
1379    doc.add_paragraph("");
1380
1381    // ── Payment Details ──
1382    {
1383        let mut p = doc.add_paragraph("");
1384        p.add_run("Payment Details")
1385            .bold(true)
1386            .size(14.0)
1387            .color("333333");
1388    }
1389    doc.add_paragraph("Bank: Silicon Valley Bank")
1390        .indent_left(Length::inches(0.3));
1391    doc.add_paragraph("Account: Tensorbee Inc. — 0847-2953-1120")
1392        .indent_left(Length::inches(0.3));
1393    doc.add_paragraph("Routing: 121140399")
1394        .indent_left(Length::inches(0.3));
1395    doc.add_paragraph("Swift: SVBKUS6S")
1396        .indent_left(Length::inches(0.3));
1397
1398    doc.add_paragraph("");
1399
1400    doc.add_paragraph("Please include invoice number INV-2026-0392 in the payment reference.")
1401        .shading("FAF0F0")
1402        .border_all(BorderStyle::Single, 2, "B22222");
1403
1404    doc
1405}
1406
1407// =============================================================================
1408// 5. REPORT — Forest green + earth tones, with images & hierarchical sections
1409// =============================================================================
1410fn generate_report(_samples_dir: &Path) -> Document {
1411    let mut doc = Document::new();
1412
1413    // Colors: Forest #2D5016, Sage #6B8E23, Earth #8B7355, Cream #FFFAF0
1414    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1415    doc.set_margins(
1416        Length::inches(1.0),
1417        Length::inches(1.0),
1418        Length::inches(1.0),
1419        Length::inches(1.0),
1420    );
1421    doc.set_title("Q4 2025 Environmental Impact Report");
1422    doc.set_author("Walter White");
1423    doc.set_subject("Quarterly Environmental Report");
1424    doc.set_keywords("environment, sustainability, report, Tensorbee");
1425
1426    doc.set_different_first_page(true);
1427    doc.set_first_page_header("");
1428    doc.set_header("Tensorbee — Q4 2025 Environmental Impact Report");
1429    doc.set_footer("CONFIDENTIAL — Page");
1430
1431    // ── Cover ──
1432    let cover_bg = create_sample_png(612, 792, [20, 50, 15]);
1433    doc.add_background_image(&cover_bg, "report_cover.png");
1434
1435    for _ in 0..5 {
1436        doc.add_paragraph("");
1437    }
1438    {
1439        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1440        p.add_run("Q4 2025")
1441            .bold(true)
1442            .size(48.0)
1443            .color("FFFFFF")
1444            .font("Georgia");
1445    }
1446    {
1447        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1448        p.add_run("Environmental Impact Report")
1449            .size(24.0)
1450            .color("90EE90")
1451            .font("Georgia")
1452            .italic(true);
1453    }
1454    doc.add_paragraph("");
1455    {
1456        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1457        p.add_run("Tensorbee — Sustainability Division")
1458            .size(14.0)
1459            .color("C0C0C0");
1460    }
1461    {
1462        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1463        p.add_run("Prepared by Walter White, Chief Sustainability Officer")
1464            .size(11.0)
1465            .color("AAAAAA");
1466    }
1467
1468    // ── TOC ──
1469    doc.add_paragraph("").page_break_before(true);
1470    doc.insert_toc(doc.content_count(), 3);
1471
1472    // ── Section 1: Executive Overview ──
1473    doc.add_paragraph("").page_break_before(true);
1474    doc.add_paragraph("Executive Overview").style("Heading1");
1475    doc.add_paragraph(
1476        "This report presents Tensorbee's environmental performance for Q4 2025. Our \
1477         sustainability initiatives have yielded a 34% reduction in carbon emissions compared \
1478         to Q4 2024, exceeding our target of 25%. Key achievements include the transition to \
1479         100% renewable energy in our primary data centers and the launch of our carbon offset \
1480         marketplace.",
1481    )
1482    .first_line_indent(Length::inches(0.3));
1483
1484    // Image: performance chart
1485    let chart_img = create_chart_png(400, 200);
1486    doc.add_paragraph("");
1487    doc.add_picture(
1488        &chart_img,
1489        "performance_chart.png",
1490        Length::inches(5.0),
1491        Length::inches(2.5),
1492    )
1493    .alignment(Alignment::Center);
1494    {
1495        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1496        p.add_run("Figure 1: Quarterly Carbon Emissions (tonnes CO2e)")
1497            .italic(true)
1498            .size(9.0)
1499            .color("666666");
1500    }
1501
1502    // ── Section 2: Energy Consumption ──
1503    doc.add_paragraph("").page_break_before(true);
1504    doc.add_paragraph("Energy Consumption").style("Heading1");
1505
1506    doc.add_paragraph("Data Center Operations")
1507        .style("Heading2");
1508    doc.add_paragraph(
1509        "Our three primary data centers consumed a combined 4.2 GWh in Q4 2025, \
1510         a 12% reduction from Q3 2025 driven by improved cooling efficiency and \
1511         server consolidation.",
1512    );
1513
1514    // Data table
1515    {
1516        let mut tbl = doc
1517            .add_table(5, 4)
1518            .borders(BorderStyle::Single, 4, "2D5016")
1519            .width_pct(100.0);
1520        let hdrs = ["Data Center", "Capacity (MW)", "Usage (GWh)", "PUE"];
1521        for (c, h) in hdrs.iter().enumerate() {
1522            tbl.cell(0, c).unwrap().set_text(h);
1523            tbl.cell(0, c).unwrap().shading("2D5016");
1524        }
1525        let rows = [
1526            ("San Francisco (Primary)", "3.2", "1.8", "1.12"),
1527            ("Dublin (EU)", "2.1", "1.2", "1.18"),
1528            ("Singapore (APAC)", "1.8", "1.2", "1.24"),
1529            ("TOTAL", "7.1", "4.2", "1.17 avg"),
1530        ];
1531        for (i, (dc, cap, use_, pue)) in rows.iter().enumerate() {
1532            tbl.cell(i + 1, 0).unwrap().set_text(dc);
1533            tbl.cell(i + 1, 1).unwrap().set_text(cap);
1534            tbl.cell(i + 1, 2).unwrap().set_text(use_);
1535            tbl.cell(i + 1, 3).unwrap().set_text(pue);
1536            if i == 3 {
1537                for c in 0..4 {
1538                    tbl.cell(i + 1, c).unwrap().shading("E2EFDA");
1539                }
1540            }
1541        }
1542    }
1543
1544    doc.add_paragraph("");
1545
1546    doc.add_paragraph("Renewable Energy Mix").style("Heading2");
1547    doc.add_paragraph("Breakdown of energy sources across all facilities:");
1548
1549    doc.add_bullet_list_item("Solar PV: 42% (1.76 GWh)", 0);
1550    doc.add_bullet_list_item("Wind Power Purchase Agreements: 38% (1.60 GWh)", 0);
1551    doc.add_bullet_list_item("Hydroelectric: 12% (0.50 GWh)", 0);
1552    doc.add_bullet_list_item("Grid (non-renewable): 8% (0.34 GWh)", 0);
1553
1554    // ── Section 3: Water & Waste ──
1555    doc.add_paragraph("Water & Waste Management")
1556        .style("Heading1");
1557
1558    doc.add_paragraph("Water Usage").style("Heading2");
1559    doc.add_paragraph(
1560        "Total water consumption was 12.4 million gallons, a 15% reduction from Q3. \
1561         Our closed-loop cooling systems now recycle 78% of water used in cooling operations.",
1562    );
1563
1564    doc.add_paragraph("Waste Reduction").style("Heading2");
1565    doc.add_paragraph("E-waste management results for Q4:")
1566        .keep_with_next(true);
1567    {
1568        let mut tbl = doc
1569            .add_table(4, 3)
1570            .borders(BorderStyle::Single, 2, "6B8E23")
1571            .width_pct(80.0);
1572        tbl.cell(0, 0).unwrap().set_text("Category");
1573        tbl.cell(0, 0).unwrap().shading("6B8E23");
1574        tbl.cell(0, 1).unwrap().set_text("Weight (kg)");
1575        tbl.cell(0, 1).unwrap().shading("6B8E23");
1576        tbl.cell(0, 2).unwrap().set_text("Recycled %");
1577        tbl.cell(0, 2).unwrap().shading("6B8E23");
1578        let rows = [
1579            ("Server Hardware", "2,450", "94%"),
1580            ("Networking Equipment", "820", "91%"),
1581            ("Storage Media", "340", "99%"),
1582        ];
1583        for (i, (cat, wt, pct)) in rows.iter().enumerate() {
1584            tbl.cell(i + 1, 0).unwrap().set_text(cat);
1585            tbl.cell(i + 1, 1).unwrap().set_text(wt);
1586            tbl.cell(i + 1, 2).unwrap().set_text(pct);
1587        }
1588    }
1589
1590    // ── Section 4: Initiatives ──
1591    doc.add_paragraph("").page_break_before(true);
1592    doc.add_paragraph("Q1 2026 Initiatives").style("Heading1");
1593
1594    doc.add_paragraph("Planned Programs").style("Heading2");
1595    doc.add_numbered_list_item(
1596        "Deploy on-site battery storage at SF data center (2 MWh capacity)",
1597        0,
1598    );
1599    doc.add_numbered_list_item("Pilot immersion cooling in Dublin facility", 0);
1600    doc.add_numbered_list_item("Launch employee commute carbon offset program", 0);
1601    doc.add_numbered_list_item("Achieve ISO 14001 certification for Singapore facility", 0);
1602
1603    doc.add_paragraph("Investment Targets").style("Heading2");
1604    doc.add_paragraph("Sustainability CapEx allocation for FY2026:")
1605        .keep_with_next(true);
1606    {
1607        let mut tbl = doc
1608            .add_table(5, 2)
1609            .borders(BorderStyle::Single, 4, "2D5016");
1610        tbl.cell(0, 0).unwrap().set_text("Initiative");
1611        tbl.cell(0, 0).unwrap().shading("2D5016");
1612        tbl.cell(0, 1).unwrap().set_text("Budget");
1613        tbl.cell(0, 1).unwrap().shading("2D5016");
1614        let rows = [
1615            ("Battery Storage", "$1.2M"),
1616            ("Immersion Cooling Pilot", "$800K"),
1617            ("Solar Panel Expansion", "$2.1M"),
1618            ("Carbon Credits", "$500K"),
1619        ];
1620        for (i, (init, budget)) in rows.iter().enumerate() {
1621            tbl.cell(i + 1, 0).unwrap().set_text(init);
1622            tbl.cell(i + 1, 1).unwrap().set_text(budget);
1623            if i % 2 == 0 {
1624                tbl.cell(i + 1, 0).unwrap().shading("FFFAF0");
1625                tbl.cell(i + 1, 1).unwrap().shading("FFFAF0");
1626            }
1627        }
1628    }
1629
1630    // Image: sustainability roadmap
1631    let roadmap_img = create_sample_png(500, 100, [40, 80, 30]);
1632    doc.add_paragraph("");
1633    doc.add_picture(
1634        &roadmap_img,
1635        "roadmap.png",
1636        Length::inches(6.0),
1637        Length::inches(1.2),
1638    )
1639    .alignment(Alignment::Center);
1640    {
1641        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1642        p.add_run("Figure 2: 2026 Sustainability Roadmap")
1643            .italic(true)
1644            .size(9.0)
1645            .color("666666");
1646    }
1647
1648    doc.add_paragraph("");
1649    doc.add_paragraph(
1650        "For questions about this report, contact Walter White at sustainability@tensorbee.com.",
1651    )
1652    .shading("FFFAF0")
1653    .border_all(BorderStyle::Single, 2, "2D5016");
1654
1655    doc
1656}
Source

pub fn add_anchored_image( &mut self, image_data: &[u8], image_filename: &str, width: Length, height: Length, behind_text: bool, ) -> Paragraph<'_>

Add an anchored (floating) image to the document.

If behind_text is true, the image renders behind text content. The image is inserted at the beginning of the document body.

Source

pub fn embed_image(&mut self, image_data: &[u8], filename: &str) -> String

Embed an image into the OPC package and return the relationship ID.

Public so callers can pre-embed an image and then pass the returned rel_id to crate::Cell::add_picture for inline cell images.

Source

pub fn numbering_is_bullet(&self, num_id: u32) -> Option<bool>

Whether the given numbering definition renders as bullets (true) or numbers (false). None if the id is unknown.

Append an external hyperlink to the last paragraph (creating one if the document is empty): adds the External relationship and wraps the new run in a hyperlink span.

Add an external hyperlink relationship and return its relationship ID.

Use this with crate::Paragraph::add_hyperlink when the target paragraph is not the last body paragraph, such as a paragraph inside a table cell.

Source

pub fn last_paragraph_mut(&mut self) -> Option<Paragraph<'_>>

Get a builder for the last paragraph in the body, if any. Lets callers interleave plain runs with append_hyperlink calls.

Source

pub fn image_data(&self, rel_id: &str) -> Option<Vec<u8>>

Fetch the raw bytes of an embedded image by its relationship ID.

Resolve a hyperlink relationship ID to its external URL.

Source

pub fn set_header(&mut self, text: &str)

Set the default header text.

Creates a header part with the given text and references it from the section properties.

Examples found in repository?
examples/template_replace.rs (line 53)
43fn create_template(path: &Path) {
44    let mut doc = Document::new();
45    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
46    doc.set_margins(
47        Length::inches(1.0),
48        Length::inches(1.0),
49        Length::inches(1.0),
50        Length::inches(1.0),
51    );
52
53    doc.set_header("{{company_name}} — Confidential");
54    doc.set_footer("Prepared by {{author_name}} on {{date}}");
55
56    // ── Title ──
57    doc.add_paragraph("{{company_name}}")
58        .style("Heading1")
59        .alignment(Alignment::Center);
60
61    doc.add_paragraph("Project Proposal")
62        .alignment(Alignment::Center);
63
64    doc.add_paragraph("");
65
66    // ── Summary section ──
67    doc.add_paragraph("Executive Summary").style("Heading2");
68
69    doc.add_paragraph(
70        "This proposal outlines the {{project_name}} project for {{company_name}}. \
71         The primary contact is {{contact_name}} ({{contact_email}}). \
72         The proposed start date is {{start_date}} with an estimated duration of {{duration}}.",
73    );
74
75    doc.add_paragraph("");
76
77    // ── Cross-run placeholder (bold label + normal value) ──
78    doc.add_paragraph("Key Details").style("Heading2");
79
80    {
81        let mut p = doc.add_paragraph("");
82        p.add_run("Project: ").bold(true);
83        p.add_run("{{project_name}}");
84    }
85    {
86        let mut p = doc.add_paragraph("");
87        p.add_run("Budget: ").bold(true);
88        p.add_run("{{budget}}");
89    }
90    {
91        let mut p = doc.add_paragraph("");
92        p.add_run("Status: ").bold(true);
93        p.add_run("{{status}}");
94    }
95
96    doc.add_paragraph("");
97
98    // ── Table with placeholders ──
99    doc.add_paragraph("Team Members").style("Heading2");
100
101    {
102        let mut tbl = doc.add_table(4, 3);
103        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
104
105        // Header row
106        for col in 0..3 {
107            tbl.cell(0, col).unwrap().shading("2E75B6");
108        }
109        tbl.cell(0, 0).unwrap().set_text("Name");
110        tbl.cell(0, 1).unwrap().set_text("Role");
111        tbl.cell(0, 2).unwrap().set_text("Email");
112
113        tbl.cell(1, 0).unwrap().set_text("{{member1_name}}");
114        tbl.cell(1, 1).unwrap().set_text("{{member1_role}}");
115        tbl.cell(1, 2).unwrap().set_text("{{member1_email}}");
116
117        tbl.cell(2, 0).unwrap().set_text("{{member2_name}}");
118        tbl.cell(2, 1).unwrap().set_text("{{member2_role}}");
119        tbl.cell(2, 2).unwrap().set_text("{{member2_email}}");
120
121        tbl.cell(3, 0).unwrap().set_text("{{member3_name}}");
122        tbl.cell(3, 1).unwrap().set_text("{{member3_role}}");
123        tbl.cell(3, 2).unwrap().set_text("{{member3_email}}");
124    }
125
126    doc.add_paragraph("");
127
128    // ── Deliverables section ──
129    doc.add_paragraph("Deliverables").style("Heading2");
130
131    doc.add_paragraph("INSERTION_POINT");
132
133    doc.add_paragraph("");
134
135    // ── Signature block ──
136    doc.add_paragraph("Acceptance").style("Heading2");
137
138    doc.add_paragraph(
139        "By signing below, {{company_name}} agrees to the terms outlined in this proposal.",
140    );
141
142    {
143        let mut tbl = doc.add_table(2, 2);
144        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
145        tbl.cell(0, 0)
146            .unwrap()
147            .set_text("Customer: ___________________");
148        tbl.cell(0, 1)
149            .unwrap()
150            .set_text("Provider: ___________________");
151        tbl.cell(1, 0).unwrap().set_text("Date: {{date}}");
152        tbl.cell(1, 1).unwrap().set_text("Date: {{date}}");
153    }
154
155    doc.set_title("{{company_name}} — Project Proposal Template");
156    doc.set_author("Template Generator");
157
158    doc.save(path).unwrap();
159}
More examples
Hide additional examples
examples/generate_all_samples.rs (line 119)
97fn generate_feature_showcase(_samples_dir: &Path) -> Document {
98    let mut doc = Document::new();
99
100    // ── Page Setup & Metadata ──
101    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
102    doc.set_margins(
103        Length::inches(1.0),
104        Length::inches(1.0),
105        Length::inches(1.0),
106        Length::inches(1.0),
107    );
108    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
109    doc.set_gutter(Length::twips(0));
110    doc.set_title("rdocx Feature Showcase");
111    doc.set_author("rdocx Sample Generator");
112    doc.set_subject("Comprehensive feature demonstration");
113    doc.set_keywords("rdocx, docx, rust, sample, showcase");
114
115    // Headers & Footers with different first page
116    doc.set_different_first_page(true);
117    doc.set_first_page_header("rdocx");
118    doc.set_first_page_footer("Feature Showcase — Cover Page");
119    doc.set_header("rdocx Feature Showcase");
120    doc.set_footer("Generated by rdocx");
121
122    // ── COVER PAGE ──
123    let bg = create_sample_png(612, 792, [20, 45, 90]);
124    doc.add_background_image(&bg, "cover_bg.png");
125
126    for _ in 0..3 {
127        doc.add_paragraph("");
128    }
129    {
130        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
131        p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
132    }
133    {
134        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
135        p.add_run("Complete Feature Showcase")
136            .size(28.0)
137            .color("FFFFFF")
138            .italic(true);
139    }
140    doc.add_paragraph("");
141    {
142        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
143        p.add_run("Every feature of the rdocx Rust library")
144            .size(13.0)
145            .color("B0C4DE");
146    }
147    {
148        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
149        p.add_run("demonstrated in a single document.")
150            .size(13.0)
151            .color("B0C4DE");
152    }
153
154    // ── TABLE OF CONTENTS ──
155    doc.add_paragraph("").page_break_before(true);
156    doc.insert_toc(doc.content_count(), 3);
157
158    // ── SECTION 1: TEXT FORMATTING ──
159    doc.add_paragraph("").page_break_before(true);
160    doc.add_paragraph("1. Text Formatting").style("Heading1");
161
162    // Paragraph Alignment
163    doc.add_paragraph("1.1 Paragraph Alignment")
164        .style("Heading2");
165    doc.add_paragraph("Left-aligned paragraph (default).")
166        .alignment(Alignment::Left);
167    doc.add_paragraph("Center-aligned paragraph.")
168        .alignment(Alignment::Center);
169    doc.add_paragraph("Right-aligned paragraph.")
170        .alignment(Alignment::Right);
171    doc.add_paragraph(
172        "Justified paragraph — this text is long enough to demonstrate how justified alignment \
173         distributes extra space across word gaps so lines fill the full width of the text area.",
174    )
175    .alignment(Alignment::Justify);
176
177    doc.add_paragraph("");
178
179    // Run Formatting
180    doc.add_paragraph("1.2 Run Formatting").style("Heading2");
181    {
182        let mut p = doc.add_paragraph("");
183        p.add_run("Bold").bold(true);
184        p.add_run(" | ");
185        p.add_run("Italic").italic(true);
186        p.add_run(" | ");
187        p.add_run("Bold Italic").bold(true).italic(true);
188        p.add_run(" | ");
189        p.add_run("Underline").underline(true);
190        p.add_run(" | ");
191        p.add_run("Strikethrough").strike(true);
192        p.add_run(" | ");
193        p.add_run("Double Strike").double_strike(true);
194    }
195    {
196        let mut p = doc.add_paragraph("");
197        p.add_run("Red").color("FF0000");
198        p.add_run(" | ");
199        p.add_run("Blue").color("0000FF");
200        p.add_run(" | ");
201        p.add_run("Green").color("00AA00");
202        p.add_run(" | ");
203        p.add_run("Highlighted").highlight("FFFF00");
204    }
205    {
206        let mut p = doc.add_paragraph("");
207        p.add_run("8pt").size(8.0);
208        p.add_run(" | ");
209        p.add_run("11pt").size(11.0);
210        p.add_run(" | ");
211        p.add_run("16pt").size(16.0);
212        p.add_run(" | ");
213        p.add_run("24pt").size(24.0);
214    }
215    {
216        let mut p = doc.add_paragraph("");
217        p.add_run("Arial").font("Arial");
218        p.add_run(" | ");
219        p.add_run("Times New Roman").font("Times New Roman");
220        p.add_run(" | ");
221        p.add_run("Courier New").font("Courier New");
222    }
223    {
224        let mut p = doc.add_paragraph("");
225        p.add_run("H");
226        p.add_run("2").subscript();
227        p.add_run("O (subscript) | E=mc");
228        p.add_run("2").superscript();
229        p.add_run(" (superscript)");
230    }
231    {
232        let mut p = doc.add_paragraph("");
233        p.add_run("ALL CAPS").all_caps(true);
234        p.add_run(" | ");
235        p.add_run("Small Caps").small_caps(true);
236        p.add_run(" | ");
237        p.add_run("Expanded").character_spacing(Length::twips(40));
238        p.add_run(" | ");
239        p.add_run("Hidden text (not visible)").hidden(true);
240    }
241
242    doc.add_paragraph("");
243
244    // Underline Styles
245    doc.add_paragraph("1.3 Underline Styles").style("Heading2");
246    {
247        let mut p = doc.add_paragraph("");
248        p.add_run("Single ")
249            .underline_style(rdocx::UnderlineStyle::Single);
250        p.add_run("Double ")
251            .underline_style(rdocx::UnderlineStyle::Double);
252        p.add_run("Thick ")
253            .underline_style(rdocx::UnderlineStyle::Thick);
254        p.add_run("Dotted ")
255            .underline_style(rdocx::UnderlineStyle::Dotted);
256        p.add_run("Dash ")
257            .underline_style(rdocx::UnderlineStyle::Dash);
258        p.add_run("Wave ")
259            .underline_style(rdocx::UnderlineStyle::Wave);
260    }
261
262    // ── SECTION 2: PARAGRAPH FORMATTING ──
263    doc.add_paragraph("").page_break_before(true);
264    doc.add_paragraph("2. Paragraph Formatting")
265        .style("Heading1");
266
267    doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
268    doc.add_paragraph("Paragraph with light green shading")
269        .shading("E2EFDA");
270    doc.add_paragraph("Paragraph with bottom border")
271        .border_bottom(BorderStyle::Single, 6, "2E75B6");
272    doc.add_paragraph("Paragraph with all borders (red)")
273        .border_all(BorderStyle::Single, 4, "FF0000");
274
275    doc.add_paragraph("2.2 Indentation").style("Heading2");
276    doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
277        .indent_left(Length::inches(1.0))
278        .hanging_indent(Length::inches(0.5));
279    doc.add_paragraph("0.5-inch first-line indent on this paragraph")
280        .first_line_indent(Length::inches(0.5));
281    doc.add_paragraph("Both left and right indent (0.75 inches each)")
282        .indent_left(Length::inches(0.75))
283        .indent_right(Length::inches(0.75));
284
285    doc.add_paragraph("2.3 Spacing & Line Height")
286        .style("Heading2");
287    doc.add_paragraph("Extra space before (24pt) and after (12pt)")
288        .space_before(Length::pt(24.0))
289        .space_after(Length::pt(12.0));
290    doc.add_paragraph(
291        "Double line spacing paragraph. This text should have extra vertical space between \
292         lines to demonstrate line_spacing_multiple(2.0).",
293    )
294    .line_spacing_multiple(2.0);
295    doc.add_paragraph("Exact 20pt line spacing (fixed height).")
296        .line_spacing(20.0);
297
298    doc.add_paragraph("2.4 Pagination Controls")
299        .style("Heading2");
300    doc.add_paragraph("keep_with_next — this paragraph stays with the next")
301        .keep_with_next(true);
302    doc.add_paragraph("(This paragraph was kept with the one above.)");
303    doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
304        .keep_together(true);
305    doc.add_paragraph("widow_control — prevents widow/orphan lines")
306        .widow_control(true);
307
308    // ── SECTION 3: LISTS ──
309    doc.add_paragraph("").page_break_before(true);
310    doc.add_paragraph("3. Lists").style("Heading1");
311
312    doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
313    doc.add_bullet_list_item("First item", 0);
314    doc.add_bullet_list_item("Second item", 0);
315    doc.add_bullet_list_item("Nested level 1", 1);
316    doc.add_bullet_list_item("Nested level 2", 2);
317    doc.add_bullet_list_item("Back to level 1", 1);
318    doc.add_bullet_list_item("Third item", 0);
319
320    doc.add_paragraph("");
321
322    doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
323    doc.add_numbered_list_item("First numbered item", 0);
324    doc.add_numbered_list_item("Second numbered item", 0);
325    doc.add_numbered_list_item("Sub-item A", 1);
326    doc.add_numbered_list_item("Sub-item B", 1);
327    doc.add_numbered_list_item("Third numbered item", 0);
328
329    // ── SECTION 4: TAB STOPS ──
330    doc.add_paragraph("");
331    doc.add_paragraph("4. Tab Stops").style("Heading1");
332
333    doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
334    doc.add_paragraph("Left\tCenter\tRight\tDecimal")
335        .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
336        .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
337        .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
338        .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
339
340    doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
341    doc.add_paragraph("Item\t\tPrice")
342        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
343        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
344        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
345    doc.add_paragraph("Widget\t\t$19.99")
346        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
347        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
348        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
349    doc.add_paragraph("Gadget\t\t$249.50")
350        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
351        .add_tab_stop_with_leader(
352            TabAlignment::Right,
353            Length::inches(4.0),
354            TabLeader::Underscore,
355        )
356        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
357
358    // ── SECTION 5: TABLES ──
359    doc.add_paragraph("").page_break_before(true);
360    doc.add_paragraph("5. Tables").style("Heading1");
361
362    doc.add_paragraph("5.1 Basic Table").style("Heading2");
363    {
364        let mut tbl = doc
365            .add_table(4, 3)
366            .borders(BorderStyle::Single, 4, "000000");
367        for col in 0..3 {
368            tbl.cell(0, col).unwrap().shading("2E75B6");
369        }
370        tbl.cell(0, 0).unwrap().set_text("Name");
371        tbl.cell(0, 1).unwrap().set_text("Role");
372        tbl.cell(0, 2).unwrap().set_text("Location");
373        tbl.cell(1, 0).unwrap().set_text("Walter White");
374        tbl.cell(1, 1).unwrap().set_text("CEO");
375        tbl.cell(1, 2).unwrap().set_text("Albuquerque");
376        tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
377        tbl.cell(2, 1).unwrap().set_text("CTO");
378        tbl.cell(2, 2).unwrap().set_text("Remote");
379        tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
380        tbl.cell(3, 1).unwrap().set_text("Security");
381        tbl.cell(3, 2).unwrap().set_text("Washington");
382    }
383
384    doc.add_paragraph("");
385
386    doc.add_paragraph("5.2 Column Span & Cell Shading")
387        .style("Heading2");
388    {
389        let mut tbl = doc
390            .add_table(3, 4)
391            .borders(BorderStyle::Single, 4, "000000")
392            .width_pct(100.0);
393        tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
394        tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
395        tbl.cell(1, 0).unwrap().set_text("Region");
396        tbl.cell(1, 0).unwrap().shading("D6E4F0");
397        tbl.cell(1, 1).unwrap().set_text("Q1");
398        tbl.cell(1, 1).unwrap().shading("D6E4F0");
399        tbl.cell(1, 2).unwrap().set_text("Q2");
400        tbl.cell(1, 2).unwrap().shading("D6E4F0");
401        tbl.cell(1, 3).unwrap().set_text("Total");
402        tbl.cell(1, 3).unwrap().shading("D6E4F0");
403        tbl.cell(2, 0).unwrap().set_text("Americas");
404        tbl.cell(2, 1).unwrap().set_text("$2.4M");
405        tbl.cell(2, 2).unwrap().set_text("$2.7M");
406        tbl.cell(2, 3).unwrap().set_text("$5.1M");
407    }
408
409    doc.add_paragraph("");
410
411    doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
412    {
413        let mut tbl = doc
414            .add_table(4, 3)
415            .borders(BorderStyle::Single, 4, "000000");
416        tbl.cell(0, 0).unwrap().set_text("Category");
417        tbl.cell(0, 0).unwrap().shading("E2EFDA");
418        tbl.cell(0, 1).unwrap().set_text("Item");
419        tbl.cell(0, 1).unwrap().shading("E2EFDA");
420        tbl.cell(0, 2).unwrap().set_text("Price");
421        tbl.cell(0, 2).unwrap().shading("E2EFDA");
422        tbl.cell(1, 0).unwrap().set_text("Hardware");
423        tbl.cell(1, 0).unwrap().v_merge_restart();
424        tbl.cell(1, 1).unwrap().set_text("Laptop");
425        tbl.cell(1, 2).unwrap().set_text("$1,200");
426        tbl.cell(2, 0).unwrap().v_merge_continue();
427        tbl.cell(2, 1).unwrap().set_text("Monitor");
428        tbl.cell(2, 2).unwrap().set_text("$450");
429        tbl.cell(3, 0).unwrap().set_text("Software");
430        tbl.cell(3, 1).unwrap().set_text("IDE License");
431        tbl.cell(3, 2).unwrap().set_text("$200/yr");
432    }
433
434    doc.add_paragraph("");
435
436    doc.add_paragraph("5.4 Nested Table").style("Heading2");
437    {
438        let mut tbl = doc
439            .add_table(2, 2)
440            .borders(BorderStyle::Single, 6, "2E75B6");
441        tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
442        tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
443        tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
444        {
445            let mut cell = tbl.cell(1, 1).unwrap();
446            cell.set_text("Nested table below:");
447            let mut nested = cell
448                .add_table(2, 2)
449                .borders(BorderStyle::Single, 2, "FF6600");
450            nested.cell(0, 0).unwrap().set_text("A");
451            nested.cell(0, 1).unwrap().set_text("B");
452            nested.cell(1, 0).unwrap().set_text("C");
453            nested.cell(1, 1).unwrap().set_text("D");
454        }
455    }
456
457    doc.add_paragraph("");
458
459    doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
460        .style("Heading2");
461    {
462        let mut tbl = doc
463            .add_table(2, 3)
464            .borders(BorderStyle::Single, 4, "666666");
465        tbl.row(0).unwrap().height(Length::pt(50.0)).header();
466        tbl.cell(0, 0).unwrap().set_text("Top");
467        tbl.cell(0, 0)
468            .unwrap()
469            .vertical_alignment(VerticalAlignment::Top)
470            .shading("FFF2CC");
471        tbl.cell(0, 1).unwrap().set_text("Center");
472        tbl.cell(0, 1)
473            .unwrap()
474            .vertical_alignment(VerticalAlignment::Center)
475            .shading("D9E2F3");
476        tbl.cell(0, 2).unwrap().set_text("Bottom");
477        tbl.cell(0, 2)
478            .unwrap()
479            .vertical_alignment(VerticalAlignment::Bottom)
480            .shading("E2EFDA");
481        tbl.row(1).unwrap().cant_split();
482        tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
483        tbl.cell(1, 0).unwrap().no_wrap();
484        tbl.cell(1, 1).unwrap().set_text("Fixed width");
485        tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
486        tbl.cell(1, 2).unwrap().set_text("Normal");
487    }
488
489    // ── SECTION 6: IMAGES ──
490    doc.add_paragraph("").page_break_before(true);
491    doc.add_paragraph("6. Images").style("Heading1");
492
493    doc.add_paragraph("6.1 Inline Image").style("Heading2");
494    doc.add_paragraph("A blue gradient image below:");
495    let img = create_sample_png(200, 50, [0, 80, 200]);
496    doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
497
498    doc.add_paragraph("");
499    doc.add_paragraph("6.2 Header Image").style("Heading2");
500    let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
501    doc.set_header_image(
502        &hdr_img,
503        "header_logo.png",
504        Length::inches(2.0),
505        Length::inches(0.2),
506    );
507    doc.add_paragraph("The header now contains an inline image (check the top of this page).");
508
509    doc.add_paragraph("");
510    doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
511
512    // ── SECTION 7: CONTENT MANIPULATION ──
513    doc.add_paragraph("").page_break_before(true);
514    doc.add_paragraph("7. Content Manipulation")
515        .style("Heading1");
516
517    doc.add_paragraph("7.1 Placeholder Replacement")
518        .style("Heading2");
519    doc.add_paragraph("Customer: {{customer}}");
520    doc.add_paragraph("Date: {{date}}");
521    doc.add_paragraph("Reference: {{ref_number}}");
522    {
523        let mut tbl = doc
524            .add_table(2, 2)
525            .borders(BorderStyle::Single, 4, "000000");
526        tbl.cell(0, 0).unwrap().set_text("Project");
527        tbl.cell(0, 0).unwrap().shading("D6E4F0");
528        tbl.cell(0, 1).unwrap().set_text("{{project}}");
529        tbl.cell(1, 0).unwrap().set_text("Status");
530        tbl.cell(1, 0).unwrap().shading("D6E4F0");
531        tbl.cell(1, 1).unwrap().set_text("{{status}}");
532    }
533
534    let mut replacements = HashMap::new();
535    replacements.insert("{{customer}}", "Tensorbee Inc.");
536    replacements.insert("{{date}}", "February 22, 2026");
537    replacements.insert("{{ref_number}}", "TB-2026-001");
538    replacements.insert("{{project}}", "Infrastructure Upgrade");
539    replacements.insert("{{status}}", "In Progress");
540    let n = doc.replace_all(&replacements);
541    doc.add_paragraph(&format!("({n} placeholders replaced above)"));
542
543    doc.add_paragraph("");
544
545    doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
546    doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
547    let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
548    doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
549
550    doc.add_paragraph("");
551
552    doc.add_paragraph("7.3 Content Insertion").style("Heading2");
553    doc.add_paragraph("Section A: First content.");
554    doc.add_paragraph("Section C: Third content.");
555    if let Some(idx) = doc.find_content_index("Section C") {
556        doc.insert_paragraph(
557            idx,
558            "Section B: Inserted between A and C via find_content_index().",
559        );
560    }
561
562    // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
563    doc.add_paragraph("").section_break(SectionBreak::NextPage);
564    doc.add_paragraph("8. Section Breaks & Orientation")
565        .style("Heading1");
566    doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
567
568    {
569        let mut tbl = doc
570            .add_table(3, 7)
571            .borders(BorderStyle::Single, 4, "2E75B6");
572        let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
573        for (c, h) in headers.iter().enumerate() {
574            tbl.cell(0, c).unwrap().set_text(h);
575            tbl.cell(0, c).unwrap().shading("2E75B6");
576        }
577        let data = [
578            [
579                "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
580            ],
581            [
582                "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
583            ],
584        ];
585        for (r, row) in data.iter().enumerate() {
586            for (c, v) in row.iter().enumerate() {
587                tbl.cell(r + 1, c).unwrap().set_text(v);
588            }
589        }
590    }
591
592    doc.add_paragraph("")
593        .section_break(SectionBreak::NextPage)
594        .section_landscape();
595
596    // ── SECTION 9: CUSTOM STYLES ──
597    doc.add_paragraph("9. Custom Styles").style("Heading1");
598    doc.add_style(
599        StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
600            .based_on("Normal")
601            .paragraph_properties(rdocx_oxml::properties::CT_PPr {
602                shading: Some(rdocx_oxml::properties::CT_Shd {
603                    val: "clear".to_string(),
604                    color: None,
605                    fill: Some("FFF2CC".to_string()),
606                }),
607                ..Default::default()
608            })
609            .run_properties(rdocx_oxml::properties::CT_RPr {
610                bold: Some(true),
611                color: Some("C45911".to_string()),
612                ..Default::default()
613            }),
614    );
615    doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
616        .style("CustomHighlight");
617
618    doc.add_paragraph("");
619
620    // ── SECTION 10: DOCUMENT INTELLIGENCE ──
621    doc.add_paragraph("10. Document Intelligence API")
622        .style("Heading1");
623
624    let wc = doc.word_count();
625    let hc = doc.headings().len();
626    let ic = doc.images().len();
627    let lc = doc.links().len();
628    doc.add_paragraph(&format!("Word count: {wc}"));
629    doc.add_paragraph(&format!("Heading count: {hc}"));
630    doc.add_paragraph(&format!("Image count: {ic}"));
631    doc.add_paragraph(&format!("Link count: {lc}"));
632
633    let outline = doc.document_outline();
634    doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
635
636    let issues = doc.audit_accessibility();
637    doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
638    for issue in &issues {
639        doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
640    }
641
642    // ── SECTION 11: DOCUMENT MERGING ──
643    doc.add_paragraph("");
644    doc.add_paragraph("11. Document Merging").style("Heading1");
645    {
646        let mut other = Document::new();
647        other.add_paragraph("This paragraph was merged from another document using append().");
648        doc.append(&other);
649    }
650
651    doc.add_paragraph("");
652
653    // ── SUMMARY ──
654    doc.add_paragraph("Summary of Demonstrated Features")
655        .style("Heading1");
656    let features = [
657        "Page setup: size, margins, header/footer distance, gutter",
658        "Document metadata: title, author, subject, keywords",
659        "Headers and footers: text, images, different first page",
660        "Background images (full-page behind text)",
661        "Table of Contents generation with bookmarks",
662        "Text formatting: bold, italic, underline styles, strike, color, size, font",
663        "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
664        "Paragraph formatting: alignment, borders, shading, spacing, indentation",
665        "Pagination controls: keep-with-next, keep-together, widow control",
666        "Bullet and numbered lists with nesting",
667        "Tab stops with dot/underscore/hyphen leaders",
668        "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
669        "Row properties: header rows, exact height, cant-split",
670        "Cell properties: width, no-wrap, vertical alignment",
671        "Inline images and header images",
672        "Placeholder replacement (single and batch)",
673        "Regex-based find and replace",
674        "Content insertion at specific positions",
675        "Section breaks with mixed portrait/landscape",
676        "Custom paragraph and character styles",
677        "Document intelligence: word count, headings, outline, images, links",
678        "Accessibility audit",
679        "Document merging (append)",
680        "Export: DOCX, PDF, HTML, Markdown",
681    ];
682    for f in &features {
683        doc.add_bullet_list_item(f, 0);
684    }
685    doc.add_paragraph("");
686    doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
687        .alignment(Alignment::Center)
688        .shading("E2EFDA")
689        .border_all(BorderStyle::Single, 2, "00AA00");
690
691    doc
692}
693
694// =============================================================================
695// 2. PROPOSAL DOCUMENT — Deep navy + gold scheme
696// =============================================================================
697fn generate_proposal(_samples_dir: &Path) -> Document {
698    let mut doc = Document::new();
699
700    // Colors: Navy #1B2A4A, Gold #C5922E, Light #F4F1EB
701    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
702    doc.set_margins(
703        Length::inches(1.0),
704        Length::inches(1.0),
705        Length::inches(1.0),
706        Length::inches(1.0),
707    );
708    doc.set_title("AI Platform Modernization Proposal");
709    doc.set_author("Walter White");
710    doc.set_subject("Technology Proposal");
711    doc.set_keywords("proposal, AI, modernization, Tensorbee");
712
713    // Banner header
714    let logo_img = create_logo_png(220, 48);
715    let banner = build_header_banner_xml(
716        "rId1",
717        &BannerOpts {
718            bg_color: "1B2A4A",
719            banner_width: 7772400,
720            banner_height: 914400,
721            logo_width: 2011680,
722            logo_height: 438912,
723            logo_x_offset: 295125,
724            logo_y_offset: 237744,
725        },
726    );
727    doc.set_raw_header_with_images(
728        banner,
729        &[("rId1", &logo_img, "logo.png")],
730        rdocx_oxml::header_footer::HdrFtrType::Default,
731    );
732    doc.set_different_first_page(true);
733    let cover_banner = build_header_banner_xml(
734        "rId1",
735        &BannerOpts {
736            bg_color: "C5922E",
737            banner_width: 7772400,
738            banner_height: 914400,
739            logo_width: 2011680,
740            logo_height: 438912,
741            logo_x_offset: 295125,
742            logo_y_offset: 237744,
743        },
744    );
745    doc.set_raw_header_with_images(
746        cover_banner,
747        &[("rId1", &logo_img, "logo.png")],
748        rdocx_oxml::header_footer::HdrFtrType::First,
749    );
750    doc.set_margins(
751        Length::twips(2292),
752        Length::twips(1440),
753        Length::twips(1440),
754        Length::twips(1440),
755    );
756    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
757    doc.set_footer("Tensorbee — Confidential");
758
759    // Custom styles
760    doc.add_style(
761        StyleBuilder::paragraph("ProposalTitle", "Proposal Title")
762            .based_on("Normal")
763            .run_properties(rdocx_oxml::properties::CT_RPr {
764                bold: Some(true),
765                font_ascii: Some("Georgia".to_string()),
766                font_hansi: Some("Georgia".to_string()),
767                sz: Some(rdocx_oxml::HalfPoint(56)), // half-points → 28pt
768                color: Some("1B2A4A".to_string()),
769                ..Default::default()
770            }),
771    );
772
773    // ── Cover Page ──
774    for _ in 0..4 {
775        doc.add_paragraph("");
776    }
777    doc.add_paragraph("AI Platform Modernization")
778        .style("ProposalTitle")
779        .alignment(Alignment::Center);
780    doc.add_paragraph("")
781        .alignment(Alignment::Center)
782        .border_bottom(BorderStyle::Single, 8, "C5922E");
783    {
784        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
785        p.add_run("Prepared for Global Financial Corp.")
786            .size(14.0)
787            .color("666666")
788            .italic(true);
789    }
790    {
791        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
792        p.add_run("Prepared by: Walter White, CEO — Tensorbee")
793            .size(12.0)
794            .color("888888");
795    }
796    {
797        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
798        p.add_run("February 22, 2026").size(12.0).color("888888");
799    }
800
801    // ── TOC ──
802    doc.add_paragraph("").page_break_before(true);
803    doc.add_paragraph("Table of Contents").style("Heading1");
804    // We'll add a manual-style TOC since the insert_toc goes at a specific position
805    doc.insert_toc(doc.content_count(), 2);
806
807    // ── Executive Summary ──
808    doc.add_paragraph("").page_break_before(true);
809    doc.add_paragraph("Executive Summary").style("Heading1");
810    doc.add_paragraph(
811        "Tensorbee proposes a comprehensive modernization of Global Financial Corp's AI platform, \
812         transitioning from legacy batch-processing systems to a real-time inference architecture. \
813         This transformation will reduce model deployment time from weeks to hours, improve \
814         prediction accuracy by an estimated 23%, and deliver $4.2M in annual operational savings.",
815    )
816    .first_line_indent(Length::inches(0.3));
817    doc.add_paragraph(
818        "The project spans three phases over 18 months, with a total investment of $2.8M. \
819         Tensorbee brings deep expertise in ML infrastructure, having successfully completed \
820         similar transformations for three Fortune 500 financial institutions.",
821    )
822    .first_line_indent(Length::inches(0.3));
823
824    // Key metrics table
825    doc.add_paragraph("");
826    {
827        let mut tbl = doc
828            .add_table(5, 2)
829            .borders(BorderStyle::Single, 4, "1B2A4A")
830            .width_pct(80.0);
831        tbl.cell(0, 0).unwrap().set_text("Key Metric");
832        tbl.cell(0, 0).unwrap().shading("1B2A4A");
833        tbl.cell(0, 1).unwrap().set_text("Value");
834        tbl.cell(0, 1).unwrap().shading("1B2A4A");
835        let rows = [
836            ("Total Investment", "$2.8M"),
837            ("Annual Savings", "$4.2M"),
838            ("ROI (Year 1)", "150%"),
839            ("Timeline", "18 months"),
840        ];
841        for (i, (k, v)) in rows.iter().enumerate() {
842            tbl.cell(i + 1, 0).unwrap().set_text(k);
843            if i % 2 == 0 {
844                tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
845            }
846            tbl.cell(i + 1, 1).unwrap().set_text(v);
847            if i % 2 == 0 {
848                tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
849            }
850        }
851    }
852
853    // ── Problem Statement ──
854    doc.add_paragraph("").page_break_before(true);
855    doc.add_paragraph("Problem Statement").style("Heading1");
856    doc.add_paragraph(
857        "Global Financial Corp's current AI infrastructure faces three critical challenges:",
858    );
859    doc.add_numbered_list_item("Deployment Latency — New models take 3-4 weeks to deploy to production, compared to the industry benchmark of 2-3 days.", 0);
860    doc.add_numbered_list_item("Scalability Constraints — The batch processing architecture cannot handle real-time inference demands during peak trading hours.", 0);
861    doc.add_numbered_list_item("Technical Debt — Legacy Python 2.7 codebases and manual deployment scripts create reliability risks and slow iteration speed.", 0);
862
863    // ── Proposed Solution ──
864    doc.add_paragraph("").page_break_before(true);
865    doc.add_paragraph("Proposed Solution").style("Heading1");
866
867    doc.add_paragraph("Phase 1: Foundation (Months 1-6)")
868        .style("Heading2");
869    doc.add_bullet_list_item("Deploy Kubernetes-based ML serving infrastructure", 0);
870    doc.add_bullet_list_item("Implement CI/CD pipeline for model deployment", 0);
871    doc.add_bullet_list_item("Migrate top 5 production models to new platform", 0);
872
873    doc.add_paragraph("Phase 2: Expansion (Months 7-12)")
874        .style("Heading2");
875    doc.add_bullet_list_item(
876        "Real-time feature engineering pipeline (Apache Kafka + Flink)",
877        0,
878    );
879    doc.add_bullet_list_item("A/B testing framework for model variants", 0);
880    doc.add_bullet_list_item("Automated model monitoring and drift detection", 0);
881
882    doc.add_paragraph("Phase 3: Optimization (Months 13-18)")
883        .style("Heading2");
884    doc.add_bullet_list_item("GPU inference optimization (TensorRT/ONNX Runtime)", 0);
885    doc.add_bullet_list_item("Multi-region deployment for latency reduction", 0);
886    doc.add_bullet_list_item(
887        "Self-service model deployment portal for data science teams",
888        0,
889    );
890
891    // ── Budget ──
892    doc.add_paragraph("Budget Breakdown").style("Heading1");
893    {
894        let mut tbl = doc
895            .add_table(6, 3)
896            .borders(BorderStyle::Single, 4, "1B2A4A")
897            .width_pct(100.0);
898        tbl.cell(0, 0).unwrap().set_text("Category");
899        tbl.cell(0, 0).unwrap().shading("1B2A4A");
900        tbl.cell(0, 1).unwrap().set_text("Cost");
901        tbl.cell(0, 1).unwrap().shading("1B2A4A");
902        tbl.cell(0, 2).unwrap().set_text("Notes");
903        tbl.cell(0, 2).unwrap().shading("1B2A4A");
904        let items = [
905            (
906                "Infrastructure",
907                "$450,000",
908                "Cloud compute + GPU instances",
909            ),
910            ("Engineering Services", "$1,600,000", "12 FTE x 18 months"),
911            (
912                "Software Licenses",
913                "$350,000",
914                "Kafka, monitoring, CI/CD tools",
915            ),
916            (
917                "Training & Enablement",
918                "$200,000",
919                "Team training, documentation",
920            ),
921            ("Contingency (10%)", "$200,000", "Risk buffer"),
922        ];
923        for (i, (cat, cost, note)) in items.iter().enumerate() {
924            tbl.cell(i + 1, 0).unwrap().set_text(cat);
925            tbl.cell(i + 1, 1).unwrap().set_text(cost);
926            tbl.cell(i + 1, 2).unwrap().set_text(note);
927            if i % 2 == 0 {
928                tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
929                tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
930                tbl.cell(i + 1, 2).unwrap().shading("F4F1EB");
931            }
932        }
933    }
934
935    // ── Closing ──
936    doc.add_paragraph("");
937    doc.add_paragraph("Next Steps").style("Heading1");
938    doc.add_numbered_list_item(
939        "Schedule technical deep-dive with GFC engineering team (Week 1)",
940        0,
941    );
942    doc.add_numbered_list_item("Finalize scope and sign SOW (Week 2-3)", 0);
943    doc.add_numbered_list_item("Kick off Phase 1 with joint planning session (Week 4)", 0);
944
945    doc.add_paragraph("");
946    {
947        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
948        p.add_run("Walter White")
949            .bold(true)
950            .size(14.0)
951            .color("1B2A4A");
952    }
953    {
954        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
955        p.add_run("CEO, Tensorbee | walter@tensorbee.com")
956            .size(10.0)
957            .color("888888");
958    }
959
960    doc
961}
962
963// =============================================================================
964// 3. QUOTE / BILL OF MATERIALS — Teal + orange scheme
965// =============================================================================
966fn generate_quote(_samples_dir: &Path) -> Document {
967    let mut doc = Document::new();
968
969    // Colors: Teal #008B8B, Dark #1A3C3C, Orange #E07020, Light #F0F8F8
970    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
971    doc.set_margins(
972        Length::inches(0.75),
973        Length::inches(0.75),
974        Length::inches(0.75),
975        Length::inches(0.75),
976    );
977    doc.set_title("Quotation QT-2026-0147");
978    doc.set_author("Walter White");
979    doc.set_keywords("quote, BOM, Tensorbee");
980
981    doc.set_header("Tensorbee — Quotation");
982    doc.set_footer("QT-2026-0147 | Page");
983
984    // ── Header Block ──
985    {
986        let mut p = doc.add_paragraph("").alignment(Alignment::Left);
987        p.add_run("TENSORBEE")
988            .bold(true)
989            .size(28.0)
990            .color("008B8B")
991            .font("Helvetica");
992    }
993    doc.add_paragraph("123 Innovation Drive, Suite 400")
994        .alignment(Alignment::Left);
995    doc.add_paragraph("San Francisco, CA 94105 | +1 (415) 555-0199")
996        .alignment(Alignment::Left);
997    doc.add_paragraph("")
998        .border_bottom(BorderStyle::Single, 8, "008B8B");
999
1000    // Quote meta
1001    doc.add_paragraph("");
1002    {
1003        let mut tbl = doc.add_table(4, 4).width_pct(100.0);
1004        tbl.cell(0, 0).unwrap().set_text("QUOTATION");
1005        tbl.cell(0, 0).unwrap().shading("008B8B").grid_span(2);
1006        tbl.cell(0, 2).unwrap().set_text("DATE");
1007        tbl.cell(0, 2).unwrap().shading("008B8B");
1008        tbl.cell(0, 3).unwrap().set_text("VALID UNTIL");
1009        tbl.cell(0, 3).unwrap().shading("008B8B");
1010        tbl.cell(1, 0).unwrap().set_text("Quote #:");
1011        tbl.cell(1, 1).unwrap().set_text("QT-2026-0147");
1012        tbl.cell(1, 2).unwrap().set_text("Feb 22, 2026");
1013        tbl.cell(1, 3).unwrap().set_text("Mar 22, 2026");
1014        tbl.cell(2, 0).unwrap().set_text("Prepared by:");
1015        tbl.cell(2, 1).unwrap().set_text("Walter White");
1016        tbl.cell(2, 2).unwrap().set_text("Payment:");
1017        tbl.cell(2, 3).unwrap().set_text("Net 30");
1018        tbl.cell(3, 0).unwrap().set_text("Customer:");
1019        tbl.cell(3, 1).unwrap().set_text("Meridian Dynamics LLC");
1020        tbl.cell(3, 2).unwrap().set_text("Currency:");
1021        tbl.cell(3, 3).unwrap().set_text("USD");
1022    }
1023
1024    doc.add_paragraph("");
1025
1026    // ── Bill of Materials ──
1027    {
1028        let mut p = doc.add_paragraph("");
1029        p.add_run("Bill of Materials")
1030            .bold(true)
1031            .size(16.0)
1032            .color("1A3C3C");
1033    }
1034    doc.add_paragraph("");
1035
1036    {
1037        let mut tbl = doc
1038            .add_table(10, 6)
1039            .borders(BorderStyle::Single, 2, "008B8B")
1040            .width_pct(100.0)
1041            .layout_fixed();
1042        // Headers
1043        let hdrs = [
1044            "#",
1045            "Part Number",
1046            "Description",
1047            "Qty",
1048            "Unit Price",
1049            "Total",
1050        ];
1051        for (c, h) in hdrs.iter().enumerate() {
1052            tbl.cell(0, c).unwrap().set_text(h);
1053            tbl.cell(0, c).unwrap().shading("008B8B");
1054        }
1055
1056        let items: Vec<(&str, &str, &str, &str, &str)> = vec![
1057            (
1058                "TB-GPU-A100",
1059                "NVIDIA A100 80GB GPU",
1060                "4",
1061                "$12,500.00",
1062                "$50,000.00",
1063            ),
1064            (
1065                "TB-SRV-R750",
1066                "Dell R750xa Server Chassis",
1067                "2",
1068                "$8,200.00",
1069                "$16,400.00",
1070            ),
1071            (
1072                "TB-RAM-256G",
1073                "256GB DDR5 ECC Memory Module",
1074                "8",
1075                "$890.00",
1076                "$7,120.00",
1077            ),
1078            (
1079                "TB-SSD-3840",
1080                "3.84TB NVMe U.2 SSD",
1081                "8",
1082                "$1,150.00",
1083                "$9,200.00",
1084            ),
1085            (
1086                "TB-NET-CX7",
1087                "ConnectX-7 200GbE NIC",
1088                "4",
1089                "$1,800.00",
1090                "$7,200.00",
1091            ),
1092            (
1093                "TB-SW-48P",
1094                "48-Port 100GbE Switch",
1095                "1",
1096                "$22,000.00",
1097                "$22,000.00",
1098            ),
1099            (
1100                "TB-CAB-RACK",
1101                "42U Server Rack + PDU",
1102                "1",
1103                "$4,500.00",
1104                "$4,500.00",
1105            ),
1106            (
1107                "TB-SVC-INST",
1108                "Installation & Configuration",
1109                "1",
1110                "$8,500.00",
1111                "$8,500.00",
1112            ),
1113            (
1114                "TB-SVC-SUPP",
1115                "3-Year Premium Support",
1116                "1",
1117                "$15,000.00",
1118                "$15,000.00",
1119            ),
1120        ];
1121
1122        for (i, (pn, desc, qty, unit, total)) in items.iter().enumerate() {
1123            let row = i + 1;
1124            tbl.cell(row, 0).unwrap().set_text(&format!("{}", i + 1));
1125            tbl.cell(row, 1).unwrap().set_text(pn);
1126            tbl.cell(row, 2).unwrap().set_text(desc);
1127            tbl.cell(row, 3).unwrap().set_text(qty);
1128            tbl.cell(row, 4).unwrap().set_text(unit);
1129            tbl.cell(row, 5).unwrap().set_text(total);
1130            if i % 2 == 0 {
1131                for c in 0..6 {
1132                    tbl.cell(row, c).unwrap().shading("F0F8F8");
1133                }
1134            }
1135        }
1136    }
1137
1138    doc.add_paragraph("");
1139
1140    // ── Totals ──
1141    {
1142        let mut tbl = doc
1143            .add_table(4, 2)
1144            .borders(BorderStyle::Single, 2, "008B8B")
1145            .width(Length::inches(3.5))
1146            .alignment(Alignment::Right);
1147        tbl.cell(0, 0).unwrap().set_text("Subtotal");
1148        tbl.cell(0, 1).unwrap().set_text("$139,920.00");
1149        tbl.cell(1, 0).unwrap().set_text("Shipping & Handling");
1150        tbl.cell(1, 1).unwrap().set_text("$2,500.00");
1151        tbl.cell(2, 0).unwrap().set_text("Tax (8.625%)");
1152        tbl.cell(2, 1).unwrap().set_text("$12,068.10");
1153        tbl.cell(3, 0).unwrap().set_text("TOTAL");
1154        tbl.cell(3, 0).unwrap().shading("E07020");
1155        tbl.cell(3, 1).unwrap().set_text("$154,488.10");
1156        tbl.cell(3, 1).unwrap().shading("E07020");
1157    }
1158
1159    doc.add_paragraph("");
1160
1161    // ── Terms & Conditions ──
1162    {
1163        let mut p = doc.add_paragraph("");
1164        p.add_run("Terms & Conditions")
1165            .bold(true)
1166            .size(14.0)
1167            .color("1A3C3C");
1168    }
1169    doc.add_numbered_list_item(
1170        "This quotation is valid for 30 calendar days from the date of issue.",
1171        0,
1172    );
1173    doc.add_numbered_list_item(
1174        "All prices are in USD and exclusive of applicable taxes unless stated.",
1175        0,
1176    );
1177    doc.add_numbered_list_item("Standard lead time is 4-6 weeks from PO receipt.", 0);
1178    doc.add_numbered_list_item(
1179        "Payment terms: Net 30 from invoice date. 2% discount for payment within 10 days.",
1180        0,
1181    );
1182    doc.add_numbered_list_item(
1183        "Warranty: 3-year manufacturer warranty on all hardware components.",
1184        0,
1185    );
1186    doc.add_numbered_list_item(
1187        "Returns subject to 15% restocking fee if initiated after 14 days.",
1188        0,
1189    );
1190
1191    doc.add_paragraph("");
1192    doc.add_paragraph("")
1193        .border_bottom(BorderStyle::Single, 4, "008B8B");
1194    doc.add_paragraph("");
1195    {
1196        let mut p = doc.add_paragraph("");
1197        p.add_run("Accepted by: ").bold(true);
1198        p.add_run("___________________________ Date: ___________");
1199    }
1200    {
1201        let mut p = doc.add_paragraph("");
1202        p.add_run("Print Name: ").bold(true);
1203        p.add_run("___________________________ Title: ___________");
1204    }
1205
1206    doc
1207}
1208
1209// =============================================================================
1210// 4. INVOICE — Crimson + charcoal scheme
1211// =============================================================================
1212fn generate_invoice(_samples_dir: &Path) -> Document {
1213    let mut doc = Document::new();
1214
1215    // Colors: Crimson #B22222, Charcoal #333333, Light #FAF0F0
1216    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1217    doc.set_margins(
1218        Length::inches(0.75),
1219        Length::inches(0.75),
1220        Length::inches(0.75),
1221        Length::inches(0.75),
1222    );
1223    doc.set_title("Invoice INV-2026-0392");
1224    doc.set_author("Walter White");
1225    doc.set_keywords("invoice, Tensorbee");
1226
1227    doc.set_footer("Tensorbee — Thank you for your business!");
1228
1229    // ── Company & Invoice Header ──
1230    {
1231        let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1232        // Company name left, INVOICE right
1233        {
1234            let mut cell = tbl.cell(0, 0).unwrap();
1235            let mut p = cell.add_paragraph("");
1236            p.add_run("TENSORBEE")
1237                .bold(true)
1238                .size(32.0)
1239                .color("B22222")
1240                .font("Helvetica");
1241        }
1242        {
1243            let mut cell = tbl.cell(0, 1).unwrap();
1244            let mut p = cell.add_paragraph("");
1245            p.add_run("INVOICE").bold(true).size(32.0).color("333333");
1246        }
1247        tbl.cell(1, 0)
1248            .unwrap()
1249            .set_text("123 Innovation Drive, Suite 400");
1250        tbl.cell(1, 1).unwrap().set_text("Invoice #: INV-2026-0392");
1251        tbl.cell(2, 0).unwrap().set_text("San Francisco, CA 94105");
1252        tbl.cell(2, 1).unwrap().set_text("Date: February 22, 2026");
1253        tbl.cell(3, 0).unwrap().set_text("walter@tensorbee.com");
1254        tbl.cell(3, 1).unwrap().set_text("Due Date: March 24, 2026");
1255    }
1256
1257    doc.add_paragraph("")
1258        .border_bottom(BorderStyle::Thick, 8, "B22222");
1259    doc.add_paragraph("");
1260
1261    // ── Bill To / Ship To ──
1262    {
1263        let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1264        tbl.cell(0, 0).unwrap().set_text("BILL TO");
1265        tbl.cell(0, 0).unwrap().shading("B22222");
1266        tbl.cell(0, 1).unwrap().set_text("SHIP TO");
1267        tbl.cell(0, 1).unwrap().shading("B22222");
1268        tbl.cell(1, 0).unwrap().set_text("Meridian Dynamics LLC");
1269        tbl.cell(1, 1).unwrap().set_text("Meridian Dynamics LLC");
1270        tbl.cell(2, 0).unwrap().set_text("456 Enterprise Blvd");
1271        tbl.cell(2, 1).unwrap().set_text("Attn: Server Room B");
1272        tbl.cell(3, 0).unwrap().set_text("Austin, TX 78701");
1273        tbl.cell(3, 1)
1274            .unwrap()
1275            .set_text("456 Enterprise Blvd, Austin, TX 78701");
1276    }
1277
1278    doc.add_paragraph("");
1279
1280    // ── Line Items ──
1281    {
1282        let mut tbl = doc
1283            .add_table(8, 5)
1284            .borders(BorderStyle::Single, 2, "B22222")
1285            .width_pct(100.0);
1286        let hdrs = ["Description", "Qty", "Unit Price", "Tax", "Amount"];
1287        for (c, h) in hdrs.iter().enumerate() {
1288            tbl.cell(0, c).unwrap().set_text(h);
1289            tbl.cell(0, c).unwrap().shading("B22222");
1290        }
1291        let items = [
1292            (
1293                "ML Infrastructure Setup — Phase 1",
1294                "1",
1295                "$45,000.00",
1296                "$3,881.25",
1297                "$48,881.25",
1298            ),
1299            (
1300                "Data Pipeline Development (160 hrs)",
1301                "160",
1302                "$225.00",
1303                "$3,105.00",
1304                "$39,105.00",
1305            ),
1306            (
1307                "GPU Cluster Configuration",
1308                "1",
1309                "$12,000.00",
1310                "$1,035.00",
1311                "$13,035.00",
1312            ),
1313            (
1314                "API Gateway Implementation",
1315                "1",
1316                "$18,500.00",
1317                "$1,595.63",
1318                "$20,095.63",
1319            ),
1320            (
1321                "Load Testing & QA (80 hrs)",
1322                "80",
1323                "$195.00",
1324                "$1,345.50",
1325                "$16,945.50",
1326            ),
1327            (
1328                "Documentation & Training",
1329                "1",
1330                "$8,000.00",
1331                "$690.00",
1332                "$8,690.00",
1333            ),
1334            (
1335                "Project Management (3 months)",
1336                "3",
1337                "$6,500.00",
1338                "$1,679.63",
1339                "$21,179.63",
1340            ),
1341        ];
1342        for (i, (desc, qty, unit, tax, amt)) in items.iter().enumerate() {
1343            let r = i + 1;
1344            tbl.cell(r, 0).unwrap().set_text(desc);
1345            tbl.cell(r, 1).unwrap().set_text(qty);
1346            tbl.cell(r, 2).unwrap().set_text(unit);
1347            tbl.cell(r, 3).unwrap().set_text(tax);
1348            tbl.cell(r, 4).unwrap().set_text(amt);
1349            if i % 2 == 0 {
1350                for c in 0..5 {
1351                    tbl.cell(r, c).unwrap().shading("FAF0F0");
1352                }
1353            }
1354        }
1355    }
1356
1357    doc.add_paragraph("");
1358
1359    // ── Totals ──
1360    {
1361        let mut tbl = doc
1362            .add_table(4, 2)
1363            .borders(BorderStyle::Single, 2, "B22222")
1364            .width(Length::inches(3.0))
1365            .alignment(Alignment::Right);
1366        tbl.cell(0, 0).unwrap().set_text("Subtotal");
1367        tbl.cell(0, 1).unwrap().set_text("$154,600.00");
1368        tbl.cell(1, 0).unwrap().set_text("Tax (8.625%)");
1369        tbl.cell(1, 1).unwrap().set_text("$13,334.25");
1370        tbl.cell(2, 0).unwrap().set_text("Discount (5%)");
1371        tbl.cell(2, 1).unwrap().set_text("-$7,730.00");
1372        tbl.cell(3, 0).unwrap().set_text("AMOUNT DUE");
1373        tbl.cell(3, 0).unwrap().shading("B22222");
1374        tbl.cell(3, 1).unwrap().set_text("$160,204.25");
1375        tbl.cell(3, 1).unwrap().shading("B22222");
1376    }
1377
1378    doc.add_paragraph("");
1379    doc.add_paragraph("");
1380
1381    // ── Payment Details ──
1382    {
1383        let mut p = doc.add_paragraph("");
1384        p.add_run("Payment Details")
1385            .bold(true)
1386            .size(14.0)
1387            .color("333333");
1388    }
1389    doc.add_paragraph("Bank: Silicon Valley Bank")
1390        .indent_left(Length::inches(0.3));
1391    doc.add_paragraph("Account: Tensorbee Inc. — 0847-2953-1120")
1392        .indent_left(Length::inches(0.3));
1393    doc.add_paragraph("Routing: 121140399")
1394        .indent_left(Length::inches(0.3));
1395    doc.add_paragraph("Swift: SVBKUS6S")
1396        .indent_left(Length::inches(0.3));
1397
1398    doc.add_paragraph("");
1399
1400    doc.add_paragraph("Please include invoice number INV-2026-0392 in the payment reference.")
1401        .shading("FAF0F0")
1402        .border_all(BorderStyle::Single, 2, "B22222");
1403
1404    doc
1405}
1406
1407// =============================================================================
1408// 5. REPORT — Forest green + earth tones, with images & hierarchical sections
1409// =============================================================================
1410fn generate_report(_samples_dir: &Path) -> Document {
1411    let mut doc = Document::new();
1412
1413    // Colors: Forest #2D5016, Sage #6B8E23, Earth #8B7355, Cream #FFFAF0
1414    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1415    doc.set_margins(
1416        Length::inches(1.0),
1417        Length::inches(1.0),
1418        Length::inches(1.0),
1419        Length::inches(1.0),
1420    );
1421    doc.set_title("Q4 2025 Environmental Impact Report");
1422    doc.set_author("Walter White");
1423    doc.set_subject("Quarterly Environmental Report");
1424    doc.set_keywords("environment, sustainability, report, Tensorbee");
1425
1426    doc.set_different_first_page(true);
1427    doc.set_first_page_header("");
1428    doc.set_header("Tensorbee — Q4 2025 Environmental Impact Report");
1429    doc.set_footer("CONFIDENTIAL — Page");
1430
1431    // ── Cover ──
1432    let cover_bg = create_sample_png(612, 792, [20, 50, 15]);
1433    doc.add_background_image(&cover_bg, "report_cover.png");
1434
1435    for _ in 0..5 {
1436        doc.add_paragraph("");
1437    }
1438    {
1439        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1440        p.add_run("Q4 2025")
1441            .bold(true)
1442            .size(48.0)
1443            .color("FFFFFF")
1444            .font("Georgia");
1445    }
1446    {
1447        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1448        p.add_run("Environmental Impact Report")
1449            .size(24.0)
1450            .color("90EE90")
1451            .font("Georgia")
1452            .italic(true);
1453    }
1454    doc.add_paragraph("");
1455    {
1456        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1457        p.add_run("Tensorbee — Sustainability Division")
1458            .size(14.0)
1459            .color("C0C0C0");
1460    }
1461    {
1462        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1463        p.add_run("Prepared by Walter White, Chief Sustainability Officer")
1464            .size(11.0)
1465            .color("AAAAAA");
1466    }
1467
1468    // ── TOC ──
1469    doc.add_paragraph("").page_break_before(true);
1470    doc.insert_toc(doc.content_count(), 3);
1471
1472    // ── Section 1: Executive Overview ──
1473    doc.add_paragraph("").page_break_before(true);
1474    doc.add_paragraph("Executive Overview").style("Heading1");
1475    doc.add_paragraph(
1476        "This report presents Tensorbee's environmental performance for Q4 2025. Our \
1477         sustainability initiatives have yielded a 34% reduction in carbon emissions compared \
1478         to Q4 2024, exceeding our target of 25%. Key achievements include the transition to \
1479         100% renewable energy in our primary data centers and the launch of our carbon offset \
1480         marketplace.",
1481    )
1482    .first_line_indent(Length::inches(0.3));
1483
1484    // Image: performance chart
1485    let chart_img = create_chart_png(400, 200);
1486    doc.add_paragraph("");
1487    doc.add_picture(
1488        &chart_img,
1489        "performance_chart.png",
1490        Length::inches(5.0),
1491        Length::inches(2.5),
1492    )
1493    .alignment(Alignment::Center);
1494    {
1495        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1496        p.add_run("Figure 1: Quarterly Carbon Emissions (tonnes CO2e)")
1497            .italic(true)
1498            .size(9.0)
1499            .color("666666");
1500    }
1501
1502    // ── Section 2: Energy Consumption ──
1503    doc.add_paragraph("").page_break_before(true);
1504    doc.add_paragraph("Energy Consumption").style("Heading1");
1505
1506    doc.add_paragraph("Data Center Operations")
1507        .style("Heading2");
1508    doc.add_paragraph(
1509        "Our three primary data centers consumed a combined 4.2 GWh in Q4 2025, \
1510         a 12% reduction from Q3 2025 driven by improved cooling efficiency and \
1511         server consolidation.",
1512    );
1513
1514    // Data table
1515    {
1516        let mut tbl = doc
1517            .add_table(5, 4)
1518            .borders(BorderStyle::Single, 4, "2D5016")
1519            .width_pct(100.0);
1520        let hdrs = ["Data Center", "Capacity (MW)", "Usage (GWh)", "PUE"];
1521        for (c, h) in hdrs.iter().enumerate() {
1522            tbl.cell(0, c).unwrap().set_text(h);
1523            tbl.cell(0, c).unwrap().shading("2D5016");
1524        }
1525        let rows = [
1526            ("San Francisco (Primary)", "3.2", "1.8", "1.12"),
1527            ("Dublin (EU)", "2.1", "1.2", "1.18"),
1528            ("Singapore (APAC)", "1.8", "1.2", "1.24"),
1529            ("TOTAL", "7.1", "4.2", "1.17 avg"),
1530        ];
1531        for (i, (dc, cap, use_, pue)) in rows.iter().enumerate() {
1532            tbl.cell(i + 1, 0).unwrap().set_text(dc);
1533            tbl.cell(i + 1, 1).unwrap().set_text(cap);
1534            tbl.cell(i + 1, 2).unwrap().set_text(use_);
1535            tbl.cell(i + 1, 3).unwrap().set_text(pue);
1536            if i == 3 {
1537                for c in 0..4 {
1538                    tbl.cell(i + 1, c).unwrap().shading("E2EFDA");
1539                }
1540            }
1541        }
1542    }
1543
1544    doc.add_paragraph("");
1545
1546    doc.add_paragraph("Renewable Energy Mix").style("Heading2");
1547    doc.add_paragraph("Breakdown of energy sources across all facilities:");
1548
1549    doc.add_bullet_list_item("Solar PV: 42% (1.76 GWh)", 0);
1550    doc.add_bullet_list_item("Wind Power Purchase Agreements: 38% (1.60 GWh)", 0);
1551    doc.add_bullet_list_item("Hydroelectric: 12% (0.50 GWh)", 0);
1552    doc.add_bullet_list_item("Grid (non-renewable): 8% (0.34 GWh)", 0);
1553
1554    // ── Section 3: Water & Waste ──
1555    doc.add_paragraph("Water & Waste Management")
1556        .style("Heading1");
1557
1558    doc.add_paragraph("Water Usage").style("Heading2");
1559    doc.add_paragraph(
1560        "Total water consumption was 12.4 million gallons, a 15% reduction from Q3. \
1561         Our closed-loop cooling systems now recycle 78% of water used in cooling operations.",
1562    );
1563
1564    doc.add_paragraph("Waste Reduction").style("Heading2");
1565    doc.add_paragraph("E-waste management results for Q4:")
1566        .keep_with_next(true);
1567    {
1568        let mut tbl = doc
1569            .add_table(4, 3)
1570            .borders(BorderStyle::Single, 2, "6B8E23")
1571            .width_pct(80.0);
1572        tbl.cell(0, 0).unwrap().set_text("Category");
1573        tbl.cell(0, 0).unwrap().shading("6B8E23");
1574        tbl.cell(0, 1).unwrap().set_text("Weight (kg)");
1575        tbl.cell(0, 1).unwrap().shading("6B8E23");
1576        tbl.cell(0, 2).unwrap().set_text("Recycled %");
1577        tbl.cell(0, 2).unwrap().shading("6B8E23");
1578        let rows = [
1579            ("Server Hardware", "2,450", "94%"),
1580            ("Networking Equipment", "820", "91%"),
1581            ("Storage Media", "340", "99%"),
1582        ];
1583        for (i, (cat, wt, pct)) in rows.iter().enumerate() {
1584            tbl.cell(i + 1, 0).unwrap().set_text(cat);
1585            tbl.cell(i + 1, 1).unwrap().set_text(wt);
1586            tbl.cell(i + 1, 2).unwrap().set_text(pct);
1587        }
1588    }
1589
1590    // ── Section 4: Initiatives ──
1591    doc.add_paragraph("").page_break_before(true);
1592    doc.add_paragraph("Q1 2026 Initiatives").style("Heading1");
1593
1594    doc.add_paragraph("Planned Programs").style("Heading2");
1595    doc.add_numbered_list_item(
1596        "Deploy on-site battery storage at SF data center (2 MWh capacity)",
1597        0,
1598    );
1599    doc.add_numbered_list_item("Pilot immersion cooling in Dublin facility", 0);
1600    doc.add_numbered_list_item("Launch employee commute carbon offset program", 0);
1601    doc.add_numbered_list_item("Achieve ISO 14001 certification for Singapore facility", 0);
1602
1603    doc.add_paragraph("Investment Targets").style("Heading2");
1604    doc.add_paragraph("Sustainability CapEx allocation for FY2026:")
1605        .keep_with_next(true);
1606    {
1607        let mut tbl = doc
1608            .add_table(5, 2)
1609            .borders(BorderStyle::Single, 4, "2D5016");
1610        tbl.cell(0, 0).unwrap().set_text("Initiative");
1611        tbl.cell(0, 0).unwrap().shading("2D5016");
1612        tbl.cell(0, 1).unwrap().set_text("Budget");
1613        tbl.cell(0, 1).unwrap().shading("2D5016");
1614        let rows = [
1615            ("Battery Storage", "$1.2M"),
1616            ("Immersion Cooling Pilot", "$800K"),
1617            ("Solar Panel Expansion", "$2.1M"),
1618            ("Carbon Credits", "$500K"),
1619        ];
1620        for (i, (init, budget)) in rows.iter().enumerate() {
1621            tbl.cell(i + 1, 0).unwrap().set_text(init);
1622            tbl.cell(i + 1, 1).unwrap().set_text(budget);
1623            if i % 2 == 0 {
1624                tbl.cell(i + 1, 0).unwrap().shading("FFFAF0");
1625                tbl.cell(i + 1, 1).unwrap().shading("FFFAF0");
1626            }
1627        }
1628    }
1629
1630    // Image: sustainability roadmap
1631    let roadmap_img = create_sample_png(500, 100, [40, 80, 30]);
1632    doc.add_paragraph("");
1633    doc.add_picture(
1634        &roadmap_img,
1635        "roadmap.png",
1636        Length::inches(6.0),
1637        Length::inches(1.2),
1638    )
1639    .alignment(Alignment::Center);
1640    {
1641        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1642        p.add_run("Figure 2: 2026 Sustainability Roadmap")
1643            .italic(true)
1644            .size(9.0)
1645            .color("666666");
1646    }
1647
1648    doc.add_paragraph("");
1649    doc.add_paragraph(
1650        "For questions about this report, contact Walter White at sustainability@tensorbee.com.",
1651    )
1652    .shading("FFFAF0")
1653    .border_all(BorderStyle::Single, 2, "2D5016");
1654
1655    doc
1656}
1657
1658// =============================================================================
1659// 6. LETTER — Slate blue + silver scheme
1660// =============================================================================
1661fn generate_letter(_samples_dir: &Path) -> Document {
1662    let mut doc = Document::new();
1663
1664    // Colors: Slate #4A5568, Blue accent #3182CE
1665    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1666    doc.set_margins(
1667        Length::inches(1.25),
1668        Length::inches(1.25),
1669        Length::inches(1.25),
1670        Length::inches(1.25),
1671    );
1672    doc.set_title("Business Letter");
1673    doc.set_author("Walter White");
1674
1675    // Banner header using raw XML
1676    let logo_img = create_logo_png(220, 48);
1677    let banner = build_header_banner_xml(
1678        "rId1",
1679        &BannerOpts {
1680            bg_color: "4A5568",
1681            banner_width: 7772400,
1682            banner_height: 731520, // ~0.8"
1683            logo_width: 2011680,
1684            logo_height: 438912,
1685            logo_x_offset: 295125,
1686            logo_y_offset: 146304,
1687        },
1688    );
1689    doc.set_raw_header_with_images(
1690        banner,
1691        &[("rId1", &logo_img, "logo.png")],
1692        rdocx_oxml::header_footer::HdrFtrType::Default,
1693    );
1694    doc.set_margins(
1695        Length::twips(2000),
1696        Length::twips(1800),
1697        Length::twips(1440),
1698        Length::twips(1800),
1699    );
1700    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
1701
1702    // Footer with contact
1703    doc.set_footer(
1704        "Tensorbee | 123 Innovation Drive, Suite 400 | San Francisco, CA 94105 | tensorbee.com",
1705    );
1706
1707    // ── Sender Address ──
1708    doc.add_paragraph("Walter White");
1709    doc.add_paragraph("Chief Executive Officer");
1710    doc.add_paragraph("Tensorbee");
1711    doc.add_paragraph("123 Innovation Drive, Suite 400");
1712    doc.add_paragraph("San Francisco, CA 94105");
1713    doc.add_paragraph("");
1714    doc.add_paragraph("February 22, 2026");
1715
1716    doc.add_paragraph("");
1717
1718    // ── Recipient ──
1719    doc.add_paragraph("Ms. Sarah Chen");
1720    doc.add_paragraph("VP of Engineering");
1721    doc.add_paragraph("Meridian Dynamics LLC");
1722    doc.add_paragraph("456 Enterprise Boulevard");
1723    doc.add_paragraph("Austin, TX 78701");
1724
1725    doc.add_paragraph("");
1726
1727    // ── Subject ──
1728    {
1729        let mut p = doc.add_paragraph("");
1730        p.add_run("Re: Strategic Technology Partnership — Phase 2 Expansion")
1731            .bold(true);
1732    }
1733
1734    doc.add_paragraph("");
1735
1736    // ── Body ──
1737    doc.add_paragraph("Dear Ms. Chen,");
1738    doc.add_paragraph("");
1739
1740    doc.add_paragraph(
1741        "I am writing to express Tensorbee's enthusiasm for expanding our strategic technology \
1742         partnership with Meridian Dynamics. Following the successful completion of Phase 1, \
1743         which delivered a 40% improvement in your ML inference pipeline throughput, we believe \
1744         the foundation is firmly established for an ambitious Phase 2 engagement.",
1745    )
1746    .first_line_indent(Length::inches(0.5));
1747
1748    doc.add_paragraph(
1749        "During our recent executive review, your team highlighted three priority areas for \
1750         the next phase of collaboration:",
1751    )
1752    .first_line_indent(Length::inches(0.5));
1753
1754    doc.add_numbered_list_item(
1755        "Real-time anomaly detection for your financial transaction monitoring system, \
1756         targeting sub-100ms latency at 50,000 transactions per second.",
1757        0,
1758    );
1759    doc.add_numbered_list_item(
1760        "Federated learning infrastructure to enable model training across your distributed \
1761         data centers without centralizing sensitive financial data.",
1762        0,
1763    );
1764    doc.add_numbered_list_item(
1765        "MLOps automation to reduce your model deployment cycle from the current 5 days \
1766         to under 4 hours.",
1767        0,
1768    );
1769
1770    doc.add_paragraph(
1771        "Our engineering team has prepared a detailed technical proposal addressing each of \
1772         these areas. We have allocated a dedicated team of eight senior engineers, led by \
1773         our CTO, to ensure continuity with the Phase 1 team your organization has already \
1774         built a productive working relationship with.",
1775    )
1776    .first_line_indent(Length::inches(0.5));
1777
1778    doc.add_paragraph(
1779        "I would welcome the opportunity to present our Phase 2 proposal in person. My \
1780         assistant will reach out to coordinate a meeting at your convenience during the \
1781         first week of March.",
1782    )
1783    .first_line_indent(Length::inches(0.5));
1784
1785    doc.add_paragraph("");
1786
1787    doc.add_paragraph("Warm regards,");
1788    doc.add_paragraph("");
1789    doc.add_paragraph("");
1790
1791    // Signature line
1792    doc.add_paragraph("")
1793        .border_bottom(BorderStyle::Single, 4, "4A5568");
1794    {
1795        let mut p = doc.add_paragraph("");
1796        p.add_run("Walter White").bold(true).size(12.0);
1797    }
1798    {
1799        let mut p = doc.add_paragraph("");
1800        p.add_run("Chief Executive Officer, Tensorbee")
1801            .italic(true)
1802            .size(10.0)
1803            .color("666666");
1804    }
1805    {
1806        let mut p = doc.add_paragraph("");
1807        p.add_run("walter@tensorbee.com | +1 (415) 555-0199")
1808            .size(10.0)
1809            .color("888888");
1810    }
1811
1812    doc
1813}
1814
1815// =============================================================================
1816// 7. EMPLOYMENT CONTRACT — Purple + charcoal scheme
1817// =============================================================================
1818fn generate_contract(_samples_dir: &Path) -> Document {
1819    let mut doc = Document::new();
1820
1821    // Colors: Purple #5B2C6F, Plum #8E44AD, Gray #2C3E50
1822    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1823    doc.set_margins(
1824        Length::inches(1.25),
1825        Length::inches(1.0),
1826        Length::inches(1.0),
1827        Length::inches(1.0),
1828    );
1829    doc.set_title("Employment Agreement");
1830    doc.set_author("Tensorbee HR Department");
1831    doc.set_subject("Employment Contract — Walter White");
1832    doc.set_keywords("employment, contract, agreement, Tensorbee");
1833
1834    doc.set_header("TENSORBEE — Employment Agreement");
1835    doc.set_footer("Employment Agreement — Walter White — Page");
1836
1837    // ── Title Block ──
1838    doc.add_paragraph("")
1839        .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1840    {
1841        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1842        p.add_run("EMPLOYMENT AGREEMENT")
1843            .bold(true)
1844            .size(24.0)
1845            .color("5B2C6F")
1846            .font("Georgia");
1847    }
1848    doc.add_paragraph("")
1849        .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1850    doc.add_paragraph("");
1851
1852    // ── Parties ──
1853    doc.add_paragraph(
1854        "This Employment Agreement (\"Agreement\") is entered into as of February 22, 2026 \
1855         (\"Effective Date\"), by and between:",
1856    );
1857
1858    doc.add_paragraph("");
1859
1860    {
1861        let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1862        p.add_run("EMPLOYER: ").bold(true);
1863        p.add_run(
1864            "Tensorbee, Inc., a Delaware corporation with its principal place of business at \
1865                   123 Innovation Drive, Suite 400, San Francisco, CA 94105 (\"Company\")",
1866        );
1867    }
1868    doc.add_paragraph("");
1869    {
1870        let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1871        p.add_run("EMPLOYEE: ").bold(true);
1872        p.add_run(
1873            "Walter White, residing at 308 Negra Arroyo Lane, Albuquerque, NM 87104 (\"Employee\")",
1874        );
1875    }
1876
1877    doc.add_paragraph("");
1878    doc.add_paragraph("The Company and Employee are collectively referred to as the \"Parties.\"");
1879    doc.add_paragraph("");
1880
1881    // ── Article 1: Position and Duties ──
1882    doc.add_paragraph("Article 1 — Position and Duties")
1883        .style("Heading1");
1884    {
1885        let mut p = doc.add_paragraph("");
1886        p.add_run("1.1 ").bold(true);
1887        p.add_run("The Company hereby employs the Employee as ");
1888        p.add_run("Chief Executive Officer (CEO)")
1889            .bold(true)
1890            .italic(true);
1891        p.add_run(", reporting directly to the Board of Directors.");
1892    }
1893    {
1894        let mut p = doc.add_paragraph("");
1895        p.add_run("1.2 ").bold(true);
1896        p.add_run("The Employee shall devote full working time, attention, and best efforts to \
1897                   the performance of duties as reasonably assigned by the Board, including but not limited to:");
1898    }
1899    doc.add_bullet_list_item(
1900        "Setting and executing the Company's strategic vision and business plan",
1901        0,
1902    );
1903    doc.add_bullet_list_item(
1904        "Overseeing all operations, engineering, and commercial activities",
1905        0,
1906    );
1907    doc.add_bullet_list_item(
1908        "Representing the Company to investors, customers, and the public",
1909        0,
1910    );
1911    doc.add_bullet_list_item("Recruiting, developing, and retaining key talent", 0);
1912
1913    {
1914        let mut p = doc.add_paragraph("");
1915        p.add_run("1.3 ").bold(true);
1916        p.add_run(
1917            "The Employee's primary work location shall be the Company's San Francisco \
1918                   headquarters, with reasonable travel as required by business needs.",
1919        );
1920    }
1921
1922    // ── Article 2: Compensation ──
1923    doc.add_paragraph("Article 2 — Compensation and Benefits")
1924        .style("Heading1");
1925    {
1926        let mut p = doc.add_paragraph("");
1927        p.add_run("2.1 Base Salary. ").bold(true);
1928        p.add_run("The Company shall pay the Employee an annual base salary of ");
1929        p.add_run("$375,000.00").bold(true);
1930        p.add_run(" (Three Hundred Seventy-Five Thousand Dollars), payable in accordance with the \
1931                   Company's standard payroll schedule, less applicable withholdings and deductions.");
1932    }
1933    {
1934        let mut p = doc.add_paragraph("");
1935        p.add_run("2.2 Annual Bonus. ").bold(true);
1936        p.add_run("The Employee shall be eligible for an annual performance bonus of up to ");
1937        p.add_run("40%").bold(true);
1938        p.add_run(
1939            " of base salary, based on achievement of mutually agreed performance objectives.",
1940        );
1941    }
1942    {
1943        let mut p = doc.add_paragraph("");
1944        p.add_run("2.3 Equity. ").bold(true);
1945        p.add_run("Subject to Board approval, the Employee shall receive a stock option grant of ");
1946        p.add_run("500,000 shares").bold(true);
1947        p.add_run(
1948            " of the Company's common stock, vesting over four (4) years with a one-year cliff, \
1949                   at an exercise price equal to the fair market value on the date of grant.",
1950        );
1951    }
1952
1953    // Compensation summary table
1954    doc.add_paragraph("");
1955    {
1956        let mut tbl = doc
1957            .add_table(5, 2)
1958            .borders(BorderStyle::Single, 4, "5B2C6F")
1959            .width_pct(70.0)
1960            .alignment(Alignment::Center);
1961        tbl.cell(0, 0).unwrap().set_text("Compensation Element");
1962        tbl.cell(0, 0).unwrap().shading("5B2C6F");
1963        tbl.cell(0, 1).unwrap().set_text("Value");
1964        tbl.cell(0, 1).unwrap().shading("5B2C6F");
1965        let items = [
1966            ("Base Salary", "$375,000/year"),
1967            ("Target Bonus", "Up to 40% ($150,000)"),
1968            ("Equity Grant", "500,000 shares (4yr vest)"),
1969            ("Total Target Comp", "$525,000 + equity"),
1970        ];
1971        for (i, (elem, val)) in items.iter().enumerate() {
1972            tbl.cell(i + 1, 0).unwrap().set_text(elem);
1973            tbl.cell(i + 1, 1).unwrap().set_text(val);
1974            if i == 3 {
1975                tbl.cell(i + 1, 0).unwrap().shading("E8DAEF");
1976                tbl.cell(i + 1, 1).unwrap().shading("E8DAEF");
1977            }
1978        }
1979    }
1980
1981    // ── Article 3: Benefits ──
1982    doc.add_paragraph("").page_break_before(true);
1983    doc.add_paragraph("Article 3 — Benefits").style("Heading1");
1984    {
1985        let mut p = doc.add_paragraph("");
1986        p.add_run("3.1 ").bold(true);
1987        p.add_run(
1988            "The Employee shall be entitled to participate in all benefit programs \
1989                   generally available to senior executives, including:",
1990        );
1991    }
1992    doc.add_bullet_list_item(
1993        "Medical, dental, and vision insurance (100% premium coverage for employee and dependents)",
1994        0,
1995    );
1996    doc.add_bullet_list_item("401(k) retirement plan with 6% company match", 0);
1997    doc.add_bullet_list_item("Life insurance and long-term disability coverage", 0);
1998    doc.add_bullet_list_item("Annual professional development allowance of $10,000", 0);
1999
2000    {
2001        let mut p = doc.add_paragraph("");
2002        p.add_run("3.2 Paid Time Off. ").bold(true);
2003        p.add_run(
2004            "The Employee shall receive 25 days of paid vacation per year, plus Company holidays, \
2005                   accruing on a monthly basis.",
2006        );
2007    }
2008
2009    // ── Article 4: Term and Termination ──
2010    doc.add_paragraph("Article 4 — Term and Termination")
2011        .style("Heading1");
2012    {
2013        let mut p = doc.add_paragraph("");
2014        p.add_run("4.1 At-Will Employment. ").bold(true);
2015        p.add_run(
2016            "This Agreement is for at-will employment and may be terminated by either Party \
2017                   at any time, with or without cause, subject to the notice provisions herein.",
2018        );
2019    }
2020    {
2021        let mut p = doc.add_paragraph("");
2022        p.add_run("4.2 Notice Period. ").bold(true);
2023        p.add_run("Either Party shall provide ");
2024        p.add_run("ninety (90) days").bold(true);
2025        p.add_run(" written notice of termination, or pay in lieu thereof.");
2026    }
2027    {
2028        let mut p = doc.add_paragraph("");
2029        p.add_run("4.3 Severance. ").bold(true);
2030        p.add_run("In the event of termination by the Company without Cause, the Employee shall \
2031                   receive (a) twelve (12) months of base salary continuation, (b) pro-rata bonus \
2032                   for the year of termination, and (c) twelve (12) months of COBRA premium coverage.");
2033    }
2034
2035    // ── Article 5: Confidentiality ──
2036    doc.add_paragraph("Article 5 — Confidentiality and IP")
2037        .style("Heading1");
2038    {
2039        let mut p = doc.add_paragraph("");
2040        p.add_run("5.1 ").bold(true);
2041        p.add_run(
2042            "The Employee agrees to maintain strict confidentiality of all proprietary \
2043                   information, trade secrets, and business plans of the Company during and after \
2044                   employment.",
2045        );
2046    }
2047    {
2048        let mut p = doc.add_paragraph("");
2049        p.add_run("5.2 ").bold(true);
2050        p.add_run(
2051            "All intellectual property, inventions, and works of authorship created by the \
2052                   Employee during the term of employment and within the scope of duties shall be \
2053                   the sole and exclusive property of the Company.",
2054        );
2055    }
2056
2057    // ── Article 6: Non-Compete ──
2058    doc.add_paragraph("Article 6 — Non-Competition")
2059        .style("Heading1");
2060    {
2061        let mut p = doc.add_paragraph("");
2062        p.add_run("6.1 ").bold(true);
2063        p.add_run("For a period of twelve (12) months following termination, the Employee agrees \
2064                   not to engage in any business that directly competes with the Company's core \
2065                   business of AI infrastructure and ML operations services, within the United States.");
2066    }
2067
2068    // ── Article 7: General Provisions ──
2069    doc.add_paragraph("Article 7 — General Provisions")
2070        .style("Heading1");
2071    {
2072        let mut p = doc.add_paragraph("");
2073        p.add_run("7.1 Governing Law. ").bold(true);
2074        p.add_run(
2075            "This Agreement shall be governed by and construed in accordance with the laws of \
2076                   the State of California.",
2077        );
2078    }
2079    {
2080        let mut p = doc.add_paragraph("");
2081        p.add_run("7.2 Entire Agreement. ").bold(true);
2082        p.add_run(
2083            "This Agreement constitutes the entire agreement between the Parties and supersedes \
2084                   all prior negotiations, representations, and agreements.",
2085        );
2086    }
2087    {
2088        let mut p = doc.add_paragraph("");
2089        p.add_run("7.3 Amendment. ").bold(true);
2090        p.add_run(
2091            "This Agreement may only be amended by a written instrument signed by both Parties.",
2092        );
2093    }
2094
2095    // ── Signature Block ──
2096    doc.add_paragraph("").page_break_before(true);
2097    doc.add_paragraph("IN WITNESS WHEREOF, the Parties have executed this Employment Agreement as of the Effective Date.")
2098        .space_after(Length::pt(24.0));
2099
2100    // Signature table
2101    {
2102        let mut tbl = doc.add_table(6, 2).width_pct(100.0);
2103        tbl.cell(0, 0).unwrap().set_text("FOR THE COMPANY:");
2104        tbl.cell(0, 0).unwrap().shading("5B2C6F");
2105        tbl.cell(0, 1).unwrap().set_text("EMPLOYEE:");
2106        tbl.cell(0, 1).unwrap().shading("5B2C6F");
2107
2108        tbl.cell(1, 0).unwrap().set_text("");
2109        tbl.cell(1, 1).unwrap().set_text("");
2110        tbl.row(1).unwrap().height(Length::pt(40.0));
2111
2112        tbl.cell(2, 0)
2113            .unwrap()
2114            .set_text("Signature: ___________________________");
2115        tbl.cell(2, 1)
2116            .unwrap()
2117            .set_text("Signature: ___________________________");
2118        tbl.cell(3, 0)
2119            .unwrap()
2120            .set_text("Name: Board Representative");
2121        tbl.cell(3, 1).unwrap().set_text("Name: Walter White");
2122        tbl.cell(4, 0)
2123            .unwrap()
2124            .set_text("Title: Chair, Board of Directors");
2125        tbl.cell(4, 1)
2126            .unwrap()
2127            .set_text("Title: Chief Executive Officer");
2128        tbl.cell(5, 0).unwrap().set_text("Date: _______________");
2129        tbl.cell(5, 1).unwrap().set_text("Date: _______________");
2130    }
2131
2132    doc
2133}

Set the default footer text.

Examples found in repository?
examples/template_replace.rs (line 54)
43fn create_template(path: &Path) {
44    let mut doc = Document::new();
45    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
46    doc.set_margins(
47        Length::inches(1.0),
48        Length::inches(1.0),
49        Length::inches(1.0),
50        Length::inches(1.0),
51    );
52
53    doc.set_header("{{company_name}} — Confidential");
54    doc.set_footer("Prepared by {{author_name}} on {{date}}");
55
56    // ── Title ──
57    doc.add_paragraph("{{company_name}}")
58        .style("Heading1")
59        .alignment(Alignment::Center);
60
61    doc.add_paragraph("Project Proposal")
62        .alignment(Alignment::Center);
63
64    doc.add_paragraph("");
65
66    // ── Summary section ──
67    doc.add_paragraph("Executive Summary").style("Heading2");
68
69    doc.add_paragraph(
70        "This proposal outlines the {{project_name}} project for {{company_name}}. \
71         The primary contact is {{contact_name}} ({{contact_email}}). \
72         The proposed start date is {{start_date}} with an estimated duration of {{duration}}.",
73    );
74
75    doc.add_paragraph("");
76
77    // ── Cross-run placeholder (bold label + normal value) ──
78    doc.add_paragraph("Key Details").style("Heading2");
79
80    {
81        let mut p = doc.add_paragraph("");
82        p.add_run("Project: ").bold(true);
83        p.add_run("{{project_name}}");
84    }
85    {
86        let mut p = doc.add_paragraph("");
87        p.add_run("Budget: ").bold(true);
88        p.add_run("{{budget}}");
89    }
90    {
91        let mut p = doc.add_paragraph("");
92        p.add_run("Status: ").bold(true);
93        p.add_run("{{status}}");
94    }
95
96    doc.add_paragraph("");
97
98    // ── Table with placeholders ──
99    doc.add_paragraph("Team Members").style("Heading2");
100
101    {
102        let mut tbl = doc.add_table(4, 3);
103        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
104
105        // Header row
106        for col in 0..3 {
107            tbl.cell(0, col).unwrap().shading("2E75B6");
108        }
109        tbl.cell(0, 0).unwrap().set_text("Name");
110        tbl.cell(0, 1).unwrap().set_text("Role");
111        tbl.cell(0, 2).unwrap().set_text("Email");
112
113        tbl.cell(1, 0).unwrap().set_text("{{member1_name}}");
114        tbl.cell(1, 1).unwrap().set_text("{{member1_role}}");
115        tbl.cell(1, 2).unwrap().set_text("{{member1_email}}");
116
117        tbl.cell(2, 0).unwrap().set_text("{{member2_name}}");
118        tbl.cell(2, 1).unwrap().set_text("{{member2_role}}");
119        tbl.cell(2, 2).unwrap().set_text("{{member2_email}}");
120
121        tbl.cell(3, 0).unwrap().set_text("{{member3_name}}");
122        tbl.cell(3, 1).unwrap().set_text("{{member3_role}}");
123        tbl.cell(3, 2).unwrap().set_text("{{member3_email}}");
124    }
125
126    doc.add_paragraph("");
127
128    // ── Deliverables section ──
129    doc.add_paragraph("Deliverables").style("Heading2");
130
131    doc.add_paragraph("INSERTION_POINT");
132
133    doc.add_paragraph("");
134
135    // ── Signature block ──
136    doc.add_paragraph("Acceptance").style("Heading2");
137
138    doc.add_paragraph(
139        "By signing below, {{company_name}} agrees to the terms outlined in this proposal.",
140    );
141
142    {
143        let mut tbl = doc.add_table(2, 2);
144        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
145        tbl.cell(0, 0)
146            .unwrap()
147            .set_text("Customer: ___________________");
148        tbl.cell(0, 1)
149            .unwrap()
150            .set_text("Provider: ___________________");
151        tbl.cell(1, 0).unwrap().set_text("Date: {{date}}");
152        tbl.cell(1, 1).unwrap().set_text("Date: {{date}}");
153    }
154
155    doc.set_title("{{company_name}} — Project Proposal Template");
156    doc.set_author("Template Generator");
157
158    doc.save(path).unwrap();
159}
More examples
Hide additional examples
examples/header_banner.rs (line 86)
29fn generate_header_banner_doc(path: &Path) {
30    let mut doc = Document::new();
31
32    // Page setup with extra top margin for the banner
33    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
34    doc.set_margins(
35        Length::twips(2292), // top — extra tall for header banner
36        Length::twips(1440), // right
37        Length::twips(1440), // bottom
38        Length::twips(1440), // left
39    );
40    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
41
42    // Generate a simple logo image (white text on transparent background)
43    let logo_img = create_logo_png(220, 48);
44
45    // ── Dark blue banner ──
46    let banner = build_header_banner_xml(
47        "rId1",
48        &BannerOpts {
49            bg_color: "1A3C6E",
50            banner_width: 7772400, // full page width in EMU (~8.5")
51            banner_height: 969026, // banner height in EMU (~1.06")
52            logo_width: 2011680,   // logo display width (~2.2")
53            logo_height: 438912,   // logo display height (~0.48")
54            logo_x_offset: 295125, // left padding
55            logo_y_offset: 265057, // vertical centering
56        },
57    );
58
59    doc.set_raw_header_with_images(
60        banner.clone(),
61        &[("rId1", &logo_img, "logo.png")],
62        rdocx_oxml::header_footer::HdrFtrType::Default,
63    );
64
65    // Use a different first page header (same banner, different color)
66    doc.set_different_first_page(true);
67    let first_page_banner = build_header_banner_xml(
68        "rId1",
69        &BannerOpts {
70            bg_color: "2E75B6", // lighter blue for cover
71            banner_width: 7772400,
72            banner_height: 969026,
73            logo_width: 2011680,
74            logo_height: 438912,
75            logo_x_offset: 295125,
76            logo_y_offset: 265057,
77        },
78    );
79    doc.set_raw_header_with_images(
80        first_page_banner,
81        &[("rId1", &logo_img, "logo.png")],
82        rdocx_oxml::header_footer::HdrFtrType::First,
83    );
84
85    // Footer
86    doc.set_footer("Confidential — Internal Use Only");
87
88    // ── Page 1: Cover ──
89    doc.add_paragraph("Company Report").style("Heading1");
90
91    doc.add_paragraph(
92        "This document demonstrates a custom header banner built with DrawingML \
93         group shapes. The banner uses a colored rectangle with a logo image overlaid, \
94         positioned at the top of each page.",
95    );
96
97    doc.add_paragraph("");
98
99    doc.add_paragraph("How the Header Banner Works")
100        .style("Heading2");
101
102    doc.add_paragraph(
103        "The header banner is built using set_raw_header_with_images(), which \
104         accepts raw XML and a list of (rel_id, image_data, filename) tuples. \
105         The XML uses a DrawingML group shape (wpg:wgp) containing:",
106    );
107
108    doc.add_bullet_list_item(
109        "A wps:wsp rectangle shape with a solid color fill (the background bar)",
110        0,
111    );
112    doc.add_bullet_list_item(
113        "A pic:pic image element positioned within the group (the logo)",
114        0,
115    );
116    doc.add_bullet_list_item(
117        "The group is wrapped in a wp:anchor element for absolute page positioning",
118        0,
119    );
120
121    doc.add_paragraph("");
122
123    doc.add_paragraph("Customization").style("Heading2");
124
125    doc.add_paragraph(
126        "All dimensions are in EMU (English Metric Units) where 914400 EMU = 1 inch. \
127         You can customize:",
128    );
129
130    doc.add_bullet_list_item("bg_color — any hex color for the rectangle background", 0);
131    doc.add_bullet_list_item("banner_width / banner_height — size of the full banner", 0);
132    doc.add_bullet_list_item("logo_width / logo_height — display size of the logo", 0);
133    doc.add_bullet_list_item(
134        "logo_x_offset / logo_y_offset — logo position within the banner",
135        0,
136    );
137
138    doc.add_paragraph("");
139
140    doc.add_paragraph("Different First Page").style("Heading2");
141
142    doc.add_paragraph(
143        "This page uses a lighter blue banner (first page header). \
144         Subsequent pages use a darker blue banner (default header). \
145         Use set_different_first_page(true) to enable this.",
146    );
147
148    // ── Page 2 ──
149    doc.add_paragraph("").page_break_before(true);
150
151    doc.add_paragraph("Second Page").style("Heading1");
152
153    doc.add_paragraph(
154        "This page shows the default header banner (dark blue). The first page \
155         had a lighter blue banner because we set a different first-page header.",
156    );
157
158    doc.add_paragraph("");
159
160    doc.add_paragraph(
161        "The banner repeats on every page because it is placed in the header part. \
162         You can have different banners for default, first-page, and even-page headers.",
163    );
164
165    doc.set_title("Header Banner Example");
166    doc.set_author("rdocx");
167
168    doc.save(path).unwrap();
169}
examples/generate_all_samples.rs (line 120)
97fn generate_feature_showcase(_samples_dir: &Path) -> Document {
98    let mut doc = Document::new();
99
100    // ── Page Setup & Metadata ──
101    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
102    doc.set_margins(
103        Length::inches(1.0),
104        Length::inches(1.0),
105        Length::inches(1.0),
106        Length::inches(1.0),
107    );
108    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
109    doc.set_gutter(Length::twips(0));
110    doc.set_title("rdocx Feature Showcase");
111    doc.set_author("rdocx Sample Generator");
112    doc.set_subject("Comprehensive feature demonstration");
113    doc.set_keywords("rdocx, docx, rust, sample, showcase");
114
115    // Headers & Footers with different first page
116    doc.set_different_first_page(true);
117    doc.set_first_page_header("rdocx");
118    doc.set_first_page_footer("Feature Showcase — Cover Page");
119    doc.set_header("rdocx Feature Showcase");
120    doc.set_footer("Generated by rdocx");
121
122    // ── COVER PAGE ──
123    let bg = create_sample_png(612, 792, [20, 45, 90]);
124    doc.add_background_image(&bg, "cover_bg.png");
125
126    for _ in 0..3 {
127        doc.add_paragraph("");
128    }
129    {
130        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
131        p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
132    }
133    {
134        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
135        p.add_run("Complete Feature Showcase")
136            .size(28.0)
137            .color("FFFFFF")
138            .italic(true);
139    }
140    doc.add_paragraph("");
141    {
142        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
143        p.add_run("Every feature of the rdocx Rust library")
144            .size(13.0)
145            .color("B0C4DE");
146    }
147    {
148        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
149        p.add_run("demonstrated in a single document.")
150            .size(13.0)
151            .color("B0C4DE");
152    }
153
154    // ── TABLE OF CONTENTS ──
155    doc.add_paragraph("").page_break_before(true);
156    doc.insert_toc(doc.content_count(), 3);
157
158    // ── SECTION 1: TEXT FORMATTING ──
159    doc.add_paragraph("").page_break_before(true);
160    doc.add_paragraph("1. Text Formatting").style("Heading1");
161
162    // Paragraph Alignment
163    doc.add_paragraph("1.1 Paragraph Alignment")
164        .style("Heading2");
165    doc.add_paragraph("Left-aligned paragraph (default).")
166        .alignment(Alignment::Left);
167    doc.add_paragraph("Center-aligned paragraph.")
168        .alignment(Alignment::Center);
169    doc.add_paragraph("Right-aligned paragraph.")
170        .alignment(Alignment::Right);
171    doc.add_paragraph(
172        "Justified paragraph — this text is long enough to demonstrate how justified alignment \
173         distributes extra space across word gaps so lines fill the full width of the text area.",
174    )
175    .alignment(Alignment::Justify);
176
177    doc.add_paragraph("");
178
179    // Run Formatting
180    doc.add_paragraph("1.2 Run Formatting").style("Heading2");
181    {
182        let mut p = doc.add_paragraph("");
183        p.add_run("Bold").bold(true);
184        p.add_run(" | ");
185        p.add_run("Italic").italic(true);
186        p.add_run(" | ");
187        p.add_run("Bold Italic").bold(true).italic(true);
188        p.add_run(" | ");
189        p.add_run("Underline").underline(true);
190        p.add_run(" | ");
191        p.add_run("Strikethrough").strike(true);
192        p.add_run(" | ");
193        p.add_run("Double Strike").double_strike(true);
194    }
195    {
196        let mut p = doc.add_paragraph("");
197        p.add_run("Red").color("FF0000");
198        p.add_run(" | ");
199        p.add_run("Blue").color("0000FF");
200        p.add_run(" | ");
201        p.add_run("Green").color("00AA00");
202        p.add_run(" | ");
203        p.add_run("Highlighted").highlight("FFFF00");
204    }
205    {
206        let mut p = doc.add_paragraph("");
207        p.add_run("8pt").size(8.0);
208        p.add_run(" | ");
209        p.add_run("11pt").size(11.0);
210        p.add_run(" | ");
211        p.add_run("16pt").size(16.0);
212        p.add_run(" | ");
213        p.add_run("24pt").size(24.0);
214    }
215    {
216        let mut p = doc.add_paragraph("");
217        p.add_run("Arial").font("Arial");
218        p.add_run(" | ");
219        p.add_run("Times New Roman").font("Times New Roman");
220        p.add_run(" | ");
221        p.add_run("Courier New").font("Courier New");
222    }
223    {
224        let mut p = doc.add_paragraph("");
225        p.add_run("H");
226        p.add_run("2").subscript();
227        p.add_run("O (subscript) | E=mc");
228        p.add_run("2").superscript();
229        p.add_run(" (superscript)");
230    }
231    {
232        let mut p = doc.add_paragraph("");
233        p.add_run("ALL CAPS").all_caps(true);
234        p.add_run(" | ");
235        p.add_run("Small Caps").small_caps(true);
236        p.add_run(" | ");
237        p.add_run("Expanded").character_spacing(Length::twips(40));
238        p.add_run(" | ");
239        p.add_run("Hidden text (not visible)").hidden(true);
240    }
241
242    doc.add_paragraph("");
243
244    // Underline Styles
245    doc.add_paragraph("1.3 Underline Styles").style("Heading2");
246    {
247        let mut p = doc.add_paragraph("");
248        p.add_run("Single ")
249            .underline_style(rdocx::UnderlineStyle::Single);
250        p.add_run("Double ")
251            .underline_style(rdocx::UnderlineStyle::Double);
252        p.add_run("Thick ")
253            .underline_style(rdocx::UnderlineStyle::Thick);
254        p.add_run("Dotted ")
255            .underline_style(rdocx::UnderlineStyle::Dotted);
256        p.add_run("Dash ")
257            .underline_style(rdocx::UnderlineStyle::Dash);
258        p.add_run("Wave ")
259            .underline_style(rdocx::UnderlineStyle::Wave);
260    }
261
262    // ── SECTION 2: PARAGRAPH FORMATTING ──
263    doc.add_paragraph("").page_break_before(true);
264    doc.add_paragraph("2. Paragraph Formatting")
265        .style("Heading1");
266
267    doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
268    doc.add_paragraph("Paragraph with light green shading")
269        .shading("E2EFDA");
270    doc.add_paragraph("Paragraph with bottom border")
271        .border_bottom(BorderStyle::Single, 6, "2E75B6");
272    doc.add_paragraph("Paragraph with all borders (red)")
273        .border_all(BorderStyle::Single, 4, "FF0000");
274
275    doc.add_paragraph("2.2 Indentation").style("Heading2");
276    doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
277        .indent_left(Length::inches(1.0))
278        .hanging_indent(Length::inches(0.5));
279    doc.add_paragraph("0.5-inch first-line indent on this paragraph")
280        .first_line_indent(Length::inches(0.5));
281    doc.add_paragraph("Both left and right indent (0.75 inches each)")
282        .indent_left(Length::inches(0.75))
283        .indent_right(Length::inches(0.75));
284
285    doc.add_paragraph("2.3 Spacing & Line Height")
286        .style("Heading2");
287    doc.add_paragraph("Extra space before (24pt) and after (12pt)")
288        .space_before(Length::pt(24.0))
289        .space_after(Length::pt(12.0));
290    doc.add_paragraph(
291        "Double line spacing paragraph. This text should have extra vertical space between \
292         lines to demonstrate line_spacing_multiple(2.0).",
293    )
294    .line_spacing_multiple(2.0);
295    doc.add_paragraph("Exact 20pt line spacing (fixed height).")
296        .line_spacing(20.0);
297
298    doc.add_paragraph("2.4 Pagination Controls")
299        .style("Heading2");
300    doc.add_paragraph("keep_with_next — this paragraph stays with the next")
301        .keep_with_next(true);
302    doc.add_paragraph("(This paragraph was kept with the one above.)");
303    doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
304        .keep_together(true);
305    doc.add_paragraph("widow_control — prevents widow/orphan lines")
306        .widow_control(true);
307
308    // ── SECTION 3: LISTS ──
309    doc.add_paragraph("").page_break_before(true);
310    doc.add_paragraph("3. Lists").style("Heading1");
311
312    doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
313    doc.add_bullet_list_item("First item", 0);
314    doc.add_bullet_list_item("Second item", 0);
315    doc.add_bullet_list_item("Nested level 1", 1);
316    doc.add_bullet_list_item("Nested level 2", 2);
317    doc.add_bullet_list_item("Back to level 1", 1);
318    doc.add_bullet_list_item("Third item", 0);
319
320    doc.add_paragraph("");
321
322    doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
323    doc.add_numbered_list_item("First numbered item", 0);
324    doc.add_numbered_list_item("Second numbered item", 0);
325    doc.add_numbered_list_item("Sub-item A", 1);
326    doc.add_numbered_list_item("Sub-item B", 1);
327    doc.add_numbered_list_item("Third numbered item", 0);
328
329    // ── SECTION 4: TAB STOPS ──
330    doc.add_paragraph("");
331    doc.add_paragraph("4. Tab Stops").style("Heading1");
332
333    doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
334    doc.add_paragraph("Left\tCenter\tRight\tDecimal")
335        .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
336        .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
337        .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
338        .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
339
340    doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
341    doc.add_paragraph("Item\t\tPrice")
342        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
343        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
344        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
345    doc.add_paragraph("Widget\t\t$19.99")
346        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
347        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
348        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
349    doc.add_paragraph("Gadget\t\t$249.50")
350        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
351        .add_tab_stop_with_leader(
352            TabAlignment::Right,
353            Length::inches(4.0),
354            TabLeader::Underscore,
355        )
356        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
357
358    // ── SECTION 5: TABLES ──
359    doc.add_paragraph("").page_break_before(true);
360    doc.add_paragraph("5. Tables").style("Heading1");
361
362    doc.add_paragraph("5.1 Basic Table").style("Heading2");
363    {
364        let mut tbl = doc
365            .add_table(4, 3)
366            .borders(BorderStyle::Single, 4, "000000");
367        for col in 0..3 {
368            tbl.cell(0, col).unwrap().shading("2E75B6");
369        }
370        tbl.cell(0, 0).unwrap().set_text("Name");
371        tbl.cell(0, 1).unwrap().set_text("Role");
372        tbl.cell(0, 2).unwrap().set_text("Location");
373        tbl.cell(1, 0).unwrap().set_text("Walter White");
374        tbl.cell(1, 1).unwrap().set_text("CEO");
375        tbl.cell(1, 2).unwrap().set_text("Albuquerque");
376        tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
377        tbl.cell(2, 1).unwrap().set_text("CTO");
378        tbl.cell(2, 2).unwrap().set_text("Remote");
379        tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
380        tbl.cell(3, 1).unwrap().set_text("Security");
381        tbl.cell(3, 2).unwrap().set_text("Washington");
382    }
383
384    doc.add_paragraph("");
385
386    doc.add_paragraph("5.2 Column Span & Cell Shading")
387        .style("Heading2");
388    {
389        let mut tbl = doc
390            .add_table(3, 4)
391            .borders(BorderStyle::Single, 4, "000000")
392            .width_pct(100.0);
393        tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
394        tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
395        tbl.cell(1, 0).unwrap().set_text("Region");
396        tbl.cell(1, 0).unwrap().shading("D6E4F0");
397        tbl.cell(1, 1).unwrap().set_text("Q1");
398        tbl.cell(1, 1).unwrap().shading("D6E4F0");
399        tbl.cell(1, 2).unwrap().set_text("Q2");
400        tbl.cell(1, 2).unwrap().shading("D6E4F0");
401        tbl.cell(1, 3).unwrap().set_text("Total");
402        tbl.cell(1, 3).unwrap().shading("D6E4F0");
403        tbl.cell(2, 0).unwrap().set_text("Americas");
404        tbl.cell(2, 1).unwrap().set_text("$2.4M");
405        tbl.cell(2, 2).unwrap().set_text("$2.7M");
406        tbl.cell(2, 3).unwrap().set_text("$5.1M");
407    }
408
409    doc.add_paragraph("");
410
411    doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
412    {
413        let mut tbl = doc
414            .add_table(4, 3)
415            .borders(BorderStyle::Single, 4, "000000");
416        tbl.cell(0, 0).unwrap().set_text("Category");
417        tbl.cell(0, 0).unwrap().shading("E2EFDA");
418        tbl.cell(0, 1).unwrap().set_text("Item");
419        tbl.cell(0, 1).unwrap().shading("E2EFDA");
420        tbl.cell(0, 2).unwrap().set_text("Price");
421        tbl.cell(0, 2).unwrap().shading("E2EFDA");
422        tbl.cell(1, 0).unwrap().set_text("Hardware");
423        tbl.cell(1, 0).unwrap().v_merge_restart();
424        tbl.cell(1, 1).unwrap().set_text("Laptop");
425        tbl.cell(1, 2).unwrap().set_text("$1,200");
426        tbl.cell(2, 0).unwrap().v_merge_continue();
427        tbl.cell(2, 1).unwrap().set_text("Monitor");
428        tbl.cell(2, 2).unwrap().set_text("$450");
429        tbl.cell(3, 0).unwrap().set_text("Software");
430        tbl.cell(3, 1).unwrap().set_text("IDE License");
431        tbl.cell(3, 2).unwrap().set_text("$200/yr");
432    }
433
434    doc.add_paragraph("");
435
436    doc.add_paragraph("5.4 Nested Table").style("Heading2");
437    {
438        let mut tbl = doc
439            .add_table(2, 2)
440            .borders(BorderStyle::Single, 6, "2E75B6");
441        tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
442        tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
443        tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
444        {
445            let mut cell = tbl.cell(1, 1).unwrap();
446            cell.set_text("Nested table below:");
447            let mut nested = cell
448                .add_table(2, 2)
449                .borders(BorderStyle::Single, 2, "FF6600");
450            nested.cell(0, 0).unwrap().set_text("A");
451            nested.cell(0, 1).unwrap().set_text("B");
452            nested.cell(1, 0).unwrap().set_text("C");
453            nested.cell(1, 1).unwrap().set_text("D");
454        }
455    }
456
457    doc.add_paragraph("");
458
459    doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
460        .style("Heading2");
461    {
462        let mut tbl = doc
463            .add_table(2, 3)
464            .borders(BorderStyle::Single, 4, "666666");
465        tbl.row(0).unwrap().height(Length::pt(50.0)).header();
466        tbl.cell(0, 0).unwrap().set_text("Top");
467        tbl.cell(0, 0)
468            .unwrap()
469            .vertical_alignment(VerticalAlignment::Top)
470            .shading("FFF2CC");
471        tbl.cell(0, 1).unwrap().set_text("Center");
472        tbl.cell(0, 1)
473            .unwrap()
474            .vertical_alignment(VerticalAlignment::Center)
475            .shading("D9E2F3");
476        tbl.cell(0, 2).unwrap().set_text("Bottom");
477        tbl.cell(0, 2)
478            .unwrap()
479            .vertical_alignment(VerticalAlignment::Bottom)
480            .shading("E2EFDA");
481        tbl.row(1).unwrap().cant_split();
482        tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
483        tbl.cell(1, 0).unwrap().no_wrap();
484        tbl.cell(1, 1).unwrap().set_text("Fixed width");
485        tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
486        tbl.cell(1, 2).unwrap().set_text("Normal");
487    }
488
489    // ── SECTION 6: IMAGES ──
490    doc.add_paragraph("").page_break_before(true);
491    doc.add_paragraph("6. Images").style("Heading1");
492
493    doc.add_paragraph("6.1 Inline Image").style("Heading2");
494    doc.add_paragraph("A blue gradient image below:");
495    let img = create_sample_png(200, 50, [0, 80, 200]);
496    doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
497
498    doc.add_paragraph("");
499    doc.add_paragraph("6.2 Header Image").style("Heading2");
500    let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
501    doc.set_header_image(
502        &hdr_img,
503        "header_logo.png",
504        Length::inches(2.0),
505        Length::inches(0.2),
506    );
507    doc.add_paragraph("The header now contains an inline image (check the top of this page).");
508
509    doc.add_paragraph("");
510    doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
511
512    // ── SECTION 7: CONTENT MANIPULATION ──
513    doc.add_paragraph("").page_break_before(true);
514    doc.add_paragraph("7. Content Manipulation")
515        .style("Heading1");
516
517    doc.add_paragraph("7.1 Placeholder Replacement")
518        .style("Heading2");
519    doc.add_paragraph("Customer: {{customer}}");
520    doc.add_paragraph("Date: {{date}}");
521    doc.add_paragraph("Reference: {{ref_number}}");
522    {
523        let mut tbl = doc
524            .add_table(2, 2)
525            .borders(BorderStyle::Single, 4, "000000");
526        tbl.cell(0, 0).unwrap().set_text("Project");
527        tbl.cell(0, 0).unwrap().shading("D6E4F0");
528        tbl.cell(0, 1).unwrap().set_text("{{project}}");
529        tbl.cell(1, 0).unwrap().set_text("Status");
530        tbl.cell(1, 0).unwrap().shading("D6E4F0");
531        tbl.cell(1, 1).unwrap().set_text("{{status}}");
532    }
533
534    let mut replacements = HashMap::new();
535    replacements.insert("{{customer}}", "Tensorbee Inc.");
536    replacements.insert("{{date}}", "February 22, 2026");
537    replacements.insert("{{ref_number}}", "TB-2026-001");
538    replacements.insert("{{project}}", "Infrastructure Upgrade");
539    replacements.insert("{{status}}", "In Progress");
540    let n = doc.replace_all(&replacements);
541    doc.add_paragraph(&format!("({n} placeholders replaced above)"));
542
543    doc.add_paragraph("");
544
545    doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
546    doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
547    let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
548    doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
549
550    doc.add_paragraph("");
551
552    doc.add_paragraph("7.3 Content Insertion").style("Heading2");
553    doc.add_paragraph("Section A: First content.");
554    doc.add_paragraph("Section C: Third content.");
555    if let Some(idx) = doc.find_content_index("Section C") {
556        doc.insert_paragraph(
557            idx,
558            "Section B: Inserted between A and C via find_content_index().",
559        );
560    }
561
562    // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
563    doc.add_paragraph("").section_break(SectionBreak::NextPage);
564    doc.add_paragraph("8. Section Breaks & Orientation")
565        .style("Heading1");
566    doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
567
568    {
569        let mut tbl = doc
570            .add_table(3, 7)
571            .borders(BorderStyle::Single, 4, "2E75B6");
572        let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
573        for (c, h) in headers.iter().enumerate() {
574            tbl.cell(0, c).unwrap().set_text(h);
575            tbl.cell(0, c).unwrap().shading("2E75B6");
576        }
577        let data = [
578            [
579                "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
580            ],
581            [
582                "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
583            ],
584        ];
585        for (r, row) in data.iter().enumerate() {
586            for (c, v) in row.iter().enumerate() {
587                tbl.cell(r + 1, c).unwrap().set_text(v);
588            }
589        }
590    }
591
592    doc.add_paragraph("")
593        .section_break(SectionBreak::NextPage)
594        .section_landscape();
595
596    // ── SECTION 9: CUSTOM STYLES ──
597    doc.add_paragraph("9. Custom Styles").style("Heading1");
598    doc.add_style(
599        StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
600            .based_on("Normal")
601            .paragraph_properties(rdocx_oxml::properties::CT_PPr {
602                shading: Some(rdocx_oxml::properties::CT_Shd {
603                    val: "clear".to_string(),
604                    color: None,
605                    fill: Some("FFF2CC".to_string()),
606                }),
607                ..Default::default()
608            })
609            .run_properties(rdocx_oxml::properties::CT_RPr {
610                bold: Some(true),
611                color: Some("C45911".to_string()),
612                ..Default::default()
613            }),
614    );
615    doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
616        .style("CustomHighlight");
617
618    doc.add_paragraph("");
619
620    // ── SECTION 10: DOCUMENT INTELLIGENCE ──
621    doc.add_paragraph("10. Document Intelligence API")
622        .style("Heading1");
623
624    let wc = doc.word_count();
625    let hc = doc.headings().len();
626    let ic = doc.images().len();
627    let lc = doc.links().len();
628    doc.add_paragraph(&format!("Word count: {wc}"));
629    doc.add_paragraph(&format!("Heading count: {hc}"));
630    doc.add_paragraph(&format!("Image count: {ic}"));
631    doc.add_paragraph(&format!("Link count: {lc}"));
632
633    let outline = doc.document_outline();
634    doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
635
636    let issues = doc.audit_accessibility();
637    doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
638    for issue in &issues {
639        doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
640    }
641
642    // ── SECTION 11: DOCUMENT MERGING ──
643    doc.add_paragraph("");
644    doc.add_paragraph("11. Document Merging").style("Heading1");
645    {
646        let mut other = Document::new();
647        other.add_paragraph("This paragraph was merged from another document using append().");
648        doc.append(&other);
649    }
650
651    doc.add_paragraph("");
652
653    // ── SUMMARY ──
654    doc.add_paragraph("Summary of Demonstrated Features")
655        .style("Heading1");
656    let features = [
657        "Page setup: size, margins, header/footer distance, gutter",
658        "Document metadata: title, author, subject, keywords",
659        "Headers and footers: text, images, different first page",
660        "Background images (full-page behind text)",
661        "Table of Contents generation with bookmarks",
662        "Text formatting: bold, italic, underline styles, strike, color, size, font",
663        "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
664        "Paragraph formatting: alignment, borders, shading, spacing, indentation",
665        "Pagination controls: keep-with-next, keep-together, widow control",
666        "Bullet and numbered lists with nesting",
667        "Tab stops with dot/underscore/hyphen leaders",
668        "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
669        "Row properties: header rows, exact height, cant-split",
670        "Cell properties: width, no-wrap, vertical alignment",
671        "Inline images and header images",
672        "Placeholder replacement (single and batch)",
673        "Regex-based find and replace",
674        "Content insertion at specific positions",
675        "Section breaks with mixed portrait/landscape",
676        "Custom paragraph and character styles",
677        "Document intelligence: word count, headings, outline, images, links",
678        "Accessibility audit",
679        "Document merging (append)",
680        "Export: DOCX, PDF, HTML, Markdown",
681    ];
682    for f in &features {
683        doc.add_bullet_list_item(f, 0);
684    }
685    doc.add_paragraph("");
686    doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
687        .alignment(Alignment::Center)
688        .shading("E2EFDA")
689        .border_all(BorderStyle::Single, 2, "00AA00");
690
691    doc
692}
693
694// =============================================================================
695// 2. PROPOSAL DOCUMENT — Deep navy + gold scheme
696// =============================================================================
697fn generate_proposal(_samples_dir: &Path) -> Document {
698    let mut doc = Document::new();
699
700    // Colors: Navy #1B2A4A, Gold #C5922E, Light #F4F1EB
701    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
702    doc.set_margins(
703        Length::inches(1.0),
704        Length::inches(1.0),
705        Length::inches(1.0),
706        Length::inches(1.0),
707    );
708    doc.set_title("AI Platform Modernization Proposal");
709    doc.set_author("Walter White");
710    doc.set_subject("Technology Proposal");
711    doc.set_keywords("proposal, AI, modernization, Tensorbee");
712
713    // Banner header
714    let logo_img = create_logo_png(220, 48);
715    let banner = build_header_banner_xml(
716        "rId1",
717        &BannerOpts {
718            bg_color: "1B2A4A",
719            banner_width: 7772400,
720            banner_height: 914400,
721            logo_width: 2011680,
722            logo_height: 438912,
723            logo_x_offset: 295125,
724            logo_y_offset: 237744,
725        },
726    );
727    doc.set_raw_header_with_images(
728        banner,
729        &[("rId1", &logo_img, "logo.png")],
730        rdocx_oxml::header_footer::HdrFtrType::Default,
731    );
732    doc.set_different_first_page(true);
733    let cover_banner = build_header_banner_xml(
734        "rId1",
735        &BannerOpts {
736            bg_color: "C5922E",
737            banner_width: 7772400,
738            banner_height: 914400,
739            logo_width: 2011680,
740            logo_height: 438912,
741            logo_x_offset: 295125,
742            logo_y_offset: 237744,
743        },
744    );
745    doc.set_raw_header_with_images(
746        cover_banner,
747        &[("rId1", &logo_img, "logo.png")],
748        rdocx_oxml::header_footer::HdrFtrType::First,
749    );
750    doc.set_margins(
751        Length::twips(2292),
752        Length::twips(1440),
753        Length::twips(1440),
754        Length::twips(1440),
755    );
756    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
757    doc.set_footer("Tensorbee — Confidential");
758
759    // Custom styles
760    doc.add_style(
761        StyleBuilder::paragraph("ProposalTitle", "Proposal Title")
762            .based_on("Normal")
763            .run_properties(rdocx_oxml::properties::CT_RPr {
764                bold: Some(true),
765                font_ascii: Some("Georgia".to_string()),
766                font_hansi: Some("Georgia".to_string()),
767                sz: Some(rdocx_oxml::HalfPoint(56)), // half-points → 28pt
768                color: Some("1B2A4A".to_string()),
769                ..Default::default()
770            }),
771    );
772
773    // ── Cover Page ──
774    for _ in 0..4 {
775        doc.add_paragraph("");
776    }
777    doc.add_paragraph("AI Platform Modernization")
778        .style("ProposalTitle")
779        .alignment(Alignment::Center);
780    doc.add_paragraph("")
781        .alignment(Alignment::Center)
782        .border_bottom(BorderStyle::Single, 8, "C5922E");
783    {
784        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
785        p.add_run("Prepared for Global Financial Corp.")
786            .size(14.0)
787            .color("666666")
788            .italic(true);
789    }
790    {
791        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
792        p.add_run("Prepared by: Walter White, CEO — Tensorbee")
793            .size(12.0)
794            .color("888888");
795    }
796    {
797        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
798        p.add_run("February 22, 2026").size(12.0).color("888888");
799    }
800
801    // ── TOC ──
802    doc.add_paragraph("").page_break_before(true);
803    doc.add_paragraph("Table of Contents").style("Heading1");
804    // We'll add a manual-style TOC since the insert_toc goes at a specific position
805    doc.insert_toc(doc.content_count(), 2);
806
807    // ── Executive Summary ──
808    doc.add_paragraph("").page_break_before(true);
809    doc.add_paragraph("Executive Summary").style("Heading1");
810    doc.add_paragraph(
811        "Tensorbee proposes a comprehensive modernization of Global Financial Corp's AI platform, \
812         transitioning from legacy batch-processing systems to a real-time inference architecture. \
813         This transformation will reduce model deployment time from weeks to hours, improve \
814         prediction accuracy by an estimated 23%, and deliver $4.2M in annual operational savings.",
815    )
816    .first_line_indent(Length::inches(0.3));
817    doc.add_paragraph(
818        "The project spans three phases over 18 months, with a total investment of $2.8M. \
819         Tensorbee brings deep expertise in ML infrastructure, having successfully completed \
820         similar transformations for three Fortune 500 financial institutions.",
821    )
822    .first_line_indent(Length::inches(0.3));
823
824    // Key metrics table
825    doc.add_paragraph("");
826    {
827        let mut tbl = doc
828            .add_table(5, 2)
829            .borders(BorderStyle::Single, 4, "1B2A4A")
830            .width_pct(80.0);
831        tbl.cell(0, 0).unwrap().set_text("Key Metric");
832        tbl.cell(0, 0).unwrap().shading("1B2A4A");
833        tbl.cell(0, 1).unwrap().set_text("Value");
834        tbl.cell(0, 1).unwrap().shading("1B2A4A");
835        let rows = [
836            ("Total Investment", "$2.8M"),
837            ("Annual Savings", "$4.2M"),
838            ("ROI (Year 1)", "150%"),
839            ("Timeline", "18 months"),
840        ];
841        for (i, (k, v)) in rows.iter().enumerate() {
842            tbl.cell(i + 1, 0).unwrap().set_text(k);
843            if i % 2 == 0 {
844                tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
845            }
846            tbl.cell(i + 1, 1).unwrap().set_text(v);
847            if i % 2 == 0 {
848                tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
849            }
850        }
851    }
852
853    // ── Problem Statement ──
854    doc.add_paragraph("").page_break_before(true);
855    doc.add_paragraph("Problem Statement").style("Heading1");
856    doc.add_paragraph(
857        "Global Financial Corp's current AI infrastructure faces three critical challenges:",
858    );
859    doc.add_numbered_list_item("Deployment Latency — New models take 3-4 weeks to deploy to production, compared to the industry benchmark of 2-3 days.", 0);
860    doc.add_numbered_list_item("Scalability Constraints — The batch processing architecture cannot handle real-time inference demands during peak trading hours.", 0);
861    doc.add_numbered_list_item("Technical Debt — Legacy Python 2.7 codebases and manual deployment scripts create reliability risks and slow iteration speed.", 0);
862
863    // ── Proposed Solution ──
864    doc.add_paragraph("").page_break_before(true);
865    doc.add_paragraph("Proposed Solution").style("Heading1");
866
867    doc.add_paragraph("Phase 1: Foundation (Months 1-6)")
868        .style("Heading2");
869    doc.add_bullet_list_item("Deploy Kubernetes-based ML serving infrastructure", 0);
870    doc.add_bullet_list_item("Implement CI/CD pipeline for model deployment", 0);
871    doc.add_bullet_list_item("Migrate top 5 production models to new platform", 0);
872
873    doc.add_paragraph("Phase 2: Expansion (Months 7-12)")
874        .style("Heading2");
875    doc.add_bullet_list_item(
876        "Real-time feature engineering pipeline (Apache Kafka + Flink)",
877        0,
878    );
879    doc.add_bullet_list_item("A/B testing framework for model variants", 0);
880    doc.add_bullet_list_item("Automated model monitoring and drift detection", 0);
881
882    doc.add_paragraph("Phase 3: Optimization (Months 13-18)")
883        .style("Heading2");
884    doc.add_bullet_list_item("GPU inference optimization (TensorRT/ONNX Runtime)", 0);
885    doc.add_bullet_list_item("Multi-region deployment for latency reduction", 0);
886    doc.add_bullet_list_item(
887        "Self-service model deployment portal for data science teams",
888        0,
889    );
890
891    // ── Budget ──
892    doc.add_paragraph("Budget Breakdown").style("Heading1");
893    {
894        let mut tbl = doc
895            .add_table(6, 3)
896            .borders(BorderStyle::Single, 4, "1B2A4A")
897            .width_pct(100.0);
898        tbl.cell(0, 0).unwrap().set_text("Category");
899        tbl.cell(0, 0).unwrap().shading("1B2A4A");
900        tbl.cell(0, 1).unwrap().set_text("Cost");
901        tbl.cell(0, 1).unwrap().shading("1B2A4A");
902        tbl.cell(0, 2).unwrap().set_text("Notes");
903        tbl.cell(0, 2).unwrap().shading("1B2A4A");
904        let items = [
905            (
906                "Infrastructure",
907                "$450,000",
908                "Cloud compute + GPU instances",
909            ),
910            ("Engineering Services", "$1,600,000", "12 FTE x 18 months"),
911            (
912                "Software Licenses",
913                "$350,000",
914                "Kafka, monitoring, CI/CD tools",
915            ),
916            (
917                "Training & Enablement",
918                "$200,000",
919                "Team training, documentation",
920            ),
921            ("Contingency (10%)", "$200,000", "Risk buffer"),
922        ];
923        for (i, (cat, cost, note)) in items.iter().enumerate() {
924            tbl.cell(i + 1, 0).unwrap().set_text(cat);
925            tbl.cell(i + 1, 1).unwrap().set_text(cost);
926            tbl.cell(i + 1, 2).unwrap().set_text(note);
927            if i % 2 == 0 {
928                tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
929                tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
930                tbl.cell(i + 1, 2).unwrap().shading("F4F1EB");
931            }
932        }
933    }
934
935    // ── Closing ──
936    doc.add_paragraph("");
937    doc.add_paragraph("Next Steps").style("Heading1");
938    doc.add_numbered_list_item(
939        "Schedule technical deep-dive with GFC engineering team (Week 1)",
940        0,
941    );
942    doc.add_numbered_list_item("Finalize scope and sign SOW (Week 2-3)", 0);
943    doc.add_numbered_list_item("Kick off Phase 1 with joint planning session (Week 4)", 0);
944
945    doc.add_paragraph("");
946    {
947        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
948        p.add_run("Walter White")
949            .bold(true)
950            .size(14.0)
951            .color("1B2A4A");
952    }
953    {
954        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
955        p.add_run("CEO, Tensorbee | walter@tensorbee.com")
956            .size(10.0)
957            .color("888888");
958    }
959
960    doc
961}
962
963// =============================================================================
964// 3. QUOTE / BILL OF MATERIALS — Teal + orange scheme
965// =============================================================================
966fn generate_quote(_samples_dir: &Path) -> Document {
967    let mut doc = Document::new();
968
969    // Colors: Teal #008B8B, Dark #1A3C3C, Orange #E07020, Light #F0F8F8
970    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
971    doc.set_margins(
972        Length::inches(0.75),
973        Length::inches(0.75),
974        Length::inches(0.75),
975        Length::inches(0.75),
976    );
977    doc.set_title("Quotation QT-2026-0147");
978    doc.set_author("Walter White");
979    doc.set_keywords("quote, BOM, Tensorbee");
980
981    doc.set_header("Tensorbee — Quotation");
982    doc.set_footer("QT-2026-0147 | Page");
983
984    // ── Header Block ──
985    {
986        let mut p = doc.add_paragraph("").alignment(Alignment::Left);
987        p.add_run("TENSORBEE")
988            .bold(true)
989            .size(28.0)
990            .color("008B8B")
991            .font("Helvetica");
992    }
993    doc.add_paragraph("123 Innovation Drive, Suite 400")
994        .alignment(Alignment::Left);
995    doc.add_paragraph("San Francisco, CA 94105 | +1 (415) 555-0199")
996        .alignment(Alignment::Left);
997    doc.add_paragraph("")
998        .border_bottom(BorderStyle::Single, 8, "008B8B");
999
1000    // Quote meta
1001    doc.add_paragraph("");
1002    {
1003        let mut tbl = doc.add_table(4, 4).width_pct(100.0);
1004        tbl.cell(0, 0).unwrap().set_text("QUOTATION");
1005        tbl.cell(0, 0).unwrap().shading("008B8B").grid_span(2);
1006        tbl.cell(0, 2).unwrap().set_text("DATE");
1007        tbl.cell(0, 2).unwrap().shading("008B8B");
1008        tbl.cell(0, 3).unwrap().set_text("VALID UNTIL");
1009        tbl.cell(0, 3).unwrap().shading("008B8B");
1010        tbl.cell(1, 0).unwrap().set_text("Quote #:");
1011        tbl.cell(1, 1).unwrap().set_text("QT-2026-0147");
1012        tbl.cell(1, 2).unwrap().set_text("Feb 22, 2026");
1013        tbl.cell(1, 3).unwrap().set_text("Mar 22, 2026");
1014        tbl.cell(2, 0).unwrap().set_text("Prepared by:");
1015        tbl.cell(2, 1).unwrap().set_text("Walter White");
1016        tbl.cell(2, 2).unwrap().set_text("Payment:");
1017        tbl.cell(2, 3).unwrap().set_text("Net 30");
1018        tbl.cell(3, 0).unwrap().set_text("Customer:");
1019        tbl.cell(3, 1).unwrap().set_text("Meridian Dynamics LLC");
1020        tbl.cell(3, 2).unwrap().set_text("Currency:");
1021        tbl.cell(3, 3).unwrap().set_text("USD");
1022    }
1023
1024    doc.add_paragraph("");
1025
1026    // ── Bill of Materials ──
1027    {
1028        let mut p = doc.add_paragraph("");
1029        p.add_run("Bill of Materials")
1030            .bold(true)
1031            .size(16.0)
1032            .color("1A3C3C");
1033    }
1034    doc.add_paragraph("");
1035
1036    {
1037        let mut tbl = doc
1038            .add_table(10, 6)
1039            .borders(BorderStyle::Single, 2, "008B8B")
1040            .width_pct(100.0)
1041            .layout_fixed();
1042        // Headers
1043        let hdrs = [
1044            "#",
1045            "Part Number",
1046            "Description",
1047            "Qty",
1048            "Unit Price",
1049            "Total",
1050        ];
1051        for (c, h) in hdrs.iter().enumerate() {
1052            tbl.cell(0, c).unwrap().set_text(h);
1053            tbl.cell(0, c).unwrap().shading("008B8B");
1054        }
1055
1056        let items: Vec<(&str, &str, &str, &str, &str)> = vec![
1057            (
1058                "TB-GPU-A100",
1059                "NVIDIA A100 80GB GPU",
1060                "4",
1061                "$12,500.00",
1062                "$50,000.00",
1063            ),
1064            (
1065                "TB-SRV-R750",
1066                "Dell R750xa Server Chassis",
1067                "2",
1068                "$8,200.00",
1069                "$16,400.00",
1070            ),
1071            (
1072                "TB-RAM-256G",
1073                "256GB DDR5 ECC Memory Module",
1074                "8",
1075                "$890.00",
1076                "$7,120.00",
1077            ),
1078            (
1079                "TB-SSD-3840",
1080                "3.84TB NVMe U.2 SSD",
1081                "8",
1082                "$1,150.00",
1083                "$9,200.00",
1084            ),
1085            (
1086                "TB-NET-CX7",
1087                "ConnectX-7 200GbE NIC",
1088                "4",
1089                "$1,800.00",
1090                "$7,200.00",
1091            ),
1092            (
1093                "TB-SW-48P",
1094                "48-Port 100GbE Switch",
1095                "1",
1096                "$22,000.00",
1097                "$22,000.00",
1098            ),
1099            (
1100                "TB-CAB-RACK",
1101                "42U Server Rack + PDU",
1102                "1",
1103                "$4,500.00",
1104                "$4,500.00",
1105            ),
1106            (
1107                "TB-SVC-INST",
1108                "Installation & Configuration",
1109                "1",
1110                "$8,500.00",
1111                "$8,500.00",
1112            ),
1113            (
1114                "TB-SVC-SUPP",
1115                "3-Year Premium Support",
1116                "1",
1117                "$15,000.00",
1118                "$15,000.00",
1119            ),
1120        ];
1121
1122        for (i, (pn, desc, qty, unit, total)) in items.iter().enumerate() {
1123            let row = i + 1;
1124            tbl.cell(row, 0).unwrap().set_text(&format!("{}", i + 1));
1125            tbl.cell(row, 1).unwrap().set_text(pn);
1126            tbl.cell(row, 2).unwrap().set_text(desc);
1127            tbl.cell(row, 3).unwrap().set_text(qty);
1128            tbl.cell(row, 4).unwrap().set_text(unit);
1129            tbl.cell(row, 5).unwrap().set_text(total);
1130            if i % 2 == 0 {
1131                for c in 0..6 {
1132                    tbl.cell(row, c).unwrap().shading("F0F8F8");
1133                }
1134            }
1135        }
1136    }
1137
1138    doc.add_paragraph("");
1139
1140    // ── Totals ──
1141    {
1142        let mut tbl = doc
1143            .add_table(4, 2)
1144            .borders(BorderStyle::Single, 2, "008B8B")
1145            .width(Length::inches(3.5))
1146            .alignment(Alignment::Right);
1147        tbl.cell(0, 0).unwrap().set_text("Subtotal");
1148        tbl.cell(0, 1).unwrap().set_text("$139,920.00");
1149        tbl.cell(1, 0).unwrap().set_text("Shipping & Handling");
1150        tbl.cell(1, 1).unwrap().set_text("$2,500.00");
1151        tbl.cell(2, 0).unwrap().set_text("Tax (8.625%)");
1152        tbl.cell(2, 1).unwrap().set_text("$12,068.10");
1153        tbl.cell(3, 0).unwrap().set_text("TOTAL");
1154        tbl.cell(3, 0).unwrap().shading("E07020");
1155        tbl.cell(3, 1).unwrap().set_text("$154,488.10");
1156        tbl.cell(3, 1).unwrap().shading("E07020");
1157    }
1158
1159    doc.add_paragraph("");
1160
1161    // ── Terms & Conditions ──
1162    {
1163        let mut p = doc.add_paragraph("");
1164        p.add_run("Terms & Conditions")
1165            .bold(true)
1166            .size(14.0)
1167            .color("1A3C3C");
1168    }
1169    doc.add_numbered_list_item(
1170        "This quotation is valid for 30 calendar days from the date of issue.",
1171        0,
1172    );
1173    doc.add_numbered_list_item(
1174        "All prices are in USD and exclusive of applicable taxes unless stated.",
1175        0,
1176    );
1177    doc.add_numbered_list_item("Standard lead time is 4-6 weeks from PO receipt.", 0);
1178    doc.add_numbered_list_item(
1179        "Payment terms: Net 30 from invoice date. 2% discount for payment within 10 days.",
1180        0,
1181    );
1182    doc.add_numbered_list_item(
1183        "Warranty: 3-year manufacturer warranty on all hardware components.",
1184        0,
1185    );
1186    doc.add_numbered_list_item(
1187        "Returns subject to 15% restocking fee if initiated after 14 days.",
1188        0,
1189    );
1190
1191    doc.add_paragraph("");
1192    doc.add_paragraph("")
1193        .border_bottom(BorderStyle::Single, 4, "008B8B");
1194    doc.add_paragraph("");
1195    {
1196        let mut p = doc.add_paragraph("");
1197        p.add_run("Accepted by: ").bold(true);
1198        p.add_run("___________________________ Date: ___________");
1199    }
1200    {
1201        let mut p = doc.add_paragraph("");
1202        p.add_run("Print Name: ").bold(true);
1203        p.add_run("___________________________ Title: ___________");
1204    }
1205
1206    doc
1207}
1208
1209// =============================================================================
1210// 4. INVOICE — Crimson + charcoal scheme
1211// =============================================================================
1212fn generate_invoice(_samples_dir: &Path) -> Document {
1213    let mut doc = Document::new();
1214
1215    // Colors: Crimson #B22222, Charcoal #333333, Light #FAF0F0
1216    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1217    doc.set_margins(
1218        Length::inches(0.75),
1219        Length::inches(0.75),
1220        Length::inches(0.75),
1221        Length::inches(0.75),
1222    );
1223    doc.set_title("Invoice INV-2026-0392");
1224    doc.set_author("Walter White");
1225    doc.set_keywords("invoice, Tensorbee");
1226
1227    doc.set_footer("Tensorbee — Thank you for your business!");
1228
1229    // ── Company & Invoice Header ──
1230    {
1231        let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1232        // Company name left, INVOICE right
1233        {
1234            let mut cell = tbl.cell(0, 0).unwrap();
1235            let mut p = cell.add_paragraph("");
1236            p.add_run("TENSORBEE")
1237                .bold(true)
1238                .size(32.0)
1239                .color("B22222")
1240                .font("Helvetica");
1241        }
1242        {
1243            let mut cell = tbl.cell(0, 1).unwrap();
1244            let mut p = cell.add_paragraph("");
1245            p.add_run("INVOICE").bold(true).size(32.0).color("333333");
1246        }
1247        tbl.cell(1, 0)
1248            .unwrap()
1249            .set_text("123 Innovation Drive, Suite 400");
1250        tbl.cell(1, 1).unwrap().set_text("Invoice #: INV-2026-0392");
1251        tbl.cell(2, 0).unwrap().set_text("San Francisco, CA 94105");
1252        tbl.cell(2, 1).unwrap().set_text("Date: February 22, 2026");
1253        tbl.cell(3, 0).unwrap().set_text("walter@tensorbee.com");
1254        tbl.cell(3, 1).unwrap().set_text("Due Date: March 24, 2026");
1255    }
1256
1257    doc.add_paragraph("")
1258        .border_bottom(BorderStyle::Thick, 8, "B22222");
1259    doc.add_paragraph("");
1260
1261    // ── Bill To / Ship To ──
1262    {
1263        let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1264        tbl.cell(0, 0).unwrap().set_text("BILL TO");
1265        tbl.cell(0, 0).unwrap().shading("B22222");
1266        tbl.cell(0, 1).unwrap().set_text("SHIP TO");
1267        tbl.cell(0, 1).unwrap().shading("B22222");
1268        tbl.cell(1, 0).unwrap().set_text("Meridian Dynamics LLC");
1269        tbl.cell(1, 1).unwrap().set_text("Meridian Dynamics LLC");
1270        tbl.cell(2, 0).unwrap().set_text("456 Enterprise Blvd");
1271        tbl.cell(2, 1).unwrap().set_text("Attn: Server Room B");
1272        tbl.cell(3, 0).unwrap().set_text("Austin, TX 78701");
1273        tbl.cell(3, 1)
1274            .unwrap()
1275            .set_text("456 Enterprise Blvd, Austin, TX 78701");
1276    }
1277
1278    doc.add_paragraph("");
1279
1280    // ── Line Items ──
1281    {
1282        let mut tbl = doc
1283            .add_table(8, 5)
1284            .borders(BorderStyle::Single, 2, "B22222")
1285            .width_pct(100.0);
1286        let hdrs = ["Description", "Qty", "Unit Price", "Tax", "Amount"];
1287        for (c, h) in hdrs.iter().enumerate() {
1288            tbl.cell(0, c).unwrap().set_text(h);
1289            tbl.cell(0, c).unwrap().shading("B22222");
1290        }
1291        let items = [
1292            (
1293                "ML Infrastructure Setup — Phase 1",
1294                "1",
1295                "$45,000.00",
1296                "$3,881.25",
1297                "$48,881.25",
1298            ),
1299            (
1300                "Data Pipeline Development (160 hrs)",
1301                "160",
1302                "$225.00",
1303                "$3,105.00",
1304                "$39,105.00",
1305            ),
1306            (
1307                "GPU Cluster Configuration",
1308                "1",
1309                "$12,000.00",
1310                "$1,035.00",
1311                "$13,035.00",
1312            ),
1313            (
1314                "API Gateway Implementation",
1315                "1",
1316                "$18,500.00",
1317                "$1,595.63",
1318                "$20,095.63",
1319            ),
1320            (
1321                "Load Testing & QA (80 hrs)",
1322                "80",
1323                "$195.00",
1324                "$1,345.50",
1325                "$16,945.50",
1326            ),
1327            (
1328                "Documentation & Training",
1329                "1",
1330                "$8,000.00",
1331                "$690.00",
1332                "$8,690.00",
1333            ),
1334            (
1335                "Project Management (3 months)",
1336                "3",
1337                "$6,500.00",
1338                "$1,679.63",
1339                "$21,179.63",
1340            ),
1341        ];
1342        for (i, (desc, qty, unit, tax, amt)) in items.iter().enumerate() {
1343            let r = i + 1;
1344            tbl.cell(r, 0).unwrap().set_text(desc);
1345            tbl.cell(r, 1).unwrap().set_text(qty);
1346            tbl.cell(r, 2).unwrap().set_text(unit);
1347            tbl.cell(r, 3).unwrap().set_text(tax);
1348            tbl.cell(r, 4).unwrap().set_text(amt);
1349            if i % 2 == 0 {
1350                for c in 0..5 {
1351                    tbl.cell(r, c).unwrap().shading("FAF0F0");
1352                }
1353            }
1354        }
1355    }
1356
1357    doc.add_paragraph("");
1358
1359    // ── Totals ──
1360    {
1361        let mut tbl = doc
1362            .add_table(4, 2)
1363            .borders(BorderStyle::Single, 2, "B22222")
1364            .width(Length::inches(3.0))
1365            .alignment(Alignment::Right);
1366        tbl.cell(0, 0).unwrap().set_text("Subtotal");
1367        tbl.cell(0, 1).unwrap().set_text("$154,600.00");
1368        tbl.cell(1, 0).unwrap().set_text("Tax (8.625%)");
1369        tbl.cell(1, 1).unwrap().set_text("$13,334.25");
1370        tbl.cell(2, 0).unwrap().set_text("Discount (5%)");
1371        tbl.cell(2, 1).unwrap().set_text("-$7,730.00");
1372        tbl.cell(3, 0).unwrap().set_text("AMOUNT DUE");
1373        tbl.cell(3, 0).unwrap().shading("B22222");
1374        tbl.cell(3, 1).unwrap().set_text("$160,204.25");
1375        tbl.cell(3, 1).unwrap().shading("B22222");
1376    }
1377
1378    doc.add_paragraph("");
1379    doc.add_paragraph("");
1380
1381    // ── Payment Details ──
1382    {
1383        let mut p = doc.add_paragraph("");
1384        p.add_run("Payment Details")
1385            .bold(true)
1386            .size(14.0)
1387            .color("333333");
1388    }
1389    doc.add_paragraph("Bank: Silicon Valley Bank")
1390        .indent_left(Length::inches(0.3));
1391    doc.add_paragraph("Account: Tensorbee Inc. — 0847-2953-1120")
1392        .indent_left(Length::inches(0.3));
1393    doc.add_paragraph("Routing: 121140399")
1394        .indent_left(Length::inches(0.3));
1395    doc.add_paragraph("Swift: SVBKUS6S")
1396        .indent_left(Length::inches(0.3));
1397
1398    doc.add_paragraph("");
1399
1400    doc.add_paragraph("Please include invoice number INV-2026-0392 in the payment reference.")
1401        .shading("FAF0F0")
1402        .border_all(BorderStyle::Single, 2, "B22222");
1403
1404    doc
1405}
1406
1407// =============================================================================
1408// 5. REPORT — Forest green + earth tones, with images & hierarchical sections
1409// =============================================================================
1410fn generate_report(_samples_dir: &Path) -> Document {
1411    let mut doc = Document::new();
1412
1413    // Colors: Forest #2D5016, Sage #6B8E23, Earth #8B7355, Cream #FFFAF0
1414    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1415    doc.set_margins(
1416        Length::inches(1.0),
1417        Length::inches(1.0),
1418        Length::inches(1.0),
1419        Length::inches(1.0),
1420    );
1421    doc.set_title("Q4 2025 Environmental Impact Report");
1422    doc.set_author("Walter White");
1423    doc.set_subject("Quarterly Environmental Report");
1424    doc.set_keywords("environment, sustainability, report, Tensorbee");
1425
1426    doc.set_different_first_page(true);
1427    doc.set_first_page_header("");
1428    doc.set_header("Tensorbee — Q4 2025 Environmental Impact Report");
1429    doc.set_footer("CONFIDENTIAL — Page");
1430
1431    // ── Cover ──
1432    let cover_bg = create_sample_png(612, 792, [20, 50, 15]);
1433    doc.add_background_image(&cover_bg, "report_cover.png");
1434
1435    for _ in 0..5 {
1436        doc.add_paragraph("");
1437    }
1438    {
1439        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1440        p.add_run("Q4 2025")
1441            .bold(true)
1442            .size(48.0)
1443            .color("FFFFFF")
1444            .font("Georgia");
1445    }
1446    {
1447        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1448        p.add_run("Environmental Impact Report")
1449            .size(24.0)
1450            .color("90EE90")
1451            .font("Georgia")
1452            .italic(true);
1453    }
1454    doc.add_paragraph("");
1455    {
1456        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1457        p.add_run("Tensorbee — Sustainability Division")
1458            .size(14.0)
1459            .color("C0C0C0");
1460    }
1461    {
1462        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1463        p.add_run("Prepared by Walter White, Chief Sustainability Officer")
1464            .size(11.0)
1465            .color("AAAAAA");
1466    }
1467
1468    // ── TOC ──
1469    doc.add_paragraph("").page_break_before(true);
1470    doc.insert_toc(doc.content_count(), 3);
1471
1472    // ── Section 1: Executive Overview ──
1473    doc.add_paragraph("").page_break_before(true);
1474    doc.add_paragraph("Executive Overview").style("Heading1");
1475    doc.add_paragraph(
1476        "This report presents Tensorbee's environmental performance for Q4 2025. Our \
1477         sustainability initiatives have yielded a 34% reduction in carbon emissions compared \
1478         to Q4 2024, exceeding our target of 25%. Key achievements include the transition to \
1479         100% renewable energy in our primary data centers and the launch of our carbon offset \
1480         marketplace.",
1481    )
1482    .first_line_indent(Length::inches(0.3));
1483
1484    // Image: performance chart
1485    let chart_img = create_chart_png(400, 200);
1486    doc.add_paragraph("");
1487    doc.add_picture(
1488        &chart_img,
1489        "performance_chart.png",
1490        Length::inches(5.0),
1491        Length::inches(2.5),
1492    )
1493    .alignment(Alignment::Center);
1494    {
1495        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1496        p.add_run("Figure 1: Quarterly Carbon Emissions (tonnes CO2e)")
1497            .italic(true)
1498            .size(9.0)
1499            .color("666666");
1500    }
1501
1502    // ── Section 2: Energy Consumption ──
1503    doc.add_paragraph("").page_break_before(true);
1504    doc.add_paragraph("Energy Consumption").style("Heading1");
1505
1506    doc.add_paragraph("Data Center Operations")
1507        .style("Heading2");
1508    doc.add_paragraph(
1509        "Our three primary data centers consumed a combined 4.2 GWh in Q4 2025, \
1510         a 12% reduction from Q3 2025 driven by improved cooling efficiency and \
1511         server consolidation.",
1512    );
1513
1514    // Data table
1515    {
1516        let mut tbl = doc
1517            .add_table(5, 4)
1518            .borders(BorderStyle::Single, 4, "2D5016")
1519            .width_pct(100.0);
1520        let hdrs = ["Data Center", "Capacity (MW)", "Usage (GWh)", "PUE"];
1521        for (c, h) in hdrs.iter().enumerate() {
1522            tbl.cell(0, c).unwrap().set_text(h);
1523            tbl.cell(0, c).unwrap().shading("2D5016");
1524        }
1525        let rows = [
1526            ("San Francisco (Primary)", "3.2", "1.8", "1.12"),
1527            ("Dublin (EU)", "2.1", "1.2", "1.18"),
1528            ("Singapore (APAC)", "1.8", "1.2", "1.24"),
1529            ("TOTAL", "7.1", "4.2", "1.17 avg"),
1530        ];
1531        for (i, (dc, cap, use_, pue)) in rows.iter().enumerate() {
1532            tbl.cell(i + 1, 0).unwrap().set_text(dc);
1533            tbl.cell(i + 1, 1).unwrap().set_text(cap);
1534            tbl.cell(i + 1, 2).unwrap().set_text(use_);
1535            tbl.cell(i + 1, 3).unwrap().set_text(pue);
1536            if i == 3 {
1537                for c in 0..4 {
1538                    tbl.cell(i + 1, c).unwrap().shading("E2EFDA");
1539                }
1540            }
1541        }
1542    }
1543
1544    doc.add_paragraph("");
1545
1546    doc.add_paragraph("Renewable Energy Mix").style("Heading2");
1547    doc.add_paragraph("Breakdown of energy sources across all facilities:");
1548
1549    doc.add_bullet_list_item("Solar PV: 42% (1.76 GWh)", 0);
1550    doc.add_bullet_list_item("Wind Power Purchase Agreements: 38% (1.60 GWh)", 0);
1551    doc.add_bullet_list_item("Hydroelectric: 12% (0.50 GWh)", 0);
1552    doc.add_bullet_list_item("Grid (non-renewable): 8% (0.34 GWh)", 0);
1553
1554    // ── Section 3: Water & Waste ──
1555    doc.add_paragraph("Water & Waste Management")
1556        .style("Heading1");
1557
1558    doc.add_paragraph("Water Usage").style("Heading2");
1559    doc.add_paragraph(
1560        "Total water consumption was 12.4 million gallons, a 15% reduction from Q3. \
1561         Our closed-loop cooling systems now recycle 78% of water used in cooling operations.",
1562    );
1563
1564    doc.add_paragraph("Waste Reduction").style("Heading2");
1565    doc.add_paragraph("E-waste management results for Q4:")
1566        .keep_with_next(true);
1567    {
1568        let mut tbl = doc
1569            .add_table(4, 3)
1570            .borders(BorderStyle::Single, 2, "6B8E23")
1571            .width_pct(80.0);
1572        tbl.cell(0, 0).unwrap().set_text("Category");
1573        tbl.cell(0, 0).unwrap().shading("6B8E23");
1574        tbl.cell(0, 1).unwrap().set_text("Weight (kg)");
1575        tbl.cell(0, 1).unwrap().shading("6B8E23");
1576        tbl.cell(0, 2).unwrap().set_text("Recycled %");
1577        tbl.cell(0, 2).unwrap().shading("6B8E23");
1578        let rows = [
1579            ("Server Hardware", "2,450", "94%"),
1580            ("Networking Equipment", "820", "91%"),
1581            ("Storage Media", "340", "99%"),
1582        ];
1583        for (i, (cat, wt, pct)) in rows.iter().enumerate() {
1584            tbl.cell(i + 1, 0).unwrap().set_text(cat);
1585            tbl.cell(i + 1, 1).unwrap().set_text(wt);
1586            tbl.cell(i + 1, 2).unwrap().set_text(pct);
1587        }
1588    }
1589
1590    // ── Section 4: Initiatives ──
1591    doc.add_paragraph("").page_break_before(true);
1592    doc.add_paragraph("Q1 2026 Initiatives").style("Heading1");
1593
1594    doc.add_paragraph("Planned Programs").style("Heading2");
1595    doc.add_numbered_list_item(
1596        "Deploy on-site battery storage at SF data center (2 MWh capacity)",
1597        0,
1598    );
1599    doc.add_numbered_list_item("Pilot immersion cooling in Dublin facility", 0);
1600    doc.add_numbered_list_item("Launch employee commute carbon offset program", 0);
1601    doc.add_numbered_list_item("Achieve ISO 14001 certification for Singapore facility", 0);
1602
1603    doc.add_paragraph("Investment Targets").style("Heading2");
1604    doc.add_paragraph("Sustainability CapEx allocation for FY2026:")
1605        .keep_with_next(true);
1606    {
1607        let mut tbl = doc
1608            .add_table(5, 2)
1609            .borders(BorderStyle::Single, 4, "2D5016");
1610        tbl.cell(0, 0).unwrap().set_text("Initiative");
1611        tbl.cell(0, 0).unwrap().shading("2D5016");
1612        tbl.cell(0, 1).unwrap().set_text("Budget");
1613        tbl.cell(0, 1).unwrap().shading("2D5016");
1614        let rows = [
1615            ("Battery Storage", "$1.2M"),
1616            ("Immersion Cooling Pilot", "$800K"),
1617            ("Solar Panel Expansion", "$2.1M"),
1618            ("Carbon Credits", "$500K"),
1619        ];
1620        for (i, (init, budget)) in rows.iter().enumerate() {
1621            tbl.cell(i + 1, 0).unwrap().set_text(init);
1622            tbl.cell(i + 1, 1).unwrap().set_text(budget);
1623            if i % 2 == 0 {
1624                tbl.cell(i + 1, 0).unwrap().shading("FFFAF0");
1625                tbl.cell(i + 1, 1).unwrap().shading("FFFAF0");
1626            }
1627        }
1628    }
1629
1630    // Image: sustainability roadmap
1631    let roadmap_img = create_sample_png(500, 100, [40, 80, 30]);
1632    doc.add_paragraph("");
1633    doc.add_picture(
1634        &roadmap_img,
1635        "roadmap.png",
1636        Length::inches(6.0),
1637        Length::inches(1.2),
1638    )
1639    .alignment(Alignment::Center);
1640    {
1641        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1642        p.add_run("Figure 2: 2026 Sustainability Roadmap")
1643            .italic(true)
1644            .size(9.0)
1645            .color("666666");
1646    }
1647
1648    doc.add_paragraph("");
1649    doc.add_paragraph(
1650        "For questions about this report, contact Walter White at sustainability@tensorbee.com.",
1651    )
1652    .shading("FFFAF0")
1653    .border_all(BorderStyle::Single, 2, "2D5016");
1654
1655    doc
1656}
1657
1658// =============================================================================
1659// 6. LETTER — Slate blue + silver scheme
1660// =============================================================================
1661fn generate_letter(_samples_dir: &Path) -> Document {
1662    let mut doc = Document::new();
1663
1664    // Colors: Slate #4A5568, Blue accent #3182CE
1665    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1666    doc.set_margins(
1667        Length::inches(1.25),
1668        Length::inches(1.25),
1669        Length::inches(1.25),
1670        Length::inches(1.25),
1671    );
1672    doc.set_title("Business Letter");
1673    doc.set_author("Walter White");
1674
1675    // Banner header using raw XML
1676    let logo_img = create_logo_png(220, 48);
1677    let banner = build_header_banner_xml(
1678        "rId1",
1679        &BannerOpts {
1680            bg_color: "4A5568",
1681            banner_width: 7772400,
1682            banner_height: 731520, // ~0.8"
1683            logo_width: 2011680,
1684            logo_height: 438912,
1685            logo_x_offset: 295125,
1686            logo_y_offset: 146304,
1687        },
1688    );
1689    doc.set_raw_header_with_images(
1690        banner,
1691        &[("rId1", &logo_img, "logo.png")],
1692        rdocx_oxml::header_footer::HdrFtrType::Default,
1693    );
1694    doc.set_margins(
1695        Length::twips(2000),
1696        Length::twips(1800),
1697        Length::twips(1440),
1698        Length::twips(1800),
1699    );
1700    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
1701
1702    // Footer with contact
1703    doc.set_footer(
1704        "Tensorbee | 123 Innovation Drive, Suite 400 | San Francisco, CA 94105 | tensorbee.com",
1705    );
1706
1707    // ── Sender Address ──
1708    doc.add_paragraph("Walter White");
1709    doc.add_paragraph("Chief Executive Officer");
1710    doc.add_paragraph("Tensorbee");
1711    doc.add_paragraph("123 Innovation Drive, Suite 400");
1712    doc.add_paragraph("San Francisco, CA 94105");
1713    doc.add_paragraph("");
1714    doc.add_paragraph("February 22, 2026");
1715
1716    doc.add_paragraph("");
1717
1718    // ── Recipient ──
1719    doc.add_paragraph("Ms. Sarah Chen");
1720    doc.add_paragraph("VP of Engineering");
1721    doc.add_paragraph("Meridian Dynamics LLC");
1722    doc.add_paragraph("456 Enterprise Boulevard");
1723    doc.add_paragraph("Austin, TX 78701");
1724
1725    doc.add_paragraph("");
1726
1727    // ── Subject ──
1728    {
1729        let mut p = doc.add_paragraph("");
1730        p.add_run("Re: Strategic Technology Partnership — Phase 2 Expansion")
1731            .bold(true);
1732    }
1733
1734    doc.add_paragraph("");
1735
1736    // ── Body ──
1737    doc.add_paragraph("Dear Ms. Chen,");
1738    doc.add_paragraph("");
1739
1740    doc.add_paragraph(
1741        "I am writing to express Tensorbee's enthusiasm for expanding our strategic technology \
1742         partnership with Meridian Dynamics. Following the successful completion of Phase 1, \
1743         which delivered a 40% improvement in your ML inference pipeline throughput, we believe \
1744         the foundation is firmly established for an ambitious Phase 2 engagement.",
1745    )
1746    .first_line_indent(Length::inches(0.5));
1747
1748    doc.add_paragraph(
1749        "During our recent executive review, your team highlighted three priority areas for \
1750         the next phase of collaboration:",
1751    )
1752    .first_line_indent(Length::inches(0.5));
1753
1754    doc.add_numbered_list_item(
1755        "Real-time anomaly detection for your financial transaction monitoring system, \
1756         targeting sub-100ms latency at 50,000 transactions per second.",
1757        0,
1758    );
1759    doc.add_numbered_list_item(
1760        "Federated learning infrastructure to enable model training across your distributed \
1761         data centers without centralizing sensitive financial data.",
1762        0,
1763    );
1764    doc.add_numbered_list_item(
1765        "MLOps automation to reduce your model deployment cycle from the current 5 days \
1766         to under 4 hours.",
1767        0,
1768    );
1769
1770    doc.add_paragraph(
1771        "Our engineering team has prepared a detailed technical proposal addressing each of \
1772         these areas. We have allocated a dedicated team of eight senior engineers, led by \
1773         our CTO, to ensure continuity with the Phase 1 team your organization has already \
1774         built a productive working relationship with.",
1775    )
1776    .first_line_indent(Length::inches(0.5));
1777
1778    doc.add_paragraph(
1779        "I would welcome the opportunity to present our Phase 2 proposal in person. My \
1780         assistant will reach out to coordinate a meeting at your convenience during the \
1781         first week of March.",
1782    )
1783    .first_line_indent(Length::inches(0.5));
1784
1785    doc.add_paragraph("");
1786
1787    doc.add_paragraph("Warm regards,");
1788    doc.add_paragraph("");
1789    doc.add_paragraph("");
1790
1791    // Signature line
1792    doc.add_paragraph("")
1793        .border_bottom(BorderStyle::Single, 4, "4A5568");
1794    {
1795        let mut p = doc.add_paragraph("");
1796        p.add_run("Walter White").bold(true).size(12.0);
1797    }
1798    {
1799        let mut p = doc.add_paragraph("");
1800        p.add_run("Chief Executive Officer, Tensorbee")
1801            .italic(true)
1802            .size(10.0)
1803            .color("666666");
1804    }
1805    {
1806        let mut p = doc.add_paragraph("");
1807        p.add_run("walter@tensorbee.com | +1 (415) 555-0199")
1808            .size(10.0)
1809            .color("888888");
1810    }
1811
1812    doc
1813}
1814
1815// =============================================================================
1816// 7. EMPLOYMENT CONTRACT — Purple + charcoal scheme
1817// =============================================================================
1818fn generate_contract(_samples_dir: &Path) -> Document {
1819    let mut doc = Document::new();
1820
1821    // Colors: Purple #5B2C6F, Plum #8E44AD, Gray #2C3E50
1822    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1823    doc.set_margins(
1824        Length::inches(1.25),
1825        Length::inches(1.0),
1826        Length::inches(1.0),
1827        Length::inches(1.0),
1828    );
1829    doc.set_title("Employment Agreement");
1830    doc.set_author("Tensorbee HR Department");
1831    doc.set_subject("Employment Contract — Walter White");
1832    doc.set_keywords("employment, contract, agreement, Tensorbee");
1833
1834    doc.set_header("TENSORBEE — Employment Agreement");
1835    doc.set_footer("Employment Agreement — Walter White — Page");
1836
1837    // ── Title Block ──
1838    doc.add_paragraph("")
1839        .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1840    {
1841        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1842        p.add_run("EMPLOYMENT AGREEMENT")
1843            .bold(true)
1844            .size(24.0)
1845            .color("5B2C6F")
1846            .font("Georgia");
1847    }
1848    doc.add_paragraph("")
1849        .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1850    doc.add_paragraph("");
1851
1852    // ── Parties ──
1853    doc.add_paragraph(
1854        "This Employment Agreement (\"Agreement\") is entered into as of February 22, 2026 \
1855         (\"Effective Date\"), by and between:",
1856    );
1857
1858    doc.add_paragraph("");
1859
1860    {
1861        let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1862        p.add_run("EMPLOYER: ").bold(true);
1863        p.add_run(
1864            "Tensorbee, Inc., a Delaware corporation with its principal place of business at \
1865                   123 Innovation Drive, Suite 400, San Francisco, CA 94105 (\"Company\")",
1866        );
1867    }
1868    doc.add_paragraph("");
1869    {
1870        let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1871        p.add_run("EMPLOYEE: ").bold(true);
1872        p.add_run(
1873            "Walter White, residing at 308 Negra Arroyo Lane, Albuquerque, NM 87104 (\"Employee\")",
1874        );
1875    }
1876
1877    doc.add_paragraph("");
1878    doc.add_paragraph("The Company and Employee are collectively referred to as the \"Parties.\"");
1879    doc.add_paragraph("");
1880
1881    // ── Article 1: Position and Duties ──
1882    doc.add_paragraph("Article 1 — Position and Duties")
1883        .style("Heading1");
1884    {
1885        let mut p = doc.add_paragraph("");
1886        p.add_run("1.1 ").bold(true);
1887        p.add_run("The Company hereby employs the Employee as ");
1888        p.add_run("Chief Executive Officer (CEO)")
1889            .bold(true)
1890            .italic(true);
1891        p.add_run(", reporting directly to the Board of Directors.");
1892    }
1893    {
1894        let mut p = doc.add_paragraph("");
1895        p.add_run("1.2 ").bold(true);
1896        p.add_run("The Employee shall devote full working time, attention, and best efforts to \
1897                   the performance of duties as reasonably assigned by the Board, including but not limited to:");
1898    }
1899    doc.add_bullet_list_item(
1900        "Setting and executing the Company's strategic vision and business plan",
1901        0,
1902    );
1903    doc.add_bullet_list_item(
1904        "Overseeing all operations, engineering, and commercial activities",
1905        0,
1906    );
1907    doc.add_bullet_list_item(
1908        "Representing the Company to investors, customers, and the public",
1909        0,
1910    );
1911    doc.add_bullet_list_item("Recruiting, developing, and retaining key talent", 0);
1912
1913    {
1914        let mut p = doc.add_paragraph("");
1915        p.add_run("1.3 ").bold(true);
1916        p.add_run(
1917            "The Employee's primary work location shall be the Company's San Francisco \
1918                   headquarters, with reasonable travel as required by business needs.",
1919        );
1920    }
1921
1922    // ── Article 2: Compensation ──
1923    doc.add_paragraph("Article 2 — Compensation and Benefits")
1924        .style("Heading1");
1925    {
1926        let mut p = doc.add_paragraph("");
1927        p.add_run("2.1 Base Salary. ").bold(true);
1928        p.add_run("The Company shall pay the Employee an annual base salary of ");
1929        p.add_run("$375,000.00").bold(true);
1930        p.add_run(" (Three Hundred Seventy-Five Thousand Dollars), payable in accordance with the \
1931                   Company's standard payroll schedule, less applicable withholdings and deductions.");
1932    }
1933    {
1934        let mut p = doc.add_paragraph("");
1935        p.add_run("2.2 Annual Bonus. ").bold(true);
1936        p.add_run("The Employee shall be eligible for an annual performance bonus of up to ");
1937        p.add_run("40%").bold(true);
1938        p.add_run(
1939            " of base salary, based on achievement of mutually agreed performance objectives.",
1940        );
1941    }
1942    {
1943        let mut p = doc.add_paragraph("");
1944        p.add_run("2.3 Equity. ").bold(true);
1945        p.add_run("Subject to Board approval, the Employee shall receive a stock option grant of ");
1946        p.add_run("500,000 shares").bold(true);
1947        p.add_run(
1948            " of the Company's common stock, vesting over four (4) years with a one-year cliff, \
1949                   at an exercise price equal to the fair market value on the date of grant.",
1950        );
1951    }
1952
1953    // Compensation summary table
1954    doc.add_paragraph("");
1955    {
1956        let mut tbl = doc
1957            .add_table(5, 2)
1958            .borders(BorderStyle::Single, 4, "5B2C6F")
1959            .width_pct(70.0)
1960            .alignment(Alignment::Center);
1961        tbl.cell(0, 0).unwrap().set_text("Compensation Element");
1962        tbl.cell(0, 0).unwrap().shading("5B2C6F");
1963        tbl.cell(0, 1).unwrap().set_text("Value");
1964        tbl.cell(0, 1).unwrap().shading("5B2C6F");
1965        let items = [
1966            ("Base Salary", "$375,000/year"),
1967            ("Target Bonus", "Up to 40% ($150,000)"),
1968            ("Equity Grant", "500,000 shares (4yr vest)"),
1969            ("Total Target Comp", "$525,000 + equity"),
1970        ];
1971        for (i, (elem, val)) in items.iter().enumerate() {
1972            tbl.cell(i + 1, 0).unwrap().set_text(elem);
1973            tbl.cell(i + 1, 1).unwrap().set_text(val);
1974            if i == 3 {
1975                tbl.cell(i + 1, 0).unwrap().shading("E8DAEF");
1976                tbl.cell(i + 1, 1).unwrap().shading("E8DAEF");
1977            }
1978        }
1979    }
1980
1981    // ── Article 3: Benefits ──
1982    doc.add_paragraph("").page_break_before(true);
1983    doc.add_paragraph("Article 3 — Benefits").style("Heading1");
1984    {
1985        let mut p = doc.add_paragraph("");
1986        p.add_run("3.1 ").bold(true);
1987        p.add_run(
1988            "The Employee shall be entitled to participate in all benefit programs \
1989                   generally available to senior executives, including:",
1990        );
1991    }
1992    doc.add_bullet_list_item(
1993        "Medical, dental, and vision insurance (100% premium coverage for employee and dependents)",
1994        0,
1995    );
1996    doc.add_bullet_list_item("401(k) retirement plan with 6% company match", 0);
1997    doc.add_bullet_list_item("Life insurance and long-term disability coverage", 0);
1998    doc.add_bullet_list_item("Annual professional development allowance of $10,000", 0);
1999
2000    {
2001        let mut p = doc.add_paragraph("");
2002        p.add_run("3.2 Paid Time Off. ").bold(true);
2003        p.add_run(
2004            "The Employee shall receive 25 days of paid vacation per year, plus Company holidays, \
2005                   accruing on a monthly basis.",
2006        );
2007    }
2008
2009    // ── Article 4: Term and Termination ──
2010    doc.add_paragraph("Article 4 — Term and Termination")
2011        .style("Heading1");
2012    {
2013        let mut p = doc.add_paragraph("");
2014        p.add_run("4.1 At-Will Employment. ").bold(true);
2015        p.add_run(
2016            "This Agreement is for at-will employment and may be terminated by either Party \
2017                   at any time, with or without cause, subject to the notice provisions herein.",
2018        );
2019    }
2020    {
2021        let mut p = doc.add_paragraph("");
2022        p.add_run("4.2 Notice Period. ").bold(true);
2023        p.add_run("Either Party shall provide ");
2024        p.add_run("ninety (90) days").bold(true);
2025        p.add_run(" written notice of termination, or pay in lieu thereof.");
2026    }
2027    {
2028        let mut p = doc.add_paragraph("");
2029        p.add_run("4.3 Severance. ").bold(true);
2030        p.add_run("In the event of termination by the Company without Cause, the Employee shall \
2031                   receive (a) twelve (12) months of base salary continuation, (b) pro-rata bonus \
2032                   for the year of termination, and (c) twelve (12) months of COBRA premium coverage.");
2033    }
2034
2035    // ── Article 5: Confidentiality ──
2036    doc.add_paragraph("Article 5 — Confidentiality and IP")
2037        .style("Heading1");
2038    {
2039        let mut p = doc.add_paragraph("");
2040        p.add_run("5.1 ").bold(true);
2041        p.add_run(
2042            "The Employee agrees to maintain strict confidentiality of all proprietary \
2043                   information, trade secrets, and business plans of the Company during and after \
2044                   employment.",
2045        );
2046    }
2047    {
2048        let mut p = doc.add_paragraph("");
2049        p.add_run("5.2 ").bold(true);
2050        p.add_run(
2051            "All intellectual property, inventions, and works of authorship created by the \
2052                   Employee during the term of employment and within the scope of duties shall be \
2053                   the sole and exclusive property of the Company.",
2054        );
2055    }
2056
2057    // ── Article 6: Non-Compete ──
2058    doc.add_paragraph("Article 6 — Non-Competition")
2059        .style("Heading1");
2060    {
2061        let mut p = doc.add_paragraph("");
2062        p.add_run("6.1 ").bold(true);
2063        p.add_run("For a period of twelve (12) months following termination, the Employee agrees \
2064                   not to engage in any business that directly competes with the Company's core \
2065                   business of AI infrastructure and ML operations services, within the United States.");
2066    }
2067
2068    // ── Article 7: General Provisions ──
2069    doc.add_paragraph("Article 7 — General Provisions")
2070        .style("Heading1");
2071    {
2072        let mut p = doc.add_paragraph("");
2073        p.add_run("7.1 Governing Law. ").bold(true);
2074        p.add_run(
2075            "This Agreement shall be governed by and construed in accordance with the laws of \
2076                   the State of California.",
2077        );
2078    }
2079    {
2080        let mut p = doc.add_paragraph("");
2081        p.add_run("7.2 Entire Agreement. ").bold(true);
2082        p.add_run(
2083            "This Agreement constitutes the entire agreement between the Parties and supersedes \
2084                   all prior negotiations, representations, and agreements.",
2085        );
2086    }
2087    {
2088        let mut p = doc.add_paragraph("");
2089        p.add_run("7.3 Amendment. ").bold(true);
2090        p.add_run(
2091            "This Agreement may only be amended by a written instrument signed by both Parties.",
2092        );
2093    }
2094
2095    // ── Signature Block ──
2096    doc.add_paragraph("").page_break_before(true);
2097    doc.add_paragraph("IN WITNESS WHEREOF, the Parties have executed this Employment Agreement as of the Effective Date.")
2098        .space_after(Length::pt(24.0));
2099
2100    // Signature table
2101    {
2102        let mut tbl = doc.add_table(6, 2).width_pct(100.0);
2103        tbl.cell(0, 0).unwrap().set_text("FOR THE COMPANY:");
2104        tbl.cell(0, 0).unwrap().shading("5B2C6F");
2105        tbl.cell(0, 1).unwrap().set_text("EMPLOYEE:");
2106        tbl.cell(0, 1).unwrap().shading("5B2C6F");
2107
2108        tbl.cell(1, 0).unwrap().set_text("");
2109        tbl.cell(1, 1).unwrap().set_text("");
2110        tbl.row(1).unwrap().height(Length::pt(40.0));
2111
2112        tbl.cell(2, 0)
2113            .unwrap()
2114            .set_text("Signature: ___________________________");
2115        tbl.cell(2, 1)
2116            .unwrap()
2117            .set_text("Signature: ___________________________");
2118        tbl.cell(3, 0)
2119            .unwrap()
2120            .set_text("Name: Board Representative");
2121        tbl.cell(3, 1).unwrap().set_text("Name: Walter White");
2122        tbl.cell(4, 0)
2123            .unwrap()
2124            .set_text("Title: Chair, Board of Directors");
2125        tbl.cell(4, 1)
2126            .unwrap()
2127            .set_text("Title: Chief Executive Officer");
2128        tbl.cell(5, 0).unwrap().set_text("Date: _______________");
2129        tbl.cell(5, 1).unwrap().set_text("Date: _______________");
2130    }
2131
2132    doc
2133}
Source

pub fn set_first_page_header(&mut self, text: &str)

Set the first-page header text.

Examples found in repository?
examples/generate_all_samples.rs (line 117)
97fn generate_feature_showcase(_samples_dir: &Path) -> Document {
98    let mut doc = Document::new();
99
100    // ── Page Setup & Metadata ──
101    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
102    doc.set_margins(
103        Length::inches(1.0),
104        Length::inches(1.0),
105        Length::inches(1.0),
106        Length::inches(1.0),
107    );
108    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
109    doc.set_gutter(Length::twips(0));
110    doc.set_title("rdocx Feature Showcase");
111    doc.set_author("rdocx Sample Generator");
112    doc.set_subject("Comprehensive feature demonstration");
113    doc.set_keywords("rdocx, docx, rust, sample, showcase");
114
115    // Headers & Footers with different first page
116    doc.set_different_first_page(true);
117    doc.set_first_page_header("rdocx");
118    doc.set_first_page_footer("Feature Showcase — Cover Page");
119    doc.set_header("rdocx Feature Showcase");
120    doc.set_footer("Generated by rdocx");
121
122    // ── COVER PAGE ──
123    let bg = create_sample_png(612, 792, [20, 45, 90]);
124    doc.add_background_image(&bg, "cover_bg.png");
125
126    for _ in 0..3 {
127        doc.add_paragraph("");
128    }
129    {
130        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
131        p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
132    }
133    {
134        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
135        p.add_run("Complete Feature Showcase")
136            .size(28.0)
137            .color("FFFFFF")
138            .italic(true);
139    }
140    doc.add_paragraph("");
141    {
142        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
143        p.add_run("Every feature of the rdocx Rust library")
144            .size(13.0)
145            .color("B0C4DE");
146    }
147    {
148        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
149        p.add_run("demonstrated in a single document.")
150            .size(13.0)
151            .color("B0C4DE");
152    }
153
154    // ── TABLE OF CONTENTS ──
155    doc.add_paragraph("").page_break_before(true);
156    doc.insert_toc(doc.content_count(), 3);
157
158    // ── SECTION 1: TEXT FORMATTING ──
159    doc.add_paragraph("").page_break_before(true);
160    doc.add_paragraph("1. Text Formatting").style("Heading1");
161
162    // Paragraph Alignment
163    doc.add_paragraph("1.1 Paragraph Alignment")
164        .style("Heading2");
165    doc.add_paragraph("Left-aligned paragraph (default).")
166        .alignment(Alignment::Left);
167    doc.add_paragraph("Center-aligned paragraph.")
168        .alignment(Alignment::Center);
169    doc.add_paragraph("Right-aligned paragraph.")
170        .alignment(Alignment::Right);
171    doc.add_paragraph(
172        "Justified paragraph — this text is long enough to demonstrate how justified alignment \
173         distributes extra space across word gaps so lines fill the full width of the text area.",
174    )
175    .alignment(Alignment::Justify);
176
177    doc.add_paragraph("");
178
179    // Run Formatting
180    doc.add_paragraph("1.2 Run Formatting").style("Heading2");
181    {
182        let mut p = doc.add_paragraph("");
183        p.add_run("Bold").bold(true);
184        p.add_run(" | ");
185        p.add_run("Italic").italic(true);
186        p.add_run(" | ");
187        p.add_run("Bold Italic").bold(true).italic(true);
188        p.add_run(" | ");
189        p.add_run("Underline").underline(true);
190        p.add_run(" | ");
191        p.add_run("Strikethrough").strike(true);
192        p.add_run(" | ");
193        p.add_run("Double Strike").double_strike(true);
194    }
195    {
196        let mut p = doc.add_paragraph("");
197        p.add_run("Red").color("FF0000");
198        p.add_run(" | ");
199        p.add_run("Blue").color("0000FF");
200        p.add_run(" | ");
201        p.add_run("Green").color("00AA00");
202        p.add_run(" | ");
203        p.add_run("Highlighted").highlight("FFFF00");
204    }
205    {
206        let mut p = doc.add_paragraph("");
207        p.add_run("8pt").size(8.0);
208        p.add_run(" | ");
209        p.add_run("11pt").size(11.0);
210        p.add_run(" | ");
211        p.add_run("16pt").size(16.0);
212        p.add_run(" | ");
213        p.add_run("24pt").size(24.0);
214    }
215    {
216        let mut p = doc.add_paragraph("");
217        p.add_run("Arial").font("Arial");
218        p.add_run(" | ");
219        p.add_run("Times New Roman").font("Times New Roman");
220        p.add_run(" | ");
221        p.add_run("Courier New").font("Courier New");
222    }
223    {
224        let mut p = doc.add_paragraph("");
225        p.add_run("H");
226        p.add_run("2").subscript();
227        p.add_run("O (subscript) | E=mc");
228        p.add_run("2").superscript();
229        p.add_run(" (superscript)");
230    }
231    {
232        let mut p = doc.add_paragraph("");
233        p.add_run("ALL CAPS").all_caps(true);
234        p.add_run(" | ");
235        p.add_run("Small Caps").small_caps(true);
236        p.add_run(" | ");
237        p.add_run("Expanded").character_spacing(Length::twips(40));
238        p.add_run(" | ");
239        p.add_run("Hidden text (not visible)").hidden(true);
240    }
241
242    doc.add_paragraph("");
243
244    // Underline Styles
245    doc.add_paragraph("1.3 Underline Styles").style("Heading2");
246    {
247        let mut p = doc.add_paragraph("");
248        p.add_run("Single ")
249            .underline_style(rdocx::UnderlineStyle::Single);
250        p.add_run("Double ")
251            .underline_style(rdocx::UnderlineStyle::Double);
252        p.add_run("Thick ")
253            .underline_style(rdocx::UnderlineStyle::Thick);
254        p.add_run("Dotted ")
255            .underline_style(rdocx::UnderlineStyle::Dotted);
256        p.add_run("Dash ")
257            .underline_style(rdocx::UnderlineStyle::Dash);
258        p.add_run("Wave ")
259            .underline_style(rdocx::UnderlineStyle::Wave);
260    }
261
262    // ── SECTION 2: PARAGRAPH FORMATTING ──
263    doc.add_paragraph("").page_break_before(true);
264    doc.add_paragraph("2. Paragraph Formatting")
265        .style("Heading1");
266
267    doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
268    doc.add_paragraph("Paragraph with light green shading")
269        .shading("E2EFDA");
270    doc.add_paragraph("Paragraph with bottom border")
271        .border_bottom(BorderStyle::Single, 6, "2E75B6");
272    doc.add_paragraph("Paragraph with all borders (red)")
273        .border_all(BorderStyle::Single, 4, "FF0000");
274
275    doc.add_paragraph("2.2 Indentation").style("Heading2");
276    doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
277        .indent_left(Length::inches(1.0))
278        .hanging_indent(Length::inches(0.5));
279    doc.add_paragraph("0.5-inch first-line indent on this paragraph")
280        .first_line_indent(Length::inches(0.5));
281    doc.add_paragraph("Both left and right indent (0.75 inches each)")
282        .indent_left(Length::inches(0.75))
283        .indent_right(Length::inches(0.75));
284
285    doc.add_paragraph("2.3 Spacing & Line Height")
286        .style("Heading2");
287    doc.add_paragraph("Extra space before (24pt) and after (12pt)")
288        .space_before(Length::pt(24.0))
289        .space_after(Length::pt(12.0));
290    doc.add_paragraph(
291        "Double line spacing paragraph. This text should have extra vertical space between \
292         lines to demonstrate line_spacing_multiple(2.0).",
293    )
294    .line_spacing_multiple(2.0);
295    doc.add_paragraph("Exact 20pt line spacing (fixed height).")
296        .line_spacing(20.0);
297
298    doc.add_paragraph("2.4 Pagination Controls")
299        .style("Heading2");
300    doc.add_paragraph("keep_with_next — this paragraph stays with the next")
301        .keep_with_next(true);
302    doc.add_paragraph("(This paragraph was kept with the one above.)");
303    doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
304        .keep_together(true);
305    doc.add_paragraph("widow_control — prevents widow/orphan lines")
306        .widow_control(true);
307
308    // ── SECTION 3: LISTS ──
309    doc.add_paragraph("").page_break_before(true);
310    doc.add_paragraph("3. Lists").style("Heading1");
311
312    doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
313    doc.add_bullet_list_item("First item", 0);
314    doc.add_bullet_list_item("Second item", 0);
315    doc.add_bullet_list_item("Nested level 1", 1);
316    doc.add_bullet_list_item("Nested level 2", 2);
317    doc.add_bullet_list_item("Back to level 1", 1);
318    doc.add_bullet_list_item("Third item", 0);
319
320    doc.add_paragraph("");
321
322    doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
323    doc.add_numbered_list_item("First numbered item", 0);
324    doc.add_numbered_list_item("Second numbered item", 0);
325    doc.add_numbered_list_item("Sub-item A", 1);
326    doc.add_numbered_list_item("Sub-item B", 1);
327    doc.add_numbered_list_item("Third numbered item", 0);
328
329    // ── SECTION 4: TAB STOPS ──
330    doc.add_paragraph("");
331    doc.add_paragraph("4. Tab Stops").style("Heading1");
332
333    doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
334    doc.add_paragraph("Left\tCenter\tRight\tDecimal")
335        .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
336        .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
337        .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
338        .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
339
340    doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
341    doc.add_paragraph("Item\t\tPrice")
342        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
343        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
344        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
345    doc.add_paragraph("Widget\t\t$19.99")
346        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
347        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
348        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
349    doc.add_paragraph("Gadget\t\t$249.50")
350        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
351        .add_tab_stop_with_leader(
352            TabAlignment::Right,
353            Length::inches(4.0),
354            TabLeader::Underscore,
355        )
356        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
357
358    // ── SECTION 5: TABLES ──
359    doc.add_paragraph("").page_break_before(true);
360    doc.add_paragraph("5. Tables").style("Heading1");
361
362    doc.add_paragraph("5.1 Basic Table").style("Heading2");
363    {
364        let mut tbl = doc
365            .add_table(4, 3)
366            .borders(BorderStyle::Single, 4, "000000");
367        for col in 0..3 {
368            tbl.cell(0, col).unwrap().shading("2E75B6");
369        }
370        tbl.cell(0, 0).unwrap().set_text("Name");
371        tbl.cell(0, 1).unwrap().set_text("Role");
372        tbl.cell(0, 2).unwrap().set_text("Location");
373        tbl.cell(1, 0).unwrap().set_text("Walter White");
374        tbl.cell(1, 1).unwrap().set_text("CEO");
375        tbl.cell(1, 2).unwrap().set_text("Albuquerque");
376        tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
377        tbl.cell(2, 1).unwrap().set_text("CTO");
378        tbl.cell(2, 2).unwrap().set_text("Remote");
379        tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
380        tbl.cell(3, 1).unwrap().set_text("Security");
381        tbl.cell(3, 2).unwrap().set_text("Washington");
382    }
383
384    doc.add_paragraph("");
385
386    doc.add_paragraph("5.2 Column Span & Cell Shading")
387        .style("Heading2");
388    {
389        let mut tbl = doc
390            .add_table(3, 4)
391            .borders(BorderStyle::Single, 4, "000000")
392            .width_pct(100.0);
393        tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
394        tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
395        tbl.cell(1, 0).unwrap().set_text("Region");
396        tbl.cell(1, 0).unwrap().shading("D6E4F0");
397        tbl.cell(1, 1).unwrap().set_text("Q1");
398        tbl.cell(1, 1).unwrap().shading("D6E4F0");
399        tbl.cell(1, 2).unwrap().set_text("Q2");
400        tbl.cell(1, 2).unwrap().shading("D6E4F0");
401        tbl.cell(1, 3).unwrap().set_text("Total");
402        tbl.cell(1, 3).unwrap().shading("D6E4F0");
403        tbl.cell(2, 0).unwrap().set_text("Americas");
404        tbl.cell(2, 1).unwrap().set_text("$2.4M");
405        tbl.cell(2, 2).unwrap().set_text("$2.7M");
406        tbl.cell(2, 3).unwrap().set_text("$5.1M");
407    }
408
409    doc.add_paragraph("");
410
411    doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
412    {
413        let mut tbl = doc
414            .add_table(4, 3)
415            .borders(BorderStyle::Single, 4, "000000");
416        tbl.cell(0, 0).unwrap().set_text("Category");
417        tbl.cell(0, 0).unwrap().shading("E2EFDA");
418        tbl.cell(0, 1).unwrap().set_text("Item");
419        tbl.cell(0, 1).unwrap().shading("E2EFDA");
420        tbl.cell(0, 2).unwrap().set_text("Price");
421        tbl.cell(0, 2).unwrap().shading("E2EFDA");
422        tbl.cell(1, 0).unwrap().set_text("Hardware");
423        tbl.cell(1, 0).unwrap().v_merge_restart();
424        tbl.cell(1, 1).unwrap().set_text("Laptop");
425        tbl.cell(1, 2).unwrap().set_text("$1,200");
426        tbl.cell(2, 0).unwrap().v_merge_continue();
427        tbl.cell(2, 1).unwrap().set_text("Monitor");
428        tbl.cell(2, 2).unwrap().set_text("$450");
429        tbl.cell(3, 0).unwrap().set_text("Software");
430        tbl.cell(3, 1).unwrap().set_text("IDE License");
431        tbl.cell(3, 2).unwrap().set_text("$200/yr");
432    }
433
434    doc.add_paragraph("");
435
436    doc.add_paragraph("5.4 Nested Table").style("Heading2");
437    {
438        let mut tbl = doc
439            .add_table(2, 2)
440            .borders(BorderStyle::Single, 6, "2E75B6");
441        tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
442        tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
443        tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
444        {
445            let mut cell = tbl.cell(1, 1).unwrap();
446            cell.set_text("Nested table below:");
447            let mut nested = cell
448                .add_table(2, 2)
449                .borders(BorderStyle::Single, 2, "FF6600");
450            nested.cell(0, 0).unwrap().set_text("A");
451            nested.cell(0, 1).unwrap().set_text("B");
452            nested.cell(1, 0).unwrap().set_text("C");
453            nested.cell(1, 1).unwrap().set_text("D");
454        }
455    }
456
457    doc.add_paragraph("");
458
459    doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
460        .style("Heading2");
461    {
462        let mut tbl = doc
463            .add_table(2, 3)
464            .borders(BorderStyle::Single, 4, "666666");
465        tbl.row(0).unwrap().height(Length::pt(50.0)).header();
466        tbl.cell(0, 0).unwrap().set_text("Top");
467        tbl.cell(0, 0)
468            .unwrap()
469            .vertical_alignment(VerticalAlignment::Top)
470            .shading("FFF2CC");
471        tbl.cell(0, 1).unwrap().set_text("Center");
472        tbl.cell(0, 1)
473            .unwrap()
474            .vertical_alignment(VerticalAlignment::Center)
475            .shading("D9E2F3");
476        tbl.cell(0, 2).unwrap().set_text("Bottom");
477        tbl.cell(0, 2)
478            .unwrap()
479            .vertical_alignment(VerticalAlignment::Bottom)
480            .shading("E2EFDA");
481        tbl.row(1).unwrap().cant_split();
482        tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
483        tbl.cell(1, 0).unwrap().no_wrap();
484        tbl.cell(1, 1).unwrap().set_text("Fixed width");
485        tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
486        tbl.cell(1, 2).unwrap().set_text("Normal");
487    }
488
489    // ── SECTION 6: IMAGES ──
490    doc.add_paragraph("").page_break_before(true);
491    doc.add_paragraph("6. Images").style("Heading1");
492
493    doc.add_paragraph("6.1 Inline Image").style("Heading2");
494    doc.add_paragraph("A blue gradient image below:");
495    let img = create_sample_png(200, 50, [0, 80, 200]);
496    doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
497
498    doc.add_paragraph("");
499    doc.add_paragraph("6.2 Header Image").style("Heading2");
500    let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
501    doc.set_header_image(
502        &hdr_img,
503        "header_logo.png",
504        Length::inches(2.0),
505        Length::inches(0.2),
506    );
507    doc.add_paragraph("The header now contains an inline image (check the top of this page).");
508
509    doc.add_paragraph("");
510    doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
511
512    // ── SECTION 7: CONTENT MANIPULATION ──
513    doc.add_paragraph("").page_break_before(true);
514    doc.add_paragraph("7. Content Manipulation")
515        .style("Heading1");
516
517    doc.add_paragraph("7.1 Placeholder Replacement")
518        .style("Heading2");
519    doc.add_paragraph("Customer: {{customer}}");
520    doc.add_paragraph("Date: {{date}}");
521    doc.add_paragraph("Reference: {{ref_number}}");
522    {
523        let mut tbl = doc
524            .add_table(2, 2)
525            .borders(BorderStyle::Single, 4, "000000");
526        tbl.cell(0, 0).unwrap().set_text("Project");
527        tbl.cell(0, 0).unwrap().shading("D6E4F0");
528        tbl.cell(0, 1).unwrap().set_text("{{project}}");
529        tbl.cell(1, 0).unwrap().set_text("Status");
530        tbl.cell(1, 0).unwrap().shading("D6E4F0");
531        tbl.cell(1, 1).unwrap().set_text("{{status}}");
532    }
533
534    let mut replacements = HashMap::new();
535    replacements.insert("{{customer}}", "Tensorbee Inc.");
536    replacements.insert("{{date}}", "February 22, 2026");
537    replacements.insert("{{ref_number}}", "TB-2026-001");
538    replacements.insert("{{project}}", "Infrastructure Upgrade");
539    replacements.insert("{{status}}", "In Progress");
540    let n = doc.replace_all(&replacements);
541    doc.add_paragraph(&format!("({n} placeholders replaced above)"));
542
543    doc.add_paragraph("");
544
545    doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
546    doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
547    let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
548    doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
549
550    doc.add_paragraph("");
551
552    doc.add_paragraph("7.3 Content Insertion").style("Heading2");
553    doc.add_paragraph("Section A: First content.");
554    doc.add_paragraph("Section C: Third content.");
555    if let Some(idx) = doc.find_content_index("Section C") {
556        doc.insert_paragraph(
557            idx,
558            "Section B: Inserted between A and C via find_content_index().",
559        );
560    }
561
562    // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
563    doc.add_paragraph("").section_break(SectionBreak::NextPage);
564    doc.add_paragraph("8. Section Breaks & Orientation")
565        .style("Heading1");
566    doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
567
568    {
569        let mut tbl = doc
570            .add_table(3, 7)
571            .borders(BorderStyle::Single, 4, "2E75B6");
572        let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
573        for (c, h) in headers.iter().enumerate() {
574            tbl.cell(0, c).unwrap().set_text(h);
575            tbl.cell(0, c).unwrap().shading("2E75B6");
576        }
577        let data = [
578            [
579                "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
580            ],
581            [
582                "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
583            ],
584        ];
585        for (r, row) in data.iter().enumerate() {
586            for (c, v) in row.iter().enumerate() {
587                tbl.cell(r + 1, c).unwrap().set_text(v);
588            }
589        }
590    }
591
592    doc.add_paragraph("")
593        .section_break(SectionBreak::NextPage)
594        .section_landscape();
595
596    // ── SECTION 9: CUSTOM STYLES ──
597    doc.add_paragraph("9. Custom Styles").style("Heading1");
598    doc.add_style(
599        StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
600            .based_on("Normal")
601            .paragraph_properties(rdocx_oxml::properties::CT_PPr {
602                shading: Some(rdocx_oxml::properties::CT_Shd {
603                    val: "clear".to_string(),
604                    color: None,
605                    fill: Some("FFF2CC".to_string()),
606                }),
607                ..Default::default()
608            })
609            .run_properties(rdocx_oxml::properties::CT_RPr {
610                bold: Some(true),
611                color: Some("C45911".to_string()),
612                ..Default::default()
613            }),
614    );
615    doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
616        .style("CustomHighlight");
617
618    doc.add_paragraph("");
619
620    // ── SECTION 10: DOCUMENT INTELLIGENCE ──
621    doc.add_paragraph("10. Document Intelligence API")
622        .style("Heading1");
623
624    let wc = doc.word_count();
625    let hc = doc.headings().len();
626    let ic = doc.images().len();
627    let lc = doc.links().len();
628    doc.add_paragraph(&format!("Word count: {wc}"));
629    doc.add_paragraph(&format!("Heading count: {hc}"));
630    doc.add_paragraph(&format!("Image count: {ic}"));
631    doc.add_paragraph(&format!("Link count: {lc}"));
632
633    let outline = doc.document_outline();
634    doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
635
636    let issues = doc.audit_accessibility();
637    doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
638    for issue in &issues {
639        doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
640    }
641
642    // ── SECTION 11: DOCUMENT MERGING ──
643    doc.add_paragraph("");
644    doc.add_paragraph("11. Document Merging").style("Heading1");
645    {
646        let mut other = Document::new();
647        other.add_paragraph("This paragraph was merged from another document using append().");
648        doc.append(&other);
649    }
650
651    doc.add_paragraph("");
652
653    // ── SUMMARY ──
654    doc.add_paragraph("Summary of Demonstrated Features")
655        .style("Heading1");
656    let features = [
657        "Page setup: size, margins, header/footer distance, gutter",
658        "Document metadata: title, author, subject, keywords",
659        "Headers and footers: text, images, different first page",
660        "Background images (full-page behind text)",
661        "Table of Contents generation with bookmarks",
662        "Text formatting: bold, italic, underline styles, strike, color, size, font",
663        "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
664        "Paragraph formatting: alignment, borders, shading, spacing, indentation",
665        "Pagination controls: keep-with-next, keep-together, widow control",
666        "Bullet and numbered lists with nesting",
667        "Tab stops with dot/underscore/hyphen leaders",
668        "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
669        "Row properties: header rows, exact height, cant-split",
670        "Cell properties: width, no-wrap, vertical alignment",
671        "Inline images and header images",
672        "Placeholder replacement (single and batch)",
673        "Regex-based find and replace",
674        "Content insertion at specific positions",
675        "Section breaks with mixed portrait/landscape",
676        "Custom paragraph and character styles",
677        "Document intelligence: word count, headings, outline, images, links",
678        "Accessibility audit",
679        "Document merging (append)",
680        "Export: DOCX, PDF, HTML, Markdown",
681    ];
682    for f in &features {
683        doc.add_bullet_list_item(f, 0);
684    }
685    doc.add_paragraph("");
686    doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
687        .alignment(Alignment::Center)
688        .shading("E2EFDA")
689        .border_all(BorderStyle::Single, 2, "00AA00");
690
691    doc
692}
693
694// =============================================================================
695// 2. PROPOSAL DOCUMENT — Deep navy + gold scheme
696// =============================================================================
697fn generate_proposal(_samples_dir: &Path) -> Document {
698    let mut doc = Document::new();
699
700    // Colors: Navy #1B2A4A, Gold #C5922E, Light #F4F1EB
701    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
702    doc.set_margins(
703        Length::inches(1.0),
704        Length::inches(1.0),
705        Length::inches(1.0),
706        Length::inches(1.0),
707    );
708    doc.set_title("AI Platform Modernization Proposal");
709    doc.set_author("Walter White");
710    doc.set_subject("Technology Proposal");
711    doc.set_keywords("proposal, AI, modernization, Tensorbee");
712
713    // Banner header
714    let logo_img = create_logo_png(220, 48);
715    let banner = build_header_banner_xml(
716        "rId1",
717        &BannerOpts {
718            bg_color: "1B2A4A",
719            banner_width: 7772400,
720            banner_height: 914400,
721            logo_width: 2011680,
722            logo_height: 438912,
723            logo_x_offset: 295125,
724            logo_y_offset: 237744,
725        },
726    );
727    doc.set_raw_header_with_images(
728        banner,
729        &[("rId1", &logo_img, "logo.png")],
730        rdocx_oxml::header_footer::HdrFtrType::Default,
731    );
732    doc.set_different_first_page(true);
733    let cover_banner = build_header_banner_xml(
734        "rId1",
735        &BannerOpts {
736            bg_color: "C5922E",
737            banner_width: 7772400,
738            banner_height: 914400,
739            logo_width: 2011680,
740            logo_height: 438912,
741            logo_x_offset: 295125,
742            logo_y_offset: 237744,
743        },
744    );
745    doc.set_raw_header_with_images(
746        cover_banner,
747        &[("rId1", &logo_img, "logo.png")],
748        rdocx_oxml::header_footer::HdrFtrType::First,
749    );
750    doc.set_margins(
751        Length::twips(2292),
752        Length::twips(1440),
753        Length::twips(1440),
754        Length::twips(1440),
755    );
756    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
757    doc.set_footer("Tensorbee — Confidential");
758
759    // Custom styles
760    doc.add_style(
761        StyleBuilder::paragraph("ProposalTitle", "Proposal Title")
762            .based_on("Normal")
763            .run_properties(rdocx_oxml::properties::CT_RPr {
764                bold: Some(true),
765                font_ascii: Some("Georgia".to_string()),
766                font_hansi: Some("Georgia".to_string()),
767                sz: Some(rdocx_oxml::HalfPoint(56)), // half-points → 28pt
768                color: Some("1B2A4A".to_string()),
769                ..Default::default()
770            }),
771    );
772
773    // ── Cover Page ──
774    for _ in 0..4 {
775        doc.add_paragraph("");
776    }
777    doc.add_paragraph("AI Platform Modernization")
778        .style("ProposalTitle")
779        .alignment(Alignment::Center);
780    doc.add_paragraph("")
781        .alignment(Alignment::Center)
782        .border_bottom(BorderStyle::Single, 8, "C5922E");
783    {
784        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
785        p.add_run("Prepared for Global Financial Corp.")
786            .size(14.0)
787            .color("666666")
788            .italic(true);
789    }
790    {
791        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
792        p.add_run("Prepared by: Walter White, CEO — Tensorbee")
793            .size(12.0)
794            .color("888888");
795    }
796    {
797        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
798        p.add_run("February 22, 2026").size(12.0).color("888888");
799    }
800
801    // ── TOC ──
802    doc.add_paragraph("").page_break_before(true);
803    doc.add_paragraph("Table of Contents").style("Heading1");
804    // We'll add a manual-style TOC since the insert_toc goes at a specific position
805    doc.insert_toc(doc.content_count(), 2);
806
807    // ── Executive Summary ──
808    doc.add_paragraph("").page_break_before(true);
809    doc.add_paragraph("Executive Summary").style("Heading1");
810    doc.add_paragraph(
811        "Tensorbee proposes a comprehensive modernization of Global Financial Corp's AI platform, \
812         transitioning from legacy batch-processing systems to a real-time inference architecture. \
813         This transformation will reduce model deployment time from weeks to hours, improve \
814         prediction accuracy by an estimated 23%, and deliver $4.2M in annual operational savings.",
815    )
816    .first_line_indent(Length::inches(0.3));
817    doc.add_paragraph(
818        "The project spans three phases over 18 months, with a total investment of $2.8M. \
819         Tensorbee brings deep expertise in ML infrastructure, having successfully completed \
820         similar transformations for three Fortune 500 financial institutions.",
821    )
822    .first_line_indent(Length::inches(0.3));
823
824    // Key metrics table
825    doc.add_paragraph("");
826    {
827        let mut tbl = doc
828            .add_table(5, 2)
829            .borders(BorderStyle::Single, 4, "1B2A4A")
830            .width_pct(80.0);
831        tbl.cell(0, 0).unwrap().set_text("Key Metric");
832        tbl.cell(0, 0).unwrap().shading("1B2A4A");
833        tbl.cell(0, 1).unwrap().set_text("Value");
834        tbl.cell(0, 1).unwrap().shading("1B2A4A");
835        let rows = [
836            ("Total Investment", "$2.8M"),
837            ("Annual Savings", "$4.2M"),
838            ("ROI (Year 1)", "150%"),
839            ("Timeline", "18 months"),
840        ];
841        for (i, (k, v)) in rows.iter().enumerate() {
842            tbl.cell(i + 1, 0).unwrap().set_text(k);
843            if i % 2 == 0 {
844                tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
845            }
846            tbl.cell(i + 1, 1).unwrap().set_text(v);
847            if i % 2 == 0 {
848                tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
849            }
850        }
851    }
852
853    // ── Problem Statement ──
854    doc.add_paragraph("").page_break_before(true);
855    doc.add_paragraph("Problem Statement").style("Heading1");
856    doc.add_paragraph(
857        "Global Financial Corp's current AI infrastructure faces three critical challenges:",
858    );
859    doc.add_numbered_list_item("Deployment Latency — New models take 3-4 weeks to deploy to production, compared to the industry benchmark of 2-3 days.", 0);
860    doc.add_numbered_list_item("Scalability Constraints — The batch processing architecture cannot handle real-time inference demands during peak trading hours.", 0);
861    doc.add_numbered_list_item("Technical Debt — Legacy Python 2.7 codebases and manual deployment scripts create reliability risks and slow iteration speed.", 0);
862
863    // ── Proposed Solution ──
864    doc.add_paragraph("").page_break_before(true);
865    doc.add_paragraph("Proposed Solution").style("Heading1");
866
867    doc.add_paragraph("Phase 1: Foundation (Months 1-6)")
868        .style("Heading2");
869    doc.add_bullet_list_item("Deploy Kubernetes-based ML serving infrastructure", 0);
870    doc.add_bullet_list_item("Implement CI/CD pipeline for model deployment", 0);
871    doc.add_bullet_list_item("Migrate top 5 production models to new platform", 0);
872
873    doc.add_paragraph("Phase 2: Expansion (Months 7-12)")
874        .style("Heading2");
875    doc.add_bullet_list_item(
876        "Real-time feature engineering pipeline (Apache Kafka + Flink)",
877        0,
878    );
879    doc.add_bullet_list_item("A/B testing framework for model variants", 0);
880    doc.add_bullet_list_item("Automated model monitoring and drift detection", 0);
881
882    doc.add_paragraph("Phase 3: Optimization (Months 13-18)")
883        .style("Heading2");
884    doc.add_bullet_list_item("GPU inference optimization (TensorRT/ONNX Runtime)", 0);
885    doc.add_bullet_list_item("Multi-region deployment for latency reduction", 0);
886    doc.add_bullet_list_item(
887        "Self-service model deployment portal for data science teams",
888        0,
889    );
890
891    // ── Budget ──
892    doc.add_paragraph("Budget Breakdown").style("Heading1");
893    {
894        let mut tbl = doc
895            .add_table(6, 3)
896            .borders(BorderStyle::Single, 4, "1B2A4A")
897            .width_pct(100.0);
898        tbl.cell(0, 0).unwrap().set_text("Category");
899        tbl.cell(0, 0).unwrap().shading("1B2A4A");
900        tbl.cell(0, 1).unwrap().set_text("Cost");
901        tbl.cell(0, 1).unwrap().shading("1B2A4A");
902        tbl.cell(0, 2).unwrap().set_text("Notes");
903        tbl.cell(0, 2).unwrap().shading("1B2A4A");
904        let items = [
905            (
906                "Infrastructure",
907                "$450,000",
908                "Cloud compute + GPU instances",
909            ),
910            ("Engineering Services", "$1,600,000", "12 FTE x 18 months"),
911            (
912                "Software Licenses",
913                "$350,000",
914                "Kafka, monitoring, CI/CD tools",
915            ),
916            (
917                "Training & Enablement",
918                "$200,000",
919                "Team training, documentation",
920            ),
921            ("Contingency (10%)", "$200,000", "Risk buffer"),
922        ];
923        for (i, (cat, cost, note)) in items.iter().enumerate() {
924            tbl.cell(i + 1, 0).unwrap().set_text(cat);
925            tbl.cell(i + 1, 1).unwrap().set_text(cost);
926            tbl.cell(i + 1, 2).unwrap().set_text(note);
927            if i % 2 == 0 {
928                tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
929                tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
930                tbl.cell(i + 1, 2).unwrap().shading("F4F1EB");
931            }
932        }
933    }
934
935    // ── Closing ──
936    doc.add_paragraph("");
937    doc.add_paragraph("Next Steps").style("Heading1");
938    doc.add_numbered_list_item(
939        "Schedule technical deep-dive with GFC engineering team (Week 1)",
940        0,
941    );
942    doc.add_numbered_list_item("Finalize scope and sign SOW (Week 2-3)", 0);
943    doc.add_numbered_list_item("Kick off Phase 1 with joint planning session (Week 4)", 0);
944
945    doc.add_paragraph("");
946    {
947        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
948        p.add_run("Walter White")
949            .bold(true)
950            .size(14.0)
951            .color("1B2A4A");
952    }
953    {
954        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
955        p.add_run("CEO, Tensorbee | walter@tensorbee.com")
956            .size(10.0)
957            .color("888888");
958    }
959
960    doc
961}
962
963// =============================================================================
964// 3. QUOTE / BILL OF MATERIALS — Teal + orange scheme
965// =============================================================================
966fn generate_quote(_samples_dir: &Path) -> Document {
967    let mut doc = Document::new();
968
969    // Colors: Teal #008B8B, Dark #1A3C3C, Orange #E07020, Light #F0F8F8
970    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
971    doc.set_margins(
972        Length::inches(0.75),
973        Length::inches(0.75),
974        Length::inches(0.75),
975        Length::inches(0.75),
976    );
977    doc.set_title("Quotation QT-2026-0147");
978    doc.set_author("Walter White");
979    doc.set_keywords("quote, BOM, Tensorbee");
980
981    doc.set_header("Tensorbee — Quotation");
982    doc.set_footer("QT-2026-0147 | Page");
983
984    // ── Header Block ──
985    {
986        let mut p = doc.add_paragraph("").alignment(Alignment::Left);
987        p.add_run("TENSORBEE")
988            .bold(true)
989            .size(28.0)
990            .color("008B8B")
991            .font("Helvetica");
992    }
993    doc.add_paragraph("123 Innovation Drive, Suite 400")
994        .alignment(Alignment::Left);
995    doc.add_paragraph("San Francisco, CA 94105 | +1 (415) 555-0199")
996        .alignment(Alignment::Left);
997    doc.add_paragraph("")
998        .border_bottom(BorderStyle::Single, 8, "008B8B");
999
1000    // Quote meta
1001    doc.add_paragraph("");
1002    {
1003        let mut tbl = doc.add_table(4, 4).width_pct(100.0);
1004        tbl.cell(0, 0).unwrap().set_text("QUOTATION");
1005        tbl.cell(0, 0).unwrap().shading("008B8B").grid_span(2);
1006        tbl.cell(0, 2).unwrap().set_text("DATE");
1007        tbl.cell(0, 2).unwrap().shading("008B8B");
1008        tbl.cell(0, 3).unwrap().set_text("VALID UNTIL");
1009        tbl.cell(0, 3).unwrap().shading("008B8B");
1010        tbl.cell(1, 0).unwrap().set_text("Quote #:");
1011        tbl.cell(1, 1).unwrap().set_text("QT-2026-0147");
1012        tbl.cell(1, 2).unwrap().set_text("Feb 22, 2026");
1013        tbl.cell(1, 3).unwrap().set_text("Mar 22, 2026");
1014        tbl.cell(2, 0).unwrap().set_text("Prepared by:");
1015        tbl.cell(2, 1).unwrap().set_text("Walter White");
1016        tbl.cell(2, 2).unwrap().set_text("Payment:");
1017        tbl.cell(2, 3).unwrap().set_text("Net 30");
1018        tbl.cell(3, 0).unwrap().set_text("Customer:");
1019        tbl.cell(3, 1).unwrap().set_text("Meridian Dynamics LLC");
1020        tbl.cell(3, 2).unwrap().set_text("Currency:");
1021        tbl.cell(3, 3).unwrap().set_text("USD");
1022    }
1023
1024    doc.add_paragraph("");
1025
1026    // ── Bill of Materials ──
1027    {
1028        let mut p = doc.add_paragraph("");
1029        p.add_run("Bill of Materials")
1030            .bold(true)
1031            .size(16.0)
1032            .color("1A3C3C");
1033    }
1034    doc.add_paragraph("");
1035
1036    {
1037        let mut tbl = doc
1038            .add_table(10, 6)
1039            .borders(BorderStyle::Single, 2, "008B8B")
1040            .width_pct(100.0)
1041            .layout_fixed();
1042        // Headers
1043        let hdrs = [
1044            "#",
1045            "Part Number",
1046            "Description",
1047            "Qty",
1048            "Unit Price",
1049            "Total",
1050        ];
1051        for (c, h) in hdrs.iter().enumerate() {
1052            tbl.cell(0, c).unwrap().set_text(h);
1053            tbl.cell(0, c).unwrap().shading("008B8B");
1054        }
1055
1056        let items: Vec<(&str, &str, &str, &str, &str)> = vec![
1057            (
1058                "TB-GPU-A100",
1059                "NVIDIA A100 80GB GPU",
1060                "4",
1061                "$12,500.00",
1062                "$50,000.00",
1063            ),
1064            (
1065                "TB-SRV-R750",
1066                "Dell R750xa Server Chassis",
1067                "2",
1068                "$8,200.00",
1069                "$16,400.00",
1070            ),
1071            (
1072                "TB-RAM-256G",
1073                "256GB DDR5 ECC Memory Module",
1074                "8",
1075                "$890.00",
1076                "$7,120.00",
1077            ),
1078            (
1079                "TB-SSD-3840",
1080                "3.84TB NVMe U.2 SSD",
1081                "8",
1082                "$1,150.00",
1083                "$9,200.00",
1084            ),
1085            (
1086                "TB-NET-CX7",
1087                "ConnectX-7 200GbE NIC",
1088                "4",
1089                "$1,800.00",
1090                "$7,200.00",
1091            ),
1092            (
1093                "TB-SW-48P",
1094                "48-Port 100GbE Switch",
1095                "1",
1096                "$22,000.00",
1097                "$22,000.00",
1098            ),
1099            (
1100                "TB-CAB-RACK",
1101                "42U Server Rack + PDU",
1102                "1",
1103                "$4,500.00",
1104                "$4,500.00",
1105            ),
1106            (
1107                "TB-SVC-INST",
1108                "Installation & Configuration",
1109                "1",
1110                "$8,500.00",
1111                "$8,500.00",
1112            ),
1113            (
1114                "TB-SVC-SUPP",
1115                "3-Year Premium Support",
1116                "1",
1117                "$15,000.00",
1118                "$15,000.00",
1119            ),
1120        ];
1121
1122        for (i, (pn, desc, qty, unit, total)) in items.iter().enumerate() {
1123            let row = i + 1;
1124            tbl.cell(row, 0).unwrap().set_text(&format!("{}", i + 1));
1125            tbl.cell(row, 1).unwrap().set_text(pn);
1126            tbl.cell(row, 2).unwrap().set_text(desc);
1127            tbl.cell(row, 3).unwrap().set_text(qty);
1128            tbl.cell(row, 4).unwrap().set_text(unit);
1129            tbl.cell(row, 5).unwrap().set_text(total);
1130            if i % 2 == 0 {
1131                for c in 0..6 {
1132                    tbl.cell(row, c).unwrap().shading("F0F8F8");
1133                }
1134            }
1135        }
1136    }
1137
1138    doc.add_paragraph("");
1139
1140    // ── Totals ──
1141    {
1142        let mut tbl = doc
1143            .add_table(4, 2)
1144            .borders(BorderStyle::Single, 2, "008B8B")
1145            .width(Length::inches(3.5))
1146            .alignment(Alignment::Right);
1147        tbl.cell(0, 0).unwrap().set_text("Subtotal");
1148        tbl.cell(0, 1).unwrap().set_text("$139,920.00");
1149        tbl.cell(1, 0).unwrap().set_text("Shipping & Handling");
1150        tbl.cell(1, 1).unwrap().set_text("$2,500.00");
1151        tbl.cell(2, 0).unwrap().set_text("Tax (8.625%)");
1152        tbl.cell(2, 1).unwrap().set_text("$12,068.10");
1153        tbl.cell(3, 0).unwrap().set_text("TOTAL");
1154        tbl.cell(3, 0).unwrap().shading("E07020");
1155        tbl.cell(3, 1).unwrap().set_text("$154,488.10");
1156        tbl.cell(3, 1).unwrap().shading("E07020");
1157    }
1158
1159    doc.add_paragraph("");
1160
1161    // ── Terms & Conditions ──
1162    {
1163        let mut p = doc.add_paragraph("");
1164        p.add_run("Terms & Conditions")
1165            .bold(true)
1166            .size(14.0)
1167            .color("1A3C3C");
1168    }
1169    doc.add_numbered_list_item(
1170        "This quotation is valid for 30 calendar days from the date of issue.",
1171        0,
1172    );
1173    doc.add_numbered_list_item(
1174        "All prices are in USD and exclusive of applicable taxes unless stated.",
1175        0,
1176    );
1177    doc.add_numbered_list_item("Standard lead time is 4-6 weeks from PO receipt.", 0);
1178    doc.add_numbered_list_item(
1179        "Payment terms: Net 30 from invoice date. 2% discount for payment within 10 days.",
1180        0,
1181    );
1182    doc.add_numbered_list_item(
1183        "Warranty: 3-year manufacturer warranty on all hardware components.",
1184        0,
1185    );
1186    doc.add_numbered_list_item(
1187        "Returns subject to 15% restocking fee if initiated after 14 days.",
1188        0,
1189    );
1190
1191    doc.add_paragraph("");
1192    doc.add_paragraph("")
1193        .border_bottom(BorderStyle::Single, 4, "008B8B");
1194    doc.add_paragraph("");
1195    {
1196        let mut p = doc.add_paragraph("");
1197        p.add_run("Accepted by: ").bold(true);
1198        p.add_run("___________________________ Date: ___________");
1199    }
1200    {
1201        let mut p = doc.add_paragraph("");
1202        p.add_run("Print Name: ").bold(true);
1203        p.add_run("___________________________ Title: ___________");
1204    }
1205
1206    doc
1207}
1208
1209// =============================================================================
1210// 4. INVOICE — Crimson + charcoal scheme
1211// =============================================================================
1212fn generate_invoice(_samples_dir: &Path) -> Document {
1213    let mut doc = Document::new();
1214
1215    // Colors: Crimson #B22222, Charcoal #333333, Light #FAF0F0
1216    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1217    doc.set_margins(
1218        Length::inches(0.75),
1219        Length::inches(0.75),
1220        Length::inches(0.75),
1221        Length::inches(0.75),
1222    );
1223    doc.set_title("Invoice INV-2026-0392");
1224    doc.set_author("Walter White");
1225    doc.set_keywords("invoice, Tensorbee");
1226
1227    doc.set_footer("Tensorbee — Thank you for your business!");
1228
1229    // ── Company & Invoice Header ──
1230    {
1231        let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1232        // Company name left, INVOICE right
1233        {
1234            let mut cell = tbl.cell(0, 0).unwrap();
1235            let mut p = cell.add_paragraph("");
1236            p.add_run("TENSORBEE")
1237                .bold(true)
1238                .size(32.0)
1239                .color("B22222")
1240                .font("Helvetica");
1241        }
1242        {
1243            let mut cell = tbl.cell(0, 1).unwrap();
1244            let mut p = cell.add_paragraph("");
1245            p.add_run("INVOICE").bold(true).size(32.0).color("333333");
1246        }
1247        tbl.cell(1, 0)
1248            .unwrap()
1249            .set_text("123 Innovation Drive, Suite 400");
1250        tbl.cell(1, 1).unwrap().set_text("Invoice #: INV-2026-0392");
1251        tbl.cell(2, 0).unwrap().set_text("San Francisco, CA 94105");
1252        tbl.cell(2, 1).unwrap().set_text("Date: February 22, 2026");
1253        tbl.cell(3, 0).unwrap().set_text("walter@tensorbee.com");
1254        tbl.cell(3, 1).unwrap().set_text("Due Date: March 24, 2026");
1255    }
1256
1257    doc.add_paragraph("")
1258        .border_bottom(BorderStyle::Thick, 8, "B22222");
1259    doc.add_paragraph("");
1260
1261    // ── Bill To / Ship To ──
1262    {
1263        let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1264        tbl.cell(0, 0).unwrap().set_text("BILL TO");
1265        tbl.cell(0, 0).unwrap().shading("B22222");
1266        tbl.cell(0, 1).unwrap().set_text("SHIP TO");
1267        tbl.cell(0, 1).unwrap().shading("B22222");
1268        tbl.cell(1, 0).unwrap().set_text("Meridian Dynamics LLC");
1269        tbl.cell(1, 1).unwrap().set_text("Meridian Dynamics LLC");
1270        tbl.cell(2, 0).unwrap().set_text("456 Enterprise Blvd");
1271        tbl.cell(2, 1).unwrap().set_text("Attn: Server Room B");
1272        tbl.cell(3, 0).unwrap().set_text("Austin, TX 78701");
1273        tbl.cell(3, 1)
1274            .unwrap()
1275            .set_text("456 Enterprise Blvd, Austin, TX 78701");
1276    }
1277
1278    doc.add_paragraph("");
1279
1280    // ── Line Items ──
1281    {
1282        let mut tbl = doc
1283            .add_table(8, 5)
1284            .borders(BorderStyle::Single, 2, "B22222")
1285            .width_pct(100.0);
1286        let hdrs = ["Description", "Qty", "Unit Price", "Tax", "Amount"];
1287        for (c, h) in hdrs.iter().enumerate() {
1288            tbl.cell(0, c).unwrap().set_text(h);
1289            tbl.cell(0, c).unwrap().shading("B22222");
1290        }
1291        let items = [
1292            (
1293                "ML Infrastructure Setup — Phase 1",
1294                "1",
1295                "$45,000.00",
1296                "$3,881.25",
1297                "$48,881.25",
1298            ),
1299            (
1300                "Data Pipeline Development (160 hrs)",
1301                "160",
1302                "$225.00",
1303                "$3,105.00",
1304                "$39,105.00",
1305            ),
1306            (
1307                "GPU Cluster Configuration",
1308                "1",
1309                "$12,000.00",
1310                "$1,035.00",
1311                "$13,035.00",
1312            ),
1313            (
1314                "API Gateway Implementation",
1315                "1",
1316                "$18,500.00",
1317                "$1,595.63",
1318                "$20,095.63",
1319            ),
1320            (
1321                "Load Testing & QA (80 hrs)",
1322                "80",
1323                "$195.00",
1324                "$1,345.50",
1325                "$16,945.50",
1326            ),
1327            (
1328                "Documentation & Training",
1329                "1",
1330                "$8,000.00",
1331                "$690.00",
1332                "$8,690.00",
1333            ),
1334            (
1335                "Project Management (3 months)",
1336                "3",
1337                "$6,500.00",
1338                "$1,679.63",
1339                "$21,179.63",
1340            ),
1341        ];
1342        for (i, (desc, qty, unit, tax, amt)) in items.iter().enumerate() {
1343            let r = i + 1;
1344            tbl.cell(r, 0).unwrap().set_text(desc);
1345            tbl.cell(r, 1).unwrap().set_text(qty);
1346            tbl.cell(r, 2).unwrap().set_text(unit);
1347            tbl.cell(r, 3).unwrap().set_text(tax);
1348            tbl.cell(r, 4).unwrap().set_text(amt);
1349            if i % 2 == 0 {
1350                for c in 0..5 {
1351                    tbl.cell(r, c).unwrap().shading("FAF0F0");
1352                }
1353            }
1354        }
1355    }
1356
1357    doc.add_paragraph("");
1358
1359    // ── Totals ──
1360    {
1361        let mut tbl = doc
1362            .add_table(4, 2)
1363            .borders(BorderStyle::Single, 2, "B22222")
1364            .width(Length::inches(3.0))
1365            .alignment(Alignment::Right);
1366        tbl.cell(0, 0).unwrap().set_text("Subtotal");
1367        tbl.cell(0, 1).unwrap().set_text("$154,600.00");
1368        tbl.cell(1, 0).unwrap().set_text("Tax (8.625%)");
1369        tbl.cell(1, 1).unwrap().set_text("$13,334.25");
1370        tbl.cell(2, 0).unwrap().set_text("Discount (5%)");
1371        tbl.cell(2, 1).unwrap().set_text("-$7,730.00");
1372        tbl.cell(3, 0).unwrap().set_text("AMOUNT DUE");
1373        tbl.cell(3, 0).unwrap().shading("B22222");
1374        tbl.cell(3, 1).unwrap().set_text("$160,204.25");
1375        tbl.cell(3, 1).unwrap().shading("B22222");
1376    }
1377
1378    doc.add_paragraph("");
1379    doc.add_paragraph("");
1380
1381    // ── Payment Details ──
1382    {
1383        let mut p = doc.add_paragraph("");
1384        p.add_run("Payment Details")
1385            .bold(true)
1386            .size(14.0)
1387            .color("333333");
1388    }
1389    doc.add_paragraph("Bank: Silicon Valley Bank")
1390        .indent_left(Length::inches(0.3));
1391    doc.add_paragraph("Account: Tensorbee Inc. — 0847-2953-1120")
1392        .indent_left(Length::inches(0.3));
1393    doc.add_paragraph("Routing: 121140399")
1394        .indent_left(Length::inches(0.3));
1395    doc.add_paragraph("Swift: SVBKUS6S")
1396        .indent_left(Length::inches(0.3));
1397
1398    doc.add_paragraph("");
1399
1400    doc.add_paragraph("Please include invoice number INV-2026-0392 in the payment reference.")
1401        .shading("FAF0F0")
1402        .border_all(BorderStyle::Single, 2, "B22222");
1403
1404    doc
1405}
1406
1407// =============================================================================
1408// 5. REPORT — Forest green + earth tones, with images & hierarchical sections
1409// =============================================================================
1410fn generate_report(_samples_dir: &Path) -> Document {
1411    let mut doc = Document::new();
1412
1413    // Colors: Forest #2D5016, Sage #6B8E23, Earth #8B7355, Cream #FFFAF0
1414    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1415    doc.set_margins(
1416        Length::inches(1.0),
1417        Length::inches(1.0),
1418        Length::inches(1.0),
1419        Length::inches(1.0),
1420    );
1421    doc.set_title("Q4 2025 Environmental Impact Report");
1422    doc.set_author("Walter White");
1423    doc.set_subject("Quarterly Environmental Report");
1424    doc.set_keywords("environment, sustainability, report, Tensorbee");
1425
1426    doc.set_different_first_page(true);
1427    doc.set_first_page_header("");
1428    doc.set_header("Tensorbee — Q4 2025 Environmental Impact Report");
1429    doc.set_footer("CONFIDENTIAL — Page");
1430
1431    // ── Cover ──
1432    let cover_bg = create_sample_png(612, 792, [20, 50, 15]);
1433    doc.add_background_image(&cover_bg, "report_cover.png");
1434
1435    for _ in 0..5 {
1436        doc.add_paragraph("");
1437    }
1438    {
1439        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1440        p.add_run("Q4 2025")
1441            .bold(true)
1442            .size(48.0)
1443            .color("FFFFFF")
1444            .font("Georgia");
1445    }
1446    {
1447        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1448        p.add_run("Environmental Impact Report")
1449            .size(24.0)
1450            .color("90EE90")
1451            .font("Georgia")
1452            .italic(true);
1453    }
1454    doc.add_paragraph("");
1455    {
1456        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1457        p.add_run("Tensorbee — Sustainability Division")
1458            .size(14.0)
1459            .color("C0C0C0");
1460    }
1461    {
1462        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1463        p.add_run("Prepared by Walter White, Chief Sustainability Officer")
1464            .size(11.0)
1465            .color("AAAAAA");
1466    }
1467
1468    // ── TOC ──
1469    doc.add_paragraph("").page_break_before(true);
1470    doc.insert_toc(doc.content_count(), 3);
1471
1472    // ── Section 1: Executive Overview ──
1473    doc.add_paragraph("").page_break_before(true);
1474    doc.add_paragraph("Executive Overview").style("Heading1");
1475    doc.add_paragraph(
1476        "This report presents Tensorbee's environmental performance for Q4 2025. Our \
1477         sustainability initiatives have yielded a 34% reduction in carbon emissions compared \
1478         to Q4 2024, exceeding our target of 25%. Key achievements include the transition to \
1479         100% renewable energy in our primary data centers and the launch of our carbon offset \
1480         marketplace.",
1481    )
1482    .first_line_indent(Length::inches(0.3));
1483
1484    // Image: performance chart
1485    let chart_img = create_chart_png(400, 200);
1486    doc.add_paragraph("");
1487    doc.add_picture(
1488        &chart_img,
1489        "performance_chart.png",
1490        Length::inches(5.0),
1491        Length::inches(2.5),
1492    )
1493    .alignment(Alignment::Center);
1494    {
1495        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1496        p.add_run("Figure 1: Quarterly Carbon Emissions (tonnes CO2e)")
1497            .italic(true)
1498            .size(9.0)
1499            .color("666666");
1500    }
1501
1502    // ── Section 2: Energy Consumption ──
1503    doc.add_paragraph("").page_break_before(true);
1504    doc.add_paragraph("Energy Consumption").style("Heading1");
1505
1506    doc.add_paragraph("Data Center Operations")
1507        .style("Heading2");
1508    doc.add_paragraph(
1509        "Our three primary data centers consumed a combined 4.2 GWh in Q4 2025, \
1510         a 12% reduction from Q3 2025 driven by improved cooling efficiency and \
1511         server consolidation.",
1512    );
1513
1514    // Data table
1515    {
1516        let mut tbl = doc
1517            .add_table(5, 4)
1518            .borders(BorderStyle::Single, 4, "2D5016")
1519            .width_pct(100.0);
1520        let hdrs = ["Data Center", "Capacity (MW)", "Usage (GWh)", "PUE"];
1521        for (c, h) in hdrs.iter().enumerate() {
1522            tbl.cell(0, c).unwrap().set_text(h);
1523            tbl.cell(0, c).unwrap().shading("2D5016");
1524        }
1525        let rows = [
1526            ("San Francisco (Primary)", "3.2", "1.8", "1.12"),
1527            ("Dublin (EU)", "2.1", "1.2", "1.18"),
1528            ("Singapore (APAC)", "1.8", "1.2", "1.24"),
1529            ("TOTAL", "7.1", "4.2", "1.17 avg"),
1530        ];
1531        for (i, (dc, cap, use_, pue)) in rows.iter().enumerate() {
1532            tbl.cell(i + 1, 0).unwrap().set_text(dc);
1533            tbl.cell(i + 1, 1).unwrap().set_text(cap);
1534            tbl.cell(i + 1, 2).unwrap().set_text(use_);
1535            tbl.cell(i + 1, 3).unwrap().set_text(pue);
1536            if i == 3 {
1537                for c in 0..4 {
1538                    tbl.cell(i + 1, c).unwrap().shading("E2EFDA");
1539                }
1540            }
1541        }
1542    }
1543
1544    doc.add_paragraph("");
1545
1546    doc.add_paragraph("Renewable Energy Mix").style("Heading2");
1547    doc.add_paragraph("Breakdown of energy sources across all facilities:");
1548
1549    doc.add_bullet_list_item("Solar PV: 42% (1.76 GWh)", 0);
1550    doc.add_bullet_list_item("Wind Power Purchase Agreements: 38% (1.60 GWh)", 0);
1551    doc.add_bullet_list_item("Hydroelectric: 12% (0.50 GWh)", 0);
1552    doc.add_bullet_list_item("Grid (non-renewable): 8% (0.34 GWh)", 0);
1553
1554    // ── Section 3: Water & Waste ──
1555    doc.add_paragraph("Water & Waste Management")
1556        .style("Heading1");
1557
1558    doc.add_paragraph("Water Usage").style("Heading2");
1559    doc.add_paragraph(
1560        "Total water consumption was 12.4 million gallons, a 15% reduction from Q3. \
1561         Our closed-loop cooling systems now recycle 78% of water used in cooling operations.",
1562    );
1563
1564    doc.add_paragraph("Waste Reduction").style("Heading2");
1565    doc.add_paragraph("E-waste management results for Q4:")
1566        .keep_with_next(true);
1567    {
1568        let mut tbl = doc
1569            .add_table(4, 3)
1570            .borders(BorderStyle::Single, 2, "6B8E23")
1571            .width_pct(80.0);
1572        tbl.cell(0, 0).unwrap().set_text("Category");
1573        tbl.cell(0, 0).unwrap().shading("6B8E23");
1574        tbl.cell(0, 1).unwrap().set_text("Weight (kg)");
1575        tbl.cell(0, 1).unwrap().shading("6B8E23");
1576        tbl.cell(0, 2).unwrap().set_text("Recycled %");
1577        tbl.cell(0, 2).unwrap().shading("6B8E23");
1578        let rows = [
1579            ("Server Hardware", "2,450", "94%"),
1580            ("Networking Equipment", "820", "91%"),
1581            ("Storage Media", "340", "99%"),
1582        ];
1583        for (i, (cat, wt, pct)) in rows.iter().enumerate() {
1584            tbl.cell(i + 1, 0).unwrap().set_text(cat);
1585            tbl.cell(i + 1, 1).unwrap().set_text(wt);
1586            tbl.cell(i + 1, 2).unwrap().set_text(pct);
1587        }
1588    }
1589
1590    // ── Section 4: Initiatives ──
1591    doc.add_paragraph("").page_break_before(true);
1592    doc.add_paragraph("Q1 2026 Initiatives").style("Heading1");
1593
1594    doc.add_paragraph("Planned Programs").style("Heading2");
1595    doc.add_numbered_list_item(
1596        "Deploy on-site battery storage at SF data center (2 MWh capacity)",
1597        0,
1598    );
1599    doc.add_numbered_list_item("Pilot immersion cooling in Dublin facility", 0);
1600    doc.add_numbered_list_item("Launch employee commute carbon offset program", 0);
1601    doc.add_numbered_list_item("Achieve ISO 14001 certification for Singapore facility", 0);
1602
1603    doc.add_paragraph("Investment Targets").style("Heading2");
1604    doc.add_paragraph("Sustainability CapEx allocation for FY2026:")
1605        .keep_with_next(true);
1606    {
1607        let mut tbl = doc
1608            .add_table(5, 2)
1609            .borders(BorderStyle::Single, 4, "2D5016");
1610        tbl.cell(0, 0).unwrap().set_text("Initiative");
1611        tbl.cell(0, 0).unwrap().shading("2D5016");
1612        tbl.cell(0, 1).unwrap().set_text("Budget");
1613        tbl.cell(0, 1).unwrap().shading("2D5016");
1614        let rows = [
1615            ("Battery Storage", "$1.2M"),
1616            ("Immersion Cooling Pilot", "$800K"),
1617            ("Solar Panel Expansion", "$2.1M"),
1618            ("Carbon Credits", "$500K"),
1619        ];
1620        for (i, (init, budget)) in rows.iter().enumerate() {
1621            tbl.cell(i + 1, 0).unwrap().set_text(init);
1622            tbl.cell(i + 1, 1).unwrap().set_text(budget);
1623            if i % 2 == 0 {
1624                tbl.cell(i + 1, 0).unwrap().shading("FFFAF0");
1625                tbl.cell(i + 1, 1).unwrap().shading("FFFAF0");
1626            }
1627        }
1628    }
1629
1630    // Image: sustainability roadmap
1631    let roadmap_img = create_sample_png(500, 100, [40, 80, 30]);
1632    doc.add_paragraph("");
1633    doc.add_picture(
1634        &roadmap_img,
1635        "roadmap.png",
1636        Length::inches(6.0),
1637        Length::inches(1.2),
1638    )
1639    .alignment(Alignment::Center);
1640    {
1641        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1642        p.add_run("Figure 2: 2026 Sustainability Roadmap")
1643            .italic(true)
1644            .size(9.0)
1645            .color("666666");
1646    }
1647
1648    doc.add_paragraph("");
1649    doc.add_paragraph(
1650        "For questions about this report, contact Walter White at sustainability@tensorbee.com.",
1651    )
1652    .shading("FFFAF0")
1653    .border_all(BorderStyle::Single, 2, "2D5016");
1654
1655    doc
1656}

Set the first-page footer text.

Examples found in repository?
examples/generate_all_samples.rs (line 118)
97fn generate_feature_showcase(_samples_dir: &Path) -> Document {
98    let mut doc = Document::new();
99
100    // ── Page Setup & Metadata ──
101    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
102    doc.set_margins(
103        Length::inches(1.0),
104        Length::inches(1.0),
105        Length::inches(1.0),
106        Length::inches(1.0),
107    );
108    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
109    doc.set_gutter(Length::twips(0));
110    doc.set_title("rdocx Feature Showcase");
111    doc.set_author("rdocx Sample Generator");
112    doc.set_subject("Comprehensive feature demonstration");
113    doc.set_keywords("rdocx, docx, rust, sample, showcase");
114
115    // Headers & Footers with different first page
116    doc.set_different_first_page(true);
117    doc.set_first_page_header("rdocx");
118    doc.set_first_page_footer("Feature Showcase — Cover Page");
119    doc.set_header("rdocx Feature Showcase");
120    doc.set_footer("Generated by rdocx");
121
122    // ── COVER PAGE ──
123    let bg = create_sample_png(612, 792, [20, 45, 90]);
124    doc.add_background_image(&bg, "cover_bg.png");
125
126    for _ in 0..3 {
127        doc.add_paragraph("");
128    }
129    {
130        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
131        p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
132    }
133    {
134        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
135        p.add_run("Complete Feature Showcase")
136            .size(28.0)
137            .color("FFFFFF")
138            .italic(true);
139    }
140    doc.add_paragraph("");
141    {
142        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
143        p.add_run("Every feature of the rdocx Rust library")
144            .size(13.0)
145            .color("B0C4DE");
146    }
147    {
148        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
149        p.add_run("demonstrated in a single document.")
150            .size(13.0)
151            .color("B0C4DE");
152    }
153
154    // ── TABLE OF CONTENTS ──
155    doc.add_paragraph("").page_break_before(true);
156    doc.insert_toc(doc.content_count(), 3);
157
158    // ── SECTION 1: TEXT FORMATTING ──
159    doc.add_paragraph("").page_break_before(true);
160    doc.add_paragraph("1. Text Formatting").style("Heading1");
161
162    // Paragraph Alignment
163    doc.add_paragraph("1.1 Paragraph Alignment")
164        .style("Heading2");
165    doc.add_paragraph("Left-aligned paragraph (default).")
166        .alignment(Alignment::Left);
167    doc.add_paragraph("Center-aligned paragraph.")
168        .alignment(Alignment::Center);
169    doc.add_paragraph("Right-aligned paragraph.")
170        .alignment(Alignment::Right);
171    doc.add_paragraph(
172        "Justified paragraph — this text is long enough to demonstrate how justified alignment \
173         distributes extra space across word gaps so lines fill the full width of the text area.",
174    )
175    .alignment(Alignment::Justify);
176
177    doc.add_paragraph("");
178
179    // Run Formatting
180    doc.add_paragraph("1.2 Run Formatting").style("Heading2");
181    {
182        let mut p = doc.add_paragraph("");
183        p.add_run("Bold").bold(true);
184        p.add_run(" | ");
185        p.add_run("Italic").italic(true);
186        p.add_run(" | ");
187        p.add_run("Bold Italic").bold(true).italic(true);
188        p.add_run(" | ");
189        p.add_run("Underline").underline(true);
190        p.add_run(" | ");
191        p.add_run("Strikethrough").strike(true);
192        p.add_run(" | ");
193        p.add_run("Double Strike").double_strike(true);
194    }
195    {
196        let mut p = doc.add_paragraph("");
197        p.add_run("Red").color("FF0000");
198        p.add_run(" | ");
199        p.add_run("Blue").color("0000FF");
200        p.add_run(" | ");
201        p.add_run("Green").color("00AA00");
202        p.add_run(" | ");
203        p.add_run("Highlighted").highlight("FFFF00");
204    }
205    {
206        let mut p = doc.add_paragraph("");
207        p.add_run("8pt").size(8.0);
208        p.add_run(" | ");
209        p.add_run("11pt").size(11.0);
210        p.add_run(" | ");
211        p.add_run("16pt").size(16.0);
212        p.add_run(" | ");
213        p.add_run("24pt").size(24.0);
214    }
215    {
216        let mut p = doc.add_paragraph("");
217        p.add_run("Arial").font("Arial");
218        p.add_run(" | ");
219        p.add_run("Times New Roman").font("Times New Roman");
220        p.add_run(" | ");
221        p.add_run("Courier New").font("Courier New");
222    }
223    {
224        let mut p = doc.add_paragraph("");
225        p.add_run("H");
226        p.add_run("2").subscript();
227        p.add_run("O (subscript) | E=mc");
228        p.add_run("2").superscript();
229        p.add_run(" (superscript)");
230    }
231    {
232        let mut p = doc.add_paragraph("");
233        p.add_run("ALL CAPS").all_caps(true);
234        p.add_run(" | ");
235        p.add_run("Small Caps").small_caps(true);
236        p.add_run(" | ");
237        p.add_run("Expanded").character_spacing(Length::twips(40));
238        p.add_run(" | ");
239        p.add_run("Hidden text (not visible)").hidden(true);
240    }
241
242    doc.add_paragraph("");
243
244    // Underline Styles
245    doc.add_paragraph("1.3 Underline Styles").style("Heading2");
246    {
247        let mut p = doc.add_paragraph("");
248        p.add_run("Single ")
249            .underline_style(rdocx::UnderlineStyle::Single);
250        p.add_run("Double ")
251            .underline_style(rdocx::UnderlineStyle::Double);
252        p.add_run("Thick ")
253            .underline_style(rdocx::UnderlineStyle::Thick);
254        p.add_run("Dotted ")
255            .underline_style(rdocx::UnderlineStyle::Dotted);
256        p.add_run("Dash ")
257            .underline_style(rdocx::UnderlineStyle::Dash);
258        p.add_run("Wave ")
259            .underline_style(rdocx::UnderlineStyle::Wave);
260    }
261
262    // ── SECTION 2: PARAGRAPH FORMATTING ──
263    doc.add_paragraph("").page_break_before(true);
264    doc.add_paragraph("2. Paragraph Formatting")
265        .style("Heading1");
266
267    doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
268    doc.add_paragraph("Paragraph with light green shading")
269        .shading("E2EFDA");
270    doc.add_paragraph("Paragraph with bottom border")
271        .border_bottom(BorderStyle::Single, 6, "2E75B6");
272    doc.add_paragraph("Paragraph with all borders (red)")
273        .border_all(BorderStyle::Single, 4, "FF0000");
274
275    doc.add_paragraph("2.2 Indentation").style("Heading2");
276    doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
277        .indent_left(Length::inches(1.0))
278        .hanging_indent(Length::inches(0.5));
279    doc.add_paragraph("0.5-inch first-line indent on this paragraph")
280        .first_line_indent(Length::inches(0.5));
281    doc.add_paragraph("Both left and right indent (0.75 inches each)")
282        .indent_left(Length::inches(0.75))
283        .indent_right(Length::inches(0.75));
284
285    doc.add_paragraph("2.3 Spacing & Line Height")
286        .style("Heading2");
287    doc.add_paragraph("Extra space before (24pt) and after (12pt)")
288        .space_before(Length::pt(24.0))
289        .space_after(Length::pt(12.0));
290    doc.add_paragraph(
291        "Double line spacing paragraph. This text should have extra vertical space between \
292         lines to demonstrate line_spacing_multiple(2.0).",
293    )
294    .line_spacing_multiple(2.0);
295    doc.add_paragraph("Exact 20pt line spacing (fixed height).")
296        .line_spacing(20.0);
297
298    doc.add_paragraph("2.4 Pagination Controls")
299        .style("Heading2");
300    doc.add_paragraph("keep_with_next — this paragraph stays with the next")
301        .keep_with_next(true);
302    doc.add_paragraph("(This paragraph was kept with the one above.)");
303    doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
304        .keep_together(true);
305    doc.add_paragraph("widow_control — prevents widow/orphan lines")
306        .widow_control(true);
307
308    // ── SECTION 3: LISTS ──
309    doc.add_paragraph("").page_break_before(true);
310    doc.add_paragraph("3. Lists").style("Heading1");
311
312    doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
313    doc.add_bullet_list_item("First item", 0);
314    doc.add_bullet_list_item("Second item", 0);
315    doc.add_bullet_list_item("Nested level 1", 1);
316    doc.add_bullet_list_item("Nested level 2", 2);
317    doc.add_bullet_list_item("Back to level 1", 1);
318    doc.add_bullet_list_item("Third item", 0);
319
320    doc.add_paragraph("");
321
322    doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
323    doc.add_numbered_list_item("First numbered item", 0);
324    doc.add_numbered_list_item("Second numbered item", 0);
325    doc.add_numbered_list_item("Sub-item A", 1);
326    doc.add_numbered_list_item("Sub-item B", 1);
327    doc.add_numbered_list_item("Third numbered item", 0);
328
329    // ── SECTION 4: TAB STOPS ──
330    doc.add_paragraph("");
331    doc.add_paragraph("4. Tab Stops").style("Heading1");
332
333    doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
334    doc.add_paragraph("Left\tCenter\tRight\tDecimal")
335        .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
336        .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
337        .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
338        .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
339
340    doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
341    doc.add_paragraph("Item\t\tPrice")
342        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
343        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
344        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
345    doc.add_paragraph("Widget\t\t$19.99")
346        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
347        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
348        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
349    doc.add_paragraph("Gadget\t\t$249.50")
350        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
351        .add_tab_stop_with_leader(
352            TabAlignment::Right,
353            Length::inches(4.0),
354            TabLeader::Underscore,
355        )
356        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
357
358    // ── SECTION 5: TABLES ──
359    doc.add_paragraph("").page_break_before(true);
360    doc.add_paragraph("5. Tables").style("Heading1");
361
362    doc.add_paragraph("5.1 Basic Table").style("Heading2");
363    {
364        let mut tbl = doc
365            .add_table(4, 3)
366            .borders(BorderStyle::Single, 4, "000000");
367        for col in 0..3 {
368            tbl.cell(0, col).unwrap().shading("2E75B6");
369        }
370        tbl.cell(0, 0).unwrap().set_text("Name");
371        tbl.cell(0, 1).unwrap().set_text("Role");
372        tbl.cell(0, 2).unwrap().set_text("Location");
373        tbl.cell(1, 0).unwrap().set_text("Walter White");
374        tbl.cell(1, 1).unwrap().set_text("CEO");
375        tbl.cell(1, 2).unwrap().set_text("Albuquerque");
376        tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
377        tbl.cell(2, 1).unwrap().set_text("CTO");
378        tbl.cell(2, 2).unwrap().set_text("Remote");
379        tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
380        tbl.cell(3, 1).unwrap().set_text("Security");
381        tbl.cell(3, 2).unwrap().set_text("Washington");
382    }
383
384    doc.add_paragraph("");
385
386    doc.add_paragraph("5.2 Column Span & Cell Shading")
387        .style("Heading2");
388    {
389        let mut tbl = doc
390            .add_table(3, 4)
391            .borders(BorderStyle::Single, 4, "000000")
392            .width_pct(100.0);
393        tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
394        tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
395        tbl.cell(1, 0).unwrap().set_text("Region");
396        tbl.cell(1, 0).unwrap().shading("D6E4F0");
397        tbl.cell(1, 1).unwrap().set_text("Q1");
398        tbl.cell(1, 1).unwrap().shading("D6E4F0");
399        tbl.cell(1, 2).unwrap().set_text("Q2");
400        tbl.cell(1, 2).unwrap().shading("D6E4F0");
401        tbl.cell(1, 3).unwrap().set_text("Total");
402        tbl.cell(1, 3).unwrap().shading("D6E4F0");
403        tbl.cell(2, 0).unwrap().set_text("Americas");
404        tbl.cell(2, 1).unwrap().set_text("$2.4M");
405        tbl.cell(2, 2).unwrap().set_text("$2.7M");
406        tbl.cell(2, 3).unwrap().set_text("$5.1M");
407    }
408
409    doc.add_paragraph("");
410
411    doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
412    {
413        let mut tbl = doc
414            .add_table(4, 3)
415            .borders(BorderStyle::Single, 4, "000000");
416        tbl.cell(0, 0).unwrap().set_text("Category");
417        tbl.cell(0, 0).unwrap().shading("E2EFDA");
418        tbl.cell(0, 1).unwrap().set_text("Item");
419        tbl.cell(0, 1).unwrap().shading("E2EFDA");
420        tbl.cell(0, 2).unwrap().set_text("Price");
421        tbl.cell(0, 2).unwrap().shading("E2EFDA");
422        tbl.cell(1, 0).unwrap().set_text("Hardware");
423        tbl.cell(1, 0).unwrap().v_merge_restart();
424        tbl.cell(1, 1).unwrap().set_text("Laptop");
425        tbl.cell(1, 2).unwrap().set_text("$1,200");
426        tbl.cell(2, 0).unwrap().v_merge_continue();
427        tbl.cell(2, 1).unwrap().set_text("Monitor");
428        tbl.cell(2, 2).unwrap().set_text("$450");
429        tbl.cell(3, 0).unwrap().set_text("Software");
430        tbl.cell(3, 1).unwrap().set_text("IDE License");
431        tbl.cell(3, 2).unwrap().set_text("$200/yr");
432    }
433
434    doc.add_paragraph("");
435
436    doc.add_paragraph("5.4 Nested Table").style("Heading2");
437    {
438        let mut tbl = doc
439            .add_table(2, 2)
440            .borders(BorderStyle::Single, 6, "2E75B6");
441        tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
442        tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
443        tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
444        {
445            let mut cell = tbl.cell(1, 1).unwrap();
446            cell.set_text("Nested table below:");
447            let mut nested = cell
448                .add_table(2, 2)
449                .borders(BorderStyle::Single, 2, "FF6600");
450            nested.cell(0, 0).unwrap().set_text("A");
451            nested.cell(0, 1).unwrap().set_text("B");
452            nested.cell(1, 0).unwrap().set_text("C");
453            nested.cell(1, 1).unwrap().set_text("D");
454        }
455    }
456
457    doc.add_paragraph("");
458
459    doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
460        .style("Heading2");
461    {
462        let mut tbl = doc
463            .add_table(2, 3)
464            .borders(BorderStyle::Single, 4, "666666");
465        tbl.row(0).unwrap().height(Length::pt(50.0)).header();
466        tbl.cell(0, 0).unwrap().set_text("Top");
467        tbl.cell(0, 0)
468            .unwrap()
469            .vertical_alignment(VerticalAlignment::Top)
470            .shading("FFF2CC");
471        tbl.cell(0, 1).unwrap().set_text("Center");
472        tbl.cell(0, 1)
473            .unwrap()
474            .vertical_alignment(VerticalAlignment::Center)
475            .shading("D9E2F3");
476        tbl.cell(0, 2).unwrap().set_text("Bottom");
477        tbl.cell(0, 2)
478            .unwrap()
479            .vertical_alignment(VerticalAlignment::Bottom)
480            .shading("E2EFDA");
481        tbl.row(1).unwrap().cant_split();
482        tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
483        tbl.cell(1, 0).unwrap().no_wrap();
484        tbl.cell(1, 1).unwrap().set_text("Fixed width");
485        tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
486        tbl.cell(1, 2).unwrap().set_text("Normal");
487    }
488
489    // ── SECTION 6: IMAGES ──
490    doc.add_paragraph("").page_break_before(true);
491    doc.add_paragraph("6. Images").style("Heading1");
492
493    doc.add_paragraph("6.1 Inline Image").style("Heading2");
494    doc.add_paragraph("A blue gradient image below:");
495    let img = create_sample_png(200, 50, [0, 80, 200]);
496    doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
497
498    doc.add_paragraph("");
499    doc.add_paragraph("6.2 Header Image").style("Heading2");
500    let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
501    doc.set_header_image(
502        &hdr_img,
503        "header_logo.png",
504        Length::inches(2.0),
505        Length::inches(0.2),
506    );
507    doc.add_paragraph("The header now contains an inline image (check the top of this page).");
508
509    doc.add_paragraph("");
510    doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
511
512    // ── SECTION 7: CONTENT MANIPULATION ──
513    doc.add_paragraph("").page_break_before(true);
514    doc.add_paragraph("7. Content Manipulation")
515        .style("Heading1");
516
517    doc.add_paragraph("7.1 Placeholder Replacement")
518        .style("Heading2");
519    doc.add_paragraph("Customer: {{customer}}");
520    doc.add_paragraph("Date: {{date}}");
521    doc.add_paragraph("Reference: {{ref_number}}");
522    {
523        let mut tbl = doc
524            .add_table(2, 2)
525            .borders(BorderStyle::Single, 4, "000000");
526        tbl.cell(0, 0).unwrap().set_text("Project");
527        tbl.cell(0, 0).unwrap().shading("D6E4F0");
528        tbl.cell(0, 1).unwrap().set_text("{{project}}");
529        tbl.cell(1, 0).unwrap().set_text("Status");
530        tbl.cell(1, 0).unwrap().shading("D6E4F0");
531        tbl.cell(1, 1).unwrap().set_text("{{status}}");
532    }
533
534    let mut replacements = HashMap::new();
535    replacements.insert("{{customer}}", "Tensorbee Inc.");
536    replacements.insert("{{date}}", "February 22, 2026");
537    replacements.insert("{{ref_number}}", "TB-2026-001");
538    replacements.insert("{{project}}", "Infrastructure Upgrade");
539    replacements.insert("{{status}}", "In Progress");
540    let n = doc.replace_all(&replacements);
541    doc.add_paragraph(&format!("({n} placeholders replaced above)"));
542
543    doc.add_paragraph("");
544
545    doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
546    doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
547    let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
548    doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
549
550    doc.add_paragraph("");
551
552    doc.add_paragraph("7.3 Content Insertion").style("Heading2");
553    doc.add_paragraph("Section A: First content.");
554    doc.add_paragraph("Section C: Third content.");
555    if let Some(idx) = doc.find_content_index("Section C") {
556        doc.insert_paragraph(
557            idx,
558            "Section B: Inserted between A and C via find_content_index().",
559        );
560    }
561
562    // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
563    doc.add_paragraph("").section_break(SectionBreak::NextPage);
564    doc.add_paragraph("8. Section Breaks & Orientation")
565        .style("Heading1");
566    doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
567
568    {
569        let mut tbl = doc
570            .add_table(3, 7)
571            .borders(BorderStyle::Single, 4, "2E75B6");
572        let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
573        for (c, h) in headers.iter().enumerate() {
574            tbl.cell(0, c).unwrap().set_text(h);
575            tbl.cell(0, c).unwrap().shading("2E75B6");
576        }
577        let data = [
578            [
579                "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
580            ],
581            [
582                "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
583            ],
584        ];
585        for (r, row) in data.iter().enumerate() {
586            for (c, v) in row.iter().enumerate() {
587                tbl.cell(r + 1, c).unwrap().set_text(v);
588            }
589        }
590    }
591
592    doc.add_paragraph("")
593        .section_break(SectionBreak::NextPage)
594        .section_landscape();
595
596    // ── SECTION 9: CUSTOM STYLES ──
597    doc.add_paragraph("9. Custom Styles").style("Heading1");
598    doc.add_style(
599        StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
600            .based_on("Normal")
601            .paragraph_properties(rdocx_oxml::properties::CT_PPr {
602                shading: Some(rdocx_oxml::properties::CT_Shd {
603                    val: "clear".to_string(),
604                    color: None,
605                    fill: Some("FFF2CC".to_string()),
606                }),
607                ..Default::default()
608            })
609            .run_properties(rdocx_oxml::properties::CT_RPr {
610                bold: Some(true),
611                color: Some("C45911".to_string()),
612                ..Default::default()
613            }),
614    );
615    doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
616        .style("CustomHighlight");
617
618    doc.add_paragraph("");
619
620    // ── SECTION 10: DOCUMENT INTELLIGENCE ──
621    doc.add_paragraph("10. Document Intelligence API")
622        .style("Heading1");
623
624    let wc = doc.word_count();
625    let hc = doc.headings().len();
626    let ic = doc.images().len();
627    let lc = doc.links().len();
628    doc.add_paragraph(&format!("Word count: {wc}"));
629    doc.add_paragraph(&format!("Heading count: {hc}"));
630    doc.add_paragraph(&format!("Image count: {ic}"));
631    doc.add_paragraph(&format!("Link count: {lc}"));
632
633    let outline = doc.document_outline();
634    doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
635
636    let issues = doc.audit_accessibility();
637    doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
638    for issue in &issues {
639        doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
640    }
641
642    // ── SECTION 11: DOCUMENT MERGING ──
643    doc.add_paragraph("");
644    doc.add_paragraph("11. Document Merging").style("Heading1");
645    {
646        let mut other = Document::new();
647        other.add_paragraph("This paragraph was merged from another document using append().");
648        doc.append(&other);
649    }
650
651    doc.add_paragraph("");
652
653    // ── SUMMARY ──
654    doc.add_paragraph("Summary of Demonstrated Features")
655        .style("Heading1");
656    let features = [
657        "Page setup: size, margins, header/footer distance, gutter",
658        "Document metadata: title, author, subject, keywords",
659        "Headers and footers: text, images, different first page",
660        "Background images (full-page behind text)",
661        "Table of Contents generation with bookmarks",
662        "Text formatting: bold, italic, underline styles, strike, color, size, font",
663        "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
664        "Paragraph formatting: alignment, borders, shading, spacing, indentation",
665        "Pagination controls: keep-with-next, keep-together, widow control",
666        "Bullet and numbered lists with nesting",
667        "Tab stops with dot/underscore/hyphen leaders",
668        "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
669        "Row properties: header rows, exact height, cant-split",
670        "Cell properties: width, no-wrap, vertical alignment",
671        "Inline images and header images",
672        "Placeholder replacement (single and batch)",
673        "Regex-based find and replace",
674        "Content insertion at specific positions",
675        "Section breaks with mixed portrait/landscape",
676        "Custom paragraph and character styles",
677        "Document intelligence: word count, headings, outline, images, links",
678        "Accessibility audit",
679        "Document merging (append)",
680        "Export: DOCX, PDF, HTML, Markdown",
681    ];
682    for f in &features {
683        doc.add_bullet_list_item(f, 0);
684    }
685    doc.add_paragraph("");
686    doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
687        .alignment(Alignment::Center)
688        .shading("E2EFDA")
689        .border_all(BorderStyle::Single, 2, "00AA00");
690
691    doc
692}
Source

pub fn header_text(&self) -> Option<String>

Get the default header text, if set.

Source

pub fn footer_text(&self) -> Option<String>

Get the default footer text, if set.

Source

pub fn set_header_image( &mut self, image_data: &[u8], image_filename: &str, width: Length, height: Length, )

Set the default header to an inline image.

Creates a header part with an image paragraph. The image is embedded in the header part’s relationships.

Examples found in repository?
examples/generate_all_samples.rs (lines 501-506)
97fn generate_feature_showcase(_samples_dir: &Path) -> Document {
98    let mut doc = Document::new();
99
100    // ── Page Setup & Metadata ──
101    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
102    doc.set_margins(
103        Length::inches(1.0),
104        Length::inches(1.0),
105        Length::inches(1.0),
106        Length::inches(1.0),
107    );
108    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
109    doc.set_gutter(Length::twips(0));
110    doc.set_title("rdocx Feature Showcase");
111    doc.set_author("rdocx Sample Generator");
112    doc.set_subject("Comprehensive feature demonstration");
113    doc.set_keywords("rdocx, docx, rust, sample, showcase");
114
115    // Headers & Footers with different first page
116    doc.set_different_first_page(true);
117    doc.set_first_page_header("rdocx");
118    doc.set_first_page_footer("Feature Showcase — Cover Page");
119    doc.set_header("rdocx Feature Showcase");
120    doc.set_footer("Generated by rdocx");
121
122    // ── COVER PAGE ──
123    let bg = create_sample_png(612, 792, [20, 45, 90]);
124    doc.add_background_image(&bg, "cover_bg.png");
125
126    for _ in 0..3 {
127        doc.add_paragraph("");
128    }
129    {
130        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
131        p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
132    }
133    {
134        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
135        p.add_run("Complete Feature Showcase")
136            .size(28.0)
137            .color("FFFFFF")
138            .italic(true);
139    }
140    doc.add_paragraph("");
141    {
142        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
143        p.add_run("Every feature of the rdocx Rust library")
144            .size(13.0)
145            .color("B0C4DE");
146    }
147    {
148        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
149        p.add_run("demonstrated in a single document.")
150            .size(13.0)
151            .color("B0C4DE");
152    }
153
154    // ── TABLE OF CONTENTS ──
155    doc.add_paragraph("").page_break_before(true);
156    doc.insert_toc(doc.content_count(), 3);
157
158    // ── SECTION 1: TEXT FORMATTING ──
159    doc.add_paragraph("").page_break_before(true);
160    doc.add_paragraph("1. Text Formatting").style("Heading1");
161
162    // Paragraph Alignment
163    doc.add_paragraph("1.1 Paragraph Alignment")
164        .style("Heading2");
165    doc.add_paragraph("Left-aligned paragraph (default).")
166        .alignment(Alignment::Left);
167    doc.add_paragraph("Center-aligned paragraph.")
168        .alignment(Alignment::Center);
169    doc.add_paragraph("Right-aligned paragraph.")
170        .alignment(Alignment::Right);
171    doc.add_paragraph(
172        "Justified paragraph — this text is long enough to demonstrate how justified alignment \
173         distributes extra space across word gaps so lines fill the full width of the text area.",
174    )
175    .alignment(Alignment::Justify);
176
177    doc.add_paragraph("");
178
179    // Run Formatting
180    doc.add_paragraph("1.2 Run Formatting").style("Heading2");
181    {
182        let mut p = doc.add_paragraph("");
183        p.add_run("Bold").bold(true);
184        p.add_run(" | ");
185        p.add_run("Italic").italic(true);
186        p.add_run(" | ");
187        p.add_run("Bold Italic").bold(true).italic(true);
188        p.add_run(" | ");
189        p.add_run("Underline").underline(true);
190        p.add_run(" | ");
191        p.add_run("Strikethrough").strike(true);
192        p.add_run(" | ");
193        p.add_run("Double Strike").double_strike(true);
194    }
195    {
196        let mut p = doc.add_paragraph("");
197        p.add_run("Red").color("FF0000");
198        p.add_run(" | ");
199        p.add_run("Blue").color("0000FF");
200        p.add_run(" | ");
201        p.add_run("Green").color("00AA00");
202        p.add_run(" | ");
203        p.add_run("Highlighted").highlight("FFFF00");
204    }
205    {
206        let mut p = doc.add_paragraph("");
207        p.add_run("8pt").size(8.0);
208        p.add_run(" | ");
209        p.add_run("11pt").size(11.0);
210        p.add_run(" | ");
211        p.add_run("16pt").size(16.0);
212        p.add_run(" | ");
213        p.add_run("24pt").size(24.0);
214    }
215    {
216        let mut p = doc.add_paragraph("");
217        p.add_run("Arial").font("Arial");
218        p.add_run(" | ");
219        p.add_run("Times New Roman").font("Times New Roman");
220        p.add_run(" | ");
221        p.add_run("Courier New").font("Courier New");
222    }
223    {
224        let mut p = doc.add_paragraph("");
225        p.add_run("H");
226        p.add_run("2").subscript();
227        p.add_run("O (subscript) | E=mc");
228        p.add_run("2").superscript();
229        p.add_run(" (superscript)");
230    }
231    {
232        let mut p = doc.add_paragraph("");
233        p.add_run("ALL CAPS").all_caps(true);
234        p.add_run(" | ");
235        p.add_run("Small Caps").small_caps(true);
236        p.add_run(" | ");
237        p.add_run("Expanded").character_spacing(Length::twips(40));
238        p.add_run(" | ");
239        p.add_run("Hidden text (not visible)").hidden(true);
240    }
241
242    doc.add_paragraph("");
243
244    // Underline Styles
245    doc.add_paragraph("1.3 Underline Styles").style("Heading2");
246    {
247        let mut p = doc.add_paragraph("");
248        p.add_run("Single ")
249            .underline_style(rdocx::UnderlineStyle::Single);
250        p.add_run("Double ")
251            .underline_style(rdocx::UnderlineStyle::Double);
252        p.add_run("Thick ")
253            .underline_style(rdocx::UnderlineStyle::Thick);
254        p.add_run("Dotted ")
255            .underline_style(rdocx::UnderlineStyle::Dotted);
256        p.add_run("Dash ")
257            .underline_style(rdocx::UnderlineStyle::Dash);
258        p.add_run("Wave ")
259            .underline_style(rdocx::UnderlineStyle::Wave);
260    }
261
262    // ── SECTION 2: PARAGRAPH FORMATTING ──
263    doc.add_paragraph("").page_break_before(true);
264    doc.add_paragraph("2. Paragraph Formatting")
265        .style("Heading1");
266
267    doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
268    doc.add_paragraph("Paragraph with light green shading")
269        .shading("E2EFDA");
270    doc.add_paragraph("Paragraph with bottom border")
271        .border_bottom(BorderStyle::Single, 6, "2E75B6");
272    doc.add_paragraph("Paragraph with all borders (red)")
273        .border_all(BorderStyle::Single, 4, "FF0000");
274
275    doc.add_paragraph("2.2 Indentation").style("Heading2");
276    doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
277        .indent_left(Length::inches(1.0))
278        .hanging_indent(Length::inches(0.5));
279    doc.add_paragraph("0.5-inch first-line indent on this paragraph")
280        .first_line_indent(Length::inches(0.5));
281    doc.add_paragraph("Both left and right indent (0.75 inches each)")
282        .indent_left(Length::inches(0.75))
283        .indent_right(Length::inches(0.75));
284
285    doc.add_paragraph("2.3 Spacing & Line Height")
286        .style("Heading2");
287    doc.add_paragraph("Extra space before (24pt) and after (12pt)")
288        .space_before(Length::pt(24.0))
289        .space_after(Length::pt(12.0));
290    doc.add_paragraph(
291        "Double line spacing paragraph. This text should have extra vertical space between \
292         lines to demonstrate line_spacing_multiple(2.0).",
293    )
294    .line_spacing_multiple(2.0);
295    doc.add_paragraph("Exact 20pt line spacing (fixed height).")
296        .line_spacing(20.0);
297
298    doc.add_paragraph("2.4 Pagination Controls")
299        .style("Heading2");
300    doc.add_paragraph("keep_with_next — this paragraph stays with the next")
301        .keep_with_next(true);
302    doc.add_paragraph("(This paragraph was kept with the one above.)");
303    doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
304        .keep_together(true);
305    doc.add_paragraph("widow_control — prevents widow/orphan lines")
306        .widow_control(true);
307
308    // ── SECTION 3: LISTS ──
309    doc.add_paragraph("").page_break_before(true);
310    doc.add_paragraph("3. Lists").style("Heading1");
311
312    doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
313    doc.add_bullet_list_item("First item", 0);
314    doc.add_bullet_list_item("Second item", 0);
315    doc.add_bullet_list_item("Nested level 1", 1);
316    doc.add_bullet_list_item("Nested level 2", 2);
317    doc.add_bullet_list_item("Back to level 1", 1);
318    doc.add_bullet_list_item("Third item", 0);
319
320    doc.add_paragraph("");
321
322    doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
323    doc.add_numbered_list_item("First numbered item", 0);
324    doc.add_numbered_list_item("Second numbered item", 0);
325    doc.add_numbered_list_item("Sub-item A", 1);
326    doc.add_numbered_list_item("Sub-item B", 1);
327    doc.add_numbered_list_item("Third numbered item", 0);
328
329    // ── SECTION 4: TAB STOPS ──
330    doc.add_paragraph("");
331    doc.add_paragraph("4. Tab Stops").style("Heading1");
332
333    doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
334    doc.add_paragraph("Left\tCenter\tRight\tDecimal")
335        .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
336        .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
337        .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
338        .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
339
340    doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
341    doc.add_paragraph("Item\t\tPrice")
342        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
343        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
344        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
345    doc.add_paragraph("Widget\t\t$19.99")
346        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
347        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
348        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
349    doc.add_paragraph("Gadget\t\t$249.50")
350        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
351        .add_tab_stop_with_leader(
352            TabAlignment::Right,
353            Length::inches(4.0),
354            TabLeader::Underscore,
355        )
356        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
357
358    // ── SECTION 5: TABLES ──
359    doc.add_paragraph("").page_break_before(true);
360    doc.add_paragraph("5. Tables").style("Heading1");
361
362    doc.add_paragraph("5.1 Basic Table").style("Heading2");
363    {
364        let mut tbl = doc
365            .add_table(4, 3)
366            .borders(BorderStyle::Single, 4, "000000");
367        for col in 0..3 {
368            tbl.cell(0, col).unwrap().shading("2E75B6");
369        }
370        tbl.cell(0, 0).unwrap().set_text("Name");
371        tbl.cell(0, 1).unwrap().set_text("Role");
372        tbl.cell(0, 2).unwrap().set_text("Location");
373        tbl.cell(1, 0).unwrap().set_text("Walter White");
374        tbl.cell(1, 1).unwrap().set_text("CEO");
375        tbl.cell(1, 2).unwrap().set_text("Albuquerque");
376        tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
377        tbl.cell(2, 1).unwrap().set_text("CTO");
378        tbl.cell(2, 2).unwrap().set_text("Remote");
379        tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
380        tbl.cell(3, 1).unwrap().set_text("Security");
381        tbl.cell(3, 2).unwrap().set_text("Washington");
382    }
383
384    doc.add_paragraph("");
385
386    doc.add_paragraph("5.2 Column Span & Cell Shading")
387        .style("Heading2");
388    {
389        let mut tbl = doc
390            .add_table(3, 4)
391            .borders(BorderStyle::Single, 4, "000000")
392            .width_pct(100.0);
393        tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
394        tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
395        tbl.cell(1, 0).unwrap().set_text("Region");
396        tbl.cell(1, 0).unwrap().shading("D6E4F0");
397        tbl.cell(1, 1).unwrap().set_text("Q1");
398        tbl.cell(1, 1).unwrap().shading("D6E4F0");
399        tbl.cell(1, 2).unwrap().set_text("Q2");
400        tbl.cell(1, 2).unwrap().shading("D6E4F0");
401        tbl.cell(1, 3).unwrap().set_text("Total");
402        tbl.cell(1, 3).unwrap().shading("D6E4F0");
403        tbl.cell(2, 0).unwrap().set_text("Americas");
404        tbl.cell(2, 1).unwrap().set_text("$2.4M");
405        tbl.cell(2, 2).unwrap().set_text("$2.7M");
406        tbl.cell(2, 3).unwrap().set_text("$5.1M");
407    }
408
409    doc.add_paragraph("");
410
411    doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
412    {
413        let mut tbl = doc
414            .add_table(4, 3)
415            .borders(BorderStyle::Single, 4, "000000");
416        tbl.cell(0, 0).unwrap().set_text("Category");
417        tbl.cell(0, 0).unwrap().shading("E2EFDA");
418        tbl.cell(0, 1).unwrap().set_text("Item");
419        tbl.cell(0, 1).unwrap().shading("E2EFDA");
420        tbl.cell(0, 2).unwrap().set_text("Price");
421        tbl.cell(0, 2).unwrap().shading("E2EFDA");
422        tbl.cell(1, 0).unwrap().set_text("Hardware");
423        tbl.cell(1, 0).unwrap().v_merge_restart();
424        tbl.cell(1, 1).unwrap().set_text("Laptop");
425        tbl.cell(1, 2).unwrap().set_text("$1,200");
426        tbl.cell(2, 0).unwrap().v_merge_continue();
427        tbl.cell(2, 1).unwrap().set_text("Monitor");
428        tbl.cell(2, 2).unwrap().set_text("$450");
429        tbl.cell(3, 0).unwrap().set_text("Software");
430        tbl.cell(3, 1).unwrap().set_text("IDE License");
431        tbl.cell(3, 2).unwrap().set_text("$200/yr");
432    }
433
434    doc.add_paragraph("");
435
436    doc.add_paragraph("5.4 Nested Table").style("Heading2");
437    {
438        let mut tbl = doc
439            .add_table(2, 2)
440            .borders(BorderStyle::Single, 6, "2E75B6");
441        tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
442        tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
443        tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
444        {
445            let mut cell = tbl.cell(1, 1).unwrap();
446            cell.set_text("Nested table below:");
447            let mut nested = cell
448                .add_table(2, 2)
449                .borders(BorderStyle::Single, 2, "FF6600");
450            nested.cell(0, 0).unwrap().set_text("A");
451            nested.cell(0, 1).unwrap().set_text("B");
452            nested.cell(1, 0).unwrap().set_text("C");
453            nested.cell(1, 1).unwrap().set_text("D");
454        }
455    }
456
457    doc.add_paragraph("");
458
459    doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
460        .style("Heading2");
461    {
462        let mut tbl = doc
463            .add_table(2, 3)
464            .borders(BorderStyle::Single, 4, "666666");
465        tbl.row(0).unwrap().height(Length::pt(50.0)).header();
466        tbl.cell(0, 0).unwrap().set_text("Top");
467        tbl.cell(0, 0)
468            .unwrap()
469            .vertical_alignment(VerticalAlignment::Top)
470            .shading("FFF2CC");
471        tbl.cell(0, 1).unwrap().set_text("Center");
472        tbl.cell(0, 1)
473            .unwrap()
474            .vertical_alignment(VerticalAlignment::Center)
475            .shading("D9E2F3");
476        tbl.cell(0, 2).unwrap().set_text("Bottom");
477        tbl.cell(0, 2)
478            .unwrap()
479            .vertical_alignment(VerticalAlignment::Bottom)
480            .shading("E2EFDA");
481        tbl.row(1).unwrap().cant_split();
482        tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
483        tbl.cell(1, 0).unwrap().no_wrap();
484        tbl.cell(1, 1).unwrap().set_text("Fixed width");
485        tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
486        tbl.cell(1, 2).unwrap().set_text("Normal");
487    }
488
489    // ── SECTION 6: IMAGES ──
490    doc.add_paragraph("").page_break_before(true);
491    doc.add_paragraph("6. Images").style("Heading1");
492
493    doc.add_paragraph("6.1 Inline Image").style("Heading2");
494    doc.add_paragraph("A blue gradient image below:");
495    let img = create_sample_png(200, 50, [0, 80, 200]);
496    doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
497
498    doc.add_paragraph("");
499    doc.add_paragraph("6.2 Header Image").style("Heading2");
500    let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
501    doc.set_header_image(
502        &hdr_img,
503        "header_logo.png",
504        Length::inches(2.0),
505        Length::inches(0.2),
506    );
507    doc.add_paragraph("The header now contains an inline image (check the top of this page).");
508
509    doc.add_paragraph("");
510    doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
511
512    // ── SECTION 7: CONTENT MANIPULATION ──
513    doc.add_paragraph("").page_break_before(true);
514    doc.add_paragraph("7. Content Manipulation")
515        .style("Heading1");
516
517    doc.add_paragraph("7.1 Placeholder Replacement")
518        .style("Heading2");
519    doc.add_paragraph("Customer: {{customer}}");
520    doc.add_paragraph("Date: {{date}}");
521    doc.add_paragraph("Reference: {{ref_number}}");
522    {
523        let mut tbl = doc
524            .add_table(2, 2)
525            .borders(BorderStyle::Single, 4, "000000");
526        tbl.cell(0, 0).unwrap().set_text("Project");
527        tbl.cell(0, 0).unwrap().shading("D6E4F0");
528        tbl.cell(0, 1).unwrap().set_text("{{project}}");
529        tbl.cell(1, 0).unwrap().set_text("Status");
530        tbl.cell(1, 0).unwrap().shading("D6E4F0");
531        tbl.cell(1, 1).unwrap().set_text("{{status}}");
532    }
533
534    let mut replacements = HashMap::new();
535    replacements.insert("{{customer}}", "Tensorbee Inc.");
536    replacements.insert("{{date}}", "February 22, 2026");
537    replacements.insert("{{ref_number}}", "TB-2026-001");
538    replacements.insert("{{project}}", "Infrastructure Upgrade");
539    replacements.insert("{{status}}", "In Progress");
540    let n = doc.replace_all(&replacements);
541    doc.add_paragraph(&format!("({n} placeholders replaced above)"));
542
543    doc.add_paragraph("");
544
545    doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
546    doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
547    let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
548    doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
549
550    doc.add_paragraph("");
551
552    doc.add_paragraph("7.3 Content Insertion").style("Heading2");
553    doc.add_paragraph("Section A: First content.");
554    doc.add_paragraph("Section C: Third content.");
555    if let Some(idx) = doc.find_content_index("Section C") {
556        doc.insert_paragraph(
557            idx,
558            "Section B: Inserted between A and C via find_content_index().",
559        );
560    }
561
562    // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
563    doc.add_paragraph("").section_break(SectionBreak::NextPage);
564    doc.add_paragraph("8. Section Breaks & Orientation")
565        .style("Heading1");
566    doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
567
568    {
569        let mut tbl = doc
570            .add_table(3, 7)
571            .borders(BorderStyle::Single, 4, "2E75B6");
572        let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
573        for (c, h) in headers.iter().enumerate() {
574            tbl.cell(0, c).unwrap().set_text(h);
575            tbl.cell(0, c).unwrap().shading("2E75B6");
576        }
577        let data = [
578            [
579                "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
580            ],
581            [
582                "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
583            ],
584        ];
585        for (r, row) in data.iter().enumerate() {
586            for (c, v) in row.iter().enumerate() {
587                tbl.cell(r + 1, c).unwrap().set_text(v);
588            }
589        }
590    }
591
592    doc.add_paragraph("")
593        .section_break(SectionBreak::NextPage)
594        .section_landscape();
595
596    // ── SECTION 9: CUSTOM STYLES ──
597    doc.add_paragraph("9. Custom Styles").style("Heading1");
598    doc.add_style(
599        StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
600            .based_on("Normal")
601            .paragraph_properties(rdocx_oxml::properties::CT_PPr {
602                shading: Some(rdocx_oxml::properties::CT_Shd {
603                    val: "clear".to_string(),
604                    color: None,
605                    fill: Some("FFF2CC".to_string()),
606                }),
607                ..Default::default()
608            })
609            .run_properties(rdocx_oxml::properties::CT_RPr {
610                bold: Some(true),
611                color: Some("C45911".to_string()),
612                ..Default::default()
613            }),
614    );
615    doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
616        .style("CustomHighlight");
617
618    doc.add_paragraph("");
619
620    // ── SECTION 10: DOCUMENT INTELLIGENCE ──
621    doc.add_paragraph("10. Document Intelligence API")
622        .style("Heading1");
623
624    let wc = doc.word_count();
625    let hc = doc.headings().len();
626    let ic = doc.images().len();
627    let lc = doc.links().len();
628    doc.add_paragraph(&format!("Word count: {wc}"));
629    doc.add_paragraph(&format!("Heading count: {hc}"));
630    doc.add_paragraph(&format!("Image count: {ic}"));
631    doc.add_paragraph(&format!("Link count: {lc}"));
632
633    let outline = doc.document_outline();
634    doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
635
636    let issues = doc.audit_accessibility();
637    doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
638    for issue in &issues {
639        doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
640    }
641
642    // ── SECTION 11: DOCUMENT MERGING ──
643    doc.add_paragraph("");
644    doc.add_paragraph("11. Document Merging").style("Heading1");
645    {
646        let mut other = Document::new();
647        other.add_paragraph("This paragraph was merged from another document using append().");
648        doc.append(&other);
649    }
650
651    doc.add_paragraph("");
652
653    // ── SUMMARY ──
654    doc.add_paragraph("Summary of Demonstrated Features")
655        .style("Heading1");
656    let features = [
657        "Page setup: size, margins, header/footer distance, gutter",
658        "Document metadata: title, author, subject, keywords",
659        "Headers and footers: text, images, different first page",
660        "Background images (full-page behind text)",
661        "Table of Contents generation with bookmarks",
662        "Text formatting: bold, italic, underline styles, strike, color, size, font",
663        "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
664        "Paragraph formatting: alignment, borders, shading, spacing, indentation",
665        "Pagination controls: keep-with-next, keep-together, widow control",
666        "Bullet and numbered lists with nesting",
667        "Tab stops with dot/underscore/hyphen leaders",
668        "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
669        "Row properties: header rows, exact height, cant-split",
670        "Cell properties: width, no-wrap, vertical alignment",
671        "Inline images and header images",
672        "Placeholder replacement (single and batch)",
673        "Regex-based find and replace",
674        "Content insertion at specific positions",
675        "Section breaks with mixed portrait/landscape",
676        "Custom paragraph and character styles",
677        "Document intelligence: word count, headings, outline, images, links",
678        "Accessibility audit",
679        "Document merging (append)",
680        "Export: DOCX, PDF, HTML, Markdown",
681    ];
682    for f in &features {
683        doc.add_bullet_list_item(f, 0);
684    }
685    doc.add_paragraph("");
686    doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
687        .alignment(Alignment::Center)
688        .shading("E2EFDA")
689        .border_all(BorderStyle::Single, 2, "00AA00");
690
691    doc
692}

Set the default footer to an inline image.

Source

pub fn set_raw_header_with_images( &mut self, header_xml: Vec<u8>, images: &[(&str, &[u8], &str)], hdr_type: HdrFtrType, )

Set a header from raw XML bytes with associated images.

This is useful for copying complex headers from template documents that contain grouped shapes, VML, or other elements not easily recreated through the high-level API.

Each entry in images is (rel_id, image_data, image_filename):

  • rel_id: the relationship ID referenced in the header XML (e.g. “rId1”)
  • image_data: the raw image bytes
  • image_filename: used to derive the part name and content type (e.g. “image5.png”)
Examples found in repository?
examples/header_banner.rs (lines 59-63)
29fn generate_header_banner_doc(path: &Path) {
30    let mut doc = Document::new();
31
32    // Page setup with extra top margin for the banner
33    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
34    doc.set_margins(
35        Length::twips(2292), // top — extra tall for header banner
36        Length::twips(1440), // right
37        Length::twips(1440), // bottom
38        Length::twips(1440), // left
39    );
40    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
41
42    // Generate a simple logo image (white text on transparent background)
43    let logo_img = create_logo_png(220, 48);
44
45    // ── Dark blue banner ──
46    let banner = build_header_banner_xml(
47        "rId1",
48        &BannerOpts {
49            bg_color: "1A3C6E",
50            banner_width: 7772400, // full page width in EMU (~8.5")
51            banner_height: 969026, // banner height in EMU (~1.06")
52            logo_width: 2011680,   // logo display width (~2.2")
53            logo_height: 438912,   // logo display height (~0.48")
54            logo_x_offset: 295125, // left padding
55            logo_y_offset: 265057, // vertical centering
56        },
57    );
58
59    doc.set_raw_header_with_images(
60        banner.clone(),
61        &[("rId1", &logo_img, "logo.png")],
62        rdocx_oxml::header_footer::HdrFtrType::Default,
63    );
64
65    // Use a different first page header (same banner, different color)
66    doc.set_different_first_page(true);
67    let first_page_banner = build_header_banner_xml(
68        "rId1",
69        &BannerOpts {
70            bg_color: "2E75B6", // lighter blue for cover
71            banner_width: 7772400,
72            banner_height: 969026,
73            logo_width: 2011680,
74            logo_height: 438912,
75            logo_x_offset: 295125,
76            logo_y_offset: 265057,
77        },
78    );
79    doc.set_raw_header_with_images(
80        first_page_banner,
81        &[("rId1", &logo_img, "logo.png")],
82        rdocx_oxml::header_footer::HdrFtrType::First,
83    );
84
85    // Footer
86    doc.set_footer("Confidential — Internal Use Only");
87
88    // ── Page 1: Cover ──
89    doc.add_paragraph("Company Report").style("Heading1");
90
91    doc.add_paragraph(
92        "This document demonstrates a custom header banner built with DrawingML \
93         group shapes. The banner uses a colored rectangle with a logo image overlaid, \
94         positioned at the top of each page.",
95    );
96
97    doc.add_paragraph("");
98
99    doc.add_paragraph("How the Header Banner Works")
100        .style("Heading2");
101
102    doc.add_paragraph(
103        "The header banner is built using set_raw_header_with_images(), which \
104         accepts raw XML and a list of (rel_id, image_data, filename) tuples. \
105         The XML uses a DrawingML group shape (wpg:wgp) containing:",
106    );
107
108    doc.add_bullet_list_item(
109        "A wps:wsp rectangle shape with a solid color fill (the background bar)",
110        0,
111    );
112    doc.add_bullet_list_item(
113        "A pic:pic image element positioned within the group (the logo)",
114        0,
115    );
116    doc.add_bullet_list_item(
117        "The group is wrapped in a wp:anchor element for absolute page positioning",
118        0,
119    );
120
121    doc.add_paragraph("");
122
123    doc.add_paragraph("Customization").style("Heading2");
124
125    doc.add_paragraph(
126        "All dimensions are in EMU (English Metric Units) where 914400 EMU = 1 inch. \
127         You can customize:",
128    );
129
130    doc.add_bullet_list_item("bg_color — any hex color for the rectangle background", 0);
131    doc.add_bullet_list_item("banner_width / banner_height — size of the full banner", 0);
132    doc.add_bullet_list_item("logo_width / logo_height — display size of the logo", 0);
133    doc.add_bullet_list_item(
134        "logo_x_offset / logo_y_offset — logo position within the banner",
135        0,
136    );
137
138    doc.add_paragraph("");
139
140    doc.add_paragraph("Different First Page").style("Heading2");
141
142    doc.add_paragraph(
143        "This page uses a lighter blue banner (first page header). \
144         Subsequent pages use a darker blue banner (default header). \
145         Use set_different_first_page(true) to enable this.",
146    );
147
148    // ── Page 2 ──
149    doc.add_paragraph("").page_break_before(true);
150
151    doc.add_paragraph("Second Page").style("Heading1");
152
153    doc.add_paragraph(
154        "This page shows the default header banner (dark blue). The first page \
155         had a lighter blue banner because we set a different first-page header.",
156    );
157
158    doc.add_paragraph("");
159
160    doc.add_paragraph(
161        "The banner repeats on every page because it is placed in the header part. \
162         You can have different banners for default, first-page, and even-page headers.",
163    );
164
165    doc.set_title("Header Banner Example");
166    doc.set_author("rdocx");
167
168    doc.save(path).unwrap();
169}
More examples
Hide additional examples
examples/generate_all_samples.rs (lines 727-731)
697fn generate_proposal(_samples_dir: &Path) -> Document {
698    let mut doc = Document::new();
699
700    // Colors: Navy #1B2A4A, Gold #C5922E, Light #F4F1EB
701    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
702    doc.set_margins(
703        Length::inches(1.0),
704        Length::inches(1.0),
705        Length::inches(1.0),
706        Length::inches(1.0),
707    );
708    doc.set_title("AI Platform Modernization Proposal");
709    doc.set_author("Walter White");
710    doc.set_subject("Technology Proposal");
711    doc.set_keywords("proposal, AI, modernization, Tensorbee");
712
713    // Banner header
714    let logo_img = create_logo_png(220, 48);
715    let banner = build_header_banner_xml(
716        "rId1",
717        &BannerOpts {
718            bg_color: "1B2A4A",
719            banner_width: 7772400,
720            banner_height: 914400,
721            logo_width: 2011680,
722            logo_height: 438912,
723            logo_x_offset: 295125,
724            logo_y_offset: 237744,
725        },
726    );
727    doc.set_raw_header_with_images(
728        banner,
729        &[("rId1", &logo_img, "logo.png")],
730        rdocx_oxml::header_footer::HdrFtrType::Default,
731    );
732    doc.set_different_first_page(true);
733    let cover_banner = build_header_banner_xml(
734        "rId1",
735        &BannerOpts {
736            bg_color: "C5922E",
737            banner_width: 7772400,
738            banner_height: 914400,
739            logo_width: 2011680,
740            logo_height: 438912,
741            logo_x_offset: 295125,
742            logo_y_offset: 237744,
743        },
744    );
745    doc.set_raw_header_with_images(
746        cover_banner,
747        &[("rId1", &logo_img, "logo.png")],
748        rdocx_oxml::header_footer::HdrFtrType::First,
749    );
750    doc.set_margins(
751        Length::twips(2292),
752        Length::twips(1440),
753        Length::twips(1440),
754        Length::twips(1440),
755    );
756    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
757    doc.set_footer("Tensorbee — Confidential");
758
759    // Custom styles
760    doc.add_style(
761        StyleBuilder::paragraph("ProposalTitle", "Proposal Title")
762            .based_on("Normal")
763            .run_properties(rdocx_oxml::properties::CT_RPr {
764                bold: Some(true),
765                font_ascii: Some("Georgia".to_string()),
766                font_hansi: Some("Georgia".to_string()),
767                sz: Some(rdocx_oxml::HalfPoint(56)), // half-points → 28pt
768                color: Some("1B2A4A".to_string()),
769                ..Default::default()
770            }),
771    );
772
773    // ── Cover Page ──
774    for _ in 0..4 {
775        doc.add_paragraph("");
776    }
777    doc.add_paragraph("AI Platform Modernization")
778        .style("ProposalTitle")
779        .alignment(Alignment::Center);
780    doc.add_paragraph("")
781        .alignment(Alignment::Center)
782        .border_bottom(BorderStyle::Single, 8, "C5922E");
783    {
784        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
785        p.add_run("Prepared for Global Financial Corp.")
786            .size(14.0)
787            .color("666666")
788            .italic(true);
789    }
790    {
791        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
792        p.add_run("Prepared by: Walter White, CEO — Tensorbee")
793            .size(12.0)
794            .color("888888");
795    }
796    {
797        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
798        p.add_run("February 22, 2026").size(12.0).color("888888");
799    }
800
801    // ── TOC ──
802    doc.add_paragraph("").page_break_before(true);
803    doc.add_paragraph("Table of Contents").style("Heading1");
804    // We'll add a manual-style TOC since the insert_toc goes at a specific position
805    doc.insert_toc(doc.content_count(), 2);
806
807    // ── Executive Summary ──
808    doc.add_paragraph("").page_break_before(true);
809    doc.add_paragraph("Executive Summary").style("Heading1");
810    doc.add_paragraph(
811        "Tensorbee proposes a comprehensive modernization of Global Financial Corp's AI platform, \
812         transitioning from legacy batch-processing systems to a real-time inference architecture. \
813         This transformation will reduce model deployment time from weeks to hours, improve \
814         prediction accuracy by an estimated 23%, and deliver $4.2M in annual operational savings.",
815    )
816    .first_line_indent(Length::inches(0.3));
817    doc.add_paragraph(
818        "The project spans three phases over 18 months, with a total investment of $2.8M. \
819         Tensorbee brings deep expertise in ML infrastructure, having successfully completed \
820         similar transformations for three Fortune 500 financial institutions.",
821    )
822    .first_line_indent(Length::inches(0.3));
823
824    // Key metrics table
825    doc.add_paragraph("");
826    {
827        let mut tbl = doc
828            .add_table(5, 2)
829            .borders(BorderStyle::Single, 4, "1B2A4A")
830            .width_pct(80.0);
831        tbl.cell(0, 0).unwrap().set_text("Key Metric");
832        tbl.cell(0, 0).unwrap().shading("1B2A4A");
833        tbl.cell(0, 1).unwrap().set_text("Value");
834        tbl.cell(0, 1).unwrap().shading("1B2A4A");
835        let rows = [
836            ("Total Investment", "$2.8M"),
837            ("Annual Savings", "$4.2M"),
838            ("ROI (Year 1)", "150%"),
839            ("Timeline", "18 months"),
840        ];
841        for (i, (k, v)) in rows.iter().enumerate() {
842            tbl.cell(i + 1, 0).unwrap().set_text(k);
843            if i % 2 == 0 {
844                tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
845            }
846            tbl.cell(i + 1, 1).unwrap().set_text(v);
847            if i % 2 == 0 {
848                tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
849            }
850        }
851    }
852
853    // ── Problem Statement ──
854    doc.add_paragraph("").page_break_before(true);
855    doc.add_paragraph("Problem Statement").style("Heading1");
856    doc.add_paragraph(
857        "Global Financial Corp's current AI infrastructure faces three critical challenges:",
858    );
859    doc.add_numbered_list_item("Deployment Latency — New models take 3-4 weeks to deploy to production, compared to the industry benchmark of 2-3 days.", 0);
860    doc.add_numbered_list_item("Scalability Constraints — The batch processing architecture cannot handle real-time inference demands during peak trading hours.", 0);
861    doc.add_numbered_list_item("Technical Debt — Legacy Python 2.7 codebases and manual deployment scripts create reliability risks and slow iteration speed.", 0);
862
863    // ── Proposed Solution ──
864    doc.add_paragraph("").page_break_before(true);
865    doc.add_paragraph("Proposed Solution").style("Heading1");
866
867    doc.add_paragraph("Phase 1: Foundation (Months 1-6)")
868        .style("Heading2");
869    doc.add_bullet_list_item("Deploy Kubernetes-based ML serving infrastructure", 0);
870    doc.add_bullet_list_item("Implement CI/CD pipeline for model deployment", 0);
871    doc.add_bullet_list_item("Migrate top 5 production models to new platform", 0);
872
873    doc.add_paragraph("Phase 2: Expansion (Months 7-12)")
874        .style("Heading2");
875    doc.add_bullet_list_item(
876        "Real-time feature engineering pipeline (Apache Kafka + Flink)",
877        0,
878    );
879    doc.add_bullet_list_item("A/B testing framework for model variants", 0);
880    doc.add_bullet_list_item("Automated model monitoring and drift detection", 0);
881
882    doc.add_paragraph("Phase 3: Optimization (Months 13-18)")
883        .style("Heading2");
884    doc.add_bullet_list_item("GPU inference optimization (TensorRT/ONNX Runtime)", 0);
885    doc.add_bullet_list_item("Multi-region deployment for latency reduction", 0);
886    doc.add_bullet_list_item(
887        "Self-service model deployment portal for data science teams",
888        0,
889    );
890
891    // ── Budget ──
892    doc.add_paragraph("Budget Breakdown").style("Heading1");
893    {
894        let mut tbl = doc
895            .add_table(6, 3)
896            .borders(BorderStyle::Single, 4, "1B2A4A")
897            .width_pct(100.0);
898        tbl.cell(0, 0).unwrap().set_text("Category");
899        tbl.cell(0, 0).unwrap().shading("1B2A4A");
900        tbl.cell(0, 1).unwrap().set_text("Cost");
901        tbl.cell(0, 1).unwrap().shading("1B2A4A");
902        tbl.cell(0, 2).unwrap().set_text("Notes");
903        tbl.cell(0, 2).unwrap().shading("1B2A4A");
904        let items = [
905            (
906                "Infrastructure",
907                "$450,000",
908                "Cloud compute + GPU instances",
909            ),
910            ("Engineering Services", "$1,600,000", "12 FTE x 18 months"),
911            (
912                "Software Licenses",
913                "$350,000",
914                "Kafka, monitoring, CI/CD tools",
915            ),
916            (
917                "Training & Enablement",
918                "$200,000",
919                "Team training, documentation",
920            ),
921            ("Contingency (10%)", "$200,000", "Risk buffer"),
922        ];
923        for (i, (cat, cost, note)) in items.iter().enumerate() {
924            tbl.cell(i + 1, 0).unwrap().set_text(cat);
925            tbl.cell(i + 1, 1).unwrap().set_text(cost);
926            tbl.cell(i + 1, 2).unwrap().set_text(note);
927            if i % 2 == 0 {
928                tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
929                tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
930                tbl.cell(i + 1, 2).unwrap().shading("F4F1EB");
931            }
932        }
933    }
934
935    // ── Closing ──
936    doc.add_paragraph("");
937    doc.add_paragraph("Next Steps").style("Heading1");
938    doc.add_numbered_list_item(
939        "Schedule technical deep-dive with GFC engineering team (Week 1)",
940        0,
941    );
942    doc.add_numbered_list_item("Finalize scope and sign SOW (Week 2-3)", 0);
943    doc.add_numbered_list_item("Kick off Phase 1 with joint planning session (Week 4)", 0);
944
945    doc.add_paragraph("");
946    {
947        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
948        p.add_run("Walter White")
949            .bold(true)
950            .size(14.0)
951            .color("1B2A4A");
952    }
953    {
954        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
955        p.add_run("CEO, Tensorbee | walter@tensorbee.com")
956            .size(10.0)
957            .color("888888");
958    }
959
960    doc
961}
962
963// =============================================================================
964// 3. QUOTE / BILL OF MATERIALS — Teal + orange scheme
965// =============================================================================
966fn generate_quote(_samples_dir: &Path) -> Document {
967    let mut doc = Document::new();
968
969    // Colors: Teal #008B8B, Dark #1A3C3C, Orange #E07020, Light #F0F8F8
970    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
971    doc.set_margins(
972        Length::inches(0.75),
973        Length::inches(0.75),
974        Length::inches(0.75),
975        Length::inches(0.75),
976    );
977    doc.set_title("Quotation QT-2026-0147");
978    doc.set_author("Walter White");
979    doc.set_keywords("quote, BOM, Tensorbee");
980
981    doc.set_header("Tensorbee — Quotation");
982    doc.set_footer("QT-2026-0147 | Page");
983
984    // ── Header Block ──
985    {
986        let mut p = doc.add_paragraph("").alignment(Alignment::Left);
987        p.add_run("TENSORBEE")
988            .bold(true)
989            .size(28.0)
990            .color("008B8B")
991            .font("Helvetica");
992    }
993    doc.add_paragraph("123 Innovation Drive, Suite 400")
994        .alignment(Alignment::Left);
995    doc.add_paragraph("San Francisco, CA 94105 | +1 (415) 555-0199")
996        .alignment(Alignment::Left);
997    doc.add_paragraph("")
998        .border_bottom(BorderStyle::Single, 8, "008B8B");
999
1000    // Quote meta
1001    doc.add_paragraph("");
1002    {
1003        let mut tbl = doc.add_table(4, 4).width_pct(100.0);
1004        tbl.cell(0, 0).unwrap().set_text("QUOTATION");
1005        tbl.cell(0, 0).unwrap().shading("008B8B").grid_span(2);
1006        tbl.cell(0, 2).unwrap().set_text("DATE");
1007        tbl.cell(0, 2).unwrap().shading("008B8B");
1008        tbl.cell(0, 3).unwrap().set_text("VALID UNTIL");
1009        tbl.cell(0, 3).unwrap().shading("008B8B");
1010        tbl.cell(1, 0).unwrap().set_text("Quote #:");
1011        tbl.cell(1, 1).unwrap().set_text("QT-2026-0147");
1012        tbl.cell(1, 2).unwrap().set_text("Feb 22, 2026");
1013        tbl.cell(1, 3).unwrap().set_text("Mar 22, 2026");
1014        tbl.cell(2, 0).unwrap().set_text("Prepared by:");
1015        tbl.cell(2, 1).unwrap().set_text("Walter White");
1016        tbl.cell(2, 2).unwrap().set_text("Payment:");
1017        tbl.cell(2, 3).unwrap().set_text("Net 30");
1018        tbl.cell(3, 0).unwrap().set_text("Customer:");
1019        tbl.cell(3, 1).unwrap().set_text("Meridian Dynamics LLC");
1020        tbl.cell(3, 2).unwrap().set_text("Currency:");
1021        tbl.cell(3, 3).unwrap().set_text("USD");
1022    }
1023
1024    doc.add_paragraph("");
1025
1026    // ── Bill of Materials ──
1027    {
1028        let mut p = doc.add_paragraph("");
1029        p.add_run("Bill of Materials")
1030            .bold(true)
1031            .size(16.0)
1032            .color("1A3C3C");
1033    }
1034    doc.add_paragraph("");
1035
1036    {
1037        let mut tbl = doc
1038            .add_table(10, 6)
1039            .borders(BorderStyle::Single, 2, "008B8B")
1040            .width_pct(100.0)
1041            .layout_fixed();
1042        // Headers
1043        let hdrs = [
1044            "#",
1045            "Part Number",
1046            "Description",
1047            "Qty",
1048            "Unit Price",
1049            "Total",
1050        ];
1051        for (c, h) in hdrs.iter().enumerate() {
1052            tbl.cell(0, c).unwrap().set_text(h);
1053            tbl.cell(0, c).unwrap().shading("008B8B");
1054        }
1055
1056        let items: Vec<(&str, &str, &str, &str, &str)> = vec![
1057            (
1058                "TB-GPU-A100",
1059                "NVIDIA A100 80GB GPU",
1060                "4",
1061                "$12,500.00",
1062                "$50,000.00",
1063            ),
1064            (
1065                "TB-SRV-R750",
1066                "Dell R750xa Server Chassis",
1067                "2",
1068                "$8,200.00",
1069                "$16,400.00",
1070            ),
1071            (
1072                "TB-RAM-256G",
1073                "256GB DDR5 ECC Memory Module",
1074                "8",
1075                "$890.00",
1076                "$7,120.00",
1077            ),
1078            (
1079                "TB-SSD-3840",
1080                "3.84TB NVMe U.2 SSD",
1081                "8",
1082                "$1,150.00",
1083                "$9,200.00",
1084            ),
1085            (
1086                "TB-NET-CX7",
1087                "ConnectX-7 200GbE NIC",
1088                "4",
1089                "$1,800.00",
1090                "$7,200.00",
1091            ),
1092            (
1093                "TB-SW-48P",
1094                "48-Port 100GbE Switch",
1095                "1",
1096                "$22,000.00",
1097                "$22,000.00",
1098            ),
1099            (
1100                "TB-CAB-RACK",
1101                "42U Server Rack + PDU",
1102                "1",
1103                "$4,500.00",
1104                "$4,500.00",
1105            ),
1106            (
1107                "TB-SVC-INST",
1108                "Installation & Configuration",
1109                "1",
1110                "$8,500.00",
1111                "$8,500.00",
1112            ),
1113            (
1114                "TB-SVC-SUPP",
1115                "3-Year Premium Support",
1116                "1",
1117                "$15,000.00",
1118                "$15,000.00",
1119            ),
1120        ];
1121
1122        for (i, (pn, desc, qty, unit, total)) in items.iter().enumerate() {
1123            let row = i + 1;
1124            tbl.cell(row, 0).unwrap().set_text(&format!("{}", i + 1));
1125            tbl.cell(row, 1).unwrap().set_text(pn);
1126            tbl.cell(row, 2).unwrap().set_text(desc);
1127            tbl.cell(row, 3).unwrap().set_text(qty);
1128            tbl.cell(row, 4).unwrap().set_text(unit);
1129            tbl.cell(row, 5).unwrap().set_text(total);
1130            if i % 2 == 0 {
1131                for c in 0..6 {
1132                    tbl.cell(row, c).unwrap().shading("F0F8F8");
1133                }
1134            }
1135        }
1136    }
1137
1138    doc.add_paragraph("");
1139
1140    // ── Totals ──
1141    {
1142        let mut tbl = doc
1143            .add_table(4, 2)
1144            .borders(BorderStyle::Single, 2, "008B8B")
1145            .width(Length::inches(3.5))
1146            .alignment(Alignment::Right);
1147        tbl.cell(0, 0).unwrap().set_text("Subtotal");
1148        tbl.cell(0, 1).unwrap().set_text("$139,920.00");
1149        tbl.cell(1, 0).unwrap().set_text("Shipping & Handling");
1150        tbl.cell(1, 1).unwrap().set_text("$2,500.00");
1151        tbl.cell(2, 0).unwrap().set_text("Tax (8.625%)");
1152        tbl.cell(2, 1).unwrap().set_text("$12,068.10");
1153        tbl.cell(3, 0).unwrap().set_text("TOTAL");
1154        tbl.cell(3, 0).unwrap().shading("E07020");
1155        tbl.cell(3, 1).unwrap().set_text("$154,488.10");
1156        tbl.cell(3, 1).unwrap().shading("E07020");
1157    }
1158
1159    doc.add_paragraph("");
1160
1161    // ── Terms & Conditions ──
1162    {
1163        let mut p = doc.add_paragraph("");
1164        p.add_run("Terms & Conditions")
1165            .bold(true)
1166            .size(14.0)
1167            .color("1A3C3C");
1168    }
1169    doc.add_numbered_list_item(
1170        "This quotation is valid for 30 calendar days from the date of issue.",
1171        0,
1172    );
1173    doc.add_numbered_list_item(
1174        "All prices are in USD and exclusive of applicable taxes unless stated.",
1175        0,
1176    );
1177    doc.add_numbered_list_item("Standard lead time is 4-6 weeks from PO receipt.", 0);
1178    doc.add_numbered_list_item(
1179        "Payment terms: Net 30 from invoice date. 2% discount for payment within 10 days.",
1180        0,
1181    );
1182    doc.add_numbered_list_item(
1183        "Warranty: 3-year manufacturer warranty on all hardware components.",
1184        0,
1185    );
1186    doc.add_numbered_list_item(
1187        "Returns subject to 15% restocking fee if initiated after 14 days.",
1188        0,
1189    );
1190
1191    doc.add_paragraph("");
1192    doc.add_paragraph("")
1193        .border_bottom(BorderStyle::Single, 4, "008B8B");
1194    doc.add_paragraph("");
1195    {
1196        let mut p = doc.add_paragraph("");
1197        p.add_run("Accepted by: ").bold(true);
1198        p.add_run("___________________________ Date: ___________");
1199    }
1200    {
1201        let mut p = doc.add_paragraph("");
1202        p.add_run("Print Name: ").bold(true);
1203        p.add_run("___________________________ Title: ___________");
1204    }
1205
1206    doc
1207}
1208
1209// =============================================================================
1210// 4. INVOICE — Crimson + charcoal scheme
1211// =============================================================================
1212fn generate_invoice(_samples_dir: &Path) -> Document {
1213    let mut doc = Document::new();
1214
1215    // Colors: Crimson #B22222, Charcoal #333333, Light #FAF0F0
1216    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1217    doc.set_margins(
1218        Length::inches(0.75),
1219        Length::inches(0.75),
1220        Length::inches(0.75),
1221        Length::inches(0.75),
1222    );
1223    doc.set_title("Invoice INV-2026-0392");
1224    doc.set_author("Walter White");
1225    doc.set_keywords("invoice, Tensorbee");
1226
1227    doc.set_footer("Tensorbee — Thank you for your business!");
1228
1229    // ── Company & Invoice Header ──
1230    {
1231        let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1232        // Company name left, INVOICE right
1233        {
1234            let mut cell = tbl.cell(0, 0).unwrap();
1235            let mut p = cell.add_paragraph("");
1236            p.add_run("TENSORBEE")
1237                .bold(true)
1238                .size(32.0)
1239                .color("B22222")
1240                .font("Helvetica");
1241        }
1242        {
1243            let mut cell = tbl.cell(0, 1).unwrap();
1244            let mut p = cell.add_paragraph("");
1245            p.add_run("INVOICE").bold(true).size(32.0).color("333333");
1246        }
1247        tbl.cell(1, 0)
1248            .unwrap()
1249            .set_text("123 Innovation Drive, Suite 400");
1250        tbl.cell(1, 1).unwrap().set_text("Invoice #: INV-2026-0392");
1251        tbl.cell(2, 0).unwrap().set_text("San Francisco, CA 94105");
1252        tbl.cell(2, 1).unwrap().set_text("Date: February 22, 2026");
1253        tbl.cell(3, 0).unwrap().set_text("walter@tensorbee.com");
1254        tbl.cell(3, 1).unwrap().set_text("Due Date: March 24, 2026");
1255    }
1256
1257    doc.add_paragraph("")
1258        .border_bottom(BorderStyle::Thick, 8, "B22222");
1259    doc.add_paragraph("");
1260
1261    // ── Bill To / Ship To ──
1262    {
1263        let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1264        tbl.cell(0, 0).unwrap().set_text("BILL TO");
1265        tbl.cell(0, 0).unwrap().shading("B22222");
1266        tbl.cell(0, 1).unwrap().set_text("SHIP TO");
1267        tbl.cell(0, 1).unwrap().shading("B22222");
1268        tbl.cell(1, 0).unwrap().set_text("Meridian Dynamics LLC");
1269        tbl.cell(1, 1).unwrap().set_text("Meridian Dynamics LLC");
1270        tbl.cell(2, 0).unwrap().set_text("456 Enterprise Blvd");
1271        tbl.cell(2, 1).unwrap().set_text("Attn: Server Room B");
1272        tbl.cell(3, 0).unwrap().set_text("Austin, TX 78701");
1273        tbl.cell(3, 1)
1274            .unwrap()
1275            .set_text("456 Enterprise Blvd, Austin, TX 78701");
1276    }
1277
1278    doc.add_paragraph("");
1279
1280    // ── Line Items ──
1281    {
1282        let mut tbl = doc
1283            .add_table(8, 5)
1284            .borders(BorderStyle::Single, 2, "B22222")
1285            .width_pct(100.0);
1286        let hdrs = ["Description", "Qty", "Unit Price", "Tax", "Amount"];
1287        for (c, h) in hdrs.iter().enumerate() {
1288            tbl.cell(0, c).unwrap().set_text(h);
1289            tbl.cell(0, c).unwrap().shading("B22222");
1290        }
1291        let items = [
1292            (
1293                "ML Infrastructure Setup — Phase 1",
1294                "1",
1295                "$45,000.00",
1296                "$3,881.25",
1297                "$48,881.25",
1298            ),
1299            (
1300                "Data Pipeline Development (160 hrs)",
1301                "160",
1302                "$225.00",
1303                "$3,105.00",
1304                "$39,105.00",
1305            ),
1306            (
1307                "GPU Cluster Configuration",
1308                "1",
1309                "$12,000.00",
1310                "$1,035.00",
1311                "$13,035.00",
1312            ),
1313            (
1314                "API Gateway Implementation",
1315                "1",
1316                "$18,500.00",
1317                "$1,595.63",
1318                "$20,095.63",
1319            ),
1320            (
1321                "Load Testing & QA (80 hrs)",
1322                "80",
1323                "$195.00",
1324                "$1,345.50",
1325                "$16,945.50",
1326            ),
1327            (
1328                "Documentation & Training",
1329                "1",
1330                "$8,000.00",
1331                "$690.00",
1332                "$8,690.00",
1333            ),
1334            (
1335                "Project Management (3 months)",
1336                "3",
1337                "$6,500.00",
1338                "$1,679.63",
1339                "$21,179.63",
1340            ),
1341        ];
1342        for (i, (desc, qty, unit, tax, amt)) in items.iter().enumerate() {
1343            let r = i + 1;
1344            tbl.cell(r, 0).unwrap().set_text(desc);
1345            tbl.cell(r, 1).unwrap().set_text(qty);
1346            tbl.cell(r, 2).unwrap().set_text(unit);
1347            tbl.cell(r, 3).unwrap().set_text(tax);
1348            tbl.cell(r, 4).unwrap().set_text(amt);
1349            if i % 2 == 0 {
1350                for c in 0..5 {
1351                    tbl.cell(r, c).unwrap().shading("FAF0F0");
1352                }
1353            }
1354        }
1355    }
1356
1357    doc.add_paragraph("");
1358
1359    // ── Totals ──
1360    {
1361        let mut tbl = doc
1362            .add_table(4, 2)
1363            .borders(BorderStyle::Single, 2, "B22222")
1364            .width(Length::inches(3.0))
1365            .alignment(Alignment::Right);
1366        tbl.cell(0, 0).unwrap().set_text("Subtotal");
1367        tbl.cell(0, 1).unwrap().set_text("$154,600.00");
1368        tbl.cell(1, 0).unwrap().set_text("Tax (8.625%)");
1369        tbl.cell(1, 1).unwrap().set_text("$13,334.25");
1370        tbl.cell(2, 0).unwrap().set_text("Discount (5%)");
1371        tbl.cell(2, 1).unwrap().set_text("-$7,730.00");
1372        tbl.cell(3, 0).unwrap().set_text("AMOUNT DUE");
1373        tbl.cell(3, 0).unwrap().shading("B22222");
1374        tbl.cell(3, 1).unwrap().set_text("$160,204.25");
1375        tbl.cell(3, 1).unwrap().shading("B22222");
1376    }
1377
1378    doc.add_paragraph("");
1379    doc.add_paragraph("");
1380
1381    // ── Payment Details ──
1382    {
1383        let mut p = doc.add_paragraph("");
1384        p.add_run("Payment Details")
1385            .bold(true)
1386            .size(14.0)
1387            .color("333333");
1388    }
1389    doc.add_paragraph("Bank: Silicon Valley Bank")
1390        .indent_left(Length::inches(0.3));
1391    doc.add_paragraph("Account: Tensorbee Inc. — 0847-2953-1120")
1392        .indent_left(Length::inches(0.3));
1393    doc.add_paragraph("Routing: 121140399")
1394        .indent_left(Length::inches(0.3));
1395    doc.add_paragraph("Swift: SVBKUS6S")
1396        .indent_left(Length::inches(0.3));
1397
1398    doc.add_paragraph("");
1399
1400    doc.add_paragraph("Please include invoice number INV-2026-0392 in the payment reference.")
1401        .shading("FAF0F0")
1402        .border_all(BorderStyle::Single, 2, "B22222");
1403
1404    doc
1405}
1406
1407// =============================================================================
1408// 5. REPORT — Forest green + earth tones, with images & hierarchical sections
1409// =============================================================================
1410fn generate_report(_samples_dir: &Path) -> Document {
1411    let mut doc = Document::new();
1412
1413    // Colors: Forest #2D5016, Sage #6B8E23, Earth #8B7355, Cream #FFFAF0
1414    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1415    doc.set_margins(
1416        Length::inches(1.0),
1417        Length::inches(1.0),
1418        Length::inches(1.0),
1419        Length::inches(1.0),
1420    );
1421    doc.set_title("Q4 2025 Environmental Impact Report");
1422    doc.set_author("Walter White");
1423    doc.set_subject("Quarterly Environmental Report");
1424    doc.set_keywords("environment, sustainability, report, Tensorbee");
1425
1426    doc.set_different_first_page(true);
1427    doc.set_first_page_header("");
1428    doc.set_header("Tensorbee — Q4 2025 Environmental Impact Report");
1429    doc.set_footer("CONFIDENTIAL — Page");
1430
1431    // ── Cover ──
1432    let cover_bg = create_sample_png(612, 792, [20, 50, 15]);
1433    doc.add_background_image(&cover_bg, "report_cover.png");
1434
1435    for _ in 0..5 {
1436        doc.add_paragraph("");
1437    }
1438    {
1439        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1440        p.add_run("Q4 2025")
1441            .bold(true)
1442            .size(48.0)
1443            .color("FFFFFF")
1444            .font("Georgia");
1445    }
1446    {
1447        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1448        p.add_run("Environmental Impact Report")
1449            .size(24.0)
1450            .color("90EE90")
1451            .font("Georgia")
1452            .italic(true);
1453    }
1454    doc.add_paragraph("");
1455    {
1456        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1457        p.add_run("Tensorbee — Sustainability Division")
1458            .size(14.0)
1459            .color("C0C0C0");
1460    }
1461    {
1462        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1463        p.add_run("Prepared by Walter White, Chief Sustainability Officer")
1464            .size(11.0)
1465            .color("AAAAAA");
1466    }
1467
1468    // ── TOC ──
1469    doc.add_paragraph("").page_break_before(true);
1470    doc.insert_toc(doc.content_count(), 3);
1471
1472    // ── Section 1: Executive Overview ──
1473    doc.add_paragraph("").page_break_before(true);
1474    doc.add_paragraph("Executive Overview").style("Heading1");
1475    doc.add_paragraph(
1476        "This report presents Tensorbee's environmental performance for Q4 2025. Our \
1477         sustainability initiatives have yielded a 34% reduction in carbon emissions compared \
1478         to Q4 2024, exceeding our target of 25%. Key achievements include the transition to \
1479         100% renewable energy in our primary data centers and the launch of our carbon offset \
1480         marketplace.",
1481    )
1482    .first_line_indent(Length::inches(0.3));
1483
1484    // Image: performance chart
1485    let chart_img = create_chart_png(400, 200);
1486    doc.add_paragraph("");
1487    doc.add_picture(
1488        &chart_img,
1489        "performance_chart.png",
1490        Length::inches(5.0),
1491        Length::inches(2.5),
1492    )
1493    .alignment(Alignment::Center);
1494    {
1495        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1496        p.add_run("Figure 1: Quarterly Carbon Emissions (tonnes CO2e)")
1497            .italic(true)
1498            .size(9.0)
1499            .color("666666");
1500    }
1501
1502    // ── Section 2: Energy Consumption ──
1503    doc.add_paragraph("").page_break_before(true);
1504    doc.add_paragraph("Energy Consumption").style("Heading1");
1505
1506    doc.add_paragraph("Data Center Operations")
1507        .style("Heading2");
1508    doc.add_paragraph(
1509        "Our three primary data centers consumed a combined 4.2 GWh in Q4 2025, \
1510         a 12% reduction from Q3 2025 driven by improved cooling efficiency and \
1511         server consolidation.",
1512    );
1513
1514    // Data table
1515    {
1516        let mut tbl = doc
1517            .add_table(5, 4)
1518            .borders(BorderStyle::Single, 4, "2D5016")
1519            .width_pct(100.0);
1520        let hdrs = ["Data Center", "Capacity (MW)", "Usage (GWh)", "PUE"];
1521        for (c, h) in hdrs.iter().enumerate() {
1522            tbl.cell(0, c).unwrap().set_text(h);
1523            tbl.cell(0, c).unwrap().shading("2D5016");
1524        }
1525        let rows = [
1526            ("San Francisco (Primary)", "3.2", "1.8", "1.12"),
1527            ("Dublin (EU)", "2.1", "1.2", "1.18"),
1528            ("Singapore (APAC)", "1.8", "1.2", "1.24"),
1529            ("TOTAL", "7.1", "4.2", "1.17 avg"),
1530        ];
1531        for (i, (dc, cap, use_, pue)) in rows.iter().enumerate() {
1532            tbl.cell(i + 1, 0).unwrap().set_text(dc);
1533            tbl.cell(i + 1, 1).unwrap().set_text(cap);
1534            tbl.cell(i + 1, 2).unwrap().set_text(use_);
1535            tbl.cell(i + 1, 3).unwrap().set_text(pue);
1536            if i == 3 {
1537                for c in 0..4 {
1538                    tbl.cell(i + 1, c).unwrap().shading("E2EFDA");
1539                }
1540            }
1541        }
1542    }
1543
1544    doc.add_paragraph("");
1545
1546    doc.add_paragraph("Renewable Energy Mix").style("Heading2");
1547    doc.add_paragraph("Breakdown of energy sources across all facilities:");
1548
1549    doc.add_bullet_list_item("Solar PV: 42% (1.76 GWh)", 0);
1550    doc.add_bullet_list_item("Wind Power Purchase Agreements: 38% (1.60 GWh)", 0);
1551    doc.add_bullet_list_item("Hydroelectric: 12% (0.50 GWh)", 0);
1552    doc.add_bullet_list_item("Grid (non-renewable): 8% (0.34 GWh)", 0);
1553
1554    // ── Section 3: Water & Waste ──
1555    doc.add_paragraph("Water & Waste Management")
1556        .style("Heading1");
1557
1558    doc.add_paragraph("Water Usage").style("Heading2");
1559    doc.add_paragraph(
1560        "Total water consumption was 12.4 million gallons, a 15% reduction from Q3. \
1561         Our closed-loop cooling systems now recycle 78% of water used in cooling operations.",
1562    );
1563
1564    doc.add_paragraph("Waste Reduction").style("Heading2");
1565    doc.add_paragraph("E-waste management results for Q4:")
1566        .keep_with_next(true);
1567    {
1568        let mut tbl = doc
1569            .add_table(4, 3)
1570            .borders(BorderStyle::Single, 2, "6B8E23")
1571            .width_pct(80.0);
1572        tbl.cell(0, 0).unwrap().set_text("Category");
1573        tbl.cell(0, 0).unwrap().shading("6B8E23");
1574        tbl.cell(0, 1).unwrap().set_text("Weight (kg)");
1575        tbl.cell(0, 1).unwrap().shading("6B8E23");
1576        tbl.cell(0, 2).unwrap().set_text("Recycled %");
1577        tbl.cell(0, 2).unwrap().shading("6B8E23");
1578        let rows = [
1579            ("Server Hardware", "2,450", "94%"),
1580            ("Networking Equipment", "820", "91%"),
1581            ("Storage Media", "340", "99%"),
1582        ];
1583        for (i, (cat, wt, pct)) in rows.iter().enumerate() {
1584            tbl.cell(i + 1, 0).unwrap().set_text(cat);
1585            tbl.cell(i + 1, 1).unwrap().set_text(wt);
1586            tbl.cell(i + 1, 2).unwrap().set_text(pct);
1587        }
1588    }
1589
1590    // ── Section 4: Initiatives ──
1591    doc.add_paragraph("").page_break_before(true);
1592    doc.add_paragraph("Q1 2026 Initiatives").style("Heading1");
1593
1594    doc.add_paragraph("Planned Programs").style("Heading2");
1595    doc.add_numbered_list_item(
1596        "Deploy on-site battery storage at SF data center (2 MWh capacity)",
1597        0,
1598    );
1599    doc.add_numbered_list_item("Pilot immersion cooling in Dublin facility", 0);
1600    doc.add_numbered_list_item("Launch employee commute carbon offset program", 0);
1601    doc.add_numbered_list_item("Achieve ISO 14001 certification for Singapore facility", 0);
1602
1603    doc.add_paragraph("Investment Targets").style("Heading2");
1604    doc.add_paragraph("Sustainability CapEx allocation for FY2026:")
1605        .keep_with_next(true);
1606    {
1607        let mut tbl = doc
1608            .add_table(5, 2)
1609            .borders(BorderStyle::Single, 4, "2D5016");
1610        tbl.cell(0, 0).unwrap().set_text("Initiative");
1611        tbl.cell(0, 0).unwrap().shading("2D5016");
1612        tbl.cell(0, 1).unwrap().set_text("Budget");
1613        tbl.cell(0, 1).unwrap().shading("2D5016");
1614        let rows = [
1615            ("Battery Storage", "$1.2M"),
1616            ("Immersion Cooling Pilot", "$800K"),
1617            ("Solar Panel Expansion", "$2.1M"),
1618            ("Carbon Credits", "$500K"),
1619        ];
1620        for (i, (init, budget)) in rows.iter().enumerate() {
1621            tbl.cell(i + 1, 0).unwrap().set_text(init);
1622            tbl.cell(i + 1, 1).unwrap().set_text(budget);
1623            if i % 2 == 0 {
1624                tbl.cell(i + 1, 0).unwrap().shading("FFFAF0");
1625                tbl.cell(i + 1, 1).unwrap().shading("FFFAF0");
1626            }
1627        }
1628    }
1629
1630    // Image: sustainability roadmap
1631    let roadmap_img = create_sample_png(500, 100, [40, 80, 30]);
1632    doc.add_paragraph("");
1633    doc.add_picture(
1634        &roadmap_img,
1635        "roadmap.png",
1636        Length::inches(6.0),
1637        Length::inches(1.2),
1638    )
1639    .alignment(Alignment::Center);
1640    {
1641        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1642        p.add_run("Figure 2: 2026 Sustainability Roadmap")
1643            .italic(true)
1644            .size(9.0)
1645            .color("666666");
1646    }
1647
1648    doc.add_paragraph("");
1649    doc.add_paragraph(
1650        "For questions about this report, contact Walter White at sustainability@tensorbee.com.",
1651    )
1652    .shading("FFFAF0")
1653    .border_all(BorderStyle::Single, 2, "2D5016");
1654
1655    doc
1656}
1657
1658// =============================================================================
1659// 6. LETTER — Slate blue + silver scheme
1660// =============================================================================
1661fn generate_letter(_samples_dir: &Path) -> Document {
1662    let mut doc = Document::new();
1663
1664    // Colors: Slate #4A5568, Blue accent #3182CE
1665    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1666    doc.set_margins(
1667        Length::inches(1.25),
1668        Length::inches(1.25),
1669        Length::inches(1.25),
1670        Length::inches(1.25),
1671    );
1672    doc.set_title("Business Letter");
1673    doc.set_author("Walter White");
1674
1675    // Banner header using raw XML
1676    let logo_img = create_logo_png(220, 48);
1677    let banner = build_header_banner_xml(
1678        "rId1",
1679        &BannerOpts {
1680            bg_color: "4A5568",
1681            banner_width: 7772400,
1682            banner_height: 731520, // ~0.8"
1683            logo_width: 2011680,
1684            logo_height: 438912,
1685            logo_x_offset: 295125,
1686            logo_y_offset: 146304,
1687        },
1688    );
1689    doc.set_raw_header_with_images(
1690        banner,
1691        &[("rId1", &logo_img, "logo.png")],
1692        rdocx_oxml::header_footer::HdrFtrType::Default,
1693    );
1694    doc.set_margins(
1695        Length::twips(2000),
1696        Length::twips(1800),
1697        Length::twips(1440),
1698        Length::twips(1800),
1699    );
1700    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
1701
1702    // Footer with contact
1703    doc.set_footer(
1704        "Tensorbee | 123 Innovation Drive, Suite 400 | San Francisco, CA 94105 | tensorbee.com",
1705    );
1706
1707    // ── Sender Address ──
1708    doc.add_paragraph("Walter White");
1709    doc.add_paragraph("Chief Executive Officer");
1710    doc.add_paragraph("Tensorbee");
1711    doc.add_paragraph("123 Innovation Drive, Suite 400");
1712    doc.add_paragraph("San Francisco, CA 94105");
1713    doc.add_paragraph("");
1714    doc.add_paragraph("February 22, 2026");
1715
1716    doc.add_paragraph("");
1717
1718    // ── Recipient ──
1719    doc.add_paragraph("Ms. Sarah Chen");
1720    doc.add_paragraph("VP of Engineering");
1721    doc.add_paragraph("Meridian Dynamics LLC");
1722    doc.add_paragraph("456 Enterprise Boulevard");
1723    doc.add_paragraph("Austin, TX 78701");
1724
1725    doc.add_paragraph("");
1726
1727    // ── Subject ──
1728    {
1729        let mut p = doc.add_paragraph("");
1730        p.add_run("Re: Strategic Technology Partnership — Phase 2 Expansion")
1731            .bold(true);
1732    }
1733
1734    doc.add_paragraph("");
1735
1736    // ── Body ──
1737    doc.add_paragraph("Dear Ms. Chen,");
1738    doc.add_paragraph("");
1739
1740    doc.add_paragraph(
1741        "I am writing to express Tensorbee's enthusiasm for expanding our strategic technology \
1742         partnership with Meridian Dynamics. Following the successful completion of Phase 1, \
1743         which delivered a 40% improvement in your ML inference pipeline throughput, we believe \
1744         the foundation is firmly established for an ambitious Phase 2 engagement.",
1745    )
1746    .first_line_indent(Length::inches(0.5));
1747
1748    doc.add_paragraph(
1749        "During our recent executive review, your team highlighted three priority areas for \
1750         the next phase of collaboration:",
1751    )
1752    .first_line_indent(Length::inches(0.5));
1753
1754    doc.add_numbered_list_item(
1755        "Real-time anomaly detection for your financial transaction monitoring system, \
1756         targeting sub-100ms latency at 50,000 transactions per second.",
1757        0,
1758    );
1759    doc.add_numbered_list_item(
1760        "Federated learning infrastructure to enable model training across your distributed \
1761         data centers without centralizing sensitive financial data.",
1762        0,
1763    );
1764    doc.add_numbered_list_item(
1765        "MLOps automation to reduce your model deployment cycle from the current 5 days \
1766         to under 4 hours.",
1767        0,
1768    );
1769
1770    doc.add_paragraph(
1771        "Our engineering team has prepared a detailed technical proposal addressing each of \
1772         these areas. We have allocated a dedicated team of eight senior engineers, led by \
1773         our CTO, to ensure continuity with the Phase 1 team your organization has already \
1774         built a productive working relationship with.",
1775    )
1776    .first_line_indent(Length::inches(0.5));
1777
1778    doc.add_paragraph(
1779        "I would welcome the opportunity to present our Phase 2 proposal in person. My \
1780         assistant will reach out to coordinate a meeting at your convenience during the \
1781         first week of March.",
1782    )
1783    .first_line_indent(Length::inches(0.5));
1784
1785    doc.add_paragraph("");
1786
1787    doc.add_paragraph("Warm regards,");
1788    doc.add_paragraph("");
1789    doc.add_paragraph("");
1790
1791    // Signature line
1792    doc.add_paragraph("")
1793        .border_bottom(BorderStyle::Single, 4, "4A5568");
1794    {
1795        let mut p = doc.add_paragraph("");
1796        p.add_run("Walter White").bold(true).size(12.0);
1797    }
1798    {
1799        let mut p = doc.add_paragraph("");
1800        p.add_run("Chief Executive Officer, Tensorbee")
1801            .italic(true)
1802            .size(10.0)
1803            .color("666666");
1804    }
1805    {
1806        let mut p = doc.add_paragraph("");
1807        p.add_run("walter@tensorbee.com | +1 (415) 555-0199")
1808            .size(10.0)
1809            .color("888888");
1810    }
1811
1812    doc
1813}

Set a footer from raw XML bytes with associated images.

Source

pub fn set_header_image_with_background( &mut self, image_data: &[u8], image_filename: &str, width: Length, height: Length, bg_color: &str, )

Set the default header to an inline image with a colored background.

Creates a header part where the paragraph has shading fill set to bg_color (hex string, e.g. “000000” for black) and contains the inline image.

Source

pub fn set_first_page_header_image( &mut self, image_data: &[u8], image_filename: &str, width: Length, height: Length, )

Set the first-page header to an inline image.

Source

pub fn add_bullet_list_item(&mut self, text: &str, level: u32) -> Paragraph<'_>

Add a bullet list item at the given indentation level (0-based).

If no bullet list definition exists yet, one is created automatically. Returns a mutable Paragraph for further configuration.

Examples found in repository?
examples/header_banner.rs (lines 108-111)
29fn generate_header_banner_doc(path: &Path) {
30    let mut doc = Document::new();
31
32    // Page setup with extra top margin for the banner
33    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
34    doc.set_margins(
35        Length::twips(2292), // top — extra tall for header banner
36        Length::twips(1440), // right
37        Length::twips(1440), // bottom
38        Length::twips(1440), // left
39    );
40    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
41
42    // Generate a simple logo image (white text on transparent background)
43    let logo_img = create_logo_png(220, 48);
44
45    // ── Dark blue banner ──
46    let banner = build_header_banner_xml(
47        "rId1",
48        &BannerOpts {
49            bg_color: "1A3C6E",
50            banner_width: 7772400, // full page width in EMU (~8.5")
51            banner_height: 969026, // banner height in EMU (~1.06")
52            logo_width: 2011680,   // logo display width (~2.2")
53            logo_height: 438912,   // logo display height (~0.48")
54            logo_x_offset: 295125, // left padding
55            logo_y_offset: 265057, // vertical centering
56        },
57    );
58
59    doc.set_raw_header_with_images(
60        banner.clone(),
61        &[("rId1", &logo_img, "logo.png")],
62        rdocx_oxml::header_footer::HdrFtrType::Default,
63    );
64
65    // Use a different first page header (same banner, different color)
66    doc.set_different_first_page(true);
67    let first_page_banner = build_header_banner_xml(
68        "rId1",
69        &BannerOpts {
70            bg_color: "2E75B6", // lighter blue for cover
71            banner_width: 7772400,
72            banner_height: 969026,
73            logo_width: 2011680,
74            logo_height: 438912,
75            logo_x_offset: 295125,
76            logo_y_offset: 265057,
77        },
78    );
79    doc.set_raw_header_with_images(
80        first_page_banner,
81        &[("rId1", &logo_img, "logo.png")],
82        rdocx_oxml::header_footer::HdrFtrType::First,
83    );
84
85    // Footer
86    doc.set_footer("Confidential — Internal Use Only");
87
88    // ── Page 1: Cover ──
89    doc.add_paragraph("Company Report").style("Heading1");
90
91    doc.add_paragraph(
92        "This document demonstrates a custom header banner built with DrawingML \
93         group shapes. The banner uses a colored rectangle with a logo image overlaid, \
94         positioned at the top of each page.",
95    );
96
97    doc.add_paragraph("");
98
99    doc.add_paragraph("How the Header Banner Works")
100        .style("Heading2");
101
102    doc.add_paragraph(
103        "The header banner is built using set_raw_header_with_images(), which \
104         accepts raw XML and a list of (rel_id, image_data, filename) tuples. \
105         The XML uses a DrawingML group shape (wpg:wgp) containing:",
106    );
107
108    doc.add_bullet_list_item(
109        "A wps:wsp rectangle shape with a solid color fill (the background bar)",
110        0,
111    );
112    doc.add_bullet_list_item(
113        "A pic:pic image element positioned within the group (the logo)",
114        0,
115    );
116    doc.add_bullet_list_item(
117        "The group is wrapped in a wp:anchor element for absolute page positioning",
118        0,
119    );
120
121    doc.add_paragraph("");
122
123    doc.add_paragraph("Customization").style("Heading2");
124
125    doc.add_paragraph(
126        "All dimensions are in EMU (English Metric Units) where 914400 EMU = 1 inch. \
127         You can customize:",
128    );
129
130    doc.add_bullet_list_item("bg_color — any hex color for the rectangle background", 0);
131    doc.add_bullet_list_item("banner_width / banner_height — size of the full banner", 0);
132    doc.add_bullet_list_item("logo_width / logo_height — display size of the logo", 0);
133    doc.add_bullet_list_item(
134        "logo_x_offset / logo_y_offset — logo position within the banner",
135        0,
136    );
137
138    doc.add_paragraph("");
139
140    doc.add_paragraph("Different First Page").style("Heading2");
141
142    doc.add_paragraph(
143        "This page uses a lighter blue banner (first page header). \
144         Subsequent pages use a darker blue banner (default header). \
145         Use set_different_first_page(true) to enable this.",
146    );
147
148    // ── Page 2 ──
149    doc.add_paragraph("").page_break_before(true);
150
151    doc.add_paragraph("Second Page").style("Heading1");
152
153    doc.add_paragraph(
154        "This page shows the default header banner (dark blue). The first page \
155         had a lighter blue banner because we set a different first-page header.",
156    );
157
158    doc.add_paragraph("");
159
160    doc.add_paragraph(
161        "The banner repeats on every page because it is placed in the header part. \
162         You can have different banners for default, first-page, and even-page headers.",
163    );
164
165    doc.set_title("Header Banner Example");
166    doc.set_author("rdocx");
167
168    doc.save(path).unwrap();
169}
More examples
Hide additional examples
examples/generate_all_samples.rs (line 313)
97fn generate_feature_showcase(_samples_dir: &Path) -> Document {
98    let mut doc = Document::new();
99
100    // ── Page Setup & Metadata ──
101    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
102    doc.set_margins(
103        Length::inches(1.0),
104        Length::inches(1.0),
105        Length::inches(1.0),
106        Length::inches(1.0),
107    );
108    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
109    doc.set_gutter(Length::twips(0));
110    doc.set_title("rdocx Feature Showcase");
111    doc.set_author("rdocx Sample Generator");
112    doc.set_subject("Comprehensive feature demonstration");
113    doc.set_keywords("rdocx, docx, rust, sample, showcase");
114
115    // Headers & Footers with different first page
116    doc.set_different_first_page(true);
117    doc.set_first_page_header("rdocx");
118    doc.set_first_page_footer("Feature Showcase — Cover Page");
119    doc.set_header("rdocx Feature Showcase");
120    doc.set_footer("Generated by rdocx");
121
122    // ── COVER PAGE ──
123    let bg = create_sample_png(612, 792, [20, 45, 90]);
124    doc.add_background_image(&bg, "cover_bg.png");
125
126    for _ in 0..3 {
127        doc.add_paragraph("");
128    }
129    {
130        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
131        p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
132    }
133    {
134        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
135        p.add_run("Complete Feature Showcase")
136            .size(28.0)
137            .color("FFFFFF")
138            .italic(true);
139    }
140    doc.add_paragraph("");
141    {
142        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
143        p.add_run("Every feature of the rdocx Rust library")
144            .size(13.0)
145            .color("B0C4DE");
146    }
147    {
148        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
149        p.add_run("demonstrated in a single document.")
150            .size(13.0)
151            .color("B0C4DE");
152    }
153
154    // ── TABLE OF CONTENTS ──
155    doc.add_paragraph("").page_break_before(true);
156    doc.insert_toc(doc.content_count(), 3);
157
158    // ── SECTION 1: TEXT FORMATTING ──
159    doc.add_paragraph("").page_break_before(true);
160    doc.add_paragraph("1. Text Formatting").style("Heading1");
161
162    // Paragraph Alignment
163    doc.add_paragraph("1.1 Paragraph Alignment")
164        .style("Heading2");
165    doc.add_paragraph("Left-aligned paragraph (default).")
166        .alignment(Alignment::Left);
167    doc.add_paragraph("Center-aligned paragraph.")
168        .alignment(Alignment::Center);
169    doc.add_paragraph("Right-aligned paragraph.")
170        .alignment(Alignment::Right);
171    doc.add_paragraph(
172        "Justified paragraph — this text is long enough to demonstrate how justified alignment \
173         distributes extra space across word gaps so lines fill the full width of the text area.",
174    )
175    .alignment(Alignment::Justify);
176
177    doc.add_paragraph("");
178
179    // Run Formatting
180    doc.add_paragraph("1.2 Run Formatting").style("Heading2");
181    {
182        let mut p = doc.add_paragraph("");
183        p.add_run("Bold").bold(true);
184        p.add_run(" | ");
185        p.add_run("Italic").italic(true);
186        p.add_run(" | ");
187        p.add_run("Bold Italic").bold(true).italic(true);
188        p.add_run(" | ");
189        p.add_run("Underline").underline(true);
190        p.add_run(" | ");
191        p.add_run("Strikethrough").strike(true);
192        p.add_run(" | ");
193        p.add_run("Double Strike").double_strike(true);
194    }
195    {
196        let mut p = doc.add_paragraph("");
197        p.add_run("Red").color("FF0000");
198        p.add_run(" | ");
199        p.add_run("Blue").color("0000FF");
200        p.add_run(" | ");
201        p.add_run("Green").color("00AA00");
202        p.add_run(" | ");
203        p.add_run("Highlighted").highlight("FFFF00");
204    }
205    {
206        let mut p = doc.add_paragraph("");
207        p.add_run("8pt").size(8.0);
208        p.add_run(" | ");
209        p.add_run("11pt").size(11.0);
210        p.add_run(" | ");
211        p.add_run("16pt").size(16.0);
212        p.add_run(" | ");
213        p.add_run("24pt").size(24.0);
214    }
215    {
216        let mut p = doc.add_paragraph("");
217        p.add_run("Arial").font("Arial");
218        p.add_run(" | ");
219        p.add_run("Times New Roman").font("Times New Roman");
220        p.add_run(" | ");
221        p.add_run("Courier New").font("Courier New");
222    }
223    {
224        let mut p = doc.add_paragraph("");
225        p.add_run("H");
226        p.add_run("2").subscript();
227        p.add_run("O (subscript) | E=mc");
228        p.add_run("2").superscript();
229        p.add_run(" (superscript)");
230    }
231    {
232        let mut p = doc.add_paragraph("");
233        p.add_run("ALL CAPS").all_caps(true);
234        p.add_run(" | ");
235        p.add_run("Small Caps").small_caps(true);
236        p.add_run(" | ");
237        p.add_run("Expanded").character_spacing(Length::twips(40));
238        p.add_run(" | ");
239        p.add_run("Hidden text (not visible)").hidden(true);
240    }
241
242    doc.add_paragraph("");
243
244    // Underline Styles
245    doc.add_paragraph("1.3 Underline Styles").style("Heading2");
246    {
247        let mut p = doc.add_paragraph("");
248        p.add_run("Single ")
249            .underline_style(rdocx::UnderlineStyle::Single);
250        p.add_run("Double ")
251            .underline_style(rdocx::UnderlineStyle::Double);
252        p.add_run("Thick ")
253            .underline_style(rdocx::UnderlineStyle::Thick);
254        p.add_run("Dotted ")
255            .underline_style(rdocx::UnderlineStyle::Dotted);
256        p.add_run("Dash ")
257            .underline_style(rdocx::UnderlineStyle::Dash);
258        p.add_run("Wave ")
259            .underline_style(rdocx::UnderlineStyle::Wave);
260    }
261
262    // ── SECTION 2: PARAGRAPH FORMATTING ──
263    doc.add_paragraph("").page_break_before(true);
264    doc.add_paragraph("2. Paragraph Formatting")
265        .style("Heading1");
266
267    doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
268    doc.add_paragraph("Paragraph with light green shading")
269        .shading("E2EFDA");
270    doc.add_paragraph("Paragraph with bottom border")
271        .border_bottom(BorderStyle::Single, 6, "2E75B6");
272    doc.add_paragraph("Paragraph with all borders (red)")
273        .border_all(BorderStyle::Single, 4, "FF0000");
274
275    doc.add_paragraph("2.2 Indentation").style("Heading2");
276    doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
277        .indent_left(Length::inches(1.0))
278        .hanging_indent(Length::inches(0.5));
279    doc.add_paragraph("0.5-inch first-line indent on this paragraph")
280        .first_line_indent(Length::inches(0.5));
281    doc.add_paragraph("Both left and right indent (0.75 inches each)")
282        .indent_left(Length::inches(0.75))
283        .indent_right(Length::inches(0.75));
284
285    doc.add_paragraph("2.3 Spacing & Line Height")
286        .style("Heading2");
287    doc.add_paragraph("Extra space before (24pt) and after (12pt)")
288        .space_before(Length::pt(24.0))
289        .space_after(Length::pt(12.0));
290    doc.add_paragraph(
291        "Double line spacing paragraph. This text should have extra vertical space between \
292         lines to demonstrate line_spacing_multiple(2.0).",
293    )
294    .line_spacing_multiple(2.0);
295    doc.add_paragraph("Exact 20pt line spacing (fixed height).")
296        .line_spacing(20.0);
297
298    doc.add_paragraph("2.4 Pagination Controls")
299        .style("Heading2");
300    doc.add_paragraph("keep_with_next — this paragraph stays with the next")
301        .keep_with_next(true);
302    doc.add_paragraph("(This paragraph was kept with the one above.)");
303    doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
304        .keep_together(true);
305    doc.add_paragraph("widow_control — prevents widow/orphan lines")
306        .widow_control(true);
307
308    // ── SECTION 3: LISTS ──
309    doc.add_paragraph("").page_break_before(true);
310    doc.add_paragraph("3. Lists").style("Heading1");
311
312    doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
313    doc.add_bullet_list_item("First item", 0);
314    doc.add_bullet_list_item("Second item", 0);
315    doc.add_bullet_list_item("Nested level 1", 1);
316    doc.add_bullet_list_item("Nested level 2", 2);
317    doc.add_bullet_list_item("Back to level 1", 1);
318    doc.add_bullet_list_item("Third item", 0);
319
320    doc.add_paragraph("");
321
322    doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
323    doc.add_numbered_list_item("First numbered item", 0);
324    doc.add_numbered_list_item("Second numbered item", 0);
325    doc.add_numbered_list_item("Sub-item A", 1);
326    doc.add_numbered_list_item("Sub-item B", 1);
327    doc.add_numbered_list_item("Third numbered item", 0);
328
329    // ── SECTION 4: TAB STOPS ──
330    doc.add_paragraph("");
331    doc.add_paragraph("4. Tab Stops").style("Heading1");
332
333    doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
334    doc.add_paragraph("Left\tCenter\tRight\tDecimal")
335        .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
336        .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
337        .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
338        .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
339
340    doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
341    doc.add_paragraph("Item\t\tPrice")
342        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
343        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
344        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
345    doc.add_paragraph("Widget\t\t$19.99")
346        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
347        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
348        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
349    doc.add_paragraph("Gadget\t\t$249.50")
350        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
351        .add_tab_stop_with_leader(
352            TabAlignment::Right,
353            Length::inches(4.0),
354            TabLeader::Underscore,
355        )
356        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
357
358    // ── SECTION 5: TABLES ──
359    doc.add_paragraph("").page_break_before(true);
360    doc.add_paragraph("5. Tables").style("Heading1");
361
362    doc.add_paragraph("5.1 Basic Table").style("Heading2");
363    {
364        let mut tbl = doc
365            .add_table(4, 3)
366            .borders(BorderStyle::Single, 4, "000000");
367        for col in 0..3 {
368            tbl.cell(0, col).unwrap().shading("2E75B6");
369        }
370        tbl.cell(0, 0).unwrap().set_text("Name");
371        tbl.cell(0, 1).unwrap().set_text("Role");
372        tbl.cell(0, 2).unwrap().set_text("Location");
373        tbl.cell(1, 0).unwrap().set_text("Walter White");
374        tbl.cell(1, 1).unwrap().set_text("CEO");
375        tbl.cell(1, 2).unwrap().set_text("Albuquerque");
376        tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
377        tbl.cell(2, 1).unwrap().set_text("CTO");
378        tbl.cell(2, 2).unwrap().set_text("Remote");
379        tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
380        tbl.cell(3, 1).unwrap().set_text("Security");
381        tbl.cell(3, 2).unwrap().set_text("Washington");
382    }
383
384    doc.add_paragraph("");
385
386    doc.add_paragraph("5.2 Column Span & Cell Shading")
387        .style("Heading2");
388    {
389        let mut tbl = doc
390            .add_table(3, 4)
391            .borders(BorderStyle::Single, 4, "000000")
392            .width_pct(100.0);
393        tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
394        tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
395        tbl.cell(1, 0).unwrap().set_text("Region");
396        tbl.cell(1, 0).unwrap().shading("D6E4F0");
397        tbl.cell(1, 1).unwrap().set_text("Q1");
398        tbl.cell(1, 1).unwrap().shading("D6E4F0");
399        tbl.cell(1, 2).unwrap().set_text("Q2");
400        tbl.cell(1, 2).unwrap().shading("D6E4F0");
401        tbl.cell(1, 3).unwrap().set_text("Total");
402        tbl.cell(1, 3).unwrap().shading("D6E4F0");
403        tbl.cell(2, 0).unwrap().set_text("Americas");
404        tbl.cell(2, 1).unwrap().set_text("$2.4M");
405        tbl.cell(2, 2).unwrap().set_text("$2.7M");
406        tbl.cell(2, 3).unwrap().set_text("$5.1M");
407    }
408
409    doc.add_paragraph("");
410
411    doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
412    {
413        let mut tbl = doc
414            .add_table(4, 3)
415            .borders(BorderStyle::Single, 4, "000000");
416        tbl.cell(0, 0).unwrap().set_text("Category");
417        tbl.cell(0, 0).unwrap().shading("E2EFDA");
418        tbl.cell(0, 1).unwrap().set_text("Item");
419        tbl.cell(0, 1).unwrap().shading("E2EFDA");
420        tbl.cell(0, 2).unwrap().set_text("Price");
421        tbl.cell(0, 2).unwrap().shading("E2EFDA");
422        tbl.cell(1, 0).unwrap().set_text("Hardware");
423        tbl.cell(1, 0).unwrap().v_merge_restart();
424        tbl.cell(1, 1).unwrap().set_text("Laptop");
425        tbl.cell(1, 2).unwrap().set_text("$1,200");
426        tbl.cell(2, 0).unwrap().v_merge_continue();
427        tbl.cell(2, 1).unwrap().set_text("Monitor");
428        tbl.cell(2, 2).unwrap().set_text("$450");
429        tbl.cell(3, 0).unwrap().set_text("Software");
430        tbl.cell(3, 1).unwrap().set_text("IDE License");
431        tbl.cell(3, 2).unwrap().set_text("$200/yr");
432    }
433
434    doc.add_paragraph("");
435
436    doc.add_paragraph("5.4 Nested Table").style("Heading2");
437    {
438        let mut tbl = doc
439            .add_table(2, 2)
440            .borders(BorderStyle::Single, 6, "2E75B6");
441        tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
442        tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
443        tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
444        {
445            let mut cell = tbl.cell(1, 1).unwrap();
446            cell.set_text("Nested table below:");
447            let mut nested = cell
448                .add_table(2, 2)
449                .borders(BorderStyle::Single, 2, "FF6600");
450            nested.cell(0, 0).unwrap().set_text("A");
451            nested.cell(0, 1).unwrap().set_text("B");
452            nested.cell(1, 0).unwrap().set_text("C");
453            nested.cell(1, 1).unwrap().set_text("D");
454        }
455    }
456
457    doc.add_paragraph("");
458
459    doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
460        .style("Heading2");
461    {
462        let mut tbl = doc
463            .add_table(2, 3)
464            .borders(BorderStyle::Single, 4, "666666");
465        tbl.row(0).unwrap().height(Length::pt(50.0)).header();
466        tbl.cell(0, 0).unwrap().set_text("Top");
467        tbl.cell(0, 0)
468            .unwrap()
469            .vertical_alignment(VerticalAlignment::Top)
470            .shading("FFF2CC");
471        tbl.cell(0, 1).unwrap().set_text("Center");
472        tbl.cell(0, 1)
473            .unwrap()
474            .vertical_alignment(VerticalAlignment::Center)
475            .shading("D9E2F3");
476        tbl.cell(0, 2).unwrap().set_text("Bottom");
477        tbl.cell(0, 2)
478            .unwrap()
479            .vertical_alignment(VerticalAlignment::Bottom)
480            .shading("E2EFDA");
481        tbl.row(1).unwrap().cant_split();
482        tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
483        tbl.cell(1, 0).unwrap().no_wrap();
484        tbl.cell(1, 1).unwrap().set_text("Fixed width");
485        tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
486        tbl.cell(1, 2).unwrap().set_text("Normal");
487    }
488
489    // ── SECTION 6: IMAGES ──
490    doc.add_paragraph("").page_break_before(true);
491    doc.add_paragraph("6. Images").style("Heading1");
492
493    doc.add_paragraph("6.1 Inline Image").style("Heading2");
494    doc.add_paragraph("A blue gradient image below:");
495    let img = create_sample_png(200, 50, [0, 80, 200]);
496    doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
497
498    doc.add_paragraph("");
499    doc.add_paragraph("6.2 Header Image").style("Heading2");
500    let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
501    doc.set_header_image(
502        &hdr_img,
503        "header_logo.png",
504        Length::inches(2.0),
505        Length::inches(0.2),
506    );
507    doc.add_paragraph("The header now contains an inline image (check the top of this page).");
508
509    doc.add_paragraph("");
510    doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
511
512    // ── SECTION 7: CONTENT MANIPULATION ──
513    doc.add_paragraph("").page_break_before(true);
514    doc.add_paragraph("7. Content Manipulation")
515        .style("Heading1");
516
517    doc.add_paragraph("7.1 Placeholder Replacement")
518        .style("Heading2");
519    doc.add_paragraph("Customer: {{customer}}");
520    doc.add_paragraph("Date: {{date}}");
521    doc.add_paragraph("Reference: {{ref_number}}");
522    {
523        let mut tbl = doc
524            .add_table(2, 2)
525            .borders(BorderStyle::Single, 4, "000000");
526        tbl.cell(0, 0).unwrap().set_text("Project");
527        tbl.cell(0, 0).unwrap().shading("D6E4F0");
528        tbl.cell(0, 1).unwrap().set_text("{{project}}");
529        tbl.cell(1, 0).unwrap().set_text("Status");
530        tbl.cell(1, 0).unwrap().shading("D6E4F0");
531        tbl.cell(1, 1).unwrap().set_text("{{status}}");
532    }
533
534    let mut replacements = HashMap::new();
535    replacements.insert("{{customer}}", "Tensorbee Inc.");
536    replacements.insert("{{date}}", "February 22, 2026");
537    replacements.insert("{{ref_number}}", "TB-2026-001");
538    replacements.insert("{{project}}", "Infrastructure Upgrade");
539    replacements.insert("{{status}}", "In Progress");
540    let n = doc.replace_all(&replacements);
541    doc.add_paragraph(&format!("({n} placeholders replaced above)"));
542
543    doc.add_paragraph("");
544
545    doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
546    doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
547    let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
548    doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
549
550    doc.add_paragraph("");
551
552    doc.add_paragraph("7.3 Content Insertion").style("Heading2");
553    doc.add_paragraph("Section A: First content.");
554    doc.add_paragraph("Section C: Third content.");
555    if let Some(idx) = doc.find_content_index("Section C") {
556        doc.insert_paragraph(
557            idx,
558            "Section B: Inserted between A and C via find_content_index().",
559        );
560    }
561
562    // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
563    doc.add_paragraph("").section_break(SectionBreak::NextPage);
564    doc.add_paragraph("8. Section Breaks & Orientation")
565        .style("Heading1");
566    doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
567
568    {
569        let mut tbl = doc
570            .add_table(3, 7)
571            .borders(BorderStyle::Single, 4, "2E75B6");
572        let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
573        for (c, h) in headers.iter().enumerate() {
574            tbl.cell(0, c).unwrap().set_text(h);
575            tbl.cell(0, c).unwrap().shading("2E75B6");
576        }
577        let data = [
578            [
579                "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
580            ],
581            [
582                "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
583            ],
584        ];
585        for (r, row) in data.iter().enumerate() {
586            for (c, v) in row.iter().enumerate() {
587                tbl.cell(r + 1, c).unwrap().set_text(v);
588            }
589        }
590    }
591
592    doc.add_paragraph("")
593        .section_break(SectionBreak::NextPage)
594        .section_landscape();
595
596    // ── SECTION 9: CUSTOM STYLES ──
597    doc.add_paragraph("9. Custom Styles").style("Heading1");
598    doc.add_style(
599        StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
600            .based_on("Normal")
601            .paragraph_properties(rdocx_oxml::properties::CT_PPr {
602                shading: Some(rdocx_oxml::properties::CT_Shd {
603                    val: "clear".to_string(),
604                    color: None,
605                    fill: Some("FFF2CC".to_string()),
606                }),
607                ..Default::default()
608            })
609            .run_properties(rdocx_oxml::properties::CT_RPr {
610                bold: Some(true),
611                color: Some("C45911".to_string()),
612                ..Default::default()
613            }),
614    );
615    doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
616        .style("CustomHighlight");
617
618    doc.add_paragraph("");
619
620    // ── SECTION 10: DOCUMENT INTELLIGENCE ──
621    doc.add_paragraph("10. Document Intelligence API")
622        .style("Heading1");
623
624    let wc = doc.word_count();
625    let hc = doc.headings().len();
626    let ic = doc.images().len();
627    let lc = doc.links().len();
628    doc.add_paragraph(&format!("Word count: {wc}"));
629    doc.add_paragraph(&format!("Heading count: {hc}"));
630    doc.add_paragraph(&format!("Image count: {ic}"));
631    doc.add_paragraph(&format!("Link count: {lc}"));
632
633    let outline = doc.document_outline();
634    doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
635
636    let issues = doc.audit_accessibility();
637    doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
638    for issue in &issues {
639        doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
640    }
641
642    // ── SECTION 11: DOCUMENT MERGING ──
643    doc.add_paragraph("");
644    doc.add_paragraph("11. Document Merging").style("Heading1");
645    {
646        let mut other = Document::new();
647        other.add_paragraph("This paragraph was merged from another document using append().");
648        doc.append(&other);
649    }
650
651    doc.add_paragraph("");
652
653    // ── SUMMARY ──
654    doc.add_paragraph("Summary of Demonstrated Features")
655        .style("Heading1");
656    let features = [
657        "Page setup: size, margins, header/footer distance, gutter",
658        "Document metadata: title, author, subject, keywords",
659        "Headers and footers: text, images, different first page",
660        "Background images (full-page behind text)",
661        "Table of Contents generation with bookmarks",
662        "Text formatting: bold, italic, underline styles, strike, color, size, font",
663        "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
664        "Paragraph formatting: alignment, borders, shading, spacing, indentation",
665        "Pagination controls: keep-with-next, keep-together, widow control",
666        "Bullet and numbered lists with nesting",
667        "Tab stops with dot/underscore/hyphen leaders",
668        "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
669        "Row properties: header rows, exact height, cant-split",
670        "Cell properties: width, no-wrap, vertical alignment",
671        "Inline images and header images",
672        "Placeholder replacement (single and batch)",
673        "Regex-based find and replace",
674        "Content insertion at specific positions",
675        "Section breaks with mixed portrait/landscape",
676        "Custom paragraph and character styles",
677        "Document intelligence: word count, headings, outline, images, links",
678        "Accessibility audit",
679        "Document merging (append)",
680        "Export: DOCX, PDF, HTML, Markdown",
681    ];
682    for f in &features {
683        doc.add_bullet_list_item(f, 0);
684    }
685    doc.add_paragraph("");
686    doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
687        .alignment(Alignment::Center)
688        .shading("E2EFDA")
689        .border_all(BorderStyle::Single, 2, "00AA00");
690
691    doc
692}
693
694// =============================================================================
695// 2. PROPOSAL DOCUMENT — Deep navy + gold scheme
696// =============================================================================
697fn generate_proposal(_samples_dir: &Path) -> Document {
698    let mut doc = Document::new();
699
700    // Colors: Navy #1B2A4A, Gold #C5922E, Light #F4F1EB
701    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
702    doc.set_margins(
703        Length::inches(1.0),
704        Length::inches(1.0),
705        Length::inches(1.0),
706        Length::inches(1.0),
707    );
708    doc.set_title("AI Platform Modernization Proposal");
709    doc.set_author("Walter White");
710    doc.set_subject("Technology Proposal");
711    doc.set_keywords("proposal, AI, modernization, Tensorbee");
712
713    // Banner header
714    let logo_img = create_logo_png(220, 48);
715    let banner = build_header_banner_xml(
716        "rId1",
717        &BannerOpts {
718            bg_color: "1B2A4A",
719            banner_width: 7772400,
720            banner_height: 914400,
721            logo_width: 2011680,
722            logo_height: 438912,
723            logo_x_offset: 295125,
724            logo_y_offset: 237744,
725        },
726    );
727    doc.set_raw_header_with_images(
728        banner,
729        &[("rId1", &logo_img, "logo.png")],
730        rdocx_oxml::header_footer::HdrFtrType::Default,
731    );
732    doc.set_different_first_page(true);
733    let cover_banner = build_header_banner_xml(
734        "rId1",
735        &BannerOpts {
736            bg_color: "C5922E",
737            banner_width: 7772400,
738            banner_height: 914400,
739            logo_width: 2011680,
740            logo_height: 438912,
741            logo_x_offset: 295125,
742            logo_y_offset: 237744,
743        },
744    );
745    doc.set_raw_header_with_images(
746        cover_banner,
747        &[("rId1", &logo_img, "logo.png")],
748        rdocx_oxml::header_footer::HdrFtrType::First,
749    );
750    doc.set_margins(
751        Length::twips(2292),
752        Length::twips(1440),
753        Length::twips(1440),
754        Length::twips(1440),
755    );
756    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
757    doc.set_footer("Tensorbee — Confidential");
758
759    // Custom styles
760    doc.add_style(
761        StyleBuilder::paragraph("ProposalTitle", "Proposal Title")
762            .based_on("Normal")
763            .run_properties(rdocx_oxml::properties::CT_RPr {
764                bold: Some(true),
765                font_ascii: Some("Georgia".to_string()),
766                font_hansi: Some("Georgia".to_string()),
767                sz: Some(rdocx_oxml::HalfPoint(56)), // half-points → 28pt
768                color: Some("1B2A4A".to_string()),
769                ..Default::default()
770            }),
771    );
772
773    // ── Cover Page ──
774    for _ in 0..4 {
775        doc.add_paragraph("");
776    }
777    doc.add_paragraph("AI Platform Modernization")
778        .style("ProposalTitle")
779        .alignment(Alignment::Center);
780    doc.add_paragraph("")
781        .alignment(Alignment::Center)
782        .border_bottom(BorderStyle::Single, 8, "C5922E");
783    {
784        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
785        p.add_run("Prepared for Global Financial Corp.")
786            .size(14.0)
787            .color("666666")
788            .italic(true);
789    }
790    {
791        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
792        p.add_run("Prepared by: Walter White, CEO — Tensorbee")
793            .size(12.0)
794            .color("888888");
795    }
796    {
797        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
798        p.add_run("February 22, 2026").size(12.0).color("888888");
799    }
800
801    // ── TOC ──
802    doc.add_paragraph("").page_break_before(true);
803    doc.add_paragraph("Table of Contents").style("Heading1");
804    // We'll add a manual-style TOC since the insert_toc goes at a specific position
805    doc.insert_toc(doc.content_count(), 2);
806
807    // ── Executive Summary ──
808    doc.add_paragraph("").page_break_before(true);
809    doc.add_paragraph("Executive Summary").style("Heading1");
810    doc.add_paragraph(
811        "Tensorbee proposes a comprehensive modernization of Global Financial Corp's AI platform, \
812         transitioning from legacy batch-processing systems to a real-time inference architecture. \
813         This transformation will reduce model deployment time from weeks to hours, improve \
814         prediction accuracy by an estimated 23%, and deliver $4.2M in annual operational savings.",
815    )
816    .first_line_indent(Length::inches(0.3));
817    doc.add_paragraph(
818        "The project spans three phases over 18 months, with a total investment of $2.8M. \
819         Tensorbee brings deep expertise in ML infrastructure, having successfully completed \
820         similar transformations for three Fortune 500 financial institutions.",
821    )
822    .first_line_indent(Length::inches(0.3));
823
824    // Key metrics table
825    doc.add_paragraph("");
826    {
827        let mut tbl = doc
828            .add_table(5, 2)
829            .borders(BorderStyle::Single, 4, "1B2A4A")
830            .width_pct(80.0);
831        tbl.cell(0, 0).unwrap().set_text("Key Metric");
832        tbl.cell(0, 0).unwrap().shading("1B2A4A");
833        tbl.cell(0, 1).unwrap().set_text("Value");
834        tbl.cell(0, 1).unwrap().shading("1B2A4A");
835        let rows = [
836            ("Total Investment", "$2.8M"),
837            ("Annual Savings", "$4.2M"),
838            ("ROI (Year 1)", "150%"),
839            ("Timeline", "18 months"),
840        ];
841        for (i, (k, v)) in rows.iter().enumerate() {
842            tbl.cell(i + 1, 0).unwrap().set_text(k);
843            if i % 2 == 0 {
844                tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
845            }
846            tbl.cell(i + 1, 1).unwrap().set_text(v);
847            if i % 2 == 0 {
848                tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
849            }
850        }
851    }
852
853    // ── Problem Statement ──
854    doc.add_paragraph("").page_break_before(true);
855    doc.add_paragraph("Problem Statement").style("Heading1");
856    doc.add_paragraph(
857        "Global Financial Corp's current AI infrastructure faces three critical challenges:",
858    );
859    doc.add_numbered_list_item("Deployment Latency — New models take 3-4 weeks to deploy to production, compared to the industry benchmark of 2-3 days.", 0);
860    doc.add_numbered_list_item("Scalability Constraints — The batch processing architecture cannot handle real-time inference demands during peak trading hours.", 0);
861    doc.add_numbered_list_item("Technical Debt — Legacy Python 2.7 codebases and manual deployment scripts create reliability risks and slow iteration speed.", 0);
862
863    // ── Proposed Solution ──
864    doc.add_paragraph("").page_break_before(true);
865    doc.add_paragraph("Proposed Solution").style("Heading1");
866
867    doc.add_paragraph("Phase 1: Foundation (Months 1-6)")
868        .style("Heading2");
869    doc.add_bullet_list_item("Deploy Kubernetes-based ML serving infrastructure", 0);
870    doc.add_bullet_list_item("Implement CI/CD pipeline for model deployment", 0);
871    doc.add_bullet_list_item("Migrate top 5 production models to new platform", 0);
872
873    doc.add_paragraph("Phase 2: Expansion (Months 7-12)")
874        .style("Heading2");
875    doc.add_bullet_list_item(
876        "Real-time feature engineering pipeline (Apache Kafka + Flink)",
877        0,
878    );
879    doc.add_bullet_list_item("A/B testing framework for model variants", 0);
880    doc.add_bullet_list_item("Automated model monitoring and drift detection", 0);
881
882    doc.add_paragraph("Phase 3: Optimization (Months 13-18)")
883        .style("Heading2");
884    doc.add_bullet_list_item("GPU inference optimization (TensorRT/ONNX Runtime)", 0);
885    doc.add_bullet_list_item("Multi-region deployment for latency reduction", 0);
886    doc.add_bullet_list_item(
887        "Self-service model deployment portal for data science teams",
888        0,
889    );
890
891    // ── Budget ──
892    doc.add_paragraph("Budget Breakdown").style("Heading1");
893    {
894        let mut tbl = doc
895            .add_table(6, 3)
896            .borders(BorderStyle::Single, 4, "1B2A4A")
897            .width_pct(100.0);
898        tbl.cell(0, 0).unwrap().set_text("Category");
899        tbl.cell(0, 0).unwrap().shading("1B2A4A");
900        tbl.cell(0, 1).unwrap().set_text("Cost");
901        tbl.cell(0, 1).unwrap().shading("1B2A4A");
902        tbl.cell(0, 2).unwrap().set_text("Notes");
903        tbl.cell(0, 2).unwrap().shading("1B2A4A");
904        let items = [
905            (
906                "Infrastructure",
907                "$450,000",
908                "Cloud compute + GPU instances",
909            ),
910            ("Engineering Services", "$1,600,000", "12 FTE x 18 months"),
911            (
912                "Software Licenses",
913                "$350,000",
914                "Kafka, monitoring, CI/CD tools",
915            ),
916            (
917                "Training & Enablement",
918                "$200,000",
919                "Team training, documentation",
920            ),
921            ("Contingency (10%)", "$200,000", "Risk buffer"),
922        ];
923        for (i, (cat, cost, note)) in items.iter().enumerate() {
924            tbl.cell(i + 1, 0).unwrap().set_text(cat);
925            tbl.cell(i + 1, 1).unwrap().set_text(cost);
926            tbl.cell(i + 1, 2).unwrap().set_text(note);
927            if i % 2 == 0 {
928                tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
929                tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
930                tbl.cell(i + 1, 2).unwrap().shading("F4F1EB");
931            }
932        }
933    }
934
935    // ── Closing ──
936    doc.add_paragraph("");
937    doc.add_paragraph("Next Steps").style("Heading1");
938    doc.add_numbered_list_item(
939        "Schedule technical deep-dive with GFC engineering team (Week 1)",
940        0,
941    );
942    doc.add_numbered_list_item("Finalize scope and sign SOW (Week 2-3)", 0);
943    doc.add_numbered_list_item("Kick off Phase 1 with joint planning session (Week 4)", 0);
944
945    doc.add_paragraph("");
946    {
947        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
948        p.add_run("Walter White")
949            .bold(true)
950            .size(14.0)
951            .color("1B2A4A");
952    }
953    {
954        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
955        p.add_run("CEO, Tensorbee | walter@tensorbee.com")
956            .size(10.0)
957            .color("888888");
958    }
959
960    doc
961}
962
963// =============================================================================
964// 3. QUOTE / BILL OF MATERIALS — Teal + orange scheme
965// =============================================================================
966fn generate_quote(_samples_dir: &Path) -> Document {
967    let mut doc = Document::new();
968
969    // Colors: Teal #008B8B, Dark #1A3C3C, Orange #E07020, Light #F0F8F8
970    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
971    doc.set_margins(
972        Length::inches(0.75),
973        Length::inches(0.75),
974        Length::inches(0.75),
975        Length::inches(0.75),
976    );
977    doc.set_title("Quotation QT-2026-0147");
978    doc.set_author("Walter White");
979    doc.set_keywords("quote, BOM, Tensorbee");
980
981    doc.set_header("Tensorbee — Quotation");
982    doc.set_footer("QT-2026-0147 | Page");
983
984    // ── Header Block ──
985    {
986        let mut p = doc.add_paragraph("").alignment(Alignment::Left);
987        p.add_run("TENSORBEE")
988            .bold(true)
989            .size(28.0)
990            .color("008B8B")
991            .font("Helvetica");
992    }
993    doc.add_paragraph("123 Innovation Drive, Suite 400")
994        .alignment(Alignment::Left);
995    doc.add_paragraph("San Francisco, CA 94105 | +1 (415) 555-0199")
996        .alignment(Alignment::Left);
997    doc.add_paragraph("")
998        .border_bottom(BorderStyle::Single, 8, "008B8B");
999
1000    // Quote meta
1001    doc.add_paragraph("");
1002    {
1003        let mut tbl = doc.add_table(4, 4).width_pct(100.0);
1004        tbl.cell(0, 0).unwrap().set_text("QUOTATION");
1005        tbl.cell(0, 0).unwrap().shading("008B8B").grid_span(2);
1006        tbl.cell(0, 2).unwrap().set_text("DATE");
1007        tbl.cell(0, 2).unwrap().shading("008B8B");
1008        tbl.cell(0, 3).unwrap().set_text("VALID UNTIL");
1009        tbl.cell(0, 3).unwrap().shading("008B8B");
1010        tbl.cell(1, 0).unwrap().set_text("Quote #:");
1011        tbl.cell(1, 1).unwrap().set_text("QT-2026-0147");
1012        tbl.cell(1, 2).unwrap().set_text("Feb 22, 2026");
1013        tbl.cell(1, 3).unwrap().set_text("Mar 22, 2026");
1014        tbl.cell(2, 0).unwrap().set_text("Prepared by:");
1015        tbl.cell(2, 1).unwrap().set_text("Walter White");
1016        tbl.cell(2, 2).unwrap().set_text("Payment:");
1017        tbl.cell(2, 3).unwrap().set_text("Net 30");
1018        tbl.cell(3, 0).unwrap().set_text("Customer:");
1019        tbl.cell(3, 1).unwrap().set_text("Meridian Dynamics LLC");
1020        tbl.cell(3, 2).unwrap().set_text("Currency:");
1021        tbl.cell(3, 3).unwrap().set_text("USD");
1022    }
1023
1024    doc.add_paragraph("");
1025
1026    // ── Bill of Materials ──
1027    {
1028        let mut p = doc.add_paragraph("");
1029        p.add_run("Bill of Materials")
1030            .bold(true)
1031            .size(16.0)
1032            .color("1A3C3C");
1033    }
1034    doc.add_paragraph("");
1035
1036    {
1037        let mut tbl = doc
1038            .add_table(10, 6)
1039            .borders(BorderStyle::Single, 2, "008B8B")
1040            .width_pct(100.0)
1041            .layout_fixed();
1042        // Headers
1043        let hdrs = [
1044            "#",
1045            "Part Number",
1046            "Description",
1047            "Qty",
1048            "Unit Price",
1049            "Total",
1050        ];
1051        for (c, h) in hdrs.iter().enumerate() {
1052            tbl.cell(0, c).unwrap().set_text(h);
1053            tbl.cell(0, c).unwrap().shading("008B8B");
1054        }
1055
1056        let items: Vec<(&str, &str, &str, &str, &str)> = vec![
1057            (
1058                "TB-GPU-A100",
1059                "NVIDIA A100 80GB GPU",
1060                "4",
1061                "$12,500.00",
1062                "$50,000.00",
1063            ),
1064            (
1065                "TB-SRV-R750",
1066                "Dell R750xa Server Chassis",
1067                "2",
1068                "$8,200.00",
1069                "$16,400.00",
1070            ),
1071            (
1072                "TB-RAM-256G",
1073                "256GB DDR5 ECC Memory Module",
1074                "8",
1075                "$890.00",
1076                "$7,120.00",
1077            ),
1078            (
1079                "TB-SSD-3840",
1080                "3.84TB NVMe U.2 SSD",
1081                "8",
1082                "$1,150.00",
1083                "$9,200.00",
1084            ),
1085            (
1086                "TB-NET-CX7",
1087                "ConnectX-7 200GbE NIC",
1088                "4",
1089                "$1,800.00",
1090                "$7,200.00",
1091            ),
1092            (
1093                "TB-SW-48P",
1094                "48-Port 100GbE Switch",
1095                "1",
1096                "$22,000.00",
1097                "$22,000.00",
1098            ),
1099            (
1100                "TB-CAB-RACK",
1101                "42U Server Rack + PDU",
1102                "1",
1103                "$4,500.00",
1104                "$4,500.00",
1105            ),
1106            (
1107                "TB-SVC-INST",
1108                "Installation & Configuration",
1109                "1",
1110                "$8,500.00",
1111                "$8,500.00",
1112            ),
1113            (
1114                "TB-SVC-SUPP",
1115                "3-Year Premium Support",
1116                "1",
1117                "$15,000.00",
1118                "$15,000.00",
1119            ),
1120        ];
1121
1122        for (i, (pn, desc, qty, unit, total)) in items.iter().enumerate() {
1123            let row = i + 1;
1124            tbl.cell(row, 0).unwrap().set_text(&format!("{}", i + 1));
1125            tbl.cell(row, 1).unwrap().set_text(pn);
1126            tbl.cell(row, 2).unwrap().set_text(desc);
1127            tbl.cell(row, 3).unwrap().set_text(qty);
1128            tbl.cell(row, 4).unwrap().set_text(unit);
1129            tbl.cell(row, 5).unwrap().set_text(total);
1130            if i % 2 == 0 {
1131                for c in 0..6 {
1132                    tbl.cell(row, c).unwrap().shading("F0F8F8");
1133                }
1134            }
1135        }
1136    }
1137
1138    doc.add_paragraph("");
1139
1140    // ── Totals ──
1141    {
1142        let mut tbl = doc
1143            .add_table(4, 2)
1144            .borders(BorderStyle::Single, 2, "008B8B")
1145            .width(Length::inches(3.5))
1146            .alignment(Alignment::Right);
1147        tbl.cell(0, 0).unwrap().set_text("Subtotal");
1148        tbl.cell(0, 1).unwrap().set_text("$139,920.00");
1149        tbl.cell(1, 0).unwrap().set_text("Shipping & Handling");
1150        tbl.cell(1, 1).unwrap().set_text("$2,500.00");
1151        tbl.cell(2, 0).unwrap().set_text("Tax (8.625%)");
1152        tbl.cell(2, 1).unwrap().set_text("$12,068.10");
1153        tbl.cell(3, 0).unwrap().set_text("TOTAL");
1154        tbl.cell(3, 0).unwrap().shading("E07020");
1155        tbl.cell(3, 1).unwrap().set_text("$154,488.10");
1156        tbl.cell(3, 1).unwrap().shading("E07020");
1157    }
1158
1159    doc.add_paragraph("");
1160
1161    // ── Terms & Conditions ──
1162    {
1163        let mut p = doc.add_paragraph("");
1164        p.add_run("Terms & Conditions")
1165            .bold(true)
1166            .size(14.0)
1167            .color("1A3C3C");
1168    }
1169    doc.add_numbered_list_item(
1170        "This quotation is valid for 30 calendar days from the date of issue.",
1171        0,
1172    );
1173    doc.add_numbered_list_item(
1174        "All prices are in USD and exclusive of applicable taxes unless stated.",
1175        0,
1176    );
1177    doc.add_numbered_list_item("Standard lead time is 4-6 weeks from PO receipt.", 0);
1178    doc.add_numbered_list_item(
1179        "Payment terms: Net 30 from invoice date. 2% discount for payment within 10 days.",
1180        0,
1181    );
1182    doc.add_numbered_list_item(
1183        "Warranty: 3-year manufacturer warranty on all hardware components.",
1184        0,
1185    );
1186    doc.add_numbered_list_item(
1187        "Returns subject to 15% restocking fee if initiated after 14 days.",
1188        0,
1189    );
1190
1191    doc.add_paragraph("");
1192    doc.add_paragraph("")
1193        .border_bottom(BorderStyle::Single, 4, "008B8B");
1194    doc.add_paragraph("");
1195    {
1196        let mut p = doc.add_paragraph("");
1197        p.add_run("Accepted by: ").bold(true);
1198        p.add_run("___________________________ Date: ___________");
1199    }
1200    {
1201        let mut p = doc.add_paragraph("");
1202        p.add_run("Print Name: ").bold(true);
1203        p.add_run("___________________________ Title: ___________");
1204    }
1205
1206    doc
1207}
1208
1209// =============================================================================
1210// 4. INVOICE — Crimson + charcoal scheme
1211// =============================================================================
1212fn generate_invoice(_samples_dir: &Path) -> Document {
1213    let mut doc = Document::new();
1214
1215    // Colors: Crimson #B22222, Charcoal #333333, Light #FAF0F0
1216    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1217    doc.set_margins(
1218        Length::inches(0.75),
1219        Length::inches(0.75),
1220        Length::inches(0.75),
1221        Length::inches(0.75),
1222    );
1223    doc.set_title("Invoice INV-2026-0392");
1224    doc.set_author("Walter White");
1225    doc.set_keywords("invoice, Tensorbee");
1226
1227    doc.set_footer("Tensorbee — Thank you for your business!");
1228
1229    // ── Company & Invoice Header ──
1230    {
1231        let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1232        // Company name left, INVOICE right
1233        {
1234            let mut cell = tbl.cell(0, 0).unwrap();
1235            let mut p = cell.add_paragraph("");
1236            p.add_run("TENSORBEE")
1237                .bold(true)
1238                .size(32.0)
1239                .color("B22222")
1240                .font("Helvetica");
1241        }
1242        {
1243            let mut cell = tbl.cell(0, 1).unwrap();
1244            let mut p = cell.add_paragraph("");
1245            p.add_run("INVOICE").bold(true).size(32.0).color("333333");
1246        }
1247        tbl.cell(1, 0)
1248            .unwrap()
1249            .set_text("123 Innovation Drive, Suite 400");
1250        tbl.cell(1, 1).unwrap().set_text("Invoice #: INV-2026-0392");
1251        tbl.cell(2, 0).unwrap().set_text("San Francisco, CA 94105");
1252        tbl.cell(2, 1).unwrap().set_text("Date: February 22, 2026");
1253        tbl.cell(3, 0).unwrap().set_text("walter@tensorbee.com");
1254        tbl.cell(3, 1).unwrap().set_text("Due Date: March 24, 2026");
1255    }
1256
1257    doc.add_paragraph("")
1258        .border_bottom(BorderStyle::Thick, 8, "B22222");
1259    doc.add_paragraph("");
1260
1261    // ── Bill To / Ship To ──
1262    {
1263        let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1264        tbl.cell(0, 0).unwrap().set_text("BILL TO");
1265        tbl.cell(0, 0).unwrap().shading("B22222");
1266        tbl.cell(0, 1).unwrap().set_text("SHIP TO");
1267        tbl.cell(0, 1).unwrap().shading("B22222");
1268        tbl.cell(1, 0).unwrap().set_text("Meridian Dynamics LLC");
1269        tbl.cell(1, 1).unwrap().set_text("Meridian Dynamics LLC");
1270        tbl.cell(2, 0).unwrap().set_text("456 Enterprise Blvd");
1271        tbl.cell(2, 1).unwrap().set_text("Attn: Server Room B");
1272        tbl.cell(3, 0).unwrap().set_text("Austin, TX 78701");
1273        tbl.cell(3, 1)
1274            .unwrap()
1275            .set_text("456 Enterprise Blvd, Austin, TX 78701");
1276    }
1277
1278    doc.add_paragraph("");
1279
1280    // ── Line Items ──
1281    {
1282        let mut tbl = doc
1283            .add_table(8, 5)
1284            .borders(BorderStyle::Single, 2, "B22222")
1285            .width_pct(100.0);
1286        let hdrs = ["Description", "Qty", "Unit Price", "Tax", "Amount"];
1287        for (c, h) in hdrs.iter().enumerate() {
1288            tbl.cell(0, c).unwrap().set_text(h);
1289            tbl.cell(0, c).unwrap().shading("B22222");
1290        }
1291        let items = [
1292            (
1293                "ML Infrastructure Setup — Phase 1",
1294                "1",
1295                "$45,000.00",
1296                "$3,881.25",
1297                "$48,881.25",
1298            ),
1299            (
1300                "Data Pipeline Development (160 hrs)",
1301                "160",
1302                "$225.00",
1303                "$3,105.00",
1304                "$39,105.00",
1305            ),
1306            (
1307                "GPU Cluster Configuration",
1308                "1",
1309                "$12,000.00",
1310                "$1,035.00",
1311                "$13,035.00",
1312            ),
1313            (
1314                "API Gateway Implementation",
1315                "1",
1316                "$18,500.00",
1317                "$1,595.63",
1318                "$20,095.63",
1319            ),
1320            (
1321                "Load Testing & QA (80 hrs)",
1322                "80",
1323                "$195.00",
1324                "$1,345.50",
1325                "$16,945.50",
1326            ),
1327            (
1328                "Documentation & Training",
1329                "1",
1330                "$8,000.00",
1331                "$690.00",
1332                "$8,690.00",
1333            ),
1334            (
1335                "Project Management (3 months)",
1336                "3",
1337                "$6,500.00",
1338                "$1,679.63",
1339                "$21,179.63",
1340            ),
1341        ];
1342        for (i, (desc, qty, unit, tax, amt)) in items.iter().enumerate() {
1343            let r = i + 1;
1344            tbl.cell(r, 0).unwrap().set_text(desc);
1345            tbl.cell(r, 1).unwrap().set_text(qty);
1346            tbl.cell(r, 2).unwrap().set_text(unit);
1347            tbl.cell(r, 3).unwrap().set_text(tax);
1348            tbl.cell(r, 4).unwrap().set_text(amt);
1349            if i % 2 == 0 {
1350                for c in 0..5 {
1351                    tbl.cell(r, c).unwrap().shading("FAF0F0");
1352                }
1353            }
1354        }
1355    }
1356
1357    doc.add_paragraph("");
1358
1359    // ── Totals ──
1360    {
1361        let mut tbl = doc
1362            .add_table(4, 2)
1363            .borders(BorderStyle::Single, 2, "B22222")
1364            .width(Length::inches(3.0))
1365            .alignment(Alignment::Right);
1366        tbl.cell(0, 0).unwrap().set_text("Subtotal");
1367        tbl.cell(0, 1).unwrap().set_text("$154,600.00");
1368        tbl.cell(1, 0).unwrap().set_text("Tax (8.625%)");
1369        tbl.cell(1, 1).unwrap().set_text("$13,334.25");
1370        tbl.cell(2, 0).unwrap().set_text("Discount (5%)");
1371        tbl.cell(2, 1).unwrap().set_text("-$7,730.00");
1372        tbl.cell(3, 0).unwrap().set_text("AMOUNT DUE");
1373        tbl.cell(3, 0).unwrap().shading("B22222");
1374        tbl.cell(3, 1).unwrap().set_text("$160,204.25");
1375        tbl.cell(3, 1).unwrap().shading("B22222");
1376    }
1377
1378    doc.add_paragraph("");
1379    doc.add_paragraph("");
1380
1381    // ── Payment Details ──
1382    {
1383        let mut p = doc.add_paragraph("");
1384        p.add_run("Payment Details")
1385            .bold(true)
1386            .size(14.0)
1387            .color("333333");
1388    }
1389    doc.add_paragraph("Bank: Silicon Valley Bank")
1390        .indent_left(Length::inches(0.3));
1391    doc.add_paragraph("Account: Tensorbee Inc. — 0847-2953-1120")
1392        .indent_left(Length::inches(0.3));
1393    doc.add_paragraph("Routing: 121140399")
1394        .indent_left(Length::inches(0.3));
1395    doc.add_paragraph("Swift: SVBKUS6S")
1396        .indent_left(Length::inches(0.3));
1397
1398    doc.add_paragraph("");
1399
1400    doc.add_paragraph("Please include invoice number INV-2026-0392 in the payment reference.")
1401        .shading("FAF0F0")
1402        .border_all(BorderStyle::Single, 2, "B22222");
1403
1404    doc
1405}
1406
1407// =============================================================================
1408// 5. REPORT — Forest green + earth tones, with images & hierarchical sections
1409// =============================================================================
1410fn generate_report(_samples_dir: &Path) -> Document {
1411    let mut doc = Document::new();
1412
1413    // Colors: Forest #2D5016, Sage #6B8E23, Earth #8B7355, Cream #FFFAF0
1414    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1415    doc.set_margins(
1416        Length::inches(1.0),
1417        Length::inches(1.0),
1418        Length::inches(1.0),
1419        Length::inches(1.0),
1420    );
1421    doc.set_title("Q4 2025 Environmental Impact Report");
1422    doc.set_author("Walter White");
1423    doc.set_subject("Quarterly Environmental Report");
1424    doc.set_keywords("environment, sustainability, report, Tensorbee");
1425
1426    doc.set_different_first_page(true);
1427    doc.set_first_page_header("");
1428    doc.set_header("Tensorbee — Q4 2025 Environmental Impact Report");
1429    doc.set_footer("CONFIDENTIAL — Page");
1430
1431    // ── Cover ──
1432    let cover_bg = create_sample_png(612, 792, [20, 50, 15]);
1433    doc.add_background_image(&cover_bg, "report_cover.png");
1434
1435    for _ in 0..5 {
1436        doc.add_paragraph("");
1437    }
1438    {
1439        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1440        p.add_run("Q4 2025")
1441            .bold(true)
1442            .size(48.0)
1443            .color("FFFFFF")
1444            .font("Georgia");
1445    }
1446    {
1447        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1448        p.add_run("Environmental Impact Report")
1449            .size(24.0)
1450            .color("90EE90")
1451            .font("Georgia")
1452            .italic(true);
1453    }
1454    doc.add_paragraph("");
1455    {
1456        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1457        p.add_run("Tensorbee — Sustainability Division")
1458            .size(14.0)
1459            .color("C0C0C0");
1460    }
1461    {
1462        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1463        p.add_run("Prepared by Walter White, Chief Sustainability Officer")
1464            .size(11.0)
1465            .color("AAAAAA");
1466    }
1467
1468    // ── TOC ──
1469    doc.add_paragraph("").page_break_before(true);
1470    doc.insert_toc(doc.content_count(), 3);
1471
1472    // ── Section 1: Executive Overview ──
1473    doc.add_paragraph("").page_break_before(true);
1474    doc.add_paragraph("Executive Overview").style("Heading1");
1475    doc.add_paragraph(
1476        "This report presents Tensorbee's environmental performance for Q4 2025. Our \
1477         sustainability initiatives have yielded a 34% reduction in carbon emissions compared \
1478         to Q4 2024, exceeding our target of 25%. Key achievements include the transition to \
1479         100% renewable energy in our primary data centers and the launch of our carbon offset \
1480         marketplace.",
1481    )
1482    .first_line_indent(Length::inches(0.3));
1483
1484    // Image: performance chart
1485    let chart_img = create_chart_png(400, 200);
1486    doc.add_paragraph("");
1487    doc.add_picture(
1488        &chart_img,
1489        "performance_chart.png",
1490        Length::inches(5.0),
1491        Length::inches(2.5),
1492    )
1493    .alignment(Alignment::Center);
1494    {
1495        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1496        p.add_run("Figure 1: Quarterly Carbon Emissions (tonnes CO2e)")
1497            .italic(true)
1498            .size(9.0)
1499            .color("666666");
1500    }
1501
1502    // ── Section 2: Energy Consumption ──
1503    doc.add_paragraph("").page_break_before(true);
1504    doc.add_paragraph("Energy Consumption").style("Heading1");
1505
1506    doc.add_paragraph("Data Center Operations")
1507        .style("Heading2");
1508    doc.add_paragraph(
1509        "Our three primary data centers consumed a combined 4.2 GWh in Q4 2025, \
1510         a 12% reduction from Q3 2025 driven by improved cooling efficiency and \
1511         server consolidation.",
1512    );
1513
1514    // Data table
1515    {
1516        let mut tbl = doc
1517            .add_table(5, 4)
1518            .borders(BorderStyle::Single, 4, "2D5016")
1519            .width_pct(100.0);
1520        let hdrs = ["Data Center", "Capacity (MW)", "Usage (GWh)", "PUE"];
1521        for (c, h) in hdrs.iter().enumerate() {
1522            tbl.cell(0, c).unwrap().set_text(h);
1523            tbl.cell(0, c).unwrap().shading("2D5016");
1524        }
1525        let rows = [
1526            ("San Francisco (Primary)", "3.2", "1.8", "1.12"),
1527            ("Dublin (EU)", "2.1", "1.2", "1.18"),
1528            ("Singapore (APAC)", "1.8", "1.2", "1.24"),
1529            ("TOTAL", "7.1", "4.2", "1.17 avg"),
1530        ];
1531        for (i, (dc, cap, use_, pue)) in rows.iter().enumerate() {
1532            tbl.cell(i + 1, 0).unwrap().set_text(dc);
1533            tbl.cell(i + 1, 1).unwrap().set_text(cap);
1534            tbl.cell(i + 1, 2).unwrap().set_text(use_);
1535            tbl.cell(i + 1, 3).unwrap().set_text(pue);
1536            if i == 3 {
1537                for c in 0..4 {
1538                    tbl.cell(i + 1, c).unwrap().shading("E2EFDA");
1539                }
1540            }
1541        }
1542    }
1543
1544    doc.add_paragraph("");
1545
1546    doc.add_paragraph("Renewable Energy Mix").style("Heading2");
1547    doc.add_paragraph("Breakdown of energy sources across all facilities:");
1548
1549    doc.add_bullet_list_item("Solar PV: 42% (1.76 GWh)", 0);
1550    doc.add_bullet_list_item("Wind Power Purchase Agreements: 38% (1.60 GWh)", 0);
1551    doc.add_bullet_list_item("Hydroelectric: 12% (0.50 GWh)", 0);
1552    doc.add_bullet_list_item("Grid (non-renewable): 8% (0.34 GWh)", 0);
1553
1554    // ── Section 3: Water & Waste ──
1555    doc.add_paragraph("Water & Waste Management")
1556        .style("Heading1");
1557
1558    doc.add_paragraph("Water Usage").style("Heading2");
1559    doc.add_paragraph(
1560        "Total water consumption was 12.4 million gallons, a 15% reduction from Q3. \
1561         Our closed-loop cooling systems now recycle 78% of water used in cooling operations.",
1562    );
1563
1564    doc.add_paragraph("Waste Reduction").style("Heading2");
1565    doc.add_paragraph("E-waste management results for Q4:")
1566        .keep_with_next(true);
1567    {
1568        let mut tbl = doc
1569            .add_table(4, 3)
1570            .borders(BorderStyle::Single, 2, "6B8E23")
1571            .width_pct(80.0);
1572        tbl.cell(0, 0).unwrap().set_text("Category");
1573        tbl.cell(0, 0).unwrap().shading("6B8E23");
1574        tbl.cell(0, 1).unwrap().set_text("Weight (kg)");
1575        tbl.cell(0, 1).unwrap().shading("6B8E23");
1576        tbl.cell(0, 2).unwrap().set_text("Recycled %");
1577        tbl.cell(0, 2).unwrap().shading("6B8E23");
1578        let rows = [
1579            ("Server Hardware", "2,450", "94%"),
1580            ("Networking Equipment", "820", "91%"),
1581            ("Storage Media", "340", "99%"),
1582        ];
1583        for (i, (cat, wt, pct)) in rows.iter().enumerate() {
1584            tbl.cell(i + 1, 0).unwrap().set_text(cat);
1585            tbl.cell(i + 1, 1).unwrap().set_text(wt);
1586            tbl.cell(i + 1, 2).unwrap().set_text(pct);
1587        }
1588    }
1589
1590    // ── Section 4: Initiatives ──
1591    doc.add_paragraph("").page_break_before(true);
1592    doc.add_paragraph("Q1 2026 Initiatives").style("Heading1");
1593
1594    doc.add_paragraph("Planned Programs").style("Heading2");
1595    doc.add_numbered_list_item(
1596        "Deploy on-site battery storage at SF data center (2 MWh capacity)",
1597        0,
1598    );
1599    doc.add_numbered_list_item("Pilot immersion cooling in Dublin facility", 0);
1600    doc.add_numbered_list_item("Launch employee commute carbon offset program", 0);
1601    doc.add_numbered_list_item("Achieve ISO 14001 certification for Singapore facility", 0);
1602
1603    doc.add_paragraph("Investment Targets").style("Heading2");
1604    doc.add_paragraph("Sustainability CapEx allocation for FY2026:")
1605        .keep_with_next(true);
1606    {
1607        let mut tbl = doc
1608            .add_table(5, 2)
1609            .borders(BorderStyle::Single, 4, "2D5016");
1610        tbl.cell(0, 0).unwrap().set_text("Initiative");
1611        tbl.cell(0, 0).unwrap().shading("2D5016");
1612        tbl.cell(0, 1).unwrap().set_text("Budget");
1613        tbl.cell(0, 1).unwrap().shading("2D5016");
1614        let rows = [
1615            ("Battery Storage", "$1.2M"),
1616            ("Immersion Cooling Pilot", "$800K"),
1617            ("Solar Panel Expansion", "$2.1M"),
1618            ("Carbon Credits", "$500K"),
1619        ];
1620        for (i, (init, budget)) in rows.iter().enumerate() {
1621            tbl.cell(i + 1, 0).unwrap().set_text(init);
1622            tbl.cell(i + 1, 1).unwrap().set_text(budget);
1623            if i % 2 == 0 {
1624                tbl.cell(i + 1, 0).unwrap().shading("FFFAF0");
1625                tbl.cell(i + 1, 1).unwrap().shading("FFFAF0");
1626            }
1627        }
1628    }
1629
1630    // Image: sustainability roadmap
1631    let roadmap_img = create_sample_png(500, 100, [40, 80, 30]);
1632    doc.add_paragraph("");
1633    doc.add_picture(
1634        &roadmap_img,
1635        "roadmap.png",
1636        Length::inches(6.0),
1637        Length::inches(1.2),
1638    )
1639    .alignment(Alignment::Center);
1640    {
1641        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1642        p.add_run("Figure 2: 2026 Sustainability Roadmap")
1643            .italic(true)
1644            .size(9.0)
1645            .color("666666");
1646    }
1647
1648    doc.add_paragraph("");
1649    doc.add_paragraph(
1650        "For questions about this report, contact Walter White at sustainability@tensorbee.com.",
1651    )
1652    .shading("FFFAF0")
1653    .border_all(BorderStyle::Single, 2, "2D5016");
1654
1655    doc
1656}
1657
1658// =============================================================================
1659// 6. LETTER — Slate blue + silver scheme
1660// =============================================================================
1661fn generate_letter(_samples_dir: &Path) -> Document {
1662    let mut doc = Document::new();
1663
1664    // Colors: Slate #4A5568, Blue accent #3182CE
1665    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1666    doc.set_margins(
1667        Length::inches(1.25),
1668        Length::inches(1.25),
1669        Length::inches(1.25),
1670        Length::inches(1.25),
1671    );
1672    doc.set_title("Business Letter");
1673    doc.set_author("Walter White");
1674
1675    // Banner header using raw XML
1676    let logo_img = create_logo_png(220, 48);
1677    let banner = build_header_banner_xml(
1678        "rId1",
1679        &BannerOpts {
1680            bg_color: "4A5568",
1681            banner_width: 7772400,
1682            banner_height: 731520, // ~0.8"
1683            logo_width: 2011680,
1684            logo_height: 438912,
1685            logo_x_offset: 295125,
1686            logo_y_offset: 146304,
1687        },
1688    );
1689    doc.set_raw_header_with_images(
1690        banner,
1691        &[("rId1", &logo_img, "logo.png")],
1692        rdocx_oxml::header_footer::HdrFtrType::Default,
1693    );
1694    doc.set_margins(
1695        Length::twips(2000),
1696        Length::twips(1800),
1697        Length::twips(1440),
1698        Length::twips(1800),
1699    );
1700    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
1701
1702    // Footer with contact
1703    doc.set_footer(
1704        "Tensorbee | 123 Innovation Drive, Suite 400 | San Francisco, CA 94105 | tensorbee.com",
1705    );
1706
1707    // ── Sender Address ──
1708    doc.add_paragraph("Walter White");
1709    doc.add_paragraph("Chief Executive Officer");
1710    doc.add_paragraph("Tensorbee");
1711    doc.add_paragraph("123 Innovation Drive, Suite 400");
1712    doc.add_paragraph("San Francisco, CA 94105");
1713    doc.add_paragraph("");
1714    doc.add_paragraph("February 22, 2026");
1715
1716    doc.add_paragraph("");
1717
1718    // ── Recipient ──
1719    doc.add_paragraph("Ms. Sarah Chen");
1720    doc.add_paragraph("VP of Engineering");
1721    doc.add_paragraph("Meridian Dynamics LLC");
1722    doc.add_paragraph("456 Enterprise Boulevard");
1723    doc.add_paragraph("Austin, TX 78701");
1724
1725    doc.add_paragraph("");
1726
1727    // ── Subject ──
1728    {
1729        let mut p = doc.add_paragraph("");
1730        p.add_run("Re: Strategic Technology Partnership — Phase 2 Expansion")
1731            .bold(true);
1732    }
1733
1734    doc.add_paragraph("");
1735
1736    // ── Body ──
1737    doc.add_paragraph("Dear Ms. Chen,");
1738    doc.add_paragraph("");
1739
1740    doc.add_paragraph(
1741        "I am writing to express Tensorbee's enthusiasm for expanding our strategic technology \
1742         partnership with Meridian Dynamics. Following the successful completion of Phase 1, \
1743         which delivered a 40% improvement in your ML inference pipeline throughput, we believe \
1744         the foundation is firmly established for an ambitious Phase 2 engagement.",
1745    )
1746    .first_line_indent(Length::inches(0.5));
1747
1748    doc.add_paragraph(
1749        "During our recent executive review, your team highlighted three priority areas for \
1750         the next phase of collaboration:",
1751    )
1752    .first_line_indent(Length::inches(0.5));
1753
1754    doc.add_numbered_list_item(
1755        "Real-time anomaly detection for your financial transaction monitoring system, \
1756         targeting sub-100ms latency at 50,000 transactions per second.",
1757        0,
1758    );
1759    doc.add_numbered_list_item(
1760        "Federated learning infrastructure to enable model training across your distributed \
1761         data centers without centralizing sensitive financial data.",
1762        0,
1763    );
1764    doc.add_numbered_list_item(
1765        "MLOps automation to reduce your model deployment cycle from the current 5 days \
1766         to under 4 hours.",
1767        0,
1768    );
1769
1770    doc.add_paragraph(
1771        "Our engineering team has prepared a detailed technical proposal addressing each of \
1772         these areas. We have allocated a dedicated team of eight senior engineers, led by \
1773         our CTO, to ensure continuity with the Phase 1 team your organization has already \
1774         built a productive working relationship with.",
1775    )
1776    .first_line_indent(Length::inches(0.5));
1777
1778    doc.add_paragraph(
1779        "I would welcome the opportunity to present our Phase 2 proposal in person. My \
1780         assistant will reach out to coordinate a meeting at your convenience during the \
1781         first week of March.",
1782    )
1783    .first_line_indent(Length::inches(0.5));
1784
1785    doc.add_paragraph("");
1786
1787    doc.add_paragraph("Warm regards,");
1788    doc.add_paragraph("");
1789    doc.add_paragraph("");
1790
1791    // Signature line
1792    doc.add_paragraph("")
1793        .border_bottom(BorderStyle::Single, 4, "4A5568");
1794    {
1795        let mut p = doc.add_paragraph("");
1796        p.add_run("Walter White").bold(true).size(12.0);
1797    }
1798    {
1799        let mut p = doc.add_paragraph("");
1800        p.add_run("Chief Executive Officer, Tensorbee")
1801            .italic(true)
1802            .size(10.0)
1803            .color("666666");
1804    }
1805    {
1806        let mut p = doc.add_paragraph("");
1807        p.add_run("walter@tensorbee.com | +1 (415) 555-0199")
1808            .size(10.0)
1809            .color("888888");
1810    }
1811
1812    doc
1813}
1814
1815// =============================================================================
1816// 7. EMPLOYMENT CONTRACT — Purple + charcoal scheme
1817// =============================================================================
1818fn generate_contract(_samples_dir: &Path) -> Document {
1819    let mut doc = Document::new();
1820
1821    // Colors: Purple #5B2C6F, Plum #8E44AD, Gray #2C3E50
1822    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1823    doc.set_margins(
1824        Length::inches(1.25),
1825        Length::inches(1.0),
1826        Length::inches(1.0),
1827        Length::inches(1.0),
1828    );
1829    doc.set_title("Employment Agreement");
1830    doc.set_author("Tensorbee HR Department");
1831    doc.set_subject("Employment Contract — Walter White");
1832    doc.set_keywords("employment, contract, agreement, Tensorbee");
1833
1834    doc.set_header("TENSORBEE — Employment Agreement");
1835    doc.set_footer("Employment Agreement — Walter White — Page");
1836
1837    // ── Title Block ──
1838    doc.add_paragraph("")
1839        .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1840    {
1841        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1842        p.add_run("EMPLOYMENT AGREEMENT")
1843            .bold(true)
1844            .size(24.0)
1845            .color("5B2C6F")
1846            .font("Georgia");
1847    }
1848    doc.add_paragraph("")
1849        .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1850    doc.add_paragraph("");
1851
1852    // ── Parties ──
1853    doc.add_paragraph(
1854        "This Employment Agreement (\"Agreement\") is entered into as of February 22, 2026 \
1855         (\"Effective Date\"), by and between:",
1856    );
1857
1858    doc.add_paragraph("");
1859
1860    {
1861        let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1862        p.add_run("EMPLOYER: ").bold(true);
1863        p.add_run(
1864            "Tensorbee, Inc., a Delaware corporation with its principal place of business at \
1865                   123 Innovation Drive, Suite 400, San Francisco, CA 94105 (\"Company\")",
1866        );
1867    }
1868    doc.add_paragraph("");
1869    {
1870        let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1871        p.add_run("EMPLOYEE: ").bold(true);
1872        p.add_run(
1873            "Walter White, residing at 308 Negra Arroyo Lane, Albuquerque, NM 87104 (\"Employee\")",
1874        );
1875    }
1876
1877    doc.add_paragraph("");
1878    doc.add_paragraph("The Company and Employee are collectively referred to as the \"Parties.\"");
1879    doc.add_paragraph("");
1880
1881    // ── Article 1: Position and Duties ──
1882    doc.add_paragraph("Article 1 — Position and Duties")
1883        .style("Heading1");
1884    {
1885        let mut p = doc.add_paragraph("");
1886        p.add_run("1.1 ").bold(true);
1887        p.add_run("The Company hereby employs the Employee as ");
1888        p.add_run("Chief Executive Officer (CEO)")
1889            .bold(true)
1890            .italic(true);
1891        p.add_run(", reporting directly to the Board of Directors.");
1892    }
1893    {
1894        let mut p = doc.add_paragraph("");
1895        p.add_run("1.2 ").bold(true);
1896        p.add_run("The Employee shall devote full working time, attention, and best efforts to \
1897                   the performance of duties as reasonably assigned by the Board, including but not limited to:");
1898    }
1899    doc.add_bullet_list_item(
1900        "Setting and executing the Company's strategic vision and business plan",
1901        0,
1902    );
1903    doc.add_bullet_list_item(
1904        "Overseeing all operations, engineering, and commercial activities",
1905        0,
1906    );
1907    doc.add_bullet_list_item(
1908        "Representing the Company to investors, customers, and the public",
1909        0,
1910    );
1911    doc.add_bullet_list_item("Recruiting, developing, and retaining key talent", 0);
1912
1913    {
1914        let mut p = doc.add_paragraph("");
1915        p.add_run("1.3 ").bold(true);
1916        p.add_run(
1917            "The Employee's primary work location shall be the Company's San Francisco \
1918                   headquarters, with reasonable travel as required by business needs.",
1919        );
1920    }
1921
1922    // ── Article 2: Compensation ──
1923    doc.add_paragraph("Article 2 — Compensation and Benefits")
1924        .style("Heading1");
1925    {
1926        let mut p = doc.add_paragraph("");
1927        p.add_run("2.1 Base Salary. ").bold(true);
1928        p.add_run("The Company shall pay the Employee an annual base salary of ");
1929        p.add_run("$375,000.00").bold(true);
1930        p.add_run(" (Three Hundred Seventy-Five Thousand Dollars), payable in accordance with the \
1931                   Company's standard payroll schedule, less applicable withholdings and deductions.");
1932    }
1933    {
1934        let mut p = doc.add_paragraph("");
1935        p.add_run("2.2 Annual Bonus. ").bold(true);
1936        p.add_run("The Employee shall be eligible for an annual performance bonus of up to ");
1937        p.add_run("40%").bold(true);
1938        p.add_run(
1939            " of base salary, based on achievement of mutually agreed performance objectives.",
1940        );
1941    }
1942    {
1943        let mut p = doc.add_paragraph("");
1944        p.add_run("2.3 Equity. ").bold(true);
1945        p.add_run("Subject to Board approval, the Employee shall receive a stock option grant of ");
1946        p.add_run("500,000 shares").bold(true);
1947        p.add_run(
1948            " of the Company's common stock, vesting over four (4) years with a one-year cliff, \
1949                   at an exercise price equal to the fair market value on the date of grant.",
1950        );
1951    }
1952
1953    // Compensation summary table
1954    doc.add_paragraph("");
1955    {
1956        let mut tbl = doc
1957            .add_table(5, 2)
1958            .borders(BorderStyle::Single, 4, "5B2C6F")
1959            .width_pct(70.0)
1960            .alignment(Alignment::Center);
1961        tbl.cell(0, 0).unwrap().set_text("Compensation Element");
1962        tbl.cell(0, 0).unwrap().shading("5B2C6F");
1963        tbl.cell(0, 1).unwrap().set_text("Value");
1964        tbl.cell(0, 1).unwrap().shading("5B2C6F");
1965        let items = [
1966            ("Base Salary", "$375,000/year"),
1967            ("Target Bonus", "Up to 40% ($150,000)"),
1968            ("Equity Grant", "500,000 shares (4yr vest)"),
1969            ("Total Target Comp", "$525,000 + equity"),
1970        ];
1971        for (i, (elem, val)) in items.iter().enumerate() {
1972            tbl.cell(i + 1, 0).unwrap().set_text(elem);
1973            tbl.cell(i + 1, 1).unwrap().set_text(val);
1974            if i == 3 {
1975                tbl.cell(i + 1, 0).unwrap().shading("E8DAEF");
1976                tbl.cell(i + 1, 1).unwrap().shading("E8DAEF");
1977            }
1978        }
1979    }
1980
1981    // ── Article 3: Benefits ──
1982    doc.add_paragraph("").page_break_before(true);
1983    doc.add_paragraph("Article 3 — Benefits").style("Heading1");
1984    {
1985        let mut p = doc.add_paragraph("");
1986        p.add_run("3.1 ").bold(true);
1987        p.add_run(
1988            "The Employee shall be entitled to participate in all benefit programs \
1989                   generally available to senior executives, including:",
1990        );
1991    }
1992    doc.add_bullet_list_item(
1993        "Medical, dental, and vision insurance (100% premium coverage for employee and dependents)",
1994        0,
1995    );
1996    doc.add_bullet_list_item("401(k) retirement plan with 6% company match", 0);
1997    doc.add_bullet_list_item("Life insurance and long-term disability coverage", 0);
1998    doc.add_bullet_list_item("Annual professional development allowance of $10,000", 0);
1999
2000    {
2001        let mut p = doc.add_paragraph("");
2002        p.add_run("3.2 Paid Time Off. ").bold(true);
2003        p.add_run(
2004            "The Employee shall receive 25 days of paid vacation per year, plus Company holidays, \
2005                   accruing on a monthly basis.",
2006        );
2007    }
2008
2009    // ── Article 4: Term and Termination ──
2010    doc.add_paragraph("Article 4 — Term and Termination")
2011        .style("Heading1");
2012    {
2013        let mut p = doc.add_paragraph("");
2014        p.add_run("4.1 At-Will Employment. ").bold(true);
2015        p.add_run(
2016            "This Agreement is for at-will employment and may be terminated by either Party \
2017                   at any time, with or without cause, subject to the notice provisions herein.",
2018        );
2019    }
2020    {
2021        let mut p = doc.add_paragraph("");
2022        p.add_run("4.2 Notice Period. ").bold(true);
2023        p.add_run("Either Party shall provide ");
2024        p.add_run("ninety (90) days").bold(true);
2025        p.add_run(" written notice of termination, or pay in lieu thereof.");
2026    }
2027    {
2028        let mut p = doc.add_paragraph("");
2029        p.add_run("4.3 Severance. ").bold(true);
2030        p.add_run("In the event of termination by the Company without Cause, the Employee shall \
2031                   receive (a) twelve (12) months of base salary continuation, (b) pro-rata bonus \
2032                   for the year of termination, and (c) twelve (12) months of COBRA premium coverage.");
2033    }
2034
2035    // ── Article 5: Confidentiality ──
2036    doc.add_paragraph("Article 5 — Confidentiality and IP")
2037        .style("Heading1");
2038    {
2039        let mut p = doc.add_paragraph("");
2040        p.add_run("5.1 ").bold(true);
2041        p.add_run(
2042            "The Employee agrees to maintain strict confidentiality of all proprietary \
2043                   information, trade secrets, and business plans of the Company during and after \
2044                   employment.",
2045        );
2046    }
2047    {
2048        let mut p = doc.add_paragraph("");
2049        p.add_run("5.2 ").bold(true);
2050        p.add_run(
2051            "All intellectual property, inventions, and works of authorship created by the \
2052                   Employee during the term of employment and within the scope of duties shall be \
2053                   the sole and exclusive property of the Company.",
2054        );
2055    }
2056
2057    // ── Article 6: Non-Compete ──
2058    doc.add_paragraph("Article 6 — Non-Competition")
2059        .style("Heading1");
2060    {
2061        let mut p = doc.add_paragraph("");
2062        p.add_run("6.1 ").bold(true);
2063        p.add_run("For a period of twelve (12) months following termination, the Employee agrees \
2064                   not to engage in any business that directly competes with the Company's core \
2065                   business of AI infrastructure and ML operations services, within the United States.");
2066    }
2067
2068    // ── Article 7: General Provisions ──
2069    doc.add_paragraph("Article 7 — General Provisions")
2070        .style("Heading1");
2071    {
2072        let mut p = doc.add_paragraph("");
2073        p.add_run("7.1 Governing Law. ").bold(true);
2074        p.add_run(
2075            "This Agreement shall be governed by and construed in accordance with the laws of \
2076                   the State of California.",
2077        );
2078    }
2079    {
2080        let mut p = doc.add_paragraph("");
2081        p.add_run("7.2 Entire Agreement. ").bold(true);
2082        p.add_run(
2083            "This Agreement constitutes the entire agreement between the Parties and supersedes \
2084                   all prior negotiations, representations, and agreements.",
2085        );
2086    }
2087    {
2088        let mut p = doc.add_paragraph("");
2089        p.add_run("7.3 Amendment. ").bold(true);
2090        p.add_run(
2091            "This Agreement may only be amended by a written instrument signed by both Parties.",
2092        );
2093    }
2094
2095    // ── Signature Block ──
2096    doc.add_paragraph("").page_break_before(true);
2097    doc.add_paragraph("IN WITNESS WHEREOF, the Parties have executed this Employment Agreement as of the Effective Date.")
2098        .space_after(Length::pt(24.0));
2099
2100    // Signature table
2101    {
2102        let mut tbl = doc.add_table(6, 2).width_pct(100.0);
2103        tbl.cell(0, 0).unwrap().set_text("FOR THE COMPANY:");
2104        tbl.cell(0, 0).unwrap().shading("5B2C6F");
2105        tbl.cell(0, 1).unwrap().set_text("EMPLOYEE:");
2106        tbl.cell(0, 1).unwrap().shading("5B2C6F");
2107
2108        tbl.cell(1, 0).unwrap().set_text("");
2109        tbl.cell(1, 1).unwrap().set_text("");
2110        tbl.row(1).unwrap().height(Length::pt(40.0));
2111
2112        tbl.cell(2, 0)
2113            .unwrap()
2114            .set_text("Signature: ___________________________");
2115        tbl.cell(2, 1)
2116            .unwrap()
2117            .set_text("Signature: ___________________________");
2118        tbl.cell(3, 0)
2119            .unwrap()
2120            .set_text("Name: Board Representative");
2121        tbl.cell(3, 1).unwrap().set_text("Name: Walter White");
2122        tbl.cell(4, 0)
2123            .unwrap()
2124            .set_text("Title: Chair, Board of Directors");
2125        tbl.cell(4, 1)
2126            .unwrap()
2127            .set_text("Title: Chief Executive Officer");
2128        tbl.cell(5, 0).unwrap().set_text("Date: _______________");
2129        tbl.cell(5, 1).unwrap().set_text("Date: _______________");
2130    }
2131
2132    doc
2133}
Source

pub fn add_numbered_list_item( &mut self, text: &str, level: u32, ) -> Paragraph<'_>

Add a numbered list item at the given indentation level (0-based).

If no numbered list definition exists yet, one is created automatically. Returns a mutable Paragraph for further configuration.

Examples found in repository?
examples/generate_all_samples.rs (line 323)
97fn generate_feature_showcase(_samples_dir: &Path) -> Document {
98    let mut doc = Document::new();
99
100    // ── Page Setup & Metadata ──
101    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
102    doc.set_margins(
103        Length::inches(1.0),
104        Length::inches(1.0),
105        Length::inches(1.0),
106        Length::inches(1.0),
107    );
108    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
109    doc.set_gutter(Length::twips(0));
110    doc.set_title("rdocx Feature Showcase");
111    doc.set_author("rdocx Sample Generator");
112    doc.set_subject("Comprehensive feature demonstration");
113    doc.set_keywords("rdocx, docx, rust, sample, showcase");
114
115    // Headers & Footers with different first page
116    doc.set_different_first_page(true);
117    doc.set_first_page_header("rdocx");
118    doc.set_first_page_footer("Feature Showcase — Cover Page");
119    doc.set_header("rdocx Feature Showcase");
120    doc.set_footer("Generated by rdocx");
121
122    // ── COVER PAGE ──
123    let bg = create_sample_png(612, 792, [20, 45, 90]);
124    doc.add_background_image(&bg, "cover_bg.png");
125
126    for _ in 0..3 {
127        doc.add_paragraph("");
128    }
129    {
130        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
131        p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
132    }
133    {
134        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
135        p.add_run("Complete Feature Showcase")
136            .size(28.0)
137            .color("FFFFFF")
138            .italic(true);
139    }
140    doc.add_paragraph("");
141    {
142        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
143        p.add_run("Every feature of the rdocx Rust library")
144            .size(13.0)
145            .color("B0C4DE");
146    }
147    {
148        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
149        p.add_run("demonstrated in a single document.")
150            .size(13.0)
151            .color("B0C4DE");
152    }
153
154    // ── TABLE OF CONTENTS ──
155    doc.add_paragraph("").page_break_before(true);
156    doc.insert_toc(doc.content_count(), 3);
157
158    // ── SECTION 1: TEXT FORMATTING ──
159    doc.add_paragraph("").page_break_before(true);
160    doc.add_paragraph("1. Text Formatting").style("Heading1");
161
162    // Paragraph Alignment
163    doc.add_paragraph("1.1 Paragraph Alignment")
164        .style("Heading2");
165    doc.add_paragraph("Left-aligned paragraph (default).")
166        .alignment(Alignment::Left);
167    doc.add_paragraph("Center-aligned paragraph.")
168        .alignment(Alignment::Center);
169    doc.add_paragraph("Right-aligned paragraph.")
170        .alignment(Alignment::Right);
171    doc.add_paragraph(
172        "Justified paragraph — this text is long enough to demonstrate how justified alignment \
173         distributes extra space across word gaps so lines fill the full width of the text area.",
174    )
175    .alignment(Alignment::Justify);
176
177    doc.add_paragraph("");
178
179    // Run Formatting
180    doc.add_paragraph("1.2 Run Formatting").style("Heading2");
181    {
182        let mut p = doc.add_paragraph("");
183        p.add_run("Bold").bold(true);
184        p.add_run(" | ");
185        p.add_run("Italic").italic(true);
186        p.add_run(" | ");
187        p.add_run("Bold Italic").bold(true).italic(true);
188        p.add_run(" | ");
189        p.add_run("Underline").underline(true);
190        p.add_run(" | ");
191        p.add_run("Strikethrough").strike(true);
192        p.add_run(" | ");
193        p.add_run("Double Strike").double_strike(true);
194    }
195    {
196        let mut p = doc.add_paragraph("");
197        p.add_run("Red").color("FF0000");
198        p.add_run(" | ");
199        p.add_run("Blue").color("0000FF");
200        p.add_run(" | ");
201        p.add_run("Green").color("00AA00");
202        p.add_run(" | ");
203        p.add_run("Highlighted").highlight("FFFF00");
204    }
205    {
206        let mut p = doc.add_paragraph("");
207        p.add_run("8pt").size(8.0);
208        p.add_run(" | ");
209        p.add_run("11pt").size(11.0);
210        p.add_run(" | ");
211        p.add_run("16pt").size(16.0);
212        p.add_run(" | ");
213        p.add_run("24pt").size(24.0);
214    }
215    {
216        let mut p = doc.add_paragraph("");
217        p.add_run("Arial").font("Arial");
218        p.add_run(" | ");
219        p.add_run("Times New Roman").font("Times New Roman");
220        p.add_run(" | ");
221        p.add_run("Courier New").font("Courier New");
222    }
223    {
224        let mut p = doc.add_paragraph("");
225        p.add_run("H");
226        p.add_run("2").subscript();
227        p.add_run("O (subscript) | E=mc");
228        p.add_run("2").superscript();
229        p.add_run(" (superscript)");
230    }
231    {
232        let mut p = doc.add_paragraph("");
233        p.add_run("ALL CAPS").all_caps(true);
234        p.add_run(" | ");
235        p.add_run("Small Caps").small_caps(true);
236        p.add_run(" | ");
237        p.add_run("Expanded").character_spacing(Length::twips(40));
238        p.add_run(" | ");
239        p.add_run("Hidden text (not visible)").hidden(true);
240    }
241
242    doc.add_paragraph("");
243
244    // Underline Styles
245    doc.add_paragraph("1.3 Underline Styles").style("Heading2");
246    {
247        let mut p = doc.add_paragraph("");
248        p.add_run("Single ")
249            .underline_style(rdocx::UnderlineStyle::Single);
250        p.add_run("Double ")
251            .underline_style(rdocx::UnderlineStyle::Double);
252        p.add_run("Thick ")
253            .underline_style(rdocx::UnderlineStyle::Thick);
254        p.add_run("Dotted ")
255            .underline_style(rdocx::UnderlineStyle::Dotted);
256        p.add_run("Dash ")
257            .underline_style(rdocx::UnderlineStyle::Dash);
258        p.add_run("Wave ")
259            .underline_style(rdocx::UnderlineStyle::Wave);
260    }
261
262    // ── SECTION 2: PARAGRAPH FORMATTING ──
263    doc.add_paragraph("").page_break_before(true);
264    doc.add_paragraph("2. Paragraph Formatting")
265        .style("Heading1");
266
267    doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
268    doc.add_paragraph("Paragraph with light green shading")
269        .shading("E2EFDA");
270    doc.add_paragraph("Paragraph with bottom border")
271        .border_bottom(BorderStyle::Single, 6, "2E75B6");
272    doc.add_paragraph("Paragraph with all borders (red)")
273        .border_all(BorderStyle::Single, 4, "FF0000");
274
275    doc.add_paragraph("2.2 Indentation").style("Heading2");
276    doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
277        .indent_left(Length::inches(1.0))
278        .hanging_indent(Length::inches(0.5));
279    doc.add_paragraph("0.5-inch first-line indent on this paragraph")
280        .first_line_indent(Length::inches(0.5));
281    doc.add_paragraph("Both left and right indent (0.75 inches each)")
282        .indent_left(Length::inches(0.75))
283        .indent_right(Length::inches(0.75));
284
285    doc.add_paragraph("2.3 Spacing & Line Height")
286        .style("Heading2");
287    doc.add_paragraph("Extra space before (24pt) and after (12pt)")
288        .space_before(Length::pt(24.0))
289        .space_after(Length::pt(12.0));
290    doc.add_paragraph(
291        "Double line spacing paragraph. This text should have extra vertical space between \
292         lines to demonstrate line_spacing_multiple(2.0).",
293    )
294    .line_spacing_multiple(2.0);
295    doc.add_paragraph("Exact 20pt line spacing (fixed height).")
296        .line_spacing(20.0);
297
298    doc.add_paragraph("2.4 Pagination Controls")
299        .style("Heading2");
300    doc.add_paragraph("keep_with_next — this paragraph stays with the next")
301        .keep_with_next(true);
302    doc.add_paragraph("(This paragraph was kept with the one above.)");
303    doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
304        .keep_together(true);
305    doc.add_paragraph("widow_control — prevents widow/orphan lines")
306        .widow_control(true);
307
308    // ── SECTION 3: LISTS ──
309    doc.add_paragraph("").page_break_before(true);
310    doc.add_paragraph("3. Lists").style("Heading1");
311
312    doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
313    doc.add_bullet_list_item("First item", 0);
314    doc.add_bullet_list_item("Second item", 0);
315    doc.add_bullet_list_item("Nested level 1", 1);
316    doc.add_bullet_list_item("Nested level 2", 2);
317    doc.add_bullet_list_item("Back to level 1", 1);
318    doc.add_bullet_list_item("Third item", 0);
319
320    doc.add_paragraph("");
321
322    doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
323    doc.add_numbered_list_item("First numbered item", 0);
324    doc.add_numbered_list_item("Second numbered item", 0);
325    doc.add_numbered_list_item("Sub-item A", 1);
326    doc.add_numbered_list_item("Sub-item B", 1);
327    doc.add_numbered_list_item("Third numbered item", 0);
328
329    // ── SECTION 4: TAB STOPS ──
330    doc.add_paragraph("");
331    doc.add_paragraph("4. Tab Stops").style("Heading1");
332
333    doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
334    doc.add_paragraph("Left\tCenter\tRight\tDecimal")
335        .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
336        .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
337        .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
338        .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
339
340    doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
341    doc.add_paragraph("Item\t\tPrice")
342        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
343        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
344        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
345    doc.add_paragraph("Widget\t\t$19.99")
346        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
347        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
348        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
349    doc.add_paragraph("Gadget\t\t$249.50")
350        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
351        .add_tab_stop_with_leader(
352            TabAlignment::Right,
353            Length::inches(4.0),
354            TabLeader::Underscore,
355        )
356        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
357
358    // ── SECTION 5: TABLES ──
359    doc.add_paragraph("").page_break_before(true);
360    doc.add_paragraph("5. Tables").style("Heading1");
361
362    doc.add_paragraph("5.1 Basic Table").style("Heading2");
363    {
364        let mut tbl = doc
365            .add_table(4, 3)
366            .borders(BorderStyle::Single, 4, "000000");
367        for col in 0..3 {
368            tbl.cell(0, col).unwrap().shading("2E75B6");
369        }
370        tbl.cell(0, 0).unwrap().set_text("Name");
371        tbl.cell(0, 1).unwrap().set_text("Role");
372        tbl.cell(0, 2).unwrap().set_text("Location");
373        tbl.cell(1, 0).unwrap().set_text("Walter White");
374        tbl.cell(1, 1).unwrap().set_text("CEO");
375        tbl.cell(1, 2).unwrap().set_text("Albuquerque");
376        tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
377        tbl.cell(2, 1).unwrap().set_text("CTO");
378        tbl.cell(2, 2).unwrap().set_text("Remote");
379        tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
380        tbl.cell(3, 1).unwrap().set_text("Security");
381        tbl.cell(3, 2).unwrap().set_text("Washington");
382    }
383
384    doc.add_paragraph("");
385
386    doc.add_paragraph("5.2 Column Span & Cell Shading")
387        .style("Heading2");
388    {
389        let mut tbl = doc
390            .add_table(3, 4)
391            .borders(BorderStyle::Single, 4, "000000")
392            .width_pct(100.0);
393        tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
394        tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
395        tbl.cell(1, 0).unwrap().set_text("Region");
396        tbl.cell(1, 0).unwrap().shading("D6E4F0");
397        tbl.cell(1, 1).unwrap().set_text("Q1");
398        tbl.cell(1, 1).unwrap().shading("D6E4F0");
399        tbl.cell(1, 2).unwrap().set_text("Q2");
400        tbl.cell(1, 2).unwrap().shading("D6E4F0");
401        tbl.cell(1, 3).unwrap().set_text("Total");
402        tbl.cell(1, 3).unwrap().shading("D6E4F0");
403        tbl.cell(2, 0).unwrap().set_text("Americas");
404        tbl.cell(2, 1).unwrap().set_text("$2.4M");
405        tbl.cell(2, 2).unwrap().set_text("$2.7M");
406        tbl.cell(2, 3).unwrap().set_text("$5.1M");
407    }
408
409    doc.add_paragraph("");
410
411    doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
412    {
413        let mut tbl = doc
414            .add_table(4, 3)
415            .borders(BorderStyle::Single, 4, "000000");
416        tbl.cell(0, 0).unwrap().set_text("Category");
417        tbl.cell(0, 0).unwrap().shading("E2EFDA");
418        tbl.cell(0, 1).unwrap().set_text("Item");
419        tbl.cell(0, 1).unwrap().shading("E2EFDA");
420        tbl.cell(0, 2).unwrap().set_text("Price");
421        tbl.cell(0, 2).unwrap().shading("E2EFDA");
422        tbl.cell(1, 0).unwrap().set_text("Hardware");
423        tbl.cell(1, 0).unwrap().v_merge_restart();
424        tbl.cell(1, 1).unwrap().set_text("Laptop");
425        tbl.cell(1, 2).unwrap().set_text("$1,200");
426        tbl.cell(2, 0).unwrap().v_merge_continue();
427        tbl.cell(2, 1).unwrap().set_text("Monitor");
428        tbl.cell(2, 2).unwrap().set_text("$450");
429        tbl.cell(3, 0).unwrap().set_text("Software");
430        tbl.cell(3, 1).unwrap().set_text("IDE License");
431        tbl.cell(3, 2).unwrap().set_text("$200/yr");
432    }
433
434    doc.add_paragraph("");
435
436    doc.add_paragraph("5.4 Nested Table").style("Heading2");
437    {
438        let mut tbl = doc
439            .add_table(2, 2)
440            .borders(BorderStyle::Single, 6, "2E75B6");
441        tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
442        tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
443        tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
444        {
445            let mut cell = tbl.cell(1, 1).unwrap();
446            cell.set_text("Nested table below:");
447            let mut nested = cell
448                .add_table(2, 2)
449                .borders(BorderStyle::Single, 2, "FF6600");
450            nested.cell(0, 0).unwrap().set_text("A");
451            nested.cell(0, 1).unwrap().set_text("B");
452            nested.cell(1, 0).unwrap().set_text("C");
453            nested.cell(1, 1).unwrap().set_text("D");
454        }
455    }
456
457    doc.add_paragraph("");
458
459    doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
460        .style("Heading2");
461    {
462        let mut tbl = doc
463            .add_table(2, 3)
464            .borders(BorderStyle::Single, 4, "666666");
465        tbl.row(0).unwrap().height(Length::pt(50.0)).header();
466        tbl.cell(0, 0).unwrap().set_text("Top");
467        tbl.cell(0, 0)
468            .unwrap()
469            .vertical_alignment(VerticalAlignment::Top)
470            .shading("FFF2CC");
471        tbl.cell(0, 1).unwrap().set_text("Center");
472        tbl.cell(0, 1)
473            .unwrap()
474            .vertical_alignment(VerticalAlignment::Center)
475            .shading("D9E2F3");
476        tbl.cell(0, 2).unwrap().set_text("Bottom");
477        tbl.cell(0, 2)
478            .unwrap()
479            .vertical_alignment(VerticalAlignment::Bottom)
480            .shading("E2EFDA");
481        tbl.row(1).unwrap().cant_split();
482        tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
483        tbl.cell(1, 0).unwrap().no_wrap();
484        tbl.cell(1, 1).unwrap().set_text("Fixed width");
485        tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
486        tbl.cell(1, 2).unwrap().set_text("Normal");
487    }
488
489    // ── SECTION 6: IMAGES ──
490    doc.add_paragraph("").page_break_before(true);
491    doc.add_paragraph("6. Images").style("Heading1");
492
493    doc.add_paragraph("6.1 Inline Image").style("Heading2");
494    doc.add_paragraph("A blue gradient image below:");
495    let img = create_sample_png(200, 50, [0, 80, 200]);
496    doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
497
498    doc.add_paragraph("");
499    doc.add_paragraph("6.2 Header Image").style("Heading2");
500    let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
501    doc.set_header_image(
502        &hdr_img,
503        "header_logo.png",
504        Length::inches(2.0),
505        Length::inches(0.2),
506    );
507    doc.add_paragraph("The header now contains an inline image (check the top of this page).");
508
509    doc.add_paragraph("");
510    doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
511
512    // ── SECTION 7: CONTENT MANIPULATION ──
513    doc.add_paragraph("").page_break_before(true);
514    doc.add_paragraph("7. Content Manipulation")
515        .style("Heading1");
516
517    doc.add_paragraph("7.1 Placeholder Replacement")
518        .style("Heading2");
519    doc.add_paragraph("Customer: {{customer}}");
520    doc.add_paragraph("Date: {{date}}");
521    doc.add_paragraph("Reference: {{ref_number}}");
522    {
523        let mut tbl = doc
524            .add_table(2, 2)
525            .borders(BorderStyle::Single, 4, "000000");
526        tbl.cell(0, 0).unwrap().set_text("Project");
527        tbl.cell(0, 0).unwrap().shading("D6E4F0");
528        tbl.cell(0, 1).unwrap().set_text("{{project}}");
529        tbl.cell(1, 0).unwrap().set_text("Status");
530        tbl.cell(1, 0).unwrap().shading("D6E4F0");
531        tbl.cell(1, 1).unwrap().set_text("{{status}}");
532    }
533
534    let mut replacements = HashMap::new();
535    replacements.insert("{{customer}}", "Tensorbee Inc.");
536    replacements.insert("{{date}}", "February 22, 2026");
537    replacements.insert("{{ref_number}}", "TB-2026-001");
538    replacements.insert("{{project}}", "Infrastructure Upgrade");
539    replacements.insert("{{status}}", "In Progress");
540    let n = doc.replace_all(&replacements);
541    doc.add_paragraph(&format!("({n} placeholders replaced above)"));
542
543    doc.add_paragraph("");
544
545    doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
546    doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
547    let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
548    doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
549
550    doc.add_paragraph("");
551
552    doc.add_paragraph("7.3 Content Insertion").style("Heading2");
553    doc.add_paragraph("Section A: First content.");
554    doc.add_paragraph("Section C: Third content.");
555    if let Some(idx) = doc.find_content_index("Section C") {
556        doc.insert_paragraph(
557            idx,
558            "Section B: Inserted between A and C via find_content_index().",
559        );
560    }
561
562    // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
563    doc.add_paragraph("").section_break(SectionBreak::NextPage);
564    doc.add_paragraph("8. Section Breaks & Orientation")
565        .style("Heading1");
566    doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
567
568    {
569        let mut tbl = doc
570            .add_table(3, 7)
571            .borders(BorderStyle::Single, 4, "2E75B6");
572        let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
573        for (c, h) in headers.iter().enumerate() {
574            tbl.cell(0, c).unwrap().set_text(h);
575            tbl.cell(0, c).unwrap().shading("2E75B6");
576        }
577        let data = [
578            [
579                "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
580            ],
581            [
582                "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
583            ],
584        ];
585        for (r, row) in data.iter().enumerate() {
586            for (c, v) in row.iter().enumerate() {
587                tbl.cell(r + 1, c).unwrap().set_text(v);
588            }
589        }
590    }
591
592    doc.add_paragraph("")
593        .section_break(SectionBreak::NextPage)
594        .section_landscape();
595
596    // ── SECTION 9: CUSTOM STYLES ──
597    doc.add_paragraph("9. Custom Styles").style("Heading1");
598    doc.add_style(
599        StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
600            .based_on("Normal")
601            .paragraph_properties(rdocx_oxml::properties::CT_PPr {
602                shading: Some(rdocx_oxml::properties::CT_Shd {
603                    val: "clear".to_string(),
604                    color: None,
605                    fill: Some("FFF2CC".to_string()),
606                }),
607                ..Default::default()
608            })
609            .run_properties(rdocx_oxml::properties::CT_RPr {
610                bold: Some(true),
611                color: Some("C45911".to_string()),
612                ..Default::default()
613            }),
614    );
615    doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
616        .style("CustomHighlight");
617
618    doc.add_paragraph("");
619
620    // ── SECTION 10: DOCUMENT INTELLIGENCE ──
621    doc.add_paragraph("10. Document Intelligence API")
622        .style("Heading1");
623
624    let wc = doc.word_count();
625    let hc = doc.headings().len();
626    let ic = doc.images().len();
627    let lc = doc.links().len();
628    doc.add_paragraph(&format!("Word count: {wc}"));
629    doc.add_paragraph(&format!("Heading count: {hc}"));
630    doc.add_paragraph(&format!("Image count: {ic}"));
631    doc.add_paragraph(&format!("Link count: {lc}"));
632
633    let outline = doc.document_outline();
634    doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
635
636    let issues = doc.audit_accessibility();
637    doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
638    for issue in &issues {
639        doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
640    }
641
642    // ── SECTION 11: DOCUMENT MERGING ──
643    doc.add_paragraph("");
644    doc.add_paragraph("11. Document Merging").style("Heading1");
645    {
646        let mut other = Document::new();
647        other.add_paragraph("This paragraph was merged from another document using append().");
648        doc.append(&other);
649    }
650
651    doc.add_paragraph("");
652
653    // ── SUMMARY ──
654    doc.add_paragraph("Summary of Demonstrated Features")
655        .style("Heading1");
656    let features = [
657        "Page setup: size, margins, header/footer distance, gutter",
658        "Document metadata: title, author, subject, keywords",
659        "Headers and footers: text, images, different first page",
660        "Background images (full-page behind text)",
661        "Table of Contents generation with bookmarks",
662        "Text formatting: bold, italic, underline styles, strike, color, size, font",
663        "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
664        "Paragraph formatting: alignment, borders, shading, spacing, indentation",
665        "Pagination controls: keep-with-next, keep-together, widow control",
666        "Bullet and numbered lists with nesting",
667        "Tab stops with dot/underscore/hyphen leaders",
668        "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
669        "Row properties: header rows, exact height, cant-split",
670        "Cell properties: width, no-wrap, vertical alignment",
671        "Inline images and header images",
672        "Placeholder replacement (single and batch)",
673        "Regex-based find and replace",
674        "Content insertion at specific positions",
675        "Section breaks with mixed portrait/landscape",
676        "Custom paragraph and character styles",
677        "Document intelligence: word count, headings, outline, images, links",
678        "Accessibility audit",
679        "Document merging (append)",
680        "Export: DOCX, PDF, HTML, Markdown",
681    ];
682    for f in &features {
683        doc.add_bullet_list_item(f, 0);
684    }
685    doc.add_paragraph("");
686    doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
687        .alignment(Alignment::Center)
688        .shading("E2EFDA")
689        .border_all(BorderStyle::Single, 2, "00AA00");
690
691    doc
692}
693
694// =============================================================================
695// 2. PROPOSAL DOCUMENT — Deep navy + gold scheme
696// =============================================================================
697fn generate_proposal(_samples_dir: &Path) -> Document {
698    let mut doc = Document::new();
699
700    // Colors: Navy #1B2A4A, Gold #C5922E, Light #F4F1EB
701    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
702    doc.set_margins(
703        Length::inches(1.0),
704        Length::inches(1.0),
705        Length::inches(1.0),
706        Length::inches(1.0),
707    );
708    doc.set_title("AI Platform Modernization Proposal");
709    doc.set_author("Walter White");
710    doc.set_subject("Technology Proposal");
711    doc.set_keywords("proposal, AI, modernization, Tensorbee");
712
713    // Banner header
714    let logo_img = create_logo_png(220, 48);
715    let banner = build_header_banner_xml(
716        "rId1",
717        &BannerOpts {
718            bg_color: "1B2A4A",
719            banner_width: 7772400,
720            banner_height: 914400,
721            logo_width: 2011680,
722            logo_height: 438912,
723            logo_x_offset: 295125,
724            logo_y_offset: 237744,
725        },
726    );
727    doc.set_raw_header_with_images(
728        banner,
729        &[("rId1", &logo_img, "logo.png")],
730        rdocx_oxml::header_footer::HdrFtrType::Default,
731    );
732    doc.set_different_first_page(true);
733    let cover_banner = build_header_banner_xml(
734        "rId1",
735        &BannerOpts {
736            bg_color: "C5922E",
737            banner_width: 7772400,
738            banner_height: 914400,
739            logo_width: 2011680,
740            logo_height: 438912,
741            logo_x_offset: 295125,
742            logo_y_offset: 237744,
743        },
744    );
745    doc.set_raw_header_with_images(
746        cover_banner,
747        &[("rId1", &logo_img, "logo.png")],
748        rdocx_oxml::header_footer::HdrFtrType::First,
749    );
750    doc.set_margins(
751        Length::twips(2292),
752        Length::twips(1440),
753        Length::twips(1440),
754        Length::twips(1440),
755    );
756    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
757    doc.set_footer("Tensorbee — Confidential");
758
759    // Custom styles
760    doc.add_style(
761        StyleBuilder::paragraph("ProposalTitle", "Proposal Title")
762            .based_on("Normal")
763            .run_properties(rdocx_oxml::properties::CT_RPr {
764                bold: Some(true),
765                font_ascii: Some("Georgia".to_string()),
766                font_hansi: Some("Georgia".to_string()),
767                sz: Some(rdocx_oxml::HalfPoint(56)), // half-points → 28pt
768                color: Some("1B2A4A".to_string()),
769                ..Default::default()
770            }),
771    );
772
773    // ── Cover Page ──
774    for _ in 0..4 {
775        doc.add_paragraph("");
776    }
777    doc.add_paragraph("AI Platform Modernization")
778        .style("ProposalTitle")
779        .alignment(Alignment::Center);
780    doc.add_paragraph("")
781        .alignment(Alignment::Center)
782        .border_bottom(BorderStyle::Single, 8, "C5922E");
783    {
784        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
785        p.add_run("Prepared for Global Financial Corp.")
786            .size(14.0)
787            .color("666666")
788            .italic(true);
789    }
790    {
791        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
792        p.add_run("Prepared by: Walter White, CEO — Tensorbee")
793            .size(12.0)
794            .color("888888");
795    }
796    {
797        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
798        p.add_run("February 22, 2026").size(12.0).color("888888");
799    }
800
801    // ── TOC ──
802    doc.add_paragraph("").page_break_before(true);
803    doc.add_paragraph("Table of Contents").style("Heading1");
804    // We'll add a manual-style TOC since the insert_toc goes at a specific position
805    doc.insert_toc(doc.content_count(), 2);
806
807    // ── Executive Summary ──
808    doc.add_paragraph("").page_break_before(true);
809    doc.add_paragraph("Executive Summary").style("Heading1");
810    doc.add_paragraph(
811        "Tensorbee proposes a comprehensive modernization of Global Financial Corp's AI platform, \
812         transitioning from legacy batch-processing systems to a real-time inference architecture. \
813         This transformation will reduce model deployment time from weeks to hours, improve \
814         prediction accuracy by an estimated 23%, and deliver $4.2M in annual operational savings.",
815    )
816    .first_line_indent(Length::inches(0.3));
817    doc.add_paragraph(
818        "The project spans three phases over 18 months, with a total investment of $2.8M. \
819         Tensorbee brings deep expertise in ML infrastructure, having successfully completed \
820         similar transformations for three Fortune 500 financial institutions.",
821    )
822    .first_line_indent(Length::inches(0.3));
823
824    // Key metrics table
825    doc.add_paragraph("");
826    {
827        let mut tbl = doc
828            .add_table(5, 2)
829            .borders(BorderStyle::Single, 4, "1B2A4A")
830            .width_pct(80.0);
831        tbl.cell(0, 0).unwrap().set_text("Key Metric");
832        tbl.cell(0, 0).unwrap().shading("1B2A4A");
833        tbl.cell(0, 1).unwrap().set_text("Value");
834        tbl.cell(0, 1).unwrap().shading("1B2A4A");
835        let rows = [
836            ("Total Investment", "$2.8M"),
837            ("Annual Savings", "$4.2M"),
838            ("ROI (Year 1)", "150%"),
839            ("Timeline", "18 months"),
840        ];
841        for (i, (k, v)) in rows.iter().enumerate() {
842            tbl.cell(i + 1, 0).unwrap().set_text(k);
843            if i % 2 == 0 {
844                tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
845            }
846            tbl.cell(i + 1, 1).unwrap().set_text(v);
847            if i % 2 == 0 {
848                tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
849            }
850        }
851    }
852
853    // ── Problem Statement ──
854    doc.add_paragraph("").page_break_before(true);
855    doc.add_paragraph("Problem Statement").style("Heading1");
856    doc.add_paragraph(
857        "Global Financial Corp's current AI infrastructure faces three critical challenges:",
858    );
859    doc.add_numbered_list_item("Deployment Latency — New models take 3-4 weeks to deploy to production, compared to the industry benchmark of 2-3 days.", 0);
860    doc.add_numbered_list_item("Scalability Constraints — The batch processing architecture cannot handle real-time inference demands during peak trading hours.", 0);
861    doc.add_numbered_list_item("Technical Debt — Legacy Python 2.7 codebases and manual deployment scripts create reliability risks and slow iteration speed.", 0);
862
863    // ── Proposed Solution ──
864    doc.add_paragraph("").page_break_before(true);
865    doc.add_paragraph("Proposed Solution").style("Heading1");
866
867    doc.add_paragraph("Phase 1: Foundation (Months 1-6)")
868        .style("Heading2");
869    doc.add_bullet_list_item("Deploy Kubernetes-based ML serving infrastructure", 0);
870    doc.add_bullet_list_item("Implement CI/CD pipeline for model deployment", 0);
871    doc.add_bullet_list_item("Migrate top 5 production models to new platform", 0);
872
873    doc.add_paragraph("Phase 2: Expansion (Months 7-12)")
874        .style("Heading2");
875    doc.add_bullet_list_item(
876        "Real-time feature engineering pipeline (Apache Kafka + Flink)",
877        0,
878    );
879    doc.add_bullet_list_item("A/B testing framework for model variants", 0);
880    doc.add_bullet_list_item("Automated model monitoring and drift detection", 0);
881
882    doc.add_paragraph("Phase 3: Optimization (Months 13-18)")
883        .style("Heading2");
884    doc.add_bullet_list_item("GPU inference optimization (TensorRT/ONNX Runtime)", 0);
885    doc.add_bullet_list_item("Multi-region deployment for latency reduction", 0);
886    doc.add_bullet_list_item(
887        "Self-service model deployment portal for data science teams",
888        0,
889    );
890
891    // ── Budget ──
892    doc.add_paragraph("Budget Breakdown").style("Heading1");
893    {
894        let mut tbl = doc
895            .add_table(6, 3)
896            .borders(BorderStyle::Single, 4, "1B2A4A")
897            .width_pct(100.0);
898        tbl.cell(0, 0).unwrap().set_text("Category");
899        tbl.cell(0, 0).unwrap().shading("1B2A4A");
900        tbl.cell(0, 1).unwrap().set_text("Cost");
901        tbl.cell(0, 1).unwrap().shading("1B2A4A");
902        tbl.cell(0, 2).unwrap().set_text("Notes");
903        tbl.cell(0, 2).unwrap().shading("1B2A4A");
904        let items = [
905            (
906                "Infrastructure",
907                "$450,000",
908                "Cloud compute + GPU instances",
909            ),
910            ("Engineering Services", "$1,600,000", "12 FTE x 18 months"),
911            (
912                "Software Licenses",
913                "$350,000",
914                "Kafka, monitoring, CI/CD tools",
915            ),
916            (
917                "Training & Enablement",
918                "$200,000",
919                "Team training, documentation",
920            ),
921            ("Contingency (10%)", "$200,000", "Risk buffer"),
922        ];
923        for (i, (cat, cost, note)) in items.iter().enumerate() {
924            tbl.cell(i + 1, 0).unwrap().set_text(cat);
925            tbl.cell(i + 1, 1).unwrap().set_text(cost);
926            tbl.cell(i + 1, 2).unwrap().set_text(note);
927            if i % 2 == 0 {
928                tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
929                tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
930                tbl.cell(i + 1, 2).unwrap().shading("F4F1EB");
931            }
932        }
933    }
934
935    // ── Closing ──
936    doc.add_paragraph("");
937    doc.add_paragraph("Next Steps").style("Heading1");
938    doc.add_numbered_list_item(
939        "Schedule technical deep-dive with GFC engineering team (Week 1)",
940        0,
941    );
942    doc.add_numbered_list_item("Finalize scope and sign SOW (Week 2-3)", 0);
943    doc.add_numbered_list_item("Kick off Phase 1 with joint planning session (Week 4)", 0);
944
945    doc.add_paragraph("");
946    {
947        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
948        p.add_run("Walter White")
949            .bold(true)
950            .size(14.0)
951            .color("1B2A4A");
952    }
953    {
954        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
955        p.add_run("CEO, Tensorbee | walter@tensorbee.com")
956            .size(10.0)
957            .color("888888");
958    }
959
960    doc
961}
962
963// =============================================================================
964// 3. QUOTE / BILL OF MATERIALS — Teal + orange scheme
965// =============================================================================
966fn generate_quote(_samples_dir: &Path) -> Document {
967    let mut doc = Document::new();
968
969    // Colors: Teal #008B8B, Dark #1A3C3C, Orange #E07020, Light #F0F8F8
970    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
971    doc.set_margins(
972        Length::inches(0.75),
973        Length::inches(0.75),
974        Length::inches(0.75),
975        Length::inches(0.75),
976    );
977    doc.set_title("Quotation QT-2026-0147");
978    doc.set_author("Walter White");
979    doc.set_keywords("quote, BOM, Tensorbee");
980
981    doc.set_header("Tensorbee — Quotation");
982    doc.set_footer("QT-2026-0147 | Page");
983
984    // ── Header Block ──
985    {
986        let mut p = doc.add_paragraph("").alignment(Alignment::Left);
987        p.add_run("TENSORBEE")
988            .bold(true)
989            .size(28.0)
990            .color("008B8B")
991            .font("Helvetica");
992    }
993    doc.add_paragraph("123 Innovation Drive, Suite 400")
994        .alignment(Alignment::Left);
995    doc.add_paragraph("San Francisco, CA 94105 | +1 (415) 555-0199")
996        .alignment(Alignment::Left);
997    doc.add_paragraph("")
998        .border_bottom(BorderStyle::Single, 8, "008B8B");
999
1000    // Quote meta
1001    doc.add_paragraph("");
1002    {
1003        let mut tbl = doc.add_table(4, 4).width_pct(100.0);
1004        tbl.cell(0, 0).unwrap().set_text("QUOTATION");
1005        tbl.cell(0, 0).unwrap().shading("008B8B").grid_span(2);
1006        tbl.cell(0, 2).unwrap().set_text("DATE");
1007        tbl.cell(0, 2).unwrap().shading("008B8B");
1008        tbl.cell(0, 3).unwrap().set_text("VALID UNTIL");
1009        tbl.cell(0, 3).unwrap().shading("008B8B");
1010        tbl.cell(1, 0).unwrap().set_text("Quote #:");
1011        tbl.cell(1, 1).unwrap().set_text("QT-2026-0147");
1012        tbl.cell(1, 2).unwrap().set_text("Feb 22, 2026");
1013        tbl.cell(1, 3).unwrap().set_text("Mar 22, 2026");
1014        tbl.cell(2, 0).unwrap().set_text("Prepared by:");
1015        tbl.cell(2, 1).unwrap().set_text("Walter White");
1016        tbl.cell(2, 2).unwrap().set_text("Payment:");
1017        tbl.cell(2, 3).unwrap().set_text("Net 30");
1018        tbl.cell(3, 0).unwrap().set_text("Customer:");
1019        tbl.cell(3, 1).unwrap().set_text("Meridian Dynamics LLC");
1020        tbl.cell(3, 2).unwrap().set_text("Currency:");
1021        tbl.cell(3, 3).unwrap().set_text("USD");
1022    }
1023
1024    doc.add_paragraph("");
1025
1026    // ── Bill of Materials ──
1027    {
1028        let mut p = doc.add_paragraph("");
1029        p.add_run("Bill of Materials")
1030            .bold(true)
1031            .size(16.0)
1032            .color("1A3C3C");
1033    }
1034    doc.add_paragraph("");
1035
1036    {
1037        let mut tbl = doc
1038            .add_table(10, 6)
1039            .borders(BorderStyle::Single, 2, "008B8B")
1040            .width_pct(100.0)
1041            .layout_fixed();
1042        // Headers
1043        let hdrs = [
1044            "#",
1045            "Part Number",
1046            "Description",
1047            "Qty",
1048            "Unit Price",
1049            "Total",
1050        ];
1051        for (c, h) in hdrs.iter().enumerate() {
1052            tbl.cell(0, c).unwrap().set_text(h);
1053            tbl.cell(0, c).unwrap().shading("008B8B");
1054        }
1055
1056        let items: Vec<(&str, &str, &str, &str, &str)> = vec![
1057            (
1058                "TB-GPU-A100",
1059                "NVIDIA A100 80GB GPU",
1060                "4",
1061                "$12,500.00",
1062                "$50,000.00",
1063            ),
1064            (
1065                "TB-SRV-R750",
1066                "Dell R750xa Server Chassis",
1067                "2",
1068                "$8,200.00",
1069                "$16,400.00",
1070            ),
1071            (
1072                "TB-RAM-256G",
1073                "256GB DDR5 ECC Memory Module",
1074                "8",
1075                "$890.00",
1076                "$7,120.00",
1077            ),
1078            (
1079                "TB-SSD-3840",
1080                "3.84TB NVMe U.2 SSD",
1081                "8",
1082                "$1,150.00",
1083                "$9,200.00",
1084            ),
1085            (
1086                "TB-NET-CX7",
1087                "ConnectX-7 200GbE NIC",
1088                "4",
1089                "$1,800.00",
1090                "$7,200.00",
1091            ),
1092            (
1093                "TB-SW-48P",
1094                "48-Port 100GbE Switch",
1095                "1",
1096                "$22,000.00",
1097                "$22,000.00",
1098            ),
1099            (
1100                "TB-CAB-RACK",
1101                "42U Server Rack + PDU",
1102                "1",
1103                "$4,500.00",
1104                "$4,500.00",
1105            ),
1106            (
1107                "TB-SVC-INST",
1108                "Installation & Configuration",
1109                "1",
1110                "$8,500.00",
1111                "$8,500.00",
1112            ),
1113            (
1114                "TB-SVC-SUPP",
1115                "3-Year Premium Support",
1116                "1",
1117                "$15,000.00",
1118                "$15,000.00",
1119            ),
1120        ];
1121
1122        for (i, (pn, desc, qty, unit, total)) in items.iter().enumerate() {
1123            let row = i + 1;
1124            tbl.cell(row, 0).unwrap().set_text(&format!("{}", i + 1));
1125            tbl.cell(row, 1).unwrap().set_text(pn);
1126            tbl.cell(row, 2).unwrap().set_text(desc);
1127            tbl.cell(row, 3).unwrap().set_text(qty);
1128            tbl.cell(row, 4).unwrap().set_text(unit);
1129            tbl.cell(row, 5).unwrap().set_text(total);
1130            if i % 2 == 0 {
1131                for c in 0..6 {
1132                    tbl.cell(row, c).unwrap().shading("F0F8F8");
1133                }
1134            }
1135        }
1136    }
1137
1138    doc.add_paragraph("");
1139
1140    // ── Totals ──
1141    {
1142        let mut tbl = doc
1143            .add_table(4, 2)
1144            .borders(BorderStyle::Single, 2, "008B8B")
1145            .width(Length::inches(3.5))
1146            .alignment(Alignment::Right);
1147        tbl.cell(0, 0).unwrap().set_text("Subtotal");
1148        tbl.cell(0, 1).unwrap().set_text("$139,920.00");
1149        tbl.cell(1, 0).unwrap().set_text("Shipping & Handling");
1150        tbl.cell(1, 1).unwrap().set_text("$2,500.00");
1151        tbl.cell(2, 0).unwrap().set_text("Tax (8.625%)");
1152        tbl.cell(2, 1).unwrap().set_text("$12,068.10");
1153        tbl.cell(3, 0).unwrap().set_text("TOTAL");
1154        tbl.cell(3, 0).unwrap().shading("E07020");
1155        tbl.cell(3, 1).unwrap().set_text("$154,488.10");
1156        tbl.cell(3, 1).unwrap().shading("E07020");
1157    }
1158
1159    doc.add_paragraph("");
1160
1161    // ── Terms & Conditions ──
1162    {
1163        let mut p = doc.add_paragraph("");
1164        p.add_run("Terms & Conditions")
1165            .bold(true)
1166            .size(14.0)
1167            .color("1A3C3C");
1168    }
1169    doc.add_numbered_list_item(
1170        "This quotation is valid for 30 calendar days from the date of issue.",
1171        0,
1172    );
1173    doc.add_numbered_list_item(
1174        "All prices are in USD and exclusive of applicable taxes unless stated.",
1175        0,
1176    );
1177    doc.add_numbered_list_item("Standard lead time is 4-6 weeks from PO receipt.", 0);
1178    doc.add_numbered_list_item(
1179        "Payment terms: Net 30 from invoice date. 2% discount for payment within 10 days.",
1180        0,
1181    );
1182    doc.add_numbered_list_item(
1183        "Warranty: 3-year manufacturer warranty on all hardware components.",
1184        0,
1185    );
1186    doc.add_numbered_list_item(
1187        "Returns subject to 15% restocking fee if initiated after 14 days.",
1188        0,
1189    );
1190
1191    doc.add_paragraph("");
1192    doc.add_paragraph("")
1193        .border_bottom(BorderStyle::Single, 4, "008B8B");
1194    doc.add_paragraph("");
1195    {
1196        let mut p = doc.add_paragraph("");
1197        p.add_run("Accepted by: ").bold(true);
1198        p.add_run("___________________________ Date: ___________");
1199    }
1200    {
1201        let mut p = doc.add_paragraph("");
1202        p.add_run("Print Name: ").bold(true);
1203        p.add_run("___________________________ Title: ___________");
1204    }
1205
1206    doc
1207}
1208
1209// =============================================================================
1210// 4. INVOICE — Crimson + charcoal scheme
1211// =============================================================================
1212fn generate_invoice(_samples_dir: &Path) -> Document {
1213    let mut doc = Document::new();
1214
1215    // Colors: Crimson #B22222, Charcoal #333333, Light #FAF0F0
1216    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1217    doc.set_margins(
1218        Length::inches(0.75),
1219        Length::inches(0.75),
1220        Length::inches(0.75),
1221        Length::inches(0.75),
1222    );
1223    doc.set_title("Invoice INV-2026-0392");
1224    doc.set_author("Walter White");
1225    doc.set_keywords("invoice, Tensorbee");
1226
1227    doc.set_footer("Tensorbee — Thank you for your business!");
1228
1229    // ── Company & Invoice Header ──
1230    {
1231        let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1232        // Company name left, INVOICE right
1233        {
1234            let mut cell = tbl.cell(0, 0).unwrap();
1235            let mut p = cell.add_paragraph("");
1236            p.add_run("TENSORBEE")
1237                .bold(true)
1238                .size(32.0)
1239                .color("B22222")
1240                .font("Helvetica");
1241        }
1242        {
1243            let mut cell = tbl.cell(0, 1).unwrap();
1244            let mut p = cell.add_paragraph("");
1245            p.add_run("INVOICE").bold(true).size(32.0).color("333333");
1246        }
1247        tbl.cell(1, 0)
1248            .unwrap()
1249            .set_text("123 Innovation Drive, Suite 400");
1250        tbl.cell(1, 1).unwrap().set_text("Invoice #: INV-2026-0392");
1251        tbl.cell(2, 0).unwrap().set_text("San Francisco, CA 94105");
1252        tbl.cell(2, 1).unwrap().set_text("Date: February 22, 2026");
1253        tbl.cell(3, 0).unwrap().set_text("walter@tensorbee.com");
1254        tbl.cell(3, 1).unwrap().set_text("Due Date: March 24, 2026");
1255    }
1256
1257    doc.add_paragraph("")
1258        .border_bottom(BorderStyle::Thick, 8, "B22222");
1259    doc.add_paragraph("");
1260
1261    // ── Bill To / Ship To ──
1262    {
1263        let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1264        tbl.cell(0, 0).unwrap().set_text("BILL TO");
1265        tbl.cell(0, 0).unwrap().shading("B22222");
1266        tbl.cell(0, 1).unwrap().set_text("SHIP TO");
1267        tbl.cell(0, 1).unwrap().shading("B22222");
1268        tbl.cell(1, 0).unwrap().set_text("Meridian Dynamics LLC");
1269        tbl.cell(1, 1).unwrap().set_text("Meridian Dynamics LLC");
1270        tbl.cell(2, 0).unwrap().set_text("456 Enterprise Blvd");
1271        tbl.cell(2, 1).unwrap().set_text("Attn: Server Room B");
1272        tbl.cell(3, 0).unwrap().set_text("Austin, TX 78701");
1273        tbl.cell(3, 1)
1274            .unwrap()
1275            .set_text("456 Enterprise Blvd, Austin, TX 78701");
1276    }
1277
1278    doc.add_paragraph("");
1279
1280    // ── Line Items ──
1281    {
1282        let mut tbl = doc
1283            .add_table(8, 5)
1284            .borders(BorderStyle::Single, 2, "B22222")
1285            .width_pct(100.0);
1286        let hdrs = ["Description", "Qty", "Unit Price", "Tax", "Amount"];
1287        for (c, h) in hdrs.iter().enumerate() {
1288            tbl.cell(0, c).unwrap().set_text(h);
1289            tbl.cell(0, c).unwrap().shading("B22222");
1290        }
1291        let items = [
1292            (
1293                "ML Infrastructure Setup — Phase 1",
1294                "1",
1295                "$45,000.00",
1296                "$3,881.25",
1297                "$48,881.25",
1298            ),
1299            (
1300                "Data Pipeline Development (160 hrs)",
1301                "160",
1302                "$225.00",
1303                "$3,105.00",
1304                "$39,105.00",
1305            ),
1306            (
1307                "GPU Cluster Configuration",
1308                "1",
1309                "$12,000.00",
1310                "$1,035.00",
1311                "$13,035.00",
1312            ),
1313            (
1314                "API Gateway Implementation",
1315                "1",
1316                "$18,500.00",
1317                "$1,595.63",
1318                "$20,095.63",
1319            ),
1320            (
1321                "Load Testing & QA (80 hrs)",
1322                "80",
1323                "$195.00",
1324                "$1,345.50",
1325                "$16,945.50",
1326            ),
1327            (
1328                "Documentation & Training",
1329                "1",
1330                "$8,000.00",
1331                "$690.00",
1332                "$8,690.00",
1333            ),
1334            (
1335                "Project Management (3 months)",
1336                "3",
1337                "$6,500.00",
1338                "$1,679.63",
1339                "$21,179.63",
1340            ),
1341        ];
1342        for (i, (desc, qty, unit, tax, amt)) in items.iter().enumerate() {
1343            let r = i + 1;
1344            tbl.cell(r, 0).unwrap().set_text(desc);
1345            tbl.cell(r, 1).unwrap().set_text(qty);
1346            tbl.cell(r, 2).unwrap().set_text(unit);
1347            tbl.cell(r, 3).unwrap().set_text(tax);
1348            tbl.cell(r, 4).unwrap().set_text(amt);
1349            if i % 2 == 0 {
1350                for c in 0..5 {
1351                    tbl.cell(r, c).unwrap().shading("FAF0F0");
1352                }
1353            }
1354        }
1355    }
1356
1357    doc.add_paragraph("");
1358
1359    // ── Totals ──
1360    {
1361        let mut tbl = doc
1362            .add_table(4, 2)
1363            .borders(BorderStyle::Single, 2, "B22222")
1364            .width(Length::inches(3.0))
1365            .alignment(Alignment::Right);
1366        tbl.cell(0, 0).unwrap().set_text("Subtotal");
1367        tbl.cell(0, 1).unwrap().set_text("$154,600.00");
1368        tbl.cell(1, 0).unwrap().set_text("Tax (8.625%)");
1369        tbl.cell(1, 1).unwrap().set_text("$13,334.25");
1370        tbl.cell(2, 0).unwrap().set_text("Discount (5%)");
1371        tbl.cell(2, 1).unwrap().set_text("-$7,730.00");
1372        tbl.cell(3, 0).unwrap().set_text("AMOUNT DUE");
1373        tbl.cell(3, 0).unwrap().shading("B22222");
1374        tbl.cell(3, 1).unwrap().set_text("$160,204.25");
1375        tbl.cell(3, 1).unwrap().shading("B22222");
1376    }
1377
1378    doc.add_paragraph("");
1379    doc.add_paragraph("");
1380
1381    // ── Payment Details ──
1382    {
1383        let mut p = doc.add_paragraph("");
1384        p.add_run("Payment Details")
1385            .bold(true)
1386            .size(14.0)
1387            .color("333333");
1388    }
1389    doc.add_paragraph("Bank: Silicon Valley Bank")
1390        .indent_left(Length::inches(0.3));
1391    doc.add_paragraph("Account: Tensorbee Inc. — 0847-2953-1120")
1392        .indent_left(Length::inches(0.3));
1393    doc.add_paragraph("Routing: 121140399")
1394        .indent_left(Length::inches(0.3));
1395    doc.add_paragraph("Swift: SVBKUS6S")
1396        .indent_left(Length::inches(0.3));
1397
1398    doc.add_paragraph("");
1399
1400    doc.add_paragraph("Please include invoice number INV-2026-0392 in the payment reference.")
1401        .shading("FAF0F0")
1402        .border_all(BorderStyle::Single, 2, "B22222");
1403
1404    doc
1405}
1406
1407// =============================================================================
1408// 5. REPORT — Forest green + earth tones, with images & hierarchical sections
1409// =============================================================================
1410fn generate_report(_samples_dir: &Path) -> Document {
1411    let mut doc = Document::new();
1412
1413    // Colors: Forest #2D5016, Sage #6B8E23, Earth #8B7355, Cream #FFFAF0
1414    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1415    doc.set_margins(
1416        Length::inches(1.0),
1417        Length::inches(1.0),
1418        Length::inches(1.0),
1419        Length::inches(1.0),
1420    );
1421    doc.set_title("Q4 2025 Environmental Impact Report");
1422    doc.set_author("Walter White");
1423    doc.set_subject("Quarterly Environmental Report");
1424    doc.set_keywords("environment, sustainability, report, Tensorbee");
1425
1426    doc.set_different_first_page(true);
1427    doc.set_first_page_header("");
1428    doc.set_header("Tensorbee — Q4 2025 Environmental Impact Report");
1429    doc.set_footer("CONFIDENTIAL — Page");
1430
1431    // ── Cover ──
1432    let cover_bg = create_sample_png(612, 792, [20, 50, 15]);
1433    doc.add_background_image(&cover_bg, "report_cover.png");
1434
1435    for _ in 0..5 {
1436        doc.add_paragraph("");
1437    }
1438    {
1439        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1440        p.add_run("Q4 2025")
1441            .bold(true)
1442            .size(48.0)
1443            .color("FFFFFF")
1444            .font("Georgia");
1445    }
1446    {
1447        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1448        p.add_run("Environmental Impact Report")
1449            .size(24.0)
1450            .color("90EE90")
1451            .font("Georgia")
1452            .italic(true);
1453    }
1454    doc.add_paragraph("");
1455    {
1456        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1457        p.add_run("Tensorbee — Sustainability Division")
1458            .size(14.0)
1459            .color("C0C0C0");
1460    }
1461    {
1462        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1463        p.add_run("Prepared by Walter White, Chief Sustainability Officer")
1464            .size(11.0)
1465            .color("AAAAAA");
1466    }
1467
1468    // ── TOC ──
1469    doc.add_paragraph("").page_break_before(true);
1470    doc.insert_toc(doc.content_count(), 3);
1471
1472    // ── Section 1: Executive Overview ──
1473    doc.add_paragraph("").page_break_before(true);
1474    doc.add_paragraph("Executive Overview").style("Heading1");
1475    doc.add_paragraph(
1476        "This report presents Tensorbee's environmental performance for Q4 2025. Our \
1477         sustainability initiatives have yielded a 34% reduction in carbon emissions compared \
1478         to Q4 2024, exceeding our target of 25%. Key achievements include the transition to \
1479         100% renewable energy in our primary data centers and the launch of our carbon offset \
1480         marketplace.",
1481    )
1482    .first_line_indent(Length::inches(0.3));
1483
1484    // Image: performance chart
1485    let chart_img = create_chart_png(400, 200);
1486    doc.add_paragraph("");
1487    doc.add_picture(
1488        &chart_img,
1489        "performance_chart.png",
1490        Length::inches(5.0),
1491        Length::inches(2.5),
1492    )
1493    .alignment(Alignment::Center);
1494    {
1495        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1496        p.add_run("Figure 1: Quarterly Carbon Emissions (tonnes CO2e)")
1497            .italic(true)
1498            .size(9.0)
1499            .color("666666");
1500    }
1501
1502    // ── Section 2: Energy Consumption ──
1503    doc.add_paragraph("").page_break_before(true);
1504    doc.add_paragraph("Energy Consumption").style("Heading1");
1505
1506    doc.add_paragraph("Data Center Operations")
1507        .style("Heading2");
1508    doc.add_paragraph(
1509        "Our three primary data centers consumed a combined 4.2 GWh in Q4 2025, \
1510         a 12% reduction from Q3 2025 driven by improved cooling efficiency and \
1511         server consolidation.",
1512    );
1513
1514    // Data table
1515    {
1516        let mut tbl = doc
1517            .add_table(5, 4)
1518            .borders(BorderStyle::Single, 4, "2D5016")
1519            .width_pct(100.0);
1520        let hdrs = ["Data Center", "Capacity (MW)", "Usage (GWh)", "PUE"];
1521        for (c, h) in hdrs.iter().enumerate() {
1522            tbl.cell(0, c).unwrap().set_text(h);
1523            tbl.cell(0, c).unwrap().shading("2D5016");
1524        }
1525        let rows = [
1526            ("San Francisco (Primary)", "3.2", "1.8", "1.12"),
1527            ("Dublin (EU)", "2.1", "1.2", "1.18"),
1528            ("Singapore (APAC)", "1.8", "1.2", "1.24"),
1529            ("TOTAL", "7.1", "4.2", "1.17 avg"),
1530        ];
1531        for (i, (dc, cap, use_, pue)) in rows.iter().enumerate() {
1532            tbl.cell(i + 1, 0).unwrap().set_text(dc);
1533            tbl.cell(i + 1, 1).unwrap().set_text(cap);
1534            tbl.cell(i + 1, 2).unwrap().set_text(use_);
1535            tbl.cell(i + 1, 3).unwrap().set_text(pue);
1536            if i == 3 {
1537                for c in 0..4 {
1538                    tbl.cell(i + 1, c).unwrap().shading("E2EFDA");
1539                }
1540            }
1541        }
1542    }
1543
1544    doc.add_paragraph("");
1545
1546    doc.add_paragraph("Renewable Energy Mix").style("Heading2");
1547    doc.add_paragraph("Breakdown of energy sources across all facilities:");
1548
1549    doc.add_bullet_list_item("Solar PV: 42% (1.76 GWh)", 0);
1550    doc.add_bullet_list_item("Wind Power Purchase Agreements: 38% (1.60 GWh)", 0);
1551    doc.add_bullet_list_item("Hydroelectric: 12% (0.50 GWh)", 0);
1552    doc.add_bullet_list_item("Grid (non-renewable): 8% (0.34 GWh)", 0);
1553
1554    // ── Section 3: Water & Waste ──
1555    doc.add_paragraph("Water & Waste Management")
1556        .style("Heading1");
1557
1558    doc.add_paragraph("Water Usage").style("Heading2");
1559    doc.add_paragraph(
1560        "Total water consumption was 12.4 million gallons, a 15% reduction from Q3. \
1561         Our closed-loop cooling systems now recycle 78% of water used in cooling operations.",
1562    );
1563
1564    doc.add_paragraph("Waste Reduction").style("Heading2");
1565    doc.add_paragraph("E-waste management results for Q4:")
1566        .keep_with_next(true);
1567    {
1568        let mut tbl = doc
1569            .add_table(4, 3)
1570            .borders(BorderStyle::Single, 2, "6B8E23")
1571            .width_pct(80.0);
1572        tbl.cell(0, 0).unwrap().set_text("Category");
1573        tbl.cell(0, 0).unwrap().shading("6B8E23");
1574        tbl.cell(0, 1).unwrap().set_text("Weight (kg)");
1575        tbl.cell(0, 1).unwrap().shading("6B8E23");
1576        tbl.cell(0, 2).unwrap().set_text("Recycled %");
1577        tbl.cell(0, 2).unwrap().shading("6B8E23");
1578        let rows = [
1579            ("Server Hardware", "2,450", "94%"),
1580            ("Networking Equipment", "820", "91%"),
1581            ("Storage Media", "340", "99%"),
1582        ];
1583        for (i, (cat, wt, pct)) in rows.iter().enumerate() {
1584            tbl.cell(i + 1, 0).unwrap().set_text(cat);
1585            tbl.cell(i + 1, 1).unwrap().set_text(wt);
1586            tbl.cell(i + 1, 2).unwrap().set_text(pct);
1587        }
1588    }
1589
1590    // ── Section 4: Initiatives ──
1591    doc.add_paragraph("").page_break_before(true);
1592    doc.add_paragraph("Q1 2026 Initiatives").style("Heading1");
1593
1594    doc.add_paragraph("Planned Programs").style("Heading2");
1595    doc.add_numbered_list_item(
1596        "Deploy on-site battery storage at SF data center (2 MWh capacity)",
1597        0,
1598    );
1599    doc.add_numbered_list_item("Pilot immersion cooling in Dublin facility", 0);
1600    doc.add_numbered_list_item("Launch employee commute carbon offset program", 0);
1601    doc.add_numbered_list_item("Achieve ISO 14001 certification for Singapore facility", 0);
1602
1603    doc.add_paragraph("Investment Targets").style("Heading2");
1604    doc.add_paragraph("Sustainability CapEx allocation for FY2026:")
1605        .keep_with_next(true);
1606    {
1607        let mut tbl = doc
1608            .add_table(5, 2)
1609            .borders(BorderStyle::Single, 4, "2D5016");
1610        tbl.cell(0, 0).unwrap().set_text("Initiative");
1611        tbl.cell(0, 0).unwrap().shading("2D5016");
1612        tbl.cell(0, 1).unwrap().set_text("Budget");
1613        tbl.cell(0, 1).unwrap().shading("2D5016");
1614        let rows = [
1615            ("Battery Storage", "$1.2M"),
1616            ("Immersion Cooling Pilot", "$800K"),
1617            ("Solar Panel Expansion", "$2.1M"),
1618            ("Carbon Credits", "$500K"),
1619        ];
1620        for (i, (init, budget)) in rows.iter().enumerate() {
1621            tbl.cell(i + 1, 0).unwrap().set_text(init);
1622            tbl.cell(i + 1, 1).unwrap().set_text(budget);
1623            if i % 2 == 0 {
1624                tbl.cell(i + 1, 0).unwrap().shading("FFFAF0");
1625                tbl.cell(i + 1, 1).unwrap().shading("FFFAF0");
1626            }
1627        }
1628    }
1629
1630    // Image: sustainability roadmap
1631    let roadmap_img = create_sample_png(500, 100, [40, 80, 30]);
1632    doc.add_paragraph("");
1633    doc.add_picture(
1634        &roadmap_img,
1635        "roadmap.png",
1636        Length::inches(6.0),
1637        Length::inches(1.2),
1638    )
1639    .alignment(Alignment::Center);
1640    {
1641        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1642        p.add_run("Figure 2: 2026 Sustainability Roadmap")
1643            .italic(true)
1644            .size(9.0)
1645            .color("666666");
1646    }
1647
1648    doc.add_paragraph("");
1649    doc.add_paragraph(
1650        "For questions about this report, contact Walter White at sustainability@tensorbee.com.",
1651    )
1652    .shading("FFFAF0")
1653    .border_all(BorderStyle::Single, 2, "2D5016");
1654
1655    doc
1656}
1657
1658// =============================================================================
1659// 6. LETTER — Slate blue + silver scheme
1660// =============================================================================
1661fn generate_letter(_samples_dir: &Path) -> Document {
1662    let mut doc = Document::new();
1663
1664    // Colors: Slate #4A5568, Blue accent #3182CE
1665    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1666    doc.set_margins(
1667        Length::inches(1.25),
1668        Length::inches(1.25),
1669        Length::inches(1.25),
1670        Length::inches(1.25),
1671    );
1672    doc.set_title("Business Letter");
1673    doc.set_author("Walter White");
1674
1675    // Banner header using raw XML
1676    let logo_img = create_logo_png(220, 48);
1677    let banner = build_header_banner_xml(
1678        "rId1",
1679        &BannerOpts {
1680            bg_color: "4A5568",
1681            banner_width: 7772400,
1682            banner_height: 731520, // ~0.8"
1683            logo_width: 2011680,
1684            logo_height: 438912,
1685            logo_x_offset: 295125,
1686            logo_y_offset: 146304,
1687        },
1688    );
1689    doc.set_raw_header_with_images(
1690        banner,
1691        &[("rId1", &logo_img, "logo.png")],
1692        rdocx_oxml::header_footer::HdrFtrType::Default,
1693    );
1694    doc.set_margins(
1695        Length::twips(2000),
1696        Length::twips(1800),
1697        Length::twips(1440),
1698        Length::twips(1800),
1699    );
1700    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
1701
1702    // Footer with contact
1703    doc.set_footer(
1704        "Tensorbee | 123 Innovation Drive, Suite 400 | San Francisco, CA 94105 | tensorbee.com",
1705    );
1706
1707    // ── Sender Address ──
1708    doc.add_paragraph("Walter White");
1709    doc.add_paragraph("Chief Executive Officer");
1710    doc.add_paragraph("Tensorbee");
1711    doc.add_paragraph("123 Innovation Drive, Suite 400");
1712    doc.add_paragraph("San Francisco, CA 94105");
1713    doc.add_paragraph("");
1714    doc.add_paragraph("February 22, 2026");
1715
1716    doc.add_paragraph("");
1717
1718    // ── Recipient ──
1719    doc.add_paragraph("Ms. Sarah Chen");
1720    doc.add_paragraph("VP of Engineering");
1721    doc.add_paragraph("Meridian Dynamics LLC");
1722    doc.add_paragraph("456 Enterprise Boulevard");
1723    doc.add_paragraph("Austin, TX 78701");
1724
1725    doc.add_paragraph("");
1726
1727    // ── Subject ──
1728    {
1729        let mut p = doc.add_paragraph("");
1730        p.add_run("Re: Strategic Technology Partnership — Phase 2 Expansion")
1731            .bold(true);
1732    }
1733
1734    doc.add_paragraph("");
1735
1736    // ── Body ──
1737    doc.add_paragraph("Dear Ms. Chen,");
1738    doc.add_paragraph("");
1739
1740    doc.add_paragraph(
1741        "I am writing to express Tensorbee's enthusiasm for expanding our strategic technology \
1742         partnership with Meridian Dynamics. Following the successful completion of Phase 1, \
1743         which delivered a 40% improvement in your ML inference pipeline throughput, we believe \
1744         the foundation is firmly established for an ambitious Phase 2 engagement.",
1745    )
1746    .first_line_indent(Length::inches(0.5));
1747
1748    doc.add_paragraph(
1749        "During our recent executive review, your team highlighted three priority areas for \
1750         the next phase of collaboration:",
1751    )
1752    .first_line_indent(Length::inches(0.5));
1753
1754    doc.add_numbered_list_item(
1755        "Real-time anomaly detection for your financial transaction monitoring system, \
1756         targeting sub-100ms latency at 50,000 transactions per second.",
1757        0,
1758    );
1759    doc.add_numbered_list_item(
1760        "Federated learning infrastructure to enable model training across your distributed \
1761         data centers without centralizing sensitive financial data.",
1762        0,
1763    );
1764    doc.add_numbered_list_item(
1765        "MLOps automation to reduce your model deployment cycle from the current 5 days \
1766         to under 4 hours.",
1767        0,
1768    );
1769
1770    doc.add_paragraph(
1771        "Our engineering team has prepared a detailed technical proposal addressing each of \
1772         these areas. We have allocated a dedicated team of eight senior engineers, led by \
1773         our CTO, to ensure continuity with the Phase 1 team your organization has already \
1774         built a productive working relationship with.",
1775    )
1776    .first_line_indent(Length::inches(0.5));
1777
1778    doc.add_paragraph(
1779        "I would welcome the opportunity to present our Phase 2 proposal in person. My \
1780         assistant will reach out to coordinate a meeting at your convenience during the \
1781         first week of March.",
1782    )
1783    .first_line_indent(Length::inches(0.5));
1784
1785    doc.add_paragraph("");
1786
1787    doc.add_paragraph("Warm regards,");
1788    doc.add_paragraph("");
1789    doc.add_paragraph("");
1790
1791    // Signature line
1792    doc.add_paragraph("")
1793        .border_bottom(BorderStyle::Single, 4, "4A5568");
1794    {
1795        let mut p = doc.add_paragraph("");
1796        p.add_run("Walter White").bold(true).size(12.0);
1797    }
1798    {
1799        let mut p = doc.add_paragraph("");
1800        p.add_run("Chief Executive Officer, Tensorbee")
1801            .italic(true)
1802            .size(10.0)
1803            .color("666666");
1804    }
1805    {
1806        let mut p = doc.add_paragraph("");
1807        p.add_run("walter@tensorbee.com | +1 (415) 555-0199")
1808            .size(10.0)
1809            .color("888888");
1810    }
1811
1812    doc
1813}
Source

pub fn add_list_definition(&mut self, levels: &[ListLevel]) -> u32

Create a list definition with explicit per-level formats and return its numId.

Unlike Self::add_bullet_list_item / Self::add_numbered_list_item, which share one bullet and one numbered definition per document, every call creates a fresh definition — so separate lists restart their numbering, and one definition can mix formats across levels (e.g. a bullet list whose nested level is decimal). Attach paragraphs with crate::Paragraph::set_numbering.

levels[i] configures level i; deeper unspecified levels fall back to the standard template rotation for the last specified format’s family. An empty slice produces the standard numbered template. Word supports nine levels, so entries after index eight are ignored.

use rdocx::{Document, ListLevel};

let mut doc = Document::new();
let num_id = doc.add_list_definition(&[
    ListLevel::bullet(),
    ListLevel::decimal().start(3),
]);
doc.add_paragraph("first bullet").set_numbering(num_id, 0);
doc.add_paragraph("third decimal").set_numbering(num_id, 1);
Source

pub fn set_list_level( &mut self, num_id: u32, level: u32, spec: ListLevel, ) -> bool

Redefine one level (0–8) of an existing list definition, for callers that only learn a deeper level’s format when content first reaches it.

Returns false when num_id is unknown or level is out of range.

Source

pub fn styles(&self) -> Vec<Style<'_>>

Get all styles.

Source

pub fn style(&self, style_id: &str) -> Option<Style<'_>>

Find a style by its ID.

Source

pub fn add_style(&mut self, builder: StyleBuilder)

Add a custom style to the document.

Examples found in repository?
examples/generate_all_samples.rs (lines 598-614)
97fn generate_feature_showcase(_samples_dir: &Path) -> Document {
98    let mut doc = Document::new();
99
100    // ── Page Setup & Metadata ──
101    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
102    doc.set_margins(
103        Length::inches(1.0),
104        Length::inches(1.0),
105        Length::inches(1.0),
106        Length::inches(1.0),
107    );
108    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
109    doc.set_gutter(Length::twips(0));
110    doc.set_title("rdocx Feature Showcase");
111    doc.set_author("rdocx Sample Generator");
112    doc.set_subject("Comprehensive feature demonstration");
113    doc.set_keywords("rdocx, docx, rust, sample, showcase");
114
115    // Headers & Footers with different first page
116    doc.set_different_first_page(true);
117    doc.set_first_page_header("rdocx");
118    doc.set_first_page_footer("Feature Showcase — Cover Page");
119    doc.set_header("rdocx Feature Showcase");
120    doc.set_footer("Generated by rdocx");
121
122    // ── COVER PAGE ──
123    let bg = create_sample_png(612, 792, [20, 45, 90]);
124    doc.add_background_image(&bg, "cover_bg.png");
125
126    for _ in 0..3 {
127        doc.add_paragraph("");
128    }
129    {
130        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
131        p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
132    }
133    {
134        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
135        p.add_run("Complete Feature Showcase")
136            .size(28.0)
137            .color("FFFFFF")
138            .italic(true);
139    }
140    doc.add_paragraph("");
141    {
142        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
143        p.add_run("Every feature of the rdocx Rust library")
144            .size(13.0)
145            .color("B0C4DE");
146    }
147    {
148        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
149        p.add_run("demonstrated in a single document.")
150            .size(13.0)
151            .color("B0C4DE");
152    }
153
154    // ── TABLE OF CONTENTS ──
155    doc.add_paragraph("").page_break_before(true);
156    doc.insert_toc(doc.content_count(), 3);
157
158    // ── SECTION 1: TEXT FORMATTING ──
159    doc.add_paragraph("").page_break_before(true);
160    doc.add_paragraph("1. Text Formatting").style("Heading1");
161
162    // Paragraph Alignment
163    doc.add_paragraph("1.1 Paragraph Alignment")
164        .style("Heading2");
165    doc.add_paragraph("Left-aligned paragraph (default).")
166        .alignment(Alignment::Left);
167    doc.add_paragraph("Center-aligned paragraph.")
168        .alignment(Alignment::Center);
169    doc.add_paragraph("Right-aligned paragraph.")
170        .alignment(Alignment::Right);
171    doc.add_paragraph(
172        "Justified paragraph — this text is long enough to demonstrate how justified alignment \
173         distributes extra space across word gaps so lines fill the full width of the text area.",
174    )
175    .alignment(Alignment::Justify);
176
177    doc.add_paragraph("");
178
179    // Run Formatting
180    doc.add_paragraph("1.2 Run Formatting").style("Heading2");
181    {
182        let mut p = doc.add_paragraph("");
183        p.add_run("Bold").bold(true);
184        p.add_run(" | ");
185        p.add_run("Italic").italic(true);
186        p.add_run(" | ");
187        p.add_run("Bold Italic").bold(true).italic(true);
188        p.add_run(" | ");
189        p.add_run("Underline").underline(true);
190        p.add_run(" | ");
191        p.add_run("Strikethrough").strike(true);
192        p.add_run(" | ");
193        p.add_run("Double Strike").double_strike(true);
194    }
195    {
196        let mut p = doc.add_paragraph("");
197        p.add_run("Red").color("FF0000");
198        p.add_run(" | ");
199        p.add_run("Blue").color("0000FF");
200        p.add_run(" | ");
201        p.add_run("Green").color("00AA00");
202        p.add_run(" | ");
203        p.add_run("Highlighted").highlight("FFFF00");
204    }
205    {
206        let mut p = doc.add_paragraph("");
207        p.add_run("8pt").size(8.0);
208        p.add_run(" | ");
209        p.add_run("11pt").size(11.0);
210        p.add_run(" | ");
211        p.add_run("16pt").size(16.0);
212        p.add_run(" | ");
213        p.add_run("24pt").size(24.0);
214    }
215    {
216        let mut p = doc.add_paragraph("");
217        p.add_run("Arial").font("Arial");
218        p.add_run(" | ");
219        p.add_run("Times New Roman").font("Times New Roman");
220        p.add_run(" | ");
221        p.add_run("Courier New").font("Courier New");
222    }
223    {
224        let mut p = doc.add_paragraph("");
225        p.add_run("H");
226        p.add_run("2").subscript();
227        p.add_run("O (subscript) | E=mc");
228        p.add_run("2").superscript();
229        p.add_run(" (superscript)");
230    }
231    {
232        let mut p = doc.add_paragraph("");
233        p.add_run("ALL CAPS").all_caps(true);
234        p.add_run(" | ");
235        p.add_run("Small Caps").small_caps(true);
236        p.add_run(" | ");
237        p.add_run("Expanded").character_spacing(Length::twips(40));
238        p.add_run(" | ");
239        p.add_run("Hidden text (not visible)").hidden(true);
240    }
241
242    doc.add_paragraph("");
243
244    // Underline Styles
245    doc.add_paragraph("1.3 Underline Styles").style("Heading2");
246    {
247        let mut p = doc.add_paragraph("");
248        p.add_run("Single ")
249            .underline_style(rdocx::UnderlineStyle::Single);
250        p.add_run("Double ")
251            .underline_style(rdocx::UnderlineStyle::Double);
252        p.add_run("Thick ")
253            .underline_style(rdocx::UnderlineStyle::Thick);
254        p.add_run("Dotted ")
255            .underline_style(rdocx::UnderlineStyle::Dotted);
256        p.add_run("Dash ")
257            .underline_style(rdocx::UnderlineStyle::Dash);
258        p.add_run("Wave ")
259            .underline_style(rdocx::UnderlineStyle::Wave);
260    }
261
262    // ── SECTION 2: PARAGRAPH FORMATTING ──
263    doc.add_paragraph("").page_break_before(true);
264    doc.add_paragraph("2. Paragraph Formatting")
265        .style("Heading1");
266
267    doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
268    doc.add_paragraph("Paragraph with light green shading")
269        .shading("E2EFDA");
270    doc.add_paragraph("Paragraph with bottom border")
271        .border_bottom(BorderStyle::Single, 6, "2E75B6");
272    doc.add_paragraph("Paragraph with all borders (red)")
273        .border_all(BorderStyle::Single, 4, "FF0000");
274
275    doc.add_paragraph("2.2 Indentation").style("Heading2");
276    doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
277        .indent_left(Length::inches(1.0))
278        .hanging_indent(Length::inches(0.5));
279    doc.add_paragraph("0.5-inch first-line indent on this paragraph")
280        .first_line_indent(Length::inches(0.5));
281    doc.add_paragraph("Both left and right indent (0.75 inches each)")
282        .indent_left(Length::inches(0.75))
283        .indent_right(Length::inches(0.75));
284
285    doc.add_paragraph("2.3 Spacing & Line Height")
286        .style("Heading2");
287    doc.add_paragraph("Extra space before (24pt) and after (12pt)")
288        .space_before(Length::pt(24.0))
289        .space_after(Length::pt(12.0));
290    doc.add_paragraph(
291        "Double line spacing paragraph. This text should have extra vertical space between \
292         lines to demonstrate line_spacing_multiple(2.0).",
293    )
294    .line_spacing_multiple(2.0);
295    doc.add_paragraph("Exact 20pt line spacing (fixed height).")
296        .line_spacing(20.0);
297
298    doc.add_paragraph("2.4 Pagination Controls")
299        .style("Heading2");
300    doc.add_paragraph("keep_with_next — this paragraph stays with the next")
301        .keep_with_next(true);
302    doc.add_paragraph("(This paragraph was kept with the one above.)");
303    doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
304        .keep_together(true);
305    doc.add_paragraph("widow_control — prevents widow/orphan lines")
306        .widow_control(true);
307
308    // ── SECTION 3: LISTS ──
309    doc.add_paragraph("").page_break_before(true);
310    doc.add_paragraph("3. Lists").style("Heading1");
311
312    doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
313    doc.add_bullet_list_item("First item", 0);
314    doc.add_bullet_list_item("Second item", 0);
315    doc.add_bullet_list_item("Nested level 1", 1);
316    doc.add_bullet_list_item("Nested level 2", 2);
317    doc.add_bullet_list_item("Back to level 1", 1);
318    doc.add_bullet_list_item("Third item", 0);
319
320    doc.add_paragraph("");
321
322    doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
323    doc.add_numbered_list_item("First numbered item", 0);
324    doc.add_numbered_list_item("Second numbered item", 0);
325    doc.add_numbered_list_item("Sub-item A", 1);
326    doc.add_numbered_list_item("Sub-item B", 1);
327    doc.add_numbered_list_item("Third numbered item", 0);
328
329    // ── SECTION 4: TAB STOPS ──
330    doc.add_paragraph("");
331    doc.add_paragraph("4. Tab Stops").style("Heading1");
332
333    doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
334    doc.add_paragraph("Left\tCenter\tRight\tDecimal")
335        .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
336        .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
337        .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
338        .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
339
340    doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
341    doc.add_paragraph("Item\t\tPrice")
342        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
343        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
344        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
345    doc.add_paragraph("Widget\t\t$19.99")
346        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
347        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
348        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
349    doc.add_paragraph("Gadget\t\t$249.50")
350        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
351        .add_tab_stop_with_leader(
352            TabAlignment::Right,
353            Length::inches(4.0),
354            TabLeader::Underscore,
355        )
356        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
357
358    // ── SECTION 5: TABLES ──
359    doc.add_paragraph("").page_break_before(true);
360    doc.add_paragraph("5. Tables").style("Heading1");
361
362    doc.add_paragraph("5.1 Basic Table").style("Heading2");
363    {
364        let mut tbl = doc
365            .add_table(4, 3)
366            .borders(BorderStyle::Single, 4, "000000");
367        for col in 0..3 {
368            tbl.cell(0, col).unwrap().shading("2E75B6");
369        }
370        tbl.cell(0, 0).unwrap().set_text("Name");
371        tbl.cell(0, 1).unwrap().set_text("Role");
372        tbl.cell(0, 2).unwrap().set_text("Location");
373        tbl.cell(1, 0).unwrap().set_text("Walter White");
374        tbl.cell(1, 1).unwrap().set_text("CEO");
375        tbl.cell(1, 2).unwrap().set_text("Albuquerque");
376        tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
377        tbl.cell(2, 1).unwrap().set_text("CTO");
378        tbl.cell(2, 2).unwrap().set_text("Remote");
379        tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
380        tbl.cell(3, 1).unwrap().set_text("Security");
381        tbl.cell(3, 2).unwrap().set_text("Washington");
382    }
383
384    doc.add_paragraph("");
385
386    doc.add_paragraph("5.2 Column Span & Cell Shading")
387        .style("Heading2");
388    {
389        let mut tbl = doc
390            .add_table(3, 4)
391            .borders(BorderStyle::Single, 4, "000000")
392            .width_pct(100.0);
393        tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
394        tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
395        tbl.cell(1, 0).unwrap().set_text("Region");
396        tbl.cell(1, 0).unwrap().shading("D6E4F0");
397        tbl.cell(1, 1).unwrap().set_text("Q1");
398        tbl.cell(1, 1).unwrap().shading("D6E4F0");
399        tbl.cell(1, 2).unwrap().set_text("Q2");
400        tbl.cell(1, 2).unwrap().shading("D6E4F0");
401        tbl.cell(1, 3).unwrap().set_text("Total");
402        tbl.cell(1, 3).unwrap().shading("D6E4F0");
403        tbl.cell(2, 0).unwrap().set_text("Americas");
404        tbl.cell(2, 1).unwrap().set_text("$2.4M");
405        tbl.cell(2, 2).unwrap().set_text("$2.7M");
406        tbl.cell(2, 3).unwrap().set_text("$5.1M");
407    }
408
409    doc.add_paragraph("");
410
411    doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
412    {
413        let mut tbl = doc
414            .add_table(4, 3)
415            .borders(BorderStyle::Single, 4, "000000");
416        tbl.cell(0, 0).unwrap().set_text("Category");
417        tbl.cell(0, 0).unwrap().shading("E2EFDA");
418        tbl.cell(0, 1).unwrap().set_text("Item");
419        tbl.cell(0, 1).unwrap().shading("E2EFDA");
420        tbl.cell(0, 2).unwrap().set_text("Price");
421        tbl.cell(0, 2).unwrap().shading("E2EFDA");
422        tbl.cell(1, 0).unwrap().set_text("Hardware");
423        tbl.cell(1, 0).unwrap().v_merge_restart();
424        tbl.cell(1, 1).unwrap().set_text("Laptop");
425        tbl.cell(1, 2).unwrap().set_text("$1,200");
426        tbl.cell(2, 0).unwrap().v_merge_continue();
427        tbl.cell(2, 1).unwrap().set_text("Monitor");
428        tbl.cell(2, 2).unwrap().set_text("$450");
429        tbl.cell(3, 0).unwrap().set_text("Software");
430        tbl.cell(3, 1).unwrap().set_text("IDE License");
431        tbl.cell(3, 2).unwrap().set_text("$200/yr");
432    }
433
434    doc.add_paragraph("");
435
436    doc.add_paragraph("5.4 Nested Table").style("Heading2");
437    {
438        let mut tbl = doc
439            .add_table(2, 2)
440            .borders(BorderStyle::Single, 6, "2E75B6");
441        tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
442        tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
443        tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
444        {
445            let mut cell = tbl.cell(1, 1).unwrap();
446            cell.set_text("Nested table below:");
447            let mut nested = cell
448                .add_table(2, 2)
449                .borders(BorderStyle::Single, 2, "FF6600");
450            nested.cell(0, 0).unwrap().set_text("A");
451            nested.cell(0, 1).unwrap().set_text("B");
452            nested.cell(1, 0).unwrap().set_text("C");
453            nested.cell(1, 1).unwrap().set_text("D");
454        }
455    }
456
457    doc.add_paragraph("");
458
459    doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
460        .style("Heading2");
461    {
462        let mut tbl = doc
463            .add_table(2, 3)
464            .borders(BorderStyle::Single, 4, "666666");
465        tbl.row(0).unwrap().height(Length::pt(50.0)).header();
466        tbl.cell(0, 0).unwrap().set_text("Top");
467        tbl.cell(0, 0)
468            .unwrap()
469            .vertical_alignment(VerticalAlignment::Top)
470            .shading("FFF2CC");
471        tbl.cell(0, 1).unwrap().set_text("Center");
472        tbl.cell(0, 1)
473            .unwrap()
474            .vertical_alignment(VerticalAlignment::Center)
475            .shading("D9E2F3");
476        tbl.cell(0, 2).unwrap().set_text("Bottom");
477        tbl.cell(0, 2)
478            .unwrap()
479            .vertical_alignment(VerticalAlignment::Bottom)
480            .shading("E2EFDA");
481        tbl.row(1).unwrap().cant_split();
482        tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
483        tbl.cell(1, 0).unwrap().no_wrap();
484        tbl.cell(1, 1).unwrap().set_text("Fixed width");
485        tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
486        tbl.cell(1, 2).unwrap().set_text("Normal");
487    }
488
489    // ── SECTION 6: IMAGES ──
490    doc.add_paragraph("").page_break_before(true);
491    doc.add_paragraph("6. Images").style("Heading1");
492
493    doc.add_paragraph("6.1 Inline Image").style("Heading2");
494    doc.add_paragraph("A blue gradient image below:");
495    let img = create_sample_png(200, 50, [0, 80, 200]);
496    doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
497
498    doc.add_paragraph("");
499    doc.add_paragraph("6.2 Header Image").style("Heading2");
500    let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
501    doc.set_header_image(
502        &hdr_img,
503        "header_logo.png",
504        Length::inches(2.0),
505        Length::inches(0.2),
506    );
507    doc.add_paragraph("The header now contains an inline image (check the top of this page).");
508
509    doc.add_paragraph("");
510    doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
511
512    // ── SECTION 7: CONTENT MANIPULATION ──
513    doc.add_paragraph("").page_break_before(true);
514    doc.add_paragraph("7. Content Manipulation")
515        .style("Heading1");
516
517    doc.add_paragraph("7.1 Placeholder Replacement")
518        .style("Heading2");
519    doc.add_paragraph("Customer: {{customer}}");
520    doc.add_paragraph("Date: {{date}}");
521    doc.add_paragraph("Reference: {{ref_number}}");
522    {
523        let mut tbl = doc
524            .add_table(2, 2)
525            .borders(BorderStyle::Single, 4, "000000");
526        tbl.cell(0, 0).unwrap().set_text("Project");
527        tbl.cell(0, 0).unwrap().shading("D6E4F0");
528        tbl.cell(0, 1).unwrap().set_text("{{project}}");
529        tbl.cell(1, 0).unwrap().set_text("Status");
530        tbl.cell(1, 0).unwrap().shading("D6E4F0");
531        tbl.cell(1, 1).unwrap().set_text("{{status}}");
532    }
533
534    let mut replacements = HashMap::new();
535    replacements.insert("{{customer}}", "Tensorbee Inc.");
536    replacements.insert("{{date}}", "February 22, 2026");
537    replacements.insert("{{ref_number}}", "TB-2026-001");
538    replacements.insert("{{project}}", "Infrastructure Upgrade");
539    replacements.insert("{{status}}", "In Progress");
540    let n = doc.replace_all(&replacements);
541    doc.add_paragraph(&format!("({n} placeholders replaced above)"));
542
543    doc.add_paragraph("");
544
545    doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
546    doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
547    let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
548    doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
549
550    doc.add_paragraph("");
551
552    doc.add_paragraph("7.3 Content Insertion").style("Heading2");
553    doc.add_paragraph("Section A: First content.");
554    doc.add_paragraph("Section C: Third content.");
555    if let Some(idx) = doc.find_content_index("Section C") {
556        doc.insert_paragraph(
557            idx,
558            "Section B: Inserted between A and C via find_content_index().",
559        );
560    }
561
562    // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
563    doc.add_paragraph("").section_break(SectionBreak::NextPage);
564    doc.add_paragraph("8. Section Breaks & Orientation")
565        .style("Heading1");
566    doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
567
568    {
569        let mut tbl = doc
570            .add_table(3, 7)
571            .borders(BorderStyle::Single, 4, "2E75B6");
572        let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
573        for (c, h) in headers.iter().enumerate() {
574            tbl.cell(0, c).unwrap().set_text(h);
575            tbl.cell(0, c).unwrap().shading("2E75B6");
576        }
577        let data = [
578            [
579                "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
580            ],
581            [
582                "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
583            ],
584        ];
585        for (r, row) in data.iter().enumerate() {
586            for (c, v) in row.iter().enumerate() {
587                tbl.cell(r + 1, c).unwrap().set_text(v);
588            }
589        }
590    }
591
592    doc.add_paragraph("")
593        .section_break(SectionBreak::NextPage)
594        .section_landscape();
595
596    // ── SECTION 9: CUSTOM STYLES ──
597    doc.add_paragraph("9. Custom Styles").style("Heading1");
598    doc.add_style(
599        StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
600            .based_on("Normal")
601            .paragraph_properties(rdocx_oxml::properties::CT_PPr {
602                shading: Some(rdocx_oxml::properties::CT_Shd {
603                    val: "clear".to_string(),
604                    color: None,
605                    fill: Some("FFF2CC".to_string()),
606                }),
607                ..Default::default()
608            })
609            .run_properties(rdocx_oxml::properties::CT_RPr {
610                bold: Some(true),
611                color: Some("C45911".to_string()),
612                ..Default::default()
613            }),
614    );
615    doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
616        .style("CustomHighlight");
617
618    doc.add_paragraph("");
619
620    // ── SECTION 10: DOCUMENT INTELLIGENCE ──
621    doc.add_paragraph("10. Document Intelligence API")
622        .style("Heading1");
623
624    let wc = doc.word_count();
625    let hc = doc.headings().len();
626    let ic = doc.images().len();
627    let lc = doc.links().len();
628    doc.add_paragraph(&format!("Word count: {wc}"));
629    doc.add_paragraph(&format!("Heading count: {hc}"));
630    doc.add_paragraph(&format!("Image count: {ic}"));
631    doc.add_paragraph(&format!("Link count: {lc}"));
632
633    let outline = doc.document_outline();
634    doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
635
636    let issues = doc.audit_accessibility();
637    doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
638    for issue in &issues {
639        doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
640    }
641
642    // ── SECTION 11: DOCUMENT MERGING ──
643    doc.add_paragraph("");
644    doc.add_paragraph("11. Document Merging").style("Heading1");
645    {
646        let mut other = Document::new();
647        other.add_paragraph("This paragraph was merged from another document using append().");
648        doc.append(&other);
649    }
650
651    doc.add_paragraph("");
652
653    // ── SUMMARY ──
654    doc.add_paragraph("Summary of Demonstrated Features")
655        .style("Heading1");
656    let features = [
657        "Page setup: size, margins, header/footer distance, gutter",
658        "Document metadata: title, author, subject, keywords",
659        "Headers and footers: text, images, different first page",
660        "Background images (full-page behind text)",
661        "Table of Contents generation with bookmarks",
662        "Text formatting: bold, italic, underline styles, strike, color, size, font",
663        "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
664        "Paragraph formatting: alignment, borders, shading, spacing, indentation",
665        "Pagination controls: keep-with-next, keep-together, widow control",
666        "Bullet and numbered lists with nesting",
667        "Tab stops with dot/underscore/hyphen leaders",
668        "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
669        "Row properties: header rows, exact height, cant-split",
670        "Cell properties: width, no-wrap, vertical alignment",
671        "Inline images and header images",
672        "Placeholder replacement (single and batch)",
673        "Regex-based find and replace",
674        "Content insertion at specific positions",
675        "Section breaks with mixed portrait/landscape",
676        "Custom paragraph and character styles",
677        "Document intelligence: word count, headings, outline, images, links",
678        "Accessibility audit",
679        "Document merging (append)",
680        "Export: DOCX, PDF, HTML, Markdown",
681    ];
682    for f in &features {
683        doc.add_bullet_list_item(f, 0);
684    }
685    doc.add_paragraph("");
686    doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
687        .alignment(Alignment::Center)
688        .shading("E2EFDA")
689        .border_all(BorderStyle::Single, 2, "00AA00");
690
691    doc
692}
693
694// =============================================================================
695// 2. PROPOSAL DOCUMENT — Deep navy + gold scheme
696// =============================================================================
697fn generate_proposal(_samples_dir: &Path) -> Document {
698    let mut doc = Document::new();
699
700    // Colors: Navy #1B2A4A, Gold #C5922E, Light #F4F1EB
701    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
702    doc.set_margins(
703        Length::inches(1.0),
704        Length::inches(1.0),
705        Length::inches(1.0),
706        Length::inches(1.0),
707    );
708    doc.set_title("AI Platform Modernization Proposal");
709    doc.set_author("Walter White");
710    doc.set_subject("Technology Proposal");
711    doc.set_keywords("proposal, AI, modernization, Tensorbee");
712
713    // Banner header
714    let logo_img = create_logo_png(220, 48);
715    let banner = build_header_banner_xml(
716        "rId1",
717        &BannerOpts {
718            bg_color: "1B2A4A",
719            banner_width: 7772400,
720            banner_height: 914400,
721            logo_width: 2011680,
722            logo_height: 438912,
723            logo_x_offset: 295125,
724            logo_y_offset: 237744,
725        },
726    );
727    doc.set_raw_header_with_images(
728        banner,
729        &[("rId1", &logo_img, "logo.png")],
730        rdocx_oxml::header_footer::HdrFtrType::Default,
731    );
732    doc.set_different_first_page(true);
733    let cover_banner = build_header_banner_xml(
734        "rId1",
735        &BannerOpts {
736            bg_color: "C5922E",
737            banner_width: 7772400,
738            banner_height: 914400,
739            logo_width: 2011680,
740            logo_height: 438912,
741            logo_x_offset: 295125,
742            logo_y_offset: 237744,
743        },
744    );
745    doc.set_raw_header_with_images(
746        cover_banner,
747        &[("rId1", &logo_img, "logo.png")],
748        rdocx_oxml::header_footer::HdrFtrType::First,
749    );
750    doc.set_margins(
751        Length::twips(2292),
752        Length::twips(1440),
753        Length::twips(1440),
754        Length::twips(1440),
755    );
756    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
757    doc.set_footer("Tensorbee — Confidential");
758
759    // Custom styles
760    doc.add_style(
761        StyleBuilder::paragraph("ProposalTitle", "Proposal Title")
762            .based_on("Normal")
763            .run_properties(rdocx_oxml::properties::CT_RPr {
764                bold: Some(true),
765                font_ascii: Some("Georgia".to_string()),
766                font_hansi: Some("Georgia".to_string()),
767                sz: Some(rdocx_oxml::HalfPoint(56)), // half-points → 28pt
768                color: Some("1B2A4A".to_string()),
769                ..Default::default()
770            }),
771    );
772
773    // ── Cover Page ──
774    for _ in 0..4 {
775        doc.add_paragraph("");
776    }
777    doc.add_paragraph("AI Platform Modernization")
778        .style("ProposalTitle")
779        .alignment(Alignment::Center);
780    doc.add_paragraph("")
781        .alignment(Alignment::Center)
782        .border_bottom(BorderStyle::Single, 8, "C5922E");
783    {
784        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
785        p.add_run("Prepared for Global Financial Corp.")
786            .size(14.0)
787            .color("666666")
788            .italic(true);
789    }
790    {
791        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
792        p.add_run("Prepared by: Walter White, CEO — Tensorbee")
793            .size(12.0)
794            .color("888888");
795    }
796    {
797        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
798        p.add_run("February 22, 2026").size(12.0).color("888888");
799    }
800
801    // ── TOC ──
802    doc.add_paragraph("").page_break_before(true);
803    doc.add_paragraph("Table of Contents").style("Heading1");
804    // We'll add a manual-style TOC since the insert_toc goes at a specific position
805    doc.insert_toc(doc.content_count(), 2);
806
807    // ── Executive Summary ──
808    doc.add_paragraph("").page_break_before(true);
809    doc.add_paragraph("Executive Summary").style("Heading1");
810    doc.add_paragraph(
811        "Tensorbee proposes a comprehensive modernization of Global Financial Corp's AI platform, \
812         transitioning from legacy batch-processing systems to a real-time inference architecture. \
813         This transformation will reduce model deployment time from weeks to hours, improve \
814         prediction accuracy by an estimated 23%, and deliver $4.2M in annual operational savings.",
815    )
816    .first_line_indent(Length::inches(0.3));
817    doc.add_paragraph(
818        "The project spans three phases over 18 months, with a total investment of $2.8M. \
819         Tensorbee brings deep expertise in ML infrastructure, having successfully completed \
820         similar transformations for three Fortune 500 financial institutions.",
821    )
822    .first_line_indent(Length::inches(0.3));
823
824    // Key metrics table
825    doc.add_paragraph("");
826    {
827        let mut tbl = doc
828            .add_table(5, 2)
829            .borders(BorderStyle::Single, 4, "1B2A4A")
830            .width_pct(80.0);
831        tbl.cell(0, 0).unwrap().set_text("Key Metric");
832        tbl.cell(0, 0).unwrap().shading("1B2A4A");
833        tbl.cell(0, 1).unwrap().set_text("Value");
834        tbl.cell(0, 1).unwrap().shading("1B2A4A");
835        let rows = [
836            ("Total Investment", "$2.8M"),
837            ("Annual Savings", "$4.2M"),
838            ("ROI (Year 1)", "150%"),
839            ("Timeline", "18 months"),
840        ];
841        for (i, (k, v)) in rows.iter().enumerate() {
842            tbl.cell(i + 1, 0).unwrap().set_text(k);
843            if i % 2 == 0 {
844                tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
845            }
846            tbl.cell(i + 1, 1).unwrap().set_text(v);
847            if i % 2 == 0 {
848                tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
849            }
850        }
851    }
852
853    // ── Problem Statement ──
854    doc.add_paragraph("").page_break_before(true);
855    doc.add_paragraph("Problem Statement").style("Heading1");
856    doc.add_paragraph(
857        "Global Financial Corp's current AI infrastructure faces three critical challenges:",
858    );
859    doc.add_numbered_list_item("Deployment Latency — New models take 3-4 weeks to deploy to production, compared to the industry benchmark of 2-3 days.", 0);
860    doc.add_numbered_list_item("Scalability Constraints — The batch processing architecture cannot handle real-time inference demands during peak trading hours.", 0);
861    doc.add_numbered_list_item("Technical Debt — Legacy Python 2.7 codebases and manual deployment scripts create reliability risks and slow iteration speed.", 0);
862
863    // ── Proposed Solution ──
864    doc.add_paragraph("").page_break_before(true);
865    doc.add_paragraph("Proposed Solution").style("Heading1");
866
867    doc.add_paragraph("Phase 1: Foundation (Months 1-6)")
868        .style("Heading2");
869    doc.add_bullet_list_item("Deploy Kubernetes-based ML serving infrastructure", 0);
870    doc.add_bullet_list_item("Implement CI/CD pipeline for model deployment", 0);
871    doc.add_bullet_list_item("Migrate top 5 production models to new platform", 0);
872
873    doc.add_paragraph("Phase 2: Expansion (Months 7-12)")
874        .style("Heading2");
875    doc.add_bullet_list_item(
876        "Real-time feature engineering pipeline (Apache Kafka + Flink)",
877        0,
878    );
879    doc.add_bullet_list_item("A/B testing framework for model variants", 0);
880    doc.add_bullet_list_item("Automated model monitoring and drift detection", 0);
881
882    doc.add_paragraph("Phase 3: Optimization (Months 13-18)")
883        .style("Heading2");
884    doc.add_bullet_list_item("GPU inference optimization (TensorRT/ONNX Runtime)", 0);
885    doc.add_bullet_list_item("Multi-region deployment for latency reduction", 0);
886    doc.add_bullet_list_item(
887        "Self-service model deployment portal for data science teams",
888        0,
889    );
890
891    // ── Budget ──
892    doc.add_paragraph("Budget Breakdown").style("Heading1");
893    {
894        let mut tbl = doc
895            .add_table(6, 3)
896            .borders(BorderStyle::Single, 4, "1B2A4A")
897            .width_pct(100.0);
898        tbl.cell(0, 0).unwrap().set_text("Category");
899        tbl.cell(0, 0).unwrap().shading("1B2A4A");
900        tbl.cell(0, 1).unwrap().set_text("Cost");
901        tbl.cell(0, 1).unwrap().shading("1B2A4A");
902        tbl.cell(0, 2).unwrap().set_text("Notes");
903        tbl.cell(0, 2).unwrap().shading("1B2A4A");
904        let items = [
905            (
906                "Infrastructure",
907                "$450,000",
908                "Cloud compute + GPU instances",
909            ),
910            ("Engineering Services", "$1,600,000", "12 FTE x 18 months"),
911            (
912                "Software Licenses",
913                "$350,000",
914                "Kafka, monitoring, CI/CD tools",
915            ),
916            (
917                "Training & Enablement",
918                "$200,000",
919                "Team training, documentation",
920            ),
921            ("Contingency (10%)", "$200,000", "Risk buffer"),
922        ];
923        for (i, (cat, cost, note)) in items.iter().enumerate() {
924            tbl.cell(i + 1, 0).unwrap().set_text(cat);
925            tbl.cell(i + 1, 1).unwrap().set_text(cost);
926            tbl.cell(i + 1, 2).unwrap().set_text(note);
927            if i % 2 == 0 {
928                tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
929                tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
930                tbl.cell(i + 1, 2).unwrap().shading("F4F1EB");
931            }
932        }
933    }
934
935    // ── Closing ──
936    doc.add_paragraph("");
937    doc.add_paragraph("Next Steps").style("Heading1");
938    doc.add_numbered_list_item(
939        "Schedule technical deep-dive with GFC engineering team (Week 1)",
940        0,
941    );
942    doc.add_numbered_list_item("Finalize scope and sign SOW (Week 2-3)", 0);
943    doc.add_numbered_list_item("Kick off Phase 1 with joint planning session (Week 4)", 0);
944
945    doc.add_paragraph("");
946    {
947        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
948        p.add_run("Walter White")
949            .bold(true)
950            .size(14.0)
951            .color("1B2A4A");
952    }
953    {
954        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
955        p.add_run("CEO, Tensorbee | walter@tensorbee.com")
956            .size(10.0)
957            .color("888888");
958    }
959
960    doc
961}
Source

pub fn resolve_paragraph_properties(&self, style_id: Option<&str>) -> CT_PPr

Resolve the effective paragraph properties for a given style ID, walking the full inheritance chain (docDefaults → basedOn → …).

Source

pub fn resolve_run_properties( &self, para_style_id: Option<&str>, run_style_id: Option<&str>, ) -> CT_RPr

Resolve the effective run properties for the given paragraph and character styles, walking the full inheritance chain.

Source

pub fn section_properties(&self) -> Option<&CT_SectPr>

Get the section properties (page size, margins).

Source

pub fn section_properties_mut(&mut self) -> &mut CT_SectPr

Get a mutable reference to section properties, creating defaults if needed.

Source

pub fn set_page_size(&mut self, width: Length, height: Length)

Set page size.

Examples found in repository?
examples/template_replace.rs (line 45)
43fn create_template(path: &Path) {
44    let mut doc = Document::new();
45    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
46    doc.set_margins(
47        Length::inches(1.0),
48        Length::inches(1.0),
49        Length::inches(1.0),
50        Length::inches(1.0),
51    );
52
53    doc.set_header("{{company_name}} — Confidential");
54    doc.set_footer("Prepared by {{author_name}} on {{date}}");
55
56    // ── Title ──
57    doc.add_paragraph("{{company_name}}")
58        .style("Heading1")
59        .alignment(Alignment::Center);
60
61    doc.add_paragraph("Project Proposal")
62        .alignment(Alignment::Center);
63
64    doc.add_paragraph("");
65
66    // ── Summary section ──
67    doc.add_paragraph("Executive Summary").style("Heading2");
68
69    doc.add_paragraph(
70        "This proposal outlines the {{project_name}} project for {{company_name}}. \
71         The primary contact is {{contact_name}} ({{contact_email}}). \
72         The proposed start date is {{start_date}} with an estimated duration of {{duration}}.",
73    );
74
75    doc.add_paragraph("");
76
77    // ── Cross-run placeholder (bold label + normal value) ──
78    doc.add_paragraph("Key Details").style("Heading2");
79
80    {
81        let mut p = doc.add_paragraph("");
82        p.add_run("Project: ").bold(true);
83        p.add_run("{{project_name}}");
84    }
85    {
86        let mut p = doc.add_paragraph("");
87        p.add_run("Budget: ").bold(true);
88        p.add_run("{{budget}}");
89    }
90    {
91        let mut p = doc.add_paragraph("");
92        p.add_run("Status: ").bold(true);
93        p.add_run("{{status}}");
94    }
95
96    doc.add_paragraph("");
97
98    // ── Table with placeholders ──
99    doc.add_paragraph("Team Members").style("Heading2");
100
101    {
102        let mut tbl = doc.add_table(4, 3);
103        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
104
105        // Header row
106        for col in 0..3 {
107            tbl.cell(0, col).unwrap().shading("2E75B6");
108        }
109        tbl.cell(0, 0).unwrap().set_text("Name");
110        tbl.cell(0, 1).unwrap().set_text("Role");
111        tbl.cell(0, 2).unwrap().set_text("Email");
112
113        tbl.cell(1, 0).unwrap().set_text("{{member1_name}}");
114        tbl.cell(1, 1).unwrap().set_text("{{member1_role}}");
115        tbl.cell(1, 2).unwrap().set_text("{{member1_email}}");
116
117        tbl.cell(2, 0).unwrap().set_text("{{member2_name}}");
118        tbl.cell(2, 1).unwrap().set_text("{{member2_role}}");
119        tbl.cell(2, 2).unwrap().set_text("{{member2_email}}");
120
121        tbl.cell(3, 0).unwrap().set_text("{{member3_name}}");
122        tbl.cell(3, 1).unwrap().set_text("{{member3_role}}");
123        tbl.cell(3, 2).unwrap().set_text("{{member3_email}}");
124    }
125
126    doc.add_paragraph("");
127
128    // ── Deliverables section ──
129    doc.add_paragraph("Deliverables").style("Heading2");
130
131    doc.add_paragraph("INSERTION_POINT");
132
133    doc.add_paragraph("");
134
135    // ── Signature block ──
136    doc.add_paragraph("Acceptance").style("Heading2");
137
138    doc.add_paragraph(
139        "By signing below, {{company_name}} agrees to the terms outlined in this proposal.",
140    );
141
142    {
143        let mut tbl = doc.add_table(2, 2);
144        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
145        tbl.cell(0, 0)
146            .unwrap()
147            .set_text("Customer: ___________________");
148        tbl.cell(0, 1)
149            .unwrap()
150            .set_text("Provider: ___________________");
151        tbl.cell(1, 0).unwrap().set_text("Date: {{date}}");
152        tbl.cell(1, 1).unwrap().set_text("Date: {{date}}");
153    }
154
155    doc.set_title("{{company_name}} — Project Proposal Template");
156    doc.set_author("Template Generator");
157
158    doc.save(path).unwrap();
159}
More examples
Hide additional examples
examples/header_banner.rs (line 33)
29fn generate_header_banner_doc(path: &Path) {
30    let mut doc = Document::new();
31
32    // Page setup with extra top margin for the banner
33    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
34    doc.set_margins(
35        Length::twips(2292), // top — extra tall for header banner
36        Length::twips(1440), // right
37        Length::twips(1440), // bottom
38        Length::twips(1440), // left
39    );
40    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
41
42    // Generate a simple logo image (white text on transparent background)
43    let logo_img = create_logo_png(220, 48);
44
45    // ── Dark blue banner ──
46    let banner = build_header_banner_xml(
47        "rId1",
48        &BannerOpts {
49            bg_color: "1A3C6E",
50            banner_width: 7772400, // full page width in EMU (~8.5")
51            banner_height: 969026, // banner height in EMU (~1.06")
52            logo_width: 2011680,   // logo display width (~2.2")
53            logo_height: 438912,   // logo display height (~0.48")
54            logo_x_offset: 295125, // left padding
55            logo_y_offset: 265057, // vertical centering
56        },
57    );
58
59    doc.set_raw_header_with_images(
60        banner.clone(),
61        &[("rId1", &logo_img, "logo.png")],
62        rdocx_oxml::header_footer::HdrFtrType::Default,
63    );
64
65    // Use a different first page header (same banner, different color)
66    doc.set_different_first_page(true);
67    let first_page_banner = build_header_banner_xml(
68        "rId1",
69        &BannerOpts {
70            bg_color: "2E75B6", // lighter blue for cover
71            banner_width: 7772400,
72            banner_height: 969026,
73            logo_width: 2011680,
74            logo_height: 438912,
75            logo_x_offset: 295125,
76            logo_y_offset: 265057,
77        },
78    );
79    doc.set_raw_header_with_images(
80        first_page_banner,
81        &[("rId1", &logo_img, "logo.png")],
82        rdocx_oxml::header_footer::HdrFtrType::First,
83    );
84
85    // Footer
86    doc.set_footer("Confidential — Internal Use Only");
87
88    // ── Page 1: Cover ──
89    doc.add_paragraph("Company Report").style("Heading1");
90
91    doc.add_paragraph(
92        "This document demonstrates a custom header banner built with DrawingML \
93         group shapes. The banner uses a colored rectangle with a logo image overlaid, \
94         positioned at the top of each page.",
95    );
96
97    doc.add_paragraph("");
98
99    doc.add_paragraph("How the Header Banner Works")
100        .style("Heading2");
101
102    doc.add_paragraph(
103        "The header banner is built using set_raw_header_with_images(), which \
104         accepts raw XML and a list of (rel_id, image_data, filename) tuples. \
105         The XML uses a DrawingML group shape (wpg:wgp) containing:",
106    );
107
108    doc.add_bullet_list_item(
109        "A wps:wsp rectangle shape with a solid color fill (the background bar)",
110        0,
111    );
112    doc.add_bullet_list_item(
113        "A pic:pic image element positioned within the group (the logo)",
114        0,
115    );
116    doc.add_bullet_list_item(
117        "The group is wrapped in a wp:anchor element for absolute page positioning",
118        0,
119    );
120
121    doc.add_paragraph("");
122
123    doc.add_paragraph("Customization").style("Heading2");
124
125    doc.add_paragraph(
126        "All dimensions are in EMU (English Metric Units) where 914400 EMU = 1 inch. \
127         You can customize:",
128    );
129
130    doc.add_bullet_list_item("bg_color — any hex color for the rectangle background", 0);
131    doc.add_bullet_list_item("banner_width / banner_height — size of the full banner", 0);
132    doc.add_bullet_list_item("logo_width / logo_height — display size of the logo", 0);
133    doc.add_bullet_list_item(
134        "logo_x_offset / logo_y_offset — logo position within the banner",
135        0,
136    );
137
138    doc.add_paragraph("");
139
140    doc.add_paragraph("Different First Page").style("Heading2");
141
142    doc.add_paragraph(
143        "This page uses a lighter blue banner (first page header). \
144         Subsequent pages use a darker blue banner (default header). \
145         Use set_different_first_page(true) to enable this.",
146    );
147
148    // ── Page 2 ──
149    doc.add_paragraph("").page_break_before(true);
150
151    doc.add_paragraph("Second Page").style("Heading1");
152
153    doc.add_paragraph(
154        "This page shows the default header banner (dark blue). The first page \
155         had a lighter blue banner because we set a different first-page header.",
156    );
157
158    doc.add_paragraph("");
159
160    doc.add_paragraph(
161        "The banner repeats on every page because it is placed in the header part. \
162         You can have different banners for default, first-page, and even-page headers.",
163    );
164
165    doc.set_title("Header Banner Example");
166    doc.set_author("rdocx");
167
168    doc.save(path).unwrap();
169}
examples/styled_tables.rs (line 26)
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}
examples/generate_all_samples.rs (line 101)
97fn generate_feature_showcase(_samples_dir: &Path) -> Document {
98    let mut doc = Document::new();
99
100    // ── Page Setup & Metadata ──
101    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
102    doc.set_margins(
103        Length::inches(1.0),
104        Length::inches(1.0),
105        Length::inches(1.0),
106        Length::inches(1.0),
107    );
108    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
109    doc.set_gutter(Length::twips(0));
110    doc.set_title("rdocx Feature Showcase");
111    doc.set_author("rdocx Sample Generator");
112    doc.set_subject("Comprehensive feature demonstration");
113    doc.set_keywords("rdocx, docx, rust, sample, showcase");
114
115    // Headers & Footers with different first page
116    doc.set_different_first_page(true);
117    doc.set_first_page_header("rdocx");
118    doc.set_first_page_footer("Feature Showcase — Cover Page");
119    doc.set_header("rdocx Feature Showcase");
120    doc.set_footer("Generated by rdocx");
121
122    // ── COVER PAGE ──
123    let bg = create_sample_png(612, 792, [20, 45, 90]);
124    doc.add_background_image(&bg, "cover_bg.png");
125
126    for _ in 0..3 {
127        doc.add_paragraph("");
128    }
129    {
130        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
131        p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
132    }
133    {
134        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
135        p.add_run("Complete Feature Showcase")
136            .size(28.0)
137            .color("FFFFFF")
138            .italic(true);
139    }
140    doc.add_paragraph("");
141    {
142        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
143        p.add_run("Every feature of the rdocx Rust library")
144            .size(13.0)
145            .color("B0C4DE");
146    }
147    {
148        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
149        p.add_run("demonstrated in a single document.")
150            .size(13.0)
151            .color("B0C4DE");
152    }
153
154    // ── TABLE OF CONTENTS ──
155    doc.add_paragraph("").page_break_before(true);
156    doc.insert_toc(doc.content_count(), 3);
157
158    // ── SECTION 1: TEXT FORMATTING ──
159    doc.add_paragraph("").page_break_before(true);
160    doc.add_paragraph("1. Text Formatting").style("Heading1");
161
162    // Paragraph Alignment
163    doc.add_paragraph("1.1 Paragraph Alignment")
164        .style("Heading2");
165    doc.add_paragraph("Left-aligned paragraph (default).")
166        .alignment(Alignment::Left);
167    doc.add_paragraph("Center-aligned paragraph.")
168        .alignment(Alignment::Center);
169    doc.add_paragraph("Right-aligned paragraph.")
170        .alignment(Alignment::Right);
171    doc.add_paragraph(
172        "Justified paragraph — this text is long enough to demonstrate how justified alignment \
173         distributes extra space across word gaps so lines fill the full width of the text area.",
174    )
175    .alignment(Alignment::Justify);
176
177    doc.add_paragraph("");
178
179    // Run Formatting
180    doc.add_paragraph("1.2 Run Formatting").style("Heading2");
181    {
182        let mut p = doc.add_paragraph("");
183        p.add_run("Bold").bold(true);
184        p.add_run(" | ");
185        p.add_run("Italic").italic(true);
186        p.add_run(" | ");
187        p.add_run("Bold Italic").bold(true).italic(true);
188        p.add_run(" | ");
189        p.add_run("Underline").underline(true);
190        p.add_run(" | ");
191        p.add_run("Strikethrough").strike(true);
192        p.add_run(" | ");
193        p.add_run("Double Strike").double_strike(true);
194    }
195    {
196        let mut p = doc.add_paragraph("");
197        p.add_run("Red").color("FF0000");
198        p.add_run(" | ");
199        p.add_run("Blue").color("0000FF");
200        p.add_run(" | ");
201        p.add_run("Green").color("00AA00");
202        p.add_run(" | ");
203        p.add_run("Highlighted").highlight("FFFF00");
204    }
205    {
206        let mut p = doc.add_paragraph("");
207        p.add_run("8pt").size(8.0);
208        p.add_run(" | ");
209        p.add_run("11pt").size(11.0);
210        p.add_run(" | ");
211        p.add_run("16pt").size(16.0);
212        p.add_run(" | ");
213        p.add_run("24pt").size(24.0);
214    }
215    {
216        let mut p = doc.add_paragraph("");
217        p.add_run("Arial").font("Arial");
218        p.add_run(" | ");
219        p.add_run("Times New Roman").font("Times New Roman");
220        p.add_run(" | ");
221        p.add_run("Courier New").font("Courier New");
222    }
223    {
224        let mut p = doc.add_paragraph("");
225        p.add_run("H");
226        p.add_run("2").subscript();
227        p.add_run("O (subscript) | E=mc");
228        p.add_run("2").superscript();
229        p.add_run(" (superscript)");
230    }
231    {
232        let mut p = doc.add_paragraph("");
233        p.add_run("ALL CAPS").all_caps(true);
234        p.add_run(" | ");
235        p.add_run("Small Caps").small_caps(true);
236        p.add_run(" | ");
237        p.add_run("Expanded").character_spacing(Length::twips(40));
238        p.add_run(" | ");
239        p.add_run("Hidden text (not visible)").hidden(true);
240    }
241
242    doc.add_paragraph("");
243
244    // Underline Styles
245    doc.add_paragraph("1.3 Underline Styles").style("Heading2");
246    {
247        let mut p = doc.add_paragraph("");
248        p.add_run("Single ")
249            .underline_style(rdocx::UnderlineStyle::Single);
250        p.add_run("Double ")
251            .underline_style(rdocx::UnderlineStyle::Double);
252        p.add_run("Thick ")
253            .underline_style(rdocx::UnderlineStyle::Thick);
254        p.add_run("Dotted ")
255            .underline_style(rdocx::UnderlineStyle::Dotted);
256        p.add_run("Dash ")
257            .underline_style(rdocx::UnderlineStyle::Dash);
258        p.add_run("Wave ")
259            .underline_style(rdocx::UnderlineStyle::Wave);
260    }
261
262    // ── SECTION 2: PARAGRAPH FORMATTING ──
263    doc.add_paragraph("").page_break_before(true);
264    doc.add_paragraph("2. Paragraph Formatting")
265        .style("Heading1");
266
267    doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
268    doc.add_paragraph("Paragraph with light green shading")
269        .shading("E2EFDA");
270    doc.add_paragraph("Paragraph with bottom border")
271        .border_bottom(BorderStyle::Single, 6, "2E75B6");
272    doc.add_paragraph("Paragraph with all borders (red)")
273        .border_all(BorderStyle::Single, 4, "FF0000");
274
275    doc.add_paragraph("2.2 Indentation").style("Heading2");
276    doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
277        .indent_left(Length::inches(1.0))
278        .hanging_indent(Length::inches(0.5));
279    doc.add_paragraph("0.5-inch first-line indent on this paragraph")
280        .first_line_indent(Length::inches(0.5));
281    doc.add_paragraph("Both left and right indent (0.75 inches each)")
282        .indent_left(Length::inches(0.75))
283        .indent_right(Length::inches(0.75));
284
285    doc.add_paragraph("2.3 Spacing & Line Height")
286        .style("Heading2");
287    doc.add_paragraph("Extra space before (24pt) and after (12pt)")
288        .space_before(Length::pt(24.0))
289        .space_after(Length::pt(12.0));
290    doc.add_paragraph(
291        "Double line spacing paragraph. This text should have extra vertical space between \
292         lines to demonstrate line_spacing_multiple(2.0).",
293    )
294    .line_spacing_multiple(2.0);
295    doc.add_paragraph("Exact 20pt line spacing (fixed height).")
296        .line_spacing(20.0);
297
298    doc.add_paragraph("2.4 Pagination Controls")
299        .style("Heading2");
300    doc.add_paragraph("keep_with_next — this paragraph stays with the next")
301        .keep_with_next(true);
302    doc.add_paragraph("(This paragraph was kept with the one above.)");
303    doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
304        .keep_together(true);
305    doc.add_paragraph("widow_control — prevents widow/orphan lines")
306        .widow_control(true);
307
308    // ── SECTION 3: LISTS ──
309    doc.add_paragraph("").page_break_before(true);
310    doc.add_paragraph("3. Lists").style("Heading1");
311
312    doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
313    doc.add_bullet_list_item("First item", 0);
314    doc.add_bullet_list_item("Second item", 0);
315    doc.add_bullet_list_item("Nested level 1", 1);
316    doc.add_bullet_list_item("Nested level 2", 2);
317    doc.add_bullet_list_item("Back to level 1", 1);
318    doc.add_bullet_list_item("Third item", 0);
319
320    doc.add_paragraph("");
321
322    doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
323    doc.add_numbered_list_item("First numbered item", 0);
324    doc.add_numbered_list_item("Second numbered item", 0);
325    doc.add_numbered_list_item("Sub-item A", 1);
326    doc.add_numbered_list_item("Sub-item B", 1);
327    doc.add_numbered_list_item("Third numbered item", 0);
328
329    // ── SECTION 4: TAB STOPS ──
330    doc.add_paragraph("");
331    doc.add_paragraph("4. Tab Stops").style("Heading1");
332
333    doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
334    doc.add_paragraph("Left\tCenter\tRight\tDecimal")
335        .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
336        .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
337        .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
338        .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
339
340    doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
341    doc.add_paragraph("Item\t\tPrice")
342        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
343        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
344        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
345    doc.add_paragraph("Widget\t\t$19.99")
346        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
347        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
348        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
349    doc.add_paragraph("Gadget\t\t$249.50")
350        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
351        .add_tab_stop_with_leader(
352            TabAlignment::Right,
353            Length::inches(4.0),
354            TabLeader::Underscore,
355        )
356        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
357
358    // ── SECTION 5: TABLES ──
359    doc.add_paragraph("").page_break_before(true);
360    doc.add_paragraph("5. Tables").style("Heading1");
361
362    doc.add_paragraph("5.1 Basic Table").style("Heading2");
363    {
364        let mut tbl = doc
365            .add_table(4, 3)
366            .borders(BorderStyle::Single, 4, "000000");
367        for col in 0..3 {
368            tbl.cell(0, col).unwrap().shading("2E75B6");
369        }
370        tbl.cell(0, 0).unwrap().set_text("Name");
371        tbl.cell(0, 1).unwrap().set_text("Role");
372        tbl.cell(0, 2).unwrap().set_text("Location");
373        tbl.cell(1, 0).unwrap().set_text("Walter White");
374        tbl.cell(1, 1).unwrap().set_text("CEO");
375        tbl.cell(1, 2).unwrap().set_text("Albuquerque");
376        tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
377        tbl.cell(2, 1).unwrap().set_text("CTO");
378        tbl.cell(2, 2).unwrap().set_text("Remote");
379        tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
380        tbl.cell(3, 1).unwrap().set_text("Security");
381        tbl.cell(3, 2).unwrap().set_text("Washington");
382    }
383
384    doc.add_paragraph("");
385
386    doc.add_paragraph("5.2 Column Span & Cell Shading")
387        .style("Heading2");
388    {
389        let mut tbl = doc
390            .add_table(3, 4)
391            .borders(BorderStyle::Single, 4, "000000")
392            .width_pct(100.0);
393        tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
394        tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
395        tbl.cell(1, 0).unwrap().set_text("Region");
396        tbl.cell(1, 0).unwrap().shading("D6E4F0");
397        tbl.cell(1, 1).unwrap().set_text("Q1");
398        tbl.cell(1, 1).unwrap().shading("D6E4F0");
399        tbl.cell(1, 2).unwrap().set_text("Q2");
400        tbl.cell(1, 2).unwrap().shading("D6E4F0");
401        tbl.cell(1, 3).unwrap().set_text("Total");
402        tbl.cell(1, 3).unwrap().shading("D6E4F0");
403        tbl.cell(2, 0).unwrap().set_text("Americas");
404        tbl.cell(2, 1).unwrap().set_text("$2.4M");
405        tbl.cell(2, 2).unwrap().set_text("$2.7M");
406        tbl.cell(2, 3).unwrap().set_text("$5.1M");
407    }
408
409    doc.add_paragraph("");
410
411    doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
412    {
413        let mut tbl = doc
414            .add_table(4, 3)
415            .borders(BorderStyle::Single, 4, "000000");
416        tbl.cell(0, 0).unwrap().set_text("Category");
417        tbl.cell(0, 0).unwrap().shading("E2EFDA");
418        tbl.cell(0, 1).unwrap().set_text("Item");
419        tbl.cell(0, 1).unwrap().shading("E2EFDA");
420        tbl.cell(0, 2).unwrap().set_text("Price");
421        tbl.cell(0, 2).unwrap().shading("E2EFDA");
422        tbl.cell(1, 0).unwrap().set_text("Hardware");
423        tbl.cell(1, 0).unwrap().v_merge_restart();
424        tbl.cell(1, 1).unwrap().set_text("Laptop");
425        tbl.cell(1, 2).unwrap().set_text("$1,200");
426        tbl.cell(2, 0).unwrap().v_merge_continue();
427        tbl.cell(2, 1).unwrap().set_text("Monitor");
428        tbl.cell(2, 2).unwrap().set_text("$450");
429        tbl.cell(3, 0).unwrap().set_text("Software");
430        tbl.cell(3, 1).unwrap().set_text("IDE License");
431        tbl.cell(3, 2).unwrap().set_text("$200/yr");
432    }
433
434    doc.add_paragraph("");
435
436    doc.add_paragraph("5.4 Nested Table").style("Heading2");
437    {
438        let mut tbl = doc
439            .add_table(2, 2)
440            .borders(BorderStyle::Single, 6, "2E75B6");
441        tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
442        tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
443        tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
444        {
445            let mut cell = tbl.cell(1, 1).unwrap();
446            cell.set_text("Nested table below:");
447            let mut nested = cell
448                .add_table(2, 2)
449                .borders(BorderStyle::Single, 2, "FF6600");
450            nested.cell(0, 0).unwrap().set_text("A");
451            nested.cell(0, 1).unwrap().set_text("B");
452            nested.cell(1, 0).unwrap().set_text("C");
453            nested.cell(1, 1).unwrap().set_text("D");
454        }
455    }
456
457    doc.add_paragraph("");
458
459    doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
460        .style("Heading2");
461    {
462        let mut tbl = doc
463            .add_table(2, 3)
464            .borders(BorderStyle::Single, 4, "666666");
465        tbl.row(0).unwrap().height(Length::pt(50.0)).header();
466        tbl.cell(0, 0).unwrap().set_text("Top");
467        tbl.cell(0, 0)
468            .unwrap()
469            .vertical_alignment(VerticalAlignment::Top)
470            .shading("FFF2CC");
471        tbl.cell(0, 1).unwrap().set_text("Center");
472        tbl.cell(0, 1)
473            .unwrap()
474            .vertical_alignment(VerticalAlignment::Center)
475            .shading("D9E2F3");
476        tbl.cell(0, 2).unwrap().set_text("Bottom");
477        tbl.cell(0, 2)
478            .unwrap()
479            .vertical_alignment(VerticalAlignment::Bottom)
480            .shading("E2EFDA");
481        tbl.row(1).unwrap().cant_split();
482        tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
483        tbl.cell(1, 0).unwrap().no_wrap();
484        tbl.cell(1, 1).unwrap().set_text("Fixed width");
485        tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
486        tbl.cell(1, 2).unwrap().set_text("Normal");
487    }
488
489    // ── SECTION 6: IMAGES ──
490    doc.add_paragraph("").page_break_before(true);
491    doc.add_paragraph("6. Images").style("Heading1");
492
493    doc.add_paragraph("6.1 Inline Image").style("Heading2");
494    doc.add_paragraph("A blue gradient image below:");
495    let img = create_sample_png(200, 50, [0, 80, 200]);
496    doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
497
498    doc.add_paragraph("");
499    doc.add_paragraph("6.2 Header Image").style("Heading2");
500    let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
501    doc.set_header_image(
502        &hdr_img,
503        "header_logo.png",
504        Length::inches(2.0),
505        Length::inches(0.2),
506    );
507    doc.add_paragraph("The header now contains an inline image (check the top of this page).");
508
509    doc.add_paragraph("");
510    doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
511
512    // ── SECTION 7: CONTENT MANIPULATION ──
513    doc.add_paragraph("").page_break_before(true);
514    doc.add_paragraph("7. Content Manipulation")
515        .style("Heading1");
516
517    doc.add_paragraph("7.1 Placeholder Replacement")
518        .style("Heading2");
519    doc.add_paragraph("Customer: {{customer}}");
520    doc.add_paragraph("Date: {{date}}");
521    doc.add_paragraph("Reference: {{ref_number}}");
522    {
523        let mut tbl = doc
524            .add_table(2, 2)
525            .borders(BorderStyle::Single, 4, "000000");
526        tbl.cell(0, 0).unwrap().set_text("Project");
527        tbl.cell(0, 0).unwrap().shading("D6E4F0");
528        tbl.cell(0, 1).unwrap().set_text("{{project}}");
529        tbl.cell(1, 0).unwrap().set_text("Status");
530        tbl.cell(1, 0).unwrap().shading("D6E4F0");
531        tbl.cell(1, 1).unwrap().set_text("{{status}}");
532    }
533
534    let mut replacements = HashMap::new();
535    replacements.insert("{{customer}}", "Tensorbee Inc.");
536    replacements.insert("{{date}}", "February 22, 2026");
537    replacements.insert("{{ref_number}}", "TB-2026-001");
538    replacements.insert("{{project}}", "Infrastructure Upgrade");
539    replacements.insert("{{status}}", "In Progress");
540    let n = doc.replace_all(&replacements);
541    doc.add_paragraph(&format!("({n} placeholders replaced above)"));
542
543    doc.add_paragraph("");
544
545    doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
546    doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
547    let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
548    doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
549
550    doc.add_paragraph("");
551
552    doc.add_paragraph("7.3 Content Insertion").style("Heading2");
553    doc.add_paragraph("Section A: First content.");
554    doc.add_paragraph("Section C: Third content.");
555    if let Some(idx) = doc.find_content_index("Section C") {
556        doc.insert_paragraph(
557            idx,
558            "Section B: Inserted between A and C via find_content_index().",
559        );
560    }
561
562    // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
563    doc.add_paragraph("").section_break(SectionBreak::NextPage);
564    doc.add_paragraph("8. Section Breaks & Orientation")
565        .style("Heading1");
566    doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
567
568    {
569        let mut tbl = doc
570            .add_table(3, 7)
571            .borders(BorderStyle::Single, 4, "2E75B6");
572        let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
573        for (c, h) in headers.iter().enumerate() {
574            tbl.cell(0, c).unwrap().set_text(h);
575            tbl.cell(0, c).unwrap().shading("2E75B6");
576        }
577        let data = [
578            [
579                "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
580            ],
581            [
582                "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
583            ],
584        ];
585        for (r, row) in data.iter().enumerate() {
586            for (c, v) in row.iter().enumerate() {
587                tbl.cell(r + 1, c).unwrap().set_text(v);
588            }
589        }
590    }
591
592    doc.add_paragraph("")
593        .section_break(SectionBreak::NextPage)
594        .section_landscape();
595
596    // ── SECTION 9: CUSTOM STYLES ──
597    doc.add_paragraph("9. Custom Styles").style("Heading1");
598    doc.add_style(
599        StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
600            .based_on("Normal")
601            .paragraph_properties(rdocx_oxml::properties::CT_PPr {
602                shading: Some(rdocx_oxml::properties::CT_Shd {
603                    val: "clear".to_string(),
604                    color: None,
605                    fill: Some("FFF2CC".to_string()),
606                }),
607                ..Default::default()
608            })
609            .run_properties(rdocx_oxml::properties::CT_RPr {
610                bold: Some(true),
611                color: Some("C45911".to_string()),
612                ..Default::default()
613            }),
614    );
615    doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
616        .style("CustomHighlight");
617
618    doc.add_paragraph("");
619
620    // ── SECTION 10: DOCUMENT INTELLIGENCE ──
621    doc.add_paragraph("10. Document Intelligence API")
622        .style("Heading1");
623
624    let wc = doc.word_count();
625    let hc = doc.headings().len();
626    let ic = doc.images().len();
627    let lc = doc.links().len();
628    doc.add_paragraph(&format!("Word count: {wc}"));
629    doc.add_paragraph(&format!("Heading count: {hc}"));
630    doc.add_paragraph(&format!("Image count: {ic}"));
631    doc.add_paragraph(&format!("Link count: {lc}"));
632
633    let outline = doc.document_outline();
634    doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
635
636    let issues = doc.audit_accessibility();
637    doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
638    for issue in &issues {
639        doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
640    }
641
642    // ── SECTION 11: DOCUMENT MERGING ──
643    doc.add_paragraph("");
644    doc.add_paragraph("11. Document Merging").style("Heading1");
645    {
646        let mut other = Document::new();
647        other.add_paragraph("This paragraph was merged from another document using append().");
648        doc.append(&other);
649    }
650
651    doc.add_paragraph("");
652
653    // ── SUMMARY ──
654    doc.add_paragraph("Summary of Demonstrated Features")
655        .style("Heading1");
656    let features = [
657        "Page setup: size, margins, header/footer distance, gutter",
658        "Document metadata: title, author, subject, keywords",
659        "Headers and footers: text, images, different first page",
660        "Background images (full-page behind text)",
661        "Table of Contents generation with bookmarks",
662        "Text formatting: bold, italic, underline styles, strike, color, size, font",
663        "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
664        "Paragraph formatting: alignment, borders, shading, spacing, indentation",
665        "Pagination controls: keep-with-next, keep-together, widow control",
666        "Bullet and numbered lists with nesting",
667        "Tab stops with dot/underscore/hyphen leaders",
668        "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
669        "Row properties: header rows, exact height, cant-split",
670        "Cell properties: width, no-wrap, vertical alignment",
671        "Inline images and header images",
672        "Placeholder replacement (single and batch)",
673        "Regex-based find and replace",
674        "Content insertion at specific positions",
675        "Section breaks with mixed portrait/landscape",
676        "Custom paragraph and character styles",
677        "Document intelligence: word count, headings, outline, images, links",
678        "Accessibility audit",
679        "Document merging (append)",
680        "Export: DOCX, PDF, HTML, Markdown",
681    ];
682    for f in &features {
683        doc.add_bullet_list_item(f, 0);
684    }
685    doc.add_paragraph("");
686    doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
687        .alignment(Alignment::Center)
688        .shading("E2EFDA")
689        .border_all(BorderStyle::Single, 2, "00AA00");
690
691    doc
692}
693
694// =============================================================================
695// 2. PROPOSAL DOCUMENT — Deep navy + gold scheme
696// =============================================================================
697fn generate_proposal(_samples_dir: &Path) -> Document {
698    let mut doc = Document::new();
699
700    // Colors: Navy #1B2A4A, Gold #C5922E, Light #F4F1EB
701    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
702    doc.set_margins(
703        Length::inches(1.0),
704        Length::inches(1.0),
705        Length::inches(1.0),
706        Length::inches(1.0),
707    );
708    doc.set_title("AI Platform Modernization Proposal");
709    doc.set_author("Walter White");
710    doc.set_subject("Technology Proposal");
711    doc.set_keywords("proposal, AI, modernization, Tensorbee");
712
713    // Banner header
714    let logo_img = create_logo_png(220, 48);
715    let banner = build_header_banner_xml(
716        "rId1",
717        &BannerOpts {
718            bg_color: "1B2A4A",
719            banner_width: 7772400,
720            banner_height: 914400,
721            logo_width: 2011680,
722            logo_height: 438912,
723            logo_x_offset: 295125,
724            logo_y_offset: 237744,
725        },
726    );
727    doc.set_raw_header_with_images(
728        banner,
729        &[("rId1", &logo_img, "logo.png")],
730        rdocx_oxml::header_footer::HdrFtrType::Default,
731    );
732    doc.set_different_first_page(true);
733    let cover_banner = build_header_banner_xml(
734        "rId1",
735        &BannerOpts {
736            bg_color: "C5922E",
737            banner_width: 7772400,
738            banner_height: 914400,
739            logo_width: 2011680,
740            logo_height: 438912,
741            logo_x_offset: 295125,
742            logo_y_offset: 237744,
743        },
744    );
745    doc.set_raw_header_with_images(
746        cover_banner,
747        &[("rId1", &logo_img, "logo.png")],
748        rdocx_oxml::header_footer::HdrFtrType::First,
749    );
750    doc.set_margins(
751        Length::twips(2292),
752        Length::twips(1440),
753        Length::twips(1440),
754        Length::twips(1440),
755    );
756    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
757    doc.set_footer("Tensorbee — Confidential");
758
759    // Custom styles
760    doc.add_style(
761        StyleBuilder::paragraph("ProposalTitle", "Proposal Title")
762            .based_on("Normal")
763            .run_properties(rdocx_oxml::properties::CT_RPr {
764                bold: Some(true),
765                font_ascii: Some("Georgia".to_string()),
766                font_hansi: Some("Georgia".to_string()),
767                sz: Some(rdocx_oxml::HalfPoint(56)), // half-points → 28pt
768                color: Some("1B2A4A".to_string()),
769                ..Default::default()
770            }),
771    );
772
773    // ── Cover Page ──
774    for _ in 0..4 {
775        doc.add_paragraph("");
776    }
777    doc.add_paragraph("AI Platform Modernization")
778        .style("ProposalTitle")
779        .alignment(Alignment::Center);
780    doc.add_paragraph("")
781        .alignment(Alignment::Center)
782        .border_bottom(BorderStyle::Single, 8, "C5922E");
783    {
784        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
785        p.add_run("Prepared for Global Financial Corp.")
786            .size(14.0)
787            .color("666666")
788            .italic(true);
789    }
790    {
791        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
792        p.add_run("Prepared by: Walter White, CEO — Tensorbee")
793            .size(12.0)
794            .color("888888");
795    }
796    {
797        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
798        p.add_run("February 22, 2026").size(12.0).color("888888");
799    }
800
801    // ── TOC ──
802    doc.add_paragraph("").page_break_before(true);
803    doc.add_paragraph("Table of Contents").style("Heading1");
804    // We'll add a manual-style TOC since the insert_toc goes at a specific position
805    doc.insert_toc(doc.content_count(), 2);
806
807    // ── Executive Summary ──
808    doc.add_paragraph("").page_break_before(true);
809    doc.add_paragraph("Executive Summary").style("Heading1");
810    doc.add_paragraph(
811        "Tensorbee proposes a comprehensive modernization of Global Financial Corp's AI platform, \
812         transitioning from legacy batch-processing systems to a real-time inference architecture. \
813         This transformation will reduce model deployment time from weeks to hours, improve \
814         prediction accuracy by an estimated 23%, and deliver $4.2M in annual operational savings.",
815    )
816    .first_line_indent(Length::inches(0.3));
817    doc.add_paragraph(
818        "The project spans three phases over 18 months, with a total investment of $2.8M. \
819         Tensorbee brings deep expertise in ML infrastructure, having successfully completed \
820         similar transformations for three Fortune 500 financial institutions.",
821    )
822    .first_line_indent(Length::inches(0.3));
823
824    // Key metrics table
825    doc.add_paragraph("");
826    {
827        let mut tbl = doc
828            .add_table(5, 2)
829            .borders(BorderStyle::Single, 4, "1B2A4A")
830            .width_pct(80.0);
831        tbl.cell(0, 0).unwrap().set_text("Key Metric");
832        tbl.cell(0, 0).unwrap().shading("1B2A4A");
833        tbl.cell(0, 1).unwrap().set_text("Value");
834        tbl.cell(0, 1).unwrap().shading("1B2A4A");
835        let rows = [
836            ("Total Investment", "$2.8M"),
837            ("Annual Savings", "$4.2M"),
838            ("ROI (Year 1)", "150%"),
839            ("Timeline", "18 months"),
840        ];
841        for (i, (k, v)) in rows.iter().enumerate() {
842            tbl.cell(i + 1, 0).unwrap().set_text(k);
843            if i % 2 == 0 {
844                tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
845            }
846            tbl.cell(i + 1, 1).unwrap().set_text(v);
847            if i % 2 == 0 {
848                tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
849            }
850        }
851    }
852
853    // ── Problem Statement ──
854    doc.add_paragraph("").page_break_before(true);
855    doc.add_paragraph("Problem Statement").style("Heading1");
856    doc.add_paragraph(
857        "Global Financial Corp's current AI infrastructure faces three critical challenges:",
858    );
859    doc.add_numbered_list_item("Deployment Latency — New models take 3-4 weeks to deploy to production, compared to the industry benchmark of 2-3 days.", 0);
860    doc.add_numbered_list_item("Scalability Constraints — The batch processing architecture cannot handle real-time inference demands during peak trading hours.", 0);
861    doc.add_numbered_list_item("Technical Debt — Legacy Python 2.7 codebases and manual deployment scripts create reliability risks and slow iteration speed.", 0);
862
863    // ── Proposed Solution ──
864    doc.add_paragraph("").page_break_before(true);
865    doc.add_paragraph("Proposed Solution").style("Heading1");
866
867    doc.add_paragraph("Phase 1: Foundation (Months 1-6)")
868        .style("Heading2");
869    doc.add_bullet_list_item("Deploy Kubernetes-based ML serving infrastructure", 0);
870    doc.add_bullet_list_item("Implement CI/CD pipeline for model deployment", 0);
871    doc.add_bullet_list_item("Migrate top 5 production models to new platform", 0);
872
873    doc.add_paragraph("Phase 2: Expansion (Months 7-12)")
874        .style("Heading2");
875    doc.add_bullet_list_item(
876        "Real-time feature engineering pipeline (Apache Kafka + Flink)",
877        0,
878    );
879    doc.add_bullet_list_item("A/B testing framework for model variants", 0);
880    doc.add_bullet_list_item("Automated model monitoring and drift detection", 0);
881
882    doc.add_paragraph("Phase 3: Optimization (Months 13-18)")
883        .style("Heading2");
884    doc.add_bullet_list_item("GPU inference optimization (TensorRT/ONNX Runtime)", 0);
885    doc.add_bullet_list_item("Multi-region deployment for latency reduction", 0);
886    doc.add_bullet_list_item(
887        "Self-service model deployment portal for data science teams",
888        0,
889    );
890
891    // ── Budget ──
892    doc.add_paragraph("Budget Breakdown").style("Heading1");
893    {
894        let mut tbl = doc
895            .add_table(6, 3)
896            .borders(BorderStyle::Single, 4, "1B2A4A")
897            .width_pct(100.0);
898        tbl.cell(0, 0).unwrap().set_text("Category");
899        tbl.cell(0, 0).unwrap().shading("1B2A4A");
900        tbl.cell(0, 1).unwrap().set_text("Cost");
901        tbl.cell(0, 1).unwrap().shading("1B2A4A");
902        tbl.cell(0, 2).unwrap().set_text("Notes");
903        tbl.cell(0, 2).unwrap().shading("1B2A4A");
904        let items = [
905            (
906                "Infrastructure",
907                "$450,000",
908                "Cloud compute + GPU instances",
909            ),
910            ("Engineering Services", "$1,600,000", "12 FTE x 18 months"),
911            (
912                "Software Licenses",
913                "$350,000",
914                "Kafka, monitoring, CI/CD tools",
915            ),
916            (
917                "Training & Enablement",
918                "$200,000",
919                "Team training, documentation",
920            ),
921            ("Contingency (10%)", "$200,000", "Risk buffer"),
922        ];
923        for (i, (cat, cost, note)) in items.iter().enumerate() {
924            tbl.cell(i + 1, 0).unwrap().set_text(cat);
925            tbl.cell(i + 1, 1).unwrap().set_text(cost);
926            tbl.cell(i + 1, 2).unwrap().set_text(note);
927            if i % 2 == 0 {
928                tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
929                tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
930                tbl.cell(i + 1, 2).unwrap().shading("F4F1EB");
931            }
932        }
933    }
934
935    // ── Closing ──
936    doc.add_paragraph("");
937    doc.add_paragraph("Next Steps").style("Heading1");
938    doc.add_numbered_list_item(
939        "Schedule technical deep-dive with GFC engineering team (Week 1)",
940        0,
941    );
942    doc.add_numbered_list_item("Finalize scope and sign SOW (Week 2-3)", 0);
943    doc.add_numbered_list_item("Kick off Phase 1 with joint planning session (Week 4)", 0);
944
945    doc.add_paragraph("");
946    {
947        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
948        p.add_run("Walter White")
949            .bold(true)
950            .size(14.0)
951            .color("1B2A4A");
952    }
953    {
954        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
955        p.add_run("CEO, Tensorbee | walter@tensorbee.com")
956            .size(10.0)
957            .color("888888");
958    }
959
960    doc
961}
962
963// =============================================================================
964// 3. QUOTE / BILL OF MATERIALS — Teal + orange scheme
965// =============================================================================
966fn generate_quote(_samples_dir: &Path) -> Document {
967    let mut doc = Document::new();
968
969    // Colors: Teal #008B8B, Dark #1A3C3C, Orange #E07020, Light #F0F8F8
970    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
971    doc.set_margins(
972        Length::inches(0.75),
973        Length::inches(0.75),
974        Length::inches(0.75),
975        Length::inches(0.75),
976    );
977    doc.set_title("Quotation QT-2026-0147");
978    doc.set_author("Walter White");
979    doc.set_keywords("quote, BOM, Tensorbee");
980
981    doc.set_header("Tensorbee — Quotation");
982    doc.set_footer("QT-2026-0147 | Page");
983
984    // ── Header Block ──
985    {
986        let mut p = doc.add_paragraph("").alignment(Alignment::Left);
987        p.add_run("TENSORBEE")
988            .bold(true)
989            .size(28.0)
990            .color("008B8B")
991            .font("Helvetica");
992    }
993    doc.add_paragraph("123 Innovation Drive, Suite 400")
994        .alignment(Alignment::Left);
995    doc.add_paragraph("San Francisco, CA 94105 | +1 (415) 555-0199")
996        .alignment(Alignment::Left);
997    doc.add_paragraph("")
998        .border_bottom(BorderStyle::Single, 8, "008B8B");
999
1000    // Quote meta
1001    doc.add_paragraph("");
1002    {
1003        let mut tbl = doc.add_table(4, 4).width_pct(100.0);
1004        tbl.cell(0, 0).unwrap().set_text("QUOTATION");
1005        tbl.cell(0, 0).unwrap().shading("008B8B").grid_span(2);
1006        tbl.cell(0, 2).unwrap().set_text("DATE");
1007        tbl.cell(0, 2).unwrap().shading("008B8B");
1008        tbl.cell(0, 3).unwrap().set_text("VALID UNTIL");
1009        tbl.cell(0, 3).unwrap().shading("008B8B");
1010        tbl.cell(1, 0).unwrap().set_text("Quote #:");
1011        tbl.cell(1, 1).unwrap().set_text("QT-2026-0147");
1012        tbl.cell(1, 2).unwrap().set_text("Feb 22, 2026");
1013        tbl.cell(1, 3).unwrap().set_text("Mar 22, 2026");
1014        tbl.cell(2, 0).unwrap().set_text("Prepared by:");
1015        tbl.cell(2, 1).unwrap().set_text("Walter White");
1016        tbl.cell(2, 2).unwrap().set_text("Payment:");
1017        tbl.cell(2, 3).unwrap().set_text("Net 30");
1018        tbl.cell(3, 0).unwrap().set_text("Customer:");
1019        tbl.cell(3, 1).unwrap().set_text("Meridian Dynamics LLC");
1020        tbl.cell(3, 2).unwrap().set_text("Currency:");
1021        tbl.cell(3, 3).unwrap().set_text("USD");
1022    }
1023
1024    doc.add_paragraph("");
1025
1026    // ── Bill of Materials ──
1027    {
1028        let mut p = doc.add_paragraph("");
1029        p.add_run("Bill of Materials")
1030            .bold(true)
1031            .size(16.0)
1032            .color("1A3C3C");
1033    }
1034    doc.add_paragraph("");
1035
1036    {
1037        let mut tbl = doc
1038            .add_table(10, 6)
1039            .borders(BorderStyle::Single, 2, "008B8B")
1040            .width_pct(100.0)
1041            .layout_fixed();
1042        // Headers
1043        let hdrs = [
1044            "#",
1045            "Part Number",
1046            "Description",
1047            "Qty",
1048            "Unit Price",
1049            "Total",
1050        ];
1051        for (c, h) in hdrs.iter().enumerate() {
1052            tbl.cell(0, c).unwrap().set_text(h);
1053            tbl.cell(0, c).unwrap().shading("008B8B");
1054        }
1055
1056        let items: Vec<(&str, &str, &str, &str, &str)> = vec![
1057            (
1058                "TB-GPU-A100",
1059                "NVIDIA A100 80GB GPU",
1060                "4",
1061                "$12,500.00",
1062                "$50,000.00",
1063            ),
1064            (
1065                "TB-SRV-R750",
1066                "Dell R750xa Server Chassis",
1067                "2",
1068                "$8,200.00",
1069                "$16,400.00",
1070            ),
1071            (
1072                "TB-RAM-256G",
1073                "256GB DDR5 ECC Memory Module",
1074                "8",
1075                "$890.00",
1076                "$7,120.00",
1077            ),
1078            (
1079                "TB-SSD-3840",
1080                "3.84TB NVMe U.2 SSD",
1081                "8",
1082                "$1,150.00",
1083                "$9,200.00",
1084            ),
1085            (
1086                "TB-NET-CX7",
1087                "ConnectX-7 200GbE NIC",
1088                "4",
1089                "$1,800.00",
1090                "$7,200.00",
1091            ),
1092            (
1093                "TB-SW-48P",
1094                "48-Port 100GbE Switch",
1095                "1",
1096                "$22,000.00",
1097                "$22,000.00",
1098            ),
1099            (
1100                "TB-CAB-RACK",
1101                "42U Server Rack + PDU",
1102                "1",
1103                "$4,500.00",
1104                "$4,500.00",
1105            ),
1106            (
1107                "TB-SVC-INST",
1108                "Installation & Configuration",
1109                "1",
1110                "$8,500.00",
1111                "$8,500.00",
1112            ),
1113            (
1114                "TB-SVC-SUPP",
1115                "3-Year Premium Support",
1116                "1",
1117                "$15,000.00",
1118                "$15,000.00",
1119            ),
1120        ];
1121
1122        for (i, (pn, desc, qty, unit, total)) in items.iter().enumerate() {
1123            let row = i + 1;
1124            tbl.cell(row, 0).unwrap().set_text(&format!("{}", i + 1));
1125            tbl.cell(row, 1).unwrap().set_text(pn);
1126            tbl.cell(row, 2).unwrap().set_text(desc);
1127            tbl.cell(row, 3).unwrap().set_text(qty);
1128            tbl.cell(row, 4).unwrap().set_text(unit);
1129            tbl.cell(row, 5).unwrap().set_text(total);
1130            if i % 2 == 0 {
1131                for c in 0..6 {
1132                    tbl.cell(row, c).unwrap().shading("F0F8F8");
1133                }
1134            }
1135        }
1136    }
1137
1138    doc.add_paragraph("");
1139
1140    // ── Totals ──
1141    {
1142        let mut tbl = doc
1143            .add_table(4, 2)
1144            .borders(BorderStyle::Single, 2, "008B8B")
1145            .width(Length::inches(3.5))
1146            .alignment(Alignment::Right);
1147        tbl.cell(0, 0).unwrap().set_text("Subtotal");
1148        tbl.cell(0, 1).unwrap().set_text("$139,920.00");
1149        tbl.cell(1, 0).unwrap().set_text("Shipping & Handling");
1150        tbl.cell(1, 1).unwrap().set_text("$2,500.00");
1151        tbl.cell(2, 0).unwrap().set_text("Tax (8.625%)");
1152        tbl.cell(2, 1).unwrap().set_text("$12,068.10");
1153        tbl.cell(3, 0).unwrap().set_text("TOTAL");
1154        tbl.cell(3, 0).unwrap().shading("E07020");
1155        tbl.cell(3, 1).unwrap().set_text("$154,488.10");
1156        tbl.cell(3, 1).unwrap().shading("E07020");
1157    }
1158
1159    doc.add_paragraph("");
1160
1161    // ── Terms & Conditions ──
1162    {
1163        let mut p = doc.add_paragraph("");
1164        p.add_run("Terms & Conditions")
1165            .bold(true)
1166            .size(14.0)
1167            .color("1A3C3C");
1168    }
1169    doc.add_numbered_list_item(
1170        "This quotation is valid for 30 calendar days from the date of issue.",
1171        0,
1172    );
1173    doc.add_numbered_list_item(
1174        "All prices are in USD and exclusive of applicable taxes unless stated.",
1175        0,
1176    );
1177    doc.add_numbered_list_item("Standard lead time is 4-6 weeks from PO receipt.", 0);
1178    doc.add_numbered_list_item(
1179        "Payment terms: Net 30 from invoice date. 2% discount for payment within 10 days.",
1180        0,
1181    );
1182    doc.add_numbered_list_item(
1183        "Warranty: 3-year manufacturer warranty on all hardware components.",
1184        0,
1185    );
1186    doc.add_numbered_list_item(
1187        "Returns subject to 15% restocking fee if initiated after 14 days.",
1188        0,
1189    );
1190
1191    doc.add_paragraph("");
1192    doc.add_paragraph("")
1193        .border_bottom(BorderStyle::Single, 4, "008B8B");
1194    doc.add_paragraph("");
1195    {
1196        let mut p = doc.add_paragraph("");
1197        p.add_run("Accepted by: ").bold(true);
1198        p.add_run("___________________________ Date: ___________");
1199    }
1200    {
1201        let mut p = doc.add_paragraph("");
1202        p.add_run("Print Name: ").bold(true);
1203        p.add_run("___________________________ Title: ___________");
1204    }
1205
1206    doc
1207}
1208
1209// =============================================================================
1210// 4. INVOICE — Crimson + charcoal scheme
1211// =============================================================================
1212fn generate_invoice(_samples_dir: &Path) -> Document {
1213    let mut doc = Document::new();
1214
1215    // Colors: Crimson #B22222, Charcoal #333333, Light #FAF0F0
1216    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1217    doc.set_margins(
1218        Length::inches(0.75),
1219        Length::inches(0.75),
1220        Length::inches(0.75),
1221        Length::inches(0.75),
1222    );
1223    doc.set_title("Invoice INV-2026-0392");
1224    doc.set_author("Walter White");
1225    doc.set_keywords("invoice, Tensorbee");
1226
1227    doc.set_footer("Tensorbee — Thank you for your business!");
1228
1229    // ── Company & Invoice Header ──
1230    {
1231        let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1232        // Company name left, INVOICE right
1233        {
1234            let mut cell = tbl.cell(0, 0).unwrap();
1235            let mut p = cell.add_paragraph("");
1236            p.add_run("TENSORBEE")
1237                .bold(true)
1238                .size(32.0)
1239                .color("B22222")
1240                .font("Helvetica");
1241        }
1242        {
1243            let mut cell = tbl.cell(0, 1).unwrap();
1244            let mut p = cell.add_paragraph("");
1245            p.add_run("INVOICE").bold(true).size(32.0).color("333333");
1246        }
1247        tbl.cell(1, 0)
1248            .unwrap()
1249            .set_text("123 Innovation Drive, Suite 400");
1250        tbl.cell(1, 1).unwrap().set_text("Invoice #: INV-2026-0392");
1251        tbl.cell(2, 0).unwrap().set_text("San Francisco, CA 94105");
1252        tbl.cell(2, 1).unwrap().set_text("Date: February 22, 2026");
1253        tbl.cell(3, 0).unwrap().set_text("walter@tensorbee.com");
1254        tbl.cell(3, 1).unwrap().set_text("Due Date: March 24, 2026");
1255    }
1256
1257    doc.add_paragraph("")
1258        .border_bottom(BorderStyle::Thick, 8, "B22222");
1259    doc.add_paragraph("");
1260
1261    // ── Bill To / Ship To ──
1262    {
1263        let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1264        tbl.cell(0, 0).unwrap().set_text("BILL TO");
1265        tbl.cell(0, 0).unwrap().shading("B22222");
1266        tbl.cell(0, 1).unwrap().set_text("SHIP TO");
1267        tbl.cell(0, 1).unwrap().shading("B22222");
1268        tbl.cell(1, 0).unwrap().set_text("Meridian Dynamics LLC");
1269        tbl.cell(1, 1).unwrap().set_text("Meridian Dynamics LLC");
1270        tbl.cell(2, 0).unwrap().set_text("456 Enterprise Blvd");
1271        tbl.cell(2, 1).unwrap().set_text("Attn: Server Room B");
1272        tbl.cell(3, 0).unwrap().set_text("Austin, TX 78701");
1273        tbl.cell(3, 1)
1274            .unwrap()
1275            .set_text("456 Enterprise Blvd, Austin, TX 78701");
1276    }
1277
1278    doc.add_paragraph("");
1279
1280    // ── Line Items ──
1281    {
1282        let mut tbl = doc
1283            .add_table(8, 5)
1284            .borders(BorderStyle::Single, 2, "B22222")
1285            .width_pct(100.0);
1286        let hdrs = ["Description", "Qty", "Unit Price", "Tax", "Amount"];
1287        for (c, h) in hdrs.iter().enumerate() {
1288            tbl.cell(0, c).unwrap().set_text(h);
1289            tbl.cell(0, c).unwrap().shading("B22222");
1290        }
1291        let items = [
1292            (
1293                "ML Infrastructure Setup — Phase 1",
1294                "1",
1295                "$45,000.00",
1296                "$3,881.25",
1297                "$48,881.25",
1298            ),
1299            (
1300                "Data Pipeline Development (160 hrs)",
1301                "160",
1302                "$225.00",
1303                "$3,105.00",
1304                "$39,105.00",
1305            ),
1306            (
1307                "GPU Cluster Configuration",
1308                "1",
1309                "$12,000.00",
1310                "$1,035.00",
1311                "$13,035.00",
1312            ),
1313            (
1314                "API Gateway Implementation",
1315                "1",
1316                "$18,500.00",
1317                "$1,595.63",
1318                "$20,095.63",
1319            ),
1320            (
1321                "Load Testing & QA (80 hrs)",
1322                "80",
1323                "$195.00",
1324                "$1,345.50",
1325                "$16,945.50",
1326            ),
1327            (
1328                "Documentation & Training",
1329                "1",
1330                "$8,000.00",
1331                "$690.00",
1332                "$8,690.00",
1333            ),
1334            (
1335                "Project Management (3 months)",
1336                "3",
1337                "$6,500.00",
1338                "$1,679.63",
1339                "$21,179.63",
1340            ),
1341        ];
1342        for (i, (desc, qty, unit, tax, amt)) in items.iter().enumerate() {
1343            let r = i + 1;
1344            tbl.cell(r, 0).unwrap().set_text(desc);
1345            tbl.cell(r, 1).unwrap().set_text(qty);
1346            tbl.cell(r, 2).unwrap().set_text(unit);
1347            tbl.cell(r, 3).unwrap().set_text(tax);
1348            tbl.cell(r, 4).unwrap().set_text(amt);
1349            if i % 2 == 0 {
1350                for c in 0..5 {
1351                    tbl.cell(r, c).unwrap().shading("FAF0F0");
1352                }
1353            }
1354        }
1355    }
1356
1357    doc.add_paragraph("");
1358
1359    // ── Totals ──
1360    {
1361        let mut tbl = doc
1362            .add_table(4, 2)
1363            .borders(BorderStyle::Single, 2, "B22222")
1364            .width(Length::inches(3.0))
1365            .alignment(Alignment::Right);
1366        tbl.cell(0, 0).unwrap().set_text("Subtotal");
1367        tbl.cell(0, 1).unwrap().set_text("$154,600.00");
1368        tbl.cell(1, 0).unwrap().set_text("Tax (8.625%)");
1369        tbl.cell(1, 1).unwrap().set_text("$13,334.25");
1370        tbl.cell(2, 0).unwrap().set_text("Discount (5%)");
1371        tbl.cell(2, 1).unwrap().set_text("-$7,730.00");
1372        tbl.cell(3, 0).unwrap().set_text("AMOUNT DUE");
1373        tbl.cell(3, 0).unwrap().shading("B22222");
1374        tbl.cell(3, 1).unwrap().set_text("$160,204.25");
1375        tbl.cell(3, 1).unwrap().shading("B22222");
1376    }
1377
1378    doc.add_paragraph("");
1379    doc.add_paragraph("");
1380
1381    // ── Payment Details ──
1382    {
1383        let mut p = doc.add_paragraph("");
1384        p.add_run("Payment Details")
1385            .bold(true)
1386            .size(14.0)
1387            .color("333333");
1388    }
1389    doc.add_paragraph("Bank: Silicon Valley Bank")
1390        .indent_left(Length::inches(0.3));
1391    doc.add_paragraph("Account: Tensorbee Inc. — 0847-2953-1120")
1392        .indent_left(Length::inches(0.3));
1393    doc.add_paragraph("Routing: 121140399")
1394        .indent_left(Length::inches(0.3));
1395    doc.add_paragraph("Swift: SVBKUS6S")
1396        .indent_left(Length::inches(0.3));
1397
1398    doc.add_paragraph("");
1399
1400    doc.add_paragraph("Please include invoice number INV-2026-0392 in the payment reference.")
1401        .shading("FAF0F0")
1402        .border_all(BorderStyle::Single, 2, "B22222");
1403
1404    doc
1405}
1406
1407// =============================================================================
1408// 5. REPORT — Forest green + earth tones, with images & hierarchical sections
1409// =============================================================================
1410fn generate_report(_samples_dir: &Path) -> Document {
1411    let mut doc = Document::new();
1412
1413    // Colors: Forest #2D5016, Sage #6B8E23, Earth #8B7355, Cream #FFFAF0
1414    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1415    doc.set_margins(
1416        Length::inches(1.0),
1417        Length::inches(1.0),
1418        Length::inches(1.0),
1419        Length::inches(1.0),
1420    );
1421    doc.set_title("Q4 2025 Environmental Impact Report");
1422    doc.set_author("Walter White");
1423    doc.set_subject("Quarterly Environmental Report");
1424    doc.set_keywords("environment, sustainability, report, Tensorbee");
1425
1426    doc.set_different_first_page(true);
1427    doc.set_first_page_header("");
1428    doc.set_header("Tensorbee — Q4 2025 Environmental Impact Report");
1429    doc.set_footer("CONFIDENTIAL — Page");
1430
1431    // ── Cover ──
1432    let cover_bg = create_sample_png(612, 792, [20, 50, 15]);
1433    doc.add_background_image(&cover_bg, "report_cover.png");
1434
1435    for _ in 0..5 {
1436        doc.add_paragraph("");
1437    }
1438    {
1439        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1440        p.add_run("Q4 2025")
1441            .bold(true)
1442            .size(48.0)
1443            .color("FFFFFF")
1444            .font("Georgia");
1445    }
1446    {
1447        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1448        p.add_run("Environmental Impact Report")
1449            .size(24.0)
1450            .color("90EE90")
1451            .font("Georgia")
1452            .italic(true);
1453    }
1454    doc.add_paragraph("");
1455    {
1456        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1457        p.add_run("Tensorbee — Sustainability Division")
1458            .size(14.0)
1459            .color("C0C0C0");
1460    }
1461    {
1462        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1463        p.add_run("Prepared by Walter White, Chief Sustainability Officer")
1464            .size(11.0)
1465            .color("AAAAAA");
1466    }
1467
1468    // ── TOC ──
1469    doc.add_paragraph("").page_break_before(true);
1470    doc.insert_toc(doc.content_count(), 3);
1471
1472    // ── Section 1: Executive Overview ──
1473    doc.add_paragraph("").page_break_before(true);
1474    doc.add_paragraph("Executive Overview").style("Heading1");
1475    doc.add_paragraph(
1476        "This report presents Tensorbee's environmental performance for Q4 2025. Our \
1477         sustainability initiatives have yielded a 34% reduction in carbon emissions compared \
1478         to Q4 2024, exceeding our target of 25%. Key achievements include the transition to \
1479         100% renewable energy in our primary data centers and the launch of our carbon offset \
1480         marketplace.",
1481    )
1482    .first_line_indent(Length::inches(0.3));
1483
1484    // Image: performance chart
1485    let chart_img = create_chart_png(400, 200);
1486    doc.add_paragraph("");
1487    doc.add_picture(
1488        &chart_img,
1489        "performance_chart.png",
1490        Length::inches(5.0),
1491        Length::inches(2.5),
1492    )
1493    .alignment(Alignment::Center);
1494    {
1495        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1496        p.add_run("Figure 1: Quarterly Carbon Emissions (tonnes CO2e)")
1497            .italic(true)
1498            .size(9.0)
1499            .color("666666");
1500    }
1501
1502    // ── Section 2: Energy Consumption ──
1503    doc.add_paragraph("").page_break_before(true);
1504    doc.add_paragraph("Energy Consumption").style("Heading1");
1505
1506    doc.add_paragraph("Data Center Operations")
1507        .style("Heading2");
1508    doc.add_paragraph(
1509        "Our three primary data centers consumed a combined 4.2 GWh in Q4 2025, \
1510         a 12% reduction from Q3 2025 driven by improved cooling efficiency and \
1511         server consolidation.",
1512    );
1513
1514    // Data table
1515    {
1516        let mut tbl = doc
1517            .add_table(5, 4)
1518            .borders(BorderStyle::Single, 4, "2D5016")
1519            .width_pct(100.0);
1520        let hdrs = ["Data Center", "Capacity (MW)", "Usage (GWh)", "PUE"];
1521        for (c, h) in hdrs.iter().enumerate() {
1522            tbl.cell(0, c).unwrap().set_text(h);
1523            tbl.cell(0, c).unwrap().shading("2D5016");
1524        }
1525        let rows = [
1526            ("San Francisco (Primary)", "3.2", "1.8", "1.12"),
1527            ("Dublin (EU)", "2.1", "1.2", "1.18"),
1528            ("Singapore (APAC)", "1.8", "1.2", "1.24"),
1529            ("TOTAL", "7.1", "4.2", "1.17 avg"),
1530        ];
1531        for (i, (dc, cap, use_, pue)) in rows.iter().enumerate() {
1532            tbl.cell(i + 1, 0).unwrap().set_text(dc);
1533            tbl.cell(i + 1, 1).unwrap().set_text(cap);
1534            tbl.cell(i + 1, 2).unwrap().set_text(use_);
1535            tbl.cell(i + 1, 3).unwrap().set_text(pue);
1536            if i == 3 {
1537                for c in 0..4 {
1538                    tbl.cell(i + 1, c).unwrap().shading("E2EFDA");
1539                }
1540            }
1541        }
1542    }
1543
1544    doc.add_paragraph("");
1545
1546    doc.add_paragraph("Renewable Energy Mix").style("Heading2");
1547    doc.add_paragraph("Breakdown of energy sources across all facilities:");
1548
1549    doc.add_bullet_list_item("Solar PV: 42% (1.76 GWh)", 0);
1550    doc.add_bullet_list_item("Wind Power Purchase Agreements: 38% (1.60 GWh)", 0);
1551    doc.add_bullet_list_item("Hydroelectric: 12% (0.50 GWh)", 0);
1552    doc.add_bullet_list_item("Grid (non-renewable): 8% (0.34 GWh)", 0);
1553
1554    // ── Section 3: Water & Waste ──
1555    doc.add_paragraph("Water & Waste Management")
1556        .style("Heading1");
1557
1558    doc.add_paragraph("Water Usage").style("Heading2");
1559    doc.add_paragraph(
1560        "Total water consumption was 12.4 million gallons, a 15% reduction from Q3. \
1561         Our closed-loop cooling systems now recycle 78% of water used in cooling operations.",
1562    );
1563
1564    doc.add_paragraph("Waste Reduction").style("Heading2");
1565    doc.add_paragraph("E-waste management results for Q4:")
1566        .keep_with_next(true);
1567    {
1568        let mut tbl = doc
1569            .add_table(4, 3)
1570            .borders(BorderStyle::Single, 2, "6B8E23")
1571            .width_pct(80.0);
1572        tbl.cell(0, 0).unwrap().set_text("Category");
1573        tbl.cell(0, 0).unwrap().shading("6B8E23");
1574        tbl.cell(0, 1).unwrap().set_text("Weight (kg)");
1575        tbl.cell(0, 1).unwrap().shading("6B8E23");
1576        tbl.cell(0, 2).unwrap().set_text("Recycled %");
1577        tbl.cell(0, 2).unwrap().shading("6B8E23");
1578        let rows = [
1579            ("Server Hardware", "2,450", "94%"),
1580            ("Networking Equipment", "820", "91%"),
1581            ("Storage Media", "340", "99%"),
1582        ];
1583        for (i, (cat, wt, pct)) in rows.iter().enumerate() {
1584            tbl.cell(i + 1, 0).unwrap().set_text(cat);
1585            tbl.cell(i + 1, 1).unwrap().set_text(wt);
1586            tbl.cell(i + 1, 2).unwrap().set_text(pct);
1587        }
1588    }
1589
1590    // ── Section 4: Initiatives ──
1591    doc.add_paragraph("").page_break_before(true);
1592    doc.add_paragraph("Q1 2026 Initiatives").style("Heading1");
1593
1594    doc.add_paragraph("Planned Programs").style("Heading2");
1595    doc.add_numbered_list_item(
1596        "Deploy on-site battery storage at SF data center (2 MWh capacity)",
1597        0,
1598    );
1599    doc.add_numbered_list_item("Pilot immersion cooling in Dublin facility", 0);
1600    doc.add_numbered_list_item("Launch employee commute carbon offset program", 0);
1601    doc.add_numbered_list_item("Achieve ISO 14001 certification for Singapore facility", 0);
1602
1603    doc.add_paragraph("Investment Targets").style("Heading2");
1604    doc.add_paragraph("Sustainability CapEx allocation for FY2026:")
1605        .keep_with_next(true);
1606    {
1607        let mut tbl = doc
1608            .add_table(5, 2)
1609            .borders(BorderStyle::Single, 4, "2D5016");
1610        tbl.cell(0, 0).unwrap().set_text("Initiative");
1611        tbl.cell(0, 0).unwrap().shading("2D5016");
1612        tbl.cell(0, 1).unwrap().set_text("Budget");
1613        tbl.cell(0, 1).unwrap().shading("2D5016");
1614        let rows = [
1615            ("Battery Storage", "$1.2M"),
1616            ("Immersion Cooling Pilot", "$800K"),
1617            ("Solar Panel Expansion", "$2.1M"),
1618            ("Carbon Credits", "$500K"),
1619        ];
1620        for (i, (init, budget)) in rows.iter().enumerate() {
1621            tbl.cell(i + 1, 0).unwrap().set_text(init);
1622            tbl.cell(i + 1, 1).unwrap().set_text(budget);
1623            if i % 2 == 0 {
1624                tbl.cell(i + 1, 0).unwrap().shading("FFFAF0");
1625                tbl.cell(i + 1, 1).unwrap().shading("FFFAF0");
1626            }
1627        }
1628    }
1629
1630    // Image: sustainability roadmap
1631    let roadmap_img = create_sample_png(500, 100, [40, 80, 30]);
1632    doc.add_paragraph("");
1633    doc.add_picture(
1634        &roadmap_img,
1635        "roadmap.png",
1636        Length::inches(6.0),
1637        Length::inches(1.2),
1638    )
1639    .alignment(Alignment::Center);
1640    {
1641        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1642        p.add_run("Figure 2: 2026 Sustainability Roadmap")
1643            .italic(true)
1644            .size(9.0)
1645            .color("666666");
1646    }
1647
1648    doc.add_paragraph("");
1649    doc.add_paragraph(
1650        "For questions about this report, contact Walter White at sustainability@tensorbee.com.",
1651    )
1652    .shading("FFFAF0")
1653    .border_all(BorderStyle::Single, 2, "2D5016");
1654
1655    doc
1656}
1657
1658// =============================================================================
1659// 6. LETTER — Slate blue + silver scheme
1660// =============================================================================
1661fn generate_letter(_samples_dir: &Path) -> Document {
1662    let mut doc = Document::new();
1663
1664    // Colors: Slate #4A5568, Blue accent #3182CE
1665    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1666    doc.set_margins(
1667        Length::inches(1.25),
1668        Length::inches(1.25),
1669        Length::inches(1.25),
1670        Length::inches(1.25),
1671    );
1672    doc.set_title("Business Letter");
1673    doc.set_author("Walter White");
1674
1675    // Banner header using raw XML
1676    let logo_img = create_logo_png(220, 48);
1677    let banner = build_header_banner_xml(
1678        "rId1",
1679        &BannerOpts {
1680            bg_color: "4A5568",
1681            banner_width: 7772400,
1682            banner_height: 731520, // ~0.8"
1683            logo_width: 2011680,
1684            logo_height: 438912,
1685            logo_x_offset: 295125,
1686            logo_y_offset: 146304,
1687        },
1688    );
1689    doc.set_raw_header_with_images(
1690        banner,
1691        &[("rId1", &logo_img, "logo.png")],
1692        rdocx_oxml::header_footer::HdrFtrType::Default,
1693    );
1694    doc.set_margins(
1695        Length::twips(2000),
1696        Length::twips(1800),
1697        Length::twips(1440),
1698        Length::twips(1800),
1699    );
1700    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
1701
1702    // Footer with contact
1703    doc.set_footer(
1704        "Tensorbee | 123 Innovation Drive, Suite 400 | San Francisco, CA 94105 | tensorbee.com",
1705    );
1706
1707    // ── Sender Address ──
1708    doc.add_paragraph("Walter White");
1709    doc.add_paragraph("Chief Executive Officer");
1710    doc.add_paragraph("Tensorbee");
1711    doc.add_paragraph("123 Innovation Drive, Suite 400");
1712    doc.add_paragraph("San Francisco, CA 94105");
1713    doc.add_paragraph("");
1714    doc.add_paragraph("February 22, 2026");
1715
1716    doc.add_paragraph("");
1717
1718    // ── Recipient ──
1719    doc.add_paragraph("Ms. Sarah Chen");
1720    doc.add_paragraph("VP of Engineering");
1721    doc.add_paragraph("Meridian Dynamics LLC");
1722    doc.add_paragraph("456 Enterprise Boulevard");
1723    doc.add_paragraph("Austin, TX 78701");
1724
1725    doc.add_paragraph("");
1726
1727    // ── Subject ──
1728    {
1729        let mut p = doc.add_paragraph("");
1730        p.add_run("Re: Strategic Technology Partnership — Phase 2 Expansion")
1731            .bold(true);
1732    }
1733
1734    doc.add_paragraph("");
1735
1736    // ── Body ──
1737    doc.add_paragraph("Dear Ms. Chen,");
1738    doc.add_paragraph("");
1739
1740    doc.add_paragraph(
1741        "I am writing to express Tensorbee's enthusiasm for expanding our strategic technology \
1742         partnership with Meridian Dynamics. Following the successful completion of Phase 1, \
1743         which delivered a 40% improvement in your ML inference pipeline throughput, we believe \
1744         the foundation is firmly established for an ambitious Phase 2 engagement.",
1745    )
1746    .first_line_indent(Length::inches(0.5));
1747
1748    doc.add_paragraph(
1749        "During our recent executive review, your team highlighted three priority areas for \
1750         the next phase of collaboration:",
1751    )
1752    .first_line_indent(Length::inches(0.5));
1753
1754    doc.add_numbered_list_item(
1755        "Real-time anomaly detection for your financial transaction monitoring system, \
1756         targeting sub-100ms latency at 50,000 transactions per second.",
1757        0,
1758    );
1759    doc.add_numbered_list_item(
1760        "Federated learning infrastructure to enable model training across your distributed \
1761         data centers without centralizing sensitive financial data.",
1762        0,
1763    );
1764    doc.add_numbered_list_item(
1765        "MLOps automation to reduce your model deployment cycle from the current 5 days \
1766         to under 4 hours.",
1767        0,
1768    );
1769
1770    doc.add_paragraph(
1771        "Our engineering team has prepared a detailed technical proposal addressing each of \
1772         these areas. We have allocated a dedicated team of eight senior engineers, led by \
1773         our CTO, to ensure continuity with the Phase 1 team your organization has already \
1774         built a productive working relationship with.",
1775    )
1776    .first_line_indent(Length::inches(0.5));
1777
1778    doc.add_paragraph(
1779        "I would welcome the opportunity to present our Phase 2 proposal in person. My \
1780         assistant will reach out to coordinate a meeting at your convenience during the \
1781         first week of March.",
1782    )
1783    .first_line_indent(Length::inches(0.5));
1784
1785    doc.add_paragraph("");
1786
1787    doc.add_paragraph("Warm regards,");
1788    doc.add_paragraph("");
1789    doc.add_paragraph("");
1790
1791    // Signature line
1792    doc.add_paragraph("")
1793        .border_bottom(BorderStyle::Single, 4, "4A5568");
1794    {
1795        let mut p = doc.add_paragraph("");
1796        p.add_run("Walter White").bold(true).size(12.0);
1797    }
1798    {
1799        let mut p = doc.add_paragraph("");
1800        p.add_run("Chief Executive Officer, Tensorbee")
1801            .italic(true)
1802            .size(10.0)
1803            .color("666666");
1804    }
1805    {
1806        let mut p = doc.add_paragraph("");
1807        p.add_run("walter@tensorbee.com | +1 (415) 555-0199")
1808            .size(10.0)
1809            .color("888888");
1810    }
1811
1812    doc
1813}
1814
1815// =============================================================================
1816// 7. EMPLOYMENT CONTRACT — Purple + charcoal scheme
1817// =============================================================================
1818fn generate_contract(_samples_dir: &Path) -> Document {
1819    let mut doc = Document::new();
1820
1821    // Colors: Purple #5B2C6F, Plum #8E44AD, Gray #2C3E50
1822    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1823    doc.set_margins(
1824        Length::inches(1.25),
1825        Length::inches(1.0),
1826        Length::inches(1.0),
1827        Length::inches(1.0),
1828    );
1829    doc.set_title("Employment Agreement");
1830    doc.set_author("Tensorbee HR Department");
1831    doc.set_subject("Employment Contract — Walter White");
1832    doc.set_keywords("employment, contract, agreement, Tensorbee");
1833
1834    doc.set_header("TENSORBEE — Employment Agreement");
1835    doc.set_footer("Employment Agreement — Walter White — Page");
1836
1837    // ── Title Block ──
1838    doc.add_paragraph("")
1839        .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1840    {
1841        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1842        p.add_run("EMPLOYMENT AGREEMENT")
1843            .bold(true)
1844            .size(24.0)
1845            .color("5B2C6F")
1846            .font("Georgia");
1847    }
1848    doc.add_paragraph("")
1849        .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1850    doc.add_paragraph("");
1851
1852    // ── Parties ──
1853    doc.add_paragraph(
1854        "This Employment Agreement (\"Agreement\") is entered into as of February 22, 2026 \
1855         (\"Effective Date\"), by and between:",
1856    );
1857
1858    doc.add_paragraph("");
1859
1860    {
1861        let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1862        p.add_run("EMPLOYER: ").bold(true);
1863        p.add_run(
1864            "Tensorbee, Inc., a Delaware corporation with its principal place of business at \
1865                   123 Innovation Drive, Suite 400, San Francisco, CA 94105 (\"Company\")",
1866        );
1867    }
1868    doc.add_paragraph("");
1869    {
1870        let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1871        p.add_run("EMPLOYEE: ").bold(true);
1872        p.add_run(
1873            "Walter White, residing at 308 Negra Arroyo Lane, Albuquerque, NM 87104 (\"Employee\")",
1874        );
1875    }
1876
1877    doc.add_paragraph("");
1878    doc.add_paragraph("The Company and Employee are collectively referred to as the \"Parties.\"");
1879    doc.add_paragraph("");
1880
1881    // ── Article 1: Position and Duties ──
1882    doc.add_paragraph("Article 1 — Position and Duties")
1883        .style("Heading1");
1884    {
1885        let mut p = doc.add_paragraph("");
1886        p.add_run("1.1 ").bold(true);
1887        p.add_run("The Company hereby employs the Employee as ");
1888        p.add_run("Chief Executive Officer (CEO)")
1889            .bold(true)
1890            .italic(true);
1891        p.add_run(", reporting directly to the Board of Directors.");
1892    }
1893    {
1894        let mut p = doc.add_paragraph("");
1895        p.add_run("1.2 ").bold(true);
1896        p.add_run("The Employee shall devote full working time, attention, and best efforts to \
1897                   the performance of duties as reasonably assigned by the Board, including but not limited to:");
1898    }
1899    doc.add_bullet_list_item(
1900        "Setting and executing the Company's strategic vision and business plan",
1901        0,
1902    );
1903    doc.add_bullet_list_item(
1904        "Overseeing all operations, engineering, and commercial activities",
1905        0,
1906    );
1907    doc.add_bullet_list_item(
1908        "Representing the Company to investors, customers, and the public",
1909        0,
1910    );
1911    doc.add_bullet_list_item("Recruiting, developing, and retaining key talent", 0);
1912
1913    {
1914        let mut p = doc.add_paragraph("");
1915        p.add_run("1.3 ").bold(true);
1916        p.add_run(
1917            "The Employee's primary work location shall be the Company's San Francisco \
1918                   headquarters, with reasonable travel as required by business needs.",
1919        );
1920    }
1921
1922    // ── Article 2: Compensation ──
1923    doc.add_paragraph("Article 2 — Compensation and Benefits")
1924        .style("Heading1");
1925    {
1926        let mut p = doc.add_paragraph("");
1927        p.add_run("2.1 Base Salary. ").bold(true);
1928        p.add_run("The Company shall pay the Employee an annual base salary of ");
1929        p.add_run("$375,000.00").bold(true);
1930        p.add_run(" (Three Hundred Seventy-Five Thousand Dollars), payable in accordance with the \
1931                   Company's standard payroll schedule, less applicable withholdings and deductions.");
1932    }
1933    {
1934        let mut p = doc.add_paragraph("");
1935        p.add_run("2.2 Annual Bonus. ").bold(true);
1936        p.add_run("The Employee shall be eligible for an annual performance bonus of up to ");
1937        p.add_run("40%").bold(true);
1938        p.add_run(
1939            " of base salary, based on achievement of mutually agreed performance objectives.",
1940        );
1941    }
1942    {
1943        let mut p = doc.add_paragraph("");
1944        p.add_run("2.3 Equity. ").bold(true);
1945        p.add_run("Subject to Board approval, the Employee shall receive a stock option grant of ");
1946        p.add_run("500,000 shares").bold(true);
1947        p.add_run(
1948            " of the Company's common stock, vesting over four (4) years with a one-year cliff, \
1949                   at an exercise price equal to the fair market value on the date of grant.",
1950        );
1951    }
1952
1953    // Compensation summary table
1954    doc.add_paragraph("");
1955    {
1956        let mut tbl = doc
1957            .add_table(5, 2)
1958            .borders(BorderStyle::Single, 4, "5B2C6F")
1959            .width_pct(70.0)
1960            .alignment(Alignment::Center);
1961        tbl.cell(0, 0).unwrap().set_text("Compensation Element");
1962        tbl.cell(0, 0).unwrap().shading("5B2C6F");
1963        tbl.cell(0, 1).unwrap().set_text("Value");
1964        tbl.cell(0, 1).unwrap().shading("5B2C6F");
1965        let items = [
1966            ("Base Salary", "$375,000/year"),
1967            ("Target Bonus", "Up to 40% ($150,000)"),
1968            ("Equity Grant", "500,000 shares (4yr vest)"),
1969            ("Total Target Comp", "$525,000 + equity"),
1970        ];
1971        for (i, (elem, val)) in items.iter().enumerate() {
1972            tbl.cell(i + 1, 0).unwrap().set_text(elem);
1973            tbl.cell(i + 1, 1).unwrap().set_text(val);
1974            if i == 3 {
1975                tbl.cell(i + 1, 0).unwrap().shading("E8DAEF");
1976                tbl.cell(i + 1, 1).unwrap().shading("E8DAEF");
1977            }
1978        }
1979    }
1980
1981    // ── Article 3: Benefits ──
1982    doc.add_paragraph("").page_break_before(true);
1983    doc.add_paragraph("Article 3 — Benefits").style("Heading1");
1984    {
1985        let mut p = doc.add_paragraph("");
1986        p.add_run("3.1 ").bold(true);
1987        p.add_run(
1988            "The Employee shall be entitled to participate in all benefit programs \
1989                   generally available to senior executives, including:",
1990        );
1991    }
1992    doc.add_bullet_list_item(
1993        "Medical, dental, and vision insurance (100% premium coverage for employee and dependents)",
1994        0,
1995    );
1996    doc.add_bullet_list_item("401(k) retirement plan with 6% company match", 0);
1997    doc.add_bullet_list_item("Life insurance and long-term disability coverage", 0);
1998    doc.add_bullet_list_item("Annual professional development allowance of $10,000", 0);
1999
2000    {
2001        let mut p = doc.add_paragraph("");
2002        p.add_run("3.2 Paid Time Off. ").bold(true);
2003        p.add_run(
2004            "The Employee shall receive 25 days of paid vacation per year, plus Company holidays, \
2005                   accruing on a monthly basis.",
2006        );
2007    }
2008
2009    // ── Article 4: Term and Termination ──
2010    doc.add_paragraph("Article 4 — Term and Termination")
2011        .style("Heading1");
2012    {
2013        let mut p = doc.add_paragraph("");
2014        p.add_run("4.1 At-Will Employment. ").bold(true);
2015        p.add_run(
2016            "This Agreement is for at-will employment and may be terminated by either Party \
2017                   at any time, with or without cause, subject to the notice provisions herein.",
2018        );
2019    }
2020    {
2021        let mut p = doc.add_paragraph("");
2022        p.add_run("4.2 Notice Period. ").bold(true);
2023        p.add_run("Either Party shall provide ");
2024        p.add_run("ninety (90) days").bold(true);
2025        p.add_run(" written notice of termination, or pay in lieu thereof.");
2026    }
2027    {
2028        let mut p = doc.add_paragraph("");
2029        p.add_run("4.3 Severance. ").bold(true);
2030        p.add_run("In the event of termination by the Company without Cause, the Employee shall \
2031                   receive (a) twelve (12) months of base salary continuation, (b) pro-rata bonus \
2032                   for the year of termination, and (c) twelve (12) months of COBRA premium coverage.");
2033    }
2034
2035    // ── Article 5: Confidentiality ──
2036    doc.add_paragraph("Article 5 — Confidentiality and IP")
2037        .style("Heading1");
2038    {
2039        let mut p = doc.add_paragraph("");
2040        p.add_run("5.1 ").bold(true);
2041        p.add_run(
2042            "The Employee agrees to maintain strict confidentiality of all proprietary \
2043                   information, trade secrets, and business plans of the Company during and after \
2044                   employment.",
2045        );
2046    }
2047    {
2048        let mut p = doc.add_paragraph("");
2049        p.add_run("5.2 ").bold(true);
2050        p.add_run(
2051            "All intellectual property, inventions, and works of authorship created by the \
2052                   Employee during the term of employment and within the scope of duties shall be \
2053                   the sole and exclusive property of the Company.",
2054        );
2055    }
2056
2057    // ── Article 6: Non-Compete ──
2058    doc.add_paragraph("Article 6 — Non-Competition")
2059        .style("Heading1");
2060    {
2061        let mut p = doc.add_paragraph("");
2062        p.add_run("6.1 ").bold(true);
2063        p.add_run("For a period of twelve (12) months following termination, the Employee agrees \
2064                   not to engage in any business that directly competes with the Company's core \
2065                   business of AI infrastructure and ML operations services, within the United States.");
2066    }
2067
2068    // ── Article 7: General Provisions ──
2069    doc.add_paragraph("Article 7 — General Provisions")
2070        .style("Heading1");
2071    {
2072        let mut p = doc.add_paragraph("");
2073        p.add_run("7.1 Governing Law. ").bold(true);
2074        p.add_run(
2075            "This Agreement shall be governed by and construed in accordance with the laws of \
2076                   the State of California.",
2077        );
2078    }
2079    {
2080        let mut p = doc.add_paragraph("");
2081        p.add_run("7.2 Entire Agreement. ").bold(true);
2082        p.add_run(
2083            "This Agreement constitutes the entire agreement between the Parties and supersedes \
2084                   all prior negotiations, representations, and agreements.",
2085        );
2086    }
2087    {
2088        let mut p = doc.add_paragraph("");
2089        p.add_run("7.3 Amendment. ").bold(true);
2090        p.add_run(
2091            "This Agreement may only be amended by a written instrument signed by both Parties.",
2092        );
2093    }
2094
2095    // ── Signature Block ──
2096    doc.add_paragraph("").page_break_before(true);
2097    doc.add_paragraph("IN WITNESS WHEREOF, the Parties have executed this Employment Agreement as of the Effective Date.")
2098        .space_after(Length::pt(24.0));
2099
2100    // Signature table
2101    {
2102        let mut tbl = doc.add_table(6, 2).width_pct(100.0);
2103        tbl.cell(0, 0).unwrap().set_text("FOR THE COMPANY:");
2104        tbl.cell(0, 0).unwrap().shading("5B2C6F");
2105        tbl.cell(0, 1).unwrap().set_text("EMPLOYEE:");
2106        tbl.cell(0, 1).unwrap().shading("5B2C6F");
2107
2108        tbl.cell(1, 0).unwrap().set_text("");
2109        tbl.cell(1, 1).unwrap().set_text("");
2110        tbl.row(1).unwrap().height(Length::pt(40.0));
2111
2112        tbl.cell(2, 0)
2113            .unwrap()
2114            .set_text("Signature: ___________________________");
2115        tbl.cell(2, 1)
2116            .unwrap()
2117            .set_text("Signature: ___________________________");
2118        tbl.cell(3, 0)
2119            .unwrap()
2120            .set_text("Name: Board Representative");
2121        tbl.cell(3, 1).unwrap().set_text("Name: Walter White");
2122        tbl.cell(4, 0)
2123            .unwrap()
2124            .set_text("Title: Chair, Board of Directors");
2125        tbl.cell(4, 1)
2126            .unwrap()
2127            .set_text("Title: Chief Executive Officer");
2128        tbl.cell(5, 0).unwrap().set_text("Date: _______________");
2129        tbl.cell(5, 1).unwrap().set_text("Date: _______________");
2130    }
2131
2132    doc
2133}
Source

pub fn set_landscape(&mut self)

Set page orientation to landscape (swaps width and height if needed).

Source

pub fn set_portrait(&mut self)

Set page orientation to portrait (swaps width and height if needed).

Source

pub fn set_margins( &mut self, top: Length, right: Length, bottom: Length, left: Length, )

Set all page margins.

Examples found in repository?
examples/template_replace.rs (lines 46-51)
43fn create_template(path: &Path) {
44    let mut doc = Document::new();
45    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
46    doc.set_margins(
47        Length::inches(1.0),
48        Length::inches(1.0),
49        Length::inches(1.0),
50        Length::inches(1.0),
51    );
52
53    doc.set_header("{{company_name}} — Confidential");
54    doc.set_footer("Prepared by {{author_name}} on {{date}}");
55
56    // ── Title ──
57    doc.add_paragraph("{{company_name}}")
58        .style("Heading1")
59        .alignment(Alignment::Center);
60
61    doc.add_paragraph("Project Proposal")
62        .alignment(Alignment::Center);
63
64    doc.add_paragraph("");
65
66    // ── Summary section ──
67    doc.add_paragraph("Executive Summary").style("Heading2");
68
69    doc.add_paragraph(
70        "This proposal outlines the {{project_name}} project for {{company_name}}. \
71         The primary contact is {{contact_name}} ({{contact_email}}). \
72         The proposed start date is {{start_date}} with an estimated duration of {{duration}}.",
73    );
74
75    doc.add_paragraph("");
76
77    // ── Cross-run placeholder (bold label + normal value) ──
78    doc.add_paragraph("Key Details").style("Heading2");
79
80    {
81        let mut p = doc.add_paragraph("");
82        p.add_run("Project: ").bold(true);
83        p.add_run("{{project_name}}");
84    }
85    {
86        let mut p = doc.add_paragraph("");
87        p.add_run("Budget: ").bold(true);
88        p.add_run("{{budget}}");
89    }
90    {
91        let mut p = doc.add_paragraph("");
92        p.add_run("Status: ").bold(true);
93        p.add_run("{{status}}");
94    }
95
96    doc.add_paragraph("");
97
98    // ── Table with placeholders ──
99    doc.add_paragraph("Team Members").style("Heading2");
100
101    {
102        let mut tbl = doc.add_table(4, 3);
103        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
104
105        // Header row
106        for col in 0..3 {
107            tbl.cell(0, col).unwrap().shading("2E75B6");
108        }
109        tbl.cell(0, 0).unwrap().set_text("Name");
110        tbl.cell(0, 1).unwrap().set_text("Role");
111        tbl.cell(0, 2).unwrap().set_text("Email");
112
113        tbl.cell(1, 0).unwrap().set_text("{{member1_name}}");
114        tbl.cell(1, 1).unwrap().set_text("{{member1_role}}");
115        tbl.cell(1, 2).unwrap().set_text("{{member1_email}}");
116
117        tbl.cell(2, 0).unwrap().set_text("{{member2_name}}");
118        tbl.cell(2, 1).unwrap().set_text("{{member2_role}}");
119        tbl.cell(2, 2).unwrap().set_text("{{member2_email}}");
120
121        tbl.cell(3, 0).unwrap().set_text("{{member3_name}}");
122        tbl.cell(3, 1).unwrap().set_text("{{member3_role}}");
123        tbl.cell(3, 2).unwrap().set_text("{{member3_email}}");
124    }
125
126    doc.add_paragraph("");
127
128    // ── Deliverables section ──
129    doc.add_paragraph("Deliverables").style("Heading2");
130
131    doc.add_paragraph("INSERTION_POINT");
132
133    doc.add_paragraph("");
134
135    // ── Signature block ──
136    doc.add_paragraph("Acceptance").style("Heading2");
137
138    doc.add_paragraph(
139        "By signing below, {{company_name}} agrees to the terms outlined in this proposal.",
140    );
141
142    {
143        let mut tbl = doc.add_table(2, 2);
144        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
145        tbl.cell(0, 0)
146            .unwrap()
147            .set_text("Customer: ___________________");
148        tbl.cell(0, 1)
149            .unwrap()
150            .set_text("Provider: ___________________");
151        tbl.cell(1, 0).unwrap().set_text("Date: {{date}}");
152        tbl.cell(1, 1).unwrap().set_text("Date: {{date}}");
153    }
154
155    doc.set_title("{{company_name}} — Project Proposal Template");
156    doc.set_author("Template Generator");
157
158    doc.save(path).unwrap();
159}
More examples
Hide additional examples
examples/header_banner.rs (lines 34-39)
29fn generate_header_banner_doc(path: &Path) {
30    let mut doc = Document::new();
31
32    // Page setup with extra top margin for the banner
33    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
34    doc.set_margins(
35        Length::twips(2292), // top — extra tall for header banner
36        Length::twips(1440), // right
37        Length::twips(1440), // bottom
38        Length::twips(1440), // left
39    );
40    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
41
42    // Generate a simple logo image (white text on transparent background)
43    let logo_img = create_logo_png(220, 48);
44
45    // ── Dark blue banner ──
46    let banner = build_header_banner_xml(
47        "rId1",
48        &BannerOpts {
49            bg_color: "1A3C6E",
50            banner_width: 7772400, // full page width in EMU (~8.5")
51            banner_height: 969026, // banner height in EMU (~1.06")
52            logo_width: 2011680,   // logo display width (~2.2")
53            logo_height: 438912,   // logo display height (~0.48")
54            logo_x_offset: 295125, // left padding
55            logo_y_offset: 265057, // vertical centering
56        },
57    );
58
59    doc.set_raw_header_with_images(
60        banner.clone(),
61        &[("rId1", &logo_img, "logo.png")],
62        rdocx_oxml::header_footer::HdrFtrType::Default,
63    );
64
65    // Use a different first page header (same banner, different color)
66    doc.set_different_first_page(true);
67    let first_page_banner = build_header_banner_xml(
68        "rId1",
69        &BannerOpts {
70            bg_color: "2E75B6", // lighter blue for cover
71            banner_width: 7772400,
72            banner_height: 969026,
73            logo_width: 2011680,
74            logo_height: 438912,
75            logo_x_offset: 295125,
76            logo_y_offset: 265057,
77        },
78    );
79    doc.set_raw_header_with_images(
80        first_page_banner,
81        &[("rId1", &logo_img, "logo.png")],
82        rdocx_oxml::header_footer::HdrFtrType::First,
83    );
84
85    // Footer
86    doc.set_footer("Confidential — Internal Use Only");
87
88    // ── Page 1: Cover ──
89    doc.add_paragraph("Company Report").style("Heading1");
90
91    doc.add_paragraph(
92        "This document demonstrates a custom header banner built with DrawingML \
93         group shapes. The banner uses a colored rectangle with a logo image overlaid, \
94         positioned at the top of each page.",
95    );
96
97    doc.add_paragraph("");
98
99    doc.add_paragraph("How the Header Banner Works")
100        .style("Heading2");
101
102    doc.add_paragraph(
103        "The header banner is built using set_raw_header_with_images(), which \
104         accepts raw XML and a list of (rel_id, image_data, filename) tuples. \
105         The XML uses a DrawingML group shape (wpg:wgp) containing:",
106    );
107
108    doc.add_bullet_list_item(
109        "A wps:wsp rectangle shape with a solid color fill (the background bar)",
110        0,
111    );
112    doc.add_bullet_list_item(
113        "A pic:pic image element positioned within the group (the logo)",
114        0,
115    );
116    doc.add_bullet_list_item(
117        "The group is wrapped in a wp:anchor element for absolute page positioning",
118        0,
119    );
120
121    doc.add_paragraph("");
122
123    doc.add_paragraph("Customization").style("Heading2");
124
125    doc.add_paragraph(
126        "All dimensions are in EMU (English Metric Units) where 914400 EMU = 1 inch. \
127         You can customize:",
128    );
129
130    doc.add_bullet_list_item("bg_color — any hex color for the rectangle background", 0);
131    doc.add_bullet_list_item("banner_width / banner_height — size of the full banner", 0);
132    doc.add_bullet_list_item("logo_width / logo_height — display size of the logo", 0);
133    doc.add_bullet_list_item(
134        "logo_x_offset / logo_y_offset — logo position within the banner",
135        0,
136    );
137
138    doc.add_paragraph("");
139
140    doc.add_paragraph("Different First Page").style("Heading2");
141
142    doc.add_paragraph(
143        "This page uses a lighter blue banner (first page header). \
144         Subsequent pages use a darker blue banner (default header). \
145         Use set_different_first_page(true) to enable this.",
146    );
147
148    // ── Page 2 ──
149    doc.add_paragraph("").page_break_before(true);
150
151    doc.add_paragraph("Second Page").style("Heading1");
152
153    doc.add_paragraph(
154        "This page shows the default header banner (dark blue). The first page \
155         had a lighter blue banner because we set a different first-page header.",
156    );
157
158    doc.add_paragraph("");
159
160    doc.add_paragraph(
161        "The banner repeats on every page because it is placed in the header part. \
162         You can have different banners for default, first-page, and even-page headers.",
163    );
164
165    doc.set_title("Header Banner Example");
166    doc.set_author("rdocx");
167
168    doc.save(path).unwrap();
169}
examples/styled_tables.rs (lines 27-32)
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}
examples/generate_all_samples.rs (lines 102-107)
97fn generate_feature_showcase(_samples_dir: &Path) -> Document {
98    let mut doc = Document::new();
99
100    // ── Page Setup & Metadata ──
101    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
102    doc.set_margins(
103        Length::inches(1.0),
104        Length::inches(1.0),
105        Length::inches(1.0),
106        Length::inches(1.0),
107    );
108    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
109    doc.set_gutter(Length::twips(0));
110    doc.set_title("rdocx Feature Showcase");
111    doc.set_author("rdocx Sample Generator");
112    doc.set_subject("Comprehensive feature demonstration");
113    doc.set_keywords("rdocx, docx, rust, sample, showcase");
114
115    // Headers & Footers with different first page
116    doc.set_different_first_page(true);
117    doc.set_first_page_header("rdocx");
118    doc.set_first_page_footer("Feature Showcase — Cover Page");
119    doc.set_header("rdocx Feature Showcase");
120    doc.set_footer("Generated by rdocx");
121
122    // ── COVER PAGE ──
123    let bg = create_sample_png(612, 792, [20, 45, 90]);
124    doc.add_background_image(&bg, "cover_bg.png");
125
126    for _ in 0..3 {
127        doc.add_paragraph("");
128    }
129    {
130        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
131        p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
132    }
133    {
134        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
135        p.add_run("Complete Feature Showcase")
136            .size(28.0)
137            .color("FFFFFF")
138            .italic(true);
139    }
140    doc.add_paragraph("");
141    {
142        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
143        p.add_run("Every feature of the rdocx Rust library")
144            .size(13.0)
145            .color("B0C4DE");
146    }
147    {
148        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
149        p.add_run("demonstrated in a single document.")
150            .size(13.0)
151            .color("B0C4DE");
152    }
153
154    // ── TABLE OF CONTENTS ──
155    doc.add_paragraph("").page_break_before(true);
156    doc.insert_toc(doc.content_count(), 3);
157
158    // ── SECTION 1: TEXT FORMATTING ──
159    doc.add_paragraph("").page_break_before(true);
160    doc.add_paragraph("1. Text Formatting").style("Heading1");
161
162    // Paragraph Alignment
163    doc.add_paragraph("1.1 Paragraph Alignment")
164        .style("Heading2");
165    doc.add_paragraph("Left-aligned paragraph (default).")
166        .alignment(Alignment::Left);
167    doc.add_paragraph("Center-aligned paragraph.")
168        .alignment(Alignment::Center);
169    doc.add_paragraph("Right-aligned paragraph.")
170        .alignment(Alignment::Right);
171    doc.add_paragraph(
172        "Justified paragraph — this text is long enough to demonstrate how justified alignment \
173         distributes extra space across word gaps so lines fill the full width of the text area.",
174    )
175    .alignment(Alignment::Justify);
176
177    doc.add_paragraph("");
178
179    // Run Formatting
180    doc.add_paragraph("1.2 Run Formatting").style("Heading2");
181    {
182        let mut p = doc.add_paragraph("");
183        p.add_run("Bold").bold(true);
184        p.add_run(" | ");
185        p.add_run("Italic").italic(true);
186        p.add_run(" | ");
187        p.add_run("Bold Italic").bold(true).italic(true);
188        p.add_run(" | ");
189        p.add_run("Underline").underline(true);
190        p.add_run(" | ");
191        p.add_run("Strikethrough").strike(true);
192        p.add_run(" | ");
193        p.add_run("Double Strike").double_strike(true);
194    }
195    {
196        let mut p = doc.add_paragraph("");
197        p.add_run("Red").color("FF0000");
198        p.add_run(" | ");
199        p.add_run("Blue").color("0000FF");
200        p.add_run(" | ");
201        p.add_run("Green").color("00AA00");
202        p.add_run(" | ");
203        p.add_run("Highlighted").highlight("FFFF00");
204    }
205    {
206        let mut p = doc.add_paragraph("");
207        p.add_run("8pt").size(8.0);
208        p.add_run(" | ");
209        p.add_run("11pt").size(11.0);
210        p.add_run(" | ");
211        p.add_run("16pt").size(16.0);
212        p.add_run(" | ");
213        p.add_run("24pt").size(24.0);
214    }
215    {
216        let mut p = doc.add_paragraph("");
217        p.add_run("Arial").font("Arial");
218        p.add_run(" | ");
219        p.add_run("Times New Roman").font("Times New Roman");
220        p.add_run(" | ");
221        p.add_run("Courier New").font("Courier New");
222    }
223    {
224        let mut p = doc.add_paragraph("");
225        p.add_run("H");
226        p.add_run("2").subscript();
227        p.add_run("O (subscript) | E=mc");
228        p.add_run("2").superscript();
229        p.add_run(" (superscript)");
230    }
231    {
232        let mut p = doc.add_paragraph("");
233        p.add_run("ALL CAPS").all_caps(true);
234        p.add_run(" | ");
235        p.add_run("Small Caps").small_caps(true);
236        p.add_run(" | ");
237        p.add_run("Expanded").character_spacing(Length::twips(40));
238        p.add_run(" | ");
239        p.add_run("Hidden text (not visible)").hidden(true);
240    }
241
242    doc.add_paragraph("");
243
244    // Underline Styles
245    doc.add_paragraph("1.3 Underline Styles").style("Heading2");
246    {
247        let mut p = doc.add_paragraph("");
248        p.add_run("Single ")
249            .underline_style(rdocx::UnderlineStyle::Single);
250        p.add_run("Double ")
251            .underline_style(rdocx::UnderlineStyle::Double);
252        p.add_run("Thick ")
253            .underline_style(rdocx::UnderlineStyle::Thick);
254        p.add_run("Dotted ")
255            .underline_style(rdocx::UnderlineStyle::Dotted);
256        p.add_run("Dash ")
257            .underline_style(rdocx::UnderlineStyle::Dash);
258        p.add_run("Wave ")
259            .underline_style(rdocx::UnderlineStyle::Wave);
260    }
261
262    // ── SECTION 2: PARAGRAPH FORMATTING ──
263    doc.add_paragraph("").page_break_before(true);
264    doc.add_paragraph("2. Paragraph Formatting")
265        .style("Heading1");
266
267    doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
268    doc.add_paragraph("Paragraph with light green shading")
269        .shading("E2EFDA");
270    doc.add_paragraph("Paragraph with bottom border")
271        .border_bottom(BorderStyle::Single, 6, "2E75B6");
272    doc.add_paragraph("Paragraph with all borders (red)")
273        .border_all(BorderStyle::Single, 4, "FF0000");
274
275    doc.add_paragraph("2.2 Indentation").style("Heading2");
276    doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
277        .indent_left(Length::inches(1.0))
278        .hanging_indent(Length::inches(0.5));
279    doc.add_paragraph("0.5-inch first-line indent on this paragraph")
280        .first_line_indent(Length::inches(0.5));
281    doc.add_paragraph("Both left and right indent (0.75 inches each)")
282        .indent_left(Length::inches(0.75))
283        .indent_right(Length::inches(0.75));
284
285    doc.add_paragraph("2.3 Spacing & Line Height")
286        .style("Heading2");
287    doc.add_paragraph("Extra space before (24pt) and after (12pt)")
288        .space_before(Length::pt(24.0))
289        .space_after(Length::pt(12.0));
290    doc.add_paragraph(
291        "Double line spacing paragraph. This text should have extra vertical space between \
292         lines to demonstrate line_spacing_multiple(2.0).",
293    )
294    .line_spacing_multiple(2.0);
295    doc.add_paragraph("Exact 20pt line spacing (fixed height).")
296        .line_spacing(20.0);
297
298    doc.add_paragraph("2.4 Pagination Controls")
299        .style("Heading2");
300    doc.add_paragraph("keep_with_next — this paragraph stays with the next")
301        .keep_with_next(true);
302    doc.add_paragraph("(This paragraph was kept with the one above.)");
303    doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
304        .keep_together(true);
305    doc.add_paragraph("widow_control — prevents widow/orphan lines")
306        .widow_control(true);
307
308    // ── SECTION 3: LISTS ──
309    doc.add_paragraph("").page_break_before(true);
310    doc.add_paragraph("3. Lists").style("Heading1");
311
312    doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
313    doc.add_bullet_list_item("First item", 0);
314    doc.add_bullet_list_item("Second item", 0);
315    doc.add_bullet_list_item("Nested level 1", 1);
316    doc.add_bullet_list_item("Nested level 2", 2);
317    doc.add_bullet_list_item("Back to level 1", 1);
318    doc.add_bullet_list_item("Third item", 0);
319
320    doc.add_paragraph("");
321
322    doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
323    doc.add_numbered_list_item("First numbered item", 0);
324    doc.add_numbered_list_item("Second numbered item", 0);
325    doc.add_numbered_list_item("Sub-item A", 1);
326    doc.add_numbered_list_item("Sub-item B", 1);
327    doc.add_numbered_list_item("Third numbered item", 0);
328
329    // ── SECTION 4: TAB STOPS ──
330    doc.add_paragraph("");
331    doc.add_paragraph("4. Tab Stops").style("Heading1");
332
333    doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
334    doc.add_paragraph("Left\tCenter\tRight\tDecimal")
335        .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
336        .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
337        .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
338        .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
339
340    doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
341    doc.add_paragraph("Item\t\tPrice")
342        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
343        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
344        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
345    doc.add_paragraph("Widget\t\t$19.99")
346        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
347        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
348        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
349    doc.add_paragraph("Gadget\t\t$249.50")
350        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
351        .add_tab_stop_with_leader(
352            TabAlignment::Right,
353            Length::inches(4.0),
354            TabLeader::Underscore,
355        )
356        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
357
358    // ── SECTION 5: TABLES ──
359    doc.add_paragraph("").page_break_before(true);
360    doc.add_paragraph("5. Tables").style("Heading1");
361
362    doc.add_paragraph("5.1 Basic Table").style("Heading2");
363    {
364        let mut tbl = doc
365            .add_table(4, 3)
366            .borders(BorderStyle::Single, 4, "000000");
367        for col in 0..3 {
368            tbl.cell(0, col).unwrap().shading("2E75B6");
369        }
370        tbl.cell(0, 0).unwrap().set_text("Name");
371        tbl.cell(0, 1).unwrap().set_text("Role");
372        tbl.cell(0, 2).unwrap().set_text("Location");
373        tbl.cell(1, 0).unwrap().set_text("Walter White");
374        tbl.cell(1, 1).unwrap().set_text("CEO");
375        tbl.cell(1, 2).unwrap().set_text("Albuquerque");
376        tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
377        tbl.cell(2, 1).unwrap().set_text("CTO");
378        tbl.cell(2, 2).unwrap().set_text("Remote");
379        tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
380        tbl.cell(3, 1).unwrap().set_text("Security");
381        tbl.cell(3, 2).unwrap().set_text("Washington");
382    }
383
384    doc.add_paragraph("");
385
386    doc.add_paragraph("5.2 Column Span & Cell Shading")
387        .style("Heading2");
388    {
389        let mut tbl = doc
390            .add_table(3, 4)
391            .borders(BorderStyle::Single, 4, "000000")
392            .width_pct(100.0);
393        tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
394        tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
395        tbl.cell(1, 0).unwrap().set_text("Region");
396        tbl.cell(1, 0).unwrap().shading("D6E4F0");
397        tbl.cell(1, 1).unwrap().set_text("Q1");
398        tbl.cell(1, 1).unwrap().shading("D6E4F0");
399        tbl.cell(1, 2).unwrap().set_text("Q2");
400        tbl.cell(1, 2).unwrap().shading("D6E4F0");
401        tbl.cell(1, 3).unwrap().set_text("Total");
402        tbl.cell(1, 3).unwrap().shading("D6E4F0");
403        tbl.cell(2, 0).unwrap().set_text("Americas");
404        tbl.cell(2, 1).unwrap().set_text("$2.4M");
405        tbl.cell(2, 2).unwrap().set_text("$2.7M");
406        tbl.cell(2, 3).unwrap().set_text("$5.1M");
407    }
408
409    doc.add_paragraph("");
410
411    doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
412    {
413        let mut tbl = doc
414            .add_table(4, 3)
415            .borders(BorderStyle::Single, 4, "000000");
416        tbl.cell(0, 0).unwrap().set_text("Category");
417        tbl.cell(0, 0).unwrap().shading("E2EFDA");
418        tbl.cell(0, 1).unwrap().set_text("Item");
419        tbl.cell(0, 1).unwrap().shading("E2EFDA");
420        tbl.cell(0, 2).unwrap().set_text("Price");
421        tbl.cell(0, 2).unwrap().shading("E2EFDA");
422        tbl.cell(1, 0).unwrap().set_text("Hardware");
423        tbl.cell(1, 0).unwrap().v_merge_restart();
424        tbl.cell(1, 1).unwrap().set_text("Laptop");
425        tbl.cell(1, 2).unwrap().set_text("$1,200");
426        tbl.cell(2, 0).unwrap().v_merge_continue();
427        tbl.cell(2, 1).unwrap().set_text("Monitor");
428        tbl.cell(2, 2).unwrap().set_text("$450");
429        tbl.cell(3, 0).unwrap().set_text("Software");
430        tbl.cell(3, 1).unwrap().set_text("IDE License");
431        tbl.cell(3, 2).unwrap().set_text("$200/yr");
432    }
433
434    doc.add_paragraph("");
435
436    doc.add_paragraph("5.4 Nested Table").style("Heading2");
437    {
438        let mut tbl = doc
439            .add_table(2, 2)
440            .borders(BorderStyle::Single, 6, "2E75B6");
441        tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
442        tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
443        tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
444        {
445            let mut cell = tbl.cell(1, 1).unwrap();
446            cell.set_text("Nested table below:");
447            let mut nested = cell
448                .add_table(2, 2)
449                .borders(BorderStyle::Single, 2, "FF6600");
450            nested.cell(0, 0).unwrap().set_text("A");
451            nested.cell(0, 1).unwrap().set_text("B");
452            nested.cell(1, 0).unwrap().set_text("C");
453            nested.cell(1, 1).unwrap().set_text("D");
454        }
455    }
456
457    doc.add_paragraph("");
458
459    doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
460        .style("Heading2");
461    {
462        let mut tbl = doc
463            .add_table(2, 3)
464            .borders(BorderStyle::Single, 4, "666666");
465        tbl.row(0).unwrap().height(Length::pt(50.0)).header();
466        tbl.cell(0, 0).unwrap().set_text("Top");
467        tbl.cell(0, 0)
468            .unwrap()
469            .vertical_alignment(VerticalAlignment::Top)
470            .shading("FFF2CC");
471        tbl.cell(0, 1).unwrap().set_text("Center");
472        tbl.cell(0, 1)
473            .unwrap()
474            .vertical_alignment(VerticalAlignment::Center)
475            .shading("D9E2F3");
476        tbl.cell(0, 2).unwrap().set_text("Bottom");
477        tbl.cell(0, 2)
478            .unwrap()
479            .vertical_alignment(VerticalAlignment::Bottom)
480            .shading("E2EFDA");
481        tbl.row(1).unwrap().cant_split();
482        tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
483        tbl.cell(1, 0).unwrap().no_wrap();
484        tbl.cell(1, 1).unwrap().set_text("Fixed width");
485        tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
486        tbl.cell(1, 2).unwrap().set_text("Normal");
487    }
488
489    // ── SECTION 6: IMAGES ──
490    doc.add_paragraph("").page_break_before(true);
491    doc.add_paragraph("6. Images").style("Heading1");
492
493    doc.add_paragraph("6.1 Inline Image").style("Heading2");
494    doc.add_paragraph("A blue gradient image below:");
495    let img = create_sample_png(200, 50, [0, 80, 200]);
496    doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
497
498    doc.add_paragraph("");
499    doc.add_paragraph("6.2 Header Image").style("Heading2");
500    let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
501    doc.set_header_image(
502        &hdr_img,
503        "header_logo.png",
504        Length::inches(2.0),
505        Length::inches(0.2),
506    );
507    doc.add_paragraph("The header now contains an inline image (check the top of this page).");
508
509    doc.add_paragraph("");
510    doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
511
512    // ── SECTION 7: CONTENT MANIPULATION ──
513    doc.add_paragraph("").page_break_before(true);
514    doc.add_paragraph("7. Content Manipulation")
515        .style("Heading1");
516
517    doc.add_paragraph("7.1 Placeholder Replacement")
518        .style("Heading2");
519    doc.add_paragraph("Customer: {{customer}}");
520    doc.add_paragraph("Date: {{date}}");
521    doc.add_paragraph("Reference: {{ref_number}}");
522    {
523        let mut tbl = doc
524            .add_table(2, 2)
525            .borders(BorderStyle::Single, 4, "000000");
526        tbl.cell(0, 0).unwrap().set_text("Project");
527        tbl.cell(0, 0).unwrap().shading("D6E4F0");
528        tbl.cell(0, 1).unwrap().set_text("{{project}}");
529        tbl.cell(1, 0).unwrap().set_text("Status");
530        tbl.cell(1, 0).unwrap().shading("D6E4F0");
531        tbl.cell(1, 1).unwrap().set_text("{{status}}");
532    }
533
534    let mut replacements = HashMap::new();
535    replacements.insert("{{customer}}", "Tensorbee Inc.");
536    replacements.insert("{{date}}", "February 22, 2026");
537    replacements.insert("{{ref_number}}", "TB-2026-001");
538    replacements.insert("{{project}}", "Infrastructure Upgrade");
539    replacements.insert("{{status}}", "In Progress");
540    let n = doc.replace_all(&replacements);
541    doc.add_paragraph(&format!("({n} placeholders replaced above)"));
542
543    doc.add_paragraph("");
544
545    doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
546    doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
547    let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
548    doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
549
550    doc.add_paragraph("");
551
552    doc.add_paragraph("7.3 Content Insertion").style("Heading2");
553    doc.add_paragraph("Section A: First content.");
554    doc.add_paragraph("Section C: Third content.");
555    if let Some(idx) = doc.find_content_index("Section C") {
556        doc.insert_paragraph(
557            idx,
558            "Section B: Inserted between A and C via find_content_index().",
559        );
560    }
561
562    // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
563    doc.add_paragraph("").section_break(SectionBreak::NextPage);
564    doc.add_paragraph("8. Section Breaks & Orientation")
565        .style("Heading1");
566    doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
567
568    {
569        let mut tbl = doc
570            .add_table(3, 7)
571            .borders(BorderStyle::Single, 4, "2E75B6");
572        let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
573        for (c, h) in headers.iter().enumerate() {
574            tbl.cell(0, c).unwrap().set_text(h);
575            tbl.cell(0, c).unwrap().shading("2E75B6");
576        }
577        let data = [
578            [
579                "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
580            ],
581            [
582                "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
583            ],
584        ];
585        for (r, row) in data.iter().enumerate() {
586            for (c, v) in row.iter().enumerate() {
587                tbl.cell(r + 1, c).unwrap().set_text(v);
588            }
589        }
590    }
591
592    doc.add_paragraph("")
593        .section_break(SectionBreak::NextPage)
594        .section_landscape();
595
596    // ── SECTION 9: CUSTOM STYLES ──
597    doc.add_paragraph("9. Custom Styles").style("Heading1");
598    doc.add_style(
599        StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
600            .based_on("Normal")
601            .paragraph_properties(rdocx_oxml::properties::CT_PPr {
602                shading: Some(rdocx_oxml::properties::CT_Shd {
603                    val: "clear".to_string(),
604                    color: None,
605                    fill: Some("FFF2CC".to_string()),
606                }),
607                ..Default::default()
608            })
609            .run_properties(rdocx_oxml::properties::CT_RPr {
610                bold: Some(true),
611                color: Some("C45911".to_string()),
612                ..Default::default()
613            }),
614    );
615    doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
616        .style("CustomHighlight");
617
618    doc.add_paragraph("");
619
620    // ── SECTION 10: DOCUMENT INTELLIGENCE ──
621    doc.add_paragraph("10. Document Intelligence API")
622        .style("Heading1");
623
624    let wc = doc.word_count();
625    let hc = doc.headings().len();
626    let ic = doc.images().len();
627    let lc = doc.links().len();
628    doc.add_paragraph(&format!("Word count: {wc}"));
629    doc.add_paragraph(&format!("Heading count: {hc}"));
630    doc.add_paragraph(&format!("Image count: {ic}"));
631    doc.add_paragraph(&format!("Link count: {lc}"));
632
633    let outline = doc.document_outline();
634    doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
635
636    let issues = doc.audit_accessibility();
637    doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
638    for issue in &issues {
639        doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
640    }
641
642    // ── SECTION 11: DOCUMENT MERGING ──
643    doc.add_paragraph("");
644    doc.add_paragraph("11. Document Merging").style("Heading1");
645    {
646        let mut other = Document::new();
647        other.add_paragraph("This paragraph was merged from another document using append().");
648        doc.append(&other);
649    }
650
651    doc.add_paragraph("");
652
653    // ── SUMMARY ──
654    doc.add_paragraph("Summary of Demonstrated Features")
655        .style("Heading1");
656    let features = [
657        "Page setup: size, margins, header/footer distance, gutter",
658        "Document metadata: title, author, subject, keywords",
659        "Headers and footers: text, images, different first page",
660        "Background images (full-page behind text)",
661        "Table of Contents generation with bookmarks",
662        "Text formatting: bold, italic, underline styles, strike, color, size, font",
663        "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
664        "Paragraph formatting: alignment, borders, shading, spacing, indentation",
665        "Pagination controls: keep-with-next, keep-together, widow control",
666        "Bullet and numbered lists with nesting",
667        "Tab stops with dot/underscore/hyphen leaders",
668        "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
669        "Row properties: header rows, exact height, cant-split",
670        "Cell properties: width, no-wrap, vertical alignment",
671        "Inline images and header images",
672        "Placeholder replacement (single and batch)",
673        "Regex-based find and replace",
674        "Content insertion at specific positions",
675        "Section breaks with mixed portrait/landscape",
676        "Custom paragraph and character styles",
677        "Document intelligence: word count, headings, outline, images, links",
678        "Accessibility audit",
679        "Document merging (append)",
680        "Export: DOCX, PDF, HTML, Markdown",
681    ];
682    for f in &features {
683        doc.add_bullet_list_item(f, 0);
684    }
685    doc.add_paragraph("");
686    doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
687        .alignment(Alignment::Center)
688        .shading("E2EFDA")
689        .border_all(BorderStyle::Single, 2, "00AA00");
690
691    doc
692}
693
694// =============================================================================
695// 2. PROPOSAL DOCUMENT — Deep navy + gold scheme
696// =============================================================================
697fn generate_proposal(_samples_dir: &Path) -> Document {
698    let mut doc = Document::new();
699
700    // Colors: Navy #1B2A4A, Gold #C5922E, Light #F4F1EB
701    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
702    doc.set_margins(
703        Length::inches(1.0),
704        Length::inches(1.0),
705        Length::inches(1.0),
706        Length::inches(1.0),
707    );
708    doc.set_title("AI Platform Modernization Proposal");
709    doc.set_author("Walter White");
710    doc.set_subject("Technology Proposal");
711    doc.set_keywords("proposal, AI, modernization, Tensorbee");
712
713    // Banner header
714    let logo_img = create_logo_png(220, 48);
715    let banner = build_header_banner_xml(
716        "rId1",
717        &BannerOpts {
718            bg_color: "1B2A4A",
719            banner_width: 7772400,
720            banner_height: 914400,
721            logo_width: 2011680,
722            logo_height: 438912,
723            logo_x_offset: 295125,
724            logo_y_offset: 237744,
725        },
726    );
727    doc.set_raw_header_with_images(
728        banner,
729        &[("rId1", &logo_img, "logo.png")],
730        rdocx_oxml::header_footer::HdrFtrType::Default,
731    );
732    doc.set_different_first_page(true);
733    let cover_banner = build_header_banner_xml(
734        "rId1",
735        &BannerOpts {
736            bg_color: "C5922E",
737            banner_width: 7772400,
738            banner_height: 914400,
739            logo_width: 2011680,
740            logo_height: 438912,
741            logo_x_offset: 295125,
742            logo_y_offset: 237744,
743        },
744    );
745    doc.set_raw_header_with_images(
746        cover_banner,
747        &[("rId1", &logo_img, "logo.png")],
748        rdocx_oxml::header_footer::HdrFtrType::First,
749    );
750    doc.set_margins(
751        Length::twips(2292),
752        Length::twips(1440),
753        Length::twips(1440),
754        Length::twips(1440),
755    );
756    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
757    doc.set_footer("Tensorbee — Confidential");
758
759    // Custom styles
760    doc.add_style(
761        StyleBuilder::paragraph("ProposalTitle", "Proposal Title")
762            .based_on("Normal")
763            .run_properties(rdocx_oxml::properties::CT_RPr {
764                bold: Some(true),
765                font_ascii: Some("Georgia".to_string()),
766                font_hansi: Some("Georgia".to_string()),
767                sz: Some(rdocx_oxml::HalfPoint(56)), // half-points → 28pt
768                color: Some("1B2A4A".to_string()),
769                ..Default::default()
770            }),
771    );
772
773    // ── Cover Page ──
774    for _ in 0..4 {
775        doc.add_paragraph("");
776    }
777    doc.add_paragraph("AI Platform Modernization")
778        .style("ProposalTitle")
779        .alignment(Alignment::Center);
780    doc.add_paragraph("")
781        .alignment(Alignment::Center)
782        .border_bottom(BorderStyle::Single, 8, "C5922E");
783    {
784        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
785        p.add_run("Prepared for Global Financial Corp.")
786            .size(14.0)
787            .color("666666")
788            .italic(true);
789    }
790    {
791        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
792        p.add_run("Prepared by: Walter White, CEO — Tensorbee")
793            .size(12.0)
794            .color("888888");
795    }
796    {
797        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
798        p.add_run("February 22, 2026").size(12.0).color("888888");
799    }
800
801    // ── TOC ──
802    doc.add_paragraph("").page_break_before(true);
803    doc.add_paragraph("Table of Contents").style("Heading1");
804    // We'll add a manual-style TOC since the insert_toc goes at a specific position
805    doc.insert_toc(doc.content_count(), 2);
806
807    // ── Executive Summary ──
808    doc.add_paragraph("").page_break_before(true);
809    doc.add_paragraph("Executive Summary").style("Heading1");
810    doc.add_paragraph(
811        "Tensorbee proposes a comprehensive modernization of Global Financial Corp's AI platform, \
812         transitioning from legacy batch-processing systems to a real-time inference architecture. \
813         This transformation will reduce model deployment time from weeks to hours, improve \
814         prediction accuracy by an estimated 23%, and deliver $4.2M in annual operational savings.",
815    )
816    .first_line_indent(Length::inches(0.3));
817    doc.add_paragraph(
818        "The project spans three phases over 18 months, with a total investment of $2.8M. \
819         Tensorbee brings deep expertise in ML infrastructure, having successfully completed \
820         similar transformations for three Fortune 500 financial institutions.",
821    )
822    .first_line_indent(Length::inches(0.3));
823
824    // Key metrics table
825    doc.add_paragraph("");
826    {
827        let mut tbl = doc
828            .add_table(5, 2)
829            .borders(BorderStyle::Single, 4, "1B2A4A")
830            .width_pct(80.0);
831        tbl.cell(0, 0).unwrap().set_text("Key Metric");
832        tbl.cell(0, 0).unwrap().shading("1B2A4A");
833        tbl.cell(0, 1).unwrap().set_text("Value");
834        tbl.cell(0, 1).unwrap().shading("1B2A4A");
835        let rows = [
836            ("Total Investment", "$2.8M"),
837            ("Annual Savings", "$4.2M"),
838            ("ROI (Year 1)", "150%"),
839            ("Timeline", "18 months"),
840        ];
841        for (i, (k, v)) in rows.iter().enumerate() {
842            tbl.cell(i + 1, 0).unwrap().set_text(k);
843            if i % 2 == 0 {
844                tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
845            }
846            tbl.cell(i + 1, 1).unwrap().set_text(v);
847            if i % 2 == 0 {
848                tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
849            }
850        }
851    }
852
853    // ── Problem Statement ──
854    doc.add_paragraph("").page_break_before(true);
855    doc.add_paragraph("Problem Statement").style("Heading1");
856    doc.add_paragraph(
857        "Global Financial Corp's current AI infrastructure faces three critical challenges:",
858    );
859    doc.add_numbered_list_item("Deployment Latency — New models take 3-4 weeks to deploy to production, compared to the industry benchmark of 2-3 days.", 0);
860    doc.add_numbered_list_item("Scalability Constraints — The batch processing architecture cannot handle real-time inference demands during peak trading hours.", 0);
861    doc.add_numbered_list_item("Technical Debt — Legacy Python 2.7 codebases and manual deployment scripts create reliability risks and slow iteration speed.", 0);
862
863    // ── Proposed Solution ──
864    doc.add_paragraph("").page_break_before(true);
865    doc.add_paragraph("Proposed Solution").style("Heading1");
866
867    doc.add_paragraph("Phase 1: Foundation (Months 1-6)")
868        .style("Heading2");
869    doc.add_bullet_list_item("Deploy Kubernetes-based ML serving infrastructure", 0);
870    doc.add_bullet_list_item("Implement CI/CD pipeline for model deployment", 0);
871    doc.add_bullet_list_item("Migrate top 5 production models to new platform", 0);
872
873    doc.add_paragraph("Phase 2: Expansion (Months 7-12)")
874        .style("Heading2");
875    doc.add_bullet_list_item(
876        "Real-time feature engineering pipeline (Apache Kafka + Flink)",
877        0,
878    );
879    doc.add_bullet_list_item("A/B testing framework for model variants", 0);
880    doc.add_bullet_list_item("Automated model monitoring and drift detection", 0);
881
882    doc.add_paragraph("Phase 3: Optimization (Months 13-18)")
883        .style("Heading2");
884    doc.add_bullet_list_item("GPU inference optimization (TensorRT/ONNX Runtime)", 0);
885    doc.add_bullet_list_item("Multi-region deployment for latency reduction", 0);
886    doc.add_bullet_list_item(
887        "Self-service model deployment portal for data science teams",
888        0,
889    );
890
891    // ── Budget ──
892    doc.add_paragraph("Budget Breakdown").style("Heading1");
893    {
894        let mut tbl = doc
895            .add_table(6, 3)
896            .borders(BorderStyle::Single, 4, "1B2A4A")
897            .width_pct(100.0);
898        tbl.cell(0, 0).unwrap().set_text("Category");
899        tbl.cell(0, 0).unwrap().shading("1B2A4A");
900        tbl.cell(0, 1).unwrap().set_text("Cost");
901        tbl.cell(0, 1).unwrap().shading("1B2A4A");
902        tbl.cell(0, 2).unwrap().set_text("Notes");
903        tbl.cell(0, 2).unwrap().shading("1B2A4A");
904        let items = [
905            (
906                "Infrastructure",
907                "$450,000",
908                "Cloud compute + GPU instances",
909            ),
910            ("Engineering Services", "$1,600,000", "12 FTE x 18 months"),
911            (
912                "Software Licenses",
913                "$350,000",
914                "Kafka, monitoring, CI/CD tools",
915            ),
916            (
917                "Training & Enablement",
918                "$200,000",
919                "Team training, documentation",
920            ),
921            ("Contingency (10%)", "$200,000", "Risk buffer"),
922        ];
923        for (i, (cat, cost, note)) in items.iter().enumerate() {
924            tbl.cell(i + 1, 0).unwrap().set_text(cat);
925            tbl.cell(i + 1, 1).unwrap().set_text(cost);
926            tbl.cell(i + 1, 2).unwrap().set_text(note);
927            if i % 2 == 0 {
928                tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
929                tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
930                tbl.cell(i + 1, 2).unwrap().shading("F4F1EB");
931            }
932        }
933    }
934
935    // ── Closing ──
936    doc.add_paragraph("");
937    doc.add_paragraph("Next Steps").style("Heading1");
938    doc.add_numbered_list_item(
939        "Schedule technical deep-dive with GFC engineering team (Week 1)",
940        0,
941    );
942    doc.add_numbered_list_item("Finalize scope and sign SOW (Week 2-3)", 0);
943    doc.add_numbered_list_item("Kick off Phase 1 with joint planning session (Week 4)", 0);
944
945    doc.add_paragraph("");
946    {
947        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
948        p.add_run("Walter White")
949            .bold(true)
950            .size(14.0)
951            .color("1B2A4A");
952    }
953    {
954        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
955        p.add_run("CEO, Tensorbee | walter@tensorbee.com")
956            .size(10.0)
957            .color("888888");
958    }
959
960    doc
961}
962
963// =============================================================================
964// 3. QUOTE / BILL OF MATERIALS — Teal + orange scheme
965// =============================================================================
966fn generate_quote(_samples_dir: &Path) -> Document {
967    let mut doc = Document::new();
968
969    // Colors: Teal #008B8B, Dark #1A3C3C, Orange #E07020, Light #F0F8F8
970    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
971    doc.set_margins(
972        Length::inches(0.75),
973        Length::inches(0.75),
974        Length::inches(0.75),
975        Length::inches(0.75),
976    );
977    doc.set_title("Quotation QT-2026-0147");
978    doc.set_author("Walter White");
979    doc.set_keywords("quote, BOM, Tensorbee");
980
981    doc.set_header("Tensorbee — Quotation");
982    doc.set_footer("QT-2026-0147 | Page");
983
984    // ── Header Block ──
985    {
986        let mut p = doc.add_paragraph("").alignment(Alignment::Left);
987        p.add_run("TENSORBEE")
988            .bold(true)
989            .size(28.0)
990            .color("008B8B")
991            .font("Helvetica");
992    }
993    doc.add_paragraph("123 Innovation Drive, Suite 400")
994        .alignment(Alignment::Left);
995    doc.add_paragraph("San Francisco, CA 94105 | +1 (415) 555-0199")
996        .alignment(Alignment::Left);
997    doc.add_paragraph("")
998        .border_bottom(BorderStyle::Single, 8, "008B8B");
999
1000    // Quote meta
1001    doc.add_paragraph("");
1002    {
1003        let mut tbl = doc.add_table(4, 4).width_pct(100.0);
1004        tbl.cell(0, 0).unwrap().set_text("QUOTATION");
1005        tbl.cell(0, 0).unwrap().shading("008B8B").grid_span(2);
1006        tbl.cell(0, 2).unwrap().set_text("DATE");
1007        tbl.cell(0, 2).unwrap().shading("008B8B");
1008        tbl.cell(0, 3).unwrap().set_text("VALID UNTIL");
1009        tbl.cell(0, 3).unwrap().shading("008B8B");
1010        tbl.cell(1, 0).unwrap().set_text("Quote #:");
1011        tbl.cell(1, 1).unwrap().set_text("QT-2026-0147");
1012        tbl.cell(1, 2).unwrap().set_text("Feb 22, 2026");
1013        tbl.cell(1, 3).unwrap().set_text("Mar 22, 2026");
1014        tbl.cell(2, 0).unwrap().set_text("Prepared by:");
1015        tbl.cell(2, 1).unwrap().set_text("Walter White");
1016        tbl.cell(2, 2).unwrap().set_text("Payment:");
1017        tbl.cell(2, 3).unwrap().set_text("Net 30");
1018        tbl.cell(3, 0).unwrap().set_text("Customer:");
1019        tbl.cell(3, 1).unwrap().set_text("Meridian Dynamics LLC");
1020        tbl.cell(3, 2).unwrap().set_text("Currency:");
1021        tbl.cell(3, 3).unwrap().set_text("USD");
1022    }
1023
1024    doc.add_paragraph("");
1025
1026    // ── Bill of Materials ──
1027    {
1028        let mut p = doc.add_paragraph("");
1029        p.add_run("Bill of Materials")
1030            .bold(true)
1031            .size(16.0)
1032            .color("1A3C3C");
1033    }
1034    doc.add_paragraph("");
1035
1036    {
1037        let mut tbl = doc
1038            .add_table(10, 6)
1039            .borders(BorderStyle::Single, 2, "008B8B")
1040            .width_pct(100.0)
1041            .layout_fixed();
1042        // Headers
1043        let hdrs = [
1044            "#",
1045            "Part Number",
1046            "Description",
1047            "Qty",
1048            "Unit Price",
1049            "Total",
1050        ];
1051        for (c, h) in hdrs.iter().enumerate() {
1052            tbl.cell(0, c).unwrap().set_text(h);
1053            tbl.cell(0, c).unwrap().shading("008B8B");
1054        }
1055
1056        let items: Vec<(&str, &str, &str, &str, &str)> = vec![
1057            (
1058                "TB-GPU-A100",
1059                "NVIDIA A100 80GB GPU",
1060                "4",
1061                "$12,500.00",
1062                "$50,000.00",
1063            ),
1064            (
1065                "TB-SRV-R750",
1066                "Dell R750xa Server Chassis",
1067                "2",
1068                "$8,200.00",
1069                "$16,400.00",
1070            ),
1071            (
1072                "TB-RAM-256G",
1073                "256GB DDR5 ECC Memory Module",
1074                "8",
1075                "$890.00",
1076                "$7,120.00",
1077            ),
1078            (
1079                "TB-SSD-3840",
1080                "3.84TB NVMe U.2 SSD",
1081                "8",
1082                "$1,150.00",
1083                "$9,200.00",
1084            ),
1085            (
1086                "TB-NET-CX7",
1087                "ConnectX-7 200GbE NIC",
1088                "4",
1089                "$1,800.00",
1090                "$7,200.00",
1091            ),
1092            (
1093                "TB-SW-48P",
1094                "48-Port 100GbE Switch",
1095                "1",
1096                "$22,000.00",
1097                "$22,000.00",
1098            ),
1099            (
1100                "TB-CAB-RACK",
1101                "42U Server Rack + PDU",
1102                "1",
1103                "$4,500.00",
1104                "$4,500.00",
1105            ),
1106            (
1107                "TB-SVC-INST",
1108                "Installation & Configuration",
1109                "1",
1110                "$8,500.00",
1111                "$8,500.00",
1112            ),
1113            (
1114                "TB-SVC-SUPP",
1115                "3-Year Premium Support",
1116                "1",
1117                "$15,000.00",
1118                "$15,000.00",
1119            ),
1120        ];
1121
1122        for (i, (pn, desc, qty, unit, total)) in items.iter().enumerate() {
1123            let row = i + 1;
1124            tbl.cell(row, 0).unwrap().set_text(&format!("{}", i + 1));
1125            tbl.cell(row, 1).unwrap().set_text(pn);
1126            tbl.cell(row, 2).unwrap().set_text(desc);
1127            tbl.cell(row, 3).unwrap().set_text(qty);
1128            tbl.cell(row, 4).unwrap().set_text(unit);
1129            tbl.cell(row, 5).unwrap().set_text(total);
1130            if i % 2 == 0 {
1131                for c in 0..6 {
1132                    tbl.cell(row, c).unwrap().shading("F0F8F8");
1133                }
1134            }
1135        }
1136    }
1137
1138    doc.add_paragraph("");
1139
1140    // ── Totals ──
1141    {
1142        let mut tbl = doc
1143            .add_table(4, 2)
1144            .borders(BorderStyle::Single, 2, "008B8B")
1145            .width(Length::inches(3.5))
1146            .alignment(Alignment::Right);
1147        tbl.cell(0, 0).unwrap().set_text("Subtotal");
1148        tbl.cell(0, 1).unwrap().set_text("$139,920.00");
1149        tbl.cell(1, 0).unwrap().set_text("Shipping & Handling");
1150        tbl.cell(1, 1).unwrap().set_text("$2,500.00");
1151        tbl.cell(2, 0).unwrap().set_text("Tax (8.625%)");
1152        tbl.cell(2, 1).unwrap().set_text("$12,068.10");
1153        tbl.cell(3, 0).unwrap().set_text("TOTAL");
1154        tbl.cell(3, 0).unwrap().shading("E07020");
1155        tbl.cell(3, 1).unwrap().set_text("$154,488.10");
1156        tbl.cell(3, 1).unwrap().shading("E07020");
1157    }
1158
1159    doc.add_paragraph("");
1160
1161    // ── Terms & Conditions ──
1162    {
1163        let mut p = doc.add_paragraph("");
1164        p.add_run("Terms & Conditions")
1165            .bold(true)
1166            .size(14.0)
1167            .color("1A3C3C");
1168    }
1169    doc.add_numbered_list_item(
1170        "This quotation is valid for 30 calendar days from the date of issue.",
1171        0,
1172    );
1173    doc.add_numbered_list_item(
1174        "All prices are in USD and exclusive of applicable taxes unless stated.",
1175        0,
1176    );
1177    doc.add_numbered_list_item("Standard lead time is 4-6 weeks from PO receipt.", 0);
1178    doc.add_numbered_list_item(
1179        "Payment terms: Net 30 from invoice date. 2% discount for payment within 10 days.",
1180        0,
1181    );
1182    doc.add_numbered_list_item(
1183        "Warranty: 3-year manufacturer warranty on all hardware components.",
1184        0,
1185    );
1186    doc.add_numbered_list_item(
1187        "Returns subject to 15% restocking fee if initiated after 14 days.",
1188        0,
1189    );
1190
1191    doc.add_paragraph("");
1192    doc.add_paragraph("")
1193        .border_bottom(BorderStyle::Single, 4, "008B8B");
1194    doc.add_paragraph("");
1195    {
1196        let mut p = doc.add_paragraph("");
1197        p.add_run("Accepted by: ").bold(true);
1198        p.add_run("___________________________ Date: ___________");
1199    }
1200    {
1201        let mut p = doc.add_paragraph("");
1202        p.add_run("Print Name: ").bold(true);
1203        p.add_run("___________________________ Title: ___________");
1204    }
1205
1206    doc
1207}
1208
1209// =============================================================================
1210// 4. INVOICE — Crimson + charcoal scheme
1211// =============================================================================
1212fn generate_invoice(_samples_dir: &Path) -> Document {
1213    let mut doc = Document::new();
1214
1215    // Colors: Crimson #B22222, Charcoal #333333, Light #FAF0F0
1216    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1217    doc.set_margins(
1218        Length::inches(0.75),
1219        Length::inches(0.75),
1220        Length::inches(0.75),
1221        Length::inches(0.75),
1222    );
1223    doc.set_title("Invoice INV-2026-0392");
1224    doc.set_author("Walter White");
1225    doc.set_keywords("invoice, Tensorbee");
1226
1227    doc.set_footer("Tensorbee — Thank you for your business!");
1228
1229    // ── Company & Invoice Header ──
1230    {
1231        let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1232        // Company name left, INVOICE right
1233        {
1234            let mut cell = tbl.cell(0, 0).unwrap();
1235            let mut p = cell.add_paragraph("");
1236            p.add_run("TENSORBEE")
1237                .bold(true)
1238                .size(32.0)
1239                .color("B22222")
1240                .font("Helvetica");
1241        }
1242        {
1243            let mut cell = tbl.cell(0, 1).unwrap();
1244            let mut p = cell.add_paragraph("");
1245            p.add_run("INVOICE").bold(true).size(32.0).color("333333");
1246        }
1247        tbl.cell(1, 0)
1248            .unwrap()
1249            .set_text("123 Innovation Drive, Suite 400");
1250        tbl.cell(1, 1).unwrap().set_text("Invoice #: INV-2026-0392");
1251        tbl.cell(2, 0).unwrap().set_text("San Francisco, CA 94105");
1252        tbl.cell(2, 1).unwrap().set_text("Date: February 22, 2026");
1253        tbl.cell(3, 0).unwrap().set_text("walter@tensorbee.com");
1254        tbl.cell(3, 1).unwrap().set_text("Due Date: March 24, 2026");
1255    }
1256
1257    doc.add_paragraph("")
1258        .border_bottom(BorderStyle::Thick, 8, "B22222");
1259    doc.add_paragraph("");
1260
1261    // ── Bill To / Ship To ──
1262    {
1263        let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1264        tbl.cell(0, 0).unwrap().set_text("BILL TO");
1265        tbl.cell(0, 0).unwrap().shading("B22222");
1266        tbl.cell(0, 1).unwrap().set_text("SHIP TO");
1267        tbl.cell(0, 1).unwrap().shading("B22222");
1268        tbl.cell(1, 0).unwrap().set_text("Meridian Dynamics LLC");
1269        tbl.cell(1, 1).unwrap().set_text("Meridian Dynamics LLC");
1270        tbl.cell(2, 0).unwrap().set_text("456 Enterprise Blvd");
1271        tbl.cell(2, 1).unwrap().set_text("Attn: Server Room B");
1272        tbl.cell(3, 0).unwrap().set_text("Austin, TX 78701");
1273        tbl.cell(3, 1)
1274            .unwrap()
1275            .set_text("456 Enterprise Blvd, Austin, TX 78701");
1276    }
1277
1278    doc.add_paragraph("");
1279
1280    // ── Line Items ──
1281    {
1282        let mut tbl = doc
1283            .add_table(8, 5)
1284            .borders(BorderStyle::Single, 2, "B22222")
1285            .width_pct(100.0);
1286        let hdrs = ["Description", "Qty", "Unit Price", "Tax", "Amount"];
1287        for (c, h) in hdrs.iter().enumerate() {
1288            tbl.cell(0, c).unwrap().set_text(h);
1289            tbl.cell(0, c).unwrap().shading("B22222");
1290        }
1291        let items = [
1292            (
1293                "ML Infrastructure Setup — Phase 1",
1294                "1",
1295                "$45,000.00",
1296                "$3,881.25",
1297                "$48,881.25",
1298            ),
1299            (
1300                "Data Pipeline Development (160 hrs)",
1301                "160",
1302                "$225.00",
1303                "$3,105.00",
1304                "$39,105.00",
1305            ),
1306            (
1307                "GPU Cluster Configuration",
1308                "1",
1309                "$12,000.00",
1310                "$1,035.00",
1311                "$13,035.00",
1312            ),
1313            (
1314                "API Gateway Implementation",
1315                "1",
1316                "$18,500.00",
1317                "$1,595.63",
1318                "$20,095.63",
1319            ),
1320            (
1321                "Load Testing & QA (80 hrs)",
1322                "80",
1323                "$195.00",
1324                "$1,345.50",
1325                "$16,945.50",
1326            ),
1327            (
1328                "Documentation & Training",
1329                "1",
1330                "$8,000.00",
1331                "$690.00",
1332                "$8,690.00",
1333            ),
1334            (
1335                "Project Management (3 months)",
1336                "3",
1337                "$6,500.00",
1338                "$1,679.63",
1339                "$21,179.63",
1340            ),
1341        ];
1342        for (i, (desc, qty, unit, tax, amt)) in items.iter().enumerate() {
1343            let r = i + 1;
1344            tbl.cell(r, 0).unwrap().set_text(desc);
1345            tbl.cell(r, 1).unwrap().set_text(qty);
1346            tbl.cell(r, 2).unwrap().set_text(unit);
1347            tbl.cell(r, 3).unwrap().set_text(tax);
1348            tbl.cell(r, 4).unwrap().set_text(amt);
1349            if i % 2 == 0 {
1350                for c in 0..5 {
1351                    tbl.cell(r, c).unwrap().shading("FAF0F0");
1352                }
1353            }
1354        }
1355    }
1356
1357    doc.add_paragraph("");
1358
1359    // ── Totals ──
1360    {
1361        let mut tbl = doc
1362            .add_table(4, 2)
1363            .borders(BorderStyle::Single, 2, "B22222")
1364            .width(Length::inches(3.0))
1365            .alignment(Alignment::Right);
1366        tbl.cell(0, 0).unwrap().set_text("Subtotal");
1367        tbl.cell(0, 1).unwrap().set_text("$154,600.00");
1368        tbl.cell(1, 0).unwrap().set_text("Tax (8.625%)");
1369        tbl.cell(1, 1).unwrap().set_text("$13,334.25");
1370        tbl.cell(2, 0).unwrap().set_text("Discount (5%)");
1371        tbl.cell(2, 1).unwrap().set_text("-$7,730.00");
1372        tbl.cell(3, 0).unwrap().set_text("AMOUNT DUE");
1373        tbl.cell(3, 0).unwrap().shading("B22222");
1374        tbl.cell(3, 1).unwrap().set_text("$160,204.25");
1375        tbl.cell(3, 1).unwrap().shading("B22222");
1376    }
1377
1378    doc.add_paragraph("");
1379    doc.add_paragraph("");
1380
1381    // ── Payment Details ──
1382    {
1383        let mut p = doc.add_paragraph("");
1384        p.add_run("Payment Details")
1385            .bold(true)
1386            .size(14.0)
1387            .color("333333");
1388    }
1389    doc.add_paragraph("Bank: Silicon Valley Bank")
1390        .indent_left(Length::inches(0.3));
1391    doc.add_paragraph("Account: Tensorbee Inc. — 0847-2953-1120")
1392        .indent_left(Length::inches(0.3));
1393    doc.add_paragraph("Routing: 121140399")
1394        .indent_left(Length::inches(0.3));
1395    doc.add_paragraph("Swift: SVBKUS6S")
1396        .indent_left(Length::inches(0.3));
1397
1398    doc.add_paragraph("");
1399
1400    doc.add_paragraph("Please include invoice number INV-2026-0392 in the payment reference.")
1401        .shading("FAF0F0")
1402        .border_all(BorderStyle::Single, 2, "B22222");
1403
1404    doc
1405}
1406
1407// =============================================================================
1408// 5. REPORT — Forest green + earth tones, with images & hierarchical sections
1409// =============================================================================
1410fn generate_report(_samples_dir: &Path) -> Document {
1411    let mut doc = Document::new();
1412
1413    // Colors: Forest #2D5016, Sage #6B8E23, Earth #8B7355, Cream #FFFAF0
1414    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1415    doc.set_margins(
1416        Length::inches(1.0),
1417        Length::inches(1.0),
1418        Length::inches(1.0),
1419        Length::inches(1.0),
1420    );
1421    doc.set_title("Q4 2025 Environmental Impact Report");
1422    doc.set_author("Walter White");
1423    doc.set_subject("Quarterly Environmental Report");
1424    doc.set_keywords("environment, sustainability, report, Tensorbee");
1425
1426    doc.set_different_first_page(true);
1427    doc.set_first_page_header("");
1428    doc.set_header("Tensorbee — Q4 2025 Environmental Impact Report");
1429    doc.set_footer("CONFIDENTIAL — Page");
1430
1431    // ── Cover ──
1432    let cover_bg = create_sample_png(612, 792, [20, 50, 15]);
1433    doc.add_background_image(&cover_bg, "report_cover.png");
1434
1435    for _ in 0..5 {
1436        doc.add_paragraph("");
1437    }
1438    {
1439        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1440        p.add_run("Q4 2025")
1441            .bold(true)
1442            .size(48.0)
1443            .color("FFFFFF")
1444            .font("Georgia");
1445    }
1446    {
1447        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1448        p.add_run("Environmental Impact Report")
1449            .size(24.0)
1450            .color("90EE90")
1451            .font("Georgia")
1452            .italic(true);
1453    }
1454    doc.add_paragraph("");
1455    {
1456        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1457        p.add_run("Tensorbee — Sustainability Division")
1458            .size(14.0)
1459            .color("C0C0C0");
1460    }
1461    {
1462        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1463        p.add_run("Prepared by Walter White, Chief Sustainability Officer")
1464            .size(11.0)
1465            .color("AAAAAA");
1466    }
1467
1468    // ── TOC ──
1469    doc.add_paragraph("").page_break_before(true);
1470    doc.insert_toc(doc.content_count(), 3);
1471
1472    // ── Section 1: Executive Overview ──
1473    doc.add_paragraph("").page_break_before(true);
1474    doc.add_paragraph("Executive Overview").style("Heading1");
1475    doc.add_paragraph(
1476        "This report presents Tensorbee's environmental performance for Q4 2025. Our \
1477         sustainability initiatives have yielded a 34% reduction in carbon emissions compared \
1478         to Q4 2024, exceeding our target of 25%. Key achievements include the transition to \
1479         100% renewable energy in our primary data centers and the launch of our carbon offset \
1480         marketplace.",
1481    )
1482    .first_line_indent(Length::inches(0.3));
1483
1484    // Image: performance chart
1485    let chart_img = create_chart_png(400, 200);
1486    doc.add_paragraph("");
1487    doc.add_picture(
1488        &chart_img,
1489        "performance_chart.png",
1490        Length::inches(5.0),
1491        Length::inches(2.5),
1492    )
1493    .alignment(Alignment::Center);
1494    {
1495        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1496        p.add_run("Figure 1: Quarterly Carbon Emissions (tonnes CO2e)")
1497            .italic(true)
1498            .size(9.0)
1499            .color("666666");
1500    }
1501
1502    // ── Section 2: Energy Consumption ──
1503    doc.add_paragraph("").page_break_before(true);
1504    doc.add_paragraph("Energy Consumption").style("Heading1");
1505
1506    doc.add_paragraph("Data Center Operations")
1507        .style("Heading2");
1508    doc.add_paragraph(
1509        "Our three primary data centers consumed a combined 4.2 GWh in Q4 2025, \
1510         a 12% reduction from Q3 2025 driven by improved cooling efficiency and \
1511         server consolidation.",
1512    );
1513
1514    // Data table
1515    {
1516        let mut tbl = doc
1517            .add_table(5, 4)
1518            .borders(BorderStyle::Single, 4, "2D5016")
1519            .width_pct(100.0);
1520        let hdrs = ["Data Center", "Capacity (MW)", "Usage (GWh)", "PUE"];
1521        for (c, h) in hdrs.iter().enumerate() {
1522            tbl.cell(0, c).unwrap().set_text(h);
1523            tbl.cell(0, c).unwrap().shading("2D5016");
1524        }
1525        let rows = [
1526            ("San Francisco (Primary)", "3.2", "1.8", "1.12"),
1527            ("Dublin (EU)", "2.1", "1.2", "1.18"),
1528            ("Singapore (APAC)", "1.8", "1.2", "1.24"),
1529            ("TOTAL", "7.1", "4.2", "1.17 avg"),
1530        ];
1531        for (i, (dc, cap, use_, pue)) in rows.iter().enumerate() {
1532            tbl.cell(i + 1, 0).unwrap().set_text(dc);
1533            tbl.cell(i + 1, 1).unwrap().set_text(cap);
1534            tbl.cell(i + 1, 2).unwrap().set_text(use_);
1535            tbl.cell(i + 1, 3).unwrap().set_text(pue);
1536            if i == 3 {
1537                for c in 0..4 {
1538                    tbl.cell(i + 1, c).unwrap().shading("E2EFDA");
1539                }
1540            }
1541        }
1542    }
1543
1544    doc.add_paragraph("");
1545
1546    doc.add_paragraph("Renewable Energy Mix").style("Heading2");
1547    doc.add_paragraph("Breakdown of energy sources across all facilities:");
1548
1549    doc.add_bullet_list_item("Solar PV: 42% (1.76 GWh)", 0);
1550    doc.add_bullet_list_item("Wind Power Purchase Agreements: 38% (1.60 GWh)", 0);
1551    doc.add_bullet_list_item("Hydroelectric: 12% (0.50 GWh)", 0);
1552    doc.add_bullet_list_item("Grid (non-renewable): 8% (0.34 GWh)", 0);
1553
1554    // ── Section 3: Water & Waste ──
1555    doc.add_paragraph("Water & Waste Management")
1556        .style("Heading1");
1557
1558    doc.add_paragraph("Water Usage").style("Heading2");
1559    doc.add_paragraph(
1560        "Total water consumption was 12.4 million gallons, a 15% reduction from Q3. \
1561         Our closed-loop cooling systems now recycle 78% of water used in cooling operations.",
1562    );
1563
1564    doc.add_paragraph("Waste Reduction").style("Heading2");
1565    doc.add_paragraph("E-waste management results for Q4:")
1566        .keep_with_next(true);
1567    {
1568        let mut tbl = doc
1569            .add_table(4, 3)
1570            .borders(BorderStyle::Single, 2, "6B8E23")
1571            .width_pct(80.0);
1572        tbl.cell(0, 0).unwrap().set_text("Category");
1573        tbl.cell(0, 0).unwrap().shading("6B8E23");
1574        tbl.cell(0, 1).unwrap().set_text("Weight (kg)");
1575        tbl.cell(0, 1).unwrap().shading("6B8E23");
1576        tbl.cell(0, 2).unwrap().set_text("Recycled %");
1577        tbl.cell(0, 2).unwrap().shading("6B8E23");
1578        let rows = [
1579            ("Server Hardware", "2,450", "94%"),
1580            ("Networking Equipment", "820", "91%"),
1581            ("Storage Media", "340", "99%"),
1582        ];
1583        for (i, (cat, wt, pct)) in rows.iter().enumerate() {
1584            tbl.cell(i + 1, 0).unwrap().set_text(cat);
1585            tbl.cell(i + 1, 1).unwrap().set_text(wt);
1586            tbl.cell(i + 1, 2).unwrap().set_text(pct);
1587        }
1588    }
1589
1590    // ── Section 4: Initiatives ──
1591    doc.add_paragraph("").page_break_before(true);
1592    doc.add_paragraph("Q1 2026 Initiatives").style("Heading1");
1593
1594    doc.add_paragraph("Planned Programs").style("Heading2");
1595    doc.add_numbered_list_item(
1596        "Deploy on-site battery storage at SF data center (2 MWh capacity)",
1597        0,
1598    );
1599    doc.add_numbered_list_item("Pilot immersion cooling in Dublin facility", 0);
1600    doc.add_numbered_list_item("Launch employee commute carbon offset program", 0);
1601    doc.add_numbered_list_item("Achieve ISO 14001 certification for Singapore facility", 0);
1602
1603    doc.add_paragraph("Investment Targets").style("Heading2");
1604    doc.add_paragraph("Sustainability CapEx allocation for FY2026:")
1605        .keep_with_next(true);
1606    {
1607        let mut tbl = doc
1608            .add_table(5, 2)
1609            .borders(BorderStyle::Single, 4, "2D5016");
1610        tbl.cell(0, 0).unwrap().set_text("Initiative");
1611        tbl.cell(0, 0).unwrap().shading("2D5016");
1612        tbl.cell(0, 1).unwrap().set_text("Budget");
1613        tbl.cell(0, 1).unwrap().shading("2D5016");
1614        let rows = [
1615            ("Battery Storage", "$1.2M"),
1616            ("Immersion Cooling Pilot", "$800K"),
1617            ("Solar Panel Expansion", "$2.1M"),
1618            ("Carbon Credits", "$500K"),
1619        ];
1620        for (i, (init, budget)) in rows.iter().enumerate() {
1621            tbl.cell(i + 1, 0).unwrap().set_text(init);
1622            tbl.cell(i + 1, 1).unwrap().set_text(budget);
1623            if i % 2 == 0 {
1624                tbl.cell(i + 1, 0).unwrap().shading("FFFAF0");
1625                tbl.cell(i + 1, 1).unwrap().shading("FFFAF0");
1626            }
1627        }
1628    }
1629
1630    // Image: sustainability roadmap
1631    let roadmap_img = create_sample_png(500, 100, [40, 80, 30]);
1632    doc.add_paragraph("");
1633    doc.add_picture(
1634        &roadmap_img,
1635        "roadmap.png",
1636        Length::inches(6.0),
1637        Length::inches(1.2),
1638    )
1639    .alignment(Alignment::Center);
1640    {
1641        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1642        p.add_run("Figure 2: 2026 Sustainability Roadmap")
1643            .italic(true)
1644            .size(9.0)
1645            .color("666666");
1646    }
1647
1648    doc.add_paragraph("");
1649    doc.add_paragraph(
1650        "For questions about this report, contact Walter White at sustainability@tensorbee.com.",
1651    )
1652    .shading("FFFAF0")
1653    .border_all(BorderStyle::Single, 2, "2D5016");
1654
1655    doc
1656}
1657
1658// =============================================================================
1659// 6. LETTER — Slate blue + silver scheme
1660// =============================================================================
1661fn generate_letter(_samples_dir: &Path) -> Document {
1662    let mut doc = Document::new();
1663
1664    // Colors: Slate #4A5568, Blue accent #3182CE
1665    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1666    doc.set_margins(
1667        Length::inches(1.25),
1668        Length::inches(1.25),
1669        Length::inches(1.25),
1670        Length::inches(1.25),
1671    );
1672    doc.set_title("Business Letter");
1673    doc.set_author("Walter White");
1674
1675    // Banner header using raw XML
1676    let logo_img = create_logo_png(220, 48);
1677    let banner = build_header_banner_xml(
1678        "rId1",
1679        &BannerOpts {
1680            bg_color: "4A5568",
1681            banner_width: 7772400,
1682            banner_height: 731520, // ~0.8"
1683            logo_width: 2011680,
1684            logo_height: 438912,
1685            logo_x_offset: 295125,
1686            logo_y_offset: 146304,
1687        },
1688    );
1689    doc.set_raw_header_with_images(
1690        banner,
1691        &[("rId1", &logo_img, "logo.png")],
1692        rdocx_oxml::header_footer::HdrFtrType::Default,
1693    );
1694    doc.set_margins(
1695        Length::twips(2000),
1696        Length::twips(1800),
1697        Length::twips(1440),
1698        Length::twips(1800),
1699    );
1700    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
1701
1702    // Footer with contact
1703    doc.set_footer(
1704        "Tensorbee | 123 Innovation Drive, Suite 400 | San Francisco, CA 94105 | tensorbee.com",
1705    );
1706
1707    // ── Sender Address ──
1708    doc.add_paragraph("Walter White");
1709    doc.add_paragraph("Chief Executive Officer");
1710    doc.add_paragraph("Tensorbee");
1711    doc.add_paragraph("123 Innovation Drive, Suite 400");
1712    doc.add_paragraph("San Francisco, CA 94105");
1713    doc.add_paragraph("");
1714    doc.add_paragraph("February 22, 2026");
1715
1716    doc.add_paragraph("");
1717
1718    // ── Recipient ──
1719    doc.add_paragraph("Ms. Sarah Chen");
1720    doc.add_paragraph("VP of Engineering");
1721    doc.add_paragraph("Meridian Dynamics LLC");
1722    doc.add_paragraph("456 Enterprise Boulevard");
1723    doc.add_paragraph("Austin, TX 78701");
1724
1725    doc.add_paragraph("");
1726
1727    // ── Subject ──
1728    {
1729        let mut p = doc.add_paragraph("");
1730        p.add_run("Re: Strategic Technology Partnership — Phase 2 Expansion")
1731            .bold(true);
1732    }
1733
1734    doc.add_paragraph("");
1735
1736    // ── Body ──
1737    doc.add_paragraph("Dear Ms. Chen,");
1738    doc.add_paragraph("");
1739
1740    doc.add_paragraph(
1741        "I am writing to express Tensorbee's enthusiasm for expanding our strategic technology \
1742         partnership with Meridian Dynamics. Following the successful completion of Phase 1, \
1743         which delivered a 40% improvement in your ML inference pipeline throughput, we believe \
1744         the foundation is firmly established for an ambitious Phase 2 engagement.",
1745    )
1746    .first_line_indent(Length::inches(0.5));
1747
1748    doc.add_paragraph(
1749        "During our recent executive review, your team highlighted three priority areas for \
1750         the next phase of collaboration:",
1751    )
1752    .first_line_indent(Length::inches(0.5));
1753
1754    doc.add_numbered_list_item(
1755        "Real-time anomaly detection for your financial transaction monitoring system, \
1756         targeting sub-100ms latency at 50,000 transactions per second.",
1757        0,
1758    );
1759    doc.add_numbered_list_item(
1760        "Federated learning infrastructure to enable model training across your distributed \
1761         data centers without centralizing sensitive financial data.",
1762        0,
1763    );
1764    doc.add_numbered_list_item(
1765        "MLOps automation to reduce your model deployment cycle from the current 5 days \
1766         to under 4 hours.",
1767        0,
1768    );
1769
1770    doc.add_paragraph(
1771        "Our engineering team has prepared a detailed technical proposal addressing each of \
1772         these areas. We have allocated a dedicated team of eight senior engineers, led by \
1773         our CTO, to ensure continuity with the Phase 1 team your organization has already \
1774         built a productive working relationship with.",
1775    )
1776    .first_line_indent(Length::inches(0.5));
1777
1778    doc.add_paragraph(
1779        "I would welcome the opportunity to present our Phase 2 proposal in person. My \
1780         assistant will reach out to coordinate a meeting at your convenience during the \
1781         first week of March.",
1782    )
1783    .first_line_indent(Length::inches(0.5));
1784
1785    doc.add_paragraph("");
1786
1787    doc.add_paragraph("Warm regards,");
1788    doc.add_paragraph("");
1789    doc.add_paragraph("");
1790
1791    // Signature line
1792    doc.add_paragraph("")
1793        .border_bottom(BorderStyle::Single, 4, "4A5568");
1794    {
1795        let mut p = doc.add_paragraph("");
1796        p.add_run("Walter White").bold(true).size(12.0);
1797    }
1798    {
1799        let mut p = doc.add_paragraph("");
1800        p.add_run("Chief Executive Officer, Tensorbee")
1801            .italic(true)
1802            .size(10.0)
1803            .color("666666");
1804    }
1805    {
1806        let mut p = doc.add_paragraph("");
1807        p.add_run("walter@tensorbee.com | +1 (415) 555-0199")
1808            .size(10.0)
1809            .color("888888");
1810    }
1811
1812    doc
1813}
1814
1815// =============================================================================
1816// 7. EMPLOYMENT CONTRACT — Purple + charcoal scheme
1817// =============================================================================
1818fn generate_contract(_samples_dir: &Path) -> Document {
1819    let mut doc = Document::new();
1820
1821    // Colors: Purple #5B2C6F, Plum #8E44AD, Gray #2C3E50
1822    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1823    doc.set_margins(
1824        Length::inches(1.25),
1825        Length::inches(1.0),
1826        Length::inches(1.0),
1827        Length::inches(1.0),
1828    );
1829    doc.set_title("Employment Agreement");
1830    doc.set_author("Tensorbee HR Department");
1831    doc.set_subject("Employment Contract — Walter White");
1832    doc.set_keywords("employment, contract, agreement, Tensorbee");
1833
1834    doc.set_header("TENSORBEE — Employment Agreement");
1835    doc.set_footer("Employment Agreement — Walter White — Page");
1836
1837    // ── Title Block ──
1838    doc.add_paragraph("")
1839        .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1840    {
1841        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1842        p.add_run("EMPLOYMENT AGREEMENT")
1843            .bold(true)
1844            .size(24.0)
1845            .color("5B2C6F")
1846            .font("Georgia");
1847    }
1848    doc.add_paragraph("")
1849        .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1850    doc.add_paragraph("");
1851
1852    // ── Parties ──
1853    doc.add_paragraph(
1854        "This Employment Agreement (\"Agreement\") is entered into as of February 22, 2026 \
1855         (\"Effective Date\"), by and between:",
1856    );
1857
1858    doc.add_paragraph("");
1859
1860    {
1861        let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1862        p.add_run("EMPLOYER: ").bold(true);
1863        p.add_run(
1864            "Tensorbee, Inc., a Delaware corporation with its principal place of business at \
1865                   123 Innovation Drive, Suite 400, San Francisco, CA 94105 (\"Company\")",
1866        );
1867    }
1868    doc.add_paragraph("");
1869    {
1870        let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1871        p.add_run("EMPLOYEE: ").bold(true);
1872        p.add_run(
1873            "Walter White, residing at 308 Negra Arroyo Lane, Albuquerque, NM 87104 (\"Employee\")",
1874        );
1875    }
1876
1877    doc.add_paragraph("");
1878    doc.add_paragraph("The Company and Employee are collectively referred to as the \"Parties.\"");
1879    doc.add_paragraph("");
1880
1881    // ── Article 1: Position and Duties ──
1882    doc.add_paragraph("Article 1 — Position and Duties")
1883        .style("Heading1");
1884    {
1885        let mut p = doc.add_paragraph("");
1886        p.add_run("1.1 ").bold(true);
1887        p.add_run("The Company hereby employs the Employee as ");
1888        p.add_run("Chief Executive Officer (CEO)")
1889            .bold(true)
1890            .italic(true);
1891        p.add_run(", reporting directly to the Board of Directors.");
1892    }
1893    {
1894        let mut p = doc.add_paragraph("");
1895        p.add_run("1.2 ").bold(true);
1896        p.add_run("The Employee shall devote full working time, attention, and best efforts to \
1897                   the performance of duties as reasonably assigned by the Board, including but not limited to:");
1898    }
1899    doc.add_bullet_list_item(
1900        "Setting and executing the Company's strategic vision and business plan",
1901        0,
1902    );
1903    doc.add_bullet_list_item(
1904        "Overseeing all operations, engineering, and commercial activities",
1905        0,
1906    );
1907    doc.add_bullet_list_item(
1908        "Representing the Company to investors, customers, and the public",
1909        0,
1910    );
1911    doc.add_bullet_list_item("Recruiting, developing, and retaining key talent", 0);
1912
1913    {
1914        let mut p = doc.add_paragraph("");
1915        p.add_run("1.3 ").bold(true);
1916        p.add_run(
1917            "The Employee's primary work location shall be the Company's San Francisco \
1918                   headquarters, with reasonable travel as required by business needs.",
1919        );
1920    }
1921
1922    // ── Article 2: Compensation ──
1923    doc.add_paragraph("Article 2 — Compensation and Benefits")
1924        .style("Heading1");
1925    {
1926        let mut p = doc.add_paragraph("");
1927        p.add_run("2.1 Base Salary. ").bold(true);
1928        p.add_run("The Company shall pay the Employee an annual base salary of ");
1929        p.add_run("$375,000.00").bold(true);
1930        p.add_run(" (Three Hundred Seventy-Five Thousand Dollars), payable in accordance with the \
1931                   Company's standard payroll schedule, less applicable withholdings and deductions.");
1932    }
1933    {
1934        let mut p = doc.add_paragraph("");
1935        p.add_run("2.2 Annual Bonus. ").bold(true);
1936        p.add_run("The Employee shall be eligible for an annual performance bonus of up to ");
1937        p.add_run("40%").bold(true);
1938        p.add_run(
1939            " of base salary, based on achievement of mutually agreed performance objectives.",
1940        );
1941    }
1942    {
1943        let mut p = doc.add_paragraph("");
1944        p.add_run("2.3 Equity. ").bold(true);
1945        p.add_run("Subject to Board approval, the Employee shall receive a stock option grant of ");
1946        p.add_run("500,000 shares").bold(true);
1947        p.add_run(
1948            " of the Company's common stock, vesting over four (4) years with a one-year cliff, \
1949                   at an exercise price equal to the fair market value on the date of grant.",
1950        );
1951    }
1952
1953    // Compensation summary table
1954    doc.add_paragraph("");
1955    {
1956        let mut tbl = doc
1957            .add_table(5, 2)
1958            .borders(BorderStyle::Single, 4, "5B2C6F")
1959            .width_pct(70.0)
1960            .alignment(Alignment::Center);
1961        tbl.cell(0, 0).unwrap().set_text("Compensation Element");
1962        tbl.cell(0, 0).unwrap().shading("5B2C6F");
1963        tbl.cell(0, 1).unwrap().set_text("Value");
1964        tbl.cell(0, 1).unwrap().shading("5B2C6F");
1965        let items = [
1966            ("Base Salary", "$375,000/year"),
1967            ("Target Bonus", "Up to 40% ($150,000)"),
1968            ("Equity Grant", "500,000 shares (4yr vest)"),
1969            ("Total Target Comp", "$525,000 + equity"),
1970        ];
1971        for (i, (elem, val)) in items.iter().enumerate() {
1972            tbl.cell(i + 1, 0).unwrap().set_text(elem);
1973            tbl.cell(i + 1, 1).unwrap().set_text(val);
1974            if i == 3 {
1975                tbl.cell(i + 1, 0).unwrap().shading("E8DAEF");
1976                tbl.cell(i + 1, 1).unwrap().shading("E8DAEF");
1977            }
1978        }
1979    }
1980
1981    // ── Article 3: Benefits ──
1982    doc.add_paragraph("").page_break_before(true);
1983    doc.add_paragraph("Article 3 — Benefits").style("Heading1");
1984    {
1985        let mut p = doc.add_paragraph("");
1986        p.add_run("3.1 ").bold(true);
1987        p.add_run(
1988            "The Employee shall be entitled to participate in all benefit programs \
1989                   generally available to senior executives, including:",
1990        );
1991    }
1992    doc.add_bullet_list_item(
1993        "Medical, dental, and vision insurance (100% premium coverage for employee and dependents)",
1994        0,
1995    );
1996    doc.add_bullet_list_item("401(k) retirement plan with 6% company match", 0);
1997    doc.add_bullet_list_item("Life insurance and long-term disability coverage", 0);
1998    doc.add_bullet_list_item("Annual professional development allowance of $10,000", 0);
1999
2000    {
2001        let mut p = doc.add_paragraph("");
2002        p.add_run("3.2 Paid Time Off. ").bold(true);
2003        p.add_run(
2004            "The Employee shall receive 25 days of paid vacation per year, plus Company holidays, \
2005                   accruing on a monthly basis.",
2006        );
2007    }
2008
2009    // ── Article 4: Term and Termination ──
2010    doc.add_paragraph("Article 4 — Term and Termination")
2011        .style("Heading1");
2012    {
2013        let mut p = doc.add_paragraph("");
2014        p.add_run("4.1 At-Will Employment. ").bold(true);
2015        p.add_run(
2016            "This Agreement is for at-will employment and may be terminated by either Party \
2017                   at any time, with or without cause, subject to the notice provisions herein.",
2018        );
2019    }
2020    {
2021        let mut p = doc.add_paragraph("");
2022        p.add_run("4.2 Notice Period. ").bold(true);
2023        p.add_run("Either Party shall provide ");
2024        p.add_run("ninety (90) days").bold(true);
2025        p.add_run(" written notice of termination, or pay in lieu thereof.");
2026    }
2027    {
2028        let mut p = doc.add_paragraph("");
2029        p.add_run("4.3 Severance. ").bold(true);
2030        p.add_run("In the event of termination by the Company without Cause, the Employee shall \
2031                   receive (a) twelve (12) months of base salary continuation, (b) pro-rata bonus \
2032                   for the year of termination, and (c) twelve (12) months of COBRA premium coverage.");
2033    }
2034
2035    // ── Article 5: Confidentiality ──
2036    doc.add_paragraph("Article 5 — Confidentiality and IP")
2037        .style("Heading1");
2038    {
2039        let mut p = doc.add_paragraph("");
2040        p.add_run("5.1 ").bold(true);
2041        p.add_run(
2042            "The Employee agrees to maintain strict confidentiality of all proprietary \
2043                   information, trade secrets, and business plans of the Company during and after \
2044                   employment.",
2045        );
2046    }
2047    {
2048        let mut p = doc.add_paragraph("");
2049        p.add_run("5.2 ").bold(true);
2050        p.add_run(
2051            "All intellectual property, inventions, and works of authorship created by the \
2052                   Employee during the term of employment and within the scope of duties shall be \
2053                   the sole and exclusive property of the Company.",
2054        );
2055    }
2056
2057    // ── Article 6: Non-Compete ──
2058    doc.add_paragraph("Article 6 — Non-Competition")
2059        .style("Heading1");
2060    {
2061        let mut p = doc.add_paragraph("");
2062        p.add_run("6.1 ").bold(true);
2063        p.add_run("For a period of twelve (12) months following termination, the Employee agrees \
2064                   not to engage in any business that directly competes with the Company's core \
2065                   business of AI infrastructure and ML operations services, within the United States.");
2066    }
2067
2068    // ── Article 7: General Provisions ──
2069    doc.add_paragraph("Article 7 — General Provisions")
2070        .style("Heading1");
2071    {
2072        let mut p = doc.add_paragraph("");
2073        p.add_run("7.1 Governing Law. ").bold(true);
2074        p.add_run(
2075            "This Agreement shall be governed by and construed in accordance with the laws of \
2076                   the State of California.",
2077        );
2078    }
2079    {
2080        let mut p = doc.add_paragraph("");
2081        p.add_run("7.2 Entire Agreement. ").bold(true);
2082        p.add_run(
2083            "This Agreement constitutes the entire agreement between the Parties and supersedes \
2084                   all prior negotiations, representations, and agreements.",
2085        );
2086    }
2087    {
2088        let mut p = doc.add_paragraph("");
2089        p.add_run("7.3 Amendment. ").bold(true);
2090        p.add_run(
2091            "This Agreement may only be amended by a written instrument signed by both Parties.",
2092        );
2093    }
2094
2095    // ── Signature Block ──
2096    doc.add_paragraph("").page_break_before(true);
2097    doc.add_paragraph("IN WITNESS WHEREOF, the Parties have executed this Employment Agreement as of the Effective Date.")
2098        .space_after(Length::pt(24.0));
2099
2100    // Signature table
2101    {
2102        let mut tbl = doc.add_table(6, 2).width_pct(100.0);
2103        tbl.cell(0, 0).unwrap().set_text("FOR THE COMPANY:");
2104        tbl.cell(0, 0).unwrap().shading("5B2C6F");
2105        tbl.cell(0, 1).unwrap().set_text("EMPLOYEE:");
2106        tbl.cell(0, 1).unwrap().shading("5B2C6F");
2107
2108        tbl.cell(1, 0).unwrap().set_text("");
2109        tbl.cell(1, 1).unwrap().set_text("");
2110        tbl.row(1).unwrap().height(Length::pt(40.0));
2111
2112        tbl.cell(2, 0)
2113            .unwrap()
2114            .set_text("Signature: ___________________________");
2115        tbl.cell(2, 1)
2116            .unwrap()
2117            .set_text("Signature: ___________________________");
2118        tbl.cell(3, 0)
2119            .unwrap()
2120            .set_text("Name: Board Representative");
2121        tbl.cell(3, 1).unwrap().set_text("Name: Walter White");
2122        tbl.cell(4, 0)
2123            .unwrap()
2124            .set_text("Title: Chair, Board of Directors");
2125        tbl.cell(4, 1)
2126            .unwrap()
2127            .set_text("Title: Chief Executive Officer");
2128        tbl.cell(5, 0).unwrap().set_text("Date: _______________");
2129        tbl.cell(5, 1).unwrap().set_text("Date: _______________");
2130    }
2131
2132    doc
2133}
Source

pub fn set_columns(&mut self, num: u32, spacing: Length)

Set equal-width column layout.

Set header and footer distances from page edges.

Examples found in repository?
examples/header_banner.rs (line 40)
29fn generate_header_banner_doc(path: &Path) {
30    let mut doc = Document::new();
31
32    // Page setup with extra top margin for the banner
33    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
34    doc.set_margins(
35        Length::twips(2292), // top — extra tall for header banner
36        Length::twips(1440), // right
37        Length::twips(1440), // bottom
38        Length::twips(1440), // left
39    );
40    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
41
42    // Generate a simple logo image (white text on transparent background)
43    let logo_img = create_logo_png(220, 48);
44
45    // ── Dark blue banner ──
46    let banner = build_header_banner_xml(
47        "rId1",
48        &BannerOpts {
49            bg_color: "1A3C6E",
50            banner_width: 7772400, // full page width in EMU (~8.5")
51            banner_height: 969026, // banner height in EMU (~1.06")
52            logo_width: 2011680,   // logo display width (~2.2")
53            logo_height: 438912,   // logo display height (~0.48")
54            logo_x_offset: 295125, // left padding
55            logo_y_offset: 265057, // vertical centering
56        },
57    );
58
59    doc.set_raw_header_with_images(
60        banner.clone(),
61        &[("rId1", &logo_img, "logo.png")],
62        rdocx_oxml::header_footer::HdrFtrType::Default,
63    );
64
65    // Use a different first page header (same banner, different color)
66    doc.set_different_first_page(true);
67    let first_page_banner = build_header_banner_xml(
68        "rId1",
69        &BannerOpts {
70            bg_color: "2E75B6", // lighter blue for cover
71            banner_width: 7772400,
72            banner_height: 969026,
73            logo_width: 2011680,
74            logo_height: 438912,
75            logo_x_offset: 295125,
76            logo_y_offset: 265057,
77        },
78    );
79    doc.set_raw_header_with_images(
80        first_page_banner,
81        &[("rId1", &logo_img, "logo.png")],
82        rdocx_oxml::header_footer::HdrFtrType::First,
83    );
84
85    // Footer
86    doc.set_footer("Confidential — Internal Use Only");
87
88    // ── Page 1: Cover ──
89    doc.add_paragraph("Company Report").style("Heading1");
90
91    doc.add_paragraph(
92        "This document demonstrates a custom header banner built with DrawingML \
93         group shapes. The banner uses a colored rectangle with a logo image overlaid, \
94         positioned at the top of each page.",
95    );
96
97    doc.add_paragraph("");
98
99    doc.add_paragraph("How the Header Banner Works")
100        .style("Heading2");
101
102    doc.add_paragraph(
103        "The header banner is built using set_raw_header_with_images(), which \
104         accepts raw XML and a list of (rel_id, image_data, filename) tuples. \
105         The XML uses a DrawingML group shape (wpg:wgp) containing:",
106    );
107
108    doc.add_bullet_list_item(
109        "A wps:wsp rectangle shape with a solid color fill (the background bar)",
110        0,
111    );
112    doc.add_bullet_list_item(
113        "A pic:pic image element positioned within the group (the logo)",
114        0,
115    );
116    doc.add_bullet_list_item(
117        "The group is wrapped in a wp:anchor element for absolute page positioning",
118        0,
119    );
120
121    doc.add_paragraph("");
122
123    doc.add_paragraph("Customization").style("Heading2");
124
125    doc.add_paragraph(
126        "All dimensions are in EMU (English Metric Units) where 914400 EMU = 1 inch. \
127         You can customize:",
128    );
129
130    doc.add_bullet_list_item("bg_color — any hex color for the rectangle background", 0);
131    doc.add_bullet_list_item("banner_width / banner_height — size of the full banner", 0);
132    doc.add_bullet_list_item("logo_width / logo_height — display size of the logo", 0);
133    doc.add_bullet_list_item(
134        "logo_x_offset / logo_y_offset — logo position within the banner",
135        0,
136    );
137
138    doc.add_paragraph("");
139
140    doc.add_paragraph("Different First Page").style("Heading2");
141
142    doc.add_paragraph(
143        "This page uses a lighter blue banner (first page header). \
144         Subsequent pages use a darker blue banner (default header). \
145         Use set_different_first_page(true) to enable this.",
146    );
147
148    // ── Page 2 ──
149    doc.add_paragraph("").page_break_before(true);
150
151    doc.add_paragraph("Second Page").style("Heading1");
152
153    doc.add_paragraph(
154        "This page shows the default header banner (dark blue). The first page \
155         had a lighter blue banner because we set a different first-page header.",
156    );
157
158    doc.add_paragraph("");
159
160    doc.add_paragraph(
161        "The banner repeats on every page because it is placed in the header part. \
162         You can have different banners for default, first-page, and even-page headers.",
163    );
164
165    doc.set_title("Header Banner Example");
166    doc.set_author("rdocx");
167
168    doc.save(path).unwrap();
169}
More examples
Hide additional examples
examples/generate_all_samples.rs (line 108)
97fn generate_feature_showcase(_samples_dir: &Path) -> Document {
98    let mut doc = Document::new();
99
100    // ── Page Setup & Metadata ──
101    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
102    doc.set_margins(
103        Length::inches(1.0),
104        Length::inches(1.0),
105        Length::inches(1.0),
106        Length::inches(1.0),
107    );
108    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
109    doc.set_gutter(Length::twips(0));
110    doc.set_title("rdocx Feature Showcase");
111    doc.set_author("rdocx Sample Generator");
112    doc.set_subject("Comprehensive feature demonstration");
113    doc.set_keywords("rdocx, docx, rust, sample, showcase");
114
115    // Headers & Footers with different first page
116    doc.set_different_first_page(true);
117    doc.set_first_page_header("rdocx");
118    doc.set_first_page_footer("Feature Showcase — Cover Page");
119    doc.set_header("rdocx Feature Showcase");
120    doc.set_footer("Generated by rdocx");
121
122    // ── COVER PAGE ──
123    let bg = create_sample_png(612, 792, [20, 45, 90]);
124    doc.add_background_image(&bg, "cover_bg.png");
125
126    for _ in 0..3 {
127        doc.add_paragraph("");
128    }
129    {
130        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
131        p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
132    }
133    {
134        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
135        p.add_run("Complete Feature Showcase")
136            .size(28.0)
137            .color("FFFFFF")
138            .italic(true);
139    }
140    doc.add_paragraph("");
141    {
142        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
143        p.add_run("Every feature of the rdocx Rust library")
144            .size(13.0)
145            .color("B0C4DE");
146    }
147    {
148        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
149        p.add_run("demonstrated in a single document.")
150            .size(13.0)
151            .color("B0C4DE");
152    }
153
154    // ── TABLE OF CONTENTS ──
155    doc.add_paragraph("").page_break_before(true);
156    doc.insert_toc(doc.content_count(), 3);
157
158    // ── SECTION 1: TEXT FORMATTING ──
159    doc.add_paragraph("").page_break_before(true);
160    doc.add_paragraph("1. Text Formatting").style("Heading1");
161
162    // Paragraph Alignment
163    doc.add_paragraph("1.1 Paragraph Alignment")
164        .style("Heading2");
165    doc.add_paragraph("Left-aligned paragraph (default).")
166        .alignment(Alignment::Left);
167    doc.add_paragraph("Center-aligned paragraph.")
168        .alignment(Alignment::Center);
169    doc.add_paragraph("Right-aligned paragraph.")
170        .alignment(Alignment::Right);
171    doc.add_paragraph(
172        "Justified paragraph — this text is long enough to demonstrate how justified alignment \
173         distributes extra space across word gaps so lines fill the full width of the text area.",
174    )
175    .alignment(Alignment::Justify);
176
177    doc.add_paragraph("");
178
179    // Run Formatting
180    doc.add_paragraph("1.2 Run Formatting").style("Heading2");
181    {
182        let mut p = doc.add_paragraph("");
183        p.add_run("Bold").bold(true);
184        p.add_run(" | ");
185        p.add_run("Italic").italic(true);
186        p.add_run(" | ");
187        p.add_run("Bold Italic").bold(true).italic(true);
188        p.add_run(" | ");
189        p.add_run("Underline").underline(true);
190        p.add_run(" | ");
191        p.add_run("Strikethrough").strike(true);
192        p.add_run(" | ");
193        p.add_run("Double Strike").double_strike(true);
194    }
195    {
196        let mut p = doc.add_paragraph("");
197        p.add_run("Red").color("FF0000");
198        p.add_run(" | ");
199        p.add_run("Blue").color("0000FF");
200        p.add_run(" | ");
201        p.add_run("Green").color("00AA00");
202        p.add_run(" | ");
203        p.add_run("Highlighted").highlight("FFFF00");
204    }
205    {
206        let mut p = doc.add_paragraph("");
207        p.add_run("8pt").size(8.0);
208        p.add_run(" | ");
209        p.add_run("11pt").size(11.0);
210        p.add_run(" | ");
211        p.add_run("16pt").size(16.0);
212        p.add_run(" | ");
213        p.add_run("24pt").size(24.0);
214    }
215    {
216        let mut p = doc.add_paragraph("");
217        p.add_run("Arial").font("Arial");
218        p.add_run(" | ");
219        p.add_run("Times New Roman").font("Times New Roman");
220        p.add_run(" | ");
221        p.add_run("Courier New").font("Courier New");
222    }
223    {
224        let mut p = doc.add_paragraph("");
225        p.add_run("H");
226        p.add_run("2").subscript();
227        p.add_run("O (subscript) | E=mc");
228        p.add_run("2").superscript();
229        p.add_run(" (superscript)");
230    }
231    {
232        let mut p = doc.add_paragraph("");
233        p.add_run("ALL CAPS").all_caps(true);
234        p.add_run(" | ");
235        p.add_run("Small Caps").small_caps(true);
236        p.add_run(" | ");
237        p.add_run("Expanded").character_spacing(Length::twips(40));
238        p.add_run(" | ");
239        p.add_run("Hidden text (not visible)").hidden(true);
240    }
241
242    doc.add_paragraph("");
243
244    // Underline Styles
245    doc.add_paragraph("1.3 Underline Styles").style("Heading2");
246    {
247        let mut p = doc.add_paragraph("");
248        p.add_run("Single ")
249            .underline_style(rdocx::UnderlineStyle::Single);
250        p.add_run("Double ")
251            .underline_style(rdocx::UnderlineStyle::Double);
252        p.add_run("Thick ")
253            .underline_style(rdocx::UnderlineStyle::Thick);
254        p.add_run("Dotted ")
255            .underline_style(rdocx::UnderlineStyle::Dotted);
256        p.add_run("Dash ")
257            .underline_style(rdocx::UnderlineStyle::Dash);
258        p.add_run("Wave ")
259            .underline_style(rdocx::UnderlineStyle::Wave);
260    }
261
262    // ── SECTION 2: PARAGRAPH FORMATTING ──
263    doc.add_paragraph("").page_break_before(true);
264    doc.add_paragraph("2. Paragraph Formatting")
265        .style("Heading1");
266
267    doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
268    doc.add_paragraph("Paragraph with light green shading")
269        .shading("E2EFDA");
270    doc.add_paragraph("Paragraph with bottom border")
271        .border_bottom(BorderStyle::Single, 6, "2E75B6");
272    doc.add_paragraph("Paragraph with all borders (red)")
273        .border_all(BorderStyle::Single, 4, "FF0000");
274
275    doc.add_paragraph("2.2 Indentation").style("Heading2");
276    doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
277        .indent_left(Length::inches(1.0))
278        .hanging_indent(Length::inches(0.5));
279    doc.add_paragraph("0.5-inch first-line indent on this paragraph")
280        .first_line_indent(Length::inches(0.5));
281    doc.add_paragraph("Both left and right indent (0.75 inches each)")
282        .indent_left(Length::inches(0.75))
283        .indent_right(Length::inches(0.75));
284
285    doc.add_paragraph("2.3 Spacing & Line Height")
286        .style("Heading2");
287    doc.add_paragraph("Extra space before (24pt) and after (12pt)")
288        .space_before(Length::pt(24.0))
289        .space_after(Length::pt(12.0));
290    doc.add_paragraph(
291        "Double line spacing paragraph. This text should have extra vertical space between \
292         lines to demonstrate line_spacing_multiple(2.0).",
293    )
294    .line_spacing_multiple(2.0);
295    doc.add_paragraph("Exact 20pt line spacing (fixed height).")
296        .line_spacing(20.0);
297
298    doc.add_paragraph("2.4 Pagination Controls")
299        .style("Heading2");
300    doc.add_paragraph("keep_with_next — this paragraph stays with the next")
301        .keep_with_next(true);
302    doc.add_paragraph("(This paragraph was kept with the one above.)");
303    doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
304        .keep_together(true);
305    doc.add_paragraph("widow_control — prevents widow/orphan lines")
306        .widow_control(true);
307
308    // ── SECTION 3: LISTS ──
309    doc.add_paragraph("").page_break_before(true);
310    doc.add_paragraph("3. Lists").style("Heading1");
311
312    doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
313    doc.add_bullet_list_item("First item", 0);
314    doc.add_bullet_list_item("Second item", 0);
315    doc.add_bullet_list_item("Nested level 1", 1);
316    doc.add_bullet_list_item("Nested level 2", 2);
317    doc.add_bullet_list_item("Back to level 1", 1);
318    doc.add_bullet_list_item("Third item", 0);
319
320    doc.add_paragraph("");
321
322    doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
323    doc.add_numbered_list_item("First numbered item", 0);
324    doc.add_numbered_list_item("Second numbered item", 0);
325    doc.add_numbered_list_item("Sub-item A", 1);
326    doc.add_numbered_list_item("Sub-item B", 1);
327    doc.add_numbered_list_item("Third numbered item", 0);
328
329    // ── SECTION 4: TAB STOPS ──
330    doc.add_paragraph("");
331    doc.add_paragraph("4. Tab Stops").style("Heading1");
332
333    doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
334    doc.add_paragraph("Left\tCenter\tRight\tDecimal")
335        .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
336        .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
337        .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
338        .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
339
340    doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
341    doc.add_paragraph("Item\t\tPrice")
342        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
343        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
344        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
345    doc.add_paragraph("Widget\t\t$19.99")
346        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
347        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
348        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
349    doc.add_paragraph("Gadget\t\t$249.50")
350        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
351        .add_tab_stop_with_leader(
352            TabAlignment::Right,
353            Length::inches(4.0),
354            TabLeader::Underscore,
355        )
356        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
357
358    // ── SECTION 5: TABLES ──
359    doc.add_paragraph("").page_break_before(true);
360    doc.add_paragraph("5. Tables").style("Heading1");
361
362    doc.add_paragraph("5.1 Basic Table").style("Heading2");
363    {
364        let mut tbl = doc
365            .add_table(4, 3)
366            .borders(BorderStyle::Single, 4, "000000");
367        for col in 0..3 {
368            tbl.cell(0, col).unwrap().shading("2E75B6");
369        }
370        tbl.cell(0, 0).unwrap().set_text("Name");
371        tbl.cell(0, 1).unwrap().set_text("Role");
372        tbl.cell(0, 2).unwrap().set_text("Location");
373        tbl.cell(1, 0).unwrap().set_text("Walter White");
374        tbl.cell(1, 1).unwrap().set_text("CEO");
375        tbl.cell(1, 2).unwrap().set_text("Albuquerque");
376        tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
377        tbl.cell(2, 1).unwrap().set_text("CTO");
378        tbl.cell(2, 2).unwrap().set_text("Remote");
379        tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
380        tbl.cell(3, 1).unwrap().set_text("Security");
381        tbl.cell(3, 2).unwrap().set_text("Washington");
382    }
383
384    doc.add_paragraph("");
385
386    doc.add_paragraph("5.2 Column Span & Cell Shading")
387        .style("Heading2");
388    {
389        let mut tbl = doc
390            .add_table(3, 4)
391            .borders(BorderStyle::Single, 4, "000000")
392            .width_pct(100.0);
393        tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
394        tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
395        tbl.cell(1, 0).unwrap().set_text("Region");
396        tbl.cell(1, 0).unwrap().shading("D6E4F0");
397        tbl.cell(1, 1).unwrap().set_text("Q1");
398        tbl.cell(1, 1).unwrap().shading("D6E4F0");
399        tbl.cell(1, 2).unwrap().set_text("Q2");
400        tbl.cell(1, 2).unwrap().shading("D6E4F0");
401        tbl.cell(1, 3).unwrap().set_text("Total");
402        tbl.cell(1, 3).unwrap().shading("D6E4F0");
403        tbl.cell(2, 0).unwrap().set_text("Americas");
404        tbl.cell(2, 1).unwrap().set_text("$2.4M");
405        tbl.cell(2, 2).unwrap().set_text("$2.7M");
406        tbl.cell(2, 3).unwrap().set_text("$5.1M");
407    }
408
409    doc.add_paragraph("");
410
411    doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
412    {
413        let mut tbl = doc
414            .add_table(4, 3)
415            .borders(BorderStyle::Single, 4, "000000");
416        tbl.cell(0, 0).unwrap().set_text("Category");
417        tbl.cell(0, 0).unwrap().shading("E2EFDA");
418        tbl.cell(0, 1).unwrap().set_text("Item");
419        tbl.cell(0, 1).unwrap().shading("E2EFDA");
420        tbl.cell(0, 2).unwrap().set_text("Price");
421        tbl.cell(0, 2).unwrap().shading("E2EFDA");
422        tbl.cell(1, 0).unwrap().set_text("Hardware");
423        tbl.cell(1, 0).unwrap().v_merge_restart();
424        tbl.cell(1, 1).unwrap().set_text("Laptop");
425        tbl.cell(1, 2).unwrap().set_text("$1,200");
426        tbl.cell(2, 0).unwrap().v_merge_continue();
427        tbl.cell(2, 1).unwrap().set_text("Monitor");
428        tbl.cell(2, 2).unwrap().set_text("$450");
429        tbl.cell(3, 0).unwrap().set_text("Software");
430        tbl.cell(3, 1).unwrap().set_text("IDE License");
431        tbl.cell(3, 2).unwrap().set_text("$200/yr");
432    }
433
434    doc.add_paragraph("");
435
436    doc.add_paragraph("5.4 Nested Table").style("Heading2");
437    {
438        let mut tbl = doc
439            .add_table(2, 2)
440            .borders(BorderStyle::Single, 6, "2E75B6");
441        tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
442        tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
443        tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
444        {
445            let mut cell = tbl.cell(1, 1).unwrap();
446            cell.set_text("Nested table below:");
447            let mut nested = cell
448                .add_table(2, 2)
449                .borders(BorderStyle::Single, 2, "FF6600");
450            nested.cell(0, 0).unwrap().set_text("A");
451            nested.cell(0, 1).unwrap().set_text("B");
452            nested.cell(1, 0).unwrap().set_text("C");
453            nested.cell(1, 1).unwrap().set_text("D");
454        }
455    }
456
457    doc.add_paragraph("");
458
459    doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
460        .style("Heading2");
461    {
462        let mut tbl = doc
463            .add_table(2, 3)
464            .borders(BorderStyle::Single, 4, "666666");
465        tbl.row(0).unwrap().height(Length::pt(50.0)).header();
466        tbl.cell(0, 0).unwrap().set_text("Top");
467        tbl.cell(0, 0)
468            .unwrap()
469            .vertical_alignment(VerticalAlignment::Top)
470            .shading("FFF2CC");
471        tbl.cell(0, 1).unwrap().set_text("Center");
472        tbl.cell(0, 1)
473            .unwrap()
474            .vertical_alignment(VerticalAlignment::Center)
475            .shading("D9E2F3");
476        tbl.cell(0, 2).unwrap().set_text("Bottom");
477        tbl.cell(0, 2)
478            .unwrap()
479            .vertical_alignment(VerticalAlignment::Bottom)
480            .shading("E2EFDA");
481        tbl.row(1).unwrap().cant_split();
482        tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
483        tbl.cell(1, 0).unwrap().no_wrap();
484        tbl.cell(1, 1).unwrap().set_text("Fixed width");
485        tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
486        tbl.cell(1, 2).unwrap().set_text("Normal");
487    }
488
489    // ── SECTION 6: IMAGES ──
490    doc.add_paragraph("").page_break_before(true);
491    doc.add_paragraph("6. Images").style("Heading1");
492
493    doc.add_paragraph("6.1 Inline Image").style("Heading2");
494    doc.add_paragraph("A blue gradient image below:");
495    let img = create_sample_png(200, 50, [0, 80, 200]);
496    doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
497
498    doc.add_paragraph("");
499    doc.add_paragraph("6.2 Header Image").style("Heading2");
500    let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
501    doc.set_header_image(
502        &hdr_img,
503        "header_logo.png",
504        Length::inches(2.0),
505        Length::inches(0.2),
506    );
507    doc.add_paragraph("The header now contains an inline image (check the top of this page).");
508
509    doc.add_paragraph("");
510    doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
511
512    // ── SECTION 7: CONTENT MANIPULATION ──
513    doc.add_paragraph("").page_break_before(true);
514    doc.add_paragraph("7. Content Manipulation")
515        .style("Heading1");
516
517    doc.add_paragraph("7.1 Placeholder Replacement")
518        .style("Heading2");
519    doc.add_paragraph("Customer: {{customer}}");
520    doc.add_paragraph("Date: {{date}}");
521    doc.add_paragraph("Reference: {{ref_number}}");
522    {
523        let mut tbl = doc
524            .add_table(2, 2)
525            .borders(BorderStyle::Single, 4, "000000");
526        tbl.cell(0, 0).unwrap().set_text("Project");
527        tbl.cell(0, 0).unwrap().shading("D6E4F0");
528        tbl.cell(0, 1).unwrap().set_text("{{project}}");
529        tbl.cell(1, 0).unwrap().set_text("Status");
530        tbl.cell(1, 0).unwrap().shading("D6E4F0");
531        tbl.cell(1, 1).unwrap().set_text("{{status}}");
532    }
533
534    let mut replacements = HashMap::new();
535    replacements.insert("{{customer}}", "Tensorbee Inc.");
536    replacements.insert("{{date}}", "February 22, 2026");
537    replacements.insert("{{ref_number}}", "TB-2026-001");
538    replacements.insert("{{project}}", "Infrastructure Upgrade");
539    replacements.insert("{{status}}", "In Progress");
540    let n = doc.replace_all(&replacements);
541    doc.add_paragraph(&format!("({n} placeholders replaced above)"));
542
543    doc.add_paragraph("");
544
545    doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
546    doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
547    let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
548    doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
549
550    doc.add_paragraph("");
551
552    doc.add_paragraph("7.3 Content Insertion").style("Heading2");
553    doc.add_paragraph("Section A: First content.");
554    doc.add_paragraph("Section C: Third content.");
555    if let Some(idx) = doc.find_content_index("Section C") {
556        doc.insert_paragraph(
557            idx,
558            "Section B: Inserted between A and C via find_content_index().",
559        );
560    }
561
562    // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
563    doc.add_paragraph("").section_break(SectionBreak::NextPage);
564    doc.add_paragraph("8. Section Breaks & Orientation")
565        .style("Heading1");
566    doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
567
568    {
569        let mut tbl = doc
570            .add_table(3, 7)
571            .borders(BorderStyle::Single, 4, "2E75B6");
572        let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
573        for (c, h) in headers.iter().enumerate() {
574            tbl.cell(0, c).unwrap().set_text(h);
575            tbl.cell(0, c).unwrap().shading("2E75B6");
576        }
577        let data = [
578            [
579                "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
580            ],
581            [
582                "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
583            ],
584        ];
585        for (r, row) in data.iter().enumerate() {
586            for (c, v) in row.iter().enumerate() {
587                tbl.cell(r + 1, c).unwrap().set_text(v);
588            }
589        }
590    }
591
592    doc.add_paragraph("")
593        .section_break(SectionBreak::NextPage)
594        .section_landscape();
595
596    // ── SECTION 9: CUSTOM STYLES ──
597    doc.add_paragraph("9. Custom Styles").style("Heading1");
598    doc.add_style(
599        StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
600            .based_on("Normal")
601            .paragraph_properties(rdocx_oxml::properties::CT_PPr {
602                shading: Some(rdocx_oxml::properties::CT_Shd {
603                    val: "clear".to_string(),
604                    color: None,
605                    fill: Some("FFF2CC".to_string()),
606                }),
607                ..Default::default()
608            })
609            .run_properties(rdocx_oxml::properties::CT_RPr {
610                bold: Some(true),
611                color: Some("C45911".to_string()),
612                ..Default::default()
613            }),
614    );
615    doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
616        .style("CustomHighlight");
617
618    doc.add_paragraph("");
619
620    // ── SECTION 10: DOCUMENT INTELLIGENCE ──
621    doc.add_paragraph("10. Document Intelligence API")
622        .style("Heading1");
623
624    let wc = doc.word_count();
625    let hc = doc.headings().len();
626    let ic = doc.images().len();
627    let lc = doc.links().len();
628    doc.add_paragraph(&format!("Word count: {wc}"));
629    doc.add_paragraph(&format!("Heading count: {hc}"));
630    doc.add_paragraph(&format!("Image count: {ic}"));
631    doc.add_paragraph(&format!("Link count: {lc}"));
632
633    let outline = doc.document_outline();
634    doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
635
636    let issues = doc.audit_accessibility();
637    doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
638    for issue in &issues {
639        doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
640    }
641
642    // ── SECTION 11: DOCUMENT MERGING ──
643    doc.add_paragraph("");
644    doc.add_paragraph("11. Document Merging").style("Heading1");
645    {
646        let mut other = Document::new();
647        other.add_paragraph("This paragraph was merged from another document using append().");
648        doc.append(&other);
649    }
650
651    doc.add_paragraph("");
652
653    // ── SUMMARY ──
654    doc.add_paragraph("Summary of Demonstrated Features")
655        .style("Heading1");
656    let features = [
657        "Page setup: size, margins, header/footer distance, gutter",
658        "Document metadata: title, author, subject, keywords",
659        "Headers and footers: text, images, different first page",
660        "Background images (full-page behind text)",
661        "Table of Contents generation with bookmarks",
662        "Text formatting: bold, italic, underline styles, strike, color, size, font",
663        "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
664        "Paragraph formatting: alignment, borders, shading, spacing, indentation",
665        "Pagination controls: keep-with-next, keep-together, widow control",
666        "Bullet and numbered lists with nesting",
667        "Tab stops with dot/underscore/hyphen leaders",
668        "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
669        "Row properties: header rows, exact height, cant-split",
670        "Cell properties: width, no-wrap, vertical alignment",
671        "Inline images and header images",
672        "Placeholder replacement (single and batch)",
673        "Regex-based find and replace",
674        "Content insertion at specific positions",
675        "Section breaks with mixed portrait/landscape",
676        "Custom paragraph and character styles",
677        "Document intelligence: word count, headings, outline, images, links",
678        "Accessibility audit",
679        "Document merging (append)",
680        "Export: DOCX, PDF, HTML, Markdown",
681    ];
682    for f in &features {
683        doc.add_bullet_list_item(f, 0);
684    }
685    doc.add_paragraph("");
686    doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
687        .alignment(Alignment::Center)
688        .shading("E2EFDA")
689        .border_all(BorderStyle::Single, 2, "00AA00");
690
691    doc
692}
693
694// =============================================================================
695// 2. PROPOSAL DOCUMENT — Deep navy + gold scheme
696// =============================================================================
697fn generate_proposal(_samples_dir: &Path) -> Document {
698    let mut doc = Document::new();
699
700    // Colors: Navy #1B2A4A, Gold #C5922E, Light #F4F1EB
701    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
702    doc.set_margins(
703        Length::inches(1.0),
704        Length::inches(1.0),
705        Length::inches(1.0),
706        Length::inches(1.0),
707    );
708    doc.set_title("AI Platform Modernization Proposal");
709    doc.set_author("Walter White");
710    doc.set_subject("Technology Proposal");
711    doc.set_keywords("proposal, AI, modernization, Tensorbee");
712
713    // Banner header
714    let logo_img = create_logo_png(220, 48);
715    let banner = build_header_banner_xml(
716        "rId1",
717        &BannerOpts {
718            bg_color: "1B2A4A",
719            banner_width: 7772400,
720            banner_height: 914400,
721            logo_width: 2011680,
722            logo_height: 438912,
723            logo_x_offset: 295125,
724            logo_y_offset: 237744,
725        },
726    );
727    doc.set_raw_header_with_images(
728        banner,
729        &[("rId1", &logo_img, "logo.png")],
730        rdocx_oxml::header_footer::HdrFtrType::Default,
731    );
732    doc.set_different_first_page(true);
733    let cover_banner = build_header_banner_xml(
734        "rId1",
735        &BannerOpts {
736            bg_color: "C5922E",
737            banner_width: 7772400,
738            banner_height: 914400,
739            logo_width: 2011680,
740            logo_height: 438912,
741            logo_x_offset: 295125,
742            logo_y_offset: 237744,
743        },
744    );
745    doc.set_raw_header_with_images(
746        cover_banner,
747        &[("rId1", &logo_img, "logo.png")],
748        rdocx_oxml::header_footer::HdrFtrType::First,
749    );
750    doc.set_margins(
751        Length::twips(2292),
752        Length::twips(1440),
753        Length::twips(1440),
754        Length::twips(1440),
755    );
756    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
757    doc.set_footer("Tensorbee — Confidential");
758
759    // Custom styles
760    doc.add_style(
761        StyleBuilder::paragraph("ProposalTitle", "Proposal Title")
762            .based_on("Normal")
763            .run_properties(rdocx_oxml::properties::CT_RPr {
764                bold: Some(true),
765                font_ascii: Some("Georgia".to_string()),
766                font_hansi: Some("Georgia".to_string()),
767                sz: Some(rdocx_oxml::HalfPoint(56)), // half-points → 28pt
768                color: Some("1B2A4A".to_string()),
769                ..Default::default()
770            }),
771    );
772
773    // ── Cover Page ──
774    for _ in 0..4 {
775        doc.add_paragraph("");
776    }
777    doc.add_paragraph("AI Platform Modernization")
778        .style("ProposalTitle")
779        .alignment(Alignment::Center);
780    doc.add_paragraph("")
781        .alignment(Alignment::Center)
782        .border_bottom(BorderStyle::Single, 8, "C5922E");
783    {
784        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
785        p.add_run("Prepared for Global Financial Corp.")
786            .size(14.0)
787            .color("666666")
788            .italic(true);
789    }
790    {
791        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
792        p.add_run("Prepared by: Walter White, CEO — Tensorbee")
793            .size(12.0)
794            .color("888888");
795    }
796    {
797        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
798        p.add_run("February 22, 2026").size(12.0).color("888888");
799    }
800
801    // ── TOC ──
802    doc.add_paragraph("").page_break_before(true);
803    doc.add_paragraph("Table of Contents").style("Heading1");
804    // We'll add a manual-style TOC since the insert_toc goes at a specific position
805    doc.insert_toc(doc.content_count(), 2);
806
807    // ── Executive Summary ──
808    doc.add_paragraph("").page_break_before(true);
809    doc.add_paragraph("Executive Summary").style("Heading1");
810    doc.add_paragraph(
811        "Tensorbee proposes a comprehensive modernization of Global Financial Corp's AI platform, \
812         transitioning from legacy batch-processing systems to a real-time inference architecture. \
813         This transformation will reduce model deployment time from weeks to hours, improve \
814         prediction accuracy by an estimated 23%, and deliver $4.2M in annual operational savings.",
815    )
816    .first_line_indent(Length::inches(0.3));
817    doc.add_paragraph(
818        "The project spans three phases over 18 months, with a total investment of $2.8M. \
819         Tensorbee brings deep expertise in ML infrastructure, having successfully completed \
820         similar transformations for three Fortune 500 financial institutions.",
821    )
822    .first_line_indent(Length::inches(0.3));
823
824    // Key metrics table
825    doc.add_paragraph("");
826    {
827        let mut tbl = doc
828            .add_table(5, 2)
829            .borders(BorderStyle::Single, 4, "1B2A4A")
830            .width_pct(80.0);
831        tbl.cell(0, 0).unwrap().set_text("Key Metric");
832        tbl.cell(0, 0).unwrap().shading("1B2A4A");
833        tbl.cell(0, 1).unwrap().set_text("Value");
834        tbl.cell(0, 1).unwrap().shading("1B2A4A");
835        let rows = [
836            ("Total Investment", "$2.8M"),
837            ("Annual Savings", "$4.2M"),
838            ("ROI (Year 1)", "150%"),
839            ("Timeline", "18 months"),
840        ];
841        for (i, (k, v)) in rows.iter().enumerate() {
842            tbl.cell(i + 1, 0).unwrap().set_text(k);
843            if i % 2 == 0 {
844                tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
845            }
846            tbl.cell(i + 1, 1).unwrap().set_text(v);
847            if i % 2 == 0 {
848                tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
849            }
850        }
851    }
852
853    // ── Problem Statement ──
854    doc.add_paragraph("").page_break_before(true);
855    doc.add_paragraph("Problem Statement").style("Heading1");
856    doc.add_paragraph(
857        "Global Financial Corp's current AI infrastructure faces three critical challenges:",
858    );
859    doc.add_numbered_list_item("Deployment Latency — New models take 3-4 weeks to deploy to production, compared to the industry benchmark of 2-3 days.", 0);
860    doc.add_numbered_list_item("Scalability Constraints — The batch processing architecture cannot handle real-time inference demands during peak trading hours.", 0);
861    doc.add_numbered_list_item("Technical Debt — Legacy Python 2.7 codebases and manual deployment scripts create reliability risks and slow iteration speed.", 0);
862
863    // ── Proposed Solution ──
864    doc.add_paragraph("").page_break_before(true);
865    doc.add_paragraph("Proposed Solution").style("Heading1");
866
867    doc.add_paragraph("Phase 1: Foundation (Months 1-6)")
868        .style("Heading2");
869    doc.add_bullet_list_item("Deploy Kubernetes-based ML serving infrastructure", 0);
870    doc.add_bullet_list_item("Implement CI/CD pipeline for model deployment", 0);
871    doc.add_bullet_list_item("Migrate top 5 production models to new platform", 0);
872
873    doc.add_paragraph("Phase 2: Expansion (Months 7-12)")
874        .style("Heading2");
875    doc.add_bullet_list_item(
876        "Real-time feature engineering pipeline (Apache Kafka + Flink)",
877        0,
878    );
879    doc.add_bullet_list_item("A/B testing framework for model variants", 0);
880    doc.add_bullet_list_item("Automated model monitoring and drift detection", 0);
881
882    doc.add_paragraph("Phase 3: Optimization (Months 13-18)")
883        .style("Heading2");
884    doc.add_bullet_list_item("GPU inference optimization (TensorRT/ONNX Runtime)", 0);
885    doc.add_bullet_list_item("Multi-region deployment for latency reduction", 0);
886    doc.add_bullet_list_item(
887        "Self-service model deployment portal for data science teams",
888        0,
889    );
890
891    // ── Budget ──
892    doc.add_paragraph("Budget Breakdown").style("Heading1");
893    {
894        let mut tbl = doc
895            .add_table(6, 3)
896            .borders(BorderStyle::Single, 4, "1B2A4A")
897            .width_pct(100.0);
898        tbl.cell(0, 0).unwrap().set_text("Category");
899        tbl.cell(0, 0).unwrap().shading("1B2A4A");
900        tbl.cell(0, 1).unwrap().set_text("Cost");
901        tbl.cell(0, 1).unwrap().shading("1B2A4A");
902        tbl.cell(0, 2).unwrap().set_text("Notes");
903        tbl.cell(0, 2).unwrap().shading("1B2A4A");
904        let items = [
905            (
906                "Infrastructure",
907                "$450,000",
908                "Cloud compute + GPU instances",
909            ),
910            ("Engineering Services", "$1,600,000", "12 FTE x 18 months"),
911            (
912                "Software Licenses",
913                "$350,000",
914                "Kafka, monitoring, CI/CD tools",
915            ),
916            (
917                "Training & Enablement",
918                "$200,000",
919                "Team training, documentation",
920            ),
921            ("Contingency (10%)", "$200,000", "Risk buffer"),
922        ];
923        for (i, (cat, cost, note)) in items.iter().enumerate() {
924            tbl.cell(i + 1, 0).unwrap().set_text(cat);
925            tbl.cell(i + 1, 1).unwrap().set_text(cost);
926            tbl.cell(i + 1, 2).unwrap().set_text(note);
927            if i % 2 == 0 {
928                tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
929                tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
930                tbl.cell(i + 1, 2).unwrap().shading("F4F1EB");
931            }
932        }
933    }
934
935    // ── Closing ──
936    doc.add_paragraph("");
937    doc.add_paragraph("Next Steps").style("Heading1");
938    doc.add_numbered_list_item(
939        "Schedule technical deep-dive with GFC engineering team (Week 1)",
940        0,
941    );
942    doc.add_numbered_list_item("Finalize scope and sign SOW (Week 2-3)", 0);
943    doc.add_numbered_list_item("Kick off Phase 1 with joint planning session (Week 4)", 0);
944
945    doc.add_paragraph("");
946    {
947        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
948        p.add_run("Walter White")
949            .bold(true)
950            .size(14.0)
951            .color("1B2A4A");
952    }
953    {
954        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
955        p.add_run("CEO, Tensorbee | walter@tensorbee.com")
956            .size(10.0)
957            .color("888888");
958    }
959
960    doc
961}
962
963// =============================================================================
964// 3. QUOTE / BILL OF MATERIALS — Teal + orange scheme
965// =============================================================================
966fn generate_quote(_samples_dir: &Path) -> Document {
967    let mut doc = Document::new();
968
969    // Colors: Teal #008B8B, Dark #1A3C3C, Orange #E07020, Light #F0F8F8
970    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
971    doc.set_margins(
972        Length::inches(0.75),
973        Length::inches(0.75),
974        Length::inches(0.75),
975        Length::inches(0.75),
976    );
977    doc.set_title("Quotation QT-2026-0147");
978    doc.set_author("Walter White");
979    doc.set_keywords("quote, BOM, Tensorbee");
980
981    doc.set_header("Tensorbee — Quotation");
982    doc.set_footer("QT-2026-0147 | Page");
983
984    // ── Header Block ──
985    {
986        let mut p = doc.add_paragraph("").alignment(Alignment::Left);
987        p.add_run("TENSORBEE")
988            .bold(true)
989            .size(28.0)
990            .color("008B8B")
991            .font("Helvetica");
992    }
993    doc.add_paragraph("123 Innovation Drive, Suite 400")
994        .alignment(Alignment::Left);
995    doc.add_paragraph("San Francisco, CA 94105 | +1 (415) 555-0199")
996        .alignment(Alignment::Left);
997    doc.add_paragraph("")
998        .border_bottom(BorderStyle::Single, 8, "008B8B");
999
1000    // Quote meta
1001    doc.add_paragraph("");
1002    {
1003        let mut tbl = doc.add_table(4, 4).width_pct(100.0);
1004        tbl.cell(0, 0).unwrap().set_text("QUOTATION");
1005        tbl.cell(0, 0).unwrap().shading("008B8B").grid_span(2);
1006        tbl.cell(0, 2).unwrap().set_text("DATE");
1007        tbl.cell(0, 2).unwrap().shading("008B8B");
1008        tbl.cell(0, 3).unwrap().set_text("VALID UNTIL");
1009        tbl.cell(0, 3).unwrap().shading("008B8B");
1010        tbl.cell(1, 0).unwrap().set_text("Quote #:");
1011        tbl.cell(1, 1).unwrap().set_text("QT-2026-0147");
1012        tbl.cell(1, 2).unwrap().set_text("Feb 22, 2026");
1013        tbl.cell(1, 3).unwrap().set_text("Mar 22, 2026");
1014        tbl.cell(2, 0).unwrap().set_text("Prepared by:");
1015        tbl.cell(2, 1).unwrap().set_text("Walter White");
1016        tbl.cell(2, 2).unwrap().set_text("Payment:");
1017        tbl.cell(2, 3).unwrap().set_text("Net 30");
1018        tbl.cell(3, 0).unwrap().set_text("Customer:");
1019        tbl.cell(3, 1).unwrap().set_text("Meridian Dynamics LLC");
1020        tbl.cell(3, 2).unwrap().set_text("Currency:");
1021        tbl.cell(3, 3).unwrap().set_text("USD");
1022    }
1023
1024    doc.add_paragraph("");
1025
1026    // ── Bill of Materials ──
1027    {
1028        let mut p = doc.add_paragraph("");
1029        p.add_run("Bill of Materials")
1030            .bold(true)
1031            .size(16.0)
1032            .color("1A3C3C");
1033    }
1034    doc.add_paragraph("");
1035
1036    {
1037        let mut tbl = doc
1038            .add_table(10, 6)
1039            .borders(BorderStyle::Single, 2, "008B8B")
1040            .width_pct(100.0)
1041            .layout_fixed();
1042        // Headers
1043        let hdrs = [
1044            "#",
1045            "Part Number",
1046            "Description",
1047            "Qty",
1048            "Unit Price",
1049            "Total",
1050        ];
1051        for (c, h) in hdrs.iter().enumerate() {
1052            tbl.cell(0, c).unwrap().set_text(h);
1053            tbl.cell(0, c).unwrap().shading("008B8B");
1054        }
1055
1056        let items: Vec<(&str, &str, &str, &str, &str)> = vec![
1057            (
1058                "TB-GPU-A100",
1059                "NVIDIA A100 80GB GPU",
1060                "4",
1061                "$12,500.00",
1062                "$50,000.00",
1063            ),
1064            (
1065                "TB-SRV-R750",
1066                "Dell R750xa Server Chassis",
1067                "2",
1068                "$8,200.00",
1069                "$16,400.00",
1070            ),
1071            (
1072                "TB-RAM-256G",
1073                "256GB DDR5 ECC Memory Module",
1074                "8",
1075                "$890.00",
1076                "$7,120.00",
1077            ),
1078            (
1079                "TB-SSD-3840",
1080                "3.84TB NVMe U.2 SSD",
1081                "8",
1082                "$1,150.00",
1083                "$9,200.00",
1084            ),
1085            (
1086                "TB-NET-CX7",
1087                "ConnectX-7 200GbE NIC",
1088                "4",
1089                "$1,800.00",
1090                "$7,200.00",
1091            ),
1092            (
1093                "TB-SW-48P",
1094                "48-Port 100GbE Switch",
1095                "1",
1096                "$22,000.00",
1097                "$22,000.00",
1098            ),
1099            (
1100                "TB-CAB-RACK",
1101                "42U Server Rack + PDU",
1102                "1",
1103                "$4,500.00",
1104                "$4,500.00",
1105            ),
1106            (
1107                "TB-SVC-INST",
1108                "Installation & Configuration",
1109                "1",
1110                "$8,500.00",
1111                "$8,500.00",
1112            ),
1113            (
1114                "TB-SVC-SUPP",
1115                "3-Year Premium Support",
1116                "1",
1117                "$15,000.00",
1118                "$15,000.00",
1119            ),
1120        ];
1121
1122        for (i, (pn, desc, qty, unit, total)) in items.iter().enumerate() {
1123            let row = i + 1;
1124            tbl.cell(row, 0).unwrap().set_text(&format!("{}", i + 1));
1125            tbl.cell(row, 1).unwrap().set_text(pn);
1126            tbl.cell(row, 2).unwrap().set_text(desc);
1127            tbl.cell(row, 3).unwrap().set_text(qty);
1128            tbl.cell(row, 4).unwrap().set_text(unit);
1129            tbl.cell(row, 5).unwrap().set_text(total);
1130            if i % 2 == 0 {
1131                for c in 0..6 {
1132                    tbl.cell(row, c).unwrap().shading("F0F8F8");
1133                }
1134            }
1135        }
1136    }
1137
1138    doc.add_paragraph("");
1139
1140    // ── Totals ──
1141    {
1142        let mut tbl = doc
1143            .add_table(4, 2)
1144            .borders(BorderStyle::Single, 2, "008B8B")
1145            .width(Length::inches(3.5))
1146            .alignment(Alignment::Right);
1147        tbl.cell(0, 0).unwrap().set_text("Subtotal");
1148        tbl.cell(0, 1).unwrap().set_text("$139,920.00");
1149        tbl.cell(1, 0).unwrap().set_text("Shipping & Handling");
1150        tbl.cell(1, 1).unwrap().set_text("$2,500.00");
1151        tbl.cell(2, 0).unwrap().set_text("Tax (8.625%)");
1152        tbl.cell(2, 1).unwrap().set_text("$12,068.10");
1153        tbl.cell(3, 0).unwrap().set_text("TOTAL");
1154        tbl.cell(3, 0).unwrap().shading("E07020");
1155        tbl.cell(3, 1).unwrap().set_text("$154,488.10");
1156        tbl.cell(3, 1).unwrap().shading("E07020");
1157    }
1158
1159    doc.add_paragraph("");
1160
1161    // ── Terms & Conditions ──
1162    {
1163        let mut p = doc.add_paragraph("");
1164        p.add_run("Terms & Conditions")
1165            .bold(true)
1166            .size(14.0)
1167            .color("1A3C3C");
1168    }
1169    doc.add_numbered_list_item(
1170        "This quotation is valid for 30 calendar days from the date of issue.",
1171        0,
1172    );
1173    doc.add_numbered_list_item(
1174        "All prices are in USD and exclusive of applicable taxes unless stated.",
1175        0,
1176    );
1177    doc.add_numbered_list_item("Standard lead time is 4-6 weeks from PO receipt.", 0);
1178    doc.add_numbered_list_item(
1179        "Payment terms: Net 30 from invoice date. 2% discount for payment within 10 days.",
1180        0,
1181    );
1182    doc.add_numbered_list_item(
1183        "Warranty: 3-year manufacturer warranty on all hardware components.",
1184        0,
1185    );
1186    doc.add_numbered_list_item(
1187        "Returns subject to 15% restocking fee if initiated after 14 days.",
1188        0,
1189    );
1190
1191    doc.add_paragraph("");
1192    doc.add_paragraph("")
1193        .border_bottom(BorderStyle::Single, 4, "008B8B");
1194    doc.add_paragraph("");
1195    {
1196        let mut p = doc.add_paragraph("");
1197        p.add_run("Accepted by: ").bold(true);
1198        p.add_run("___________________________ Date: ___________");
1199    }
1200    {
1201        let mut p = doc.add_paragraph("");
1202        p.add_run("Print Name: ").bold(true);
1203        p.add_run("___________________________ Title: ___________");
1204    }
1205
1206    doc
1207}
1208
1209// =============================================================================
1210// 4. INVOICE — Crimson + charcoal scheme
1211// =============================================================================
1212fn generate_invoice(_samples_dir: &Path) -> Document {
1213    let mut doc = Document::new();
1214
1215    // Colors: Crimson #B22222, Charcoal #333333, Light #FAF0F0
1216    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1217    doc.set_margins(
1218        Length::inches(0.75),
1219        Length::inches(0.75),
1220        Length::inches(0.75),
1221        Length::inches(0.75),
1222    );
1223    doc.set_title("Invoice INV-2026-0392");
1224    doc.set_author("Walter White");
1225    doc.set_keywords("invoice, Tensorbee");
1226
1227    doc.set_footer("Tensorbee — Thank you for your business!");
1228
1229    // ── Company & Invoice Header ──
1230    {
1231        let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1232        // Company name left, INVOICE right
1233        {
1234            let mut cell = tbl.cell(0, 0).unwrap();
1235            let mut p = cell.add_paragraph("");
1236            p.add_run("TENSORBEE")
1237                .bold(true)
1238                .size(32.0)
1239                .color("B22222")
1240                .font("Helvetica");
1241        }
1242        {
1243            let mut cell = tbl.cell(0, 1).unwrap();
1244            let mut p = cell.add_paragraph("");
1245            p.add_run("INVOICE").bold(true).size(32.0).color("333333");
1246        }
1247        tbl.cell(1, 0)
1248            .unwrap()
1249            .set_text("123 Innovation Drive, Suite 400");
1250        tbl.cell(1, 1).unwrap().set_text("Invoice #: INV-2026-0392");
1251        tbl.cell(2, 0).unwrap().set_text("San Francisco, CA 94105");
1252        tbl.cell(2, 1).unwrap().set_text("Date: February 22, 2026");
1253        tbl.cell(3, 0).unwrap().set_text("walter@tensorbee.com");
1254        tbl.cell(3, 1).unwrap().set_text("Due Date: March 24, 2026");
1255    }
1256
1257    doc.add_paragraph("")
1258        .border_bottom(BorderStyle::Thick, 8, "B22222");
1259    doc.add_paragraph("");
1260
1261    // ── Bill To / Ship To ──
1262    {
1263        let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1264        tbl.cell(0, 0).unwrap().set_text("BILL TO");
1265        tbl.cell(0, 0).unwrap().shading("B22222");
1266        tbl.cell(0, 1).unwrap().set_text("SHIP TO");
1267        tbl.cell(0, 1).unwrap().shading("B22222");
1268        tbl.cell(1, 0).unwrap().set_text("Meridian Dynamics LLC");
1269        tbl.cell(1, 1).unwrap().set_text("Meridian Dynamics LLC");
1270        tbl.cell(2, 0).unwrap().set_text("456 Enterprise Blvd");
1271        tbl.cell(2, 1).unwrap().set_text("Attn: Server Room B");
1272        tbl.cell(3, 0).unwrap().set_text("Austin, TX 78701");
1273        tbl.cell(3, 1)
1274            .unwrap()
1275            .set_text("456 Enterprise Blvd, Austin, TX 78701");
1276    }
1277
1278    doc.add_paragraph("");
1279
1280    // ── Line Items ──
1281    {
1282        let mut tbl = doc
1283            .add_table(8, 5)
1284            .borders(BorderStyle::Single, 2, "B22222")
1285            .width_pct(100.0);
1286        let hdrs = ["Description", "Qty", "Unit Price", "Tax", "Amount"];
1287        for (c, h) in hdrs.iter().enumerate() {
1288            tbl.cell(0, c).unwrap().set_text(h);
1289            tbl.cell(0, c).unwrap().shading("B22222");
1290        }
1291        let items = [
1292            (
1293                "ML Infrastructure Setup — Phase 1",
1294                "1",
1295                "$45,000.00",
1296                "$3,881.25",
1297                "$48,881.25",
1298            ),
1299            (
1300                "Data Pipeline Development (160 hrs)",
1301                "160",
1302                "$225.00",
1303                "$3,105.00",
1304                "$39,105.00",
1305            ),
1306            (
1307                "GPU Cluster Configuration",
1308                "1",
1309                "$12,000.00",
1310                "$1,035.00",
1311                "$13,035.00",
1312            ),
1313            (
1314                "API Gateway Implementation",
1315                "1",
1316                "$18,500.00",
1317                "$1,595.63",
1318                "$20,095.63",
1319            ),
1320            (
1321                "Load Testing & QA (80 hrs)",
1322                "80",
1323                "$195.00",
1324                "$1,345.50",
1325                "$16,945.50",
1326            ),
1327            (
1328                "Documentation & Training",
1329                "1",
1330                "$8,000.00",
1331                "$690.00",
1332                "$8,690.00",
1333            ),
1334            (
1335                "Project Management (3 months)",
1336                "3",
1337                "$6,500.00",
1338                "$1,679.63",
1339                "$21,179.63",
1340            ),
1341        ];
1342        for (i, (desc, qty, unit, tax, amt)) in items.iter().enumerate() {
1343            let r = i + 1;
1344            tbl.cell(r, 0).unwrap().set_text(desc);
1345            tbl.cell(r, 1).unwrap().set_text(qty);
1346            tbl.cell(r, 2).unwrap().set_text(unit);
1347            tbl.cell(r, 3).unwrap().set_text(tax);
1348            tbl.cell(r, 4).unwrap().set_text(amt);
1349            if i % 2 == 0 {
1350                for c in 0..5 {
1351                    tbl.cell(r, c).unwrap().shading("FAF0F0");
1352                }
1353            }
1354        }
1355    }
1356
1357    doc.add_paragraph("");
1358
1359    // ── Totals ──
1360    {
1361        let mut tbl = doc
1362            .add_table(4, 2)
1363            .borders(BorderStyle::Single, 2, "B22222")
1364            .width(Length::inches(3.0))
1365            .alignment(Alignment::Right);
1366        tbl.cell(0, 0).unwrap().set_text("Subtotal");
1367        tbl.cell(0, 1).unwrap().set_text("$154,600.00");
1368        tbl.cell(1, 0).unwrap().set_text("Tax (8.625%)");
1369        tbl.cell(1, 1).unwrap().set_text("$13,334.25");
1370        tbl.cell(2, 0).unwrap().set_text("Discount (5%)");
1371        tbl.cell(2, 1).unwrap().set_text("-$7,730.00");
1372        tbl.cell(3, 0).unwrap().set_text("AMOUNT DUE");
1373        tbl.cell(3, 0).unwrap().shading("B22222");
1374        tbl.cell(3, 1).unwrap().set_text("$160,204.25");
1375        tbl.cell(3, 1).unwrap().shading("B22222");
1376    }
1377
1378    doc.add_paragraph("");
1379    doc.add_paragraph("");
1380
1381    // ── Payment Details ──
1382    {
1383        let mut p = doc.add_paragraph("");
1384        p.add_run("Payment Details")
1385            .bold(true)
1386            .size(14.0)
1387            .color("333333");
1388    }
1389    doc.add_paragraph("Bank: Silicon Valley Bank")
1390        .indent_left(Length::inches(0.3));
1391    doc.add_paragraph("Account: Tensorbee Inc. — 0847-2953-1120")
1392        .indent_left(Length::inches(0.3));
1393    doc.add_paragraph("Routing: 121140399")
1394        .indent_left(Length::inches(0.3));
1395    doc.add_paragraph("Swift: SVBKUS6S")
1396        .indent_left(Length::inches(0.3));
1397
1398    doc.add_paragraph("");
1399
1400    doc.add_paragraph("Please include invoice number INV-2026-0392 in the payment reference.")
1401        .shading("FAF0F0")
1402        .border_all(BorderStyle::Single, 2, "B22222");
1403
1404    doc
1405}
1406
1407// =============================================================================
1408// 5. REPORT — Forest green + earth tones, with images & hierarchical sections
1409// =============================================================================
1410fn generate_report(_samples_dir: &Path) -> Document {
1411    let mut doc = Document::new();
1412
1413    // Colors: Forest #2D5016, Sage #6B8E23, Earth #8B7355, Cream #FFFAF0
1414    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1415    doc.set_margins(
1416        Length::inches(1.0),
1417        Length::inches(1.0),
1418        Length::inches(1.0),
1419        Length::inches(1.0),
1420    );
1421    doc.set_title("Q4 2025 Environmental Impact Report");
1422    doc.set_author("Walter White");
1423    doc.set_subject("Quarterly Environmental Report");
1424    doc.set_keywords("environment, sustainability, report, Tensorbee");
1425
1426    doc.set_different_first_page(true);
1427    doc.set_first_page_header("");
1428    doc.set_header("Tensorbee — Q4 2025 Environmental Impact Report");
1429    doc.set_footer("CONFIDENTIAL — Page");
1430
1431    // ── Cover ──
1432    let cover_bg = create_sample_png(612, 792, [20, 50, 15]);
1433    doc.add_background_image(&cover_bg, "report_cover.png");
1434
1435    for _ in 0..5 {
1436        doc.add_paragraph("");
1437    }
1438    {
1439        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1440        p.add_run("Q4 2025")
1441            .bold(true)
1442            .size(48.0)
1443            .color("FFFFFF")
1444            .font("Georgia");
1445    }
1446    {
1447        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1448        p.add_run("Environmental Impact Report")
1449            .size(24.0)
1450            .color("90EE90")
1451            .font("Georgia")
1452            .italic(true);
1453    }
1454    doc.add_paragraph("");
1455    {
1456        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1457        p.add_run("Tensorbee — Sustainability Division")
1458            .size(14.0)
1459            .color("C0C0C0");
1460    }
1461    {
1462        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1463        p.add_run("Prepared by Walter White, Chief Sustainability Officer")
1464            .size(11.0)
1465            .color("AAAAAA");
1466    }
1467
1468    // ── TOC ──
1469    doc.add_paragraph("").page_break_before(true);
1470    doc.insert_toc(doc.content_count(), 3);
1471
1472    // ── Section 1: Executive Overview ──
1473    doc.add_paragraph("").page_break_before(true);
1474    doc.add_paragraph("Executive Overview").style("Heading1");
1475    doc.add_paragraph(
1476        "This report presents Tensorbee's environmental performance for Q4 2025. Our \
1477         sustainability initiatives have yielded a 34% reduction in carbon emissions compared \
1478         to Q4 2024, exceeding our target of 25%. Key achievements include the transition to \
1479         100% renewable energy in our primary data centers and the launch of our carbon offset \
1480         marketplace.",
1481    )
1482    .first_line_indent(Length::inches(0.3));
1483
1484    // Image: performance chart
1485    let chart_img = create_chart_png(400, 200);
1486    doc.add_paragraph("");
1487    doc.add_picture(
1488        &chart_img,
1489        "performance_chart.png",
1490        Length::inches(5.0),
1491        Length::inches(2.5),
1492    )
1493    .alignment(Alignment::Center);
1494    {
1495        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1496        p.add_run("Figure 1: Quarterly Carbon Emissions (tonnes CO2e)")
1497            .italic(true)
1498            .size(9.0)
1499            .color("666666");
1500    }
1501
1502    // ── Section 2: Energy Consumption ──
1503    doc.add_paragraph("").page_break_before(true);
1504    doc.add_paragraph("Energy Consumption").style("Heading1");
1505
1506    doc.add_paragraph("Data Center Operations")
1507        .style("Heading2");
1508    doc.add_paragraph(
1509        "Our three primary data centers consumed a combined 4.2 GWh in Q4 2025, \
1510         a 12% reduction from Q3 2025 driven by improved cooling efficiency and \
1511         server consolidation.",
1512    );
1513
1514    // Data table
1515    {
1516        let mut tbl = doc
1517            .add_table(5, 4)
1518            .borders(BorderStyle::Single, 4, "2D5016")
1519            .width_pct(100.0);
1520        let hdrs = ["Data Center", "Capacity (MW)", "Usage (GWh)", "PUE"];
1521        for (c, h) in hdrs.iter().enumerate() {
1522            tbl.cell(0, c).unwrap().set_text(h);
1523            tbl.cell(0, c).unwrap().shading("2D5016");
1524        }
1525        let rows = [
1526            ("San Francisco (Primary)", "3.2", "1.8", "1.12"),
1527            ("Dublin (EU)", "2.1", "1.2", "1.18"),
1528            ("Singapore (APAC)", "1.8", "1.2", "1.24"),
1529            ("TOTAL", "7.1", "4.2", "1.17 avg"),
1530        ];
1531        for (i, (dc, cap, use_, pue)) in rows.iter().enumerate() {
1532            tbl.cell(i + 1, 0).unwrap().set_text(dc);
1533            tbl.cell(i + 1, 1).unwrap().set_text(cap);
1534            tbl.cell(i + 1, 2).unwrap().set_text(use_);
1535            tbl.cell(i + 1, 3).unwrap().set_text(pue);
1536            if i == 3 {
1537                for c in 0..4 {
1538                    tbl.cell(i + 1, c).unwrap().shading("E2EFDA");
1539                }
1540            }
1541        }
1542    }
1543
1544    doc.add_paragraph("");
1545
1546    doc.add_paragraph("Renewable Energy Mix").style("Heading2");
1547    doc.add_paragraph("Breakdown of energy sources across all facilities:");
1548
1549    doc.add_bullet_list_item("Solar PV: 42% (1.76 GWh)", 0);
1550    doc.add_bullet_list_item("Wind Power Purchase Agreements: 38% (1.60 GWh)", 0);
1551    doc.add_bullet_list_item("Hydroelectric: 12% (0.50 GWh)", 0);
1552    doc.add_bullet_list_item("Grid (non-renewable): 8% (0.34 GWh)", 0);
1553
1554    // ── Section 3: Water & Waste ──
1555    doc.add_paragraph("Water & Waste Management")
1556        .style("Heading1");
1557
1558    doc.add_paragraph("Water Usage").style("Heading2");
1559    doc.add_paragraph(
1560        "Total water consumption was 12.4 million gallons, a 15% reduction from Q3. \
1561         Our closed-loop cooling systems now recycle 78% of water used in cooling operations.",
1562    );
1563
1564    doc.add_paragraph("Waste Reduction").style("Heading2");
1565    doc.add_paragraph("E-waste management results for Q4:")
1566        .keep_with_next(true);
1567    {
1568        let mut tbl = doc
1569            .add_table(4, 3)
1570            .borders(BorderStyle::Single, 2, "6B8E23")
1571            .width_pct(80.0);
1572        tbl.cell(0, 0).unwrap().set_text("Category");
1573        tbl.cell(0, 0).unwrap().shading("6B8E23");
1574        tbl.cell(0, 1).unwrap().set_text("Weight (kg)");
1575        tbl.cell(0, 1).unwrap().shading("6B8E23");
1576        tbl.cell(0, 2).unwrap().set_text("Recycled %");
1577        tbl.cell(0, 2).unwrap().shading("6B8E23");
1578        let rows = [
1579            ("Server Hardware", "2,450", "94%"),
1580            ("Networking Equipment", "820", "91%"),
1581            ("Storage Media", "340", "99%"),
1582        ];
1583        for (i, (cat, wt, pct)) in rows.iter().enumerate() {
1584            tbl.cell(i + 1, 0).unwrap().set_text(cat);
1585            tbl.cell(i + 1, 1).unwrap().set_text(wt);
1586            tbl.cell(i + 1, 2).unwrap().set_text(pct);
1587        }
1588    }
1589
1590    // ── Section 4: Initiatives ──
1591    doc.add_paragraph("").page_break_before(true);
1592    doc.add_paragraph("Q1 2026 Initiatives").style("Heading1");
1593
1594    doc.add_paragraph("Planned Programs").style("Heading2");
1595    doc.add_numbered_list_item(
1596        "Deploy on-site battery storage at SF data center (2 MWh capacity)",
1597        0,
1598    );
1599    doc.add_numbered_list_item("Pilot immersion cooling in Dublin facility", 0);
1600    doc.add_numbered_list_item("Launch employee commute carbon offset program", 0);
1601    doc.add_numbered_list_item("Achieve ISO 14001 certification for Singapore facility", 0);
1602
1603    doc.add_paragraph("Investment Targets").style("Heading2");
1604    doc.add_paragraph("Sustainability CapEx allocation for FY2026:")
1605        .keep_with_next(true);
1606    {
1607        let mut tbl = doc
1608            .add_table(5, 2)
1609            .borders(BorderStyle::Single, 4, "2D5016");
1610        tbl.cell(0, 0).unwrap().set_text("Initiative");
1611        tbl.cell(0, 0).unwrap().shading("2D5016");
1612        tbl.cell(0, 1).unwrap().set_text("Budget");
1613        tbl.cell(0, 1).unwrap().shading("2D5016");
1614        let rows = [
1615            ("Battery Storage", "$1.2M"),
1616            ("Immersion Cooling Pilot", "$800K"),
1617            ("Solar Panel Expansion", "$2.1M"),
1618            ("Carbon Credits", "$500K"),
1619        ];
1620        for (i, (init, budget)) in rows.iter().enumerate() {
1621            tbl.cell(i + 1, 0).unwrap().set_text(init);
1622            tbl.cell(i + 1, 1).unwrap().set_text(budget);
1623            if i % 2 == 0 {
1624                tbl.cell(i + 1, 0).unwrap().shading("FFFAF0");
1625                tbl.cell(i + 1, 1).unwrap().shading("FFFAF0");
1626            }
1627        }
1628    }
1629
1630    // Image: sustainability roadmap
1631    let roadmap_img = create_sample_png(500, 100, [40, 80, 30]);
1632    doc.add_paragraph("");
1633    doc.add_picture(
1634        &roadmap_img,
1635        "roadmap.png",
1636        Length::inches(6.0),
1637        Length::inches(1.2),
1638    )
1639    .alignment(Alignment::Center);
1640    {
1641        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1642        p.add_run("Figure 2: 2026 Sustainability Roadmap")
1643            .italic(true)
1644            .size(9.0)
1645            .color("666666");
1646    }
1647
1648    doc.add_paragraph("");
1649    doc.add_paragraph(
1650        "For questions about this report, contact Walter White at sustainability@tensorbee.com.",
1651    )
1652    .shading("FFFAF0")
1653    .border_all(BorderStyle::Single, 2, "2D5016");
1654
1655    doc
1656}
1657
1658// =============================================================================
1659// 6. LETTER — Slate blue + silver scheme
1660// =============================================================================
1661fn generate_letter(_samples_dir: &Path) -> Document {
1662    let mut doc = Document::new();
1663
1664    // Colors: Slate #4A5568, Blue accent #3182CE
1665    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1666    doc.set_margins(
1667        Length::inches(1.25),
1668        Length::inches(1.25),
1669        Length::inches(1.25),
1670        Length::inches(1.25),
1671    );
1672    doc.set_title("Business Letter");
1673    doc.set_author("Walter White");
1674
1675    // Banner header using raw XML
1676    let logo_img = create_logo_png(220, 48);
1677    let banner = build_header_banner_xml(
1678        "rId1",
1679        &BannerOpts {
1680            bg_color: "4A5568",
1681            banner_width: 7772400,
1682            banner_height: 731520, // ~0.8"
1683            logo_width: 2011680,
1684            logo_height: 438912,
1685            logo_x_offset: 295125,
1686            logo_y_offset: 146304,
1687        },
1688    );
1689    doc.set_raw_header_with_images(
1690        banner,
1691        &[("rId1", &logo_img, "logo.png")],
1692        rdocx_oxml::header_footer::HdrFtrType::Default,
1693    );
1694    doc.set_margins(
1695        Length::twips(2000),
1696        Length::twips(1800),
1697        Length::twips(1440),
1698        Length::twips(1800),
1699    );
1700    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
1701
1702    // Footer with contact
1703    doc.set_footer(
1704        "Tensorbee | 123 Innovation Drive, Suite 400 | San Francisco, CA 94105 | tensorbee.com",
1705    );
1706
1707    // ── Sender Address ──
1708    doc.add_paragraph("Walter White");
1709    doc.add_paragraph("Chief Executive Officer");
1710    doc.add_paragraph("Tensorbee");
1711    doc.add_paragraph("123 Innovation Drive, Suite 400");
1712    doc.add_paragraph("San Francisco, CA 94105");
1713    doc.add_paragraph("");
1714    doc.add_paragraph("February 22, 2026");
1715
1716    doc.add_paragraph("");
1717
1718    // ── Recipient ──
1719    doc.add_paragraph("Ms. Sarah Chen");
1720    doc.add_paragraph("VP of Engineering");
1721    doc.add_paragraph("Meridian Dynamics LLC");
1722    doc.add_paragraph("456 Enterprise Boulevard");
1723    doc.add_paragraph("Austin, TX 78701");
1724
1725    doc.add_paragraph("");
1726
1727    // ── Subject ──
1728    {
1729        let mut p = doc.add_paragraph("");
1730        p.add_run("Re: Strategic Technology Partnership — Phase 2 Expansion")
1731            .bold(true);
1732    }
1733
1734    doc.add_paragraph("");
1735
1736    // ── Body ──
1737    doc.add_paragraph("Dear Ms. Chen,");
1738    doc.add_paragraph("");
1739
1740    doc.add_paragraph(
1741        "I am writing to express Tensorbee's enthusiasm for expanding our strategic technology \
1742         partnership with Meridian Dynamics. Following the successful completion of Phase 1, \
1743         which delivered a 40% improvement in your ML inference pipeline throughput, we believe \
1744         the foundation is firmly established for an ambitious Phase 2 engagement.",
1745    )
1746    .first_line_indent(Length::inches(0.5));
1747
1748    doc.add_paragraph(
1749        "During our recent executive review, your team highlighted three priority areas for \
1750         the next phase of collaboration:",
1751    )
1752    .first_line_indent(Length::inches(0.5));
1753
1754    doc.add_numbered_list_item(
1755        "Real-time anomaly detection for your financial transaction monitoring system, \
1756         targeting sub-100ms latency at 50,000 transactions per second.",
1757        0,
1758    );
1759    doc.add_numbered_list_item(
1760        "Federated learning infrastructure to enable model training across your distributed \
1761         data centers without centralizing sensitive financial data.",
1762        0,
1763    );
1764    doc.add_numbered_list_item(
1765        "MLOps automation to reduce your model deployment cycle from the current 5 days \
1766         to under 4 hours.",
1767        0,
1768    );
1769
1770    doc.add_paragraph(
1771        "Our engineering team has prepared a detailed technical proposal addressing each of \
1772         these areas. We have allocated a dedicated team of eight senior engineers, led by \
1773         our CTO, to ensure continuity with the Phase 1 team your organization has already \
1774         built a productive working relationship with.",
1775    )
1776    .first_line_indent(Length::inches(0.5));
1777
1778    doc.add_paragraph(
1779        "I would welcome the opportunity to present our Phase 2 proposal in person. My \
1780         assistant will reach out to coordinate a meeting at your convenience during the \
1781         first week of March.",
1782    )
1783    .first_line_indent(Length::inches(0.5));
1784
1785    doc.add_paragraph("");
1786
1787    doc.add_paragraph("Warm regards,");
1788    doc.add_paragraph("");
1789    doc.add_paragraph("");
1790
1791    // Signature line
1792    doc.add_paragraph("")
1793        .border_bottom(BorderStyle::Single, 4, "4A5568");
1794    {
1795        let mut p = doc.add_paragraph("");
1796        p.add_run("Walter White").bold(true).size(12.0);
1797    }
1798    {
1799        let mut p = doc.add_paragraph("");
1800        p.add_run("Chief Executive Officer, Tensorbee")
1801            .italic(true)
1802            .size(10.0)
1803            .color("666666");
1804    }
1805    {
1806        let mut p = doc.add_paragraph("");
1807        p.add_run("walter@tensorbee.com | +1 (415) 555-0199")
1808            .size(10.0)
1809            .color("888888");
1810    }
1811
1812    doc
1813}
Source

pub fn set_gutter(&mut self, gutter: Length)

Set the gutter margin.

Examples found in repository?
examples/generate_all_samples.rs (line 109)
97fn generate_feature_showcase(_samples_dir: &Path) -> Document {
98    let mut doc = Document::new();
99
100    // ── Page Setup & Metadata ──
101    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
102    doc.set_margins(
103        Length::inches(1.0),
104        Length::inches(1.0),
105        Length::inches(1.0),
106        Length::inches(1.0),
107    );
108    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
109    doc.set_gutter(Length::twips(0));
110    doc.set_title("rdocx Feature Showcase");
111    doc.set_author("rdocx Sample Generator");
112    doc.set_subject("Comprehensive feature demonstration");
113    doc.set_keywords("rdocx, docx, rust, sample, showcase");
114
115    // Headers & Footers with different first page
116    doc.set_different_first_page(true);
117    doc.set_first_page_header("rdocx");
118    doc.set_first_page_footer("Feature Showcase — Cover Page");
119    doc.set_header("rdocx Feature Showcase");
120    doc.set_footer("Generated by rdocx");
121
122    // ── COVER PAGE ──
123    let bg = create_sample_png(612, 792, [20, 45, 90]);
124    doc.add_background_image(&bg, "cover_bg.png");
125
126    for _ in 0..3 {
127        doc.add_paragraph("");
128    }
129    {
130        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
131        p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
132    }
133    {
134        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
135        p.add_run("Complete Feature Showcase")
136            .size(28.0)
137            .color("FFFFFF")
138            .italic(true);
139    }
140    doc.add_paragraph("");
141    {
142        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
143        p.add_run("Every feature of the rdocx Rust library")
144            .size(13.0)
145            .color("B0C4DE");
146    }
147    {
148        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
149        p.add_run("demonstrated in a single document.")
150            .size(13.0)
151            .color("B0C4DE");
152    }
153
154    // ── TABLE OF CONTENTS ──
155    doc.add_paragraph("").page_break_before(true);
156    doc.insert_toc(doc.content_count(), 3);
157
158    // ── SECTION 1: TEXT FORMATTING ──
159    doc.add_paragraph("").page_break_before(true);
160    doc.add_paragraph("1. Text Formatting").style("Heading1");
161
162    // Paragraph Alignment
163    doc.add_paragraph("1.1 Paragraph Alignment")
164        .style("Heading2");
165    doc.add_paragraph("Left-aligned paragraph (default).")
166        .alignment(Alignment::Left);
167    doc.add_paragraph("Center-aligned paragraph.")
168        .alignment(Alignment::Center);
169    doc.add_paragraph("Right-aligned paragraph.")
170        .alignment(Alignment::Right);
171    doc.add_paragraph(
172        "Justified paragraph — this text is long enough to demonstrate how justified alignment \
173         distributes extra space across word gaps so lines fill the full width of the text area.",
174    )
175    .alignment(Alignment::Justify);
176
177    doc.add_paragraph("");
178
179    // Run Formatting
180    doc.add_paragraph("1.2 Run Formatting").style("Heading2");
181    {
182        let mut p = doc.add_paragraph("");
183        p.add_run("Bold").bold(true);
184        p.add_run(" | ");
185        p.add_run("Italic").italic(true);
186        p.add_run(" | ");
187        p.add_run("Bold Italic").bold(true).italic(true);
188        p.add_run(" | ");
189        p.add_run("Underline").underline(true);
190        p.add_run(" | ");
191        p.add_run("Strikethrough").strike(true);
192        p.add_run(" | ");
193        p.add_run("Double Strike").double_strike(true);
194    }
195    {
196        let mut p = doc.add_paragraph("");
197        p.add_run("Red").color("FF0000");
198        p.add_run(" | ");
199        p.add_run("Blue").color("0000FF");
200        p.add_run(" | ");
201        p.add_run("Green").color("00AA00");
202        p.add_run(" | ");
203        p.add_run("Highlighted").highlight("FFFF00");
204    }
205    {
206        let mut p = doc.add_paragraph("");
207        p.add_run("8pt").size(8.0);
208        p.add_run(" | ");
209        p.add_run("11pt").size(11.0);
210        p.add_run(" | ");
211        p.add_run("16pt").size(16.0);
212        p.add_run(" | ");
213        p.add_run("24pt").size(24.0);
214    }
215    {
216        let mut p = doc.add_paragraph("");
217        p.add_run("Arial").font("Arial");
218        p.add_run(" | ");
219        p.add_run("Times New Roman").font("Times New Roman");
220        p.add_run(" | ");
221        p.add_run("Courier New").font("Courier New");
222    }
223    {
224        let mut p = doc.add_paragraph("");
225        p.add_run("H");
226        p.add_run("2").subscript();
227        p.add_run("O (subscript) | E=mc");
228        p.add_run("2").superscript();
229        p.add_run(" (superscript)");
230    }
231    {
232        let mut p = doc.add_paragraph("");
233        p.add_run("ALL CAPS").all_caps(true);
234        p.add_run(" | ");
235        p.add_run("Small Caps").small_caps(true);
236        p.add_run(" | ");
237        p.add_run("Expanded").character_spacing(Length::twips(40));
238        p.add_run(" | ");
239        p.add_run("Hidden text (not visible)").hidden(true);
240    }
241
242    doc.add_paragraph("");
243
244    // Underline Styles
245    doc.add_paragraph("1.3 Underline Styles").style("Heading2");
246    {
247        let mut p = doc.add_paragraph("");
248        p.add_run("Single ")
249            .underline_style(rdocx::UnderlineStyle::Single);
250        p.add_run("Double ")
251            .underline_style(rdocx::UnderlineStyle::Double);
252        p.add_run("Thick ")
253            .underline_style(rdocx::UnderlineStyle::Thick);
254        p.add_run("Dotted ")
255            .underline_style(rdocx::UnderlineStyle::Dotted);
256        p.add_run("Dash ")
257            .underline_style(rdocx::UnderlineStyle::Dash);
258        p.add_run("Wave ")
259            .underline_style(rdocx::UnderlineStyle::Wave);
260    }
261
262    // ── SECTION 2: PARAGRAPH FORMATTING ──
263    doc.add_paragraph("").page_break_before(true);
264    doc.add_paragraph("2. Paragraph Formatting")
265        .style("Heading1");
266
267    doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
268    doc.add_paragraph("Paragraph with light green shading")
269        .shading("E2EFDA");
270    doc.add_paragraph("Paragraph with bottom border")
271        .border_bottom(BorderStyle::Single, 6, "2E75B6");
272    doc.add_paragraph("Paragraph with all borders (red)")
273        .border_all(BorderStyle::Single, 4, "FF0000");
274
275    doc.add_paragraph("2.2 Indentation").style("Heading2");
276    doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
277        .indent_left(Length::inches(1.0))
278        .hanging_indent(Length::inches(0.5));
279    doc.add_paragraph("0.5-inch first-line indent on this paragraph")
280        .first_line_indent(Length::inches(0.5));
281    doc.add_paragraph("Both left and right indent (0.75 inches each)")
282        .indent_left(Length::inches(0.75))
283        .indent_right(Length::inches(0.75));
284
285    doc.add_paragraph("2.3 Spacing & Line Height")
286        .style("Heading2");
287    doc.add_paragraph("Extra space before (24pt) and after (12pt)")
288        .space_before(Length::pt(24.0))
289        .space_after(Length::pt(12.0));
290    doc.add_paragraph(
291        "Double line spacing paragraph. This text should have extra vertical space between \
292         lines to demonstrate line_spacing_multiple(2.0).",
293    )
294    .line_spacing_multiple(2.0);
295    doc.add_paragraph("Exact 20pt line spacing (fixed height).")
296        .line_spacing(20.0);
297
298    doc.add_paragraph("2.4 Pagination Controls")
299        .style("Heading2");
300    doc.add_paragraph("keep_with_next — this paragraph stays with the next")
301        .keep_with_next(true);
302    doc.add_paragraph("(This paragraph was kept with the one above.)");
303    doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
304        .keep_together(true);
305    doc.add_paragraph("widow_control — prevents widow/orphan lines")
306        .widow_control(true);
307
308    // ── SECTION 3: LISTS ──
309    doc.add_paragraph("").page_break_before(true);
310    doc.add_paragraph("3. Lists").style("Heading1");
311
312    doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
313    doc.add_bullet_list_item("First item", 0);
314    doc.add_bullet_list_item("Second item", 0);
315    doc.add_bullet_list_item("Nested level 1", 1);
316    doc.add_bullet_list_item("Nested level 2", 2);
317    doc.add_bullet_list_item("Back to level 1", 1);
318    doc.add_bullet_list_item("Third item", 0);
319
320    doc.add_paragraph("");
321
322    doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
323    doc.add_numbered_list_item("First numbered item", 0);
324    doc.add_numbered_list_item("Second numbered item", 0);
325    doc.add_numbered_list_item("Sub-item A", 1);
326    doc.add_numbered_list_item("Sub-item B", 1);
327    doc.add_numbered_list_item("Third numbered item", 0);
328
329    // ── SECTION 4: TAB STOPS ──
330    doc.add_paragraph("");
331    doc.add_paragraph("4. Tab Stops").style("Heading1");
332
333    doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
334    doc.add_paragraph("Left\tCenter\tRight\tDecimal")
335        .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
336        .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
337        .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
338        .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
339
340    doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
341    doc.add_paragraph("Item\t\tPrice")
342        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
343        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
344        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
345    doc.add_paragraph("Widget\t\t$19.99")
346        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
347        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
348        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
349    doc.add_paragraph("Gadget\t\t$249.50")
350        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
351        .add_tab_stop_with_leader(
352            TabAlignment::Right,
353            Length::inches(4.0),
354            TabLeader::Underscore,
355        )
356        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
357
358    // ── SECTION 5: TABLES ──
359    doc.add_paragraph("").page_break_before(true);
360    doc.add_paragraph("5. Tables").style("Heading1");
361
362    doc.add_paragraph("5.1 Basic Table").style("Heading2");
363    {
364        let mut tbl = doc
365            .add_table(4, 3)
366            .borders(BorderStyle::Single, 4, "000000");
367        for col in 0..3 {
368            tbl.cell(0, col).unwrap().shading("2E75B6");
369        }
370        tbl.cell(0, 0).unwrap().set_text("Name");
371        tbl.cell(0, 1).unwrap().set_text("Role");
372        tbl.cell(0, 2).unwrap().set_text("Location");
373        tbl.cell(1, 0).unwrap().set_text("Walter White");
374        tbl.cell(1, 1).unwrap().set_text("CEO");
375        tbl.cell(1, 2).unwrap().set_text("Albuquerque");
376        tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
377        tbl.cell(2, 1).unwrap().set_text("CTO");
378        tbl.cell(2, 2).unwrap().set_text("Remote");
379        tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
380        tbl.cell(3, 1).unwrap().set_text("Security");
381        tbl.cell(3, 2).unwrap().set_text("Washington");
382    }
383
384    doc.add_paragraph("");
385
386    doc.add_paragraph("5.2 Column Span & Cell Shading")
387        .style("Heading2");
388    {
389        let mut tbl = doc
390            .add_table(3, 4)
391            .borders(BorderStyle::Single, 4, "000000")
392            .width_pct(100.0);
393        tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
394        tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
395        tbl.cell(1, 0).unwrap().set_text("Region");
396        tbl.cell(1, 0).unwrap().shading("D6E4F0");
397        tbl.cell(1, 1).unwrap().set_text("Q1");
398        tbl.cell(1, 1).unwrap().shading("D6E4F0");
399        tbl.cell(1, 2).unwrap().set_text("Q2");
400        tbl.cell(1, 2).unwrap().shading("D6E4F0");
401        tbl.cell(1, 3).unwrap().set_text("Total");
402        tbl.cell(1, 3).unwrap().shading("D6E4F0");
403        tbl.cell(2, 0).unwrap().set_text("Americas");
404        tbl.cell(2, 1).unwrap().set_text("$2.4M");
405        tbl.cell(2, 2).unwrap().set_text("$2.7M");
406        tbl.cell(2, 3).unwrap().set_text("$5.1M");
407    }
408
409    doc.add_paragraph("");
410
411    doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
412    {
413        let mut tbl = doc
414            .add_table(4, 3)
415            .borders(BorderStyle::Single, 4, "000000");
416        tbl.cell(0, 0).unwrap().set_text("Category");
417        tbl.cell(0, 0).unwrap().shading("E2EFDA");
418        tbl.cell(0, 1).unwrap().set_text("Item");
419        tbl.cell(0, 1).unwrap().shading("E2EFDA");
420        tbl.cell(0, 2).unwrap().set_text("Price");
421        tbl.cell(0, 2).unwrap().shading("E2EFDA");
422        tbl.cell(1, 0).unwrap().set_text("Hardware");
423        tbl.cell(1, 0).unwrap().v_merge_restart();
424        tbl.cell(1, 1).unwrap().set_text("Laptop");
425        tbl.cell(1, 2).unwrap().set_text("$1,200");
426        tbl.cell(2, 0).unwrap().v_merge_continue();
427        tbl.cell(2, 1).unwrap().set_text("Monitor");
428        tbl.cell(2, 2).unwrap().set_text("$450");
429        tbl.cell(3, 0).unwrap().set_text("Software");
430        tbl.cell(3, 1).unwrap().set_text("IDE License");
431        tbl.cell(3, 2).unwrap().set_text("$200/yr");
432    }
433
434    doc.add_paragraph("");
435
436    doc.add_paragraph("5.4 Nested Table").style("Heading2");
437    {
438        let mut tbl = doc
439            .add_table(2, 2)
440            .borders(BorderStyle::Single, 6, "2E75B6");
441        tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
442        tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
443        tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
444        {
445            let mut cell = tbl.cell(1, 1).unwrap();
446            cell.set_text("Nested table below:");
447            let mut nested = cell
448                .add_table(2, 2)
449                .borders(BorderStyle::Single, 2, "FF6600");
450            nested.cell(0, 0).unwrap().set_text("A");
451            nested.cell(0, 1).unwrap().set_text("B");
452            nested.cell(1, 0).unwrap().set_text("C");
453            nested.cell(1, 1).unwrap().set_text("D");
454        }
455    }
456
457    doc.add_paragraph("");
458
459    doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
460        .style("Heading2");
461    {
462        let mut tbl = doc
463            .add_table(2, 3)
464            .borders(BorderStyle::Single, 4, "666666");
465        tbl.row(0).unwrap().height(Length::pt(50.0)).header();
466        tbl.cell(0, 0).unwrap().set_text("Top");
467        tbl.cell(0, 0)
468            .unwrap()
469            .vertical_alignment(VerticalAlignment::Top)
470            .shading("FFF2CC");
471        tbl.cell(0, 1).unwrap().set_text("Center");
472        tbl.cell(0, 1)
473            .unwrap()
474            .vertical_alignment(VerticalAlignment::Center)
475            .shading("D9E2F3");
476        tbl.cell(0, 2).unwrap().set_text("Bottom");
477        tbl.cell(0, 2)
478            .unwrap()
479            .vertical_alignment(VerticalAlignment::Bottom)
480            .shading("E2EFDA");
481        tbl.row(1).unwrap().cant_split();
482        tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
483        tbl.cell(1, 0).unwrap().no_wrap();
484        tbl.cell(1, 1).unwrap().set_text("Fixed width");
485        tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
486        tbl.cell(1, 2).unwrap().set_text("Normal");
487    }
488
489    // ── SECTION 6: IMAGES ──
490    doc.add_paragraph("").page_break_before(true);
491    doc.add_paragraph("6. Images").style("Heading1");
492
493    doc.add_paragraph("6.1 Inline Image").style("Heading2");
494    doc.add_paragraph("A blue gradient image below:");
495    let img = create_sample_png(200, 50, [0, 80, 200]);
496    doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
497
498    doc.add_paragraph("");
499    doc.add_paragraph("6.2 Header Image").style("Heading2");
500    let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
501    doc.set_header_image(
502        &hdr_img,
503        "header_logo.png",
504        Length::inches(2.0),
505        Length::inches(0.2),
506    );
507    doc.add_paragraph("The header now contains an inline image (check the top of this page).");
508
509    doc.add_paragraph("");
510    doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
511
512    // ── SECTION 7: CONTENT MANIPULATION ──
513    doc.add_paragraph("").page_break_before(true);
514    doc.add_paragraph("7. Content Manipulation")
515        .style("Heading1");
516
517    doc.add_paragraph("7.1 Placeholder Replacement")
518        .style("Heading2");
519    doc.add_paragraph("Customer: {{customer}}");
520    doc.add_paragraph("Date: {{date}}");
521    doc.add_paragraph("Reference: {{ref_number}}");
522    {
523        let mut tbl = doc
524            .add_table(2, 2)
525            .borders(BorderStyle::Single, 4, "000000");
526        tbl.cell(0, 0).unwrap().set_text("Project");
527        tbl.cell(0, 0).unwrap().shading("D6E4F0");
528        tbl.cell(0, 1).unwrap().set_text("{{project}}");
529        tbl.cell(1, 0).unwrap().set_text("Status");
530        tbl.cell(1, 0).unwrap().shading("D6E4F0");
531        tbl.cell(1, 1).unwrap().set_text("{{status}}");
532    }
533
534    let mut replacements = HashMap::new();
535    replacements.insert("{{customer}}", "Tensorbee Inc.");
536    replacements.insert("{{date}}", "February 22, 2026");
537    replacements.insert("{{ref_number}}", "TB-2026-001");
538    replacements.insert("{{project}}", "Infrastructure Upgrade");
539    replacements.insert("{{status}}", "In Progress");
540    let n = doc.replace_all(&replacements);
541    doc.add_paragraph(&format!("({n} placeholders replaced above)"));
542
543    doc.add_paragraph("");
544
545    doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
546    doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
547    let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
548    doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
549
550    doc.add_paragraph("");
551
552    doc.add_paragraph("7.3 Content Insertion").style("Heading2");
553    doc.add_paragraph("Section A: First content.");
554    doc.add_paragraph("Section C: Third content.");
555    if let Some(idx) = doc.find_content_index("Section C") {
556        doc.insert_paragraph(
557            idx,
558            "Section B: Inserted between A and C via find_content_index().",
559        );
560    }
561
562    // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
563    doc.add_paragraph("").section_break(SectionBreak::NextPage);
564    doc.add_paragraph("8. Section Breaks & Orientation")
565        .style("Heading1");
566    doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
567
568    {
569        let mut tbl = doc
570            .add_table(3, 7)
571            .borders(BorderStyle::Single, 4, "2E75B6");
572        let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
573        for (c, h) in headers.iter().enumerate() {
574            tbl.cell(0, c).unwrap().set_text(h);
575            tbl.cell(0, c).unwrap().shading("2E75B6");
576        }
577        let data = [
578            [
579                "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
580            ],
581            [
582                "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
583            ],
584        ];
585        for (r, row) in data.iter().enumerate() {
586            for (c, v) in row.iter().enumerate() {
587                tbl.cell(r + 1, c).unwrap().set_text(v);
588            }
589        }
590    }
591
592    doc.add_paragraph("")
593        .section_break(SectionBreak::NextPage)
594        .section_landscape();
595
596    // ── SECTION 9: CUSTOM STYLES ──
597    doc.add_paragraph("9. Custom Styles").style("Heading1");
598    doc.add_style(
599        StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
600            .based_on("Normal")
601            .paragraph_properties(rdocx_oxml::properties::CT_PPr {
602                shading: Some(rdocx_oxml::properties::CT_Shd {
603                    val: "clear".to_string(),
604                    color: None,
605                    fill: Some("FFF2CC".to_string()),
606                }),
607                ..Default::default()
608            })
609            .run_properties(rdocx_oxml::properties::CT_RPr {
610                bold: Some(true),
611                color: Some("C45911".to_string()),
612                ..Default::default()
613            }),
614    );
615    doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
616        .style("CustomHighlight");
617
618    doc.add_paragraph("");
619
620    // ── SECTION 10: DOCUMENT INTELLIGENCE ──
621    doc.add_paragraph("10. Document Intelligence API")
622        .style("Heading1");
623
624    let wc = doc.word_count();
625    let hc = doc.headings().len();
626    let ic = doc.images().len();
627    let lc = doc.links().len();
628    doc.add_paragraph(&format!("Word count: {wc}"));
629    doc.add_paragraph(&format!("Heading count: {hc}"));
630    doc.add_paragraph(&format!("Image count: {ic}"));
631    doc.add_paragraph(&format!("Link count: {lc}"));
632
633    let outline = doc.document_outline();
634    doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
635
636    let issues = doc.audit_accessibility();
637    doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
638    for issue in &issues {
639        doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
640    }
641
642    // ── SECTION 11: DOCUMENT MERGING ──
643    doc.add_paragraph("");
644    doc.add_paragraph("11. Document Merging").style("Heading1");
645    {
646        let mut other = Document::new();
647        other.add_paragraph("This paragraph was merged from another document using append().");
648        doc.append(&other);
649    }
650
651    doc.add_paragraph("");
652
653    // ── SUMMARY ──
654    doc.add_paragraph("Summary of Demonstrated Features")
655        .style("Heading1");
656    let features = [
657        "Page setup: size, margins, header/footer distance, gutter",
658        "Document metadata: title, author, subject, keywords",
659        "Headers and footers: text, images, different first page",
660        "Background images (full-page behind text)",
661        "Table of Contents generation with bookmarks",
662        "Text formatting: bold, italic, underline styles, strike, color, size, font",
663        "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
664        "Paragraph formatting: alignment, borders, shading, spacing, indentation",
665        "Pagination controls: keep-with-next, keep-together, widow control",
666        "Bullet and numbered lists with nesting",
667        "Tab stops with dot/underscore/hyphen leaders",
668        "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
669        "Row properties: header rows, exact height, cant-split",
670        "Cell properties: width, no-wrap, vertical alignment",
671        "Inline images and header images",
672        "Placeholder replacement (single and batch)",
673        "Regex-based find and replace",
674        "Content insertion at specific positions",
675        "Section breaks with mixed portrait/landscape",
676        "Custom paragraph and character styles",
677        "Document intelligence: word count, headings, outline, images, links",
678        "Accessibility audit",
679        "Document merging (append)",
680        "Export: DOCX, PDF, HTML, Markdown",
681    ];
682    for f in &features {
683        doc.add_bullet_list_item(f, 0);
684    }
685    doc.add_paragraph("");
686    doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
687        .alignment(Alignment::Center)
688        .shading("E2EFDA")
689        .border_all(BorderStyle::Single, 2, "00AA00");
690
691    doc
692}
Source

pub fn set_different_first_page(&mut self, val: bool)

Enable or disable different first page header/footer.

Examples found in repository?
examples/header_banner.rs (line 66)
29fn generate_header_banner_doc(path: &Path) {
30    let mut doc = Document::new();
31
32    // Page setup with extra top margin for the banner
33    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
34    doc.set_margins(
35        Length::twips(2292), // top — extra tall for header banner
36        Length::twips(1440), // right
37        Length::twips(1440), // bottom
38        Length::twips(1440), // left
39    );
40    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
41
42    // Generate a simple logo image (white text on transparent background)
43    let logo_img = create_logo_png(220, 48);
44
45    // ── Dark blue banner ──
46    let banner = build_header_banner_xml(
47        "rId1",
48        &BannerOpts {
49            bg_color: "1A3C6E",
50            banner_width: 7772400, // full page width in EMU (~8.5")
51            banner_height: 969026, // banner height in EMU (~1.06")
52            logo_width: 2011680,   // logo display width (~2.2")
53            logo_height: 438912,   // logo display height (~0.48")
54            logo_x_offset: 295125, // left padding
55            logo_y_offset: 265057, // vertical centering
56        },
57    );
58
59    doc.set_raw_header_with_images(
60        banner.clone(),
61        &[("rId1", &logo_img, "logo.png")],
62        rdocx_oxml::header_footer::HdrFtrType::Default,
63    );
64
65    // Use a different first page header (same banner, different color)
66    doc.set_different_first_page(true);
67    let first_page_banner = build_header_banner_xml(
68        "rId1",
69        &BannerOpts {
70            bg_color: "2E75B6", // lighter blue for cover
71            banner_width: 7772400,
72            banner_height: 969026,
73            logo_width: 2011680,
74            logo_height: 438912,
75            logo_x_offset: 295125,
76            logo_y_offset: 265057,
77        },
78    );
79    doc.set_raw_header_with_images(
80        first_page_banner,
81        &[("rId1", &logo_img, "logo.png")],
82        rdocx_oxml::header_footer::HdrFtrType::First,
83    );
84
85    // Footer
86    doc.set_footer("Confidential — Internal Use Only");
87
88    // ── Page 1: Cover ──
89    doc.add_paragraph("Company Report").style("Heading1");
90
91    doc.add_paragraph(
92        "This document demonstrates a custom header banner built with DrawingML \
93         group shapes. The banner uses a colored rectangle with a logo image overlaid, \
94         positioned at the top of each page.",
95    );
96
97    doc.add_paragraph("");
98
99    doc.add_paragraph("How the Header Banner Works")
100        .style("Heading2");
101
102    doc.add_paragraph(
103        "The header banner is built using set_raw_header_with_images(), which \
104         accepts raw XML and a list of (rel_id, image_data, filename) tuples. \
105         The XML uses a DrawingML group shape (wpg:wgp) containing:",
106    );
107
108    doc.add_bullet_list_item(
109        "A wps:wsp rectangle shape with a solid color fill (the background bar)",
110        0,
111    );
112    doc.add_bullet_list_item(
113        "A pic:pic image element positioned within the group (the logo)",
114        0,
115    );
116    doc.add_bullet_list_item(
117        "The group is wrapped in a wp:anchor element for absolute page positioning",
118        0,
119    );
120
121    doc.add_paragraph("");
122
123    doc.add_paragraph("Customization").style("Heading2");
124
125    doc.add_paragraph(
126        "All dimensions are in EMU (English Metric Units) where 914400 EMU = 1 inch. \
127         You can customize:",
128    );
129
130    doc.add_bullet_list_item("bg_color — any hex color for the rectangle background", 0);
131    doc.add_bullet_list_item("banner_width / banner_height — size of the full banner", 0);
132    doc.add_bullet_list_item("logo_width / logo_height — display size of the logo", 0);
133    doc.add_bullet_list_item(
134        "logo_x_offset / logo_y_offset — logo position within the banner",
135        0,
136    );
137
138    doc.add_paragraph("");
139
140    doc.add_paragraph("Different First Page").style("Heading2");
141
142    doc.add_paragraph(
143        "This page uses a lighter blue banner (first page header). \
144         Subsequent pages use a darker blue banner (default header). \
145         Use set_different_first_page(true) to enable this.",
146    );
147
148    // ── Page 2 ──
149    doc.add_paragraph("").page_break_before(true);
150
151    doc.add_paragraph("Second Page").style("Heading1");
152
153    doc.add_paragraph(
154        "This page shows the default header banner (dark blue). The first page \
155         had a lighter blue banner because we set a different first-page header.",
156    );
157
158    doc.add_paragraph("");
159
160    doc.add_paragraph(
161        "The banner repeats on every page because it is placed in the header part. \
162         You can have different banners for default, first-page, and even-page headers.",
163    );
164
165    doc.set_title("Header Banner Example");
166    doc.set_author("rdocx");
167
168    doc.save(path).unwrap();
169}
More examples
Hide additional examples
examples/generate_all_samples.rs (line 116)
97fn generate_feature_showcase(_samples_dir: &Path) -> Document {
98    let mut doc = Document::new();
99
100    // ── Page Setup & Metadata ──
101    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
102    doc.set_margins(
103        Length::inches(1.0),
104        Length::inches(1.0),
105        Length::inches(1.0),
106        Length::inches(1.0),
107    );
108    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
109    doc.set_gutter(Length::twips(0));
110    doc.set_title("rdocx Feature Showcase");
111    doc.set_author("rdocx Sample Generator");
112    doc.set_subject("Comprehensive feature demonstration");
113    doc.set_keywords("rdocx, docx, rust, sample, showcase");
114
115    // Headers & Footers with different first page
116    doc.set_different_first_page(true);
117    doc.set_first_page_header("rdocx");
118    doc.set_first_page_footer("Feature Showcase — Cover Page");
119    doc.set_header("rdocx Feature Showcase");
120    doc.set_footer("Generated by rdocx");
121
122    // ── COVER PAGE ──
123    let bg = create_sample_png(612, 792, [20, 45, 90]);
124    doc.add_background_image(&bg, "cover_bg.png");
125
126    for _ in 0..3 {
127        doc.add_paragraph("");
128    }
129    {
130        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
131        p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
132    }
133    {
134        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
135        p.add_run("Complete Feature Showcase")
136            .size(28.0)
137            .color("FFFFFF")
138            .italic(true);
139    }
140    doc.add_paragraph("");
141    {
142        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
143        p.add_run("Every feature of the rdocx Rust library")
144            .size(13.0)
145            .color("B0C4DE");
146    }
147    {
148        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
149        p.add_run("demonstrated in a single document.")
150            .size(13.0)
151            .color("B0C4DE");
152    }
153
154    // ── TABLE OF CONTENTS ──
155    doc.add_paragraph("").page_break_before(true);
156    doc.insert_toc(doc.content_count(), 3);
157
158    // ── SECTION 1: TEXT FORMATTING ──
159    doc.add_paragraph("").page_break_before(true);
160    doc.add_paragraph("1. Text Formatting").style("Heading1");
161
162    // Paragraph Alignment
163    doc.add_paragraph("1.1 Paragraph Alignment")
164        .style("Heading2");
165    doc.add_paragraph("Left-aligned paragraph (default).")
166        .alignment(Alignment::Left);
167    doc.add_paragraph("Center-aligned paragraph.")
168        .alignment(Alignment::Center);
169    doc.add_paragraph("Right-aligned paragraph.")
170        .alignment(Alignment::Right);
171    doc.add_paragraph(
172        "Justified paragraph — this text is long enough to demonstrate how justified alignment \
173         distributes extra space across word gaps so lines fill the full width of the text area.",
174    )
175    .alignment(Alignment::Justify);
176
177    doc.add_paragraph("");
178
179    // Run Formatting
180    doc.add_paragraph("1.2 Run Formatting").style("Heading2");
181    {
182        let mut p = doc.add_paragraph("");
183        p.add_run("Bold").bold(true);
184        p.add_run(" | ");
185        p.add_run("Italic").italic(true);
186        p.add_run(" | ");
187        p.add_run("Bold Italic").bold(true).italic(true);
188        p.add_run(" | ");
189        p.add_run("Underline").underline(true);
190        p.add_run(" | ");
191        p.add_run("Strikethrough").strike(true);
192        p.add_run(" | ");
193        p.add_run("Double Strike").double_strike(true);
194    }
195    {
196        let mut p = doc.add_paragraph("");
197        p.add_run("Red").color("FF0000");
198        p.add_run(" | ");
199        p.add_run("Blue").color("0000FF");
200        p.add_run(" | ");
201        p.add_run("Green").color("00AA00");
202        p.add_run(" | ");
203        p.add_run("Highlighted").highlight("FFFF00");
204    }
205    {
206        let mut p = doc.add_paragraph("");
207        p.add_run("8pt").size(8.0);
208        p.add_run(" | ");
209        p.add_run("11pt").size(11.0);
210        p.add_run(" | ");
211        p.add_run("16pt").size(16.0);
212        p.add_run(" | ");
213        p.add_run("24pt").size(24.0);
214    }
215    {
216        let mut p = doc.add_paragraph("");
217        p.add_run("Arial").font("Arial");
218        p.add_run(" | ");
219        p.add_run("Times New Roman").font("Times New Roman");
220        p.add_run(" | ");
221        p.add_run("Courier New").font("Courier New");
222    }
223    {
224        let mut p = doc.add_paragraph("");
225        p.add_run("H");
226        p.add_run("2").subscript();
227        p.add_run("O (subscript) | E=mc");
228        p.add_run("2").superscript();
229        p.add_run(" (superscript)");
230    }
231    {
232        let mut p = doc.add_paragraph("");
233        p.add_run("ALL CAPS").all_caps(true);
234        p.add_run(" | ");
235        p.add_run("Small Caps").small_caps(true);
236        p.add_run(" | ");
237        p.add_run("Expanded").character_spacing(Length::twips(40));
238        p.add_run(" | ");
239        p.add_run("Hidden text (not visible)").hidden(true);
240    }
241
242    doc.add_paragraph("");
243
244    // Underline Styles
245    doc.add_paragraph("1.3 Underline Styles").style("Heading2");
246    {
247        let mut p = doc.add_paragraph("");
248        p.add_run("Single ")
249            .underline_style(rdocx::UnderlineStyle::Single);
250        p.add_run("Double ")
251            .underline_style(rdocx::UnderlineStyle::Double);
252        p.add_run("Thick ")
253            .underline_style(rdocx::UnderlineStyle::Thick);
254        p.add_run("Dotted ")
255            .underline_style(rdocx::UnderlineStyle::Dotted);
256        p.add_run("Dash ")
257            .underline_style(rdocx::UnderlineStyle::Dash);
258        p.add_run("Wave ")
259            .underline_style(rdocx::UnderlineStyle::Wave);
260    }
261
262    // ── SECTION 2: PARAGRAPH FORMATTING ──
263    doc.add_paragraph("").page_break_before(true);
264    doc.add_paragraph("2. Paragraph Formatting")
265        .style("Heading1");
266
267    doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
268    doc.add_paragraph("Paragraph with light green shading")
269        .shading("E2EFDA");
270    doc.add_paragraph("Paragraph with bottom border")
271        .border_bottom(BorderStyle::Single, 6, "2E75B6");
272    doc.add_paragraph("Paragraph with all borders (red)")
273        .border_all(BorderStyle::Single, 4, "FF0000");
274
275    doc.add_paragraph("2.2 Indentation").style("Heading2");
276    doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
277        .indent_left(Length::inches(1.0))
278        .hanging_indent(Length::inches(0.5));
279    doc.add_paragraph("0.5-inch first-line indent on this paragraph")
280        .first_line_indent(Length::inches(0.5));
281    doc.add_paragraph("Both left and right indent (0.75 inches each)")
282        .indent_left(Length::inches(0.75))
283        .indent_right(Length::inches(0.75));
284
285    doc.add_paragraph("2.3 Spacing & Line Height")
286        .style("Heading2");
287    doc.add_paragraph("Extra space before (24pt) and after (12pt)")
288        .space_before(Length::pt(24.0))
289        .space_after(Length::pt(12.0));
290    doc.add_paragraph(
291        "Double line spacing paragraph. This text should have extra vertical space between \
292         lines to demonstrate line_spacing_multiple(2.0).",
293    )
294    .line_spacing_multiple(2.0);
295    doc.add_paragraph("Exact 20pt line spacing (fixed height).")
296        .line_spacing(20.0);
297
298    doc.add_paragraph("2.4 Pagination Controls")
299        .style("Heading2");
300    doc.add_paragraph("keep_with_next — this paragraph stays with the next")
301        .keep_with_next(true);
302    doc.add_paragraph("(This paragraph was kept with the one above.)");
303    doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
304        .keep_together(true);
305    doc.add_paragraph("widow_control — prevents widow/orphan lines")
306        .widow_control(true);
307
308    // ── SECTION 3: LISTS ──
309    doc.add_paragraph("").page_break_before(true);
310    doc.add_paragraph("3. Lists").style("Heading1");
311
312    doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
313    doc.add_bullet_list_item("First item", 0);
314    doc.add_bullet_list_item("Second item", 0);
315    doc.add_bullet_list_item("Nested level 1", 1);
316    doc.add_bullet_list_item("Nested level 2", 2);
317    doc.add_bullet_list_item("Back to level 1", 1);
318    doc.add_bullet_list_item("Third item", 0);
319
320    doc.add_paragraph("");
321
322    doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
323    doc.add_numbered_list_item("First numbered item", 0);
324    doc.add_numbered_list_item("Second numbered item", 0);
325    doc.add_numbered_list_item("Sub-item A", 1);
326    doc.add_numbered_list_item("Sub-item B", 1);
327    doc.add_numbered_list_item("Third numbered item", 0);
328
329    // ── SECTION 4: TAB STOPS ──
330    doc.add_paragraph("");
331    doc.add_paragraph("4. Tab Stops").style("Heading1");
332
333    doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
334    doc.add_paragraph("Left\tCenter\tRight\tDecimal")
335        .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
336        .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
337        .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
338        .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
339
340    doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
341    doc.add_paragraph("Item\t\tPrice")
342        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
343        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
344        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
345    doc.add_paragraph("Widget\t\t$19.99")
346        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
347        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
348        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
349    doc.add_paragraph("Gadget\t\t$249.50")
350        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
351        .add_tab_stop_with_leader(
352            TabAlignment::Right,
353            Length::inches(4.0),
354            TabLeader::Underscore,
355        )
356        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
357
358    // ── SECTION 5: TABLES ──
359    doc.add_paragraph("").page_break_before(true);
360    doc.add_paragraph("5. Tables").style("Heading1");
361
362    doc.add_paragraph("5.1 Basic Table").style("Heading2");
363    {
364        let mut tbl = doc
365            .add_table(4, 3)
366            .borders(BorderStyle::Single, 4, "000000");
367        for col in 0..3 {
368            tbl.cell(0, col).unwrap().shading("2E75B6");
369        }
370        tbl.cell(0, 0).unwrap().set_text("Name");
371        tbl.cell(0, 1).unwrap().set_text("Role");
372        tbl.cell(0, 2).unwrap().set_text("Location");
373        tbl.cell(1, 0).unwrap().set_text("Walter White");
374        tbl.cell(1, 1).unwrap().set_text("CEO");
375        tbl.cell(1, 2).unwrap().set_text("Albuquerque");
376        tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
377        tbl.cell(2, 1).unwrap().set_text("CTO");
378        tbl.cell(2, 2).unwrap().set_text("Remote");
379        tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
380        tbl.cell(3, 1).unwrap().set_text("Security");
381        tbl.cell(3, 2).unwrap().set_text("Washington");
382    }
383
384    doc.add_paragraph("");
385
386    doc.add_paragraph("5.2 Column Span & Cell Shading")
387        .style("Heading2");
388    {
389        let mut tbl = doc
390            .add_table(3, 4)
391            .borders(BorderStyle::Single, 4, "000000")
392            .width_pct(100.0);
393        tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
394        tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
395        tbl.cell(1, 0).unwrap().set_text("Region");
396        tbl.cell(1, 0).unwrap().shading("D6E4F0");
397        tbl.cell(1, 1).unwrap().set_text("Q1");
398        tbl.cell(1, 1).unwrap().shading("D6E4F0");
399        tbl.cell(1, 2).unwrap().set_text("Q2");
400        tbl.cell(1, 2).unwrap().shading("D6E4F0");
401        tbl.cell(1, 3).unwrap().set_text("Total");
402        tbl.cell(1, 3).unwrap().shading("D6E4F0");
403        tbl.cell(2, 0).unwrap().set_text("Americas");
404        tbl.cell(2, 1).unwrap().set_text("$2.4M");
405        tbl.cell(2, 2).unwrap().set_text("$2.7M");
406        tbl.cell(2, 3).unwrap().set_text("$5.1M");
407    }
408
409    doc.add_paragraph("");
410
411    doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
412    {
413        let mut tbl = doc
414            .add_table(4, 3)
415            .borders(BorderStyle::Single, 4, "000000");
416        tbl.cell(0, 0).unwrap().set_text("Category");
417        tbl.cell(0, 0).unwrap().shading("E2EFDA");
418        tbl.cell(0, 1).unwrap().set_text("Item");
419        tbl.cell(0, 1).unwrap().shading("E2EFDA");
420        tbl.cell(0, 2).unwrap().set_text("Price");
421        tbl.cell(0, 2).unwrap().shading("E2EFDA");
422        tbl.cell(1, 0).unwrap().set_text("Hardware");
423        tbl.cell(1, 0).unwrap().v_merge_restart();
424        tbl.cell(1, 1).unwrap().set_text("Laptop");
425        tbl.cell(1, 2).unwrap().set_text("$1,200");
426        tbl.cell(2, 0).unwrap().v_merge_continue();
427        tbl.cell(2, 1).unwrap().set_text("Monitor");
428        tbl.cell(2, 2).unwrap().set_text("$450");
429        tbl.cell(3, 0).unwrap().set_text("Software");
430        tbl.cell(3, 1).unwrap().set_text("IDE License");
431        tbl.cell(3, 2).unwrap().set_text("$200/yr");
432    }
433
434    doc.add_paragraph("");
435
436    doc.add_paragraph("5.4 Nested Table").style("Heading2");
437    {
438        let mut tbl = doc
439            .add_table(2, 2)
440            .borders(BorderStyle::Single, 6, "2E75B6");
441        tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
442        tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
443        tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
444        {
445            let mut cell = tbl.cell(1, 1).unwrap();
446            cell.set_text("Nested table below:");
447            let mut nested = cell
448                .add_table(2, 2)
449                .borders(BorderStyle::Single, 2, "FF6600");
450            nested.cell(0, 0).unwrap().set_text("A");
451            nested.cell(0, 1).unwrap().set_text("B");
452            nested.cell(1, 0).unwrap().set_text("C");
453            nested.cell(1, 1).unwrap().set_text("D");
454        }
455    }
456
457    doc.add_paragraph("");
458
459    doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
460        .style("Heading2");
461    {
462        let mut tbl = doc
463            .add_table(2, 3)
464            .borders(BorderStyle::Single, 4, "666666");
465        tbl.row(0).unwrap().height(Length::pt(50.0)).header();
466        tbl.cell(0, 0).unwrap().set_text("Top");
467        tbl.cell(0, 0)
468            .unwrap()
469            .vertical_alignment(VerticalAlignment::Top)
470            .shading("FFF2CC");
471        tbl.cell(0, 1).unwrap().set_text("Center");
472        tbl.cell(0, 1)
473            .unwrap()
474            .vertical_alignment(VerticalAlignment::Center)
475            .shading("D9E2F3");
476        tbl.cell(0, 2).unwrap().set_text("Bottom");
477        tbl.cell(0, 2)
478            .unwrap()
479            .vertical_alignment(VerticalAlignment::Bottom)
480            .shading("E2EFDA");
481        tbl.row(1).unwrap().cant_split();
482        tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
483        tbl.cell(1, 0).unwrap().no_wrap();
484        tbl.cell(1, 1).unwrap().set_text("Fixed width");
485        tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
486        tbl.cell(1, 2).unwrap().set_text("Normal");
487    }
488
489    // ── SECTION 6: IMAGES ──
490    doc.add_paragraph("").page_break_before(true);
491    doc.add_paragraph("6. Images").style("Heading1");
492
493    doc.add_paragraph("6.1 Inline Image").style("Heading2");
494    doc.add_paragraph("A blue gradient image below:");
495    let img = create_sample_png(200, 50, [0, 80, 200]);
496    doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
497
498    doc.add_paragraph("");
499    doc.add_paragraph("6.2 Header Image").style("Heading2");
500    let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
501    doc.set_header_image(
502        &hdr_img,
503        "header_logo.png",
504        Length::inches(2.0),
505        Length::inches(0.2),
506    );
507    doc.add_paragraph("The header now contains an inline image (check the top of this page).");
508
509    doc.add_paragraph("");
510    doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
511
512    // ── SECTION 7: CONTENT MANIPULATION ──
513    doc.add_paragraph("").page_break_before(true);
514    doc.add_paragraph("7. Content Manipulation")
515        .style("Heading1");
516
517    doc.add_paragraph("7.1 Placeholder Replacement")
518        .style("Heading2");
519    doc.add_paragraph("Customer: {{customer}}");
520    doc.add_paragraph("Date: {{date}}");
521    doc.add_paragraph("Reference: {{ref_number}}");
522    {
523        let mut tbl = doc
524            .add_table(2, 2)
525            .borders(BorderStyle::Single, 4, "000000");
526        tbl.cell(0, 0).unwrap().set_text("Project");
527        tbl.cell(0, 0).unwrap().shading("D6E4F0");
528        tbl.cell(0, 1).unwrap().set_text("{{project}}");
529        tbl.cell(1, 0).unwrap().set_text("Status");
530        tbl.cell(1, 0).unwrap().shading("D6E4F0");
531        tbl.cell(1, 1).unwrap().set_text("{{status}}");
532    }
533
534    let mut replacements = HashMap::new();
535    replacements.insert("{{customer}}", "Tensorbee Inc.");
536    replacements.insert("{{date}}", "February 22, 2026");
537    replacements.insert("{{ref_number}}", "TB-2026-001");
538    replacements.insert("{{project}}", "Infrastructure Upgrade");
539    replacements.insert("{{status}}", "In Progress");
540    let n = doc.replace_all(&replacements);
541    doc.add_paragraph(&format!("({n} placeholders replaced above)"));
542
543    doc.add_paragraph("");
544
545    doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
546    doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
547    let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
548    doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
549
550    doc.add_paragraph("");
551
552    doc.add_paragraph("7.3 Content Insertion").style("Heading2");
553    doc.add_paragraph("Section A: First content.");
554    doc.add_paragraph("Section C: Third content.");
555    if let Some(idx) = doc.find_content_index("Section C") {
556        doc.insert_paragraph(
557            idx,
558            "Section B: Inserted between A and C via find_content_index().",
559        );
560    }
561
562    // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
563    doc.add_paragraph("").section_break(SectionBreak::NextPage);
564    doc.add_paragraph("8. Section Breaks & Orientation")
565        .style("Heading1");
566    doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
567
568    {
569        let mut tbl = doc
570            .add_table(3, 7)
571            .borders(BorderStyle::Single, 4, "2E75B6");
572        let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
573        for (c, h) in headers.iter().enumerate() {
574            tbl.cell(0, c).unwrap().set_text(h);
575            tbl.cell(0, c).unwrap().shading("2E75B6");
576        }
577        let data = [
578            [
579                "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
580            ],
581            [
582                "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
583            ],
584        ];
585        for (r, row) in data.iter().enumerate() {
586            for (c, v) in row.iter().enumerate() {
587                tbl.cell(r + 1, c).unwrap().set_text(v);
588            }
589        }
590    }
591
592    doc.add_paragraph("")
593        .section_break(SectionBreak::NextPage)
594        .section_landscape();
595
596    // ── SECTION 9: CUSTOM STYLES ──
597    doc.add_paragraph("9. Custom Styles").style("Heading1");
598    doc.add_style(
599        StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
600            .based_on("Normal")
601            .paragraph_properties(rdocx_oxml::properties::CT_PPr {
602                shading: Some(rdocx_oxml::properties::CT_Shd {
603                    val: "clear".to_string(),
604                    color: None,
605                    fill: Some("FFF2CC".to_string()),
606                }),
607                ..Default::default()
608            })
609            .run_properties(rdocx_oxml::properties::CT_RPr {
610                bold: Some(true),
611                color: Some("C45911".to_string()),
612                ..Default::default()
613            }),
614    );
615    doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
616        .style("CustomHighlight");
617
618    doc.add_paragraph("");
619
620    // ── SECTION 10: DOCUMENT INTELLIGENCE ──
621    doc.add_paragraph("10. Document Intelligence API")
622        .style("Heading1");
623
624    let wc = doc.word_count();
625    let hc = doc.headings().len();
626    let ic = doc.images().len();
627    let lc = doc.links().len();
628    doc.add_paragraph(&format!("Word count: {wc}"));
629    doc.add_paragraph(&format!("Heading count: {hc}"));
630    doc.add_paragraph(&format!("Image count: {ic}"));
631    doc.add_paragraph(&format!("Link count: {lc}"));
632
633    let outline = doc.document_outline();
634    doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
635
636    let issues = doc.audit_accessibility();
637    doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
638    for issue in &issues {
639        doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
640    }
641
642    // ── SECTION 11: DOCUMENT MERGING ──
643    doc.add_paragraph("");
644    doc.add_paragraph("11. Document Merging").style("Heading1");
645    {
646        let mut other = Document::new();
647        other.add_paragraph("This paragraph was merged from another document using append().");
648        doc.append(&other);
649    }
650
651    doc.add_paragraph("");
652
653    // ── SUMMARY ──
654    doc.add_paragraph("Summary of Demonstrated Features")
655        .style("Heading1");
656    let features = [
657        "Page setup: size, margins, header/footer distance, gutter",
658        "Document metadata: title, author, subject, keywords",
659        "Headers and footers: text, images, different first page",
660        "Background images (full-page behind text)",
661        "Table of Contents generation with bookmarks",
662        "Text formatting: bold, italic, underline styles, strike, color, size, font",
663        "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
664        "Paragraph formatting: alignment, borders, shading, spacing, indentation",
665        "Pagination controls: keep-with-next, keep-together, widow control",
666        "Bullet and numbered lists with nesting",
667        "Tab stops with dot/underscore/hyphen leaders",
668        "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
669        "Row properties: header rows, exact height, cant-split",
670        "Cell properties: width, no-wrap, vertical alignment",
671        "Inline images and header images",
672        "Placeholder replacement (single and batch)",
673        "Regex-based find and replace",
674        "Content insertion at specific positions",
675        "Section breaks with mixed portrait/landscape",
676        "Custom paragraph and character styles",
677        "Document intelligence: word count, headings, outline, images, links",
678        "Accessibility audit",
679        "Document merging (append)",
680        "Export: DOCX, PDF, HTML, Markdown",
681    ];
682    for f in &features {
683        doc.add_bullet_list_item(f, 0);
684    }
685    doc.add_paragraph("");
686    doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
687        .alignment(Alignment::Center)
688        .shading("E2EFDA")
689        .border_all(BorderStyle::Single, 2, "00AA00");
690
691    doc
692}
693
694// =============================================================================
695// 2. PROPOSAL DOCUMENT — Deep navy + gold scheme
696// =============================================================================
697fn generate_proposal(_samples_dir: &Path) -> Document {
698    let mut doc = Document::new();
699
700    // Colors: Navy #1B2A4A, Gold #C5922E, Light #F4F1EB
701    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
702    doc.set_margins(
703        Length::inches(1.0),
704        Length::inches(1.0),
705        Length::inches(1.0),
706        Length::inches(1.0),
707    );
708    doc.set_title("AI Platform Modernization Proposal");
709    doc.set_author("Walter White");
710    doc.set_subject("Technology Proposal");
711    doc.set_keywords("proposal, AI, modernization, Tensorbee");
712
713    // Banner header
714    let logo_img = create_logo_png(220, 48);
715    let banner = build_header_banner_xml(
716        "rId1",
717        &BannerOpts {
718            bg_color: "1B2A4A",
719            banner_width: 7772400,
720            banner_height: 914400,
721            logo_width: 2011680,
722            logo_height: 438912,
723            logo_x_offset: 295125,
724            logo_y_offset: 237744,
725        },
726    );
727    doc.set_raw_header_with_images(
728        banner,
729        &[("rId1", &logo_img, "logo.png")],
730        rdocx_oxml::header_footer::HdrFtrType::Default,
731    );
732    doc.set_different_first_page(true);
733    let cover_banner = build_header_banner_xml(
734        "rId1",
735        &BannerOpts {
736            bg_color: "C5922E",
737            banner_width: 7772400,
738            banner_height: 914400,
739            logo_width: 2011680,
740            logo_height: 438912,
741            logo_x_offset: 295125,
742            logo_y_offset: 237744,
743        },
744    );
745    doc.set_raw_header_with_images(
746        cover_banner,
747        &[("rId1", &logo_img, "logo.png")],
748        rdocx_oxml::header_footer::HdrFtrType::First,
749    );
750    doc.set_margins(
751        Length::twips(2292),
752        Length::twips(1440),
753        Length::twips(1440),
754        Length::twips(1440),
755    );
756    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
757    doc.set_footer("Tensorbee — Confidential");
758
759    // Custom styles
760    doc.add_style(
761        StyleBuilder::paragraph("ProposalTitle", "Proposal Title")
762            .based_on("Normal")
763            .run_properties(rdocx_oxml::properties::CT_RPr {
764                bold: Some(true),
765                font_ascii: Some("Georgia".to_string()),
766                font_hansi: Some("Georgia".to_string()),
767                sz: Some(rdocx_oxml::HalfPoint(56)), // half-points → 28pt
768                color: Some("1B2A4A".to_string()),
769                ..Default::default()
770            }),
771    );
772
773    // ── Cover Page ──
774    for _ in 0..4 {
775        doc.add_paragraph("");
776    }
777    doc.add_paragraph("AI Platform Modernization")
778        .style("ProposalTitle")
779        .alignment(Alignment::Center);
780    doc.add_paragraph("")
781        .alignment(Alignment::Center)
782        .border_bottom(BorderStyle::Single, 8, "C5922E");
783    {
784        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
785        p.add_run("Prepared for Global Financial Corp.")
786            .size(14.0)
787            .color("666666")
788            .italic(true);
789    }
790    {
791        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
792        p.add_run("Prepared by: Walter White, CEO — Tensorbee")
793            .size(12.0)
794            .color("888888");
795    }
796    {
797        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
798        p.add_run("February 22, 2026").size(12.0).color("888888");
799    }
800
801    // ── TOC ──
802    doc.add_paragraph("").page_break_before(true);
803    doc.add_paragraph("Table of Contents").style("Heading1");
804    // We'll add a manual-style TOC since the insert_toc goes at a specific position
805    doc.insert_toc(doc.content_count(), 2);
806
807    // ── Executive Summary ──
808    doc.add_paragraph("").page_break_before(true);
809    doc.add_paragraph("Executive Summary").style("Heading1");
810    doc.add_paragraph(
811        "Tensorbee proposes a comprehensive modernization of Global Financial Corp's AI platform, \
812         transitioning from legacy batch-processing systems to a real-time inference architecture. \
813         This transformation will reduce model deployment time from weeks to hours, improve \
814         prediction accuracy by an estimated 23%, and deliver $4.2M in annual operational savings.",
815    )
816    .first_line_indent(Length::inches(0.3));
817    doc.add_paragraph(
818        "The project spans three phases over 18 months, with a total investment of $2.8M. \
819         Tensorbee brings deep expertise in ML infrastructure, having successfully completed \
820         similar transformations for three Fortune 500 financial institutions.",
821    )
822    .first_line_indent(Length::inches(0.3));
823
824    // Key metrics table
825    doc.add_paragraph("");
826    {
827        let mut tbl = doc
828            .add_table(5, 2)
829            .borders(BorderStyle::Single, 4, "1B2A4A")
830            .width_pct(80.0);
831        tbl.cell(0, 0).unwrap().set_text("Key Metric");
832        tbl.cell(0, 0).unwrap().shading("1B2A4A");
833        tbl.cell(0, 1).unwrap().set_text("Value");
834        tbl.cell(0, 1).unwrap().shading("1B2A4A");
835        let rows = [
836            ("Total Investment", "$2.8M"),
837            ("Annual Savings", "$4.2M"),
838            ("ROI (Year 1)", "150%"),
839            ("Timeline", "18 months"),
840        ];
841        for (i, (k, v)) in rows.iter().enumerate() {
842            tbl.cell(i + 1, 0).unwrap().set_text(k);
843            if i % 2 == 0 {
844                tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
845            }
846            tbl.cell(i + 1, 1).unwrap().set_text(v);
847            if i % 2 == 0 {
848                tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
849            }
850        }
851    }
852
853    // ── Problem Statement ──
854    doc.add_paragraph("").page_break_before(true);
855    doc.add_paragraph("Problem Statement").style("Heading1");
856    doc.add_paragraph(
857        "Global Financial Corp's current AI infrastructure faces three critical challenges:",
858    );
859    doc.add_numbered_list_item("Deployment Latency — New models take 3-4 weeks to deploy to production, compared to the industry benchmark of 2-3 days.", 0);
860    doc.add_numbered_list_item("Scalability Constraints — The batch processing architecture cannot handle real-time inference demands during peak trading hours.", 0);
861    doc.add_numbered_list_item("Technical Debt — Legacy Python 2.7 codebases and manual deployment scripts create reliability risks and slow iteration speed.", 0);
862
863    // ── Proposed Solution ──
864    doc.add_paragraph("").page_break_before(true);
865    doc.add_paragraph("Proposed Solution").style("Heading1");
866
867    doc.add_paragraph("Phase 1: Foundation (Months 1-6)")
868        .style("Heading2");
869    doc.add_bullet_list_item("Deploy Kubernetes-based ML serving infrastructure", 0);
870    doc.add_bullet_list_item("Implement CI/CD pipeline for model deployment", 0);
871    doc.add_bullet_list_item("Migrate top 5 production models to new platform", 0);
872
873    doc.add_paragraph("Phase 2: Expansion (Months 7-12)")
874        .style("Heading2");
875    doc.add_bullet_list_item(
876        "Real-time feature engineering pipeline (Apache Kafka + Flink)",
877        0,
878    );
879    doc.add_bullet_list_item("A/B testing framework for model variants", 0);
880    doc.add_bullet_list_item("Automated model monitoring and drift detection", 0);
881
882    doc.add_paragraph("Phase 3: Optimization (Months 13-18)")
883        .style("Heading2");
884    doc.add_bullet_list_item("GPU inference optimization (TensorRT/ONNX Runtime)", 0);
885    doc.add_bullet_list_item("Multi-region deployment for latency reduction", 0);
886    doc.add_bullet_list_item(
887        "Self-service model deployment portal for data science teams",
888        0,
889    );
890
891    // ── Budget ──
892    doc.add_paragraph("Budget Breakdown").style("Heading1");
893    {
894        let mut tbl = doc
895            .add_table(6, 3)
896            .borders(BorderStyle::Single, 4, "1B2A4A")
897            .width_pct(100.0);
898        tbl.cell(0, 0).unwrap().set_text("Category");
899        tbl.cell(0, 0).unwrap().shading("1B2A4A");
900        tbl.cell(0, 1).unwrap().set_text("Cost");
901        tbl.cell(0, 1).unwrap().shading("1B2A4A");
902        tbl.cell(0, 2).unwrap().set_text("Notes");
903        tbl.cell(0, 2).unwrap().shading("1B2A4A");
904        let items = [
905            (
906                "Infrastructure",
907                "$450,000",
908                "Cloud compute + GPU instances",
909            ),
910            ("Engineering Services", "$1,600,000", "12 FTE x 18 months"),
911            (
912                "Software Licenses",
913                "$350,000",
914                "Kafka, monitoring, CI/CD tools",
915            ),
916            (
917                "Training & Enablement",
918                "$200,000",
919                "Team training, documentation",
920            ),
921            ("Contingency (10%)", "$200,000", "Risk buffer"),
922        ];
923        for (i, (cat, cost, note)) in items.iter().enumerate() {
924            tbl.cell(i + 1, 0).unwrap().set_text(cat);
925            tbl.cell(i + 1, 1).unwrap().set_text(cost);
926            tbl.cell(i + 1, 2).unwrap().set_text(note);
927            if i % 2 == 0 {
928                tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
929                tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
930                tbl.cell(i + 1, 2).unwrap().shading("F4F1EB");
931            }
932        }
933    }
934
935    // ── Closing ──
936    doc.add_paragraph("");
937    doc.add_paragraph("Next Steps").style("Heading1");
938    doc.add_numbered_list_item(
939        "Schedule technical deep-dive with GFC engineering team (Week 1)",
940        0,
941    );
942    doc.add_numbered_list_item("Finalize scope and sign SOW (Week 2-3)", 0);
943    doc.add_numbered_list_item("Kick off Phase 1 with joint planning session (Week 4)", 0);
944
945    doc.add_paragraph("");
946    {
947        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
948        p.add_run("Walter White")
949            .bold(true)
950            .size(14.0)
951            .color("1B2A4A");
952    }
953    {
954        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
955        p.add_run("CEO, Tensorbee | walter@tensorbee.com")
956            .size(10.0)
957            .color("888888");
958    }
959
960    doc
961}
962
963// =============================================================================
964// 3. QUOTE / BILL OF MATERIALS — Teal + orange scheme
965// =============================================================================
966fn generate_quote(_samples_dir: &Path) -> Document {
967    let mut doc = Document::new();
968
969    // Colors: Teal #008B8B, Dark #1A3C3C, Orange #E07020, Light #F0F8F8
970    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
971    doc.set_margins(
972        Length::inches(0.75),
973        Length::inches(0.75),
974        Length::inches(0.75),
975        Length::inches(0.75),
976    );
977    doc.set_title("Quotation QT-2026-0147");
978    doc.set_author("Walter White");
979    doc.set_keywords("quote, BOM, Tensorbee");
980
981    doc.set_header("Tensorbee — Quotation");
982    doc.set_footer("QT-2026-0147 | Page");
983
984    // ── Header Block ──
985    {
986        let mut p = doc.add_paragraph("").alignment(Alignment::Left);
987        p.add_run("TENSORBEE")
988            .bold(true)
989            .size(28.0)
990            .color("008B8B")
991            .font("Helvetica");
992    }
993    doc.add_paragraph("123 Innovation Drive, Suite 400")
994        .alignment(Alignment::Left);
995    doc.add_paragraph("San Francisco, CA 94105 | +1 (415) 555-0199")
996        .alignment(Alignment::Left);
997    doc.add_paragraph("")
998        .border_bottom(BorderStyle::Single, 8, "008B8B");
999
1000    // Quote meta
1001    doc.add_paragraph("");
1002    {
1003        let mut tbl = doc.add_table(4, 4).width_pct(100.0);
1004        tbl.cell(0, 0).unwrap().set_text("QUOTATION");
1005        tbl.cell(0, 0).unwrap().shading("008B8B").grid_span(2);
1006        tbl.cell(0, 2).unwrap().set_text("DATE");
1007        tbl.cell(0, 2).unwrap().shading("008B8B");
1008        tbl.cell(0, 3).unwrap().set_text("VALID UNTIL");
1009        tbl.cell(0, 3).unwrap().shading("008B8B");
1010        tbl.cell(1, 0).unwrap().set_text("Quote #:");
1011        tbl.cell(1, 1).unwrap().set_text("QT-2026-0147");
1012        tbl.cell(1, 2).unwrap().set_text("Feb 22, 2026");
1013        tbl.cell(1, 3).unwrap().set_text("Mar 22, 2026");
1014        tbl.cell(2, 0).unwrap().set_text("Prepared by:");
1015        tbl.cell(2, 1).unwrap().set_text("Walter White");
1016        tbl.cell(2, 2).unwrap().set_text("Payment:");
1017        tbl.cell(2, 3).unwrap().set_text("Net 30");
1018        tbl.cell(3, 0).unwrap().set_text("Customer:");
1019        tbl.cell(3, 1).unwrap().set_text("Meridian Dynamics LLC");
1020        tbl.cell(3, 2).unwrap().set_text("Currency:");
1021        tbl.cell(3, 3).unwrap().set_text("USD");
1022    }
1023
1024    doc.add_paragraph("");
1025
1026    // ── Bill of Materials ──
1027    {
1028        let mut p = doc.add_paragraph("");
1029        p.add_run("Bill of Materials")
1030            .bold(true)
1031            .size(16.0)
1032            .color("1A3C3C");
1033    }
1034    doc.add_paragraph("");
1035
1036    {
1037        let mut tbl = doc
1038            .add_table(10, 6)
1039            .borders(BorderStyle::Single, 2, "008B8B")
1040            .width_pct(100.0)
1041            .layout_fixed();
1042        // Headers
1043        let hdrs = [
1044            "#",
1045            "Part Number",
1046            "Description",
1047            "Qty",
1048            "Unit Price",
1049            "Total",
1050        ];
1051        for (c, h) in hdrs.iter().enumerate() {
1052            tbl.cell(0, c).unwrap().set_text(h);
1053            tbl.cell(0, c).unwrap().shading("008B8B");
1054        }
1055
1056        let items: Vec<(&str, &str, &str, &str, &str)> = vec![
1057            (
1058                "TB-GPU-A100",
1059                "NVIDIA A100 80GB GPU",
1060                "4",
1061                "$12,500.00",
1062                "$50,000.00",
1063            ),
1064            (
1065                "TB-SRV-R750",
1066                "Dell R750xa Server Chassis",
1067                "2",
1068                "$8,200.00",
1069                "$16,400.00",
1070            ),
1071            (
1072                "TB-RAM-256G",
1073                "256GB DDR5 ECC Memory Module",
1074                "8",
1075                "$890.00",
1076                "$7,120.00",
1077            ),
1078            (
1079                "TB-SSD-3840",
1080                "3.84TB NVMe U.2 SSD",
1081                "8",
1082                "$1,150.00",
1083                "$9,200.00",
1084            ),
1085            (
1086                "TB-NET-CX7",
1087                "ConnectX-7 200GbE NIC",
1088                "4",
1089                "$1,800.00",
1090                "$7,200.00",
1091            ),
1092            (
1093                "TB-SW-48P",
1094                "48-Port 100GbE Switch",
1095                "1",
1096                "$22,000.00",
1097                "$22,000.00",
1098            ),
1099            (
1100                "TB-CAB-RACK",
1101                "42U Server Rack + PDU",
1102                "1",
1103                "$4,500.00",
1104                "$4,500.00",
1105            ),
1106            (
1107                "TB-SVC-INST",
1108                "Installation & Configuration",
1109                "1",
1110                "$8,500.00",
1111                "$8,500.00",
1112            ),
1113            (
1114                "TB-SVC-SUPP",
1115                "3-Year Premium Support",
1116                "1",
1117                "$15,000.00",
1118                "$15,000.00",
1119            ),
1120        ];
1121
1122        for (i, (pn, desc, qty, unit, total)) in items.iter().enumerate() {
1123            let row = i + 1;
1124            tbl.cell(row, 0).unwrap().set_text(&format!("{}", i + 1));
1125            tbl.cell(row, 1).unwrap().set_text(pn);
1126            tbl.cell(row, 2).unwrap().set_text(desc);
1127            tbl.cell(row, 3).unwrap().set_text(qty);
1128            tbl.cell(row, 4).unwrap().set_text(unit);
1129            tbl.cell(row, 5).unwrap().set_text(total);
1130            if i % 2 == 0 {
1131                for c in 0..6 {
1132                    tbl.cell(row, c).unwrap().shading("F0F8F8");
1133                }
1134            }
1135        }
1136    }
1137
1138    doc.add_paragraph("");
1139
1140    // ── Totals ──
1141    {
1142        let mut tbl = doc
1143            .add_table(4, 2)
1144            .borders(BorderStyle::Single, 2, "008B8B")
1145            .width(Length::inches(3.5))
1146            .alignment(Alignment::Right);
1147        tbl.cell(0, 0).unwrap().set_text("Subtotal");
1148        tbl.cell(0, 1).unwrap().set_text("$139,920.00");
1149        tbl.cell(1, 0).unwrap().set_text("Shipping & Handling");
1150        tbl.cell(1, 1).unwrap().set_text("$2,500.00");
1151        tbl.cell(2, 0).unwrap().set_text("Tax (8.625%)");
1152        tbl.cell(2, 1).unwrap().set_text("$12,068.10");
1153        tbl.cell(3, 0).unwrap().set_text("TOTAL");
1154        tbl.cell(3, 0).unwrap().shading("E07020");
1155        tbl.cell(3, 1).unwrap().set_text("$154,488.10");
1156        tbl.cell(3, 1).unwrap().shading("E07020");
1157    }
1158
1159    doc.add_paragraph("");
1160
1161    // ── Terms & Conditions ──
1162    {
1163        let mut p = doc.add_paragraph("");
1164        p.add_run("Terms & Conditions")
1165            .bold(true)
1166            .size(14.0)
1167            .color("1A3C3C");
1168    }
1169    doc.add_numbered_list_item(
1170        "This quotation is valid for 30 calendar days from the date of issue.",
1171        0,
1172    );
1173    doc.add_numbered_list_item(
1174        "All prices are in USD and exclusive of applicable taxes unless stated.",
1175        0,
1176    );
1177    doc.add_numbered_list_item("Standard lead time is 4-6 weeks from PO receipt.", 0);
1178    doc.add_numbered_list_item(
1179        "Payment terms: Net 30 from invoice date. 2% discount for payment within 10 days.",
1180        0,
1181    );
1182    doc.add_numbered_list_item(
1183        "Warranty: 3-year manufacturer warranty on all hardware components.",
1184        0,
1185    );
1186    doc.add_numbered_list_item(
1187        "Returns subject to 15% restocking fee if initiated after 14 days.",
1188        0,
1189    );
1190
1191    doc.add_paragraph("");
1192    doc.add_paragraph("")
1193        .border_bottom(BorderStyle::Single, 4, "008B8B");
1194    doc.add_paragraph("");
1195    {
1196        let mut p = doc.add_paragraph("");
1197        p.add_run("Accepted by: ").bold(true);
1198        p.add_run("___________________________ Date: ___________");
1199    }
1200    {
1201        let mut p = doc.add_paragraph("");
1202        p.add_run("Print Name: ").bold(true);
1203        p.add_run("___________________________ Title: ___________");
1204    }
1205
1206    doc
1207}
1208
1209// =============================================================================
1210// 4. INVOICE — Crimson + charcoal scheme
1211// =============================================================================
1212fn generate_invoice(_samples_dir: &Path) -> Document {
1213    let mut doc = Document::new();
1214
1215    // Colors: Crimson #B22222, Charcoal #333333, Light #FAF0F0
1216    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1217    doc.set_margins(
1218        Length::inches(0.75),
1219        Length::inches(0.75),
1220        Length::inches(0.75),
1221        Length::inches(0.75),
1222    );
1223    doc.set_title("Invoice INV-2026-0392");
1224    doc.set_author("Walter White");
1225    doc.set_keywords("invoice, Tensorbee");
1226
1227    doc.set_footer("Tensorbee — Thank you for your business!");
1228
1229    // ── Company & Invoice Header ──
1230    {
1231        let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1232        // Company name left, INVOICE right
1233        {
1234            let mut cell = tbl.cell(0, 0).unwrap();
1235            let mut p = cell.add_paragraph("");
1236            p.add_run("TENSORBEE")
1237                .bold(true)
1238                .size(32.0)
1239                .color("B22222")
1240                .font("Helvetica");
1241        }
1242        {
1243            let mut cell = tbl.cell(0, 1).unwrap();
1244            let mut p = cell.add_paragraph("");
1245            p.add_run("INVOICE").bold(true).size(32.0).color("333333");
1246        }
1247        tbl.cell(1, 0)
1248            .unwrap()
1249            .set_text("123 Innovation Drive, Suite 400");
1250        tbl.cell(1, 1).unwrap().set_text("Invoice #: INV-2026-0392");
1251        tbl.cell(2, 0).unwrap().set_text("San Francisco, CA 94105");
1252        tbl.cell(2, 1).unwrap().set_text("Date: February 22, 2026");
1253        tbl.cell(3, 0).unwrap().set_text("walter@tensorbee.com");
1254        tbl.cell(3, 1).unwrap().set_text("Due Date: March 24, 2026");
1255    }
1256
1257    doc.add_paragraph("")
1258        .border_bottom(BorderStyle::Thick, 8, "B22222");
1259    doc.add_paragraph("");
1260
1261    // ── Bill To / Ship To ──
1262    {
1263        let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1264        tbl.cell(0, 0).unwrap().set_text("BILL TO");
1265        tbl.cell(0, 0).unwrap().shading("B22222");
1266        tbl.cell(0, 1).unwrap().set_text("SHIP TO");
1267        tbl.cell(0, 1).unwrap().shading("B22222");
1268        tbl.cell(1, 0).unwrap().set_text("Meridian Dynamics LLC");
1269        tbl.cell(1, 1).unwrap().set_text("Meridian Dynamics LLC");
1270        tbl.cell(2, 0).unwrap().set_text("456 Enterprise Blvd");
1271        tbl.cell(2, 1).unwrap().set_text("Attn: Server Room B");
1272        tbl.cell(3, 0).unwrap().set_text("Austin, TX 78701");
1273        tbl.cell(3, 1)
1274            .unwrap()
1275            .set_text("456 Enterprise Blvd, Austin, TX 78701");
1276    }
1277
1278    doc.add_paragraph("");
1279
1280    // ── Line Items ──
1281    {
1282        let mut tbl = doc
1283            .add_table(8, 5)
1284            .borders(BorderStyle::Single, 2, "B22222")
1285            .width_pct(100.0);
1286        let hdrs = ["Description", "Qty", "Unit Price", "Tax", "Amount"];
1287        for (c, h) in hdrs.iter().enumerate() {
1288            tbl.cell(0, c).unwrap().set_text(h);
1289            tbl.cell(0, c).unwrap().shading("B22222");
1290        }
1291        let items = [
1292            (
1293                "ML Infrastructure Setup — Phase 1",
1294                "1",
1295                "$45,000.00",
1296                "$3,881.25",
1297                "$48,881.25",
1298            ),
1299            (
1300                "Data Pipeline Development (160 hrs)",
1301                "160",
1302                "$225.00",
1303                "$3,105.00",
1304                "$39,105.00",
1305            ),
1306            (
1307                "GPU Cluster Configuration",
1308                "1",
1309                "$12,000.00",
1310                "$1,035.00",
1311                "$13,035.00",
1312            ),
1313            (
1314                "API Gateway Implementation",
1315                "1",
1316                "$18,500.00",
1317                "$1,595.63",
1318                "$20,095.63",
1319            ),
1320            (
1321                "Load Testing & QA (80 hrs)",
1322                "80",
1323                "$195.00",
1324                "$1,345.50",
1325                "$16,945.50",
1326            ),
1327            (
1328                "Documentation & Training",
1329                "1",
1330                "$8,000.00",
1331                "$690.00",
1332                "$8,690.00",
1333            ),
1334            (
1335                "Project Management (3 months)",
1336                "3",
1337                "$6,500.00",
1338                "$1,679.63",
1339                "$21,179.63",
1340            ),
1341        ];
1342        for (i, (desc, qty, unit, tax, amt)) in items.iter().enumerate() {
1343            let r = i + 1;
1344            tbl.cell(r, 0).unwrap().set_text(desc);
1345            tbl.cell(r, 1).unwrap().set_text(qty);
1346            tbl.cell(r, 2).unwrap().set_text(unit);
1347            tbl.cell(r, 3).unwrap().set_text(tax);
1348            tbl.cell(r, 4).unwrap().set_text(amt);
1349            if i % 2 == 0 {
1350                for c in 0..5 {
1351                    tbl.cell(r, c).unwrap().shading("FAF0F0");
1352                }
1353            }
1354        }
1355    }
1356
1357    doc.add_paragraph("");
1358
1359    // ── Totals ──
1360    {
1361        let mut tbl = doc
1362            .add_table(4, 2)
1363            .borders(BorderStyle::Single, 2, "B22222")
1364            .width(Length::inches(3.0))
1365            .alignment(Alignment::Right);
1366        tbl.cell(0, 0).unwrap().set_text("Subtotal");
1367        tbl.cell(0, 1).unwrap().set_text("$154,600.00");
1368        tbl.cell(1, 0).unwrap().set_text("Tax (8.625%)");
1369        tbl.cell(1, 1).unwrap().set_text("$13,334.25");
1370        tbl.cell(2, 0).unwrap().set_text("Discount (5%)");
1371        tbl.cell(2, 1).unwrap().set_text("-$7,730.00");
1372        tbl.cell(3, 0).unwrap().set_text("AMOUNT DUE");
1373        tbl.cell(3, 0).unwrap().shading("B22222");
1374        tbl.cell(3, 1).unwrap().set_text("$160,204.25");
1375        tbl.cell(3, 1).unwrap().shading("B22222");
1376    }
1377
1378    doc.add_paragraph("");
1379    doc.add_paragraph("");
1380
1381    // ── Payment Details ──
1382    {
1383        let mut p = doc.add_paragraph("");
1384        p.add_run("Payment Details")
1385            .bold(true)
1386            .size(14.0)
1387            .color("333333");
1388    }
1389    doc.add_paragraph("Bank: Silicon Valley Bank")
1390        .indent_left(Length::inches(0.3));
1391    doc.add_paragraph("Account: Tensorbee Inc. — 0847-2953-1120")
1392        .indent_left(Length::inches(0.3));
1393    doc.add_paragraph("Routing: 121140399")
1394        .indent_left(Length::inches(0.3));
1395    doc.add_paragraph("Swift: SVBKUS6S")
1396        .indent_left(Length::inches(0.3));
1397
1398    doc.add_paragraph("");
1399
1400    doc.add_paragraph("Please include invoice number INV-2026-0392 in the payment reference.")
1401        .shading("FAF0F0")
1402        .border_all(BorderStyle::Single, 2, "B22222");
1403
1404    doc
1405}
1406
1407// =============================================================================
1408// 5. REPORT — Forest green + earth tones, with images & hierarchical sections
1409// =============================================================================
1410fn generate_report(_samples_dir: &Path) -> Document {
1411    let mut doc = Document::new();
1412
1413    // Colors: Forest #2D5016, Sage #6B8E23, Earth #8B7355, Cream #FFFAF0
1414    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1415    doc.set_margins(
1416        Length::inches(1.0),
1417        Length::inches(1.0),
1418        Length::inches(1.0),
1419        Length::inches(1.0),
1420    );
1421    doc.set_title("Q4 2025 Environmental Impact Report");
1422    doc.set_author("Walter White");
1423    doc.set_subject("Quarterly Environmental Report");
1424    doc.set_keywords("environment, sustainability, report, Tensorbee");
1425
1426    doc.set_different_first_page(true);
1427    doc.set_first_page_header("");
1428    doc.set_header("Tensorbee — Q4 2025 Environmental Impact Report");
1429    doc.set_footer("CONFIDENTIAL — Page");
1430
1431    // ── Cover ──
1432    let cover_bg = create_sample_png(612, 792, [20, 50, 15]);
1433    doc.add_background_image(&cover_bg, "report_cover.png");
1434
1435    for _ in 0..5 {
1436        doc.add_paragraph("");
1437    }
1438    {
1439        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1440        p.add_run("Q4 2025")
1441            .bold(true)
1442            .size(48.0)
1443            .color("FFFFFF")
1444            .font("Georgia");
1445    }
1446    {
1447        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1448        p.add_run("Environmental Impact Report")
1449            .size(24.0)
1450            .color("90EE90")
1451            .font("Georgia")
1452            .italic(true);
1453    }
1454    doc.add_paragraph("");
1455    {
1456        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1457        p.add_run("Tensorbee — Sustainability Division")
1458            .size(14.0)
1459            .color("C0C0C0");
1460    }
1461    {
1462        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1463        p.add_run("Prepared by Walter White, Chief Sustainability Officer")
1464            .size(11.0)
1465            .color("AAAAAA");
1466    }
1467
1468    // ── TOC ──
1469    doc.add_paragraph("").page_break_before(true);
1470    doc.insert_toc(doc.content_count(), 3);
1471
1472    // ── Section 1: Executive Overview ──
1473    doc.add_paragraph("").page_break_before(true);
1474    doc.add_paragraph("Executive Overview").style("Heading1");
1475    doc.add_paragraph(
1476        "This report presents Tensorbee's environmental performance for Q4 2025. Our \
1477         sustainability initiatives have yielded a 34% reduction in carbon emissions compared \
1478         to Q4 2024, exceeding our target of 25%. Key achievements include the transition to \
1479         100% renewable energy in our primary data centers and the launch of our carbon offset \
1480         marketplace.",
1481    )
1482    .first_line_indent(Length::inches(0.3));
1483
1484    // Image: performance chart
1485    let chart_img = create_chart_png(400, 200);
1486    doc.add_paragraph("");
1487    doc.add_picture(
1488        &chart_img,
1489        "performance_chart.png",
1490        Length::inches(5.0),
1491        Length::inches(2.5),
1492    )
1493    .alignment(Alignment::Center);
1494    {
1495        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1496        p.add_run("Figure 1: Quarterly Carbon Emissions (tonnes CO2e)")
1497            .italic(true)
1498            .size(9.0)
1499            .color("666666");
1500    }
1501
1502    // ── Section 2: Energy Consumption ──
1503    doc.add_paragraph("").page_break_before(true);
1504    doc.add_paragraph("Energy Consumption").style("Heading1");
1505
1506    doc.add_paragraph("Data Center Operations")
1507        .style("Heading2");
1508    doc.add_paragraph(
1509        "Our three primary data centers consumed a combined 4.2 GWh in Q4 2025, \
1510         a 12% reduction from Q3 2025 driven by improved cooling efficiency and \
1511         server consolidation.",
1512    );
1513
1514    // Data table
1515    {
1516        let mut tbl = doc
1517            .add_table(5, 4)
1518            .borders(BorderStyle::Single, 4, "2D5016")
1519            .width_pct(100.0);
1520        let hdrs = ["Data Center", "Capacity (MW)", "Usage (GWh)", "PUE"];
1521        for (c, h) in hdrs.iter().enumerate() {
1522            tbl.cell(0, c).unwrap().set_text(h);
1523            tbl.cell(0, c).unwrap().shading("2D5016");
1524        }
1525        let rows = [
1526            ("San Francisco (Primary)", "3.2", "1.8", "1.12"),
1527            ("Dublin (EU)", "2.1", "1.2", "1.18"),
1528            ("Singapore (APAC)", "1.8", "1.2", "1.24"),
1529            ("TOTAL", "7.1", "4.2", "1.17 avg"),
1530        ];
1531        for (i, (dc, cap, use_, pue)) in rows.iter().enumerate() {
1532            tbl.cell(i + 1, 0).unwrap().set_text(dc);
1533            tbl.cell(i + 1, 1).unwrap().set_text(cap);
1534            tbl.cell(i + 1, 2).unwrap().set_text(use_);
1535            tbl.cell(i + 1, 3).unwrap().set_text(pue);
1536            if i == 3 {
1537                for c in 0..4 {
1538                    tbl.cell(i + 1, c).unwrap().shading("E2EFDA");
1539                }
1540            }
1541        }
1542    }
1543
1544    doc.add_paragraph("");
1545
1546    doc.add_paragraph("Renewable Energy Mix").style("Heading2");
1547    doc.add_paragraph("Breakdown of energy sources across all facilities:");
1548
1549    doc.add_bullet_list_item("Solar PV: 42% (1.76 GWh)", 0);
1550    doc.add_bullet_list_item("Wind Power Purchase Agreements: 38% (1.60 GWh)", 0);
1551    doc.add_bullet_list_item("Hydroelectric: 12% (0.50 GWh)", 0);
1552    doc.add_bullet_list_item("Grid (non-renewable): 8% (0.34 GWh)", 0);
1553
1554    // ── Section 3: Water & Waste ──
1555    doc.add_paragraph("Water & Waste Management")
1556        .style("Heading1");
1557
1558    doc.add_paragraph("Water Usage").style("Heading2");
1559    doc.add_paragraph(
1560        "Total water consumption was 12.4 million gallons, a 15% reduction from Q3. \
1561         Our closed-loop cooling systems now recycle 78% of water used in cooling operations.",
1562    );
1563
1564    doc.add_paragraph("Waste Reduction").style("Heading2");
1565    doc.add_paragraph("E-waste management results for Q4:")
1566        .keep_with_next(true);
1567    {
1568        let mut tbl = doc
1569            .add_table(4, 3)
1570            .borders(BorderStyle::Single, 2, "6B8E23")
1571            .width_pct(80.0);
1572        tbl.cell(0, 0).unwrap().set_text("Category");
1573        tbl.cell(0, 0).unwrap().shading("6B8E23");
1574        tbl.cell(0, 1).unwrap().set_text("Weight (kg)");
1575        tbl.cell(0, 1).unwrap().shading("6B8E23");
1576        tbl.cell(0, 2).unwrap().set_text("Recycled %");
1577        tbl.cell(0, 2).unwrap().shading("6B8E23");
1578        let rows = [
1579            ("Server Hardware", "2,450", "94%"),
1580            ("Networking Equipment", "820", "91%"),
1581            ("Storage Media", "340", "99%"),
1582        ];
1583        for (i, (cat, wt, pct)) in rows.iter().enumerate() {
1584            tbl.cell(i + 1, 0).unwrap().set_text(cat);
1585            tbl.cell(i + 1, 1).unwrap().set_text(wt);
1586            tbl.cell(i + 1, 2).unwrap().set_text(pct);
1587        }
1588    }
1589
1590    // ── Section 4: Initiatives ──
1591    doc.add_paragraph("").page_break_before(true);
1592    doc.add_paragraph("Q1 2026 Initiatives").style("Heading1");
1593
1594    doc.add_paragraph("Planned Programs").style("Heading2");
1595    doc.add_numbered_list_item(
1596        "Deploy on-site battery storage at SF data center (2 MWh capacity)",
1597        0,
1598    );
1599    doc.add_numbered_list_item("Pilot immersion cooling in Dublin facility", 0);
1600    doc.add_numbered_list_item("Launch employee commute carbon offset program", 0);
1601    doc.add_numbered_list_item("Achieve ISO 14001 certification for Singapore facility", 0);
1602
1603    doc.add_paragraph("Investment Targets").style("Heading2");
1604    doc.add_paragraph("Sustainability CapEx allocation for FY2026:")
1605        .keep_with_next(true);
1606    {
1607        let mut tbl = doc
1608            .add_table(5, 2)
1609            .borders(BorderStyle::Single, 4, "2D5016");
1610        tbl.cell(0, 0).unwrap().set_text("Initiative");
1611        tbl.cell(0, 0).unwrap().shading("2D5016");
1612        tbl.cell(0, 1).unwrap().set_text("Budget");
1613        tbl.cell(0, 1).unwrap().shading("2D5016");
1614        let rows = [
1615            ("Battery Storage", "$1.2M"),
1616            ("Immersion Cooling Pilot", "$800K"),
1617            ("Solar Panel Expansion", "$2.1M"),
1618            ("Carbon Credits", "$500K"),
1619        ];
1620        for (i, (init, budget)) in rows.iter().enumerate() {
1621            tbl.cell(i + 1, 0).unwrap().set_text(init);
1622            tbl.cell(i + 1, 1).unwrap().set_text(budget);
1623            if i % 2 == 0 {
1624                tbl.cell(i + 1, 0).unwrap().shading("FFFAF0");
1625                tbl.cell(i + 1, 1).unwrap().shading("FFFAF0");
1626            }
1627        }
1628    }
1629
1630    // Image: sustainability roadmap
1631    let roadmap_img = create_sample_png(500, 100, [40, 80, 30]);
1632    doc.add_paragraph("");
1633    doc.add_picture(
1634        &roadmap_img,
1635        "roadmap.png",
1636        Length::inches(6.0),
1637        Length::inches(1.2),
1638    )
1639    .alignment(Alignment::Center);
1640    {
1641        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1642        p.add_run("Figure 2: 2026 Sustainability Roadmap")
1643            .italic(true)
1644            .size(9.0)
1645            .color("666666");
1646    }
1647
1648    doc.add_paragraph("");
1649    doc.add_paragraph(
1650        "For questions about this report, contact Walter White at sustainability@tensorbee.com.",
1651    )
1652    .shading("FFFAF0")
1653    .border_all(BorderStyle::Single, 2, "2D5016");
1654
1655    doc
1656}
Source

pub fn title(&self) -> Option<&str>

Get the document title.

Source

pub fn set_title(&mut self, title: &str)

Set the document title.

Examples found in repository?
examples/generate_pdf.rs (line 20)
7fn main() {
8    // Test 1: Simple document
9    let doc = Document::new();
10    match doc.to_pdf() {
11        Ok(bytes) => {
12            std::fs::write("/tmp/rdocx_simple.pdf", &bytes).unwrap();
13            println!("Simple PDF: {} bytes -> /tmp/rdocx_simple.pdf", bytes.len());
14        }
15        Err(e) => println!("Simple PDF failed: {e}"),
16    }
17
18    // Test 2: Document with content
19    let mut doc = Document::new();
20    doc.set_title("Test PDF Document");
21    doc.set_author("rdocx-pdf");
22    doc.add_paragraph("Chapter 1: Introduction")
23        .style("Heading1");
24    doc.add_paragraph(
25        "This is a test document generated by rdocx and rendered to PDF. \
26         It demonstrates text rendering with proper font shaping and pagination.",
27    );
28    doc.add_paragraph("Section 1.1").style("Heading2");
29    doc.add_paragraph("More content in a sub-section.");
30
31    {
32        let mut table = doc.add_table(2, 3);
33        for r in 0..2 {
34            for c in 0..3 {
35                if let Some(mut cell) = table.cell(r, c) {
36                    cell.set_text(&format!("R{}C{}", r + 1, c + 1));
37                }
38            }
39        }
40    }
41
42    doc.add_paragraph("After the table.");
43
44    match doc.to_pdf() {
45        Ok(bytes) => {
46            std::fs::write("/tmp/rdocx_content.pdf", &bytes).unwrap();
47            println!(
48                "Content PDF: {} bytes -> /tmp/rdocx_content.pdf",
49                bytes.len()
50            );
51        }
52        Err(e) => println!("Content PDF failed: {e}"),
53    }
54
55    // Test 3: From feature_showcase.docx
56    let showcase_path = concat!(
57        env!("CARGO_MANIFEST_DIR"),
58        "/../../samples/feature_showcase.docx"
59    );
60    match Document::open(showcase_path) {
61        Ok(doc) => match doc.to_pdf() {
62            Ok(bytes) => {
63                std::fs::write("/tmp/rdocx_showcase.pdf", &bytes).unwrap();
64                println!(
65                    "Showcase PDF: {} bytes -> /tmp/rdocx_showcase.pdf",
66                    bytes.len()
67                );
68            }
69            Err(e) => println!("Showcase PDF failed: {e}"),
70        },
71        Err(e) => println!("Failed to open showcase: {e}"),
72    }
73}
More examples
Hide additional examples
examples/template_replace.rs (line 155)
43fn create_template(path: &Path) {
44    let mut doc = Document::new();
45    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
46    doc.set_margins(
47        Length::inches(1.0),
48        Length::inches(1.0),
49        Length::inches(1.0),
50        Length::inches(1.0),
51    );
52
53    doc.set_header("{{company_name}} — Confidential");
54    doc.set_footer("Prepared by {{author_name}} on {{date}}");
55
56    // ── Title ──
57    doc.add_paragraph("{{company_name}}")
58        .style("Heading1")
59        .alignment(Alignment::Center);
60
61    doc.add_paragraph("Project Proposal")
62        .alignment(Alignment::Center);
63
64    doc.add_paragraph("");
65
66    // ── Summary section ──
67    doc.add_paragraph("Executive Summary").style("Heading2");
68
69    doc.add_paragraph(
70        "This proposal outlines the {{project_name}} project for {{company_name}}. \
71         The primary contact is {{contact_name}} ({{contact_email}}). \
72         The proposed start date is {{start_date}} with an estimated duration of {{duration}}.",
73    );
74
75    doc.add_paragraph("");
76
77    // ── Cross-run placeholder (bold label + normal value) ──
78    doc.add_paragraph("Key Details").style("Heading2");
79
80    {
81        let mut p = doc.add_paragraph("");
82        p.add_run("Project: ").bold(true);
83        p.add_run("{{project_name}}");
84    }
85    {
86        let mut p = doc.add_paragraph("");
87        p.add_run("Budget: ").bold(true);
88        p.add_run("{{budget}}");
89    }
90    {
91        let mut p = doc.add_paragraph("");
92        p.add_run("Status: ").bold(true);
93        p.add_run("{{status}}");
94    }
95
96    doc.add_paragraph("");
97
98    // ── Table with placeholders ──
99    doc.add_paragraph("Team Members").style("Heading2");
100
101    {
102        let mut tbl = doc.add_table(4, 3);
103        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
104
105        // Header row
106        for col in 0..3 {
107            tbl.cell(0, col).unwrap().shading("2E75B6");
108        }
109        tbl.cell(0, 0).unwrap().set_text("Name");
110        tbl.cell(0, 1).unwrap().set_text("Role");
111        tbl.cell(0, 2).unwrap().set_text("Email");
112
113        tbl.cell(1, 0).unwrap().set_text("{{member1_name}}");
114        tbl.cell(1, 1).unwrap().set_text("{{member1_role}}");
115        tbl.cell(1, 2).unwrap().set_text("{{member1_email}}");
116
117        tbl.cell(2, 0).unwrap().set_text("{{member2_name}}");
118        tbl.cell(2, 1).unwrap().set_text("{{member2_role}}");
119        tbl.cell(2, 2).unwrap().set_text("{{member2_email}}");
120
121        tbl.cell(3, 0).unwrap().set_text("{{member3_name}}");
122        tbl.cell(3, 1).unwrap().set_text("{{member3_role}}");
123        tbl.cell(3, 2).unwrap().set_text("{{member3_email}}");
124    }
125
126    doc.add_paragraph("");
127
128    // ── Deliverables section ──
129    doc.add_paragraph("Deliverables").style("Heading2");
130
131    doc.add_paragraph("INSERTION_POINT");
132
133    doc.add_paragraph("");
134
135    // ── Signature block ──
136    doc.add_paragraph("Acceptance").style("Heading2");
137
138    doc.add_paragraph(
139        "By signing below, {{company_name}} agrees to the terms outlined in this proposal.",
140    );
141
142    {
143        let mut tbl = doc.add_table(2, 2);
144        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
145        tbl.cell(0, 0)
146            .unwrap()
147            .set_text("Customer: ___________________");
148        tbl.cell(0, 1)
149            .unwrap()
150            .set_text("Provider: ___________________");
151        tbl.cell(1, 0).unwrap().set_text("Date: {{date}}");
152        tbl.cell(1, 1).unwrap().set_text("Date: {{date}}");
153    }
154
155    doc.set_title("{{company_name}} — Project Proposal Template");
156    doc.set_author("Template Generator");
157
158    doc.save(path).unwrap();
159}
160
161/// Open the template, replace all placeholders, insert content, and save.
162fn fill_template(template_path: &Path, output_path: &Path) {
163    let mut doc = Document::open(template_path).unwrap();
164
165    // ── Batch replacement ──
166    let mut replacements = HashMap::new();
167    replacements.insert("{{company_name}}", "Riverside Medical Center");
168    replacements.insert("{{project_name}}", "Network Security Upgrade");
169    replacements.insert("{{contact_name}}", "Dr. Sarah Chen");
170    replacements.insert("{{contact_email}}", "s.chen@riverside.org");
171    replacements.insert("{{start_date}}", "March 1, 2026");
172    replacements.insert("{{duration}}", "12 weeks");
173    replacements.insert("{{budget}}", "$185,000");
174    replacements.insert("{{status}}", "Pending Approval");
175    replacements.insert("{{author_name}}", "James Wilson");
176    replacements.insert("{{date}}", "February 22, 2026");
177
178    // Team members
179    replacements.insert("{{member1_name}}", "James Wilson");
180    replacements.insert("{{member1_role}}", "Project Lead");
181    replacements.insert("{{member1_email}}", "j.wilson@provider.com");
182    replacements.insert("{{member2_name}}", "Maria Garcia");
183    replacements.insert("{{member2_role}}", "Security Architect");
184    replacements.insert("{{member2_email}}", "m.garcia@provider.com");
185    replacements.insert("{{member3_name}}", "David Park");
186    replacements.insert("{{member3_role}}", "Network Engineer");
187    replacements.insert("{{member3_email}}", "d.park@provider.com");
188
189    let count = doc.replace_all(&replacements);
190    println!("  Replaced {} placeholders", count);
191
192    // ── Insert deliverables at the insertion point ──
193    if let Some(idx) = doc.find_content_index("INSERTION_POINT") {
194        // Remove the placeholder paragraph
195        doc.remove_content(idx);
196
197        // Insert deliverables list
198        doc.insert_paragraph(idx, "The following deliverables are included:");
199
200        // Insert a deliverables table
201        let mut tbl = doc.insert_table(idx + 1, 5, 3);
202        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
203
204        for col in 0..3 {
205            tbl.cell(0, col).unwrap().shading("E2EFDA");
206        }
207        tbl.cell(0, 0).unwrap().set_text("Phase");
208        tbl.cell(0, 1).unwrap().set_text("Description");
209        tbl.cell(0, 2).unwrap().set_text("Timeline");
210
211        tbl.cell(1, 0).unwrap().set_text("1. Discovery");
212        tbl.cell(1, 1)
213            .unwrap()
214            .set_text("Network assessment and asset inventory");
215        tbl.cell(1, 2).unwrap().set_text("Weeks 1-3");
216
217        tbl.cell(2, 0).unwrap().set_text("2. Design");
218        tbl.cell(2, 1)
219            .unwrap()
220            .set_text("Security architecture and policy design");
221        tbl.cell(2, 2).unwrap().set_text("Weeks 4-6");
222
223        tbl.cell(3, 0).unwrap().set_text("3. Implementation");
224        tbl.cell(3, 1)
225            .unwrap()
226            .set_text("Deploy monitoring and access controls");
227        tbl.cell(3, 2).unwrap().set_text("Weeks 7-10");
228
229        tbl.cell(4, 0).unwrap().set_text("4. Validation");
230        tbl.cell(4, 1)
231            .unwrap()
232            .set_text("Testing, training, and handover");
233        tbl.cell(4, 2).unwrap().set_text("Weeks 11-12");
234
235        println!("  Inserted deliverables table at position {}", idx);
236    }
237
238    // ── Update metadata ──
239    doc.set_title("Riverside Medical Center — Network Security Upgrade Proposal");
240    doc.set_author("James Wilson");
241    doc.set_subject("Project Proposal");
242    doc.set_keywords("security, network, medical, proposal");
243
244    doc.save(output_path).unwrap();
245}
examples/header_banner.rs (line 165)
29fn generate_header_banner_doc(path: &Path) {
30    let mut doc = Document::new();
31
32    // Page setup with extra top margin for the banner
33    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
34    doc.set_margins(
35        Length::twips(2292), // top — extra tall for header banner
36        Length::twips(1440), // right
37        Length::twips(1440), // bottom
38        Length::twips(1440), // left
39    );
40    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
41
42    // Generate a simple logo image (white text on transparent background)
43    let logo_img = create_logo_png(220, 48);
44
45    // ── Dark blue banner ──
46    let banner = build_header_banner_xml(
47        "rId1",
48        &BannerOpts {
49            bg_color: "1A3C6E",
50            banner_width: 7772400, // full page width in EMU (~8.5")
51            banner_height: 969026, // banner height in EMU (~1.06")
52            logo_width: 2011680,   // logo display width (~2.2")
53            logo_height: 438912,   // logo display height (~0.48")
54            logo_x_offset: 295125, // left padding
55            logo_y_offset: 265057, // vertical centering
56        },
57    );
58
59    doc.set_raw_header_with_images(
60        banner.clone(),
61        &[("rId1", &logo_img, "logo.png")],
62        rdocx_oxml::header_footer::HdrFtrType::Default,
63    );
64
65    // Use a different first page header (same banner, different color)
66    doc.set_different_first_page(true);
67    let first_page_banner = build_header_banner_xml(
68        "rId1",
69        &BannerOpts {
70            bg_color: "2E75B6", // lighter blue for cover
71            banner_width: 7772400,
72            banner_height: 969026,
73            logo_width: 2011680,
74            logo_height: 438912,
75            logo_x_offset: 295125,
76            logo_y_offset: 265057,
77        },
78    );
79    doc.set_raw_header_with_images(
80        first_page_banner,
81        &[("rId1", &logo_img, "logo.png")],
82        rdocx_oxml::header_footer::HdrFtrType::First,
83    );
84
85    // Footer
86    doc.set_footer("Confidential — Internal Use Only");
87
88    // ── Page 1: Cover ──
89    doc.add_paragraph("Company Report").style("Heading1");
90
91    doc.add_paragraph(
92        "This document demonstrates a custom header banner built with DrawingML \
93         group shapes. The banner uses a colored rectangle with a logo image overlaid, \
94         positioned at the top of each page.",
95    );
96
97    doc.add_paragraph("");
98
99    doc.add_paragraph("How the Header Banner Works")
100        .style("Heading2");
101
102    doc.add_paragraph(
103        "The header banner is built using set_raw_header_with_images(), which \
104         accepts raw XML and a list of (rel_id, image_data, filename) tuples. \
105         The XML uses a DrawingML group shape (wpg:wgp) containing:",
106    );
107
108    doc.add_bullet_list_item(
109        "A wps:wsp rectangle shape with a solid color fill (the background bar)",
110        0,
111    );
112    doc.add_bullet_list_item(
113        "A pic:pic image element positioned within the group (the logo)",
114        0,
115    );
116    doc.add_bullet_list_item(
117        "The group is wrapped in a wp:anchor element for absolute page positioning",
118        0,
119    );
120
121    doc.add_paragraph("");
122
123    doc.add_paragraph("Customization").style("Heading2");
124
125    doc.add_paragraph(
126        "All dimensions are in EMU (English Metric Units) where 914400 EMU = 1 inch. \
127         You can customize:",
128    );
129
130    doc.add_bullet_list_item("bg_color — any hex color for the rectangle background", 0);
131    doc.add_bullet_list_item("banner_width / banner_height — size of the full banner", 0);
132    doc.add_bullet_list_item("logo_width / logo_height — display size of the logo", 0);
133    doc.add_bullet_list_item(
134        "logo_x_offset / logo_y_offset — logo position within the banner",
135        0,
136    );
137
138    doc.add_paragraph("");
139
140    doc.add_paragraph("Different First Page").style("Heading2");
141
142    doc.add_paragraph(
143        "This page uses a lighter blue banner (first page header). \
144         Subsequent pages use a darker blue banner (default header). \
145         Use set_different_first_page(true) to enable this.",
146    );
147
148    // ── Page 2 ──
149    doc.add_paragraph("").page_break_before(true);
150
151    doc.add_paragraph("Second Page").style("Heading1");
152
153    doc.add_paragraph(
154        "This page shows the default header banner (dark blue). The first page \
155         had a lighter blue banner because we set a different first-page header.",
156    );
157
158    doc.add_paragraph("");
159
160    doc.add_paragraph(
161        "The banner repeats on every page because it is placed in the header part. \
162         You can have different banners for default, first-page, and even-page headers.",
163    );
164
165    doc.set_title("Header Banner Example");
166    doc.set_author("rdocx");
167
168    doc.save(path).unwrap();
169}
examples/styled_tables.rs (line 445)
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}
examples/generate_all_samples.rs (line 110)
97fn generate_feature_showcase(_samples_dir: &Path) -> Document {
98    let mut doc = Document::new();
99
100    // ── Page Setup & Metadata ──
101    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
102    doc.set_margins(
103        Length::inches(1.0),
104        Length::inches(1.0),
105        Length::inches(1.0),
106        Length::inches(1.0),
107    );
108    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
109    doc.set_gutter(Length::twips(0));
110    doc.set_title("rdocx Feature Showcase");
111    doc.set_author("rdocx Sample Generator");
112    doc.set_subject("Comprehensive feature demonstration");
113    doc.set_keywords("rdocx, docx, rust, sample, showcase");
114
115    // Headers & Footers with different first page
116    doc.set_different_first_page(true);
117    doc.set_first_page_header("rdocx");
118    doc.set_first_page_footer("Feature Showcase — Cover Page");
119    doc.set_header("rdocx Feature Showcase");
120    doc.set_footer("Generated by rdocx");
121
122    // ── COVER PAGE ──
123    let bg = create_sample_png(612, 792, [20, 45, 90]);
124    doc.add_background_image(&bg, "cover_bg.png");
125
126    for _ in 0..3 {
127        doc.add_paragraph("");
128    }
129    {
130        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
131        p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
132    }
133    {
134        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
135        p.add_run("Complete Feature Showcase")
136            .size(28.0)
137            .color("FFFFFF")
138            .italic(true);
139    }
140    doc.add_paragraph("");
141    {
142        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
143        p.add_run("Every feature of the rdocx Rust library")
144            .size(13.0)
145            .color("B0C4DE");
146    }
147    {
148        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
149        p.add_run("demonstrated in a single document.")
150            .size(13.0)
151            .color("B0C4DE");
152    }
153
154    // ── TABLE OF CONTENTS ──
155    doc.add_paragraph("").page_break_before(true);
156    doc.insert_toc(doc.content_count(), 3);
157
158    // ── SECTION 1: TEXT FORMATTING ──
159    doc.add_paragraph("").page_break_before(true);
160    doc.add_paragraph("1. Text Formatting").style("Heading1");
161
162    // Paragraph Alignment
163    doc.add_paragraph("1.1 Paragraph Alignment")
164        .style("Heading2");
165    doc.add_paragraph("Left-aligned paragraph (default).")
166        .alignment(Alignment::Left);
167    doc.add_paragraph("Center-aligned paragraph.")
168        .alignment(Alignment::Center);
169    doc.add_paragraph("Right-aligned paragraph.")
170        .alignment(Alignment::Right);
171    doc.add_paragraph(
172        "Justified paragraph — this text is long enough to demonstrate how justified alignment \
173         distributes extra space across word gaps so lines fill the full width of the text area.",
174    )
175    .alignment(Alignment::Justify);
176
177    doc.add_paragraph("");
178
179    // Run Formatting
180    doc.add_paragraph("1.2 Run Formatting").style("Heading2");
181    {
182        let mut p = doc.add_paragraph("");
183        p.add_run("Bold").bold(true);
184        p.add_run(" | ");
185        p.add_run("Italic").italic(true);
186        p.add_run(" | ");
187        p.add_run("Bold Italic").bold(true).italic(true);
188        p.add_run(" | ");
189        p.add_run("Underline").underline(true);
190        p.add_run(" | ");
191        p.add_run("Strikethrough").strike(true);
192        p.add_run(" | ");
193        p.add_run("Double Strike").double_strike(true);
194    }
195    {
196        let mut p = doc.add_paragraph("");
197        p.add_run("Red").color("FF0000");
198        p.add_run(" | ");
199        p.add_run("Blue").color("0000FF");
200        p.add_run(" | ");
201        p.add_run("Green").color("00AA00");
202        p.add_run(" | ");
203        p.add_run("Highlighted").highlight("FFFF00");
204    }
205    {
206        let mut p = doc.add_paragraph("");
207        p.add_run("8pt").size(8.0);
208        p.add_run(" | ");
209        p.add_run("11pt").size(11.0);
210        p.add_run(" | ");
211        p.add_run("16pt").size(16.0);
212        p.add_run(" | ");
213        p.add_run("24pt").size(24.0);
214    }
215    {
216        let mut p = doc.add_paragraph("");
217        p.add_run("Arial").font("Arial");
218        p.add_run(" | ");
219        p.add_run("Times New Roman").font("Times New Roman");
220        p.add_run(" | ");
221        p.add_run("Courier New").font("Courier New");
222    }
223    {
224        let mut p = doc.add_paragraph("");
225        p.add_run("H");
226        p.add_run("2").subscript();
227        p.add_run("O (subscript) | E=mc");
228        p.add_run("2").superscript();
229        p.add_run(" (superscript)");
230    }
231    {
232        let mut p = doc.add_paragraph("");
233        p.add_run("ALL CAPS").all_caps(true);
234        p.add_run(" | ");
235        p.add_run("Small Caps").small_caps(true);
236        p.add_run(" | ");
237        p.add_run("Expanded").character_spacing(Length::twips(40));
238        p.add_run(" | ");
239        p.add_run("Hidden text (not visible)").hidden(true);
240    }
241
242    doc.add_paragraph("");
243
244    // Underline Styles
245    doc.add_paragraph("1.3 Underline Styles").style("Heading2");
246    {
247        let mut p = doc.add_paragraph("");
248        p.add_run("Single ")
249            .underline_style(rdocx::UnderlineStyle::Single);
250        p.add_run("Double ")
251            .underline_style(rdocx::UnderlineStyle::Double);
252        p.add_run("Thick ")
253            .underline_style(rdocx::UnderlineStyle::Thick);
254        p.add_run("Dotted ")
255            .underline_style(rdocx::UnderlineStyle::Dotted);
256        p.add_run("Dash ")
257            .underline_style(rdocx::UnderlineStyle::Dash);
258        p.add_run("Wave ")
259            .underline_style(rdocx::UnderlineStyle::Wave);
260    }
261
262    // ── SECTION 2: PARAGRAPH FORMATTING ──
263    doc.add_paragraph("").page_break_before(true);
264    doc.add_paragraph("2. Paragraph Formatting")
265        .style("Heading1");
266
267    doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
268    doc.add_paragraph("Paragraph with light green shading")
269        .shading("E2EFDA");
270    doc.add_paragraph("Paragraph with bottom border")
271        .border_bottom(BorderStyle::Single, 6, "2E75B6");
272    doc.add_paragraph("Paragraph with all borders (red)")
273        .border_all(BorderStyle::Single, 4, "FF0000");
274
275    doc.add_paragraph("2.2 Indentation").style("Heading2");
276    doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
277        .indent_left(Length::inches(1.0))
278        .hanging_indent(Length::inches(0.5));
279    doc.add_paragraph("0.5-inch first-line indent on this paragraph")
280        .first_line_indent(Length::inches(0.5));
281    doc.add_paragraph("Both left and right indent (0.75 inches each)")
282        .indent_left(Length::inches(0.75))
283        .indent_right(Length::inches(0.75));
284
285    doc.add_paragraph("2.3 Spacing & Line Height")
286        .style("Heading2");
287    doc.add_paragraph("Extra space before (24pt) and after (12pt)")
288        .space_before(Length::pt(24.0))
289        .space_after(Length::pt(12.0));
290    doc.add_paragraph(
291        "Double line spacing paragraph. This text should have extra vertical space between \
292         lines to demonstrate line_spacing_multiple(2.0).",
293    )
294    .line_spacing_multiple(2.0);
295    doc.add_paragraph("Exact 20pt line spacing (fixed height).")
296        .line_spacing(20.0);
297
298    doc.add_paragraph("2.4 Pagination Controls")
299        .style("Heading2");
300    doc.add_paragraph("keep_with_next — this paragraph stays with the next")
301        .keep_with_next(true);
302    doc.add_paragraph("(This paragraph was kept with the one above.)");
303    doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
304        .keep_together(true);
305    doc.add_paragraph("widow_control — prevents widow/orphan lines")
306        .widow_control(true);
307
308    // ── SECTION 3: LISTS ──
309    doc.add_paragraph("").page_break_before(true);
310    doc.add_paragraph("3. Lists").style("Heading1");
311
312    doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
313    doc.add_bullet_list_item("First item", 0);
314    doc.add_bullet_list_item("Second item", 0);
315    doc.add_bullet_list_item("Nested level 1", 1);
316    doc.add_bullet_list_item("Nested level 2", 2);
317    doc.add_bullet_list_item("Back to level 1", 1);
318    doc.add_bullet_list_item("Third item", 0);
319
320    doc.add_paragraph("");
321
322    doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
323    doc.add_numbered_list_item("First numbered item", 0);
324    doc.add_numbered_list_item("Second numbered item", 0);
325    doc.add_numbered_list_item("Sub-item A", 1);
326    doc.add_numbered_list_item("Sub-item B", 1);
327    doc.add_numbered_list_item("Third numbered item", 0);
328
329    // ── SECTION 4: TAB STOPS ──
330    doc.add_paragraph("");
331    doc.add_paragraph("4. Tab Stops").style("Heading1");
332
333    doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
334    doc.add_paragraph("Left\tCenter\tRight\tDecimal")
335        .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
336        .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
337        .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
338        .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
339
340    doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
341    doc.add_paragraph("Item\t\tPrice")
342        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
343        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
344        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
345    doc.add_paragraph("Widget\t\t$19.99")
346        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
347        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
348        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
349    doc.add_paragraph("Gadget\t\t$249.50")
350        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
351        .add_tab_stop_with_leader(
352            TabAlignment::Right,
353            Length::inches(4.0),
354            TabLeader::Underscore,
355        )
356        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
357
358    // ── SECTION 5: TABLES ──
359    doc.add_paragraph("").page_break_before(true);
360    doc.add_paragraph("5. Tables").style("Heading1");
361
362    doc.add_paragraph("5.1 Basic Table").style("Heading2");
363    {
364        let mut tbl = doc
365            .add_table(4, 3)
366            .borders(BorderStyle::Single, 4, "000000");
367        for col in 0..3 {
368            tbl.cell(0, col).unwrap().shading("2E75B6");
369        }
370        tbl.cell(0, 0).unwrap().set_text("Name");
371        tbl.cell(0, 1).unwrap().set_text("Role");
372        tbl.cell(0, 2).unwrap().set_text("Location");
373        tbl.cell(1, 0).unwrap().set_text("Walter White");
374        tbl.cell(1, 1).unwrap().set_text("CEO");
375        tbl.cell(1, 2).unwrap().set_text("Albuquerque");
376        tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
377        tbl.cell(2, 1).unwrap().set_text("CTO");
378        tbl.cell(2, 2).unwrap().set_text("Remote");
379        tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
380        tbl.cell(3, 1).unwrap().set_text("Security");
381        tbl.cell(3, 2).unwrap().set_text("Washington");
382    }
383
384    doc.add_paragraph("");
385
386    doc.add_paragraph("5.2 Column Span & Cell Shading")
387        .style("Heading2");
388    {
389        let mut tbl = doc
390            .add_table(3, 4)
391            .borders(BorderStyle::Single, 4, "000000")
392            .width_pct(100.0);
393        tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
394        tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
395        tbl.cell(1, 0).unwrap().set_text("Region");
396        tbl.cell(1, 0).unwrap().shading("D6E4F0");
397        tbl.cell(1, 1).unwrap().set_text("Q1");
398        tbl.cell(1, 1).unwrap().shading("D6E4F0");
399        tbl.cell(1, 2).unwrap().set_text("Q2");
400        tbl.cell(1, 2).unwrap().shading("D6E4F0");
401        tbl.cell(1, 3).unwrap().set_text("Total");
402        tbl.cell(1, 3).unwrap().shading("D6E4F0");
403        tbl.cell(2, 0).unwrap().set_text("Americas");
404        tbl.cell(2, 1).unwrap().set_text("$2.4M");
405        tbl.cell(2, 2).unwrap().set_text("$2.7M");
406        tbl.cell(2, 3).unwrap().set_text("$5.1M");
407    }
408
409    doc.add_paragraph("");
410
411    doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
412    {
413        let mut tbl = doc
414            .add_table(4, 3)
415            .borders(BorderStyle::Single, 4, "000000");
416        tbl.cell(0, 0).unwrap().set_text("Category");
417        tbl.cell(0, 0).unwrap().shading("E2EFDA");
418        tbl.cell(0, 1).unwrap().set_text("Item");
419        tbl.cell(0, 1).unwrap().shading("E2EFDA");
420        tbl.cell(0, 2).unwrap().set_text("Price");
421        tbl.cell(0, 2).unwrap().shading("E2EFDA");
422        tbl.cell(1, 0).unwrap().set_text("Hardware");
423        tbl.cell(1, 0).unwrap().v_merge_restart();
424        tbl.cell(1, 1).unwrap().set_text("Laptop");
425        tbl.cell(1, 2).unwrap().set_text("$1,200");
426        tbl.cell(2, 0).unwrap().v_merge_continue();
427        tbl.cell(2, 1).unwrap().set_text("Monitor");
428        tbl.cell(2, 2).unwrap().set_text("$450");
429        tbl.cell(3, 0).unwrap().set_text("Software");
430        tbl.cell(3, 1).unwrap().set_text("IDE License");
431        tbl.cell(3, 2).unwrap().set_text("$200/yr");
432    }
433
434    doc.add_paragraph("");
435
436    doc.add_paragraph("5.4 Nested Table").style("Heading2");
437    {
438        let mut tbl = doc
439            .add_table(2, 2)
440            .borders(BorderStyle::Single, 6, "2E75B6");
441        tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
442        tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
443        tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
444        {
445            let mut cell = tbl.cell(1, 1).unwrap();
446            cell.set_text("Nested table below:");
447            let mut nested = cell
448                .add_table(2, 2)
449                .borders(BorderStyle::Single, 2, "FF6600");
450            nested.cell(0, 0).unwrap().set_text("A");
451            nested.cell(0, 1).unwrap().set_text("B");
452            nested.cell(1, 0).unwrap().set_text("C");
453            nested.cell(1, 1).unwrap().set_text("D");
454        }
455    }
456
457    doc.add_paragraph("");
458
459    doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
460        .style("Heading2");
461    {
462        let mut tbl = doc
463            .add_table(2, 3)
464            .borders(BorderStyle::Single, 4, "666666");
465        tbl.row(0).unwrap().height(Length::pt(50.0)).header();
466        tbl.cell(0, 0).unwrap().set_text("Top");
467        tbl.cell(0, 0)
468            .unwrap()
469            .vertical_alignment(VerticalAlignment::Top)
470            .shading("FFF2CC");
471        tbl.cell(0, 1).unwrap().set_text("Center");
472        tbl.cell(0, 1)
473            .unwrap()
474            .vertical_alignment(VerticalAlignment::Center)
475            .shading("D9E2F3");
476        tbl.cell(0, 2).unwrap().set_text("Bottom");
477        tbl.cell(0, 2)
478            .unwrap()
479            .vertical_alignment(VerticalAlignment::Bottom)
480            .shading("E2EFDA");
481        tbl.row(1).unwrap().cant_split();
482        tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
483        tbl.cell(1, 0).unwrap().no_wrap();
484        tbl.cell(1, 1).unwrap().set_text("Fixed width");
485        tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
486        tbl.cell(1, 2).unwrap().set_text("Normal");
487    }
488
489    // ── SECTION 6: IMAGES ──
490    doc.add_paragraph("").page_break_before(true);
491    doc.add_paragraph("6. Images").style("Heading1");
492
493    doc.add_paragraph("6.1 Inline Image").style("Heading2");
494    doc.add_paragraph("A blue gradient image below:");
495    let img = create_sample_png(200, 50, [0, 80, 200]);
496    doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
497
498    doc.add_paragraph("");
499    doc.add_paragraph("6.2 Header Image").style("Heading2");
500    let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
501    doc.set_header_image(
502        &hdr_img,
503        "header_logo.png",
504        Length::inches(2.0),
505        Length::inches(0.2),
506    );
507    doc.add_paragraph("The header now contains an inline image (check the top of this page).");
508
509    doc.add_paragraph("");
510    doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
511
512    // ── SECTION 7: CONTENT MANIPULATION ──
513    doc.add_paragraph("").page_break_before(true);
514    doc.add_paragraph("7. Content Manipulation")
515        .style("Heading1");
516
517    doc.add_paragraph("7.1 Placeholder Replacement")
518        .style("Heading2");
519    doc.add_paragraph("Customer: {{customer}}");
520    doc.add_paragraph("Date: {{date}}");
521    doc.add_paragraph("Reference: {{ref_number}}");
522    {
523        let mut tbl = doc
524            .add_table(2, 2)
525            .borders(BorderStyle::Single, 4, "000000");
526        tbl.cell(0, 0).unwrap().set_text("Project");
527        tbl.cell(0, 0).unwrap().shading("D6E4F0");
528        tbl.cell(0, 1).unwrap().set_text("{{project}}");
529        tbl.cell(1, 0).unwrap().set_text("Status");
530        tbl.cell(1, 0).unwrap().shading("D6E4F0");
531        tbl.cell(1, 1).unwrap().set_text("{{status}}");
532    }
533
534    let mut replacements = HashMap::new();
535    replacements.insert("{{customer}}", "Tensorbee Inc.");
536    replacements.insert("{{date}}", "February 22, 2026");
537    replacements.insert("{{ref_number}}", "TB-2026-001");
538    replacements.insert("{{project}}", "Infrastructure Upgrade");
539    replacements.insert("{{status}}", "In Progress");
540    let n = doc.replace_all(&replacements);
541    doc.add_paragraph(&format!("({n} placeholders replaced above)"));
542
543    doc.add_paragraph("");
544
545    doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
546    doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
547    let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
548    doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
549
550    doc.add_paragraph("");
551
552    doc.add_paragraph("7.3 Content Insertion").style("Heading2");
553    doc.add_paragraph("Section A: First content.");
554    doc.add_paragraph("Section C: Third content.");
555    if let Some(idx) = doc.find_content_index("Section C") {
556        doc.insert_paragraph(
557            idx,
558            "Section B: Inserted between A and C via find_content_index().",
559        );
560    }
561
562    // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
563    doc.add_paragraph("").section_break(SectionBreak::NextPage);
564    doc.add_paragraph("8. Section Breaks & Orientation")
565        .style("Heading1");
566    doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
567
568    {
569        let mut tbl = doc
570            .add_table(3, 7)
571            .borders(BorderStyle::Single, 4, "2E75B6");
572        let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
573        for (c, h) in headers.iter().enumerate() {
574            tbl.cell(0, c).unwrap().set_text(h);
575            tbl.cell(0, c).unwrap().shading("2E75B6");
576        }
577        let data = [
578            [
579                "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
580            ],
581            [
582                "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
583            ],
584        ];
585        for (r, row) in data.iter().enumerate() {
586            for (c, v) in row.iter().enumerate() {
587                tbl.cell(r + 1, c).unwrap().set_text(v);
588            }
589        }
590    }
591
592    doc.add_paragraph("")
593        .section_break(SectionBreak::NextPage)
594        .section_landscape();
595
596    // ── SECTION 9: CUSTOM STYLES ──
597    doc.add_paragraph("9. Custom Styles").style("Heading1");
598    doc.add_style(
599        StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
600            .based_on("Normal")
601            .paragraph_properties(rdocx_oxml::properties::CT_PPr {
602                shading: Some(rdocx_oxml::properties::CT_Shd {
603                    val: "clear".to_string(),
604                    color: None,
605                    fill: Some("FFF2CC".to_string()),
606                }),
607                ..Default::default()
608            })
609            .run_properties(rdocx_oxml::properties::CT_RPr {
610                bold: Some(true),
611                color: Some("C45911".to_string()),
612                ..Default::default()
613            }),
614    );
615    doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
616        .style("CustomHighlight");
617
618    doc.add_paragraph("");
619
620    // ── SECTION 10: DOCUMENT INTELLIGENCE ──
621    doc.add_paragraph("10. Document Intelligence API")
622        .style("Heading1");
623
624    let wc = doc.word_count();
625    let hc = doc.headings().len();
626    let ic = doc.images().len();
627    let lc = doc.links().len();
628    doc.add_paragraph(&format!("Word count: {wc}"));
629    doc.add_paragraph(&format!("Heading count: {hc}"));
630    doc.add_paragraph(&format!("Image count: {ic}"));
631    doc.add_paragraph(&format!("Link count: {lc}"));
632
633    let outline = doc.document_outline();
634    doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
635
636    let issues = doc.audit_accessibility();
637    doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
638    for issue in &issues {
639        doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
640    }
641
642    // ── SECTION 11: DOCUMENT MERGING ──
643    doc.add_paragraph("");
644    doc.add_paragraph("11. Document Merging").style("Heading1");
645    {
646        let mut other = Document::new();
647        other.add_paragraph("This paragraph was merged from another document using append().");
648        doc.append(&other);
649    }
650
651    doc.add_paragraph("");
652
653    // ── SUMMARY ──
654    doc.add_paragraph("Summary of Demonstrated Features")
655        .style("Heading1");
656    let features = [
657        "Page setup: size, margins, header/footer distance, gutter",
658        "Document metadata: title, author, subject, keywords",
659        "Headers and footers: text, images, different first page",
660        "Background images (full-page behind text)",
661        "Table of Contents generation with bookmarks",
662        "Text formatting: bold, italic, underline styles, strike, color, size, font",
663        "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
664        "Paragraph formatting: alignment, borders, shading, spacing, indentation",
665        "Pagination controls: keep-with-next, keep-together, widow control",
666        "Bullet and numbered lists with nesting",
667        "Tab stops with dot/underscore/hyphen leaders",
668        "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
669        "Row properties: header rows, exact height, cant-split",
670        "Cell properties: width, no-wrap, vertical alignment",
671        "Inline images and header images",
672        "Placeholder replacement (single and batch)",
673        "Regex-based find and replace",
674        "Content insertion at specific positions",
675        "Section breaks with mixed portrait/landscape",
676        "Custom paragraph and character styles",
677        "Document intelligence: word count, headings, outline, images, links",
678        "Accessibility audit",
679        "Document merging (append)",
680        "Export: DOCX, PDF, HTML, Markdown",
681    ];
682    for f in &features {
683        doc.add_bullet_list_item(f, 0);
684    }
685    doc.add_paragraph("");
686    doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
687        .alignment(Alignment::Center)
688        .shading("E2EFDA")
689        .border_all(BorderStyle::Single, 2, "00AA00");
690
691    doc
692}
693
694// =============================================================================
695// 2. PROPOSAL DOCUMENT — Deep navy + gold scheme
696// =============================================================================
697fn generate_proposal(_samples_dir: &Path) -> Document {
698    let mut doc = Document::new();
699
700    // Colors: Navy #1B2A4A, Gold #C5922E, Light #F4F1EB
701    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
702    doc.set_margins(
703        Length::inches(1.0),
704        Length::inches(1.0),
705        Length::inches(1.0),
706        Length::inches(1.0),
707    );
708    doc.set_title("AI Platform Modernization Proposal");
709    doc.set_author("Walter White");
710    doc.set_subject("Technology Proposal");
711    doc.set_keywords("proposal, AI, modernization, Tensorbee");
712
713    // Banner header
714    let logo_img = create_logo_png(220, 48);
715    let banner = build_header_banner_xml(
716        "rId1",
717        &BannerOpts {
718            bg_color: "1B2A4A",
719            banner_width: 7772400,
720            banner_height: 914400,
721            logo_width: 2011680,
722            logo_height: 438912,
723            logo_x_offset: 295125,
724            logo_y_offset: 237744,
725        },
726    );
727    doc.set_raw_header_with_images(
728        banner,
729        &[("rId1", &logo_img, "logo.png")],
730        rdocx_oxml::header_footer::HdrFtrType::Default,
731    );
732    doc.set_different_first_page(true);
733    let cover_banner = build_header_banner_xml(
734        "rId1",
735        &BannerOpts {
736            bg_color: "C5922E",
737            banner_width: 7772400,
738            banner_height: 914400,
739            logo_width: 2011680,
740            logo_height: 438912,
741            logo_x_offset: 295125,
742            logo_y_offset: 237744,
743        },
744    );
745    doc.set_raw_header_with_images(
746        cover_banner,
747        &[("rId1", &logo_img, "logo.png")],
748        rdocx_oxml::header_footer::HdrFtrType::First,
749    );
750    doc.set_margins(
751        Length::twips(2292),
752        Length::twips(1440),
753        Length::twips(1440),
754        Length::twips(1440),
755    );
756    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
757    doc.set_footer("Tensorbee — Confidential");
758
759    // Custom styles
760    doc.add_style(
761        StyleBuilder::paragraph("ProposalTitle", "Proposal Title")
762            .based_on("Normal")
763            .run_properties(rdocx_oxml::properties::CT_RPr {
764                bold: Some(true),
765                font_ascii: Some("Georgia".to_string()),
766                font_hansi: Some("Georgia".to_string()),
767                sz: Some(rdocx_oxml::HalfPoint(56)), // half-points → 28pt
768                color: Some("1B2A4A".to_string()),
769                ..Default::default()
770            }),
771    );
772
773    // ── Cover Page ──
774    for _ in 0..4 {
775        doc.add_paragraph("");
776    }
777    doc.add_paragraph("AI Platform Modernization")
778        .style("ProposalTitle")
779        .alignment(Alignment::Center);
780    doc.add_paragraph("")
781        .alignment(Alignment::Center)
782        .border_bottom(BorderStyle::Single, 8, "C5922E");
783    {
784        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
785        p.add_run("Prepared for Global Financial Corp.")
786            .size(14.0)
787            .color("666666")
788            .italic(true);
789    }
790    {
791        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
792        p.add_run("Prepared by: Walter White, CEO — Tensorbee")
793            .size(12.0)
794            .color("888888");
795    }
796    {
797        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
798        p.add_run("February 22, 2026").size(12.0).color("888888");
799    }
800
801    // ── TOC ──
802    doc.add_paragraph("").page_break_before(true);
803    doc.add_paragraph("Table of Contents").style("Heading1");
804    // We'll add a manual-style TOC since the insert_toc goes at a specific position
805    doc.insert_toc(doc.content_count(), 2);
806
807    // ── Executive Summary ──
808    doc.add_paragraph("").page_break_before(true);
809    doc.add_paragraph("Executive Summary").style("Heading1");
810    doc.add_paragraph(
811        "Tensorbee proposes a comprehensive modernization of Global Financial Corp's AI platform, \
812         transitioning from legacy batch-processing systems to a real-time inference architecture. \
813         This transformation will reduce model deployment time from weeks to hours, improve \
814         prediction accuracy by an estimated 23%, and deliver $4.2M in annual operational savings.",
815    )
816    .first_line_indent(Length::inches(0.3));
817    doc.add_paragraph(
818        "The project spans three phases over 18 months, with a total investment of $2.8M. \
819         Tensorbee brings deep expertise in ML infrastructure, having successfully completed \
820         similar transformations for three Fortune 500 financial institutions.",
821    )
822    .first_line_indent(Length::inches(0.3));
823
824    // Key metrics table
825    doc.add_paragraph("");
826    {
827        let mut tbl = doc
828            .add_table(5, 2)
829            .borders(BorderStyle::Single, 4, "1B2A4A")
830            .width_pct(80.0);
831        tbl.cell(0, 0).unwrap().set_text("Key Metric");
832        tbl.cell(0, 0).unwrap().shading("1B2A4A");
833        tbl.cell(0, 1).unwrap().set_text("Value");
834        tbl.cell(0, 1).unwrap().shading("1B2A4A");
835        let rows = [
836            ("Total Investment", "$2.8M"),
837            ("Annual Savings", "$4.2M"),
838            ("ROI (Year 1)", "150%"),
839            ("Timeline", "18 months"),
840        ];
841        for (i, (k, v)) in rows.iter().enumerate() {
842            tbl.cell(i + 1, 0).unwrap().set_text(k);
843            if i % 2 == 0 {
844                tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
845            }
846            tbl.cell(i + 1, 1).unwrap().set_text(v);
847            if i % 2 == 0 {
848                tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
849            }
850        }
851    }
852
853    // ── Problem Statement ──
854    doc.add_paragraph("").page_break_before(true);
855    doc.add_paragraph("Problem Statement").style("Heading1");
856    doc.add_paragraph(
857        "Global Financial Corp's current AI infrastructure faces three critical challenges:",
858    );
859    doc.add_numbered_list_item("Deployment Latency — New models take 3-4 weeks to deploy to production, compared to the industry benchmark of 2-3 days.", 0);
860    doc.add_numbered_list_item("Scalability Constraints — The batch processing architecture cannot handle real-time inference demands during peak trading hours.", 0);
861    doc.add_numbered_list_item("Technical Debt — Legacy Python 2.7 codebases and manual deployment scripts create reliability risks and slow iteration speed.", 0);
862
863    // ── Proposed Solution ──
864    doc.add_paragraph("").page_break_before(true);
865    doc.add_paragraph("Proposed Solution").style("Heading1");
866
867    doc.add_paragraph("Phase 1: Foundation (Months 1-6)")
868        .style("Heading2");
869    doc.add_bullet_list_item("Deploy Kubernetes-based ML serving infrastructure", 0);
870    doc.add_bullet_list_item("Implement CI/CD pipeline for model deployment", 0);
871    doc.add_bullet_list_item("Migrate top 5 production models to new platform", 0);
872
873    doc.add_paragraph("Phase 2: Expansion (Months 7-12)")
874        .style("Heading2");
875    doc.add_bullet_list_item(
876        "Real-time feature engineering pipeline (Apache Kafka + Flink)",
877        0,
878    );
879    doc.add_bullet_list_item("A/B testing framework for model variants", 0);
880    doc.add_bullet_list_item("Automated model monitoring and drift detection", 0);
881
882    doc.add_paragraph("Phase 3: Optimization (Months 13-18)")
883        .style("Heading2");
884    doc.add_bullet_list_item("GPU inference optimization (TensorRT/ONNX Runtime)", 0);
885    doc.add_bullet_list_item("Multi-region deployment for latency reduction", 0);
886    doc.add_bullet_list_item(
887        "Self-service model deployment portal for data science teams",
888        0,
889    );
890
891    // ── Budget ──
892    doc.add_paragraph("Budget Breakdown").style("Heading1");
893    {
894        let mut tbl = doc
895            .add_table(6, 3)
896            .borders(BorderStyle::Single, 4, "1B2A4A")
897            .width_pct(100.0);
898        tbl.cell(0, 0).unwrap().set_text("Category");
899        tbl.cell(0, 0).unwrap().shading("1B2A4A");
900        tbl.cell(0, 1).unwrap().set_text("Cost");
901        tbl.cell(0, 1).unwrap().shading("1B2A4A");
902        tbl.cell(0, 2).unwrap().set_text("Notes");
903        tbl.cell(0, 2).unwrap().shading("1B2A4A");
904        let items = [
905            (
906                "Infrastructure",
907                "$450,000",
908                "Cloud compute + GPU instances",
909            ),
910            ("Engineering Services", "$1,600,000", "12 FTE x 18 months"),
911            (
912                "Software Licenses",
913                "$350,000",
914                "Kafka, monitoring, CI/CD tools",
915            ),
916            (
917                "Training & Enablement",
918                "$200,000",
919                "Team training, documentation",
920            ),
921            ("Contingency (10%)", "$200,000", "Risk buffer"),
922        ];
923        for (i, (cat, cost, note)) in items.iter().enumerate() {
924            tbl.cell(i + 1, 0).unwrap().set_text(cat);
925            tbl.cell(i + 1, 1).unwrap().set_text(cost);
926            tbl.cell(i + 1, 2).unwrap().set_text(note);
927            if i % 2 == 0 {
928                tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
929                tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
930                tbl.cell(i + 1, 2).unwrap().shading("F4F1EB");
931            }
932        }
933    }
934
935    // ── Closing ──
936    doc.add_paragraph("");
937    doc.add_paragraph("Next Steps").style("Heading1");
938    doc.add_numbered_list_item(
939        "Schedule technical deep-dive with GFC engineering team (Week 1)",
940        0,
941    );
942    doc.add_numbered_list_item("Finalize scope and sign SOW (Week 2-3)", 0);
943    doc.add_numbered_list_item("Kick off Phase 1 with joint planning session (Week 4)", 0);
944
945    doc.add_paragraph("");
946    {
947        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
948        p.add_run("Walter White")
949            .bold(true)
950            .size(14.0)
951            .color("1B2A4A");
952    }
953    {
954        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
955        p.add_run("CEO, Tensorbee | walter@tensorbee.com")
956            .size(10.0)
957            .color("888888");
958    }
959
960    doc
961}
962
963// =============================================================================
964// 3. QUOTE / BILL OF MATERIALS — Teal + orange scheme
965// =============================================================================
966fn generate_quote(_samples_dir: &Path) -> Document {
967    let mut doc = Document::new();
968
969    // Colors: Teal #008B8B, Dark #1A3C3C, Orange #E07020, Light #F0F8F8
970    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
971    doc.set_margins(
972        Length::inches(0.75),
973        Length::inches(0.75),
974        Length::inches(0.75),
975        Length::inches(0.75),
976    );
977    doc.set_title("Quotation QT-2026-0147");
978    doc.set_author("Walter White");
979    doc.set_keywords("quote, BOM, Tensorbee");
980
981    doc.set_header("Tensorbee — Quotation");
982    doc.set_footer("QT-2026-0147 | Page");
983
984    // ── Header Block ──
985    {
986        let mut p = doc.add_paragraph("").alignment(Alignment::Left);
987        p.add_run("TENSORBEE")
988            .bold(true)
989            .size(28.0)
990            .color("008B8B")
991            .font("Helvetica");
992    }
993    doc.add_paragraph("123 Innovation Drive, Suite 400")
994        .alignment(Alignment::Left);
995    doc.add_paragraph("San Francisco, CA 94105 | +1 (415) 555-0199")
996        .alignment(Alignment::Left);
997    doc.add_paragraph("")
998        .border_bottom(BorderStyle::Single, 8, "008B8B");
999
1000    // Quote meta
1001    doc.add_paragraph("");
1002    {
1003        let mut tbl = doc.add_table(4, 4).width_pct(100.0);
1004        tbl.cell(0, 0).unwrap().set_text("QUOTATION");
1005        tbl.cell(0, 0).unwrap().shading("008B8B").grid_span(2);
1006        tbl.cell(0, 2).unwrap().set_text("DATE");
1007        tbl.cell(0, 2).unwrap().shading("008B8B");
1008        tbl.cell(0, 3).unwrap().set_text("VALID UNTIL");
1009        tbl.cell(0, 3).unwrap().shading("008B8B");
1010        tbl.cell(1, 0).unwrap().set_text("Quote #:");
1011        tbl.cell(1, 1).unwrap().set_text("QT-2026-0147");
1012        tbl.cell(1, 2).unwrap().set_text("Feb 22, 2026");
1013        tbl.cell(1, 3).unwrap().set_text("Mar 22, 2026");
1014        tbl.cell(2, 0).unwrap().set_text("Prepared by:");
1015        tbl.cell(2, 1).unwrap().set_text("Walter White");
1016        tbl.cell(2, 2).unwrap().set_text("Payment:");
1017        tbl.cell(2, 3).unwrap().set_text("Net 30");
1018        tbl.cell(3, 0).unwrap().set_text("Customer:");
1019        tbl.cell(3, 1).unwrap().set_text("Meridian Dynamics LLC");
1020        tbl.cell(3, 2).unwrap().set_text("Currency:");
1021        tbl.cell(3, 3).unwrap().set_text("USD");
1022    }
1023
1024    doc.add_paragraph("");
1025
1026    // ── Bill of Materials ──
1027    {
1028        let mut p = doc.add_paragraph("");
1029        p.add_run("Bill of Materials")
1030            .bold(true)
1031            .size(16.0)
1032            .color("1A3C3C");
1033    }
1034    doc.add_paragraph("");
1035
1036    {
1037        let mut tbl = doc
1038            .add_table(10, 6)
1039            .borders(BorderStyle::Single, 2, "008B8B")
1040            .width_pct(100.0)
1041            .layout_fixed();
1042        // Headers
1043        let hdrs = [
1044            "#",
1045            "Part Number",
1046            "Description",
1047            "Qty",
1048            "Unit Price",
1049            "Total",
1050        ];
1051        for (c, h) in hdrs.iter().enumerate() {
1052            tbl.cell(0, c).unwrap().set_text(h);
1053            tbl.cell(0, c).unwrap().shading("008B8B");
1054        }
1055
1056        let items: Vec<(&str, &str, &str, &str, &str)> = vec![
1057            (
1058                "TB-GPU-A100",
1059                "NVIDIA A100 80GB GPU",
1060                "4",
1061                "$12,500.00",
1062                "$50,000.00",
1063            ),
1064            (
1065                "TB-SRV-R750",
1066                "Dell R750xa Server Chassis",
1067                "2",
1068                "$8,200.00",
1069                "$16,400.00",
1070            ),
1071            (
1072                "TB-RAM-256G",
1073                "256GB DDR5 ECC Memory Module",
1074                "8",
1075                "$890.00",
1076                "$7,120.00",
1077            ),
1078            (
1079                "TB-SSD-3840",
1080                "3.84TB NVMe U.2 SSD",
1081                "8",
1082                "$1,150.00",
1083                "$9,200.00",
1084            ),
1085            (
1086                "TB-NET-CX7",
1087                "ConnectX-7 200GbE NIC",
1088                "4",
1089                "$1,800.00",
1090                "$7,200.00",
1091            ),
1092            (
1093                "TB-SW-48P",
1094                "48-Port 100GbE Switch",
1095                "1",
1096                "$22,000.00",
1097                "$22,000.00",
1098            ),
1099            (
1100                "TB-CAB-RACK",
1101                "42U Server Rack + PDU",
1102                "1",
1103                "$4,500.00",
1104                "$4,500.00",
1105            ),
1106            (
1107                "TB-SVC-INST",
1108                "Installation & Configuration",
1109                "1",
1110                "$8,500.00",
1111                "$8,500.00",
1112            ),
1113            (
1114                "TB-SVC-SUPP",
1115                "3-Year Premium Support",
1116                "1",
1117                "$15,000.00",
1118                "$15,000.00",
1119            ),
1120        ];
1121
1122        for (i, (pn, desc, qty, unit, total)) in items.iter().enumerate() {
1123            let row = i + 1;
1124            tbl.cell(row, 0).unwrap().set_text(&format!("{}", i + 1));
1125            tbl.cell(row, 1).unwrap().set_text(pn);
1126            tbl.cell(row, 2).unwrap().set_text(desc);
1127            tbl.cell(row, 3).unwrap().set_text(qty);
1128            tbl.cell(row, 4).unwrap().set_text(unit);
1129            tbl.cell(row, 5).unwrap().set_text(total);
1130            if i % 2 == 0 {
1131                for c in 0..6 {
1132                    tbl.cell(row, c).unwrap().shading("F0F8F8");
1133                }
1134            }
1135        }
1136    }
1137
1138    doc.add_paragraph("");
1139
1140    // ── Totals ──
1141    {
1142        let mut tbl = doc
1143            .add_table(4, 2)
1144            .borders(BorderStyle::Single, 2, "008B8B")
1145            .width(Length::inches(3.5))
1146            .alignment(Alignment::Right);
1147        tbl.cell(0, 0).unwrap().set_text("Subtotal");
1148        tbl.cell(0, 1).unwrap().set_text("$139,920.00");
1149        tbl.cell(1, 0).unwrap().set_text("Shipping & Handling");
1150        tbl.cell(1, 1).unwrap().set_text("$2,500.00");
1151        tbl.cell(2, 0).unwrap().set_text("Tax (8.625%)");
1152        tbl.cell(2, 1).unwrap().set_text("$12,068.10");
1153        tbl.cell(3, 0).unwrap().set_text("TOTAL");
1154        tbl.cell(3, 0).unwrap().shading("E07020");
1155        tbl.cell(3, 1).unwrap().set_text("$154,488.10");
1156        tbl.cell(3, 1).unwrap().shading("E07020");
1157    }
1158
1159    doc.add_paragraph("");
1160
1161    // ── Terms & Conditions ──
1162    {
1163        let mut p = doc.add_paragraph("");
1164        p.add_run("Terms & Conditions")
1165            .bold(true)
1166            .size(14.0)
1167            .color("1A3C3C");
1168    }
1169    doc.add_numbered_list_item(
1170        "This quotation is valid for 30 calendar days from the date of issue.",
1171        0,
1172    );
1173    doc.add_numbered_list_item(
1174        "All prices are in USD and exclusive of applicable taxes unless stated.",
1175        0,
1176    );
1177    doc.add_numbered_list_item("Standard lead time is 4-6 weeks from PO receipt.", 0);
1178    doc.add_numbered_list_item(
1179        "Payment terms: Net 30 from invoice date. 2% discount for payment within 10 days.",
1180        0,
1181    );
1182    doc.add_numbered_list_item(
1183        "Warranty: 3-year manufacturer warranty on all hardware components.",
1184        0,
1185    );
1186    doc.add_numbered_list_item(
1187        "Returns subject to 15% restocking fee if initiated after 14 days.",
1188        0,
1189    );
1190
1191    doc.add_paragraph("");
1192    doc.add_paragraph("")
1193        .border_bottom(BorderStyle::Single, 4, "008B8B");
1194    doc.add_paragraph("");
1195    {
1196        let mut p = doc.add_paragraph("");
1197        p.add_run("Accepted by: ").bold(true);
1198        p.add_run("___________________________ Date: ___________");
1199    }
1200    {
1201        let mut p = doc.add_paragraph("");
1202        p.add_run("Print Name: ").bold(true);
1203        p.add_run("___________________________ Title: ___________");
1204    }
1205
1206    doc
1207}
1208
1209// =============================================================================
1210// 4. INVOICE — Crimson + charcoal scheme
1211// =============================================================================
1212fn generate_invoice(_samples_dir: &Path) -> Document {
1213    let mut doc = Document::new();
1214
1215    // Colors: Crimson #B22222, Charcoal #333333, Light #FAF0F0
1216    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1217    doc.set_margins(
1218        Length::inches(0.75),
1219        Length::inches(0.75),
1220        Length::inches(0.75),
1221        Length::inches(0.75),
1222    );
1223    doc.set_title("Invoice INV-2026-0392");
1224    doc.set_author("Walter White");
1225    doc.set_keywords("invoice, Tensorbee");
1226
1227    doc.set_footer("Tensorbee — Thank you for your business!");
1228
1229    // ── Company & Invoice Header ──
1230    {
1231        let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1232        // Company name left, INVOICE right
1233        {
1234            let mut cell = tbl.cell(0, 0).unwrap();
1235            let mut p = cell.add_paragraph("");
1236            p.add_run("TENSORBEE")
1237                .bold(true)
1238                .size(32.0)
1239                .color("B22222")
1240                .font("Helvetica");
1241        }
1242        {
1243            let mut cell = tbl.cell(0, 1).unwrap();
1244            let mut p = cell.add_paragraph("");
1245            p.add_run("INVOICE").bold(true).size(32.0).color("333333");
1246        }
1247        tbl.cell(1, 0)
1248            .unwrap()
1249            .set_text("123 Innovation Drive, Suite 400");
1250        tbl.cell(1, 1).unwrap().set_text("Invoice #: INV-2026-0392");
1251        tbl.cell(2, 0).unwrap().set_text("San Francisco, CA 94105");
1252        tbl.cell(2, 1).unwrap().set_text("Date: February 22, 2026");
1253        tbl.cell(3, 0).unwrap().set_text("walter@tensorbee.com");
1254        tbl.cell(3, 1).unwrap().set_text("Due Date: March 24, 2026");
1255    }
1256
1257    doc.add_paragraph("")
1258        .border_bottom(BorderStyle::Thick, 8, "B22222");
1259    doc.add_paragraph("");
1260
1261    // ── Bill To / Ship To ──
1262    {
1263        let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1264        tbl.cell(0, 0).unwrap().set_text("BILL TO");
1265        tbl.cell(0, 0).unwrap().shading("B22222");
1266        tbl.cell(0, 1).unwrap().set_text("SHIP TO");
1267        tbl.cell(0, 1).unwrap().shading("B22222");
1268        tbl.cell(1, 0).unwrap().set_text("Meridian Dynamics LLC");
1269        tbl.cell(1, 1).unwrap().set_text("Meridian Dynamics LLC");
1270        tbl.cell(2, 0).unwrap().set_text("456 Enterprise Blvd");
1271        tbl.cell(2, 1).unwrap().set_text("Attn: Server Room B");
1272        tbl.cell(3, 0).unwrap().set_text("Austin, TX 78701");
1273        tbl.cell(3, 1)
1274            .unwrap()
1275            .set_text("456 Enterprise Blvd, Austin, TX 78701");
1276    }
1277
1278    doc.add_paragraph("");
1279
1280    // ── Line Items ──
1281    {
1282        let mut tbl = doc
1283            .add_table(8, 5)
1284            .borders(BorderStyle::Single, 2, "B22222")
1285            .width_pct(100.0);
1286        let hdrs = ["Description", "Qty", "Unit Price", "Tax", "Amount"];
1287        for (c, h) in hdrs.iter().enumerate() {
1288            tbl.cell(0, c).unwrap().set_text(h);
1289            tbl.cell(0, c).unwrap().shading("B22222");
1290        }
1291        let items = [
1292            (
1293                "ML Infrastructure Setup — Phase 1",
1294                "1",
1295                "$45,000.00",
1296                "$3,881.25",
1297                "$48,881.25",
1298            ),
1299            (
1300                "Data Pipeline Development (160 hrs)",
1301                "160",
1302                "$225.00",
1303                "$3,105.00",
1304                "$39,105.00",
1305            ),
1306            (
1307                "GPU Cluster Configuration",
1308                "1",
1309                "$12,000.00",
1310                "$1,035.00",
1311                "$13,035.00",
1312            ),
1313            (
1314                "API Gateway Implementation",
1315                "1",
1316                "$18,500.00",
1317                "$1,595.63",
1318                "$20,095.63",
1319            ),
1320            (
1321                "Load Testing & QA (80 hrs)",
1322                "80",
1323                "$195.00",
1324                "$1,345.50",
1325                "$16,945.50",
1326            ),
1327            (
1328                "Documentation & Training",
1329                "1",
1330                "$8,000.00",
1331                "$690.00",
1332                "$8,690.00",
1333            ),
1334            (
1335                "Project Management (3 months)",
1336                "3",
1337                "$6,500.00",
1338                "$1,679.63",
1339                "$21,179.63",
1340            ),
1341        ];
1342        for (i, (desc, qty, unit, tax, amt)) in items.iter().enumerate() {
1343            let r = i + 1;
1344            tbl.cell(r, 0).unwrap().set_text(desc);
1345            tbl.cell(r, 1).unwrap().set_text(qty);
1346            tbl.cell(r, 2).unwrap().set_text(unit);
1347            tbl.cell(r, 3).unwrap().set_text(tax);
1348            tbl.cell(r, 4).unwrap().set_text(amt);
1349            if i % 2 == 0 {
1350                for c in 0..5 {
1351                    tbl.cell(r, c).unwrap().shading("FAF0F0");
1352                }
1353            }
1354        }
1355    }
1356
1357    doc.add_paragraph("");
1358
1359    // ── Totals ──
1360    {
1361        let mut tbl = doc
1362            .add_table(4, 2)
1363            .borders(BorderStyle::Single, 2, "B22222")
1364            .width(Length::inches(3.0))
1365            .alignment(Alignment::Right);
1366        tbl.cell(0, 0).unwrap().set_text("Subtotal");
1367        tbl.cell(0, 1).unwrap().set_text("$154,600.00");
1368        tbl.cell(1, 0).unwrap().set_text("Tax (8.625%)");
1369        tbl.cell(1, 1).unwrap().set_text("$13,334.25");
1370        tbl.cell(2, 0).unwrap().set_text("Discount (5%)");
1371        tbl.cell(2, 1).unwrap().set_text("-$7,730.00");
1372        tbl.cell(3, 0).unwrap().set_text("AMOUNT DUE");
1373        tbl.cell(3, 0).unwrap().shading("B22222");
1374        tbl.cell(3, 1).unwrap().set_text("$160,204.25");
1375        tbl.cell(3, 1).unwrap().shading("B22222");
1376    }
1377
1378    doc.add_paragraph("");
1379    doc.add_paragraph("");
1380
1381    // ── Payment Details ──
1382    {
1383        let mut p = doc.add_paragraph("");
1384        p.add_run("Payment Details")
1385            .bold(true)
1386            .size(14.0)
1387            .color("333333");
1388    }
1389    doc.add_paragraph("Bank: Silicon Valley Bank")
1390        .indent_left(Length::inches(0.3));
1391    doc.add_paragraph("Account: Tensorbee Inc. — 0847-2953-1120")
1392        .indent_left(Length::inches(0.3));
1393    doc.add_paragraph("Routing: 121140399")
1394        .indent_left(Length::inches(0.3));
1395    doc.add_paragraph("Swift: SVBKUS6S")
1396        .indent_left(Length::inches(0.3));
1397
1398    doc.add_paragraph("");
1399
1400    doc.add_paragraph("Please include invoice number INV-2026-0392 in the payment reference.")
1401        .shading("FAF0F0")
1402        .border_all(BorderStyle::Single, 2, "B22222");
1403
1404    doc
1405}
1406
1407// =============================================================================
1408// 5. REPORT — Forest green + earth tones, with images & hierarchical sections
1409// =============================================================================
1410fn generate_report(_samples_dir: &Path) -> Document {
1411    let mut doc = Document::new();
1412
1413    // Colors: Forest #2D5016, Sage #6B8E23, Earth #8B7355, Cream #FFFAF0
1414    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1415    doc.set_margins(
1416        Length::inches(1.0),
1417        Length::inches(1.0),
1418        Length::inches(1.0),
1419        Length::inches(1.0),
1420    );
1421    doc.set_title("Q4 2025 Environmental Impact Report");
1422    doc.set_author("Walter White");
1423    doc.set_subject("Quarterly Environmental Report");
1424    doc.set_keywords("environment, sustainability, report, Tensorbee");
1425
1426    doc.set_different_first_page(true);
1427    doc.set_first_page_header("");
1428    doc.set_header("Tensorbee — Q4 2025 Environmental Impact Report");
1429    doc.set_footer("CONFIDENTIAL — Page");
1430
1431    // ── Cover ──
1432    let cover_bg = create_sample_png(612, 792, [20, 50, 15]);
1433    doc.add_background_image(&cover_bg, "report_cover.png");
1434
1435    for _ in 0..5 {
1436        doc.add_paragraph("");
1437    }
1438    {
1439        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1440        p.add_run("Q4 2025")
1441            .bold(true)
1442            .size(48.0)
1443            .color("FFFFFF")
1444            .font("Georgia");
1445    }
1446    {
1447        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1448        p.add_run("Environmental Impact Report")
1449            .size(24.0)
1450            .color("90EE90")
1451            .font("Georgia")
1452            .italic(true);
1453    }
1454    doc.add_paragraph("");
1455    {
1456        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1457        p.add_run("Tensorbee — Sustainability Division")
1458            .size(14.0)
1459            .color("C0C0C0");
1460    }
1461    {
1462        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1463        p.add_run("Prepared by Walter White, Chief Sustainability Officer")
1464            .size(11.0)
1465            .color("AAAAAA");
1466    }
1467
1468    // ── TOC ──
1469    doc.add_paragraph("").page_break_before(true);
1470    doc.insert_toc(doc.content_count(), 3);
1471
1472    // ── Section 1: Executive Overview ──
1473    doc.add_paragraph("").page_break_before(true);
1474    doc.add_paragraph("Executive Overview").style("Heading1");
1475    doc.add_paragraph(
1476        "This report presents Tensorbee's environmental performance for Q4 2025. Our \
1477         sustainability initiatives have yielded a 34% reduction in carbon emissions compared \
1478         to Q4 2024, exceeding our target of 25%. Key achievements include the transition to \
1479         100% renewable energy in our primary data centers and the launch of our carbon offset \
1480         marketplace.",
1481    )
1482    .first_line_indent(Length::inches(0.3));
1483
1484    // Image: performance chart
1485    let chart_img = create_chart_png(400, 200);
1486    doc.add_paragraph("");
1487    doc.add_picture(
1488        &chart_img,
1489        "performance_chart.png",
1490        Length::inches(5.0),
1491        Length::inches(2.5),
1492    )
1493    .alignment(Alignment::Center);
1494    {
1495        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1496        p.add_run("Figure 1: Quarterly Carbon Emissions (tonnes CO2e)")
1497            .italic(true)
1498            .size(9.0)
1499            .color("666666");
1500    }
1501
1502    // ── Section 2: Energy Consumption ──
1503    doc.add_paragraph("").page_break_before(true);
1504    doc.add_paragraph("Energy Consumption").style("Heading1");
1505
1506    doc.add_paragraph("Data Center Operations")
1507        .style("Heading2");
1508    doc.add_paragraph(
1509        "Our three primary data centers consumed a combined 4.2 GWh in Q4 2025, \
1510         a 12% reduction from Q3 2025 driven by improved cooling efficiency and \
1511         server consolidation.",
1512    );
1513
1514    // Data table
1515    {
1516        let mut tbl = doc
1517            .add_table(5, 4)
1518            .borders(BorderStyle::Single, 4, "2D5016")
1519            .width_pct(100.0);
1520        let hdrs = ["Data Center", "Capacity (MW)", "Usage (GWh)", "PUE"];
1521        for (c, h) in hdrs.iter().enumerate() {
1522            tbl.cell(0, c).unwrap().set_text(h);
1523            tbl.cell(0, c).unwrap().shading("2D5016");
1524        }
1525        let rows = [
1526            ("San Francisco (Primary)", "3.2", "1.8", "1.12"),
1527            ("Dublin (EU)", "2.1", "1.2", "1.18"),
1528            ("Singapore (APAC)", "1.8", "1.2", "1.24"),
1529            ("TOTAL", "7.1", "4.2", "1.17 avg"),
1530        ];
1531        for (i, (dc, cap, use_, pue)) in rows.iter().enumerate() {
1532            tbl.cell(i + 1, 0).unwrap().set_text(dc);
1533            tbl.cell(i + 1, 1).unwrap().set_text(cap);
1534            tbl.cell(i + 1, 2).unwrap().set_text(use_);
1535            tbl.cell(i + 1, 3).unwrap().set_text(pue);
1536            if i == 3 {
1537                for c in 0..4 {
1538                    tbl.cell(i + 1, c).unwrap().shading("E2EFDA");
1539                }
1540            }
1541        }
1542    }
1543
1544    doc.add_paragraph("");
1545
1546    doc.add_paragraph("Renewable Energy Mix").style("Heading2");
1547    doc.add_paragraph("Breakdown of energy sources across all facilities:");
1548
1549    doc.add_bullet_list_item("Solar PV: 42% (1.76 GWh)", 0);
1550    doc.add_bullet_list_item("Wind Power Purchase Agreements: 38% (1.60 GWh)", 0);
1551    doc.add_bullet_list_item("Hydroelectric: 12% (0.50 GWh)", 0);
1552    doc.add_bullet_list_item("Grid (non-renewable): 8% (0.34 GWh)", 0);
1553
1554    // ── Section 3: Water & Waste ──
1555    doc.add_paragraph("Water & Waste Management")
1556        .style("Heading1");
1557
1558    doc.add_paragraph("Water Usage").style("Heading2");
1559    doc.add_paragraph(
1560        "Total water consumption was 12.4 million gallons, a 15% reduction from Q3. \
1561         Our closed-loop cooling systems now recycle 78% of water used in cooling operations.",
1562    );
1563
1564    doc.add_paragraph("Waste Reduction").style("Heading2");
1565    doc.add_paragraph("E-waste management results for Q4:")
1566        .keep_with_next(true);
1567    {
1568        let mut tbl = doc
1569            .add_table(4, 3)
1570            .borders(BorderStyle::Single, 2, "6B8E23")
1571            .width_pct(80.0);
1572        tbl.cell(0, 0).unwrap().set_text("Category");
1573        tbl.cell(0, 0).unwrap().shading("6B8E23");
1574        tbl.cell(0, 1).unwrap().set_text("Weight (kg)");
1575        tbl.cell(0, 1).unwrap().shading("6B8E23");
1576        tbl.cell(0, 2).unwrap().set_text("Recycled %");
1577        tbl.cell(0, 2).unwrap().shading("6B8E23");
1578        let rows = [
1579            ("Server Hardware", "2,450", "94%"),
1580            ("Networking Equipment", "820", "91%"),
1581            ("Storage Media", "340", "99%"),
1582        ];
1583        for (i, (cat, wt, pct)) in rows.iter().enumerate() {
1584            tbl.cell(i + 1, 0).unwrap().set_text(cat);
1585            tbl.cell(i + 1, 1).unwrap().set_text(wt);
1586            tbl.cell(i + 1, 2).unwrap().set_text(pct);
1587        }
1588    }
1589
1590    // ── Section 4: Initiatives ──
1591    doc.add_paragraph("").page_break_before(true);
1592    doc.add_paragraph("Q1 2026 Initiatives").style("Heading1");
1593
1594    doc.add_paragraph("Planned Programs").style("Heading2");
1595    doc.add_numbered_list_item(
1596        "Deploy on-site battery storage at SF data center (2 MWh capacity)",
1597        0,
1598    );
1599    doc.add_numbered_list_item("Pilot immersion cooling in Dublin facility", 0);
1600    doc.add_numbered_list_item("Launch employee commute carbon offset program", 0);
1601    doc.add_numbered_list_item("Achieve ISO 14001 certification for Singapore facility", 0);
1602
1603    doc.add_paragraph("Investment Targets").style("Heading2");
1604    doc.add_paragraph("Sustainability CapEx allocation for FY2026:")
1605        .keep_with_next(true);
1606    {
1607        let mut tbl = doc
1608            .add_table(5, 2)
1609            .borders(BorderStyle::Single, 4, "2D5016");
1610        tbl.cell(0, 0).unwrap().set_text("Initiative");
1611        tbl.cell(0, 0).unwrap().shading("2D5016");
1612        tbl.cell(0, 1).unwrap().set_text("Budget");
1613        tbl.cell(0, 1).unwrap().shading("2D5016");
1614        let rows = [
1615            ("Battery Storage", "$1.2M"),
1616            ("Immersion Cooling Pilot", "$800K"),
1617            ("Solar Panel Expansion", "$2.1M"),
1618            ("Carbon Credits", "$500K"),
1619        ];
1620        for (i, (init, budget)) in rows.iter().enumerate() {
1621            tbl.cell(i + 1, 0).unwrap().set_text(init);
1622            tbl.cell(i + 1, 1).unwrap().set_text(budget);
1623            if i % 2 == 0 {
1624                tbl.cell(i + 1, 0).unwrap().shading("FFFAF0");
1625                tbl.cell(i + 1, 1).unwrap().shading("FFFAF0");
1626            }
1627        }
1628    }
1629
1630    // Image: sustainability roadmap
1631    let roadmap_img = create_sample_png(500, 100, [40, 80, 30]);
1632    doc.add_paragraph("");
1633    doc.add_picture(
1634        &roadmap_img,
1635        "roadmap.png",
1636        Length::inches(6.0),
1637        Length::inches(1.2),
1638    )
1639    .alignment(Alignment::Center);
1640    {
1641        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1642        p.add_run("Figure 2: 2026 Sustainability Roadmap")
1643            .italic(true)
1644            .size(9.0)
1645            .color("666666");
1646    }
1647
1648    doc.add_paragraph("");
1649    doc.add_paragraph(
1650        "For questions about this report, contact Walter White at sustainability@tensorbee.com.",
1651    )
1652    .shading("FFFAF0")
1653    .border_all(BorderStyle::Single, 2, "2D5016");
1654
1655    doc
1656}
1657
1658// =============================================================================
1659// 6. LETTER — Slate blue + silver scheme
1660// =============================================================================
1661fn generate_letter(_samples_dir: &Path) -> Document {
1662    let mut doc = Document::new();
1663
1664    // Colors: Slate #4A5568, Blue accent #3182CE
1665    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1666    doc.set_margins(
1667        Length::inches(1.25),
1668        Length::inches(1.25),
1669        Length::inches(1.25),
1670        Length::inches(1.25),
1671    );
1672    doc.set_title("Business Letter");
1673    doc.set_author("Walter White");
1674
1675    // Banner header using raw XML
1676    let logo_img = create_logo_png(220, 48);
1677    let banner = build_header_banner_xml(
1678        "rId1",
1679        &BannerOpts {
1680            bg_color: "4A5568",
1681            banner_width: 7772400,
1682            banner_height: 731520, // ~0.8"
1683            logo_width: 2011680,
1684            logo_height: 438912,
1685            logo_x_offset: 295125,
1686            logo_y_offset: 146304,
1687        },
1688    );
1689    doc.set_raw_header_with_images(
1690        banner,
1691        &[("rId1", &logo_img, "logo.png")],
1692        rdocx_oxml::header_footer::HdrFtrType::Default,
1693    );
1694    doc.set_margins(
1695        Length::twips(2000),
1696        Length::twips(1800),
1697        Length::twips(1440),
1698        Length::twips(1800),
1699    );
1700    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
1701
1702    // Footer with contact
1703    doc.set_footer(
1704        "Tensorbee | 123 Innovation Drive, Suite 400 | San Francisco, CA 94105 | tensorbee.com",
1705    );
1706
1707    // ── Sender Address ──
1708    doc.add_paragraph("Walter White");
1709    doc.add_paragraph("Chief Executive Officer");
1710    doc.add_paragraph("Tensorbee");
1711    doc.add_paragraph("123 Innovation Drive, Suite 400");
1712    doc.add_paragraph("San Francisco, CA 94105");
1713    doc.add_paragraph("");
1714    doc.add_paragraph("February 22, 2026");
1715
1716    doc.add_paragraph("");
1717
1718    // ── Recipient ──
1719    doc.add_paragraph("Ms. Sarah Chen");
1720    doc.add_paragraph("VP of Engineering");
1721    doc.add_paragraph("Meridian Dynamics LLC");
1722    doc.add_paragraph("456 Enterprise Boulevard");
1723    doc.add_paragraph("Austin, TX 78701");
1724
1725    doc.add_paragraph("");
1726
1727    // ── Subject ──
1728    {
1729        let mut p = doc.add_paragraph("");
1730        p.add_run("Re: Strategic Technology Partnership — Phase 2 Expansion")
1731            .bold(true);
1732    }
1733
1734    doc.add_paragraph("");
1735
1736    // ── Body ──
1737    doc.add_paragraph("Dear Ms. Chen,");
1738    doc.add_paragraph("");
1739
1740    doc.add_paragraph(
1741        "I am writing to express Tensorbee's enthusiasm for expanding our strategic technology \
1742         partnership with Meridian Dynamics. Following the successful completion of Phase 1, \
1743         which delivered a 40% improvement in your ML inference pipeline throughput, we believe \
1744         the foundation is firmly established for an ambitious Phase 2 engagement.",
1745    )
1746    .first_line_indent(Length::inches(0.5));
1747
1748    doc.add_paragraph(
1749        "During our recent executive review, your team highlighted three priority areas for \
1750         the next phase of collaboration:",
1751    )
1752    .first_line_indent(Length::inches(0.5));
1753
1754    doc.add_numbered_list_item(
1755        "Real-time anomaly detection for your financial transaction monitoring system, \
1756         targeting sub-100ms latency at 50,000 transactions per second.",
1757        0,
1758    );
1759    doc.add_numbered_list_item(
1760        "Federated learning infrastructure to enable model training across your distributed \
1761         data centers without centralizing sensitive financial data.",
1762        0,
1763    );
1764    doc.add_numbered_list_item(
1765        "MLOps automation to reduce your model deployment cycle from the current 5 days \
1766         to under 4 hours.",
1767        0,
1768    );
1769
1770    doc.add_paragraph(
1771        "Our engineering team has prepared a detailed technical proposal addressing each of \
1772         these areas. We have allocated a dedicated team of eight senior engineers, led by \
1773         our CTO, to ensure continuity with the Phase 1 team your organization has already \
1774         built a productive working relationship with.",
1775    )
1776    .first_line_indent(Length::inches(0.5));
1777
1778    doc.add_paragraph(
1779        "I would welcome the opportunity to present our Phase 2 proposal in person. My \
1780         assistant will reach out to coordinate a meeting at your convenience during the \
1781         first week of March.",
1782    )
1783    .first_line_indent(Length::inches(0.5));
1784
1785    doc.add_paragraph("");
1786
1787    doc.add_paragraph("Warm regards,");
1788    doc.add_paragraph("");
1789    doc.add_paragraph("");
1790
1791    // Signature line
1792    doc.add_paragraph("")
1793        .border_bottom(BorderStyle::Single, 4, "4A5568");
1794    {
1795        let mut p = doc.add_paragraph("");
1796        p.add_run("Walter White").bold(true).size(12.0);
1797    }
1798    {
1799        let mut p = doc.add_paragraph("");
1800        p.add_run("Chief Executive Officer, Tensorbee")
1801            .italic(true)
1802            .size(10.0)
1803            .color("666666");
1804    }
1805    {
1806        let mut p = doc.add_paragraph("");
1807        p.add_run("walter@tensorbee.com | +1 (415) 555-0199")
1808            .size(10.0)
1809            .color("888888");
1810    }
1811
1812    doc
1813}
1814
1815// =============================================================================
1816// 7. EMPLOYMENT CONTRACT — Purple + charcoal scheme
1817// =============================================================================
1818fn generate_contract(_samples_dir: &Path) -> Document {
1819    let mut doc = Document::new();
1820
1821    // Colors: Purple #5B2C6F, Plum #8E44AD, Gray #2C3E50
1822    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1823    doc.set_margins(
1824        Length::inches(1.25),
1825        Length::inches(1.0),
1826        Length::inches(1.0),
1827        Length::inches(1.0),
1828    );
1829    doc.set_title("Employment Agreement");
1830    doc.set_author("Tensorbee HR Department");
1831    doc.set_subject("Employment Contract — Walter White");
1832    doc.set_keywords("employment, contract, agreement, Tensorbee");
1833
1834    doc.set_header("TENSORBEE — Employment Agreement");
1835    doc.set_footer("Employment Agreement — Walter White — Page");
1836
1837    // ── Title Block ──
1838    doc.add_paragraph("")
1839        .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1840    {
1841        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1842        p.add_run("EMPLOYMENT AGREEMENT")
1843            .bold(true)
1844            .size(24.0)
1845            .color("5B2C6F")
1846            .font("Georgia");
1847    }
1848    doc.add_paragraph("")
1849        .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1850    doc.add_paragraph("");
1851
1852    // ── Parties ──
1853    doc.add_paragraph(
1854        "This Employment Agreement (\"Agreement\") is entered into as of February 22, 2026 \
1855         (\"Effective Date\"), by and between:",
1856    );
1857
1858    doc.add_paragraph("");
1859
1860    {
1861        let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1862        p.add_run("EMPLOYER: ").bold(true);
1863        p.add_run(
1864            "Tensorbee, Inc., a Delaware corporation with its principal place of business at \
1865                   123 Innovation Drive, Suite 400, San Francisco, CA 94105 (\"Company\")",
1866        );
1867    }
1868    doc.add_paragraph("");
1869    {
1870        let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1871        p.add_run("EMPLOYEE: ").bold(true);
1872        p.add_run(
1873            "Walter White, residing at 308 Negra Arroyo Lane, Albuquerque, NM 87104 (\"Employee\")",
1874        );
1875    }
1876
1877    doc.add_paragraph("");
1878    doc.add_paragraph("The Company and Employee are collectively referred to as the \"Parties.\"");
1879    doc.add_paragraph("");
1880
1881    // ── Article 1: Position and Duties ──
1882    doc.add_paragraph("Article 1 — Position and Duties")
1883        .style("Heading1");
1884    {
1885        let mut p = doc.add_paragraph("");
1886        p.add_run("1.1 ").bold(true);
1887        p.add_run("The Company hereby employs the Employee as ");
1888        p.add_run("Chief Executive Officer (CEO)")
1889            .bold(true)
1890            .italic(true);
1891        p.add_run(", reporting directly to the Board of Directors.");
1892    }
1893    {
1894        let mut p = doc.add_paragraph("");
1895        p.add_run("1.2 ").bold(true);
1896        p.add_run("The Employee shall devote full working time, attention, and best efforts to \
1897                   the performance of duties as reasonably assigned by the Board, including but not limited to:");
1898    }
1899    doc.add_bullet_list_item(
1900        "Setting and executing the Company's strategic vision and business plan",
1901        0,
1902    );
1903    doc.add_bullet_list_item(
1904        "Overseeing all operations, engineering, and commercial activities",
1905        0,
1906    );
1907    doc.add_bullet_list_item(
1908        "Representing the Company to investors, customers, and the public",
1909        0,
1910    );
1911    doc.add_bullet_list_item("Recruiting, developing, and retaining key talent", 0);
1912
1913    {
1914        let mut p = doc.add_paragraph("");
1915        p.add_run("1.3 ").bold(true);
1916        p.add_run(
1917            "The Employee's primary work location shall be the Company's San Francisco \
1918                   headquarters, with reasonable travel as required by business needs.",
1919        );
1920    }
1921
1922    // ── Article 2: Compensation ──
1923    doc.add_paragraph("Article 2 — Compensation and Benefits")
1924        .style("Heading1");
1925    {
1926        let mut p = doc.add_paragraph("");
1927        p.add_run("2.1 Base Salary. ").bold(true);
1928        p.add_run("The Company shall pay the Employee an annual base salary of ");
1929        p.add_run("$375,000.00").bold(true);
1930        p.add_run(" (Three Hundred Seventy-Five Thousand Dollars), payable in accordance with the \
1931                   Company's standard payroll schedule, less applicable withholdings and deductions.");
1932    }
1933    {
1934        let mut p = doc.add_paragraph("");
1935        p.add_run("2.2 Annual Bonus. ").bold(true);
1936        p.add_run("The Employee shall be eligible for an annual performance bonus of up to ");
1937        p.add_run("40%").bold(true);
1938        p.add_run(
1939            " of base salary, based on achievement of mutually agreed performance objectives.",
1940        );
1941    }
1942    {
1943        let mut p = doc.add_paragraph("");
1944        p.add_run("2.3 Equity. ").bold(true);
1945        p.add_run("Subject to Board approval, the Employee shall receive a stock option grant of ");
1946        p.add_run("500,000 shares").bold(true);
1947        p.add_run(
1948            " of the Company's common stock, vesting over four (4) years with a one-year cliff, \
1949                   at an exercise price equal to the fair market value on the date of grant.",
1950        );
1951    }
1952
1953    // Compensation summary table
1954    doc.add_paragraph("");
1955    {
1956        let mut tbl = doc
1957            .add_table(5, 2)
1958            .borders(BorderStyle::Single, 4, "5B2C6F")
1959            .width_pct(70.0)
1960            .alignment(Alignment::Center);
1961        tbl.cell(0, 0).unwrap().set_text("Compensation Element");
1962        tbl.cell(0, 0).unwrap().shading("5B2C6F");
1963        tbl.cell(0, 1).unwrap().set_text("Value");
1964        tbl.cell(0, 1).unwrap().shading("5B2C6F");
1965        let items = [
1966            ("Base Salary", "$375,000/year"),
1967            ("Target Bonus", "Up to 40% ($150,000)"),
1968            ("Equity Grant", "500,000 shares (4yr vest)"),
1969            ("Total Target Comp", "$525,000 + equity"),
1970        ];
1971        for (i, (elem, val)) in items.iter().enumerate() {
1972            tbl.cell(i + 1, 0).unwrap().set_text(elem);
1973            tbl.cell(i + 1, 1).unwrap().set_text(val);
1974            if i == 3 {
1975                tbl.cell(i + 1, 0).unwrap().shading("E8DAEF");
1976                tbl.cell(i + 1, 1).unwrap().shading("E8DAEF");
1977            }
1978        }
1979    }
1980
1981    // ── Article 3: Benefits ──
1982    doc.add_paragraph("").page_break_before(true);
1983    doc.add_paragraph("Article 3 — Benefits").style("Heading1");
1984    {
1985        let mut p = doc.add_paragraph("");
1986        p.add_run("3.1 ").bold(true);
1987        p.add_run(
1988            "The Employee shall be entitled to participate in all benefit programs \
1989                   generally available to senior executives, including:",
1990        );
1991    }
1992    doc.add_bullet_list_item(
1993        "Medical, dental, and vision insurance (100% premium coverage for employee and dependents)",
1994        0,
1995    );
1996    doc.add_bullet_list_item("401(k) retirement plan with 6% company match", 0);
1997    doc.add_bullet_list_item("Life insurance and long-term disability coverage", 0);
1998    doc.add_bullet_list_item("Annual professional development allowance of $10,000", 0);
1999
2000    {
2001        let mut p = doc.add_paragraph("");
2002        p.add_run("3.2 Paid Time Off. ").bold(true);
2003        p.add_run(
2004            "The Employee shall receive 25 days of paid vacation per year, plus Company holidays, \
2005                   accruing on a monthly basis.",
2006        );
2007    }
2008
2009    // ── Article 4: Term and Termination ──
2010    doc.add_paragraph("Article 4 — Term and Termination")
2011        .style("Heading1");
2012    {
2013        let mut p = doc.add_paragraph("");
2014        p.add_run("4.1 At-Will Employment. ").bold(true);
2015        p.add_run(
2016            "This Agreement is for at-will employment and may be terminated by either Party \
2017                   at any time, with or without cause, subject to the notice provisions herein.",
2018        );
2019    }
2020    {
2021        let mut p = doc.add_paragraph("");
2022        p.add_run("4.2 Notice Period. ").bold(true);
2023        p.add_run("Either Party shall provide ");
2024        p.add_run("ninety (90) days").bold(true);
2025        p.add_run(" written notice of termination, or pay in lieu thereof.");
2026    }
2027    {
2028        let mut p = doc.add_paragraph("");
2029        p.add_run("4.3 Severance. ").bold(true);
2030        p.add_run("In the event of termination by the Company without Cause, the Employee shall \
2031                   receive (a) twelve (12) months of base salary continuation, (b) pro-rata bonus \
2032                   for the year of termination, and (c) twelve (12) months of COBRA premium coverage.");
2033    }
2034
2035    // ── Article 5: Confidentiality ──
2036    doc.add_paragraph("Article 5 — Confidentiality and IP")
2037        .style("Heading1");
2038    {
2039        let mut p = doc.add_paragraph("");
2040        p.add_run("5.1 ").bold(true);
2041        p.add_run(
2042            "The Employee agrees to maintain strict confidentiality of all proprietary \
2043                   information, trade secrets, and business plans of the Company during and after \
2044                   employment.",
2045        );
2046    }
2047    {
2048        let mut p = doc.add_paragraph("");
2049        p.add_run("5.2 ").bold(true);
2050        p.add_run(
2051            "All intellectual property, inventions, and works of authorship created by the \
2052                   Employee during the term of employment and within the scope of duties shall be \
2053                   the sole and exclusive property of the Company.",
2054        );
2055    }
2056
2057    // ── Article 6: Non-Compete ──
2058    doc.add_paragraph("Article 6 — Non-Competition")
2059        .style("Heading1");
2060    {
2061        let mut p = doc.add_paragraph("");
2062        p.add_run("6.1 ").bold(true);
2063        p.add_run("For a period of twelve (12) months following termination, the Employee agrees \
2064                   not to engage in any business that directly competes with the Company's core \
2065                   business of AI infrastructure and ML operations services, within the United States.");
2066    }
2067
2068    // ── Article 7: General Provisions ──
2069    doc.add_paragraph("Article 7 — General Provisions")
2070        .style("Heading1");
2071    {
2072        let mut p = doc.add_paragraph("");
2073        p.add_run("7.1 Governing Law. ").bold(true);
2074        p.add_run(
2075            "This Agreement shall be governed by and construed in accordance with the laws of \
2076                   the State of California.",
2077        );
2078    }
2079    {
2080        let mut p = doc.add_paragraph("");
2081        p.add_run("7.2 Entire Agreement. ").bold(true);
2082        p.add_run(
2083            "This Agreement constitutes the entire agreement between the Parties and supersedes \
2084                   all prior negotiations, representations, and agreements.",
2085        );
2086    }
2087    {
2088        let mut p = doc.add_paragraph("");
2089        p.add_run("7.3 Amendment. ").bold(true);
2090        p.add_run(
2091            "This Agreement may only be amended by a written instrument signed by both Parties.",
2092        );
2093    }
2094
2095    // ── Signature Block ──
2096    doc.add_paragraph("").page_break_before(true);
2097    doc.add_paragraph("IN WITNESS WHEREOF, the Parties have executed this Employment Agreement as of the Effective Date.")
2098        .space_after(Length::pt(24.0));
2099
2100    // Signature table
2101    {
2102        let mut tbl = doc.add_table(6, 2).width_pct(100.0);
2103        tbl.cell(0, 0).unwrap().set_text("FOR THE COMPANY:");
2104        tbl.cell(0, 0).unwrap().shading("5B2C6F");
2105        tbl.cell(0, 1).unwrap().set_text("EMPLOYEE:");
2106        tbl.cell(0, 1).unwrap().shading("5B2C6F");
2107
2108        tbl.cell(1, 0).unwrap().set_text("");
2109        tbl.cell(1, 1).unwrap().set_text("");
2110        tbl.row(1).unwrap().height(Length::pt(40.0));
2111
2112        tbl.cell(2, 0)
2113            .unwrap()
2114            .set_text("Signature: ___________________________");
2115        tbl.cell(2, 1)
2116            .unwrap()
2117            .set_text("Signature: ___________________________");
2118        tbl.cell(3, 0)
2119            .unwrap()
2120            .set_text("Name: Board Representative");
2121        tbl.cell(3, 1).unwrap().set_text("Name: Walter White");
2122        tbl.cell(4, 0)
2123            .unwrap()
2124            .set_text("Title: Chair, Board of Directors");
2125        tbl.cell(4, 1)
2126            .unwrap()
2127            .set_text("Title: Chief Executive Officer");
2128        tbl.cell(5, 0).unwrap().set_text("Date: _______________");
2129        tbl.cell(5, 1).unwrap().set_text("Date: _______________");
2130    }
2131
2132    doc
2133}
Source

pub fn author(&self) -> Option<&str>

Get the document author/creator.

Source

pub fn set_author(&mut self, author: &str)

Set the document author/creator.

Examples found in repository?
examples/generate_pdf.rs (line 21)
7fn main() {
8    // Test 1: Simple document
9    let doc = Document::new();
10    match doc.to_pdf() {
11        Ok(bytes) => {
12            std::fs::write("/tmp/rdocx_simple.pdf", &bytes).unwrap();
13            println!("Simple PDF: {} bytes -> /tmp/rdocx_simple.pdf", bytes.len());
14        }
15        Err(e) => println!("Simple PDF failed: {e}"),
16    }
17
18    // Test 2: Document with content
19    let mut doc = Document::new();
20    doc.set_title("Test PDF Document");
21    doc.set_author("rdocx-pdf");
22    doc.add_paragraph("Chapter 1: Introduction")
23        .style("Heading1");
24    doc.add_paragraph(
25        "This is a test document generated by rdocx and rendered to PDF. \
26         It demonstrates text rendering with proper font shaping and pagination.",
27    );
28    doc.add_paragraph("Section 1.1").style("Heading2");
29    doc.add_paragraph("More content in a sub-section.");
30
31    {
32        let mut table = doc.add_table(2, 3);
33        for r in 0..2 {
34            for c in 0..3 {
35                if let Some(mut cell) = table.cell(r, c) {
36                    cell.set_text(&format!("R{}C{}", r + 1, c + 1));
37                }
38            }
39        }
40    }
41
42    doc.add_paragraph("After the table.");
43
44    match doc.to_pdf() {
45        Ok(bytes) => {
46            std::fs::write("/tmp/rdocx_content.pdf", &bytes).unwrap();
47            println!(
48                "Content PDF: {} bytes -> /tmp/rdocx_content.pdf",
49                bytes.len()
50            );
51        }
52        Err(e) => println!("Content PDF failed: {e}"),
53    }
54
55    // Test 3: From feature_showcase.docx
56    let showcase_path = concat!(
57        env!("CARGO_MANIFEST_DIR"),
58        "/../../samples/feature_showcase.docx"
59    );
60    match Document::open(showcase_path) {
61        Ok(doc) => match doc.to_pdf() {
62            Ok(bytes) => {
63                std::fs::write("/tmp/rdocx_showcase.pdf", &bytes).unwrap();
64                println!(
65                    "Showcase PDF: {} bytes -> /tmp/rdocx_showcase.pdf",
66                    bytes.len()
67                );
68            }
69            Err(e) => println!("Showcase PDF failed: {e}"),
70        },
71        Err(e) => println!("Failed to open showcase: {e}"),
72    }
73}
More examples
Hide additional examples
examples/template_replace.rs (line 156)
43fn create_template(path: &Path) {
44    let mut doc = Document::new();
45    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
46    doc.set_margins(
47        Length::inches(1.0),
48        Length::inches(1.0),
49        Length::inches(1.0),
50        Length::inches(1.0),
51    );
52
53    doc.set_header("{{company_name}} — Confidential");
54    doc.set_footer("Prepared by {{author_name}} on {{date}}");
55
56    // ── Title ──
57    doc.add_paragraph("{{company_name}}")
58        .style("Heading1")
59        .alignment(Alignment::Center);
60
61    doc.add_paragraph("Project Proposal")
62        .alignment(Alignment::Center);
63
64    doc.add_paragraph("");
65
66    // ── Summary section ──
67    doc.add_paragraph("Executive Summary").style("Heading2");
68
69    doc.add_paragraph(
70        "This proposal outlines the {{project_name}} project for {{company_name}}. \
71         The primary contact is {{contact_name}} ({{contact_email}}). \
72         The proposed start date is {{start_date}} with an estimated duration of {{duration}}.",
73    );
74
75    doc.add_paragraph("");
76
77    // ── Cross-run placeholder (bold label + normal value) ──
78    doc.add_paragraph("Key Details").style("Heading2");
79
80    {
81        let mut p = doc.add_paragraph("");
82        p.add_run("Project: ").bold(true);
83        p.add_run("{{project_name}}");
84    }
85    {
86        let mut p = doc.add_paragraph("");
87        p.add_run("Budget: ").bold(true);
88        p.add_run("{{budget}}");
89    }
90    {
91        let mut p = doc.add_paragraph("");
92        p.add_run("Status: ").bold(true);
93        p.add_run("{{status}}");
94    }
95
96    doc.add_paragraph("");
97
98    // ── Table with placeholders ──
99    doc.add_paragraph("Team Members").style("Heading2");
100
101    {
102        let mut tbl = doc.add_table(4, 3);
103        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
104
105        // Header row
106        for col in 0..3 {
107            tbl.cell(0, col).unwrap().shading("2E75B6");
108        }
109        tbl.cell(0, 0).unwrap().set_text("Name");
110        tbl.cell(0, 1).unwrap().set_text("Role");
111        tbl.cell(0, 2).unwrap().set_text("Email");
112
113        tbl.cell(1, 0).unwrap().set_text("{{member1_name}}");
114        tbl.cell(1, 1).unwrap().set_text("{{member1_role}}");
115        tbl.cell(1, 2).unwrap().set_text("{{member1_email}}");
116
117        tbl.cell(2, 0).unwrap().set_text("{{member2_name}}");
118        tbl.cell(2, 1).unwrap().set_text("{{member2_role}}");
119        tbl.cell(2, 2).unwrap().set_text("{{member2_email}}");
120
121        tbl.cell(3, 0).unwrap().set_text("{{member3_name}}");
122        tbl.cell(3, 1).unwrap().set_text("{{member3_role}}");
123        tbl.cell(3, 2).unwrap().set_text("{{member3_email}}");
124    }
125
126    doc.add_paragraph("");
127
128    // ── Deliverables section ──
129    doc.add_paragraph("Deliverables").style("Heading2");
130
131    doc.add_paragraph("INSERTION_POINT");
132
133    doc.add_paragraph("");
134
135    // ── Signature block ──
136    doc.add_paragraph("Acceptance").style("Heading2");
137
138    doc.add_paragraph(
139        "By signing below, {{company_name}} agrees to the terms outlined in this proposal.",
140    );
141
142    {
143        let mut tbl = doc.add_table(2, 2);
144        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
145        tbl.cell(0, 0)
146            .unwrap()
147            .set_text("Customer: ___________________");
148        tbl.cell(0, 1)
149            .unwrap()
150            .set_text("Provider: ___________________");
151        tbl.cell(1, 0).unwrap().set_text("Date: {{date}}");
152        tbl.cell(1, 1).unwrap().set_text("Date: {{date}}");
153    }
154
155    doc.set_title("{{company_name}} — Project Proposal Template");
156    doc.set_author("Template Generator");
157
158    doc.save(path).unwrap();
159}
160
161/// Open the template, replace all placeholders, insert content, and save.
162fn fill_template(template_path: &Path, output_path: &Path) {
163    let mut doc = Document::open(template_path).unwrap();
164
165    // ── Batch replacement ──
166    let mut replacements = HashMap::new();
167    replacements.insert("{{company_name}}", "Riverside Medical Center");
168    replacements.insert("{{project_name}}", "Network Security Upgrade");
169    replacements.insert("{{contact_name}}", "Dr. Sarah Chen");
170    replacements.insert("{{contact_email}}", "s.chen@riverside.org");
171    replacements.insert("{{start_date}}", "March 1, 2026");
172    replacements.insert("{{duration}}", "12 weeks");
173    replacements.insert("{{budget}}", "$185,000");
174    replacements.insert("{{status}}", "Pending Approval");
175    replacements.insert("{{author_name}}", "James Wilson");
176    replacements.insert("{{date}}", "February 22, 2026");
177
178    // Team members
179    replacements.insert("{{member1_name}}", "James Wilson");
180    replacements.insert("{{member1_role}}", "Project Lead");
181    replacements.insert("{{member1_email}}", "j.wilson@provider.com");
182    replacements.insert("{{member2_name}}", "Maria Garcia");
183    replacements.insert("{{member2_role}}", "Security Architect");
184    replacements.insert("{{member2_email}}", "m.garcia@provider.com");
185    replacements.insert("{{member3_name}}", "David Park");
186    replacements.insert("{{member3_role}}", "Network Engineer");
187    replacements.insert("{{member3_email}}", "d.park@provider.com");
188
189    let count = doc.replace_all(&replacements);
190    println!("  Replaced {} placeholders", count);
191
192    // ── Insert deliverables at the insertion point ──
193    if let Some(idx) = doc.find_content_index("INSERTION_POINT") {
194        // Remove the placeholder paragraph
195        doc.remove_content(idx);
196
197        // Insert deliverables list
198        doc.insert_paragraph(idx, "The following deliverables are included:");
199
200        // Insert a deliverables table
201        let mut tbl = doc.insert_table(idx + 1, 5, 3);
202        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
203
204        for col in 0..3 {
205            tbl.cell(0, col).unwrap().shading("E2EFDA");
206        }
207        tbl.cell(0, 0).unwrap().set_text("Phase");
208        tbl.cell(0, 1).unwrap().set_text("Description");
209        tbl.cell(0, 2).unwrap().set_text("Timeline");
210
211        tbl.cell(1, 0).unwrap().set_text("1. Discovery");
212        tbl.cell(1, 1)
213            .unwrap()
214            .set_text("Network assessment and asset inventory");
215        tbl.cell(1, 2).unwrap().set_text("Weeks 1-3");
216
217        tbl.cell(2, 0).unwrap().set_text("2. Design");
218        tbl.cell(2, 1)
219            .unwrap()
220            .set_text("Security architecture and policy design");
221        tbl.cell(2, 2).unwrap().set_text("Weeks 4-6");
222
223        tbl.cell(3, 0).unwrap().set_text("3. Implementation");
224        tbl.cell(3, 1)
225            .unwrap()
226            .set_text("Deploy monitoring and access controls");
227        tbl.cell(3, 2).unwrap().set_text("Weeks 7-10");
228
229        tbl.cell(4, 0).unwrap().set_text("4. Validation");
230        tbl.cell(4, 1)
231            .unwrap()
232            .set_text("Testing, training, and handover");
233        tbl.cell(4, 2).unwrap().set_text("Weeks 11-12");
234
235        println!("  Inserted deliverables table at position {}", idx);
236    }
237
238    // ── Update metadata ──
239    doc.set_title("Riverside Medical Center — Network Security Upgrade Proposal");
240    doc.set_author("James Wilson");
241    doc.set_subject("Project Proposal");
242    doc.set_keywords("security, network, medical, proposal");
243
244    doc.save(output_path).unwrap();
245}
examples/header_banner.rs (line 166)
29fn generate_header_banner_doc(path: &Path) {
30    let mut doc = Document::new();
31
32    // Page setup with extra top margin for the banner
33    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
34    doc.set_margins(
35        Length::twips(2292), // top — extra tall for header banner
36        Length::twips(1440), // right
37        Length::twips(1440), // bottom
38        Length::twips(1440), // left
39    );
40    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
41
42    // Generate a simple logo image (white text on transparent background)
43    let logo_img = create_logo_png(220, 48);
44
45    // ── Dark blue banner ──
46    let banner = build_header_banner_xml(
47        "rId1",
48        &BannerOpts {
49            bg_color: "1A3C6E",
50            banner_width: 7772400, // full page width in EMU (~8.5")
51            banner_height: 969026, // banner height in EMU (~1.06")
52            logo_width: 2011680,   // logo display width (~2.2")
53            logo_height: 438912,   // logo display height (~0.48")
54            logo_x_offset: 295125, // left padding
55            logo_y_offset: 265057, // vertical centering
56        },
57    );
58
59    doc.set_raw_header_with_images(
60        banner.clone(),
61        &[("rId1", &logo_img, "logo.png")],
62        rdocx_oxml::header_footer::HdrFtrType::Default,
63    );
64
65    // Use a different first page header (same banner, different color)
66    doc.set_different_first_page(true);
67    let first_page_banner = build_header_banner_xml(
68        "rId1",
69        &BannerOpts {
70            bg_color: "2E75B6", // lighter blue for cover
71            banner_width: 7772400,
72            banner_height: 969026,
73            logo_width: 2011680,
74            logo_height: 438912,
75            logo_x_offset: 295125,
76            logo_y_offset: 265057,
77        },
78    );
79    doc.set_raw_header_with_images(
80        first_page_banner,
81        &[("rId1", &logo_img, "logo.png")],
82        rdocx_oxml::header_footer::HdrFtrType::First,
83    );
84
85    // Footer
86    doc.set_footer("Confidential — Internal Use Only");
87
88    // ── Page 1: Cover ──
89    doc.add_paragraph("Company Report").style("Heading1");
90
91    doc.add_paragraph(
92        "This document demonstrates a custom header banner built with DrawingML \
93         group shapes. The banner uses a colored rectangle with a logo image overlaid, \
94         positioned at the top of each page.",
95    );
96
97    doc.add_paragraph("");
98
99    doc.add_paragraph("How the Header Banner Works")
100        .style("Heading2");
101
102    doc.add_paragraph(
103        "The header banner is built using set_raw_header_with_images(), which \
104         accepts raw XML and a list of (rel_id, image_data, filename) tuples. \
105         The XML uses a DrawingML group shape (wpg:wgp) containing:",
106    );
107
108    doc.add_bullet_list_item(
109        "A wps:wsp rectangle shape with a solid color fill (the background bar)",
110        0,
111    );
112    doc.add_bullet_list_item(
113        "A pic:pic image element positioned within the group (the logo)",
114        0,
115    );
116    doc.add_bullet_list_item(
117        "The group is wrapped in a wp:anchor element for absolute page positioning",
118        0,
119    );
120
121    doc.add_paragraph("");
122
123    doc.add_paragraph("Customization").style("Heading2");
124
125    doc.add_paragraph(
126        "All dimensions are in EMU (English Metric Units) where 914400 EMU = 1 inch. \
127         You can customize:",
128    );
129
130    doc.add_bullet_list_item("bg_color — any hex color for the rectangle background", 0);
131    doc.add_bullet_list_item("banner_width / banner_height — size of the full banner", 0);
132    doc.add_bullet_list_item("logo_width / logo_height — display size of the logo", 0);
133    doc.add_bullet_list_item(
134        "logo_x_offset / logo_y_offset — logo position within the banner",
135        0,
136    );
137
138    doc.add_paragraph("");
139
140    doc.add_paragraph("Different First Page").style("Heading2");
141
142    doc.add_paragraph(
143        "This page uses a lighter blue banner (first page header). \
144         Subsequent pages use a darker blue banner (default header). \
145         Use set_different_first_page(true) to enable this.",
146    );
147
148    // ── Page 2 ──
149    doc.add_paragraph("").page_break_before(true);
150
151    doc.add_paragraph("Second Page").style("Heading1");
152
153    doc.add_paragraph(
154        "This page shows the default header banner (dark blue). The first page \
155         had a lighter blue banner because we set a different first-page header.",
156    );
157
158    doc.add_paragraph("");
159
160    doc.add_paragraph(
161        "The banner repeats on every page because it is placed in the header part. \
162         You can have different banners for default, first-page, and even-page headers.",
163    );
164
165    doc.set_title("Header Banner Example");
166    doc.set_author("rdocx");
167
168    doc.save(path).unwrap();
169}
examples/styled_tables.rs (line 446)
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}
examples/generate_all_samples.rs (line 111)
97fn generate_feature_showcase(_samples_dir: &Path) -> Document {
98    let mut doc = Document::new();
99
100    // ── Page Setup & Metadata ──
101    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
102    doc.set_margins(
103        Length::inches(1.0),
104        Length::inches(1.0),
105        Length::inches(1.0),
106        Length::inches(1.0),
107    );
108    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
109    doc.set_gutter(Length::twips(0));
110    doc.set_title("rdocx Feature Showcase");
111    doc.set_author("rdocx Sample Generator");
112    doc.set_subject("Comprehensive feature demonstration");
113    doc.set_keywords("rdocx, docx, rust, sample, showcase");
114
115    // Headers & Footers with different first page
116    doc.set_different_first_page(true);
117    doc.set_first_page_header("rdocx");
118    doc.set_first_page_footer("Feature Showcase — Cover Page");
119    doc.set_header("rdocx Feature Showcase");
120    doc.set_footer("Generated by rdocx");
121
122    // ── COVER PAGE ──
123    let bg = create_sample_png(612, 792, [20, 45, 90]);
124    doc.add_background_image(&bg, "cover_bg.png");
125
126    for _ in 0..3 {
127        doc.add_paragraph("");
128    }
129    {
130        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
131        p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
132    }
133    {
134        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
135        p.add_run("Complete Feature Showcase")
136            .size(28.0)
137            .color("FFFFFF")
138            .italic(true);
139    }
140    doc.add_paragraph("");
141    {
142        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
143        p.add_run("Every feature of the rdocx Rust library")
144            .size(13.0)
145            .color("B0C4DE");
146    }
147    {
148        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
149        p.add_run("demonstrated in a single document.")
150            .size(13.0)
151            .color("B0C4DE");
152    }
153
154    // ── TABLE OF CONTENTS ──
155    doc.add_paragraph("").page_break_before(true);
156    doc.insert_toc(doc.content_count(), 3);
157
158    // ── SECTION 1: TEXT FORMATTING ──
159    doc.add_paragraph("").page_break_before(true);
160    doc.add_paragraph("1. Text Formatting").style("Heading1");
161
162    // Paragraph Alignment
163    doc.add_paragraph("1.1 Paragraph Alignment")
164        .style("Heading2");
165    doc.add_paragraph("Left-aligned paragraph (default).")
166        .alignment(Alignment::Left);
167    doc.add_paragraph("Center-aligned paragraph.")
168        .alignment(Alignment::Center);
169    doc.add_paragraph("Right-aligned paragraph.")
170        .alignment(Alignment::Right);
171    doc.add_paragraph(
172        "Justified paragraph — this text is long enough to demonstrate how justified alignment \
173         distributes extra space across word gaps so lines fill the full width of the text area.",
174    )
175    .alignment(Alignment::Justify);
176
177    doc.add_paragraph("");
178
179    // Run Formatting
180    doc.add_paragraph("1.2 Run Formatting").style("Heading2");
181    {
182        let mut p = doc.add_paragraph("");
183        p.add_run("Bold").bold(true);
184        p.add_run(" | ");
185        p.add_run("Italic").italic(true);
186        p.add_run(" | ");
187        p.add_run("Bold Italic").bold(true).italic(true);
188        p.add_run(" | ");
189        p.add_run("Underline").underline(true);
190        p.add_run(" | ");
191        p.add_run("Strikethrough").strike(true);
192        p.add_run(" | ");
193        p.add_run("Double Strike").double_strike(true);
194    }
195    {
196        let mut p = doc.add_paragraph("");
197        p.add_run("Red").color("FF0000");
198        p.add_run(" | ");
199        p.add_run("Blue").color("0000FF");
200        p.add_run(" | ");
201        p.add_run("Green").color("00AA00");
202        p.add_run(" | ");
203        p.add_run("Highlighted").highlight("FFFF00");
204    }
205    {
206        let mut p = doc.add_paragraph("");
207        p.add_run("8pt").size(8.0);
208        p.add_run(" | ");
209        p.add_run("11pt").size(11.0);
210        p.add_run(" | ");
211        p.add_run("16pt").size(16.0);
212        p.add_run(" | ");
213        p.add_run("24pt").size(24.0);
214    }
215    {
216        let mut p = doc.add_paragraph("");
217        p.add_run("Arial").font("Arial");
218        p.add_run(" | ");
219        p.add_run("Times New Roman").font("Times New Roman");
220        p.add_run(" | ");
221        p.add_run("Courier New").font("Courier New");
222    }
223    {
224        let mut p = doc.add_paragraph("");
225        p.add_run("H");
226        p.add_run("2").subscript();
227        p.add_run("O (subscript) | E=mc");
228        p.add_run("2").superscript();
229        p.add_run(" (superscript)");
230    }
231    {
232        let mut p = doc.add_paragraph("");
233        p.add_run("ALL CAPS").all_caps(true);
234        p.add_run(" | ");
235        p.add_run("Small Caps").small_caps(true);
236        p.add_run(" | ");
237        p.add_run("Expanded").character_spacing(Length::twips(40));
238        p.add_run(" | ");
239        p.add_run("Hidden text (not visible)").hidden(true);
240    }
241
242    doc.add_paragraph("");
243
244    // Underline Styles
245    doc.add_paragraph("1.3 Underline Styles").style("Heading2");
246    {
247        let mut p = doc.add_paragraph("");
248        p.add_run("Single ")
249            .underline_style(rdocx::UnderlineStyle::Single);
250        p.add_run("Double ")
251            .underline_style(rdocx::UnderlineStyle::Double);
252        p.add_run("Thick ")
253            .underline_style(rdocx::UnderlineStyle::Thick);
254        p.add_run("Dotted ")
255            .underline_style(rdocx::UnderlineStyle::Dotted);
256        p.add_run("Dash ")
257            .underline_style(rdocx::UnderlineStyle::Dash);
258        p.add_run("Wave ")
259            .underline_style(rdocx::UnderlineStyle::Wave);
260    }
261
262    // ── SECTION 2: PARAGRAPH FORMATTING ──
263    doc.add_paragraph("").page_break_before(true);
264    doc.add_paragraph("2. Paragraph Formatting")
265        .style("Heading1");
266
267    doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
268    doc.add_paragraph("Paragraph with light green shading")
269        .shading("E2EFDA");
270    doc.add_paragraph("Paragraph with bottom border")
271        .border_bottom(BorderStyle::Single, 6, "2E75B6");
272    doc.add_paragraph("Paragraph with all borders (red)")
273        .border_all(BorderStyle::Single, 4, "FF0000");
274
275    doc.add_paragraph("2.2 Indentation").style("Heading2");
276    doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
277        .indent_left(Length::inches(1.0))
278        .hanging_indent(Length::inches(0.5));
279    doc.add_paragraph("0.5-inch first-line indent on this paragraph")
280        .first_line_indent(Length::inches(0.5));
281    doc.add_paragraph("Both left and right indent (0.75 inches each)")
282        .indent_left(Length::inches(0.75))
283        .indent_right(Length::inches(0.75));
284
285    doc.add_paragraph("2.3 Spacing & Line Height")
286        .style("Heading2");
287    doc.add_paragraph("Extra space before (24pt) and after (12pt)")
288        .space_before(Length::pt(24.0))
289        .space_after(Length::pt(12.0));
290    doc.add_paragraph(
291        "Double line spacing paragraph. This text should have extra vertical space between \
292         lines to demonstrate line_spacing_multiple(2.0).",
293    )
294    .line_spacing_multiple(2.0);
295    doc.add_paragraph("Exact 20pt line spacing (fixed height).")
296        .line_spacing(20.0);
297
298    doc.add_paragraph("2.4 Pagination Controls")
299        .style("Heading2");
300    doc.add_paragraph("keep_with_next — this paragraph stays with the next")
301        .keep_with_next(true);
302    doc.add_paragraph("(This paragraph was kept with the one above.)");
303    doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
304        .keep_together(true);
305    doc.add_paragraph("widow_control — prevents widow/orphan lines")
306        .widow_control(true);
307
308    // ── SECTION 3: LISTS ──
309    doc.add_paragraph("").page_break_before(true);
310    doc.add_paragraph("3. Lists").style("Heading1");
311
312    doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
313    doc.add_bullet_list_item("First item", 0);
314    doc.add_bullet_list_item("Second item", 0);
315    doc.add_bullet_list_item("Nested level 1", 1);
316    doc.add_bullet_list_item("Nested level 2", 2);
317    doc.add_bullet_list_item("Back to level 1", 1);
318    doc.add_bullet_list_item("Third item", 0);
319
320    doc.add_paragraph("");
321
322    doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
323    doc.add_numbered_list_item("First numbered item", 0);
324    doc.add_numbered_list_item("Second numbered item", 0);
325    doc.add_numbered_list_item("Sub-item A", 1);
326    doc.add_numbered_list_item("Sub-item B", 1);
327    doc.add_numbered_list_item("Third numbered item", 0);
328
329    // ── SECTION 4: TAB STOPS ──
330    doc.add_paragraph("");
331    doc.add_paragraph("4. Tab Stops").style("Heading1");
332
333    doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
334    doc.add_paragraph("Left\tCenter\tRight\tDecimal")
335        .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
336        .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
337        .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
338        .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
339
340    doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
341    doc.add_paragraph("Item\t\tPrice")
342        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
343        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
344        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
345    doc.add_paragraph("Widget\t\t$19.99")
346        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
347        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
348        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
349    doc.add_paragraph("Gadget\t\t$249.50")
350        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
351        .add_tab_stop_with_leader(
352            TabAlignment::Right,
353            Length::inches(4.0),
354            TabLeader::Underscore,
355        )
356        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
357
358    // ── SECTION 5: TABLES ──
359    doc.add_paragraph("").page_break_before(true);
360    doc.add_paragraph("5. Tables").style("Heading1");
361
362    doc.add_paragraph("5.1 Basic Table").style("Heading2");
363    {
364        let mut tbl = doc
365            .add_table(4, 3)
366            .borders(BorderStyle::Single, 4, "000000");
367        for col in 0..3 {
368            tbl.cell(0, col).unwrap().shading("2E75B6");
369        }
370        tbl.cell(0, 0).unwrap().set_text("Name");
371        tbl.cell(0, 1).unwrap().set_text("Role");
372        tbl.cell(0, 2).unwrap().set_text("Location");
373        tbl.cell(1, 0).unwrap().set_text("Walter White");
374        tbl.cell(1, 1).unwrap().set_text("CEO");
375        tbl.cell(1, 2).unwrap().set_text("Albuquerque");
376        tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
377        tbl.cell(2, 1).unwrap().set_text("CTO");
378        tbl.cell(2, 2).unwrap().set_text("Remote");
379        tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
380        tbl.cell(3, 1).unwrap().set_text("Security");
381        tbl.cell(3, 2).unwrap().set_text("Washington");
382    }
383
384    doc.add_paragraph("");
385
386    doc.add_paragraph("5.2 Column Span & Cell Shading")
387        .style("Heading2");
388    {
389        let mut tbl = doc
390            .add_table(3, 4)
391            .borders(BorderStyle::Single, 4, "000000")
392            .width_pct(100.0);
393        tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
394        tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
395        tbl.cell(1, 0).unwrap().set_text("Region");
396        tbl.cell(1, 0).unwrap().shading("D6E4F0");
397        tbl.cell(1, 1).unwrap().set_text("Q1");
398        tbl.cell(1, 1).unwrap().shading("D6E4F0");
399        tbl.cell(1, 2).unwrap().set_text("Q2");
400        tbl.cell(1, 2).unwrap().shading("D6E4F0");
401        tbl.cell(1, 3).unwrap().set_text("Total");
402        tbl.cell(1, 3).unwrap().shading("D6E4F0");
403        tbl.cell(2, 0).unwrap().set_text("Americas");
404        tbl.cell(2, 1).unwrap().set_text("$2.4M");
405        tbl.cell(2, 2).unwrap().set_text("$2.7M");
406        tbl.cell(2, 3).unwrap().set_text("$5.1M");
407    }
408
409    doc.add_paragraph("");
410
411    doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
412    {
413        let mut tbl = doc
414            .add_table(4, 3)
415            .borders(BorderStyle::Single, 4, "000000");
416        tbl.cell(0, 0).unwrap().set_text("Category");
417        tbl.cell(0, 0).unwrap().shading("E2EFDA");
418        tbl.cell(0, 1).unwrap().set_text("Item");
419        tbl.cell(0, 1).unwrap().shading("E2EFDA");
420        tbl.cell(0, 2).unwrap().set_text("Price");
421        tbl.cell(0, 2).unwrap().shading("E2EFDA");
422        tbl.cell(1, 0).unwrap().set_text("Hardware");
423        tbl.cell(1, 0).unwrap().v_merge_restart();
424        tbl.cell(1, 1).unwrap().set_text("Laptop");
425        tbl.cell(1, 2).unwrap().set_text("$1,200");
426        tbl.cell(2, 0).unwrap().v_merge_continue();
427        tbl.cell(2, 1).unwrap().set_text("Monitor");
428        tbl.cell(2, 2).unwrap().set_text("$450");
429        tbl.cell(3, 0).unwrap().set_text("Software");
430        tbl.cell(3, 1).unwrap().set_text("IDE License");
431        tbl.cell(3, 2).unwrap().set_text("$200/yr");
432    }
433
434    doc.add_paragraph("");
435
436    doc.add_paragraph("5.4 Nested Table").style("Heading2");
437    {
438        let mut tbl = doc
439            .add_table(2, 2)
440            .borders(BorderStyle::Single, 6, "2E75B6");
441        tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
442        tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
443        tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
444        {
445            let mut cell = tbl.cell(1, 1).unwrap();
446            cell.set_text("Nested table below:");
447            let mut nested = cell
448                .add_table(2, 2)
449                .borders(BorderStyle::Single, 2, "FF6600");
450            nested.cell(0, 0).unwrap().set_text("A");
451            nested.cell(0, 1).unwrap().set_text("B");
452            nested.cell(1, 0).unwrap().set_text("C");
453            nested.cell(1, 1).unwrap().set_text("D");
454        }
455    }
456
457    doc.add_paragraph("");
458
459    doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
460        .style("Heading2");
461    {
462        let mut tbl = doc
463            .add_table(2, 3)
464            .borders(BorderStyle::Single, 4, "666666");
465        tbl.row(0).unwrap().height(Length::pt(50.0)).header();
466        tbl.cell(0, 0).unwrap().set_text("Top");
467        tbl.cell(0, 0)
468            .unwrap()
469            .vertical_alignment(VerticalAlignment::Top)
470            .shading("FFF2CC");
471        tbl.cell(0, 1).unwrap().set_text("Center");
472        tbl.cell(0, 1)
473            .unwrap()
474            .vertical_alignment(VerticalAlignment::Center)
475            .shading("D9E2F3");
476        tbl.cell(0, 2).unwrap().set_text("Bottom");
477        tbl.cell(0, 2)
478            .unwrap()
479            .vertical_alignment(VerticalAlignment::Bottom)
480            .shading("E2EFDA");
481        tbl.row(1).unwrap().cant_split();
482        tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
483        tbl.cell(1, 0).unwrap().no_wrap();
484        tbl.cell(1, 1).unwrap().set_text("Fixed width");
485        tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
486        tbl.cell(1, 2).unwrap().set_text("Normal");
487    }
488
489    // ── SECTION 6: IMAGES ──
490    doc.add_paragraph("").page_break_before(true);
491    doc.add_paragraph("6. Images").style("Heading1");
492
493    doc.add_paragraph("6.1 Inline Image").style("Heading2");
494    doc.add_paragraph("A blue gradient image below:");
495    let img = create_sample_png(200, 50, [0, 80, 200]);
496    doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
497
498    doc.add_paragraph("");
499    doc.add_paragraph("6.2 Header Image").style("Heading2");
500    let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
501    doc.set_header_image(
502        &hdr_img,
503        "header_logo.png",
504        Length::inches(2.0),
505        Length::inches(0.2),
506    );
507    doc.add_paragraph("The header now contains an inline image (check the top of this page).");
508
509    doc.add_paragraph("");
510    doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
511
512    // ── SECTION 7: CONTENT MANIPULATION ──
513    doc.add_paragraph("").page_break_before(true);
514    doc.add_paragraph("7. Content Manipulation")
515        .style("Heading1");
516
517    doc.add_paragraph("7.1 Placeholder Replacement")
518        .style("Heading2");
519    doc.add_paragraph("Customer: {{customer}}");
520    doc.add_paragraph("Date: {{date}}");
521    doc.add_paragraph("Reference: {{ref_number}}");
522    {
523        let mut tbl = doc
524            .add_table(2, 2)
525            .borders(BorderStyle::Single, 4, "000000");
526        tbl.cell(0, 0).unwrap().set_text("Project");
527        tbl.cell(0, 0).unwrap().shading("D6E4F0");
528        tbl.cell(0, 1).unwrap().set_text("{{project}}");
529        tbl.cell(1, 0).unwrap().set_text("Status");
530        tbl.cell(1, 0).unwrap().shading("D6E4F0");
531        tbl.cell(1, 1).unwrap().set_text("{{status}}");
532    }
533
534    let mut replacements = HashMap::new();
535    replacements.insert("{{customer}}", "Tensorbee Inc.");
536    replacements.insert("{{date}}", "February 22, 2026");
537    replacements.insert("{{ref_number}}", "TB-2026-001");
538    replacements.insert("{{project}}", "Infrastructure Upgrade");
539    replacements.insert("{{status}}", "In Progress");
540    let n = doc.replace_all(&replacements);
541    doc.add_paragraph(&format!("({n} placeholders replaced above)"));
542
543    doc.add_paragraph("");
544
545    doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
546    doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
547    let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
548    doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
549
550    doc.add_paragraph("");
551
552    doc.add_paragraph("7.3 Content Insertion").style("Heading2");
553    doc.add_paragraph("Section A: First content.");
554    doc.add_paragraph("Section C: Third content.");
555    if let Some(idx) = doc.find_content_index("Section C") {
556        doc.insert_paragraph(
557            idx,
558            "Section B: Inserted between A and C via find_content_index().",
559        );
560    }
561
562    // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
563    doc.add_paragraph("").section_break(SectionBreak::NextPage);
564    doc.add_paragraph("8. Section Breaks & Orientation")
565        .style("Heading1");
566    doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
567
568    {
569        let mut tbl = doc
570            .add_table(3, 7)
571            .borders(BorderStyle::Single, 4, "2E75B6");
572        let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
573        for (c, h) in headers.iter().enumerate() {
574            tbl.cell(0, c).unwrap().set_text(h);
575            tbl.cell(0, c).unwrap().shading("2E75B6");
576        }
577        let data = [
578            [
579                "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
580            ],
581            [
582                "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
583            ],
584        ];
585        for (r, row) in data.iter().enumerate() {
586            for (c, v) in row.iter().enumerate() {
587                tbl.cell(r + 1, c).unwrap().set_text(v);
588            }
589        }
590    }
591
592    doc.add_paragraph("")
593        .section_break(SectionBreak::NextPage)
594        .section_landscape();
595
596    // ── SECTION 9: CUSTOM STYLES ──
597    doc.add_paragraph("9. Custom Styles").style("Heading1");
598    doc.add_style(
599        StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
600            .based_on("Normal")
601            .paragraph_properties(rdocx_oxml::properties::CT_PPr {
602                shading: Some(rdocx_oxml::properties::CT_Shd {
603                    val: "clear".to_string(),
604                    color: None,
605                    fill: Some("FFF2CC".to_string()),
606                }),
607                ..Default::default()
608            })
609            .run_properties(rdocx_oxml::properties::CT_RPr {
610                bold: Some(true),
611                color: Some("C45911".to_string()),
612                ..Default::default()
613            }),
614    );
615    doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
616        .style("CustomHighlight");
617
618    doc.add_paragraph("");
619
620    // ── SECTION 10: DOCUMENT INTELLIGENCE ──
621    doc.add_paragraph("10. Document Intelligence API")
622        .style("Heading1");
623
624    let wc = doc.word_count();
625    let hc = doc.headings().len();
626    let ic = doc.images().len();
627    let lc = doc.links().len();
628    doc.add_paragraph(&format!("Word count: {wc}"));
629    doc.add_paragraph(&format!("Heading count: {hc}"));
630    doc.add_paragraph(&format!("Image count: {ic}"));
631    doc.add_paragraph(&format!("Link count: {lc}"));
632
633    let outline = doc.document_outline();
634    doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
635
636    let issues = doc.audit_accessibility();
637    doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
638    for issue in &issues {
639        doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
640    }
641
642    // ── SECTION 11: DOCUMENT MERGING ──
643    doc.add_paragraph("");
644    doc.add_paragraph("11. Document Merging").style("Heading1");
645    {
646        let mut other = Document::new();
647        other.add_paragraph("This paragraph was merged from another document using append().");
648        doc.append(&other);
649    }
650
651    doc.add_paragraph("");
652
653    // ── SUMMARY ──
654    doc.add_paragraph("Summary of Demonstrated Features")
655        .style("Heading1");
656    let features = [
657        "Page setup: size, margins, header/footer distance, gutter",
658        "Document metadata: title, author, subject, keywords",
659        "Headers and footers: text, images, different first page",
660        "Background images (full-page behind text)",
661        "Table of Contents generation with bookmarks",
662        "Text formatting: bold, italic, underline styles, strike, color, size, font",
663        "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
664        "Paragraph formatting: alignment, borders, shading, spacing, indentation",
665        "Pagination controls: keep-with-next, keep-together, widow control",
666        "Bullet and numbered lists with nesting",
667        "Tab stops with dot/underscore/hyphen leaders",
668        "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
669        "Row properties: header rows, exact height, cant-split",
670        "Cell properties: width, no-wrap, vertical alignment",
671        "Inline images and header images",
672        "Placeholder replacement (single and batch)",
673        "Regex-based find and replace",
674        "Content insertion at specific positions",
675        "Section breaks with mixed portrait/landscape",
676        "Custom paragraph and character styles",
677        "Document intelligence: word count, headings, outline, images, links",
678        "Accessibility audit",
679        "Document merging (append)",
680        "Export: DOCX, PDF, HTML, Markdown",
681    ];
682    for f in &features {
683        doc.add_bullet_list_item(f, 0);
684    }
685    doc.add_paragraph("");
686    doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
687        .alignment(Alignment::Center)
688        .shading("E2EFDA")
689        .border_all(BorderStyle::Single, 2, "00AA00");
690
691    doc
692}
693
694// =============================================================================
695// 2. PROPOSAL DOCUMENT — Deep navy + gold scheme
696// =============================================================================
697fn generate_proposal(_samples_dir: &Path) -> Document {
698    let mut doc = Document::new();
699
700    // Colors: Navy #1B2A4A, Gold #C5922E, Light #F4F1EB
701    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
702    doc.set_margins(
703        Length::inches(1.0),
704        Length::inches(1.0),
705        Length::inches(1.0),
706        Length::inches(1.0),
707    );
708    doc.set_title("AI Platform Modernization Proposal");
709    doc.set_author("Walter White");
710    doc.set_subject("Technology Proposal");
711    doc.set_keywords("proposal, AI, modernization, Tensorbee");
712
713    // Banner header
714    let logo_img = create_logo_png(220, 48);
715    let banner = build_header_banner_xml(
716        "rId1",
717        &BannerOpts {
718            bg_color: "1B2A4A",
719            banner_width: 7772400,
720            banner_height: 914400,
721            logo_width: 2011680,
722            logo_height: 438912,
723            logo_x_offset: 295125,
724            logo_y_offset: 237744,
725        },
726    );
727    doc.set_raw_header_with_images(
728        banner,
729        &[("rId1", &logo_img, "logo.png")],
730        rdocx_oxml::header_footer::HdrFtrType::Default,
731    );
732    doc.set_different_first_page(true);
733    let cover_banner = build_header_banner_xml(
734        "rId1",
735        &BannerOpts {
736            bg_color: "C5922E",
737            banner_width: 7772400,
738            banner_height: 914400,
739            logo_width: 2011680,
740            logo_height: 438912,
741            logo_x_offset: 295125,
742            logo_y_offset: 237744,
743        },
744    );
745    doc.set_raw_header_with_images(
746        cover_banner,
747        &[("rId1", &logo_img, "logo.png")],
748        rdocx_oxml::header_footer::HdrFtrType::First,
749    );
750    doc.set_margins(
751        Length::twips(2292),
752        Length::twips(1440),
753        Length::twips(1440),
754        Length::twips(1440),
755    );
756    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
757    doc.set_footer("Tensorbee — Confidential");
758
759    // Custom styles
760    doc.add_style(
761        StyleBuilder::paragraph("ProposalTitle", "Proposal Title")
762            .based_on("Normal")
763            .run_properties(rdocx_oxml::properties::CT_RPr {
764                bold: Some(true),
765                font_ascii: Some("Georgia".to_string()),
766                font_hansi: Some("Georgia".to_string()),
767                sz: Some(rdocx_oxml::HalfPoint(56)), // half-points → 28pt
768                color: Some("1B2A4A".to_string()),
769                ..Default::default()
770            }),
771    );
772
773    // ── Cover Page ──
774    for _ in 0..4 {
775        doc.add_paragraph("");
776    }
777    doc.add_paragraph("AI Platform Modernization")
778        .style("ProposalTitle")
779        .alignment(Alignment::Center);
780    doc.add_paragraph("")
781        .alignment(Alignment::Center)
782        .border_bottom(BorderStyle::Single, 8, "C5922E");
783    {
784        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
785        p.add_run("Prepared for Global Financial Corp.")
786            .size(14.0)
787            .color("666666")
788            .italic(true);
789    }
790    {
791        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
792        p.add_run("Prepared by: Walter White, CEO — Tensorbee")
793            .size(12.0)
794            .color("888888");
795    }
796    {
797        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
798        p.add_run("February 22, 2026").size(12.0).color("888888");
799    }
800
801    // ── TOC ──
802    doc.add_paragraph("").page_break_before(true);
803    doc.add_paragraph("Table of Contents").style("Heading1");
804    // We'll add a manual-style TOC since the insert_toc goes at a specific position
805    doc.insert_toc(doc.content_count(), 2);
806
807    // ── Executive Summary ──
808    doc.add_paragraph("").page_break_before(true);
809    doc.add_paragraph("Executive Summary").style("Heading1");
810    doc.add_paragraph(
811        "Tensorbee proposes a comprehensive modernization of Global Financial Corp's AI platform, \
812         transitioning from legacy batch-processing systems to a real-time inference architecture. \
813         This transformation will reduce model deployment time from weeks to hours, improve \
814         prediction accuracy by an estimated 23%, and deliver $4.2M in annual operational savings.",
815    )
816    .first_line_indent(Length::inches(0.3));
817    doc.add_paragraph(
818        "The project spans three phases over 18 months, with a total investment of $2.8M. \
819         Tensorbee brings deep expertise in ML infrastructure, having successfully completed \
820         similar transformations for three Fortune 500 financial institutions.",
821    )
822    .first_line_indent(Length::inches(0.3));
823
824    // Key metrics table
825    doc.add_paragraph("");
826    {
827        let mut tbl = doc
828            .add_table(5, 2)
829            .borders(BorderStyle::Single, 4, "1B2A4A")
830            .width_pct(80.0);
831        tbl.cell(0, 0).unwrap().set_text("Key Metric");
832        tbl.cell(0, 0).unwrap().shading("1B2A4A");
833        tbl.cell(0, 1).unwrap().set_text("Value");
834        tbl.cell(0, 1).unwrap().shading("1B2A4A");
835        let rows = [
836            ("Total Investment", "$2.8M"),
837            ("Annual Savings", "$4.2M"),
838            ("ROI (Year 1)", "150%"),
839            ("Timeline", "18 months"),
840        ];
841        for (i, (k, v)) in rows.iter().enumerate() {
842            tbl.cell(i + 1, 0).unwrap().set_text(k);
843            if i % 2 == 0 {
844                tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
845            }
846            tbl.cell(i + 1, 1).unwrap().set_text(v);
847            if i % 2 == 0 {
848                tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
849            }
850        }
851    }
852
853    // ── Problem Statement ──
854    doc.add_paragraph("").page_break_before(true);
855    doc.add_paragraph("Problem Statement").style("Heading1");
856    doc.add_paragraph(
857        "Global Financial Corp's current AI infrastructure faces three critical challenges:",
858    );
859    doc.add_numbered_list_item("Deployment Latency — New models take 3-4 weeks to deploy to production, compared to the industry benchmark of 2-3 days.", 0);
860    doc.add_numbered_list_item("Scalability Constraints — The batch processing architecture cannot handle real-time inference demands during peak trading hours.", 0);
861    doc.add_numbered_list_item("Technical Debt — Legacy Python 2.7 codebases and manual deployment scripts create reliability risks and slow iteration speed.", 0);
862
863    // ── Proposed Solution ──
864    doc.add_paragraph("").page_break_before(true);
865    doc.add_paragraph("Proposed Solution").style("Heading1");
866
867    doc.add_paragraph("Phase 1: Foundation (Months 1-6)")
868        .style("Heading2");
869    doc.add_bullet_list_item("Deploy Kubernetes-based ML serving infrastructure", 0);
870    doc.add_bullet_list_item("Implement CI/CD pipeline for model deployment", 0);
871    doc.add_bullet_list_item("Migrate top 5 production models to new platform", 0);
872
873    doc.add_paragraph("Phase 2: Expansion (Months 7-12)")
874        .style("Heading2");
875    doc.add_bullet_list_item(
876        "Real-time feature engineering pipeline (Apache Kafka + Flink)",
877        0,
878    );
879    doc.add_bullet_list_item("A/B testing framework for model variants", 0);
880    doc.add_bullet_list_item("Automated model monitoring and drift detection", 0);
881
882    doc.add_paragraph("Phase 3: Optimization (Months 13-18)")
883        .style("Heading2");
884    doc.add_bullet_list_item("GPU inference optimization (TensorRT/ONNX Runtime)", 0);
885    doc.add_bullet_list_item("Multi-region deployment for latency reduction", 0);
886    doc.add_bullet_list_item(
887        "Self-service model deployment portal for data science teams",
888        0,
889    );
890
891    // ── Budget ──
892    doc.add_paragraph("Budget Breakdown").style("Heading1");
893    {
894        let mut tbl = doc
895            .add_table(6, 3)
896            .borders(BorderStyle::Single, 4, "1B2A4A")
897            .width_pct(100.0);
898        tbl.cell(0, 0).unwrap().set_text("Category");
899        tbl.cell(0, 0).unwrap().shading("1B2A4A");
900        tbl.cell(0, 1).unwrap().set_text("Cost");
901        tbl.cell(0, 1).unwrap().shading("1B2A4A");
902        tbl.cell(0, 2).unwrap().set_text("Notes");
903        tbl.cell(0, 2).unwrap().shading("1B2A4A");
904        let items = [
905            (
906                "Infrastructure",
907                "$450,000",
908                "Cloud compute + GPU instances",
909            ),
910            ("Engineering Services", "$1,600,000", "12 FTE x 18 months"),
911            (
912                "Software Licenses",
913                "$350,000",
914                "Kafka, monitoring, CI/CD tools",
915            ),
916            (
917                "Training & Enablement",
918                "$200,000",
919                "Team training, documentation",
920            ),
921            ("Contingency (10%)", "$200,000", "Risk buffer"),
922        ];
923        for (i, (cat, cost, note)) in items.iter().enumerate() {
924            tbl.cell(i + 1, 0).unwrap().set_text(cat);
925            tbl.cell(i + 1, 1).unwrap().set_text(cost);
926            tbl.cell(i + 1, 2).unwrap().set_text(note);
927            if i % 2 == 0 {
928                tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
929                tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
930                tbl.cell(i + 1, 2).unwrap().shading("F4F1EB");
931            }
932        }
933    }
934
935    // ── Closing ──
936    doc.add_paragraph("");
937    doc.add_paragraph("Next Steps").style("Heading1");
938    doc.add_numbered_list_item(
939        "Schedule technical deep-dive with GFC engineering team (Week 1)",
940        0,
941    );
942    doc.add_numbered_list_item("Finalize scope and sign SOW (Week 2-3)", 0);
943    doc.add_numbered_list_item("Kick off Phase 1 with joint planning session (Week 4)", 0);
944
945    doc.add_paragraph("");
946    {
947        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
948        p.add_run("Walter White")
949            .bold(true)
950            .size(14.0)
951            .color("1B2A4A");
952    }
953    {
954        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
955        p.add_run("CEO, Tensorbee | walter@tensorbee.com")
956            .size(10.0)
957            .color("888888");
958    }
959
960    doc
961}
962
963// =============================================================================
964// 3. QUOTE / BILL OF MATERIALS — Teal + orange scheme
965// =============================================================================
966fn generate_quote(_samples_dir: &Path) -> Document {
967    let mut doc = Document::new();
968
969    // Colors: Teal #008B8B, Dark #1A3C3C, Orange #E07020, Light #F0F8F8
970    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
971    doc.set_margins(
972        Length::inches(0.75),
973        Length::inches(0.75),
974        Length::inches(0.75),
975        Length::inches(0.75),
976    );
977    doc.set_title("Quotation QT-2026-0147");
978    doc.set_author("Walter White");
979    doc.set_keywords("quote, BOM, Tensorbee");
980
981    doc.set_header("Tensorbee — Quotation");
982    doc.set_footer("QT-2026-0147 | Page");
983
984    // ── Header Block ──
985    {
986        let mut p = doc.add_paragraph("").alignment(Alignment::Left);
987        p.add_run("TENSORBEE")
988            .bold(true)
989            .size(28.0)
990            .color("008B8B")
991            .font("Helvetica");
992    }
993    doc.add_paragraph("123 Innovation Drive, Suite 400")
994        .alignment(Alignment::Left);
995    doc.add_paragraph("San Francisco, CA 94105 | +1 (415) 555-0199")
996        .alignment(Alignment::Left);
997    doc.add_paragraph("")
998        .border_bottom(BorderStyle::Single, 8, "008B8B");
999
1000    // Quote meta
1001    doc.add_paragraph("");
1002    {
1003        let mut tbl = doc.add_table(4, 4).width_pct(100.0);
1004        tbl.cell(0, 0).unwrap().set_text("QUOTATION");
1005        tbl.cell(0, 0).unwrap().shading("008B8B").grid_span(2);
1006        tbl.cell(0, 2).unwrap().set_text("DATE");
1007        tbl.cell(0, 2).unwrap().shading("008B8B");
1008        tbl.cell(0, 3).unwrap().set_text("VALID UNTIL");
1009        tbl.cell(0, 3).unwrap().shading("008B8B");
1010        tbl.cell(1, 0).unwrap().set_text("Quote #:");
1011        tbl.cell(1, 1).unwrap().set_text("QT-2026-0147");
1012        tbl.cell(1, 2).unwrap().set_text("Feb 22, 2026");
1013        tbl.cell(1, 3).unwrap().set_text("Mar 22, 2026");
1014        tbl.cell(2, 0).unwrap().set_text("Prepared by:");
1015        tbl.cell(2, 1).unwrap().set_text("Walter White");
1016        tbl.cell(2, 2).unwrap().set_text("Payment:");
1017        tbl.cell(2, 3).unwrap().set_text("Net 30");
1018        tbl.cell(3, 0).unwrap().set_text("Customer:");
1019        tbl.cell(3, 1).unwrap().set_text("Meridian Dynamics LLC");
1020        tbl.cell(3, 2).unwrap().set_text("Currency:");
1021        tbl.cell(3, 3).unwrap().set_text("USD");
1022    }
1023
1024    doc.add_paragraph("");
1025
1026    // ── Bill of Materials ──
1027    {
1028        let mut p = doc.add_paragraph("");
1029        p.add_run("Bill of Materials")
1030            .bold(true)
1031            .size(16.0)
1032            .color("1A3C3C");
1033    }
1034    doc.add_paragraph("");
1035
1036    {
1037        let mut tbl = doc
1038            .add_table(10, 6)
1039            .borders(BorderStyle::Single, 2, "008B8B")
1040            .width_pct(100.0)
1041            .layout_fixed();
1042        // Headers
1043        let hdrs = [
1044            "#",
1045            "Part Number",
1046            "Description",
1047            "Qty",
1048            "Unit Price",
1049            "Total",
1050        ];
1051        for (c, h) in hdrs.iter().enumerate() {
1052            tbl.cell(0, c).unwrap().set_text(h);
1053            tbl.cell(0, c).unwrap().shading("008B8B");
1054        }
1055
1056        let items: Vec<(&str, &str, &str, &str, &str)> = vec![
1057            (
1058                "TB-GPU-A100",
1059                "NVIDIA A100 80GB GPU",
1060                "4",
1061                "$12,500.00",
1062                "$50,000.00",
1063            ),
1064            (
1065                "TB-SRV-R750",
1066                "Dell R750xa Server Chassis",
1067                "2",
1068                "$8,200.00",
1069                "$16,400.00",
1070            ),
1071            (
1072                "TB-RAM-256G",
1073                "256GB DDR5 ECC Memory Module",
1074                "8",
1075                "$890.00",
1076                "$7,120.00",
1077            ),
1078            (
1079                "TB-SSD-3840",
1080                "3.84TB NVMe U.2 SSD",
1081                "8",
1082                "$1,150.00",
1083                "$9,200.00",
1084            ),
1085            (
1086                "TB-NET-CX7",
1087                "ConnectX-7 200GbE NIC",
1088                "4",
1089                "$1,800.00",
1090                "$7,200.00",
1091            ),
1092            (
1093                "TB-SW-48P",
1094                "48-Port 100GbE Switch",
1095                "1",
1096                "$22,000.00",
1097                "$22,000.00",
1098            ),
1099            (
1100                "TB-CAB-RACK",
1101                "42U Server Rack + PDU",
1102                "1",
1103                "$4,500.00",
1104                "$4,500.00",
1105            ),
1106            (
1107                "TB-SVC-INST",
1108                "Installation & Configuration",
1109                "1",
1110                "$8,500.00",
1111                "$8,500.00",
1112            ),
1113            (
1114                "TB-SVC-SUPP",
1115                "3-Year Premium Support",
1116                "1",
1117                "$15,000.00",
1118                "$15,000.00",
1119            ),
1120        ];
1121
1122        for (i, (pn, desc, qty, unit, total)) in items.iter().enumerate() {
1123            let row = i + 1;
1124            tbl.cell(row, 0).unwrap().set_text(&format!("{}", i + 1));
1125            tbl.cell(row, 1).unwrap().set_text(pn);
1126            tbl.cell(row, 2).unwrap().set_text(desc);
1127            tbl.cell(row, 3).unwrap().set_text(qty);
1128            tbl.cell(row, 4).unwrap().set_text(unit);
1129            tbl.cell(row, 5).unwrap().set_text(total);
1130            if i % 2 == 0 {
1131                for c in 0..6 {
1132                    tbl.cell(row, c).unwrap().shading("F0F8F8");
1133                }
1134            }
1135        }
1136    }
1137
1138    doc.add_paragraph("");
1139
1140    // ── Totals ──
1141    {
1142        let mut tbl = doc
1143            .add_table(4, 2)
1144            .borders(BorderStyle::Single, 2, "008B8B")
1145            .width(Length::inches(3.5))
1146            .alignment(Alignment::Right);
1147        tbl.cell(0, 0).unwrap().set_text("Subtotal");
1148        tbl.cell(0, 1).unwrap().set_text("$139,920.00");
1149        tbl.cell(1, 0).unwrap().set_text("Shipping & Handling");
1150        tbl.cell(1, 1).unwrap().set_text("$2,500.00");
1151        tbl.cell(2, 0).unwrap().set_text("Tax (8.625%)");
1152        tbl.cell(2, 1).unwrap().set_text("$12,068.10");
1153        tbl.cell(3, 0).unwrap().set_text("TOTAL");
1154        tbl.cell(3, 0).unwrap().shading("E07020");
1155        tbl.cell(3, 1).unwrap().set_text("$154,488.10");
1156        tbl.cell(3, 1).unwrap().shading("E07020");
1157    }
1158
1159    doc.add_paragraph("");
1160
1161    // ── Terms & Conditions ──
1162    {
1163        let mut p = doc.add_paragraph("");
1164        p.add_run("Terms & Conditions")
1165            .bold(true)
1166            .size(14.0)
1167            .color("1A3C3C");
1168    }
1169    doc.add_numbered_list_item(
1170        "This quotation is valid for 30 calendar days from the date of issue.",
1171        0,
1172    );
1173    doc.add_numbered_list_item(
1174        "All prices are in USD and exclusive of applicable taxes unless stated.",
1175        0,
1176    );
1177    doc.add_numbered_list_item("Standard lead time is 4-6 weeks from PO receipt.", 0);
1178    doc.add_numbered_list_item(
1179        "Payment terms: Net 30 from invoice date. 2% discount for payment within 10 days.",
1180        0,
1181    );
1182    doc.add_numbered_list_item(
1183        "Warranty: 3-year manufacturer warranty on all hardware components.",
1184        0,
1185    );
1186    doc.add_numbered_list_item(
1187        "Returns subject to 15% restocking fee if initiated after 14 days.",
1188        0,
1189    );
1190
1191    doc.add_paragraph("");
1192    doc.add_paragraph("")
1193        .border_bottom(BorderStyle::Single, 4, "008B8B");
1194    doc.add_paragraph("");
1195    {
1196        let mut p = doc.add_paragraph("");
1197        p.add_run("Accepted by: ").bold(true);
1198        p.add_run("___________________________ Date: ___________");
1199    }
1200    {
1201        let mut p = doc.add_paragraph("");
1202        p.add_run("Print Name: ").bold(true);
1203        p.add_run("___________________________ Title: ___________");
1204    }
1205
1206    doc
1207}
1208
1209// =============================================================================
1210// 4. INVOICE — Crimson + charcoal scheme
1211// =============================================================================
1212fn generate_invoice(_samples_dir: &Path) -> Document {
1213    let mut doc = Document::new();
1214
1215    // Colors: Crimson #B22222, Charcoal #333333, Light #FAF0F0
1216    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1217    doc.set_margins(
1218        Length::inches(0.75),
1219        Length::inches(0.75),
1220        Length::inches(0.75),
1221        Length::inches(0.75),
1222    );
1223    doc.set_title("Invoice INV-2026-0392");
1224    doc.set_author("Walter White");
1225    doc.set_keywords("invoice, Tensorbee");
1226
1227    doc.set_footer("Tensorbee — Thank you for your business!");
1228
1229    // ── Company & Invoice Header ──
1230    {
1231        let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1232        // Company name left, INVOICE right
1233        {
1234            let mut cell = tbl.cell(0, 0).unwrap();
1235            let mut p = cell.add_paragraph("");
1236            p.add_run("TENSORBEE")
1237                .bold(true)
1238                .size(32.0)
1239                .color("B22222")
1240                .font("Helvetica");
1241        }
1242        {
1243            let mut cell = tbl.cell(0, 1).unwrap();
1244            let mut p = cell.add_paragraph("");
1245            p.add_run("INVOICE").bold(true).size(32.0).color("333333");
1246        }
1247        tbl.cell(1, 0)
1248            .unwrap()
1249            .set_text("123 Innovation Drive, Suite 400");
1250        tbl.cell(1, 1).unwrap().set_text("Invoice #: INV-2026-0392");
1251        tbl.cell(2, 0).unwrap().set_text("San Francisco, CA 94105");
1252        tbl.cell(2, 1).unwrap().set_text("Date: February 22, 2026");
1253        tbl.cell(3, 0).unwrap().set_text("walter@tensorbee.com");
1254        tbl.cell(3, 1).unwrap().set_text("Due Date: March 24, 2026");
1255    }
1256
1257    doc.add_paragraph("")
1258        .border_bottom(BorderStyle::Thick, 8, "B22222");
1259    doc.add_paragraph("");
1260
1261    // ── Bill To / Ship To ──
1262    {
1263        let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1264        tbl.cell(0, 0).unwrap().set_text("BILL TO");
1265        tbl.cell(0, 0).unwrap().shading("B22222");
1266        tbl.cell(0, 1).unwrap().set_text("SHIP TO");
1267        tbl.cell(0, 1).unwrap().shading("B22222");
1268        tbl.cell(1, 0).unwrap().set_text("Meridian Dynamics LLC");
1269        tbl.cell(1, 1).unwrap().set_text("Meridian Dynamics LLC");
1270        tbl.cell(2, 0).unwrap().set_text("456 Enterprise Blvd");
1271        tbl.cell(2, 1).unwrap().set_text("Attn: Server Room B");
1272        tbl.cell(3, 0).unwrap().set_text("Austin, TX 78701");
1273        tbl.cell(3, 1)
1274            .unwrap()
1275            .set_text("456 Enterprise Blvd, Austin, TX 78701");
1276    }
1277
1278    doc.add_paragraph("");
1279
1280    // ── Line Items ──
1281    {
1282        let mut tbl = doc
1283            .add_table(8, 5)
1284            .borders(BorderStyle::Single, 2, "B22222")
1285            .width_pct(100.0);
1286        let hdrs = ["Description", "Qty", "Unit Price", "Tax", "Amount"];
1287        for (c, h) in hdrs.iter().enumerate() {
1288            tbl.cell(0, c).unwrap().set_text(h);
1289            tbl.cell(0, c).unwrap().shading("B22222");
1290        }
1291        let items = [
1292            (
1293                "ML Infrastructure Setup — Phase 1",
1294                "1",
1295                "$45,000.00",
1296                "$3,881.25",
1297                "$48,881.25",
1298            ),
1299            (
1300                "Data Pipeline Development (160 hrs)",
1301                "160",
1302                "$225.00",
1303                "$3,105.00",
1304                "$39,105.00",
1305            ),
1306            (
1307                "GPU Cluster Configuration",
1308                "1",
1309                "$12,000.00",
1310                "$1,035.00",
1311                "$13,035.00",
1312            ),
1313            (
1314                "API Gateway Implementation",
1315                "1",
1316                "$18,500.00",
1317                "$1,595.63",
1318                "$20,095.63",
1319            ),
1320            (
1321                "Load Testing & QA (80 hrs)",
1322                "80",
1323                "$195.00",
1324                "$1,345.50",
1325                "$16,945.50",
1326            ),
1327            (
1328                "Documentation & Training",
1329                "1",
1330                "$8,000.00",
1331                "$690.00",
1332                "$8,690.00",
1333            ),
1334            (
1335                "Project Management (3 months)",
1336                "3",
1337                "$6,500.00",
1338                "$1,679.63",
1339                "$21,179.63",
1340            ),
1341        ];
1342        for (i, (desc, qty, unit, tax, amt)) in items.iter().enumerate() {
1343            let r = i + 1;
1344            tbl.cell(r, 0).unwrap().set_text(desc);
1345            tbl.cell(r, 1).unwrap().set_text(qty);
1346            tbl.cell(r, 2).unwrap().set_text(unit);
1347            tbl.cell(r, 3).unwrap().set_text(tax);
1348            tbl.cell(r, 4).unwrap().set_text(amt);
1349            if i % 2 == 0 {
1350                for c in 0..5 {
1351                    tbl.cell(r, c).unwrap().shading("FAF0F0");
1352                }
1353            }
1354        }
1355    }
1356
1357    doc.add_paragraph("");
1358
1359    // ── Totals ──
1360    {
1361        let mut tbl = doc
1362            .add_table(4, 2)
1363            .borders(BorderStyle::Single, 2, "B22222")
1364            .width(Length::inches(3.0))
1365            .alignment(Alignment::Right);
1366        tbl.cell(0, 0).unwrap().set_text("Subtotal");
1367        tbl.cell(0, 1).unwrap().set_text("$154,600.00");
1368        tbl.cell(1, 0).unwrap().set_text("Tax (8.625%)");
1369        tbl.cell(1, 1).unwrap().set_text("$13,334.25");
1370        tbl.cell(2, 0).unwrap().set_text("Discount (5%)");
1371        tbl.cell(2, 1).unwrap().set_text("-$7,730.00");
1372        tbl.cell(3, 0).unwrap().set_text("AMOUNT DUE");
1373        tbl.cell(3, 0).unwrap().shading("B22222");
1374        tbl.cell(3, 1).unwrap().set_text("$160,204.25");
1375        tbl.cell(3, 1).unwrap().shading("B22222");
1376    }
1377
1378    doc.add_paragraph("");
1379    doc.add_paragraph("");
1380
1381    // ── Payment Details ──
1382    {
1383        let mut p = doc.add_paragraph("");
1384        p.add_run("Payment Details")
1385            .bold(true)
1386            .size(14.0)
1387            .color("333333");
1388    }
1389    doc.add_paragraph("Bank: Silicon Valley Bank")
1390        .indent_left(Length::inches(0.3));
1391    doc.add_paragraph("Account: Tensorbee Inc. — 0847-2953-1120")
1392        .indent_left(Length::inches(0.3));
1393    doc.add_paragraph("Routing: 121140399")
1394        .indent_left(Length::inches(0.3));
1395    doc.add_paragraph("Swift: SVBKUS6S")
1396        .indent_left(Length::inches(0.3));
1397
1398    doc.add_paragraph("");
1399
1400    doc.add_paragraph("Please include invoice number INV-2026-0392 in the payment reference.")
1401        .shading("FAF0F0")
1402        .border_all(BorderStyle::Single, 2, "B22222");
1403
1404    doc
1405}
1406
1407// =============================================================================
1408// 5. REPORT — Forest green + earth tones, with images & hierarchical sections
1409// =============================================================================
1410fn generate_report(_samples_dir: &Path) -> Document {
1411    let mut doc = Document::new();
1412
1413    // Colors: Forest #2D5016, Sage #6B8E23, Earth #8B7355, Cream #FFFAF0
1414    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1415    doc.set_margins(
1416        Length::inches(1.0),
1417        Length::inches(1.0),
1418        Length::inches(1.0),
1419        Length::inches(1.0),
1420    );
1421    doc.set_title("Q4 2025 Environmental Impact Report");
1422    doc.set_author("Walter White");
1423    doc.set_subject("Quarterly Environmental Report");
1424    doc.set_keywords("environment, sustainability, report, Tensorbee");
1425
1426    doc.set_different_first_page(true);
1427    doc.set_first_page_header("");
1428    doc.set_header("Tensorbee — Q4 2025 Environmental Impact Report");
1429    doc.set_footer("CONFIDENTIAL — Page");
1430
1431    // ── Cover ──
1432    let cover_bg = create_sample_png(612, 792, [20, 50, 15]);
1433    doc.add_background_image(&cover_bg, "report_cover.png");
1434
1435    for _ in 0..5 {
1436        doc.add_paragraph("");
1437    }
1438    {
1439        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1440        p.add_run("Q4 2025")
1441            .bold(true)
1442            .size(48.0)
1443            .color("FFFFFF")
1444            .font("Georgia");
1445    }
1446    {
1447        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1448        p.add_run("Environmental Impact Report")
1449            .size(24.0)
1450            .color("90EE90")
1451            .font("Georgia")
1452            .italic(true);
1453    }
1454    doc.add_paragraph("");
1455    {
1456        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1457        p.add_run("Tensorbee — Sustainability Division")
1458            .size(14.0)
1459            .color("C0C0C0");
1460    }
1461    {
1462        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1463        p.add_run("Prepared by Walter White, Chief Sustainability Officer")
1464            .size(11.0)
1465            .color("AAAAAA");
1466    }
1467
1468    // ── TOC ──
1469    doc.add_paragraph("").page_break_before(true);
1470    doc.insert_toc(doc.content_count(), 3);
1471
1472    // ── Section 1: Executive Overview ──
1473    doc.add_paragraph("").page_break_before(true);
1474    doc.add_paragraph("Executive Overview").style("Heading1");
1475    doc.add_paragraph(
1476        "This report presents Tensorbee's environmental performance for Q4 2025. Our \
1477         sustainability initiatives have yielded a 34% reduction in carbon emissions compared \
1478         to Q4 2024, exceeding our target of 25%. Key achievements include the transition to \
1479         100% renewable energy in our primary data centers and the launch of our carbon offset \
1480         marketplace.",
1481    )
1482    .first_line_indent(Length::inches(0.3));
1483
1484    // Image: performance chart
1485    let chart_img = create_chart_png(400, 200);
1486    doc.add_paragraph("");
1487    doc.add_picture(
1488        &chart_img,
1489        "performance_chart.png",
1490        Length::inches(5.0),
1491        Length::inches(2.5),
1492    )
1493    .alignment(Alignment::Center);
1494    {
1495        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1496        p.add_run("Figure 1: Quarterly Carbon Emissions (tonnes CO2e)")
1497            .italic(true)
1498            .size(9.0)
1499            .color("666666");
1500    }
1501
1502    // ── Section 2: Energy Consumption ──
1503    doc.add_paragraph("").page_break_before(true);
1504    doc.add_paragraph("Energy Consumption").style("Heading1");
1505
1506    doc.add_paragraph("Data Center Operations")
1507        .style("Heading2");
1508    doc.add_paragraph(
1509        "Our three primary data centers consumed a combined 4.2 GWh in Q4 2025, \
1510         a 12% reduction from Q3 2025 driven by improved cooling efficiency and \
1511         server consolidation.",
1512    );
1513
1514    // Data table
1515    {
1516        let mut tbl = doc
1517            .add_table(5, 4)
1518            .borders(BorderStyle::Single, 4, "2D5016")
1519            .width_pct(100.0);
1520        let hdrs = ["Data Center", "Capacity (MW)", "Usage (GWh)", "PUE"];
1521        for (c, h) in hdrs.iter().enumerate() {
1522            tbl.cell(0, c).unwrap().set_text(h);
1523            tbl.cell(0, c).unwrap().shading("2D5016");
1524        }
1525        let rows = [
1526            ("San Francisco (Primary)", "3.2", "1.8", "1.12"),
1527            ("Dublin (EU)", "2.1", "1.2", "1.18"),
1528            ("Singapore (APAC)", "1.8", "1.2", "1.24"),
1529            ("TOTAL", "7.1", "4.2", "1.17 avg"),
1530        ];
1531        for (i, (dc, cap, use_, pue)) in rows.iter().enumerate() {
1532            tbl.cell(i + 1, 0).unwrap().set_text(dc);
1533            tbl.cell(i + 1, 1).unwrap().set_text(cap);
1534            tbl.cell(i + 1, 2).unwrap().set_text(use_);
1535            tbl.cell(i + 1, 3).unwrap().set_text(pue);
1536            if i == 3 {
1537                for c in 0..4 {
1538                    tbl.cell(i + 1, c).unwrap().shading("E2EFDA");
1539                }
1540            }
1541        }
1542    }
1543
1544    doc.add_paragraph("");
1545
1546    doc.add_paragraph("Renewable Energy Mix").style("Heading2");
1547    doc.add_paragraph("Breakdown of energy sources across all facilities:");
1548
1549    doc.add_bullet_list_item("Solar PV: 42% (1.76 GWh)", 0);
1550    doc.add_bullet_list_item("Wind Power Purchase Agreements: 38% (1.60 GWh)", 0);
1551    doc.add_bullet_list_item("Hydroelectric: 12% (0.50 GWh)", 0);
1552    doc.add_bullet_list_item("Grid (non-renewable): 8% (0.34 GWh)", 0);
1553
1554    // ── Section 3: Water & Waste ──
1555    doc.add_paragraph("Water & Waste Management")
1556        .style("Heading1");
1557
1558    doc.add_paragraph("Water Usage").style("Heading2");
1559    doc.add_paragraph(
1560        "Total water consumption was 12.4 million gallons, a 15% reduction from Q3. \
1561         Our closed-loop cooling systems now recycle 78% of water used in cooling operations.",
1562    );
1563
1564    doc.add_paragraph("Waste Reduction").style("Heading2");
1565    doc.add_paragraph("E-waste management results for Q4:")
1566        .keep_with_next(true);
1567    {
1568        let mut tbl = doc
1569            .add_table(4, 3)
1570            .borders(BorderStyle::Single, 2, "6B8E23")
1571            .width_pct(80.0);
1572        tbl.cell(0, 0).unwrap().set_text("Category");
1573        tbl.cell(0, 0).unwrap().shading("6B8E23");
1574        tbl.cell(0, 1).unwrap().set_text("Weight (kg)");
1575        tbl.cell(0, 1).unwrap().shading("6B8E23");
1576        tbl.cell(0, 2).unwrap().set_text("Recycled %");
1577        tbl.cell(0, 2).unwrap().shading("6B8E23");
1578        let rows = [
1579            ("Server Hardware", "2,450", "94%"),
1580            ("Networking Equipment", "820", "91%"),
1581            ("Storage Media", "340", "99%"),
1582        ];
1583        for (i, (cat, wt, pct)) in rows.iter().enumerate() {
1584            tbl.cell(i + 1, 0).unwrap().set_text(cat);
1585            tbl.cell(i + 1, 1).unwrap().set_text(wt);
1586            tbl.cell(i + 1, 2).unwrap().set_text(pct);
1587        }
1588    }
1589
1590    // ── Section 4: Initiatives ──
1591    doc.add_paragraph("").page_break_before(true);
1592    doc.add_paragraph("Q1 2026 Initiatives").style("Heading1");
1593
1594    doc.add_paragraph("Planned Programs").style("Heading2");
1595    doc.add_numbered_list_item(
1596        "Deploy on-site battery storage at SF data center (2 MWh capacity)",
1597        0,
1598    );
1599    doc.add_numbered_list_item("Pilot immersion cooling in Dublin facility", 0);
1600    doc.add_numbered_list_item("Launch employee commute carbon offset program", 0);
1601    doc.add_numbered_list_item("Achieve ISO 14001 certification for Singapore facility", 0);
1602
1603    doc.add_paragraph("Investment Targets").style("Heading2");
1604    doc.add_paragraph("Sustainability CapEx allocation for FY2026:")
1605        .keep_with_next(true);
1606    {
1607        let mut tbl = doc
1608            .add_table(5, 2)
1609            .borders(BorderStyle::Single, 4, "2D5016");
1610        tbl.cell(0, 0).unwrap().set_text("Initiative");
1611        tbl.cell(0, 0).unwrap().shading("2D5016");
1612        tbl.cell(0, 1).unwrap().set_text("Budget");
1613        tbl.cell(0, 1).unwrap().shading("2D5016");
1614        let rows = [
1615            ("Battery Storage", "$1.2M"),
1616            ("Immersion Cooling Pilot", "$800K"),
1617            ("Solar Panel Expansion", "$2.1M"),
1618            ("Carbon Credits", "$500K"),
1619        ];
1620        for (i, (init, budget)) in rows.iter().enumerate() {
1621            tbl.cell(i + 1, 0).unwrap().set_text(init);
1622            tbl.cell(i + 1, 1).unwrap().set_text(budget);
1623            if i % 2 == 0 {
1624                tbl.cell(i + 1, 0).unwrap().shading("FFFAF0");
1625                tbl.cell(i + 1, 1).unwrap().shading("FFFAF0");
1626            }
1627        }
1628    }
1629
1630    // Image: sustainability roadmap
1631    let roadmap_img = create_sample_png(500, 100, [40, 80, 30]);
1632    doc.add_paragraph("");
1633    doc.add_picture(
1634        &roadmap_img,
1635        "roadmap.png",
1636        Length::inches(6.0),
1637        Length::inches(1.2),
1638    )
1639    .alignment(Alignment::Center);
1640    {
1641        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1642        p.add_run("Figure 2: 2026 Sustainability Roadmap")
1643            .italic(true)
1644            .size(9.0)
1645            .color("666666");
1646    }
1647
1648    doc.add_paragraph("");
1649    doc.add_paragraph(
1650        "For questions about this report, contact Walter White at sustainability@tensorbee.com.",
1651    )
1652    .shading("FFFAF0")
1653    .border_all(BorderStyle::Single, 2, "2D5016");
1654
1655    doc
1656}
1657
1658// =============================================================================
1659// 6. LETTER — Slate blue + silver scheme
1660// =============================================================================
1661fn generate_letter(_samples_dir: &Path) -> Document {
1662    let mut doc = Document::new();
1663
1664    // Colors: Slate #4A5568, Blue accent #3182CE
1665    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1666    doc.set_margins(
1667        Length::inches(1.25),
1668        Length::inches(1.25),
1669        Length::inches(1.25),
1670        Length::inches(1.25),
1671    );
1672    doc.set_title("Business Letter");
1673    doc.set_author("Walter White");
1674
1675    // Banner header using raw XML
1676    let logo_img = create_logo_png(220, 48);
1677    let banner = build_header_banner_xml(
1678        "rId1",
1679        &BannerOpts {
1680            bg_color: "4A5568",
1681            banner_width: 7772400,
1682            banner_height: 731520, // ~0.8"
1683            logo_width: 2011680,
1684            logo_height: 438912,
1685            logo_x_offset: 295125,
1686            logo_y_offset: 146304,
1687        },
1688    );
1689    doc.set_raw_header_with_images(
1690        banner,
1691        &[("rId1", &logo_img, "logo.png")],
1692        rdocx_oxml::header_footer::HdrFtrType::Default,
1693    );
1694    doc.set_margins(
1695        Length::twips(2000),
1696        Length::twips(1800),
1697        Length::twips(1440),
1698        Length::twips(1800),
1699    );
1700    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
1701
1702    // Footer with contact
1703    doc.set_footer(
1704        "Tensorbee | 123 Innovation Drive, Suite 400 | San Francisco, CA 94105 | tensorbee.com",
1705    );
1706
1707    // ── Sender Address ──
1708    doc.add_paragraph("Walter White");
1709    doc.add_paragraph("Chief Executive Officer");
1710    doc.add_paragraph("Tensorbee");
1711    doc.add_paragraph("123 Innovation Drive, Suite 400");
1712    doc.add_paragraph("San Francisco, CA 94105");
1713    doc.add_paragraph("");
1714    doc.add_paragraph("February 22, 2026");
1715
1716    doc.add_paragraph("");
1717
1718    // ── Recipient ──
1719    doc.add_paragraph("Ms. Sarah Chen");
1720    doc.add_paragraph("VP of Engineering");
1721    doc.add_paragraph("Meridian Dynamics LLC");
1722    doc.add_paragraph("456 Enterprise Boulevard");
1723    doc.add_paragraph("Austin, TX 78701");
1724
1725    doc.add_paragraph("");
1726
1727    // ── Subject ──
1728    {
1729        let mut p = doc.add_paragraph("");
1730        p.add_run("Re: Strategic Technology Partnership — Phase 2 Expansion")
1731            .bold(true);
1732    }
1733
1734    doc.add_paragraph("");
1735
1736    // ── Body ──
1737    doc.add_paragraph("Dear Ms. Chen,");
1738    doc.add_paragraph("");
1739
1740    doc.add_paragraph(
1741        "I am writing to express Tensorbee's enthusiasm for expanding our strategic technology \
1742         partnership with Meridian Dynamics. Following the successful completion of Phase 1, \
1743         which delivered a 40% improvement in your ML inference pipeline throughput, we believe \
1744         the foundation is firmly established for an ambitious Phase 2 engagement.",
1745    )
1746    .first_line_indent(Length::inches(0.5));
1747
1748    doc.add_paragraph(
1749        "During our recent executive review, your team highlighted three priority areas for \
1750         the next phase of collaboration:",
1751    )
1752    .first_line_indent(Length::inches(0.5));
1753
1754    doc.add_numbered_list_item(
1755        "Real-time anomaly detection for your financial transaction monitoring system, \
1756         targeting sub-100ms latency at 50,000 transactions per second.",
1757        0,
1758    );
1759    doc.add_numbered_list_item(
1760        "Federated learning infrastructure to enable model training across your distributed \
1761         data centers without centralizing sensitive financial data.",
1762        0,
1763    );
1764    doc.add_numbered_list_item(
1765        "MLOps automation to reduce your model deployment cycle from the current 5 days \
1766         to under 4 hours.",
1767        0,
1768    );
1769
1770    doc.add_paragraph(
1771        "Our engineering team has prepared a detailed technical proposal addressing each of \
1772         these areas. We have allocated a dedicated team of eight senior engineers, led by \
1773         our CTO, to ensure continuity with the Phase 1 team your organization has already \
1774         built a productive working relationship with.",
1775    )
1776    .first_line_indent(Length::inches(0.5));
1777
1778    doc.add_paragraph(
1779        "I would welcome the opportunity to present our Phase 2 proposal in person. My \
1780         assistant will reach out to coordinate a meeting at your convenience during the \
1781         first week of March.",
1782    )
1783    .first_line_indent(Length::inches(0.5));
1784
1785    doc.add_paragraph("");
1786
1787    doc.add_paragraph("Warm regards,");
1788    doc.add_paragraph("");
1789    doc.add_paragraph("");
1790
1791    // Signature line
1792    doc.add_paragraph("")
1793        .border_bottom(BorderStyle::Single, 4, "4A5568");
1794    {
1795        let mut p = doc.add_paragraph("");
1796        p.add_run("Walter White").bold(true).size(12.0);
1797    }
1798    {
1799        let mut p = doc.add_paragraph("");
1800        p.add_run("Chief Executive Officer, Tensorbee")
1801            .italic(true)
1802            .size(10.0)
1803            .color("666666");
1804    }
1805    {
1806        let mut p = doc.add_paragraph("");
1807        p.add_run("walter@tensorbee.com | +1 (415) 555-0199")
1808            .size(10.0)
1809            .color("888888");
1810    }
1811
1812    doc
1813}
1814
1815// =============================================================================
1816// 7. EMPLOYMENT CONTRACT — Purple + charcoal scheme
1817// =============================================================================
1818fn generate_contract(_samples_dir: &Path) -> Document {
1819    let mut doc = Document::new();
1820
1821    // Colors: Purple #5B2C6F, Plum #8E44AD, Gray #2C3E50
1822    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1823    doc.set_margins(
1824        Length::inches(1.25),
1825        Length::inches(1.0),
1826        Length::inches(1.0),
1827        Length::inches(1.0),
1828    );
1829    doc.set_title("Employment Agreement");
1830    doc.set_author("Tensorbee HR Department");
1831    doc.set_subject("Employment Contract — Walter White");
1832    doc.set_keywords("employment, contract, agreement, Tensorbee");
1833
1834    doc.set_header("TENSORBEE — Employment Agreement");
1835    doc.set_footer("Employment Agreement — Walter White — Page");
1836
1837    // ── Title Block ──
1838    doc.add_paragraph("")
1839        .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1840    {
1841        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1842        p.add_run("EMPLOYMENT AGREEMENT")
1843            .bold(true)
1844            .size(24.0)
1845            .color("5B2C6F")
1846            .font("Georgia");
1847    }
1848    doc.add_paragraph("")
1849        .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1850    doc.add_paragraph("");
1851
1852    // ── Parties ──
1853    doc.add_paragraph(
1854        "This Employment Agreement (\"Agreement\") is entered into as of February 22, 2026 \
1855         (\"Effective Date\"), by and between:",
1856    );
1857
1858    doc.add_paragraph("");
1859
1860    {
1861        let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1862        p.add_run("EMPLOYER: ").bold(true);
1863        p.add_run(
1864            "Tensorbee, Inc., a Delaware corporation with its principal place of business at \
1865                   123 Innovation Drive, Suite 400, San Francisco, CA 94105 (\"Company\")",
1866        );
1867    }
1868    doc.add_paragraph("");
1869    {
1870        let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1871        p.add_run("EMPLOYEE: ").bold(true);
1872        p.add_run(
1873            "Walter White, residing at 308 Negra Arroyo Lane, Albuquerque, NM 87104 (\"Employee\")",
1874        );
1875    }
1876
1877    doc.add_paragraph("");
1878    doc.add_paragraph("The Company and Employee are collectively referred to as the \"Parties.\"");
1879    doc.add_paragraph("");
1880
1881    // ── Article 1: Position and Duties ──
1882    doc.add_paragraph("Article 1 — Position and Duties")
1883        .style("Heading1");
1884    {
1885        let mut p = doc.add_paragraph("");
1886        p.add_run("1.1 ").bold(true);
1887        p.add_run("The Company hereby employs the Employee as ");
1888        p.add_run("Chief Executive Officer (CEO)")
1889            .bold(true)
1890            .italic(true);
1891        p.add_run(", reporting directly to the Board of Directors.");
1892    }
1893    {
1894        let mut p = doc.add_paragraph("");
1895        p.add_run("1.2 ").bold(true);
1896        p.add_run("The Employee shall devote full working time, attention, and best efforts to \
1897                   the performance of duties as reasonably assigned by the Board, including but not limited to:");
1898    }
1899    doc.add_bullet_list_item(
1900        "Setting and executing the Company's strategic vision and business plan",
1901        0,
1902    );
1903    doc.add_bullet_list_item(
1904        "Overseeing all operations, engineering, and commercial activities",
1905        0,
1906    );
1907    doc.add_bullet_list_item(
1908        "Representing the Company to investors, customers, and the public",
1909        0,
1910    );
1911    doc.add_bullet_list_item("Recruiting, developing, and retaining key talent", 0);
1912
1913    {
1914        let mut p = doc.add_paragraph("");
1915        p.add_run("1.3 ").bold(true);
1916        p.add_run(
1917            "The Employee's primary work location shall be the Company's San Francisco \
1918                   headquarters, with reasonable travel as required by business needs.",
1919        );
1920    }
1921
1922    // ── Article 2: Compensation ──
1923    doc.add_paragraph("Article 2 — Compensation and Benefits")
1924        .style("Heading1");
1925    {
1926        let mut p = doc.add_paragraph("");
1927        p.add_run("2.1 Base Salary. ").bold(true);
1928        p.add_run("The Company shall pay the Employee an annual base salary of ");
1929        p.add_run("$375,000.00").bold(true);
1930        p.add_run(" (Three Hundred Seventy-Five Thousand Dollars), payable in accordance with the \
1931                   Company's standard payroll schedule, less applicable withholdings and deductions.");
1932    }
1933    {
1934        let mut p = doc.add_paragraph("");
1935        p.add_run("2.2 Annual Bonus. ").bold(true);
1936        p.add_run("The Employee shall be eligible for an annual performance bonus of up to ");
1937        p.add_run("40%").bold(true);
1938        p.add_run(
1939            " of base salary, based on achievement of mutually agreed performance objectives.",
1940        );
1941    }
1942    {
1943        let mut p = doc.add_paragraph("");
1944        p.add_run("2.3 Equity. ").bold(true);
1945        p.add_run("Subject to Board approval, the Employee shall receive a stock option grant of ");
1946        p.add_run("500,000 shares").bold(true);
1947        p.add_run(
1948            " of the Company's common stock, vesting over four (4) years with a one-year cliff, \
1949                   at an exercise price equal to the fair market value on the date of grant.",
1950        );
1951    }
1952
1953    // Compensation summary table
1954    doc.add_paragraph("");
1955    {
1956        let mut tbl = doc
1957            .add_table(5, 2)
1958            .borders(BorderStyle::Single, 4, "5B2C6F")
1959            .width_pct(70.0)
1960            .alignment(Alignment::Center);
1961        tbl.cell(0, 0).unwrap().set_text("Compensation Element");
1962        tbl.cell(0, 0).unwrap().shading("5B2C6F");
1963        tbl.cell(0, 1).unwrap().set_text("Value");
1964        tbl.cell(0, 1).unwrap().shading("5B2C6F");
1965        let items = [
1966            ("Base Salary", "$375,000/year"),
1967            ("Target Bonus", "Up to 40% ($150,000)"),
1968            ("Equity Grant", "500,000 shares (4yr vest)"),
1969            ("Total Target Comp", "$525,000 + equity"),
1970        ];
1971        for (i, (elem, val)) in items.iter().enumerate() {
1972            tbl.cell(i + 1, 0).unwrap().set_text(elem);
1973            tbl.cell(i + 1, 1).unwrap().set_text(val);
1974            if i == 3 {
1975                tbl.cell(i + 1, 0).unwrap().shading("E8DAEF");
1976                tbl.cell(i + 1, 1).unwrap().shading("E8DAEF");
1977            }
1978        }
1979    }
1980
1981    // ── Article 3: Benefits ──
1982    doc.add_paragraph("").page_break_before(true);
1983    doc.add_paragraph("Article 3 — Benefits").style("Heading1");
1984    {
1985        let mut p = doc.add_paragraph("");
1986        p.add_run("3.1 ").bold(true);
1987        p.add_run(
1988            "The Employee shall be entitled to participate in all benefit programs \
1989                   generally available to senior executives, including:",
1990        );
1991    }
1992    doc.add_bullet_list_item(
1993        "Medical, dental, and vision insurance (100% premium coverage for employee and dependents)",
1994        0,
1995    );
1996    doc.add_bullet_list_item("401(k) retirement plan with 6% company match", 0);
1997    doc.add_bullet_list_item("Life insurance and long-term disability coverage", 0);
1998    doc.add_bullet_list_item("Annual professional development allowance of $10,000", 0);
1999
2000    {
2001        let mut p = doc.add_paragraph("");
2002        p.add_run("3.2 Paid Time Off. ").bold(true);
2003        p.add_run(
2004            "The Employee shall receive 25 days of paid vacation per year, plus Company holidays, \
2005                   accruing on a monthly basis.",
2006        );
2007    }
2008
2009    // ── Article 4: Term and Termination ──
2010    doc.add_paragraph("Article 4 — Term and Termination")
2011        .style("Heading1");
2012    {
2013        let mut p = doc.add_paragraph("");
2014        p.add_run("4.1 At-Will Employment. ").bold(true);
2015        p.add_run(
2016            "This Agreement is for at-will employment and may be terminated by either Party \
2017                   at any time, with or without cause, subject to the notice provisions herein.",
2018        );
2019    }
2020    {
2021        let mut p = doc.add_paragraph("");
2022        p.add_run("4.2 Notice Period. ").bold(true);
2023        p.add_run("Either Party shall provide ");
2024        p.add_run("ninety (90) days").bold(true);
2025        p.add_run(" written notice of termination, or pay in lieu thereof.");
2026    }
2027    {
2028        let mut p = doc.add_paragraph("");
2029        p.add_run("4.3 Severance. ").bold(true);
2030        p.add_run("In the event of termination by the Company without Cause, the Employee shall \
2031                   receive (a) twelve (12) months of base salary continuation, (b) pro-rata bonus \
2032                   for the year of termination, and (c) twelve (12) months of COBRA premium coverage.");
2033    }
2034
2035    // ── Article 5: Confidentiality ──
2036    doc.add_paragraph("Article 5 — Confidentiality and IP")
2037        .style("Heading1");
2038    {
2039        let mut p = doc.add_paragraph("");
2040        p.add_run("5.1 ").bold(true);
2041        p.add_run(
2042            "The Employee agrees to maintain strict confidentiality of all proprietary \
2043                   information, trade secrets, and business plans of the Company during and after \
2044                   employment.",
2045        );
2046    }
2047    {
2048        let mut p = doc.add_paragraph("");
2049        p.add_run("5.2 ").bold(true);
2050        p.add_run(
2051            "All intellectual property, inventions, and works of authorship created by the \
2052                   Employee during the term of employment and within the scope of duties shall be \
2053                   the sole and exclusive property of the Company.",
2054        );
2055    }
2056
2057    // ── Article 6: Non-Compete ──
2058    doc.add_paragraph("Article 6 — Non-Competition")
2059        .style("Heading1");
2060    {
2061        let mut p = doc.add_paragraph("");
2062        p.add_run("6.1 ").bold(true);
2063        p.add_run("For a period of twelve (12) months following termination, the Employee agrees \
2064                   not to engage in any business that directly competes with the Company's core \
2065                   business of AI infrastructure and ML operations services, within the United States.");
2066    }
2067
2068    // ── Article 7: General Provisions ──
2069    doc.add_paragraph("Article 7 — General Provisions")
2070        .style("Heading1");
2071    {
2072        let mut p = doc.add_paragraph("");
2073        p.add_run("7.1 Governing Law. ").bold(true);
2074        p.add_run(
2075            "This Agreement shall be governed by and construed in accordance with the laws of \
2076                   the State of California.",
2077        );
2078    }
2079    {
2080        let mut p = doc.add_paragraph("");
2081        p.add_run("7.2 Entire Agreement. ").bold(true);
2082        p.add_run(
2083            "This Agreement constitutes the entire agreement between the Parties and supersedes \
2084                   all prior negotiations, representations, and agreements.",
2085        );
2086    }
2087    {
2088        let mut p = doc.add_paragraph("");
2089        p.add_run("7.3 Amendment. ").bold(true);
2090        p.add_run(
2091            "This Agreement may only be amended by a written instrument signed by both Parties.",
2092        );
2093    }
2094
2095    // ── Signature Block ──
2096    doc.add_paragraph("").page_break_before(true);
2097    doc.add_paragraph("IN WITNESS WHEREOF, the Parties have executed this Employment Agreement as of the Effective Date.")
2098        .space_after(Length::pt(24.0));
2099
2100    // Signature table
2101    {
2102        let mut tbl = doc.add_table(6, 2).width_pct(100.0);
2103        tbl.cell(0, 0).unwrap().set_text("FOR THE COMPANY:");
2104        tbl.cell(0, 0).unwrap().shading("5B2C6F");
2105        tbl.cell(0, 1).unwrap().set_text("EMPLOYEE:");
2106        tbl.cell(0, 1).unwrap().shading("5B2C6F");
2107
2108        tbl.cell(1, 0).unwrap().set_text("");
2109        tbl.cell(1, 1).unwrap().set_text("");
2110        tbl.row(1).unwrap().height(Length::pt(40.0));
2111
2112        tbl.cell(2, 0)
2113            .unwrap()
2114            .set_text("Signature: ___________________________");
2115        tbl.cell(2, 1)
2116            .unwrap()
2117            .set_text("Signature: ___________________________");
2118        tbl.cell(3, 0)
2119            .unwrap()
2120            .set_text("Name: Board Representative");
2121        tbl.cell(3, 1).unwrap().set_text("Name: Walter White");
2122        tbl.cell(4, 0)
2123            .unwrap()
2124            .set_text("Title: Chair, Board of Directors");
2125        tbl.cell(4, 1)
2126            .unwrap()
2127            .set_text("Title: Chief Executive Officer");
2128        tbl.cell(5, 0).unwrap().set_text("Date: _______________");
2129        tbl.cell(5, 1).unwrap().set_text("Date: _______________");
2130    }
2131
2132    doc
2133}
Source

pub fn subject(&self) -> Option<&str>

Get the document subject.

Source

pub fn set_subject(&mut self, subject: &str)

Set the document subject.

Examples found in repository?
examples/template_replace.rs (line 241)
162fn fill_template(template_path: &Path, output_path: &Path) {
163    let mut doc = Document::open(template_path).unwrap();
164
165    // ── Batch replacement ──
166    let mut replacements = HashMap::new();
167    replacements.insert("{{company_name}}", "Riverside Medical Center");
168    replacements.insert("{{project_name}}", "Network Security Upgrade");
169    replacements.insert("{{contact_name}}", "Dr. Sarah Chen");
170    replacements.insert("{{contact_email}}", "s.chen@riverside.org");
171    replacements.insert("{{start_date}}", "March 1, 2026");
172    replacements.insert("{{duration}}", "12 weeks");
173    replacements.insert("{{budget}}", "$185,000");
174    replacements.insert("{{status}}", "Pending Approval");
175    replacements.insert("{{author_name}}", "James Wilson");
176    replacements.insert("{{date}}", "February 22, 2026");
177
178    // Team members
179    replacements.insert("{{member1_name}}", "James Wilson");
180    replacements.insert("{{member1_role}}", "Project Lead");
181    replacements.insert("{{member1_email}}", "j.wilson@provider.com");
182    replacements.insert("{{member2_name}}", "Maria Garcia");
183    replacements.insert("{{member2_role}}", "Security Architect");
184    replacements.insert("{{member2_email}}", "m.garcia@provider.com");
185    replacements.insert("{{member3_name}}", "David Park");
186    replacements.insert("{{member3_role}}", "Network Engineer");
187    replacements.insert("{{member3_email}}", "d.park@provider.com");
188
189    let count = doc.replace_all(&replacements);
190    println!("  Replaced {} placeholders", count);
191
192    // ── Insert deliverables at the insertion point ──
193    if let Some(idx) = doc.find_content_index("INSERTION_POINT") {
194        // Remove the placeholder paragraph
195        doc.remove_content(idx);
196
197        // Insert deliverables list
198        doc.insert_paragraph(idx, "The following deliverables are included:");
199
200        // Insert a deliverables table
201        let mut tbl = doc.insert_table(idx + 1, 5, 3);
202        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
203
204        for col in 0..3 {
205            tbl.cell(0, col).unwrap().shading("E2EFDA");
206        }
207        tbl.cell(0, 0).unwrap().set_text("Phase");
208        tbl.cell(0, 1).unwrap().set_text("Description");
209        tbl.cell(0, 2).unwrap().set_text("Timeline");
210
211        tbl.cell(1, 0).unwrap().set_text("1. Discovery");
212        tbl.cell(1, 1)
213            .unwrap()
214            .set_text("Network assessment and asset inventory");
215        tbl.cell(1, 2).unwrap().set_text("Weeks 1-3");
216
217        tbl.cell(2, 0).unwrap().set_text("2. Design");
218        tbl.cell(2, 1)
219            .unwrap()
220            .set_text("Security architecture and policy design");
221        tbl.cell(2, 2).unwrap().set_text("Weeks 4-6");
222
223        tbl.cell(3, 0).unwrap().set_text("3. Implementation");
224        tbl.cell(3, 1)
225            .unwrap()
226            .set_text("Deploy monitoring and access controls");
227        tbl.cell(3, 2).unwrap().set_text("Weeks 7-10");
228
229        tbl.cell(4, 0).unwrap().set_text("4. Validation");
230        tbl.cell(4, 1)
231            .unwrap()
232            .set_text("Testing, training, and handover");
233        tbl.cell(4, 2).unwrap().set_text("Weeks 11-12");
234
235        println!("  Inserted deliverables table at position {}", idx);
236    }
237
238    // ── Update metadata ──
239    doc.set_title("Riverside Medical Center — Network Security Upgrade Proposal");
240    doc.set_author("James Wilson");
241    doc.set_subject("Project Proposal");
242    doc.set_keywords("security, network, medical, proposal");
243
244    doc.save(output_path).unwrap();
245}
More examples
Hide additional examples
examples/generate_all_samples.rs (line 112)
97fn generate_feature_showcase(_samples_dir: &Path) -> Document {
98    let mut doc = Document::new();
99
100    // ── Page Setup & Metadata ──
101    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
102    doc.set_margins(
103        Length::inches(1.0),
104        Length::inches(1.0),
105        Length::inches(1.0),
106        Length::inches(1.0),
107    );
108    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
109    doc.set_gutter(Length::twips(0));
110    doc.set_title("rdocx Feature Showcase");
111    doc.set_author("rdocx Sample Generator");
112    doc.set_subject("Comprehensive feature demonstration");
113    doc.set_keywords("rdocx, docx, rust, sample, showcase");
114
115    // Headers & Footers with different first page
116    doc.set_different_first_page(true);
117    doc.set_first_page_header("rdocx");
118    doc.set_first_page_footer("Feature Showcase — Cover Page");
119    doc.set_header("rdocx Feature Showcase");
120    doc.set_footer("Generated by rdocx");
121
122    // ── COVER PAGE ──
123    let bg = create_sample_png(612, 792, [20, 45, 90]);
124    doc.add_background_image(&bg, "cover_bg.png");
125
126    for _ in 0..3 {
127        doc.add_paragraph("");
128    }
129    {
130        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
131        p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
132    }
133    {
134        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
135        p.add_run("Complete Feature Showcase")
136            .size(28.0)
137            .color("FFFFFF")
138            .italic(true);
139    }
140    doc.add_paragraph("");
141    {
142        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
143        p.add_run("Every feature of the rdocx Rust library")
144            .size(13.0)
145            .color("B0C4DE");
146    }
147    {
148        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
149        p.add_run("demonstrated in a single document.")
150            .size(13.0)
151            .color("B0C4DE");
152    }
153
154    // ── TABLE OF CONTENTS ──
155    doc.add_paragraph("").page_break_before(true);
156    doc.insert_toc(doc.content_count(), 3);
157
158    // ── SECTION 1: TEXT FORMATTING ──
159    doc.add_paragraph("").page_break_before(true);
160    doc.add_paragraph("1. Text Formatting").style("Heading1");
161
162    // Paragraph Alignment
163    doc.add_paragraph("1.1 Paragraph Alignment")
164        .style("Heading2");
165    doc.add_paragraph("Left-aligned paragraph (default).")
166        .alignment(Alignment::Left);
167    doc.add_paragraph("Center-aligned paragraph.")
168        .alignment(Alignment::Center);
169    doc.add_paragraph("Right-aligned paragraph.")
170        .alignment(Alignment::Right);
171    doc.add_paragraph(
172        "Justified paragraph — this text is long enough to demonstrate how justified alignment \
173         distributes extra space across word gaps so lines fill the full width of the text area.",
174    )
175    .alignment(Alignment::Justify);
176
177    doc.add_paragraph("");
178
179    // Run Formatting
180    doc.add_paragraph("1.2 Run Formatting").style("Heading2");
181    {
182        let mut p = doc.add_paragraph("");
183        p.add_run("Bold").bold(true);
184        p.add_run(" | ");
185        p.add_run("Italic").italic(true);
186        p.add_run(" | ");
187        p.add_run("Bold Italic").bold(true).italic(true);
188        p.add_run(" | ");
189        p.add_run("Underline").underline(true);
190        p.add_run(" | ");
191        p.add_run("Strikethrough").strike(true);
192        p.add_run(" | ");
193        p.add_run("Double Strike").double_strike(true);
194    }
195    {
196        let mut p = doc.add_paragraph("");
197        p.add_run("Red").color("FF0000");
198        p.add_run(" | ");
199        p.add_run("Blue").color("0000FF");
200        p.add_run(" | ");
201        p.add_run("Green").color("00AA00");
202        p.add_run(" | ");
203        p.add_run("Highlighted").highlight("FFFF00");
204    }
205    {
206        let mut p = doc.add_paragraph("");
207        p.add_run("8pt").size(8.0);
208        p.add_run(" | ");
209        p.add_run("11pt").size(11.0);
210        p.add_run(" | ");
211        p.add_run("16pt").size(16.0);
212        p.add_run(" | ");
213        p.add_run("24pt").size(24.0);
214    }
215    {
216        let mut p = doc.add_paragraph("");
217        p.add_run("Arial").font("Arial");
218        p.add_run(" | ");
219        p.add_run("Times New Roman").font("Times New Roman");
220        p.add_run(" | ");
221        p.add_run("Courier New").font("Courier New");
222    }
223    {
224        let mut p = doc.add_paragraph("");
225        p.add_run("H");
226        p.add_run("2").subscript();
227        p.add_run("O (subscript) | E=mc");
228        p.add_run("2").superscript();
229        p.add_run(" (superscript)");
230    }
231    {
232        let mut p = doc.add_paragraph("");
233        p.add_run("ALL CAPS").all_caps(true);
234        p.add_run(" | ");
235        p.add_run("Small Caps").small_caps(true);
236        p.add_run(" | ");
237        p.add_run("Expanded").character_spacing(Length::twips(40));
238        p.add_run(" | ");
239        p.add_run("Hidden text (not visible)").hidden(true);
240    }
241
242    doc.add_paragraph("");
243
244    // Underline Styles
245    doc.add_paragraph("1.3 Underline Styles").style("Heading2");
246    {
247        let mut p = doc.add_paragraph("");
248        p.add_run("Single ")
249            .underline_style(rdocx::UnderlineStyle::Single);
250        p.add_run("Double ")
251            .underline_style(rdocx::UnderlineStyle::Double);
252        p.add_run("Thick ")
253            .underline_style(rdocx::UnderlineStyle::Thick);
254        p.add_run("Dotted ")
255            .underline_style(rdocx::UnderlineStyle::Dotted);
256        p.add_run("Dash ")
257            .underline_style(rdocx::UnderlineStyle::Dash);
258        p.add_run("Wave ")
259            .underline_style(rdocx::UnderlineStyle::Wave);
260    }
261
262    // ── SECTION 2: PARAGRAPH FORMATTING ──
263    doc.add_paragraph("").page_break_before(true);
264    doc.add_paragraph("2. Paragraph Formatting")
265        .style("Heading1");
266
267    doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
268    doc.add_paragraph("Paragraph with light green shading")
269        .shading("E2EFDA");
270    doc.add_paragraph("Paragraph with bottom border")
271        .border_bottom(BorderStyle::Single, 6, "2E75B6");
272    doc.add_paragraph("Paragraph with all borders (red)")
273        .border_all(BorderStyle::Single, 4, "FF0000");
274
275    doc.add_paragraph("2.2 Indentation").style("Heading2");
276    doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
277        .indent_left(Length::inches(1.0))
278        .hanging_indent(Length::inches(0.5));
279    doc.add_paragraph("0.5-inch first-line indent on this paragraph")
280        .first_line_indent(Length::inches(0.5));
281    doc.add_paragraph("Both left and right indent (0.75 inches each)")
282        .indent_left(Length::inches(0.75))
283        .indent_right(Length::inches(0.75));
284
285    doc.add_paragraph("2.3 Spacing & Line Height")
286        .style("Heading2");
287    doc.add_paragraph("Extra space before (24pt) and after (12pt)")
288        .space_before(Length::pt(24.0))
289        .space_after(Length::pt(12.0));
290    doc.add_paragraph(
291        "Double line spacing paragraph. This text should have extra vertical space between \
292         lines to demonstrate line_spacing_multiple(2.0).",
293    )
294    .line_spacing_multiple(2.0);
295    doc.add_paragraph("Exact 20pt line spacing (fixed height).")
296        .line_spacing(20.0);
297
298    doc.add_paragraph("2.4 Pagination Controls")
299        .style("Heading2");
300    doc.add_paragraph("keep_with_next — this paragraph stays with the next")
301        .keep_with_next(true);
302    doc.add_paragraph("(This paragraph was kept with the one above.)");
303    doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
304        .keep_together(true);
305    doc.add_paragraph("widow_control — prevents widow/orphan lines")
306        .widow_control(true);
307
308    // ── SECTION 3: LISTS ──
309    doc.add_paragraph("").page_break_before(true);
310    doc.add_paragraph("3. Lists").style("Heading1");
311
312    doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
313    doc.add_bullet_list_item("First item", 0);
314    doc.add_bullet_list_item("Second item", 0);
315    doc.add_bullet_list_item("Nested level 1", 1);
316    doc.add_bullet_list_item("Nested level 2", 2);
317    doc.add_bullet_list_item("Back to level 1", 1);
318    doc.add_bullet_list_item("Third item", 0);
319
320    doc.add_paragraph("");
321
322    doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
323    doc.add_numbered_list_item("First numbered item", 0);
324    doc.add_numbered_list_item("Second numbered item", 0);
325    doc.add_numbered_list_item("Sub-item A", 1);
326    doc.add_numbered_list_item("Sub-item B", 1);
327    doc.add_numbered_list_item("Third numbered item", 0);
328
329    // ── SECTION 4: TAB STOPS ──
330    doc.add_paragraph("");
331    doc.add_paragraph("4. Tab Stops").style("Heading1");
332
333    doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
334    doc.add_paragraph("Left\tCenter\tRight\tDecimal")
335        .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
336        .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
337        .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
338        .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
339
340    doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
341    doc.add_paragraph("Item\t\tPrice")
342        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
343        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
344        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
345    doc.add_paragraph("Widget\t\t$19.99")
346        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
347        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
348        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
349    doc.add_paragraph("Gadget\t\t$249.50")
350        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
351        .add_tab_stop_with_leader(
352            TabAlignment::Right,
353            Length::inches(4.0),
354            TabLeader::Underscore,
355        )
356        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
357
358    // ── SECTION 5: TABLES ──
359    doc.add_paragraph("").page_break_before(true);
360    doc.add_paragraph("5. Tables").style("Heading1");
361
362    doc.add_paragraph("5.1 Basic Table").style("Heading2");
363    {
364        let mut tbl = doc
365            .add_table(4, 3)
366            .borders(BorderStyle::Single, 4, "000000");
367        for col in 0..3 {
368            tbl.cell(0, col).unwrap().shading("2E75B6");
369        }
370        tbl.cell(0, 0).unwrap().set_text("Name");
371        tbl.cell(0, 1).unwrap().set_text("Role");
372        tbl.cell(0, 2).unwrap().set_text("Location");
373        tbl.cell(1, 0).unwrap().set_text("Walter White");
374        tbl.cell(1, 1).unwrap().set_text("CEO");
375        tbl.cell(1, 2).unwrap().set_text("Albuquerque");
376        tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
377        tbl.cell(2, 1).unwrap().set_text("CTO");
378        tbl.cell(2, 2).unwrap().set_text("Remote");
379        tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
380        tbl.cell(3, 1).unwrap().set_text("Security");
381        tbl.cell(3, 2).unwrap().set_text("Washington");
382    }
383
384    doc.add_paragraph("");
385
386    doc.add_paragraph("5.2 Column Span & Cell Shading")
387        .style("Heading2");
388    {
389        let mut tbl = doc
390            .add_table(3, 4)
391            .borders(BorderStyle::Single, 4, "000000")
392            .width_pct(100.0);
393        tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
394        tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
395        tbl.cell(1, 0).unwrap().set_text("Region");
396        tbl.cell(1, 0).unwrap().shading("D6E4F0");
397        tbl.cell(1, 1).unwrap().set_text("Q1");
398        tbl.cell(1, 1).unwrap().shading("D6E4F0");
399        tbl.cell(1, 2).unwrap().set_text("Q2");
400        tbl.cell(1, 2).unwrap().shading("D6E4F0");
401        tbl.cell(1, 3).unwrap().set_text("Total");
402        tbl.cell(1, 3).unwrap().shading("D6E4F0");
403        tbl.cell(2, 0).unwrap().set_text("Americas");
404        tbl.cell(2, 1).unwrap().set_text("$2.4M");
405        tbl.cell(2, 2).unwrap().set_text("$2.7M");
406        tbl.cell(2, 3).unwrap().set_text("$5.1M");
407    }
408
409    doc.add_paragraph("");
410
411    doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
412    {
413        let mut tbl = doc
414            .add_table(4, 3)
415            .borders(BorderStyle::Single, 4, "000000");
416        tbl.cell(0, 0).unwrap().set_text("Category");
417        tbl.cell(0, 0).unwrap().shading("E2EFDA");
418        tbl.cell(0, 1).unwrap().set_text("Item");
419        tbl.cell(0, 1).unwrap().shading("E2EFDA");
420        tbl.cell(0, 2).unwrap().set_text("Price");
421        tbl.cell(0, 2).unwrap().shading("E2EFDA");
422        tbl.cell(1, 0).unwrap().set_text("Hardware");
423        tbl.cell(1, 0).unwrap().v_merge_restart();
424        tbl.cell(1, 1).unwrap().set_text("Laptop");
425        tbl.cell(1, 2).unwrap().set_text("$1,200");
426        tbl.cell(2, 0).unwrap().v_merge_continue();
427        tbl.cell(2, 1).unwrap().set_text("Monitor");
428        tbl.cell(2, 2).unwrap().set_text("$450");
429        tbl.cell(3, 0).unwrap().set_text("Software");
430        tbl.cell(3, 1).unwrap().set_text("IDE License");
431        tbl.cell(3, 2).unwrap().set_text("$200/yr");
432    }
433
434    doc.add_paragraph("");
435
436    doc.add_paragraph("5.4 Nested Table").style("Heading2");
437    {
438        let mut tbl = doc
439            .add_table(2, 2)
440            .borders(BorderStyle::Single, 6, "2E75B6");
441        tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
442        tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
443        tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
444        {
445            let mut cell = tbl.cell(1, 1).unwrap();
446            cell.set_text("Nested table below:");
447            let mut nested = cell
448                .add_table(2, 2)
449                .borders(BorderStyle::Single, 2, "FF6600");
450            nested.cell(0, 0).unwrap().set_text("A");
451            nested.cell(0, 1).unwrap().set_text("B");
452            nested.cell(1, 0).unwrap().set_text("C");
453            nested.cell(1, 1).unwrap().set_text("D");
454        }
455    }
456
457    doc.add_paragraph("");
458
459    doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
460        .style("Heading2");
461    {
462        let mut tbl = doc
463            .add_table(2, 3)
464            .borders(BorderStyle::Single, 4, "666666");
465        tbl.row(0).unwrap().height(Length::pt(50.0)).header();
466        tbl.cell(0, 0).unwrap().set_text("Top");
467        tbl.cell(0, 0)
468            .unwrap()
469            .vertical_alignment(VerticalAlignment::Top)
470            .shading("FFF2CC");
471        tbl.cell(0, 1).unwrap().set_text("Center");
472        tbl.cell(0, 1)
473            .unwrap()
474            .vertical_alignment(VerticalAlignment::Center)
475            .shading("D9E2F3");
476        tbl.cell(0, 2).unwrap().set_text("Bottom");
477        tbl.cell(0, 2)
478            .unwrap()
479            .vertical_alignment(VerticalAlignment::Bottom)
480            .shading("E2EFDA");
481        tbl.row(1).unwrap().cant_split();
482        tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
483        tbl.cell(1, 0).unwrap().no_wrap();
484        tbl.cell(1, 1).unwrap().set_text("Fixed width");
485        tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
486        tbl.cell(1, 2).unwrap().set_text("Normal");
487    }
488
489    // ── SECTION 6: IMAGES ──
490    doc.add_paragraph("").page_break_before(true);
491    doc.add_paragraph("6. Images").style("Heading1");
492
493    doc.add_paragraph("6.1 Inline Image").style("Heading2");
494    doc.add_paragraph("A blue gradient image below:");
495    let img = create_sample_png(200, 50, [0, 80, 200]);
496    doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
497
498    doc.add_paragraph("");
499    doc.add_paragraph("6.2 Header Image").style("Heading2");
500    let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
501    doc.set_header_image(
502        &hdr_img,
503        "header_logo.png",
504        Length::inches(2.0),
505        Length::inches(0.2),
506    );
507    doc.add_paragraph("The header now contains an inline image (check the top of this page).");
508
509    doc.add_paragraph("");
510    doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
511
512    // ── SECTION 7: CONTENT MANIPULATION ──
513    doc.add_paragraph("").page_break_before(true);
514    doc.add_paragraph("7. Content Manipulation")
515        .style("Heading1");
516
517    doc.add_paragraph("7.1 Placeholder Replacement")
518        .style("Heading2");
519    doc.add_paragraph("Customer: {{customer}}");
520    doc.add_paragraph("Date: {{date}}");
521    doc.add_paragraph("Reference: {{ref_number}}");
522    {
523        let mut tbl = doc
524            .add_table(2, 2)
525            .borders(BorderStyle::Single, 4, "000000");
526        tbl.cell(0, 0).unwrap().set_text("Project");
527        tbl.cell(0, 0).unwrap().shading("D6E4F0");
528        tbl.cell(0, 1).unwrap().set_text("{{project}}");
529        tbl.cell(1, 0).unwrap().set_text("Status");
530        tbl.cell(1, 0).unwrap().shading("D6E4F0");
531        tbl.cell(1, 1).unwrap().set_text("{{status}}");
532    }
533
534    let mut replacements = HashMap::new();
535    replacements.insert("{{customer}}", "Tensorbee Inc.");
536    replacements.insert("{{date}}", "February 22, 2026");
537    replacements.insert("{{ref_number}}", "TB-2026-001");
538    replacements.insert("{{project}}", "Infrastructure Upgrade");
539    replacements.insert("{{status}}", "In Progress");
540    let n = doc.replace_all(&replacements);
541    doc.add_paragraph(&format!("({n} placeholders replaced above)"));
542
543    doc.add_paragraph("");
544
545    doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
546    doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
547    let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
548    doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
549
550    doc.add_paragraph("");
551
552    doc.add_paragraph("7.3 Content Insertion").style("Heading2");
553    doc.add_paragraph("Section A: First content.");
554    doc.add_paragraph("Section C: Third content.");
555    if let Some(idx) = doc.find_content_index("Section C") {
556        doc.insert_paragraph(
557            idx,
558            "Section B: Inserted between A and C via find_content_index().",
559        );
560    }
561
562    // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
563    doc.add_paragraph("").section_break(SectionBreak::NextPage);
564    doc.add_paragraph("8. Section Breaks & Orientation")
565        .style("Heading1");
566    doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
567
568    {
569        let mut tbl = doc
570            .add_table(3, 7)
571            .borders(BorderStyle::Single, 4, "2E75B6");
572        let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
573        for (c, h) in headers.iter().enumerate() {
574            tbl.cell(0, c).unwrap().set_text(h);
575            tbl.cell(0, c).unwrap().shading("2E75B6");
576        }
577        let data = [
578            [
579                "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
580            ],
581            [
582                "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
583            ],
584        ];
585        for (r, row) in data.iter().enumerate() {
586            for (c, v) in row.iter().enumerate() {
587                tbl.cell(r + 1, c).unwrap().set_text(v);
588            }
589        }
590    }
591
592    doc.add_paragraph("")
593        .section_break(SectionBreak::NextPage)
594        .section_landscape();
595
596    // ── SECTION 9: CUSTOM STYLES ──
597    doc.add_paragraph("9. Custom Styles").style("Heading1");
598    doc.add_style(
599        StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
600            .based_on("Normal")
601            .paragraph_properties(rdocx_oxml::properties::CT_PPr {
602                shading: Some(rdocx_oxml::properties::CT_Shd {
603                    val: "clear".to_string(),
604                    color: None,
605                    fill: Some("FFF2CC".to_string()),
606                }),
607                ..Default::default()
608            })
609            .run_properties(rdocx_oxml::properties::CT_RPr {
610                bold: Some(true),
611                color: Some("C45911".to_string()),
612                ..Default::default()
613            }),
614    );
615    doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
616        .style("CustomHighlight");
617
618    doc.add_paragraph("");
619
620    // ── SECTION 10: DOCUMENT INTELLIGENCE ──
621    doc.add_paragraph("10. Document Intelligence API")
622        .style("Heading1");
623
624    let wc = doc.word_count();
625    let hc = doc.headings().len();
626    let ic = doc.images().len();
627    let lc = doc.links().len();
628    doc.add_paragraph(&format!("Word count: {wc}"));
629    doc.add_paragraph(&format!("Heading count: {hc}"));
630    doc.add_paragraph(&format!("Image count: {ic}"));
631    doc.add_paragraph(&format!("Link count: {lc}"));
632
633    let outline = doc.document_outline();
634    doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
635
636    let issues = doc.audit_accessibility();
637    doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
638    for issue in &issues {
639        doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
640    }
641
642    // ── SECTION 11: DOCUMENT MERGING ──
643    doc.add_paragraph("");
644    doc.add_paragraph("11. Document Merging").style("Heading1");
645    {
646        let mut other = Document::new();
647        other.add_paragraph("This paragraph was merged from another document using append().");
648        doc.append(&other);
649    }
650
651    doc.add_paragraph("");
652
653    // ── SUMMARY ──
654    doc.add_paragraph("Summary of Demonstrated Features")
655        .style("Heading1");
656    let features = [
657        "Page setup: size, margins, header/footer distance, gutter",
658        "Document metadata: title, author, subject, keywords",
659        "Headers and footers: text, images, different first page",
660        "Background images (full-page behind text)",
661        "Table of Contents generation with bookmarks",
662        "Text formatting: bold, italic, underline styles, strike, color, size, font",
663        "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
664        "Paragraph formatting: alignment, borders, shading, spacing, indentation",
665        "Pagination controls: keep-with-next, keep-together, widow control",
666        "Bullet and numbered lists with nesting",
667        "Tab stops with dot/underscore/hyphen leaders",
668        "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
669        "Row properties: header rows, exact height, cant-split",
670        "Cell properties: width, no-wrap, vertical alignment",
671        "Inline images and header images",
672        "Placeholder replacement (single and batch)",
673        "Regex-based find and replace",
674        "Content insertion at specific positions",
675        "Section breaks with mixed portrait/landscape",
676        "Custom paragraph and character styles",
677        "Document intelligence: word count, headings, outline, images, links",
678        "Accessibility audit",
679        "Document merging (append)",
680        "Export: DOCX, PDF, HTML, Markdown",
681    ];
682    for f in &features {
683        doc.add_bullet_list_item(f, 0);
684    }
685    doc.add_paragraph("");
686    doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
687        .alignment(Alignment::Center)
688        .shading("E2EFDA")
689        .border_all(BorderStyle::Single, 2, "00AA00");
690
691    doc
692}
693
694// =============================================================================
695// 2. PROPOSAL DOCUMENT — Deep navy + gold scheme
696// =============================================================================
697fn generate_proposal(_samples_dir: &Path) -> Document {
698    let mut doc = Document::new();
699
700    // Colors: Navy #1B2A4A, Gold #C5922E, Light #F4F1EB
701    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
702    doc.set_margins(
703        Length::inches(1.0),
704        Length::inches(1.0),
705        Length::inches(1.0),
706        Length::inches(1.0),
707    );
708    doc.set_title("AI Platform Modernization Proposal");
709    doc.set_author("Walter White");
710    doc.set_subject("Technology Proposal");
711    doc.set_keywords("proposal, AI, modernization, Tensorbee");
712
713    // Banner header
714    let logo_img = create_logo_png(220, 48);
715    let banner = build_header_banner_xml(
716        "rId1",
717        &BannerOpts {
718            bg_color: "1B2A4A",
719            banner_width: 7772400,
720            banner_height: 914400,
721            logo_width: 2011680,
722            logo_height: 438912,
723            logo_x_offset: 295125,
724            logo_y_offset: 237744,
725        },
726    );
727    doc.set_raw_header_with_images(
728        banner,
729        &[("rId1", &logo_img, "logo.png")],
730        rdocx_oxml::header_footer::HdrFtrType::Default,
731    );
732    doc.set_different_first_page(true);
733    let cover_banner = build_header_banner_xml(
734        "rId1",
735        &BannerOpts {
736            bg_color: "C5922E",
737            banner_width: 7772400,
738            banner_height: 914400,
739            logo_width: 2011680,
740            logo_height: 438912,
741            logo_x_offset: 295125,
742            logo_y_offset: 237744,
743        },
744    );
745    doc.set_raw_header_with_images(
746        cover_banner,
747        &[("rId1", &logo_img, "logo.png")],
748        rdocx_oxml::header_footer::HdrFtrType::First,
749    );
750    doc.set_margins(
751        Length::twips(2292),
752        Length::twips(1440),
753        Length::twips(1440),
754        Length::twips(1440),
755    );
756    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
757    doc.set_footer("Tensorbee — Confidential");
758
759    // Custom styles
760    doc.add_style(
761        StyleBuilder::paragraph("ProposalTitle", "Proposal Title")
762            .based_on("Normal")
763            .run_properties(rdocx_oxml::properties::CT_RPr {
764                bold: Some(true),
765                font_ascii: Some("Georgia".to_string()),
766                font_hansi: Some("Georgia".to_string()),
767                sz: Some(rdocx_oxml::HalfPoint(56)), // half-points → 28pt
768                color: Some("1B2A4A".to_string()),
769                ..Default::default()
770            }),
771    );
772
773    // ── Cover Page ──
774    for _ in 0..4 {
775        doc.add_paragraph("");
776    }
777    doc.add_paragraph("AI Platform Modernization")
778        .style("ProposalTitle")
779        .alignment(Alignment::Center);
780    doc.add_paragraph("")
781        .alignment(Alignment::Center)
782        .border_bottom(BorderStyle::Single, 8, "C5922E");
783    {
784        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
785        p.add_run("Prepared for Global Financial Corp.")
786            .size(14.0)
787            .color("666666")
788            .italic(true);
789    }
790    {
791        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
792        p.add_run("Prepared by: Walter White, CEO — Tensorbee")
793            .size(12.0)
794            .color("888888");
795    }
796    {
797        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
798        p.add_run("February 22, 2026").size(12.0).color("888888");
799    }
800
801    // ── TOC ──
802    doc.add_paragraph("").page_break_before(true);
803    doc.add_paragraph("Table of Contents").style("Heading1");
804    // We'll add a manual-style TOC since the insert_toc goes at a specific position
805    doc.insert_toc(doc.content_count(), 2);
806
807    // ── Executive Summary ──
808    doc.add_paragraph("").page_break_before(true);
809    doc.add_paragraph("Executive Summary").style("Heading1");
810    doc.add_paragraph(
811        "Tensorbee proposes a comprehensive modernization of Global Financial Corp's AI platform, \
812         transitioning from legacy batch-processing systems to a real-time inference architecture. \
813         This transformation will reduce model deployment time from weeks to hours, improve \
814         prediction accuracy by an estimated 23%, and deliver $4.2M in annual operational savings.",
815    )
816    .first_line_indent(Length::inches(0.3));
817    doc.add_paragraph(
818        "The project spans three phases over 18 months, with a total investment of $2.8M. \
819         Tensorbee brings deep expertise in ML infrastructure, having successfully completed \
820         similar transformations for three Fortune 500 financial institutions.",
821    )
822    .first_line_indent(Length::inches(0.3));
823
824    // Key metrics table
825    doc.add_paragraph("");
826    {
827        let mut tbl = doc
828            .add_table(5, 2)
829            .borders(BorderStyle::Single, 4, "1B2A4A")
830            .width_pct(80.0);
831        tbl.cell(0, 0).unwrap().set_text("Key Metric");
832        tbl.cell(0, 0).unwrap().shading("1B2A4A");
833        tbl.cell(0, 1).unwrap().set_text("Value");
834        tbl.cell(0, 1).unwrap().shading("1B2A4A");
835        let rows = [
836            ("Total Investment", "$2.8M"),
837            ("Annual Savings", "$4.2M"),
838            ("ROI (Year 1)", "150%"),
839            ("Timeline", "18 months"),
840        ];
841        for (i, (k, v)) in rows.iter().enumerate() {
842            tbl.cell(i + 1, 0).unwrap().set_text(k);
843            if i % 2 == 0 {
844                tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
845            }
846            tbl.cell(i + 1, 1).unwrap().set_text(v);
847            if i % 2 == 0 {
848                tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
849            }
850        }
851    }
852
853    // ── Problem Statement ──
854    doc.add_paragraph("").page_break_before(true);
855    doc.add_paragraph("Problem Statement").style("Heading1");
856    doc.add_paragraph(
857        "Global Financial Corp's current AI infrastructure faces three critical challenges:",
858    );
859    doc.add_numbered_list_item("Deployment Latency — New models take 3-4 weeks to deploy to production, compared to the industry benchmark of 2-3 days.", 0);
860    doc.add_numbered_list_item("Scalability Constraints — The batch processing architecture cannot handle real-time inference demands during peak trading hours.", 0);
861    doc.add_numbered_list_item("Technical Debt — Legacy Python 2.7 codebases and manual deployment scripts create reliability risks and slow iteration speed.", 0);
862
863    // ── Proposed Solution ──
864    doc.add_paragraph("").page_break_before(true);
865    doc.add_paragraph("Proposed Solution").style("Heading1");
866
867    doc.add_paragraph("Phase 1: Foundation (Months 1-6)")
868        .style("Heading2");
869    doc.add_bullet_list_item("Deploy Kubernetes-based ML serving infrastructure", 0);
870    doc.add_bullet_list_item("Implement CI/CD pipeline for model deployment", 0);
871    doc.add_bullet_list_item("Migrate top 5 production models to new platform", 0);
872
873    doc.add_paragraph("Phase 2: Expansion (Months 7-12)")
874        .style("Heading2");
875    doc.add_bullet_list_item(
876        "Real-time feature engineering pipeline (Apache Kafka + Flink)",
877        0,
878    );
879    doc.add_bullet_list_item("A/B testing framework for model variants", 0);
880    doc.add_bullet_list_item("Automated model monitoring and drift detection", 0);
881
882    doc.add_paragraph("Phase 3: Optimization (Months 13-18)")
883        .style("Heading2");
884    doc.add_bullet_list_item("GPU inference optimization (TensorRT/ONNX Runtime)", 0);
885    doc.add_bullet_list_item("Multi-region deployment for latency reduction", 0);
886    doc.add_bullet_list_item(
887        "Self-service model deployment portal for data science teams",
888        0,
889    );
890
891    // ── Budget ──
892    doc.add_paragraph("Budget Breakdown").style("Heading1");
893    {
894        let mut tbl = doc
895            .add_table(6, 3)
896            .borders(BorderStyle::Single, 4, "1B2A4A")
897            .width_pct(100.0);
898        tbl.cell(0, 0).unwrap().set_text("Category");
899        tbl.cell(0, 0).unwrap().shading("1B2A4A");
900        tbl.cell(0, 1).unwrap().set_text("Cost");
901        tbl.cell(0, 1).unwrap().shading("1B2A4A");
902        tbl.cell(0, 2).unwrap().set_text("Notes");
903        tbl.cell(0, 2).unwrap().shading("1B2A4A");
904        let items = [
905            (
906                "Infrastructure",
907                "$450,000",
908                "Cloud compute + GPU instances",
909            ),
910            ("Engineering Services", "$1,600,000", "12 FTE x 18 months"),
911            (
912                "Software Licenses",
913                "$350,000",
914                "Kafka, monitoring, CI/CD tools",
915            ),
916            (
917                "Training & Enablement",
918                "$200,000",
919                "Team training, documentation",
920            ),
921            ("Contingency (10%)", "$200,000", "Risk buffer"),
922        ];
923        for (i, (cat, cost, note)) in items.iter().enumerate() {
924            tbl.cell(i + 1, 0).unwrap().set_text(cat);
925            tbl.cell(i + 1, 1).unwrap().set_text(cost);
926            tbl.cell(i + 1, 2).unwrap().set_text(note);
927            if i % 2 == 0 {
928                tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
929                tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
930                tbl.cell(i + 1, 2).unwrap().shading("F4F1EB");
931            }
932        }
933    }
934
935    // ── Closing ──
936    doc.add_paragraph("");
937    doc.add_paragraph("Next Steps").style("Heading1");
938    doc.add_numbered_list_item(
939        "Schedule technical deep-dive with GFC engineering team (Week 1)",
940        0,
941    );
942    doc.add_numbered_list_item("Finalize scope and sign SOW (Week 2-3)", 0);
943    doc.add_numbered_list_item("Kick off Phase 1 with joint planning session (Week 4)", 0);
944
945    doc.add_paragraph("");
946    {
947        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
948        p.add_run("Walter White")
949            .bold(true)
950            .size(14.0)
951            .color("1B2A4A");
952    }
953    {
954        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
955        p.add_run("CEO, Tensorbee | walter@tensorbee.com")
956            .size(10.0)
957            .color("888888");
958    }
959
960    doc
961}
962
963// =============================================================================
964// 3. QUOTE / BILL OF MATERIALS — Teal + orange scheme
965// =============================================================================
966fn generate_quote(_samples_dir: &Path) -> Document {
967    let mut doc = Document::new();
968
969    // Colors: Teal #008B8B, Dark #1A3C3C, Orange #E07020, Light #F0F8F8
970    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
971    doc.set_margins(
972        Length::inches(0.75),
973        Length::inches(0.75),
974        Length::inches(0.75),
975        Length::inches(0.75),
976    );
977    doc.set_title("Quotation QT-2026-0147");
978    doc.set_author("Walter White");
979    doc.set_keywords("quote, BOM, Tensorbee");
980
981    doc.set_header("Tensorbee — Quotation");
982    doc.set_footer("QT-2026-0147 | Page");
983
984    // ── Header Block ──
985    {
986        let mut p = doc.add_paragraph("").alignment(Alignment::Left);
987        p.add_run("TENSORBEE")
988            .bold(true)
989            .size(28.0)
990            .color("008B8B")
991            .font("Helvetica");
992    }
993    doc.add_paragraph("123 Innovation Drive, Suite 400")
994        .alignment(Alignment::Left);
995    doc.add_paragraph("San Francisco, CA 94105 | +1 (415) 555-0199")
996        .alignment(Alignment::Left);
997    doc.add_paragraph("")
998        .border_bottom(BorderStyle::Single, 8, "008B8B");
999
1000    // Quote meta
1001    doc.add_paragraph("");
1002    {
1003        let mut tbl = doc.add_table(4, 4).width_pct(100.0);
1004        tbl.cell(0, 0).unwrap().set_text("QUOTATION");
1005        tbl.cell(0, 0).unwrap().shading("008B8B").grid_span(2);
1006        tbl.cell(0, 2).unwrap().set_text("DATE");
1007        tbl.cell(0, 2).unwrap().shading("008B8B");
1008        tbl.cell(0, 3).unwrap().set_text("VALID UNTIL");
1009        tbl.cell(0, 3).unwrap().shading("008B8B");
1010        tbl.cell(1, 0).unwrap().set_text("Quote #:");
1011        tbl.cell(1, 1).unwrap().set_text("QT-2026-0147");
1012        tbl.cell(1, 2).unwrap().set_text("Feb 22, 2026");
1013        tbl.cell(1, 3).unwrap().set_text("Mar 22, 2026");
1014        tbl.cell(2, 0).unwrap().set_text("Prepared by:");
1015        tbl.cell(2, 1).unwrap().set_text("Walter White");
1016        tbl.cell(2, 2).unwrap().set_text("Payment:");
1017        tbl.cell(2, 3).unwrap().set_text("Net 30");
1018        tbl.cell(3, 0).unwrap().set_text("Customer:");
1019        tbl.cell(3, 1).unwrap().set_text("Meridian Dynamics LLC");
1020        tbl.cell(3, 2).unwrap().set_text("Currency:");
1021        tbl.cell(3, 3).unwrap().set_text("USD");
1022    }
1023
1024    doc.add_paragraph("");
1025
1026    // ── Bill of Materials ──
1027    {
1028        let mut p = doc.add_paragraph("");
1029        p.add_run("Bill of Materials")
1030            .bold(true)
1031            .size(16.0)
1032            .color("1A3C3C");
1033    }
1034    doc.add_paragraph("");
1035
1036    {
1037        let mut tbl = doc
1038            .add_table(10, 6)
1039            .borders(BorderStyle::Single, 2, "008B8B")
1040            .width_pct(100.0)
1041            .layout_fixed();
1042        // Headers
1043        let hdrs = [
1044            "#",
1045            "Part Number",
1046            "Description",
1047            "Qty",
1048            "Unit Price",
1049            "Total",
1050        ];
1051        for (c, h) in hdrs.iter().enumerate() {
1052            tbl.cell(0, c).unwrap().set_text(h);
1053            tbl.cell(0, c).unwrap().shading("008B8B");
1054        }
1055
1056        let items: Vec<(&str, &str, &str, &str, &str)> = vec![
1057            (
1058                "TB-GPU-A100",
1059                "NVIDIA A100 80GB GPU",
1060                "4",
1061                "$12,500.00",
1062                "$50,000.00",
1063            ),
1064            (
1065                "TB-SRV-R750",
1066                "Dell R750xa Server Chassis",
1067                "2",
1068                "$8,200.00",
1069                "$16,400.00",
1070            ),
1071            (
1072                "TB-RAM-256G",
1073                "256GB DDR5 ECC Memory Module",
1074                "8",
1075                "$890.00",
1076                "$7,120.00",
1077            ),
1078            (
1079                "TB-SSD-3840",
1080                "3.84TB NVMe U.2 SSD",
1081                "8",
1082                "$1,150.00",
1083                "$9,200.00",
1084            ),
1085            (
1086                "TB-NET-CX7",
1087                "ConnectX-7 200GbE NIC",
1088                "4",
1089                "$1,800.00",
1090                "$7,200.00",
1091            ),
1092            (
1093                "TB-SW-48P",
1094                "48-Port 100GbE Switch",
1095                "1",
1096                "$22,000.00",
1097                "$22,000.00",
1098            ),
1099            (
1100                "TB-CAB-RACK",
1101                "42U Server Rack + PDU",
1102                "1",
1103                "$4,500.00",
1104                "$4,500.00",
1105            ),
1106            (
1107                "TB-SVC-INST",
1108                "Installation & Configuration",
1109                "1",
1110                "$8,500.00",
1111                "$8,500.00",
1112            ),
1113            (
1114                "TB-SVC-SUPP",
1115                "3-Year Premium Support",
1116                "1",
1117                "$15,000.00",
1118                "$15,000.00",
1119            ),
1120        ];
1121
1122        for (i, (pn, desc, qty, unit, total)) in items.iter().enumerate() {
1123            let row = i + 1;
1124            tbl.cell(row, 0).unwrap().set_text(&format!("{}", i + 1));
1125            tbl.cell(row, 1).unwrap().set_text(pn);
1126            tbl.cell(row, 2).unwrap().set_text(desc);
1127            tbl.cell(row, 3).unwrap().set_text(qty);
1128            tbl.cell(row, 4).unwrap().set_text(unit);
1129            tbl.cell(row, 5).unwrap().set_text(total);
1130            if i % 2 == 0 {
1131                for c in 0..6 {
1132                    tbl.cell(row, c).unwrap().shading("F0F8F8");
1133                }
1134            }
1135        }
1136    }
1137
1138    doc.add_paragraph("");
1139
1140    // ── Totals ──
1141    {
1142        let mut tbl = doc
1143            .add_table(4, 2)
1144            .borders(BorderStyle::Single, 2, "008B8B")
1145            .width(Length::inches(3.5))
1146            .alignment(Alignment::Right);
1147        tbl.cell(0, 0).unwrap().set_text("Subtotal");
1148        tbl.cell(0, 1).unwrap().set_text("$139,920.00");
1149        tbl.cell(1, 0).unwrap().set_text("Shipping & Handling");
1150        tbl.cell(1, 1).unwrap().set_text("$2,500.00");
1151        tbl.cell(2, 0).unwrap().set_text("Tax (8.625%)");
1152        tbl.cell(2, 1).unwrap().set_text("$12,068.10");
1153        tbl.cell(3, 0).unwrap().set_text("TOTAL");
1154        tbl.cell(3, 0).unwrap().shading("E07020");
1155        tbl.cell(3, 1).unwrap().set_text("$154,488.10");
1156        tbl.cell(3, 1).unwrap().shading("E07020");
1157    }
1158
1159    doc.add_paragraph("");
1160
1161    // ── Terms & Conditions ──
1162    {
1163        let mut p = doc.add_paragraph("");
1164        p.add_run("Terms & Conditions")
1165            .bold(true)
1166            .size(14.0)
1167            .color("1A3C3C");
1168    }
1169    doc.add_numbered_list_item(
1170        "This quotation is valid for 30 calendar days from the date of issue.",
1171        0,
1172    );
1173    doc.add_numbered_list_item(
1174        "All prices are in USD and exclusive of applicable taxes unless stated.",
1175        0,
1176    );
1177    doc.add_numbered_list_item("Standard lead time is 4-6 weeks from PO receipt.", 0);
1178    doc.add_numbered_list_item(
1179        "Payment terms: Net 30 from invoice date. 2% discount for payment within 10 days.",
1180        0,
1181    );
1182    doc.add_numbered_list_item(
1183        "Warranty: 3-year manufacturer warranty on all hardware components.",
1184        0,
1185    );
1186    doc.add_numbered_list_item(
1187        "Returns subject to 15% restocking fee if initiated after 14 days.",
1188        0,
1189    );
1190
1191    doc.add_paragraph("");
1192    doc.add_paragraph("")
1193        .border_bottom(BorderStyle::Single, 4, "008B8B");
1194    doc.add_paragraph("");
1195    {
1196        let mut p = doc.add_paragraph("");
1197        p.add_run("Accepted by: ").bold(true);
1198        p.add_run("___________________________ Date: ___________");
1199    }
1200    {
1201        let mut p = doc.add_paragraph("");
1202        p.add_run("Print Name: ").bold(true);
1203        p.add_run("___________________________ Title: ___________");
1204    }
1205
1206    doc
1207}
1208
1209// =============================================================================
1210// 4. INVOICE — Crimson + charcoal scheme
1211// =============================================================================
1212fn generate_invoice(_samples_dir: &Path) -> Document {
1213    let mut doc = Document::new();
1214
1215    // Colors: Crimson #B22222, Charcoal #333333, Light #FAF0F0
1216    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1217    doc.set_margins(
1218        Length::inches(0.75),
1219        Length::inches(0.75),
1220        Length::inches(0.75),
1221        Length::inches(0.75),
1222    );
1223    doc.set_title("Invoice INV-2026-0392");
1224    doc.set_author("Walter White");
1225    doc.set_keywords("invoice, Tensorbee");
1226
1227    doc.set_footer("Tensorbee — Thank you for your business!");
1228
1229    // ── Company & Invoice Header ──
1230    {
1231        let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1232        // Company name left, INVOICE right
1233        {
1234            let mut cell = tbl.cell(0, 0).unwrap();
1235            let mut p = cell.add_paragraph("");
1236            p.add_run("TENSORBEE")
1237                .bold(true)
1238                .size(32.0)
1239                .color("B22222")
1240                .font("Helvetica");
1241        }
1242        {
1243            let mut cell = tbl.cell(0, 1).unwrap();
1244            let mut p = cell.add_paragraph("");
1245            p.add_run("INVOICE").bold(true).size(32.0).color("333333");
1246        }
1247        tbl.cell(1, 0)
1248            .unwrap()
1249            .set_text("123 Innovation Drive, Suite 400");
1250        tbl.cell(1, 1).unwrap().set_text("Invoice #: INV-2026-0392");
1251        tbl.cell(2, 0).unwrap().set_text("San Francisco, CA 94105");
1252        tbl.cell(2, 1).unwrap().set_text("Date: February 22, 2026");
1253        tbl.cell(3, 0).unwrap().set_text("walter@tensorbee.com");
1254        tbl.cell(3, 1).unwrap().set_text("Due Date: March 24, 2026");
1255    }
1256
1257    doc.add_paragraph("")
1258        .border_bottom(BorderStyle::Thick, 8, "B22222");
1259    doc.add_paragraph("");
1260
1261    // ── Bill To / Ship To ──
1262    {
1263        let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1264        tbl.cell(0, 0).unwrap().set_text("BILL TO");
1265        tbl.cell(0, 0).unwrap().shading("B22222");
1266        tbl.cell(0, 1).unwrap().set_text("SHIP TO");
1267        tbl.cell(0, 1).unwrap().shading("B22222");
1268        tbl.cell(1, 0).unwrap().set_text("Meridian Dynamics LLC");
1269        tbl.cell(1, 1).unwrap().set_text("Meridian Dynamics LLC");
1270        tbl.cell(2, 0).unwrap().set_text("456 Enterprise Blvd");
1271        tbl.cell(2, 1).unwrap().set_text("Attn: Server Room B");
1272        tbl.cell(3, 0).unwrap().set_text("Austin, TX 78701");
1273        tbl.cell(3, 1)
1274            .unwrap()
1275            .set_text("456 Enterprise Blvd, Austin, TX 78701");
1276    }
1277
1278    doc.add_paragraph("");
1279
1280    // ── Line Items ──
1281    {
1282        let mut tbl = doc
1283            .add_table(8, 5)
1284            .borders(BorderStyle::Single, 2, "B22222")
1285            .width_pct(100.0);
1286        let hdrs = ["Description", "Qty", "Unit Price", "Tax", "Amount"];
1287        for (c, h) in hdrs.iter().enumerate() {
1288            tbl.cell(0, c).unwrap().set_text(h);
1289            tbl.cell(0, c).unwrap().shading("B22222");
1290        }
1291        let items = [
1292            (
1293                "ML Infrastructure Setup — Phase 1",
1294                "1",
1295                "$45,000.00",
1296                "$3,881.25",
1297                "$48,881.25",
1298            ),
1299            (
1300                "Data Pipeline Development (160 hrs)",
1301                "160",
1302                "$225.00",
1303                "$3,105.00",
1304                "$39,105.00",
1305            ),
1306            (
1307                "GPU Cluster Configuration",
1308                "1",
1309                "$12,000.00",
1310                "$1,035.00",
1311                "$13,035.00",
1312            ),
1313            (
1314                "API Gateway Implementation",
1315                "1",
1316                "$18,500.00",
1317                "$1,595.63",
1318                "$20,095.63",
1319            ),
1320            (
1321                "Load Testing & QA (80 hrs)",
1322                "80",
1323                "$195.00",
1324                "$1,345.50",
1325                "$16,945.50",
1326            ),
1327            (
1328                "Documentation & Training",
1329                "1",
1330                "$8,000.00",
1331                "$690.00",
1332                "$8,690.00",
1333            ),
1334            (
1335                "Project Management (3 months)",
1336                "3",
1337                "$6,500.00",
1338                "$1,679.63",
1339                "$21,179.63",
1340            ),
1341        ];
1342        for (i, (desc, qty, unit, tax, amt)) in items.iter().enumerate() {
1343            let r = i + 1;
1344            tbl.cell(r, 0).unwrap().set_text(desc);
1345            tbl.cell(r, 1).unwrap().set_text(qty);
1346            tbl.cell(r, 2).unwrap().set_text(unit);
1347            tbl.cell(r, 3).unwrap().set_text(tax);
1348            tbl.cell(r, 4).unwrap().set_text(amt);
1349            if i % 2 == 0 {
1350                for c in 0..5 {
1351                    tbl.cell(r, c).unwrap().shading("FAF0F0");
1352                }
1353            }
1354        }
1355    }
1356
1357    doc.add_paragraph("");
1358
1359    // ── Totals ──
1360    {
1361        let mut tbl = doc
1362            .add_table(4, 2)
1363            .borders(BorderStyle::Single, 2, "B22222")
1364            .width(Length::inches(3.0))
1365            .alignment(Alignment::Right);
1366        tbl.cell(0, 0).unwrap().set_text("Subtotal");
1367        tbl.cell(0, 1).unwrap().set_text("$154,600.00");
1368        tbl.cell(1, 0).unwrap().set_text("Tax (8.625%)");
1369        tbl.cell(1, 1).unwrap().set_text("$13,334.25");
1370        tbl.cell(2, 0).unwrap().set_text("Discount (5%)");
1371        tbl.cell(2, 1).unwrap().set_text("-$7,730.00");
1372        tbl.cell(3, 0).unwrap().set_text("AMOUNT DUE");
1373        tbl.cell(3, 0).unwrap().shading("B22222");
1374        tbl.cell(3, 1).unwrap().set_text("$160,204.25");
1375        tbl.cell(3, 1).unwrap().shading("B22222");
1376    }
1377
1378    doc.add_paragraph("");
1379    doc.add_paragraph("");
1380
1381    // ── Payment Details ──
1382    {
1383        let mut p = doc.add_paragraph("");
1384        p.add_run("Payment Details")
1385            .bold(true)
1386            .size(14.0)
1387            .color("333333");
1388    }
1389    doc.add_paragraph("Bank: Silicon Valley Bank")
1390        .indent_left(Length::inches(0.3));
1391    doc.add_paragraph("Account: Tensorbee Inc. — 0847-2953-1120")
1392        .indent_left(Length::inches(0.3));
1393    doc.add_paragraph("Routing: 121140399")
1394        .indent_left(Length::inches(0.3));
1395    doc.add_paragraph("Swift: SVBKUS6S")
1396        .indent_left(Length::inches(0.3));
1397
1398    doc.add_paragraph("");
1399
1400    doc.add_paragraph("Please include invoice number INV-2026-0392 in the payment reference.")
1401        .shading("FAF0F0")
1402        .border_all(BorderStyle::Single, 2, "B22222");
1403
1404    doc
1405}
1406
1407// =============================================================================
1408// 5. REPORT — Forest green + earth tones, with images & hierarchical sections
1409// =============================================================================
1410fn generate_report(_samples_dir: &Path) -> Document {
1411    let mut doc = Document::new();
1412
1413    // Colors: Forest #2D5016, Sage #6B8E23, Earth #8B7355, Cream #FFFAF0
1414    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1415    doc.set_margins(
1416        Length::inches(1.0),
1417        Length::inches(1.0),
1418        Length::inches(1.0),
1419        Length::inches(1.0),
1420    );
1421    doc.set_title("Q4 2025 Environmental Impact Report");
1422    doc.set_author("Walter White");
1423    doc.set_subject("Quarterly Environmental Report");
1424    doc.set_keywords("environment, sustainability, report, Tensorbee");
1425
1426    doc.set_different_first_page(true);
1427    doc.set_first_page_header("");
1428    doc.set_header("Tensorbee — Q4 2025 Environmental Impact Report");
1429    doc.set_footer("CONFIDENTIAL — Page");
1430
1431    // ── Cover ──
1432    let cover_bg = create_sample_png(612, 792, [20, 50, 15]);
1433    doc.add_background_image(&cover_bg, "report_cover.png");
1434
1435    for _ in 0..5 {
1436        doc.add_paragraph("");
1437    }
1438    {
1439        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1440        p.add_run("Q4 2025")
1441            .bold(true)
1442            .size(48.0)
1443            .color("FFFFFF")
1444            .font("Georgia");
1445    }
1446    {
1447        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1448        p.add_run("Environmental Impact Report")
1449            .size(24.0)
1450            .color("90EE90")
1451            .font("Georgia")
1452            .italic(true);
1453    }
1454    doc.add_paragraph("");
1455    {
1456        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1457        p.add_run("Tensorbee — Sustainability Division")
1458            .size(14.0)
1459            .color("C0C0C0");
1460    }
1461    {
1462        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1463        p.add_run("Prepared by Walter White, Chief Sustainability Officer")
1464            .size(11.0)
1465            .color("AAAAAA");
1466    }
1467
1468    // ── TOC ──
1469    doc.add_paragraph("").page_break_before(true);
1470    doc.insert_toc(doc.content_count(), 3);
1471
1472    // ── Section 1: Executive Overview ──
1473    doc.add_paragraph("").page_break_before(true);
1474    doc.add_paragraph("Executive Overview").style("Heading1");
1475    doc.add_paragraph(
1476        "This report presents Tensorbee's environmental performance for Q4 2025. Our \
1477         sustainability initiatives have yielded a 34% reduction in carbon emissions compared \
1478         to Q4 2024, exceeding our target of 25%. Key achievements include the transition to \
1479         100% renewable energy in our primary data centers and the launch of our carbon offset \
1480         marketplace.",
1481    )
1482    .first_line_indent(Length::inches(0.3));
1483
1484    // Image: performance chart
1485    let chart_img = create_chart_png(400, 200);
1486    doc.add_paragraph("");
1487    doc.add_picture(
1488        &chart_img,
1489        "performance_chart.png",
1490        Length::inches(5.0),
1491        Length::inches(2.5),
1492    )
1493    .alignment(Alignment::Center);
1494    {
1495        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1496        p.add_run("Figure 1: Quarterly Carbon Emissions (tonnes CO2e)")
1497            .italic(true)
1498            .size(9.0)
1499            .color("666666");
1500    }
1501
1502    // ── Section 2: Energy Consumption ──
1503    doc.add_paragraph("").page_break_before(true);
1504    doc.add_paragraph("Energy Consumption").style("Heading1");
1505
1506    doc.add_paragraph("Data Center Operations")
1507        .style("Heading2");
1508    doc.add_paragraph(
1509        "Our three primary data centers consumed a combined 4.2 GWh in Q4 2025, \
1510         a 12% reduction from Q3 2025 driven by improved cooling efficiency and \
1511         server consolidation.",
1512    );
1513
1514    // Data table
1515    {
1516        let mut tbl = doc
1517            .add_table(5, 4)
1518            .borders(BorderStyle::Single, 4, "2D5016")
1519            .width_pct(100.0);
1520        let hdrs = ["Data Center", "Capacity (MW)", "Usage (GWh)", "PUE"];
1521        for (c, h) in hdrs.iter().enumerate() {
1522            tbl.cell(0, c).unwrap().set_text(h);
1523            tbl.cell(0, c).unwrap().shading("2D5016");
1524        }
1525        let rows = [
1526            ("San Francisco (Primary)", "3.2", "1.8", "1.12"),
1527            ("Dublin (EU)", "2.1", "1.2", "1.18"),
1528            ("Singapore (APAC)", "1.8", "1.2", "1.24"),
1529            ("TOTAL", "7.1", "4.2", "1.17 avg"),
1530        ];
1531        for (i, (dc, cap, use_, pue)) in rows.iter().enumerate() {
1532            tbl.cell(i + 1, 0).unwrap().set_text(dc);
1533            tbl.cell(i + 1, 1).unwrap().set_text(cap);
1534            tbl.cell(i + 1, 2).unwrap().set_text(use_);
1535            tbl.cell(i + 1, 3).unwrap().set_text(pue);
1536            if i == 3 {
1537                for c in 0..4 {
1538                    tbl.cell(i + 1, c).unwrap().shading("E2EFDA");
1539                }
1540            }
1541        }
1542    }
1543
1544    doc.add_paragraph("");
1545
1546    doc.add_paragraph("Renewable Energy Mix").style("Heading2");
1547    doc.add_paragraph("Breakdown of energy sources across all facilities:");
1548
1549    doc.add_bullet_list_item("Solar PV: 42% (1.76 GWh)", 0);
1550    doc.add_bullet_list_item("Wind Power Purchase Agreements: 38% (1.60 GWh)", 0);
1551    doc.add_bullet_list_item("Hydroelectric: 12% (0.50 GWh)", 0);
1552    doc.add_bullet_list_item("Grid (non-renewable): 8% (0.34 GWh)", 0);
1553
1554    // ── Section 3: Water & Waste ──
1555    doc.add_paragraph("Water & Waste Management")
1556        .style("Heading1");
1557
1558    doc.add_paragraph("Water Usage").style("Heading2");
1559    doc.add_paragraph(
1560        "Total water consumption was 12.4 million gallons, a 15% reduction from Q3. \
1561         Our closed-loop cooling systems now recycle 78% of water used in cooling operations.",
1562    );
1563
1564    doc.add_paragraph("Waste Reduction").style("Heading2");
1565    doc.add_paragraph("E-waste management results for Q4:")
1566        .keep_with_next(true);
1567    {
1568        let mut tbl = doc
1569            .add_table(4, 3)
1570            .borders(BorderStyle::Single, 2, "6B8E23")
1571            .width_pct(80.0);
1572        tbl.cell(0, 0).unwrap().set_text("Category");
1573        tbl.cell(0, 0).unwrap().shading("6B8E23");
1574        tbl.cell(0, 1).unwrap().set_text("Weight (kg)");
1575        tbl.cell(0, 1).unwrap().shading("6B8E23");
1576        tbl.cell(0, 2).unwrap().set_text("Recycled %");
1577        tbl.cell(0, 2).unwrap().shading("6B8E23");
1578        let rows = [
1579            ("Server Hardware", "2,450", "94%"),
1580            ("Networking Equipment", "820", "91%"),
1581            ("Storage Media", "340", "99%"),
1582        ];
1583        for (i, (cat, wt, pct)) in rows.iter().enumerate() {
1584            tbl.cell(i + 1, 0).unwrap().set_text(cat);
1585            tbl.cell(i + 1, 1).unwrap().set_text(wt);
1586            tbl.cell(i + 1, 2).unwrap().set_text(pct);
1587        }
1588    }
1589
1590    // ── Section 4: Initiatives ──
1591    doc.add_paragraph("").page_break_before(true);
1592    doc.add_paragraph("Q1 2026 Initiatives").style("Heading1");
1593
1594    doc.add_paragraph("Planned Programs").style("Heading2");
1595    doc.add_numbered_list_item(
1596        "Deploy on-site battery storage at SF data center (2 MWh capacity)",
1597        0,
1598    );
1599    doc.add_numbered_list_item("Pilot immersion cooling in Dublin facility", 0);
1600    doc.add_numbered_list_item("Launch employee commute carbon offset program", 0);
1601    doc.add_numbered_list_item("Achieve ISO 14001 certification for Singapore facility", 0);
1602
1603    doc.add_paragraph("Investment Targets").style("Heading2");
1604    doc.add_paragraph("Sustainability CapEx allocation for FY2026:")
1605        .keep_with_next(true);
1606    {
1607        let mut tbl = doc
1608            .add_table(5, 2)
1609            .borders(BorderStyle::Single, 4, "2D5016");
1610        tbl.cell(0, 0).unwrap().set_text("Initiative");
1611        tbl.cell(0, 0).unwrap().shading("2D5016");
1612        tbl.cell(0, 1).unwrap().set_text("Budget");
1613        tbl.cell(0, 1).unwrap().shading("2D5016");
1614        let rows = [
1615            ("Battery Storage", "$1.2M"),
1616            ("Immersion Cooling Pilot", "$800K"),
1617            ("Solar Panel Expansion", "$2.1M"),
1618            ("Carbon Credits", "$500K"),
1619        ];
1620        for (i, (init, budget)) in rows.iter().enumerate() {
1621            tbl.cell(i + 1, 0).unwrap().set_text(init);
1622            tbl.cell(i + 1, 1).unwrap().set_text(budget);
1623            if i % 2 == 0 {
1624                tbl.cell(i + 1, 0).unwrap().shading("FFFAF0");
1625                tbl.cell(i + 1, 1).unwrap().shading("FFFAF0");
1626            }
1627        }
1628    }
1629
1630    // Image: sustainability roadmap
1631    let roadmap_img = create_sample_png(500, 100, [40, 80, 30]);
1632    doc.add_paragraph("");
1633    doc.add_picture(
1634        &roadmap_img,
1635        "roadmap.png",
1636        Length::inches(6.0),
1637        Length::inches(1.2),
1638    )
1639    .alignment(Alignment::Center);
1640    {
1641        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1642        p.add_run("Figure 2: 2026 Sustainability Roadmap")
1643            .italic(true)
1644            .size(9.0)
1645            .color("666666");
1646    }
1647
1648    doc.add_paragraph("");
1649    doc.add_paragraph(
1650        "For questions about this report, contact Walter White at sustainability@tensorbee.com.",
1651    )
1652    .shading("FFFAF0")
1653    .border_all(BorderStyle::Single, 2, "2D5016");
1654
1655    doc
1656}
1657
1658// =============================================================================
1659// 6. LETTER — Slate blue + silver scheme
1660// =============================================================================
1661fn generate_letter(_samples_dir: &Path) -> Document {
1662    let mut doc = Document::new();
1663
1664    // Colors: Slate #4A5568, Blue accent #3182CE
1665    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1666    doc.set_margins(
1667        Length::inches(1.25),
1668        Length::inches(1.25),
1669        Length::inches(1.25),
1670        Length::inches(1.25),
1671    );
1672    doc.set_title("Business Letter");
1673    doc.set_author("Walter White");
1674
1675    // Banner header using raw XML
1676    let logo_img = create_logo_png(220, 48);
1677    let banner = build_header_banner_xml(
1678        "rId1",
1679        &BannerOpts {
1680            bg_color: "4A5568",
1681            banner_width: 7772400,
1682            banner_height: 731520, // ~0.8"
1683            logo_width: 2011680,
1684            logo_height: 438912,
1685            logo_x_offset: 295125,
1686            logo_y_offset: 146304,
1687        },
1688    );
1689    doc.set_raw_header_with_images(
1690        banner,
1691        &[("rId1", &logo_img, "logo.png")],
1692        rdocx_oxml::header_footer::HdrFtrType::Default,
1693    );
1694    doc.set_margins(
1695        Length::twips(2000),
1696        Length::twips(1800),
1697        Length::twips(1440),
1698        Length::twips(1800),
1699    );
1700    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
1701
1702    // Footer with contact
1703    doc.set_footer(
1704        "Tensorbee | 123 Innovation Drive, Suite 400 | San Francisco, CA 94105 | tensorbee.com",
1705    );
1706
1707    // ── Sender Address ──
1708    doc.add_paragraph("Walter White");
1709    doc.add_paragraph("Chief Executive Officer");
1710    doc.add_paragraph("Tensorbee");
1711    doc.add_paragraph("123 Innovation Drive, Suite 400");
1712    doc.add_paragraph("San Francisco, CA 94105");
1713    doc.add_paragraph("");
1714    doc.add_paragraph("February 22, 2026");
1715
1716    doc.add_paragraph("");
1717
1718    // ── Recipient ──
1719    doc.add_paragraph("Ms. Sarah Chen");
1720    doc.add_paragraph("VP of Engineering");
1721    doc.add_paragraph("Meridian Dynamics LLC");
1722    doc.add_paragraph("456 Enterprise Boulevard");
1723    doc.add_paragraph("Austin, TX 78701");
1724
1725    doc.add_paragraph("");
1726
1727    // ── Subject ──
1728    {
1729        let mut p = doc.add_paragraph("");
1730        p.add_run("Re: Strategic Technology Partnership — Phase 2 Expansion")
1731            .bold(true);
1732    }
1733
1734    doc.add_paragraph("");
1735
1736    // ── Body ──
1737    doc.add_paragraph("Dear Ms. Chen,");
1738    doc.add_paragraph("");
1739
1740    doc.add_paragraph(
1741        "I am writing to express Tensorbee's enthusiasm for expanding our strategic technology \
1742         partnership with Meridian Dynamics. Following the successful completion of Phase 1, \
1743         which delivered a 40% improvement in your ML inference pipeline throughput, we believe \
1744         the foundation is firmly established for an ambitious Phase 2 engagement.",
1745    )
1746    .first_line_indent(Length::inches(0.5));
1747
1748    doc.add_paragraph(
1749        "During our recent executive review, your team highlighted three priority areas for \
1750         the next phase of collaboration:",
1751    )
1752    .first_line_indent(Length::inches(0.5));
1753
1754    doc.add_numbered_list_item(
1755        "Real-time anomaly detection for your financial transaction monitoring system, \
1756         targeting sub-100ms latency at 50,000 transactions per second.",
1757        0,
1758    );
1759    doc.add_numbered_list_item(
1760        "Federated learning infrastructure to enable model training across your distributed \
1761         data centers without centralizing sensitive financial data.",
1762        0,
1763    );
1764    doc.add_numbered_list_item(
1765        "MLOps automation to reduce your model deployment cycle from the current 5 days \
1766         to under 4 hours.",
1767        0,
1768    );
1769
1770    doc.add_paragraph(
1771        "Our engineering team has prepared a detailed technical proposal addressing each of \
1772         these areas. We have allocated a dedicated team of eight senior engineers, led by \
1773         our CTO, to ensure continuity with the Phase 1 team your organization has already \
1774         built a productive working relationship with.",
1775    )
1776    .first_line_indent(Length::inches(0.5));
1777
1778    doc.add_paragraph(
1779        "I would welcome the opportunity to present our Phase 2 proposal in person. My \
1780         assistant will reach out to coordinate a meeting at your convenience during the \
1781         first week of March.",
1782    )
1783    .first_line_indent(Length::inches(0.5));
1784
1785    doc.add_paragraph("");
1786
1787    doc.add_paragraph("Warm regards,");
1788    doc.add_paragraph("");
1789    doc.add_paragraph("");
1790
1791    // Signature line
1792    doc.add_paragraph("")
1793        .border_bottom(BorderStyle::Single, 4, "4A5568");
1794    {
1795        let mut p = doc.add_paragraph("");
1796        p.add_run("Walter White").bold(true).size(12.0);
1797    }
1798    {
1799        let mut p = doc.add_paragraph("");
1800        p.add_run("Chief Executive Officer, Tensorbee")
1801            .italic(true)
1802            .size(10.0)
1803            .color("666666");
1804    }
1805    {
1806        let mut p = doc.add_paragraph("");
1807        p.add_run("walter@tensorbee.com | +1 (415) 555-0199")
1808            .size(10.0)
1809            .color("888888");
1810    }
1811
1812    doc
1813}
1814
1815// =============================================================================
1816// 7. EMPLOYMENT CONTRACT — Purple + charcoal scheme
1817// =============================================================================
1818fn generate_contract(_samples_dir: &Path) -> Document {
1819    let mut doc = Document::new();
1820
1821    // Colors: Purple #5B2C6F, Plum #8E44AD, Gray #2C3E50
1822    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1823    doc.set_margins(
1824        Length::inches(1.25),
1825        Length::inches(1.0),
1826        Length::inches(1.0),
1827        Length::inches(1.0),
1828    );
1829    doc.set_title("Employment Agreement");
1830    doc.set_author("Tensorbee HR Department");
1831    doc.set_subject("Employment Contract — Walter White");
1832    doc.set_keywords("employment, contract, agreement, Tensorbee");
1833
1834    doc.set_header("TENSORBEE — Employment Agreement");
1835    doc.set_footer("Employment Agreement — Walter White — Page");
1836
1837    // ── Title Block ──
1838    doc.add_paragraph("")
1839        .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1840    {
1841        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1842        p.add_run("EMPLOYMENT AGREEMENT")
1843            .bold(true)
1844            .size(24.0)
1845            .color("5B2C6F")
1846            .font("Georgia");
1847    }
1848    doc.add_paragraph("")
1849        .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1850    doc.add_paragraph("");
1851
1852    // ── Parties ──
1853    doc.add_paragraph(
1854        "This Employment Agreement (\"Agreement\") is entered into as of February 22, 2026 \
1855         (\"Effective Date\"), by and between:",
1856    );
1857
1858    doc.add_paragraph("");
1859
1860    {
1861        let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1862        p.add_run("EMPLOYER: ").bold(true);
1863        p.add_run(
1864            "Tensorbee, Inc., a Delaware corporation with its principal place of business at \
1865                   123 Innovation Drive, Suite 400, San Francisco, CA 94105 (\"Company\")",
1866        );
1867    }
1868    doc.add_paragraph("");
1869    {
1870        let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1871        p.add_run("EMPLOYEE: ").bold(true);
1872        p.add_run(
1873            "Walter White, residing at 308 Negra Arroyo Lane, Albuquerque, NM 87104 (\"Employee\")",
1874        );
1875    }
1876
1877    doc.add_paragraph("");
1878    doc.add_paragraph("The Company and Employee are collectively referred to as the \"Parties.\"");
1879    doc.add_paragraph("");
1880
1881    // ── Article 1: Position and Duties ──
1882    doc.add_paragraph("Article 1 — Position and Duties")
1883        .style("Heading1");
1884    {
1885        let mut p = doc.add_paragraph("");
1886        p.add_run("1.1 ").bold(true);
1887        p.add_run("The Company hereby employs the Employee as ");
1888        p.add_run("Chief Executive Officer (CEO)")
1889            .bold(true)
1890            .italic(true);
1891        p.add_run(", reporting directly to the Board of Directors.");
1892    }
1893    {
1894        let mut p = doc.add_paragraph("");
1895        p.add_run("1.2 ").bold(true);
1896        p.add_run("The Employee shall devote full working time, attention, and best efforts to \
1897                   the performance of duties as reasonably assigned by the Board, including but not limited to:");
1898    }
1899    doc.add_bullet_list_item(
1900        "Setting and executing the Company's strategic vision and business plan",
1901        0,
1902    );
1903    doc.add_bullet_list_item(
1904        "Overseeing all operations, engineering, and commercial activities",
1905        0,
1906    );
1907    doc.add_bullet_list_item(
1908        "Representing the Company to investors, customers, and the public",
1909        0,
1910    );
1911    doc.add_bullet_list_item("Recruiting, developing, and retaining key talent", 0);
1912
1913    {
1914        let mut p = doc.add_paragraph("");
1915        p.add_run("1.3 ").bold(true);
1916        p.add_run(
1917            "The Employee's primary work location shall be the Company's San Francisco \
1918                   headquarters, with reasonable travel as required by business needs.",
1919        );
1920    }
1921
1922    // ── Article 2: Compensation ──
1923    doc.add_paragraph("Article 2 — Compensation and Benefits")
1924        .style("Heading1");
1925    {
1926        let mut p = doc.add_paragraph("");
1927        p.add_run("2.1 Base Salary. ").bold(true);
1928        p.add_run("The Company shall pay the Employee an annual base salary of ");
1929        p.add_run("$375,000.00").bold(true);
1930        p.add_run(" (Three Hundred Seventy-Five Thousand Dollars), payable in accordance with the \
1931                   Company's standard payroll schedule, less applicable withholdings and deductions.");
1932    }
1933    {
1934        let mut p = doc.add_paragraph("");
1935        p.add_run("2.2 Annual Bonus. ").bold(true);
1936        p.add_run("The Employee shall be eligible for an annual performance bonus of up to ");
1937        p.add_run("40%").bold(true);
1938        p.add_run(
1939            " of base salary, based on achievement of mutually agreed performance objectives.",
1940        );
1941    }
1942    {
1943        let mut p = doc.add_paragraph("");
1944        p.add_run("2.3 Equity. ").bold(true);
1945        p.add_run("Subject to Board approval, the Employee shall receive a stock option grant of ");
1946        p.add_run("500,000 shares").bold(true);
1947        p.add_run(
1948            " of the Company's common stock, vesting over four (4) years with a one-year cliff, \
1949                   at an exercise price equal to the fair market value on the date of grant.",
1950        );
1951    }
1952
1953    // Compensation summary table
1954    doc.add_paragraph("");
1955    {
1956        let mut tbl = doc
1957            .add_table(5, 2)
1958            .borders(BorderStyle::Single, 4, "5B2C6F")
1959            .width_pct(70.0)
1960            .alignment(Alignment::Center);
1961        tbl.cell(0, 0).unwrap().set_text("Compensation Element");
1962        tbl.cell(0, 0).unwrap().shading("5B2C6F");
1963        tbl.cell(0, 1).unwrap().set_text("Value");
1964        tbl.cell(0, 1).unwrap().shading("5B2C6F");
1965        let items = [
1966            ("Base Salary", "$375,000/year"),
1967            ("Target Bonus", "Up to 40% ($150,000)"),
1968            ("Equity Grant", "500,000 shares (4yr vest)"),
1969            ("Total Target Comp", "$525,000 + equity"),
1970        ];
1971        for (i, (elem, val)) in items.iter().enumerate() {
1972            tbl.cell(i + 1, 0).unwrap().set_text(elem);
1973            tbl.cell(i + 1, 1).unwrap().set_text(val);
1974            if i == 3 {
1975                tbl.cell(i + 1, 0).unwrap().shading("E8DAEF");
1976                tbl.cell(i + 1, 1).unwrap().shading("E8DAEF");
1977            }
1978        }
1979    }
1980
1981    // ── Article 3: Benefits ──
1982    doc.add_paragraph("").page_break_before(true);
1983    doc.add_paragraph("Article 3 — Benefits").style("Heading1");
1984    {
1985        let mut p = doc.add_paragraph("");
1986        p.add_run("3.1 ").bold(true);
1987        p.add_run(
1988            "The Employee shall be entitled to participate in all benefit programs \
1989                   generally available to senior executives, including:",
1990        );
1991    }
1992    doc.add_bullet_list_item(
1993        "Medical, dental, and vision insurance (100% premium coverage for employee and dependents)",
1994        0,
1995    );
1996    doc.add_bullet_list_item("401(k) retirement plan with 6% company match", 0);
1997    doc.add_bullet_list_item("Life insurance and long-term disability coverage", 0);
1998    doc.add_bullet_list_item("Annual professional development allowance of $10,000", 0);
1999
2000    {
2001        let mut p = doc.add_paragraph("");
2002        p.add_run("3.2 Paid Time Off. ").bold(true);
2003        p.add_run(
2004            "The Employee shall receive 25 days of paid vacation per year, plus Company holidays, \
2005                   accruing on a monthly basis.",
2006        );
2007    }
2008
2009    // ── Article 4: Term and Termination ──
2010    doc.add_paragraph("Article 4 — Term and Termination")
2011        .style("Heading1");
2012    {
2013        let mut p = doc.add_paragraph("");
2014        p.add_run("4.1 At-Will Employment. ").bold(true);
2015        p.add_run(
2016            "This Agreement is for at-will employment and may be terminated by either Party \
2017                   at any time, with or without cause, subject to the notice provisions herein.",
2018        );
2019    }
2020    {
2021        let mut p = doc.add_paragraph("");
2022        p.add_run("4.2 Notice Period. ").bold(true);
2023        p.add_run("Either Party shall provide ");
2024        p.add_run("ninety (90) days").bold(true);
2025        p.add_run(" written notice of termination, or pay in lieu thereof.");
2026    }
2027    {
2028        let mut p = doc.add_paragraph("");
2029        p.add_run("4.3 Severance. ").bold(true);
2030        p.add_run("In the event of termination by the Company without Cause, the Employee shall \
2031                   receive (a) twelve (12) months of base salary continuation, (b) pro-rata bonus \
2032                   for the year of termination, and (c) twelve (12) months of COBRA premium coverage.");
2033    }
2034
2035    // ── Article 5: Confidentiality ──
2036    doc.add_paragraph("Article 5 — Confidentiality and IP")
2037        .style("Heading1");
2038    {
2039        let mut p = doc.add_paragraph("");
2040        p.add_run("5.1 ").bold(true);
2041        p.add_run(
2042            "The Employee agrees to maintain strict confidentiality of all proprietary \
2043                   information, trade secrets, and business plans of the Company during and after \
2044                   employment.",
2045        );
2046    }
2047    {
2048        let mut p = doc.add_paragraph("");
2049        p.add_run("5.2 ").bold(true);
2050        p.add_run(
2051            "All intellectual property, inventions, and works of authorship created by the \
2052                   Employee during the term of employment and within the scope of duties shall be \
2053                   the sole and exclusive property of the Company.",
2054        );
2055    }
2056
2057    // ── Article 6: Non-Compete ──
2058    doc.add_paragraph("Article 6 — Non-Competition")
2059        .style("Heading1");
2060    {
2061        let mut p = doc.add_paragraph("");
2062        p.add_run("6.1 ").bold(true);
2063        p.add_run("For a period of twelve (12) months following termination, the Employee agrees \
2064                   not to engage in any business that directly competes with the Company's core \
2065                   business of AI infrastructure and ML operations services, within the United States.");
2066    }
2067
2068    // ── Article 7: General Provisions ──
2069    doc.add_paragraph("Article 7 — General Provisions")
2070        .style("Heading1");
2071    {
2072        let mut p = doc.add_paragraph("");
2073        p.add_run("7.1 Governing Law. ").bold(true);
2074        p.add_run(
2075            "This Agreement shall be governed by and construed in accordance with the laws of \
2076                   the State of California.",
2077        );
2078    }
2079    {
2080        let mut p = doc.add_paragraph("");
2081        p.add_run("7.2 Entire Agreement. ").bold(true);
2082        p.add_run(
2083            "This Agreement constitutes the entire agreement between the Parties and supersedes \
2084                   all prior negotiations, representations, and agreements.",
2085        );
2086    }
2087    {
2088        let mut p = doc.add_paragraph("");
2089        p.add_run("7.3 Amendment. ").bold(true);
2090        p.add_run(
2091            "This Agreement may only be amended by a written instrument signed by both Parties.",
2092        );
2093    }
2094
2095    // ── Signature Block ──
2096    doc.add_paragraph("").page_break_before(true);
2097    doc.add_paragraph("IN WITNESS WHEREOF, the Parties have executed this Employment Agreement as of the Effective Date.")
2098        .space_after(Length::pt(24.0));
2099
2100    // Signature table
2101    {
2102        let mut tbl = doc.add_table(6, 2).width_pct(100.0);
2103        tbl.cell(0, 0).unwrap().set_text("FOR THE COMPANY:");
2104        tbl.cell(0, 0).unwrap().shading("5B2C6F");
2105        tbl.cell(0, 1).unwrap().set_text("EMPLOYEE:");
2106        tbl.cell(0, 1).unwrap().shading("5B2C6F");
2107
2108        tbl.cell(1, 0).unwrap().set_text("");
2109        tbl.cell(1, 1).unwrap().set_text("");
2110        tbl.row(1).unwrap().height(Length::pt(40.0));
2111
2112        tbl.cell(2, 0)
2113            .unwrap()
2114            .set_text("Signature: ___________________________");
2115        tbl.cell(2, 1)
2116            .unwrap()
2117            .set_text("Signature: ___________________________");
2118        tbl.cell(3, 0)
2119            .unwrap()
2120            .set_text("Name: Board Representative");
2121        tbl.cell(3, 1).unwrap().set_text("Name: Walter White");
2122        tbl.cell(4, 0)
2123            .unwrap()
2124            .set_text("Title: Chair, Board of Directors");
2125        tbl.cell(4, 1)
2126            .unwrap()
2127            .set_text("Title: Chief Executive Officer");
2128        tbl.cell(5, 0).unwrap().set_text("Date: _______________");
2129        tbl.cell(5, 1).unwrap().set_text("Date: _______________");
2130    }
2131
2132    doc
2133}
Source

pub fn keywords(&self) -> Option<&str>

Get the document keywords.

Source

pub fn set_keywords(&mut self, keywords: &str)

Set the document keywords.

Examples found in repository?
examples/template_replace.rs (line 242)
162fn fill_template(template_path: &Path, output_path: &Path) {
163    let mut doc = Document::open(template_path).unwrap();
164
165    // ── Batch replacement ──
166    let mut replacements = HashMap::new();
167    replacements.insert("{{company_name}}", "Riverside Medical Center");
168    replacements.insert("{{project_name}}", "Network Security Upgrade");
169    replacements.insert("{{contact_name}}", "Dr. Sarah Chen");
170    replacements.insert("{{contact_email}}", "s.chen@riverside.org");
171    replacements.insert("{{start_date}}", "March 1, 2026");
172    replacements.insert("{{duration}}", "12 weeks");
173    replacements.insert("{{budget}}", "$185,000");
174    replacements.insert("{{status}}", "Pending Approval");
175    replacements.insert("{{author_name}}", "James Wilson");
176    replacements.insert("{{date}}", "February 22, 2026");
177
178    // Team members
179    replacements.insert("{{member1_name}}", "James Wilson");
180    replacements.insert("{{member1_role}}", "Project Lead");
181    replacements.insert("{{member1_email}}", "j.wilson@provider.com");
182    replacements.insert("{{member2_name}}", "Maria Garcia");
183    replacements.insert("{{member2_role}}", "Security Architect");
184    replacements.insert("{{member2_email}}", "m.garcia@provider.com");
185    replacements.insert("{{member3_name}}", "David Park");
186    replacements.insert("{{member3_role}}", "Network Engineer");
187    replacements.insert("{{member3_email}}", "d.park@provider.com");
188
189    let count = doc.replace_all(&replacements);
190    println!("  Replaced {} placeholders", count);
191
192    // ── Insert deliverables at the insertion point ──
193    if let Some(idx) = doc.find_content_index("INSERTION_POINT") {
194        // Remove the placeholder paragraph
195        doc.remove_content(idx);
196
197        // Insert deliverables list
198        doc.insert_paragraph(idx, "The following deliverables are included:");
199
200        // Insert a deliverables table
201        let mut tbl = doc.insert_table(idx + 1, 5, 3);
202        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
203
204        for col in 0..3 {
205            tbl.cell(0, col).unwrap().shading("E2EFDA");
206        }
207        tbl.cell(0, 0).unwrap().set_text("Phase");
208        tbl.cell(0, 1).unwrap().set_text("Description");
209        tbl.cell(0, 2).unwrap().set_text("Timeline");
210
211        tbl.cell(1, 0).unwrap().set_text("1. Discovery");
212        tbl.cell(1, 1)
213            .unwrap()
214            .set_text("Network assessment and asset inventory");
215        tbl.cell(1, 2).unwrap().set_text("Weeks 1-3");
216
217        tbl.cell(2, 0).unwrap().set_text("2. Design");
218        tbl.cell(2, 1)
219            .unwrap()
220            .set_text("Security architecture and policy design");
221        tbl.cell(2, 2).unwrap().set_text("Weeks 4-6");
222
223        tbl.cell(3, 0).unwrap().set_text("3. Implementation");
224        tbl.cell(3, 1)
225            .unwrap()
226            .set_text("Deploy monitoring and access controls");
227        tbl.cell(3, 2).unwrap().set_text("Weeks 7-10");
228
229        tbl.cell(4, 0).unwrap().set_text("4. Validation");
230        tbl.cell(4, 1)
231            .unwrap()
232            .set_text("Testing, training, and handover");
233        tbl.cell(4, 2).unwrap().set_text("Weeks 11-12");
234
235        println!("  Inserted deliverables table at position {}", idx);
236    }
237
238    // ── Update metadata ──
239    doc.set_title("Riverside Medical Center — Network Security Upgrade Proposal");
240    doc.set_author("James Wilson");
241    doc.set_subject("Project Proposal");
242    doc.set_keywords("security, network, medical, proposal");
243
244    doc.save(output_path).unwrap();
245}
More examples
Hide additional examples
examples/generate_all_samples.rs (line 113)
97fn generate_feature_showcase(_samples_dir: &Path) -> Document {
98    let mut doc = Document::new();
99
100    // ── Page Setup & Metadata ──
101    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
102    doc.set_margins(
103        Length::inches(1.0),
104        Length::inches(1.0),
105        Length::inches(1.0),
106        Length::inches(1.0),
107    );
108    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
109    doc.set_gutter(Length::twips(0));
110    doc.set_title("rdocx Feature Showcase");
111    doc.set_author("rdocx Sample Generator");
112    doc.set_subject("Comprehensive feature demonstration");
113    doc.set_keywords("rdocx, docx, rust, sample, showcase");
114
115    // Headers & Footers with different first page
116    doc.set_different_first_page(true);
117    doc.set_first_page_header("rdocx");
118    doc.set_first_page_footer("Feature Showcase — Cover Page");
119    doc.set_header("rdocx Feature Showcase");
120    doc.set_footer("Generated by rdocx");
121
122    // ── COVER PAGE ──
123    let bg = create_sample_png(612, 792, [20, 45, 90]);
124    doc.add_background_image(&bg, "cover_bg.png");
125
126    for _ in 0..3 {
127        doc.add_paragraph("");
128    }
129    {
130        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
131        p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
132    }
133    {
134        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
135        p.add_run("Complete Feature Showcase")
136            .size(28.0)
137            .color("FFFFFF")
138            .italic(true);
139    }
140    doc.add_paragraph("");
141    {
142        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
143        p.add_run("Every feature of the rdocx Rust library")
144            .size(13.0)
145            .color("B0C4DE");
146    }
147    {
148        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
149        p.add_run("demonstrated in a single document.")
150            .size(13.0)
151            .color("B0C4DE");
152    }
153
154    // ── TABLE OF CONTENTS ──
155    doc.add_paragraph("").page_break_before(true);
156    doc.insert_toc(doc.content_count(), 3);
157
158    // ── SECTION 1: TEXT FORMATTING ──
159    doc.add_paragraph("").page_break_before(true);
160    doc.add_paragraph("1. Text Formatting").style("Heading1");
161
162    // Paragraph Alignment
163    doc.add_paragraph("1.1 Paragraph Alignment")
164        .style("Heading2");
165    doc.add_paragraph("Left-aligned paragraph (default).")
166        .alignment(Alignment::Left);
167    doc.add_paragraph("Center-aligned paragraph.")
168        .alignment(Alignment::Center);
169    doc.add_paragraph("Right-aligned paragraph.")
170        .alignment(Alignment::Right);
171    doc.add_paragraph(
172        "Justified paragraph — this text is long enough to demonstrate how justified alignment \
173         distributes extra space across word gaps so lines fill the full width of the text area.",
174    )
175    .alignment(Alignment::Justify);
176
177    doc.add_paragraph("");
178
179    // Run Formatting
180    doc.add_paragraph("1.2 Run Formatting").style("Heading2");
181    {
182        let mut p = doc.add_paragraph("");
183        p.add_run("Bold").bold(true);
184        p.add_run(" | ");
185        p.add_run("Italic").italic(true);
186        p.add_run(" | ");
187        p.add_run("Bold Italic").bold(true).italic(true);
188        p.add_run(" | ");
189        p.add_run("Underline").underline(true);
190        p.add_run(" | ");
191        p.add_run("Strikethrough").strike(true);
192        p.add_run(" | ");
193        p.add_run("Double Strike").double_strike(true);
194    }
195    {
196        let mut p = doc.add_paragraph("");
197        p.add_run("Red").color("FF0000");
198        p.add_run(" | ");
199        p.add_run("Blue").color("0000FF");
200        p.add_run(" | ");
201        p.add_run("Green").color("00AA00");
202        p.add_run(" | ");
203        p.add_run("Highlighted").highlight("FFFF00");
204    }
205    {
206        let mut p = doc.add_paragraph("");
207        p.add_run("8pt").size(8.0);
208        p.add_run(" | ");
209        p.add_run("11pt").size(11.0);
210        p.add_run(" | ");
211        p.add_run("16pt").size(16.0);
212        p.add_run(" | ");
213        p.add_run("24pt").size(24.0);
214    }
215    {
216        let mut p = doc.add_paragraph("");
217        p.add_run("Arial").font("Arial");
218        p.add_run(" | ");
219        p.add_run("Times New Roman").font("Times New Roman");
220        p.add_run(" | ");
221        p.add_run("Courier New").font("Courier New");
222    }
223    {
224        let mut p = doc.add_paragraph("");
225        p.add_run("H");
226        p.add_run("2").subscript();
227        p.add_run("O (subscript) | E=mc");
228        p.add_run("2").superscript();
229        p.add_run(" (superscript)");
230    }
231    {
232        let mut p = doc.add_paragraph("");
233        p.add_run("ALL CAPS").all_caps(true);
234        p.add_run(" | ");
235        p.add_run("Small Caps").small_caps(true);
236        p.add_run(" | ");
237        p.add_run("Expanded").character_spacing(Length::twips(40));
238        p.add_run(" | ");
239        p.add_run("Hidden text (not visible)").hidden(true);
240    }
241
242    doc.add_paragraph("");
243
244    // Underline Styles
245    doc.add_paragraph("1.3 Underline Styles").style("Heading2");
246    {
247        let mut p = doc.add_paragraph("");
248        p.add_run("Single ")
249            .underline_style(rdocx::UnderlineStyle::Single);
250        p.add_run("Double ")
251            .underline_style(rdocx::UnderlineStyle::Double);
252        p.add_run("Thick ")
253            .underline_style(rdocx::UnderlineStyle::Thick);
254        p.add_run("Dotted ")
255            .underline_style(rdocx::UnderlineStyle::Dotted);
256        p.add_run("Dash ")
257            .underline_style(rdocx::UnderlineStyle::Dash);
258        p.add_run("Wave ")
259            .underline_style(rdocx::UnderlineStyle::Wave);
260    }
261
262    // ── SECTION 2: PARAGRAPH FORMATTING ──
263    doc.add_paragraph("").page_break_before(true);
264    doc.add_paragraph("2. Paragraph Formatting")
265        .style("Heading1");
266
267    doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
268    doc.add_paragraph("Paragraph with light green shading")
269        .shading("E2EFDA");
270    doc.add_paragraph("Paragraph with bottom border")
271        .border_bottom(BorderStyle::Single, 6, "2E75B6");
272    doc.add_paragraph("Paragraph with all borders (red)")
273        .border_all(BorderStyle::Single, 4, "FF0000");
274
275    doc.add_paragraph("2.2 Indentation").style("Heading2");
276    doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
277        .indent_left(Length::inches(1.0))
278        .hanging_indent(Length::inches(0.5));
279    doc.add_paragraph("0.5-inch first-line indent on this paragraph")
280        .first_line_indent(Length::inches(0.5));
281    doc.add_paragraph("Both left and right indent (0.75 inches each)")
282        .indent_left(Length::inches(0.75))
283        .indent_right(Length::inches(0.75));
284
285    doc.add_paragraph("2.3 Spacing & Line Height")
286        .style("Heading2");
287    doc.add_paragraph("Extra space before (24pt) and after (12pt)")
288        .space_before(Length::pt(24.0))
289        .space_after(Length::pt(12.0));
290    doc.add_paragraph(
291        "Double line spacing paragraph. This text should have extra vertical space between \
292         lines to demonstrate line_spacing_multiple(2.0).",
293    )
294    .line_spacing_multiple(2.0);
295    doc.add_paragraph("Exact 20pt line spacing (fixed height).")
296        .line_spacing(20.0);
297
298    doc.add_paragraph("2.4 Pagination Controls")
299        .style("Heading2");
300    doc.add_paragraph("keep_with_next — this paragraph stays with the next")
301        .keep_with_next(true);
302    doc.add_paragraph("(This paragraph was kept with the one above.)");
303    doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
304        .keep_together(true);
305    doc.add_paragraph("widow_control — prevents widow/orphan lines")
306        .widow_control(true);
307
308    // ── SECTION 3: LISTS ──
309    doc.add_paragraph("").page_break_before(true);
310    doc.add_paragraph("3. Lists").style("Heading1");
311
312    doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
313    doc.add_bullet_list_item("First item", 0);
314    doc.add_bullet_list_item("Second item", 0);
315    doc.add_bullet_list_item("Nested level 1", 1);
316    doc.add_bullet_list_item("Nested level 2", 2);
317    doc.add_bullet_list_item("Back to level 1", 1);
318    doc.add_bullet_list_item("Third item", 0);
319
320    doc.add_paragraph("");
321
322    doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
323    doc.add_numbered_list_item("First numbered item", 0);
324    doc.add_numbered_list_item("Second numbered item", 0);
325    doc.add_numbered_list_item("Sub-item A", 1);
326    doc.add_numbered_list_item("Sub-item B", 1);
327    doc.add_numbered_list_item("Third numbered item", 0);
328
329    // ── SECTION 4: TAB STOPS ──
330    doc.add_paragraph("");
331    doc.add_paragraph("4. Tab Stops").style("Heading1");
332
333    doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
334    doc.add_paragraph("Left\tCenter\tRight\tDecimal")
335        .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
336        .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
337        .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
338        .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
339
340    doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
341    doc.add_paragraph("Item\t\tPrice")
342        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
343        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
344        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
345    doc.add_paragraph("Widget\t\t$19.99")
346        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
347        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
348        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
349    doc.add_paragraph("Gadget\t\t$249.50")
350        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
351        .add_tab_stop_with_leader(
352            TabAlignment::Right,
353            Length::inches(4.0),
354            TabLeader::Underscore,
355        )
356        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
357
358    // ── SECTION 5: TABLES ──
359    doc.add_paragraph("").page_break_before(true);
360    doc.add_paragraph("5. Tables").style("Heading1");
361
362    doc.add_paragraph("5.1 Basic Table").style("Heading2");
363    {
364        let mut tbl = doc
365            .add_table(4, 3)
366            .borders(BorderStyle::Single, 4, "000000");
367        for col in 0..3 {
368            tbl.cell(0, col).unwrap().shading("2E75B6");
369        }
370        tbl.cell(0, 0).unwrap().set_text("Name");
371        tbl.cell(0, 1).unwrap().set_text("Role");
372        tbl.cell(0, 2).unwrap().set_text("Location");
373        tbl.cell(1, 0).unwrap().set_text("Walter White");
374        tbl.cell(1, 1).unwrap().set_text("CEO");
375        tbl.cell(1, 2).unwrap().set_text("Albuquerque");
376        tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
377        tbl.cell(2, 1).unwrap().set_text("CTO");
378        tbl.cell(2, 2).unwrap().set_text("Remote");
379        tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
380        tbl.cell(3, 1).unwrap().set_text("Security");
381        tbl.cell(3, 2).unwrap().set_text("Washington");
382    }
383
384    doc.add_paragraph("");
385
386    doc.add_paragraph("5.2 Column Span & Cell Shading")
387        .style("Heading2");
388    {
389        let mut tbl = doc
390            .add_table(3, 4)
391            .borders(BorderStyle::Single, 4, "000000")
392            .width_pct(100.0);
393        tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
394        tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
395        tbl.cell(1, 0).unwrap().set_text("Region");
396        tbl.cell(1, 0).unwrap().shading("D6E4F0");
397        tbl.cell(1, 1).unwrap().set_text("Q1");
398        tbl.cell(1, 1).unwrap().shading("D6E4F0");
399        tbl.cell(1, 2).unwrap().set_text("Q2");
400        tbl.cell(1, 2).unwrap().shading("D6E4F0");
401        tbl.cell(1, 3).unwrap().set_text("Total");
402        tbl.cell(1, 3).unwrap().shading("D6E4F0");
403        tbl.cell(2, 0).unwrap().set_text("Americas");
404        tbl.cell(2, 1).unwrap().set_text("$2.4M");
405        tbl.cell(2, 2).unwrap().set_text("$2.7M");
406        tbl.cell(2, 3).unwrap().set_text("$5.1M");
407    }
408
409    doc.add_paragraph("");
410
411    doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
412    {
413        let mut tbl = doc
414            .add_table(4, 3)
415            .borders(BorderStyle::Single, 4, "000000");
416        tbl.cell(0, 0).unwrap().set_text("Category");
417        tbl.cell(0, 0).unwrap().shading("E2EFDA");
418        tbl.cell(0, 1).unwrap().set_text("Item");
419        tbl.cell(0, 1).unwrap().shading("E2EFDA");
420        tbl.cell(0, 2).unwrap().set_text("Price");
421        tbl.cell(0, 2).unwrap().shading("E2EFDA");
422        tbl.cell(1, 0).unwrap().set_text("Hardware");
423        tbl.cell(1, 0).unwrap().v_merge_restart();
424        tbl.cell(1, 1).unwrap().set_text("Laptop");
425        tbl.cell(1, 2).unwrap().set_text("$1,200");
426        tbl.cell(2, 0).unwrap().v_merge_continue();
427        tbl.cell(2, 1).unwrap().set_text("Monitor");
428        tbl.cell(2, 2).unwrap().set_text("$450");
429        tbl.cell(3, 0).unwrap().set_text("Software");
430        tbl.cell(3, 1).unwrap().set_text("IDE License");
431        tbl.cell(3, 2).unwrap().set_text("$200/yr");
432    }
433
434    doc.add_paragraph("");
435
436    doc.add_paragraph("5.4 Nested Table").style("Heading2");
437    {
438        let mut tbl = doc
439            .add_table(2, 2)
440            .borders(BorderStyle::Single, 6, "2E75B6");
441        tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
442        tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
443        tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
444        {
445            let mut cell = tbl.cell(1, 1).unwrap();
446            cell.set_text("Nested table below:");
447            let mut nested = cell
448                .add_table(2, 2)
449                .borders(BorderStyle::Single, 2, "FF6600");
450            nested.cell(0, 0).unwrap().set_text("A");
451            nested.cell(0, 1).unwrap().set_text("B");
452            nested.cell(1, 0).unwrap().set_text("C");
453            nested.cell(1, 1).unwrap().set_text("D");
454        }
455    }
456
457    doc.add_paragraph("");
458
459    doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
460        .style("Heading2");
461    {
462        let mut tbl = doc
463            .add_table(2, 3)
464            .borders(BorderStyle::Single, 4, "666666");
465        tbl.row(0).unwrap().height(Length::pt(50.0)).header();
466        tbl.cell(0, 0).unwrap().set_text("Top");
467        tbl.cell(0, 0)
468            .unwrap()
469            .vertical_alignment(VerticalAlignment::Top)
470            .shading("FFF2CC");
471        tbl.cell(0, 1).unwrap().set_text("Center");
472        tbl.cell(0, 1)
473            .unwrap()
474            .vertical_alignment(VerticalAlignment::Center)
475            .shading("D9E2F3");
476        tbl.cell(0, 2).unwrap().set_text("Bottom");
477        tbl.cell(0, 2)
478            .unwrap()
479            .vertical_alignment(VerticalAlignment::Bottom)
480            .shading("E2EFDA");
481        tbl.row(1).unwrap().cant_split();
482        tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
483        tbl.cell(1, 0).unwrap().no_wrap();
484        tbl.cell(1, 1).unwrap().set_text("Fixed width");
485        tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
486        tbl.cell(1, 2).unwrap().set_text("Normal");
487    }
488
489    // ── SECTION 6: IMAGES ──
490    doc.add_paragraph("").page_break_before(true);
491    doc.add_paragraph("6. Images").style("Heading1");
492
493    doc.add_paragraph("6.1 Inline Image").style("Heading2");
494    doc.add_paragraph("A blue gradient image below:");
495    let img = create_sample_png(200, 50, [0, 80, 200]);
496    doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
497
498    doc.add_paragraph("");
499    doc.add_paragraph("6.2 Header Image").style("Heading2");
500    let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
501    doc.set_header_image(
502        &hdr_img,
503        "header_logo.png",
504        Length::inches(2.0),
505        Length::inches(0.2),
506    );
507    doc.add_paragraph("The header now contains an inline image (check the top of this page).");
508
509    doc.add_paragraph("");
510    doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
511
512    // ── SECTION 7: CONTENT MANIPULATION ──
513    doc.add_paragraph("").page_break_before(true);
514    doc.add_paragraph("7. Content Manipulation")
515        .style("Heading1");
516
517    doc.add_paragraph("7.1 Placeholder Replacement")
518        .style("Heading2");
519    doc.add_paragraph("Customer: {{customer}}");
520    doc.add_paragraph("Date: {{date}}");
521    doc.add_paragraph("Reference: {{ref_number}}");
522    {
523        let mut tbl = doc
524            .add_table(2, 2)
525            .borders(BorderStyle::Single, 4, "000000");
526        tbl.cell(0, 0).unwrap().set_text("Project");
527        tbl.cell(0, 0).unwrap().shading("D6E4F0");
528        tbl.cell(0, 1).unwrap().set_text("{{project}}");
529        tbl.cell(1, 0).unwrap().set_text("Status");
530        tbl.cell(1, 0).unwrap().shading("D6E4F0");
531        tbl.cell(1, 1).unwrap().set_text("{{status}}");
532    }
533
534    let mut replacements = HashMap::new();
535    replacements.insert("{{customer}}", "Tensorbee Inc.");
536    replacements.insert("{{date}}", "February 22, 2026");
537    replacements.insert("{{ref_number}}", "TB-2026-001");
538    replacements.insert("{{project}}", "Infrastructure Upgrade");
539    replacements.insert("{{status}}", "In Progress");
540    let n = doc.replace_all(&replacements);
541    doc.add_paragraph(&format!("({n} placeholders replaced above)"));
542
543    doc.add_paragraph("");
544
545    doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
546    doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
547    let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
548    doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
549
550    doc.add_paragraph("");
551
552    doc.add_paragraph("7.3 Content Insertion").style("Heading2");
553    doc.add_paragraph("Section A: First content.");
554    doc.add_paragraph("Section C: Third content.");
555    if let Some(idx) = doc.find_content_index("Section C") {
556        doc.insert_paragraph(
557            idx,
558            "Section B: Inserted between A and C via find_content_index().",
559        );
560    }
561
562    // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
563    doc.add_paragraph("").section_break(SectionBreak::NextPage);
564    doc.add_paragraph("8. Section Breaks & Orientation")
565        .style("Heading1");
566    doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
567
568    {
569        let mut tbl = doc
570            .add_table(3, 7)
571            .borders(BorderStyle::Single, 4, "2E75B6");
572        let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
573        for (c, h) in headers.iter().enumerate() {
574            tbl.cell(0, c).unwrap().set_text(h);
575            tbl.cell(0, c).unwrap().shading("2E75B6");
576        }
577        let data = [
578            [
579                "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
580            ],
581            [
582                "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
583            ],
584        ];
585        for (r, row) in data.iter().enumerate() {
586            for (c, v) in row.iter().enumerate() {
587                tbl.cell(r + 1, c).unwrap().set_text(v);
588            }
589        }
590    }
591
592    doc.add_paragraph("")
593        .section_break(SectionBreak::NextPage)
594        .section_landscape();
595
596    // ── SECTION 9: CUSTOM STYLES ──
597    doc.add_paragraph("9. Custom Styles").style("Heading1");
598    doc.add_style(
599        StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
600            .based_on("Normal")
601            .paragraph_properties(rdocx_oxml::properties::CT_PPr {
602                shading: Some(rdocx_oxml::properties::CT_Shd {
603                    val: "clear".to_string(),
604                    color: None,
605                    fill: Some("FFF2CC".to_string()),
606                }),
607                ..Default::default()
608            })
609            .run_properties(rdocx_oxml::properties::CT_RPr {
610                bold: Some(true),
611                color: Some("C45911".to_string()),
612                ..Default::default()
613            }),
614    );
615    doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
616        .style("CustomHighlight");
617
618    doc.add_paragraph("");
619
620    // ── SECTION 10: DOCUMENT INTELLIGENCE ──
621    doc.add_paragraph("10. Document Intelligence API")
622        .style("Heading1");
623
624    let wc = doc.word_count();
625    let hc = doc.headings().len();
626    let ic = doc.images().len();
627    let lc = doc.links().len();
628    doc.add_paragraph(&format!("Word count: {wc}"));
629    doc.add_paragraph(&format!("Heading count: {hc}"));
630    doc.add_paragraph(&format!("Image count: {ic}"));
631    doc.add_paragraph(&format!("Link count: {lc}"));
632
633    let outline = doc.document_outline();
634    doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
635
636    let issues = doc.audit_accessibility();
637    doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
638    for issue in &issues {
639        doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
640    }
641
642    // ── SECTION 11: DOCUMENT MERGING ──
643    doc.add_paragraph("");
644    doc.add_paragraph("11. Document Merging").style("Heading1");
645    {
646        let mut other = Document::new();
647        other.add_paragraph("This paragraph was merged from another document using append().");
648        doc.append(&other);
649    }
650
651    doc.add_paragraph("");
652
653    // ── SUMMARY ──
654    doc.add_paragraph("Summary of Demonstrated Features")
655        .style("Heading1");
656    let features = [
657        "Page setup: size, margins, header/footer distance, gutter",
658        "Document metadata: title, author, subject, keywords",
659        "Headers and footers: text, images, different first page",
660        "Background images (full-page behind text)",
661        "Table of Contents generation with bookmarks",
662        "Text formatting: bold, italic, underline styles, strike, color, size, font",
663        "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
664        "Paragraph formatting: alignment, borders, shading, spacing, indentation",
665        "Pagination controls: keep-with-next, keep-together, widow control",
666        "Bullet and numbered lists with nesting",
667        "Tab stops with dot/underscore/hyphen leaders",
668        "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
669        "Row properties: header rows, exact height, cant-split",
670        "Cell properties: width, no-wrap, vertical alignment",
671        "Inline images and header images",
672        "Placeholder replacement (single and batch)",
673        "Regex-based find and replace",
674        "Content insertion at specific positions",
675        "Section breaks with mixed portrait/landscape",
676        "Custom paragraph and character styles",
677        "Document intelligence: word count, headings, outline, images, links",
678        "Accessibility audit",
679        "Document merging (append)",
680        "Export: DOCX, PDF, HTML, Markdown",
681    ];
682    for f in &features {
683        doc.add_bullet_list_item(f, 0);
684    }
685    doc.add_paragraph("");
686    doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
687        .alignment(Alignment::Center)
688        .shading("E2EFDA")
689        .border_all(BorderStyle::Single, 2, "00AA00");
690
691    doc
692}
693
694// =============================================================================
695// 2. PROPOSAL DOCUMENT — Deep navy + gold scheme
696// =============================================================================
697fn generate_proposal(_samples_dir: &Path) -> Document {
698    let mut doc = Document::new();
699
700    // Colors: Navy #1B2A4A, Gold #C5922E, Light #F4F1EB
701    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
702    doc.set_margins(
703        Length::inches(1.0),
704        Length::inches(1.0),
705        Length::inches(1.0),
706        Length::inches(1.0),
707    );
708    doc.set_title("AI Platform Modernization Proposal");
709    doc.set_author("Walter White");
710    doc.set_subject("Technology Proposal");
711    doc.set_keywords("proposal, AI, modernization, Tensorbee");
712
713    // Banner header
714    let logo_img = create_logo_png(220, 48);
715    let banner = build_header_banner_xml(
716        "rId1",
717        &BannerOpts {
718            bg_color: "1B2A4A",
719            banner_width: 7772400,
720            banner_height: 914400,
721            logo_width: 2011680,
722            logo_height: 438912,
723            logo_x_offset: 295125,
724            logo_y_offset: 237744,
725        },
726    );
727    doc.set_raw_header_with_images(
728        banner,
729        &[("rId1", &logo_img, "logo.png")],
730        rdocx_oxml::header_footer::HdrFtrType::Default,
731    );
732    doc.set_different_first_page(true);
733    let cover_banner = build_header_banner_xml(
734        "rId1",
735        &BannerOpts {
736            bg_color: "C5922E",
737            banner_width: 7772400,
738            banner_height: 914400,
739            logo_width: 2011680,
740            logo_height: 438912,
741            logo_x_offset: 295125,
742            logo_y_offset: 237744,
743        },
744    );
745    doc.set_raw_header_with_images(
746        cover_banner,
747        &[("rId1", &logo_img, "logo.png")],
748        rdocx_oxml::header_footer::HdrFtrType::First,
749    );
750    doc.set_margins(
751        Length::twips(2292),
752        Length::twips(1440),
753        Length::twips(1440),
754        Length::twips(1440),
755    );
756    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
757    doc.set_footer("Tensorbee — Confidential");
758
759    // Custom styles
760    doc.add_style(
761        StyleBuilder::paragraph("ProposalTitle", "Proposal Title")
762            .based_on("Normal")
763            .run_properties(rdocx_oxml::properties::CT_RPr {
764                bold: Some(true),
765                font_ascii: Some("Georgia".to_string()),
766                font_hansi: Some("Georgia".to_string()),
767                sz: Some(rdocx_oxml::HalfPoint(56)), // half-points → 28pt
768                color: Some("1B2A4A".to_string()),
769                ..Default::default()
770            }),
771    );
772
773    // ── Cover Page ──
774    for _ in 0..4 {
775        doc.add_paragraph("");
776    }
777    doc.add_paragraph("AI Platform Modernization")
778        .style("ProposalTitle")
779        .alignment(Alignment::Center);
780    doc.add_paragraph("")
781        .alignment(Alignment::Center)
782        .border_bottom(BorderStyle::Single, 8, "C5922E");
783    {
784        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
785        p.add_run("Prepared for Global Financial Corp.")
786            .size(14.0)
787            .color("666666")
788            .italic(true);
789    }
790    {
791        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
792        p.add_run("Prepared by: Walter White, CEO — Tensorbee")
793            .size(12.0)
794            .color("888888");
795    }
796    {
797        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
798        p.add_run("February 22, 2026").size(12.0).color("888888");
799    }
800
801    // ── TOC ──
802    doc.add_paragraph("").page_break_before(true);
803    doc.add_paragraph("Table of Contents").style("Heading1");
804    // We'll add a manual-style TOC since the insert_toc goes at a specific position
805    doc.insert_toc(doc.content_count(), 2);
806
807    // ── Executive Summary ──
808    doc.add_paragraph("").page_break_before(true);
809    doc.add_paragraph("Executive Summary").style("Heading1");
810    doc.add_paragraph(
811        "Tensorbee proposes a comprehensive modernization of Global Financial Corp's AI platform, \
812         transitioning from legacy batch-processing systems to a real-time inference architecture. \
813         This transformation will reduce model deployment time from weeks to hours, improve \
814         prediction accuracy by an estimated 23%, and deliver $4.2M in annual operational savings.",
815    )
816    .first_line_indent(Length::inches(0.3));
817    doc.add_paragraph(
818        "The project spans three phases over 18 months, with a total investment of $2.8M. \
819         Tensorbee brings deep expertise in ML infrastructure, having successfully completed \
820         similar transformations for three Fortune 500 financial institutions.",
821    )
822    .first_line_indent(Length::inches(0.3));
823
824    // Key metrics table
825    doc.add_paragraph("");
826    {
827        let mut tbl = doc
828            .add_table(5, 2)
829            .borders(BorderStyle::Single, 4, "1B2A4A")
830            .width_pct(80.0);
831        tbl.cell(0, 0).unwrap().set_text("Key Metric");
832        tbl.cell(0, 0).unwrap().shading("1B2A4A");
833        tbl.cell(0, 1).unwrap().set_text("Value");
834        tbl.cell(0, 1).unwrap().shading("1B2A4A");
835        let rows = [
836            ("Total Investment", "$2.8M"),
837            ("Annual Savings", "$4.2M"),
838            ("ROI (Year 1)", "150%"),
839            ("Timeline", "18 months"),
840        ];
841        for (i, (k, v)) in rows.iter().enumerate() {
842            tbl.cell(i + 1, 0).unwrap().set_text(k);
843            if i % 2 == 0 {
844                tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
845            }
846            tbl.cell(i + 1, 1).unwrap().set_text(v);
847            if i % 2 == 0 {
848                tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
849            }
850        }
851    }
852
853    // ── Problem Statement ──
854    doc.add_paragraph("").page_break_before(true);
855    doc.add_paragraph("Problem Statement").style("Heading1");
856    doc.add_paragraph(
857        "Global Financial Corp's current AI infrastructure faces three critical challenges:",
858    );
859    doc.add_numbered_list_item("Deployment Latency — New models take 3-4 weeks to deploy to production, compared to the industry benchmark of 2-3 days.", 0);
860    doc.add_numbered_list_item("Scalability Constraints — The batch processing architecture cannot handle real-time inference demands during peak trading hours.", 0);
861    doc.add_numbered_list_item("Technical Debt — Legacy Python 2.7 codebases and manual deployment scripts create reliability risks and slow iteration speed.", 0);
862
863    // ── Proposed Solution ──
864    doc.add_paragraph("").page_break_before(true);
865    doc.add_paragraph("Proposed Solution").style("Heading1");
866
867    doc.add_paragraph("Phase 1: Foundation (Months 1-6)")
868        .style("Heading2");
869    doc.add_bullet_list_item("Deploy Kubernetes-based ML serving infrastructure", 0);
870    doc.add_bullet_list_item("Implement CI/CD pipeline for model deployment", 0);
871    doc.add_bullet_list_item("Migrate top 5 production models to new platform", 0);
872
873    doc.add_paragraph("Phase 2: Expansion (Months 7-12)")
874        .style("Heading2");
875    doc.add_bullet_list_item(
876        "Real-time feature engineering pipeline (Apache Kafka + Flink)",
877        0,
878    );
879    doc.add_bullet_list_item("A/B testing framework for model variants", 0);
880    doc.add_bullet_list_item("Automated model monitoring and drift detection", 0);
881
882    doc.add_paragraph("Phase 3: Optimization (Months 13-18)")
883        .style("Heading2");
884    doc.add_bullet_list_item("GPU inference optimization (TensorRT/ONNX Runtime)", 0);
885    doc.add_bullet_list_item("Multi-region deployment for latency reduction", 0);
886    doc.add_bullet_list_item(
887        "Self-service model deployment portal for data science teams",
888        0,
889    );
890
891    // ── Budget ──
892    doc.add_paragraph("Budget Breakdown").style("Heading1");
893    {
894        let mut tbl = doc
895            .add_table(6, 3)
896            .borders(BorderStyle::Single, 4, "1B2A4A")
897            .width_pct(100.0);
898        tbl.cell(0, 0).unwrap().set_text("Category");
899        tbl.cell(0, 0).unwrap().shading("1B2A4A");
900        tbl.cell(0, 1).unwrap().set_text("Cost");
901        tbl.cell(0, 1).unwrap().shading("1B2A4A");
902        tbl.cell(0, 2).unwrap().set_text("Notes");
903        tbl.cell(0, 2).unwrap().shading("1B2A4A");
904        let items = [
905            (
906                "Infrastructure",
907                "$450,000",
908                "Cloud compute + GPU instances",
909            ),
910            ("Engineering Services", "$1,600,000", "12 FTE x 18 months"),
911            (
912                "Software Licenses",
913                "$350,000",
914                "Kafka, monitoring, CI/CD tools",
915            ),
916            (
917                "Training & Enablement",
918                "$200,000",
919                "Team training, documentation",
920            ),
921            ("Contingency (10%)", "$200,000", "Risk buffer"),
922        ];
923        for (i, (cat, cost, note)) in items.iter().enumerate() {
924            tbl.cell(i + 1, 0).unwrap().set_text(cat);
925            tbl.cell(i + 1, 1).unwrap().set_text(cost);
926            tbl.cell(i + 1, 2).unwrap().set_text(note);
927            if i % 2 == 0 {
928                tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
929                tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
930                tbl.cell(i + 1, 2).unwrap().shading("F4F1EB");
931            }
932        }
933    }
934
935    // ── Closing ──
936    doc.add_paragraph("");
937    doc.add_paragraph("Next Steps").style("Heading1");
938    doc.add_numbered_list_item(
939        "Schedule technical deep-dive with GFC engineering team (Week 1)",
940        0,
941    );
942    doc.add_numbered_list_item("Finalize scope and sign SOW (Week 2-3)", 0);
943    doc.add_numbered_list_item("Kick off Phase 1 with joint planning session (Week 4)", 0);
944
945    doc.add_paragraph("");
946    {
947        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
948        p.add_run("Walter White")
949            .bold(true)
950            .size(14.0)
951            .color("1B2A4A");
952    }
953    {
954        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
955        p.add_run("CEO, Tensorbee | walter@tensorbee.com")
956            .size(10.0)
957            .color("888888");
958    }
959
960    doc
961}
962
963// =============================================================================
964// 3. QUOTE / BILL OF MATERIALS — Teal + orange scheme
965// =============================================================================
966fn generate_quote(_samples_dir: &Path) -> Document {
967    let mut doc = Document::new();
968
969    // Colors: Teal #008B8B, Dark #1A3C3C, Orange #E07020, Light #F0F8F8
970    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
971    doc.set_margins(
972        Length::inches(0.75),
973        Length::inches(0.75),
974        Length::inches(0.75),
975        Length::inches(0.75),
976    );
977    doc.set_title("Quotation QT-2026-0147");
978    doc.set_author("Walter White");
979    doc.set_keywords("quote, BOM, Tensorbee");
980
981    doc.set_header("Tensorbee — Quotation");
982    doc.set_footer("QT-2026-0147 | Page");
983
984    // ── Header Block ──
985    {
986        let mut p = doc.add_paragraph("").alignment(Alignment::Left);
987        p.add_run("TENSORBEE")
988            .bold(true)
989            .size(28.0)
990            .color("008B8B")
991            .font("Helvetica");
992    }
993    doc.add_paragraph("123 Innovation Drive, Suite 400")
994        .alignment(Alignment::Left);
995    doc.add_paragraph("San Francisco, CA 94105 | +1 (415) 555-0199")
996        .alignment(Alignment::Left);
997    doc.add_paragraph("")
998        .border_bottom(BorderStyle::Single, 8, "008B8B");
999
1000    // Quote meta
1001    doc.add_paragraph("");
1002    {
1003        let mut tbl = doc.add_table(4, 4).width_pct(100.0);
1004        tbl.cell(0, 0).unwrap().set_text("QUOTATION");
1005        tbl.cell(0, 0).unwrap().shading("008B8B").grid_span(2);
1006        tbl.cell(0, 2).unwrap().set_text("DATE");
1007        tbl.cell(0, 2).unwrap().shading("008B8B");
1008        tbl.cell(0, 3).unwrap().set_text("VALID UNTIL");
1009        tbl.cell(0, 3).unwrap().shading("008B8B");
1010        tbl.cell(1, 0).unwrap().set_text("Quote #:");
1011        tbl.cell(1, 1).unwrap().set_text("QT-2026-0147");
1012        tbl.cell(1, 2).unwrap().set_text("Feb 22, 2026");
1013        tbl.cell(1, 3).unwrap().set_text("Mar 22, 2026");
1014        tbl.cell(2, 0).unwrap().set_text("Prepared by:");
1015        tbl.cell(2, 1).unwrap().set_text("Walter White");
1016        tbl.cell(2, 2).unwrap().set_text("Payment:");
1017        tbl.cell(2, 3).unwrap().set_text("Net 30");
1018        tbl.cell(3, 0).unwrap().set_text("Customer:");
1019        tbl.cell(3, 1).unwrap().set_text("Meridian Dynamics LLC");
1020        tbl.cell(3, 2).unwrap().set_text("Currency:");
1021        tbl.cell(3, 3).unwrap().set_text("USD");
1022    }
1023
1024    doc.add_paragraph("");
1025
1026    // ── Bill of Materials ──
1027    {
1028        let mut p = doc.add_paragraph("");
1029        p.add_run("Bill of Materials")
1030            .bold(true)
1031            .size(16.0)
1032            .color("1A3C3C");
1033    }
1034    doc.add_paragraph("");
1035
1036    {
1037        let mut tbl = doc
1038            .add_table(10, 6)
1039            .borders(BorderStyle::Single, 2, "008B8B")
1040            .width_pct(100.0)
1041            .layout_fixed();
1042        // Headers
1043        let hdrs = [
1044            "#",
1045            "Part Number",
1046            "Description",
1047            "Qty",
1048            "Unit Price",
1049            "Total",
1050        ];
1051        for (c, h) in hdrs.iter().enumerate() {
1052            tbl.cell(0, c).unwrap().set_text(h);
1053            tbl.cell(0, c).unwrap().shading("008B8B");
1054        }
1055
1056        let items: Vec<(&str, &str, &str, &str, &str)> = vec![
1057            (
1058                "TB-GPU-A100",
1059                "NVIDIA A100 80GB GPU",
1060                "4",
1061                "$12,500.00",
1062                "$50,000.00",
1063            ),
1064            (
1065                "TB-SRV-R750",
1066                "Dell R750xa Server Chassis",
1067                "2",
1068                "$8,200.00",
1069                "$16,400.00",
1070            ),
1071            (
1072                "TB-RAM-256G",
1073                "256GB DDR5 ECC Memory Module",
1074                "8",
1075                "$890.00",
1076                "$7,120.00",
1077            ),
1078            (
1079                "TB-SSD-3840",
1080                "3.84TB NVMe U.2 SSD",
1081                "8",
1082                "$1,150.00",
1083                "$9,200.00",
1084            ),
1085            (
1086                "TB-NET-CX7",
1087                "ConnectX-7 200GbE NIC",
1088                "4",
1089                "$1,800.00",
1090                "$7,200.00",
1091            ),
1092            (
1093                "TB-SW-48P",
1094                "48-Port 100GbE Switch",
1095                "1",
1096                "$22,000.00",
1097                "$22,000.00",
1098            ),
1099            (
1100                "TB-CAB-RACK",
1101                "42U Server Rack + PDU",
1102                "1",
1103                "$4,500.00",
1104                "$4,500.00",
1105            ),
1106            (
1107                "TB-SVC-INST",
1108                "Installation & Configuration",
1109                "1",
1110                "$8,500.00",
1111                "$8,500.00",
1112            ),
1113            (
1114                "TB-SVC-SUPP",
1115                "3-Year Premium Support",
1116                "1",
1117                "$15,000.00",
1118                "$15,000.00",
1119            ),
1120        ];
1121
1122        for (i, (pn, desc, qty, unit, total)) in items.iter().enumerate() {
1123            let row = i + 1;
1124            tbl.cell(row, 0).unwrap().set_text(&format!("{}", i + 1));
1125            tbl.cell(row, 1).unwrap().set_text(pn);
1126            tbl.cell(row, 2).unwrap().set_text(desc);
1127            tbl.cell(row, 3).unwrap().set_text(qty);
1128            tbl.cell(row, 4).unwrap().set_text(unit);
1129            tbl.cell(row, 5).unwrap().set_text(total);
1130            if i % 2 == 0 {
1131                for c in 0..6 {
1132                    tbl.cell(row, c).unwrap().shading("F0F8F8");
1133                }
1134            }
1135        }
1136    }
1137
1138    doc.add_paragraph("");
1139
1140    // ── Totals ──
1141    {
1142        let mut tbl = doc
1143            .add_table(4, 2)
1144            .borders(BorderStyle::Single, 2, "008B8B")
1145            .width(Length::inches(3.5))
1146            .alignment(Alignment::Right);
1147        tbl.cell(0, 0).unwrap().set_text("Subtotal");
1148        tbl.cell(0, 1).unwrap().set_text("$139,920.00");
1149        tbl.cell(1, 0).unwrap().set_text("Shipping & Handling");
1150        tbl.cell(1, 1).unwrap().set_text("$2,500.00");
1151        tbl.cell(2, 0).unwrap().set_text("Tax (8.625%)");
1152        tbl.cell(2, 1).unwrap().set_text("$12,068.10");
1153        tbl.cell(3, 0).unwrap().set_text("TOTAL");
1154        tbl.cell(3, 0).unwrap().shading("E07020");
1155        tbl.cell(3, 1).unwrap().set_text("$154,488.10");
1156        tbl.cell(3, 1).unwrap().shading("E07020");
1157    }
1158
1159    doc.add_paragraph("");
1160
1161    // ── Terms & Conditions ──
1162    {
1163        let mut p = doc.add_paragraph("");
1164        p.add_run("Terms & Conditions")
1165            .bold(true)
1166            .size(14.0)
1167            .color("1A3C3C");
1168    }
1169    doc.add_numbered_list_item(
1170        "This quotation is valid for 30 calendar days from the date of issue.",
1171        0,
1172    );
1173    doc.add_numbered_list_item(
1174        "All prices are in USD and exclusive of applicable taxes unless stated.",
1175        0,
1176    );
1177    doc.add_numbered_list_item("Standard lead time is 4-6 weeks from PO receipt.", 0);
1178    doc.add_numbered_list_item(
1179        "Payment terms: Net 30 from invoice date. 2% discount for payment within 10 days.",
1180        0,
1181    );
1182    doc.add_numbered_list_item(
1183        "Warranty: 3-year manufacturer warranty on all hardware components.",
1184        0,
1185    );
1186    doc.add_numbered_list_item(
1187        "Returns subject to 15% restocking fee if initiated after 14 days.",
1188        0,
1189    );
1190
1191    doc.add_paragraph("");
1192    doc.add_paragraph("")
1193        .border_bottom(BorderStyle::Single, 4, "008B8B");
1194    doc.add_paragraph("");
1195    {
1196        let mut p = doc.add_paragraph("");
1197        p.add_run("Accepted by: ").bold(true);
1198        p.add_run("___________________________ Date: ___________");
1199    }
1200    {
1201        let mut p = doc.add_paragraph("");
1202        p.add_run("Print Name: ").bold(true);
1203        p.add_run("___________________________ Title: ___________");
1204    }
1205
1206    doc
1207}
1208
1209// =============================================================================
1210// 4. INVOICE — Crimson + charcoal scheme
1211// =============================================================================
1212fn generate_invoice(_samples_dir: &Path) -> Document {
1213    let mut doc = Document::new();
1214
1215    // Colors: Crimson #B22222, Charcoal #333333, Light #FAF0F0
1216    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1217    doc.set_margins(
1218        Length::inches(0.75),
1219        Length::inches(0.75),
1220        Length::inches(0.75),
1221        Length::inches(0.75),
1222    );
1223    doc.set_title("Invoice INV-2026-0392");
1224    doc.set_author("Walter White");
1225    doc.set_keywords("invoice, Tensorbee");
1226
1227    doc.set_footer("Tensorbee — Thank you for your business!");
1228
1229    // ── Company & Invoice Header ──
1230    {
1231        let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1232        // Company name left, INVOICE right
1233        {
1234            let mut cell = tbl.cell(0, 0).unwrap();
1235            let mut p = cell.add_paragraph("");
1236            p.add_run("TENSORBEE")
1237                .bold(true)
1238                .size(32.0)
1239                .color("B22222")
1240                .font("Helvetica");
1241        }
1242        {
1243            let mut cell = tbl.cell(0, 1).unwrap();
1244            let mut p = cell.add_paragraph("");
1245            p.add_run("INVOICE").bold(true).size(32.0).color("333333");
1246        }
1247        tbl.cell(1, 0)
1248            .unwrap()
1249            .set_text("123 Innovation Drive, Suite 400");
1250        tbl.cell(1, 1).unwrap().set_text("Invoice #: INV-2026-0392");
1251        tbl.cell(2, 0).unwrap().set_text("San Francisco, CA 94105");
1252        tbl.cell(2, 1).unwrap().set_text("Date: February 22, 2026");
1253        tbl.cell(3, 0).unwrap().set_text("walter@tensorbee.com");
1254        tbl.cell(3, 1).unwrap().set_text("Due Date: March 24, 2026");
1255    }
1256
1257    doc.add_paragraph("")
1258        .border_bottom(BorderStyle::Thick, 8, "B22222");
1259    doc.add_paragraph("");
1260
1261    // ── Bill To / Ship To ──
1262    {
1263        let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1264        tbl.cell(0, 0).unwrap().set_text("BILL TO");
1265        tbl.cell(0, 0).unwrap().shading("B22222");
1266        tbl.cell(0, 1).unwrap().set_text("SHIP TO");
1267        tbl.cell(0, 1).unwrap().shading("B22222");
1268        tbl.cell(1, 0).unwrap().set_text("Meridian Dynamics LLC");
1269        tbl.cell(1, 1).unwrap().set_text("Meridian Dynamics LLC");
1270        tbl.cell(2, 0).unwrap().set_text("456 Enterprise Blvd");
1271        tbl.cell(2, 1).unwrap().set_text("Attn: Server Room B");
1272        tbl.cell(3, 0).unwrap().set_text("Austin, TX 78701");
1273        tbl.cell(3, 1)
1274            .unwrap()
1275            .set_text("456 Enterprise Blvd, Austin, TX 78701");
1276    }
1277
1278    doc.add_paragraph("");
1279
1280    // ── Line Items ──
1281    {
1282        let mut tbl = doc
1283            .add_table(8, 5)
1284            .borders(BorderStyle::Single, 2, "B22222")
1285            .width_pct(100.0);
1286        let hdrs = ["Description", "Qty", "Unit Price", "Tax", "Amount"];
1287        for (c, h) in hdrs.iter().enumerate() {
1288            tbl.cell(0, c).unwrap().set_text(h);
1289            tbl.cell(0, c).unwrap().shading("B22222");
1290        }
1291        let items = [
1292            (
1293                "ML Infrastructure Setup — Phase 1",
1294                "1",
1295                "$45,000.00",
1296                "$3,881.25",
1297                "$48,881.25",
1298            ),
1299            (
1300                "Data Pipeline Development (160 hrs)",
1301                "160",
1302                "$225.00",
1303                "$3,105.00",
1304                "$39,105.00",
1305            ),
1306            (
1307                "GPU Cluster Configuration",
1308                "1",
1309                "$12,000.00",
1310                "$1,035.00",
1311                "$13,035.00",
1312            ),
1313            (
1314                "API Gateway Implementation",
1315                "1",
1316                "$18,500.00",
1317                "$1,595.63",
1318                "$20,095.63",
1319            ),
1320            (
1321                "Load Testing & QA (80 hrs)",
1322                "80",
1323                "$195.00",
1324                "$1,345.50",
1325                "$16,945.50",
1326            ),
1327            (
1328                "Documentation & Training",
1329                "1",
1330                "$8,000.00",
1331                "$690.00",
1332                "$8,690.00",
1333            ),
1334            (
1335                "Project Management (3 months)",
1336                "3",
1337                "$6,500.00",
1338                "$1,679.63",
1339                "$21,179.63",
1340            ),
1341        ];
1342        for (i, (desc, qty, unit, tax, amt)) in items.iter().enumerate() {
1343            let r = i + 1;
1344            tbl.cell(r, 0).unwrap().set_text(desc);
1345            tbl.cell(r, 1).unwrap().set_text(qty);
1346            tbl.cell(r, 2).unwrap().set_text(unit);
1347            tbl.cell(r, 3).unwrap().set_text(tax);
1348            tbl.cell(r, 4).unwrap().set_text(amt);
1349            if i % 2 == 0 {
1350                for c in 0..5 {
1351                    tbl.cell(r, c).unwrap().shading("FAF0F0");
1352                }
1353            }
1354        }
1355    }
1356
1357    doc.add_paragraph("");
1358
1359    // ── Totals ──
1360    {
1361        let mut tbl = doc
1362            .add_table(4, 2)
1363            .borders(BorderStyle::Single, 2, "B22222")
1364            .width(Length::inches(3.0))
1365            .alignment(Alignment::Right);
1366        tbl.cell(0, 0).unwrap().set_text("Subtotal");
1367        tbl.cell(0, 1).unwrap().set_text("$154,600.00");
1368        tbl.cell(1, 0).unwrap().set_text("Tax (8.625%)");
1369        tbl.cell(1, 1).unwrap().set_text("$13,334.25");
1370        tbl.cell(2, 0).unwrap().set_text("Discount (5%)");
1371        tbl.cell(2, 1).unwrap().set_text("-$7,730.00");
1372        tbl.cell(3, 0).unwrap().set_text("AMOUNT DUE");
1373        tbl.cell(3, 0).unwrap().shading("B22222");
1374        tbl.cell(3, 1).unwrap().set_text("$160,204.25");
1375        tbl.cell(3, 1).unwrap().shading("B22222");
1376    }
1377
1378    doc.add_paragraph("");
1379    doc.add_paragraph("");
1380
1381    // ── Payment Details ──
1382    {
1383        let mut p = doc.add_paragraph("");
1384        p.add_run("Payment Details")
1385            .bold(true)
1386            .size(14.0)
1387            .color("333333");
1388    }
1389    doc.add_paragraph("Bank: Silicon Valley Bank")
1390        .indent_left(Length::inches(0.3));
1391    doc.add_paragraph("Account: Tensorbee Inc. — 0847-2953-1120")
1392        .indent_left(Length::inches(0.3));
1393    doc.add_paragraph("Routing: 121140399")
1394        .indent_left(Length::inches(0.3));
1395    doc.add_paragraph("Swift: SVBKUS6S")
1396        .indent_left(Length::inches(0.3));
1397
1398    doc.add_paragraph("");
1399
1400    doc.add_paragraph("Please include invoice number INV-2026-0392 in the payment reference.")
1401        .shading("FAF0F0")
1402        .border_all(BorderStyle::Single, 2, "B22222");
1403
1404    doc
1405}
1406
1407// =============================================================================
1408// 5. REPORT — Forest green + earth tones, with images & hierarchical sections
1409// =============================================================================
1410fn generate_report(_samples_dir: &Path) -> Document {
1411    let mut doc = Document::new();
1412
1413    // Colors: Forest #2D5016, Sage #6B8E23, Earth #8B7355, Cream #FFFAF0
1414    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1415    doc.set_margins(
1416        Length::inches(1.0),
1417        Length::inches(1.0),
1418        Length::inches(1.0),
1419        Length::inches(1.0),
1420    );
1421    doc.set_title("Q4 2025 Environmental Impact Report");
1422    doc.set_author("Walter White");
1423    doc.set_subject("Quarterly Environmental Report");
1424    doc.set_keywords("environment, sustainability, report, Tensorbee");
1425
1426    doc.set_different_first_page(true);
1427    doc.set_first_page_header("");
1428    doc.set_header("Tensorbee — Q4 2025 Environmental Impact Report");
1429    doc.set_footer("CONFIDENTIAL — Page");
1430
1431    // ── Cover ──
1432    let cover_bg = create_sample_png(612, 792, [20, 50, 15]);
1433    doc.add_background_image(&cover_bg, "report_cover.png");
1434
1435    for _ in 0..5 {
1436        doc.add_paragraph("");
1437    }
1438    {
1439        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1440        p.add_run("Q4 2025")
1441            .bold(true)
1442            .size(48.0)
1443            .color("FFFFFF")
1444            .font("Georgia");
1445    }
1446    {
1447        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1448        p.add_run("Environmental Impact Report")
1449            .size(24.0)
1450            .color("90EE90")
1451            .font("Georgia")
1452            .italic(true);
1453    }
1454    doc.add_paragraph("");
1455    {
1456        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1457        p.add_run("Tensorbee — Sustainability Division")
1458            .size(14.0)
1459            .color("C0C0C0");
1460    }
1461    {
1462        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1463        p.add_run("Prepared by Walter White, Chief Sustainability Officer")
1464            .size(11.0)
1465            .color("AAAAAA");
1466    }
1467
1468    // ── TOC ──
1469    doc.add_paragraph("").page_break_before(true);
1470    doc.insert_toc(doc.content_count(), 3);
1471
1472    // ── Section 1: Executive Overview ──
1473    doc.add_paragraph("").page_break_before(true);
1474    doc.add_paragraph("Executive Overview").style("Heading1");
1475    doc.add_paragraph(
1476        "This report presents Tensorbee's environmental performance for Q4 2025. Our \
1477         sustainability initiatives have yielded a 34% reduction in carbon emissions compared \
1478         to Q4 2024, exceeding our target of 25%. Key achievements include the transition to \
1479         100% renewable energy in our primary data centers and the launch of our carbon offset \
1480         marketplace.",
1481    )
1482    .first_line_indent(Length::inches(0.3));
1483
1484    // Image: performance chart
1485    let chart_img = create_chart_png(400, 200);
1486    doc.add_paragraph("");
1487    doc.add_picture(
1488        &chart_img,
1489        "performance_chart.png",
1490        Length::inches(5.0),
1491        Length::inches(2.5),
1492    )
1493    .alignment(Alignment::Center);
1494    {
1495        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1496        p.add_run("Figure 1: Quarterly Carbon Emissions (tonnes CO2e)")
1497            .italic(true)
1498            .size(9.0)
1499            .color("666666");
1500    }
1501
1502    // ── Section 2: Energy Consumption ──
1503    doc.add_paragraph("").page_break_before(true);
1504    doc.add_paragraph("Energy Consumption").style("Heading1");
1505
1506    doc.add_paragraph("Data Center Operations")
1507        .style("Heading2");
1508    doc.add_paragraph(
1509        "Our three primary data centers consumed a combined 4.2 GWh in Q4 2025, \
1510         a 12% reduction from Q3 2025 driven by improved cooling efficiency and \
1511         server consolidation.",
1512    );
1513
1514    // Data table
1515    {
1516        let mut tbl = doc
1517            .add_table(5, 4)
1518            .borders(BorderStyle::Single, 4, "2D5016")
1519            .width_pct(100.0);
1520        let hdrs = ["Data Center", "Capacity (MW)", "Usage (GWh)", "PUE"];
1521        for (c, h) in hdrs.iter().enumerate() {
1522            tbl.cell(0, c).unwrap().set_text(h);
1523            tbl.cell(0, c).unwrap().shading("2D5016");
1524        }
1525        let rows = [
1526            ("San Francisco (Primary)", "3.2", "1.8", "1.12"),
1527            ("Dublin (EU)", "2.1", "1.2", "1.18"),
1528            ("Singapore (APAC)", "1.8", "1.2", "1.24"),
1529            ("TOTAL", "7.1", "4.2", "1.17 avg"),
1530        ];
1531        for (i, (dc, cap, use_, pue)) in rows.iter().enumerate() {
1532            tbl.cell(i + 1, 0).unwrap().set_text(dc);
1533            tbl.cell(i + 1, 1).unwrap().set_text(cap);
1534            tbl.cell(i + 1, 2).unwrap().set_text(use_);
1535            tbl.cell(i + 1, 3).unwrap().set_text(pue);
1536            if i == 3 {
1537                for c in 0..4 {
1538                    tbl.cell(i + 1, c).unwrap().shading("E2EFDA");
1539                }
1540            }
1541        }
1542    }
1543
1544    doc.add_paragraph("");
1545
1546    doc.add_paragraph("Renewable Energy Mix").style("Heading2");
1547    doc.add_paragraph("Breakdown of energy sources across all facilities:");
1548
1549    doc.add_bullet_list_item("Solar PV: 42% (1.76 GWh)", 0);
1550    doc.add_bullet_list_item("Wind Power Purchase Agreements: 38% (1.60 GWh)", 0);
1551    doc.add_bullet_list_item("Hydroelectric: 12% (0.50 GWh)", 0);
1552    doc.add_bullet_list_item("Grid (non-renewable): 8% (0.34 GWh)", 0);
1553
1554    // ── Section 3: Water & Waste ──
1555    doc.add_paragraph("Water & Waste Management")
1556        .style("Heading1");
1557
1558    doc.add_paragraph("Water Usage").style("Heading2");
1559    doc.add_paragraph(
1560        "Total water consumption was 12.4 million gallons, a 15% reduction from Q3. \
1561         Our closed-loop cooling systems now recycle 78% of water used in cooling operations.",
1562    );
1563
1564    doc.add_paragraph("Waste Reduction").style("Heading2");
1565    doc.add_paragraph("E-waste management results for Q4:")
1566        .keep_with_next(true);
1567    {
1568        let mut tbl = doc
1569            .add_table(4, 3)
1570            .borders(BorderStyle::Single, 2, "6B8E23")
1571            .width_pct(80.0);
1572        tbl.cell(0, 0).unwrap().set_text("Category");
1573        tbl.cell(0, 0).unwrap().shading("6B8E23");
1574        tbl.cell(0, 1).unwrap().set_text("Weight (kg)");
1575        tbl.cell(0, 1).unwrap().shading("6B8E23");
1576        tbl.cell(0, 2).unwrap().set_text("Recycled %");
1577        tbl.cell(0, 2).unwrap().shading("6B8E23");
1578        let rows = [
1579            ("Server Hardware", "2,450", "94%"),
1580            ("Networking Equipment", "820", "91%"),
1581            ("Storage Media", "340", "99%"),
1582        ];
1583        for (i, (cat, wt, pct)) in rows.iter().enumerate() {
1584            tbl.cell(i + 1, 0).unwrap().set_text(cat);
1585            tbl.cell(i + 1, 1).unwrap().set_text(wt);
1586            tbl.cell(i + 1, 2).unwrap().set_text(pct);
1587        }
1588    }
1589
1590    // ── Section 4: Initiatives ──
1591    doc.add_paragraph("").page_break_before(true);
1592    doc.add_paragraph("Q1 2026 Initiatives").style("Heading1");
1593
1594    doc.add_paragraph("Planned Programs").style("Heading2");
1595    doc.add_numbered_list_item(
1596        "Deploy on-site battery storage at SF data center (2 MWh capacity)",
1597        0,
1598    );
1599    doc.add_numbered_list_item("Pilot immersion cooling in Dublin facility", 0);
1600    doc.add_numbered_list_item("Launch employee commute carbon offset program", 0);
1601    doc.add_numbered_list_item("Achieve ISO 14001 certification for Singapore facility", 0);
1602
1603    doc.add_paragraph("Investment Targets").style("Heading2");
1604    doc.add_paragraph("Sustainability CapEx allocation for FY2026:")
1605        .keep_with_next(true);
1606    {
1607        let mut tbl = doc
1608            .add_table(5, 2)
1609            .borders(BorderStyle::Single, 4, "2D5016");
1610        tbl.cell(0, 0).unwrap().set_text("Initiative");
1611        tbl.cell(0, 0).unwrap().shading("2D5016");
1612        tbl.cell(0, 1).unwrap().set_text("Budget");
1613        tbl.cell(0, 1).unwrap().shading("2D5016");
1614        let rows = [
1615            ("Battery Storage", "$1.2M"),
1616            ("Immersion Cooling Pilot", "$800K"),
1617            ("Solar Panel Expansion", "$2.1M"),
1618            ("Carbon Credits", "$500K"),
1619        ];
1620        for (i, (init, budget)) in rows.iter().enumerate() {
1621            tbl.cell(i + 1, 0).unwrap().set_text(init);
1622            tbl.cell(i + 1, 1).unwrap().set_text(budget);
1623            if i % 2 == 0 {
1624                tbl.cell(i + 1, 0).unwrap().shading("FFFAF0");
1625                tbl.cell(i + 1, 1).unwrap().shading("FFFAF0");
1626            }
1627        }
1628    }
1629
1630    // Image: sustainability roadmap
1631    let roadmap_img = create_sample_png(500, 100, [40, 80, 30]);
1632    doc.add_paragraph("");
1633    doc.add_picture(
1634        &roadmap_img,
1635        "roadmap.png",
1636        Length::inches(6.0),
1637        Length::inches(1.2),
1638    )
1639    .alignment(Alignment::Center);
1640    {
1641        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1642        p.add_run("Figure 2: 2026 Sustainability Roadmap")
1643            .italic(true)
1644            .size(9.0)
1645            .color("666666");
1646    }
1647
1648    doc.add_paragraph("");
1649    doc.add_paragraph(
1650        "For questions about this report, contact Walter White at sustainability@tensorbee.com.",
1651    )
1652    .shading("FFFAF0")
1653    .border_all(BorderStyle::Single, 2, "2D5016");
1654
1655    doc
1656}
1657
1658// =============================================================================
1659// 6. LETTER — Slate blue + silver scheme
1660// =============================================================================
1661fn generate_letter(_samples_dir: &Path) -> Document {
1662    let mut doc = Document::new();
1663
1664    // Colors: Slate #4A5568, Blue accent #3182CE
1665    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1666    doc.set_margins(
1667        Length::inches(1.25),
1668        Length::inches(1.25),
1669        Length::inches(1.25),
1670        Length::inches(1.25),
1671    );
1672    doc.set_title("Business Letter");
1673    doc.set_author("Walter White");
1674
1675    // Banner header using raw XML
1676    let logo_img = create_logo_png(220, 48);
1677    let banner = build_header_banner_xml(
1678        "rId1",
1679        &BannerOpts {
1680            bg_color: "4A5568",
1681            banner_width: 7772400,
1682            banner_height: 731520, // ~0.8"
1683            logo_width: 2011680,
1684            logo_height: 438912,
1685            logo_x_offset: 295125,
1686            logo_y_offset: 146304,
1687        },
1688    );
1689    doc.set_raw_header_with_images(
1690        banner,
1691        &[("rId1", &logo_img, "logo.png")],
1692        rdocx_oxml::header_footer::HdrFtrType::Default,
1693    );
1694    doc.set_margins(
1695        Length::twips(2000),
1696        Length::twips(1800),
1697        Length::twips(1440),
1698        Length::twips(1800),
1699    );
1700    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
1701
1702    // Footer with contact
1703    doc.set_footer(
1704        "Tensorbee | 123 Innovation Drive, Suite 400 | San Francisco, CA 94105 | tensorbee.com",
1705    );
1706
1707    // ── Sender Address ──
1708    doc.add_paragraph("Walter White");
1709    doc.add_paragraph("Chief Executive Officer");
1710    doc.add_paragraph("Tensorbee");
1711    doc.add_paragraph("123 Innovation Drive, Suite 400");
1712    doc.add_paragraph("San Francisco, CA 94105");
1713    doc.add_paragraph("");
1714    doc.add_paragraph("February 22, 2026");
1715
1716    doc.add_paragraph("");
1717
1718    // ── Recipient ──
1719    doc.add_paragraph("Ms. Sarah Chen");
1720    doc.add_paragraph("VP of Engineering");
1721    doc.add_paragraph("Meridian Dynamics LLC");
1722    doc.add_paragraph("456 Enterprise Boulevard");
1723    doc.add_paragraph("Austin, TX 78701");
1724
1725    doc.add_paragraph("");
1726
1727    // ── Subject ──
1728    {
1729        let mut p = doc.add_paragraph("");
1730        p.add_run("Re: Strategic Technology Partnership — Phase 2 Expansion")
1731            .bold(true);
1732    }
1733
1734    doc.add_paragraph("");
1735
1736    // ── Body ──
1737    doc.add_paragraph("Dear Ms. Chen,");
1738    doc.add_paragraph("");
1739
1740    doc.add_paragraph(
1741        "I am writing to express Tensorbee's enthusiasm for expanding our strategic technology \
1742         partnership with Meridian Dynamics. Following the successful completion of Phase 1, \
1743         which delivered a 40% improvement in your ML inference pipeline throughput, we believe \
1744         the foundation is firmly established for an ambitious Phase 2 engagement.",
1745    )
1746    .first_line_indent(Length::inches(0.5));
1747
1748    doc.add_paragraph(
1749        "During our recent executive review, your team highlighted three priority areas for \
1750         the next phase of collaboration:",
1751    )
1752    .first_line_indent(Length::inches(0.5));
1753
1754    doc.add_numbered_list_item(
1755        "Real-time anomaly detection for your financial transaction monitoring system, \
1756         targeting sub-100ms latency at 50,000 transactions per second.",
1757        0,
1758    );
1759    doc.add_numbered_list_item(
1760        "Federated learning infrastructure to enable model training across your distributed \
1761         data centers without centralizing sensitive financial data.",
1762        0,
1763    );
1764    doc.add_numbered_list_item(
1765        "MLOps automation to reduce your model deployment cycle from the current 5 days \
1766         to under 4 hours.",
1767        0,
1768    );
1769
1770    doc.add_paragraph(
1771        "Our engineering team has prepared a detailed technical proposal addressing each of \
1772         these areas. We have allocated a dedicated team of eight senior engineers, led by \
1773         our CTO, to ensure continuity with the Phase 1 team your organization has already \
1774         built a productive working relationship with.",
1775    )
1776    .first_line_indent(Length::inches(0.5));
1777
1778    doc.add_paragraph(
1779        "I would welcome the opportunity to present our Phase 2 proposal in person. My \
1780         assistant will reach out to coordinate a meeting at your convenience during the \
1781         first week of March.",
1782    )
1783    .first_line_indent(Length::inches(0.5));
1784
1785    doc.add_paragraph("");
1786
1787    doc.add_paragraph("Warm regards,");
1788    doc.add_paragraph("");
1789    doc.add_paragraph("");
1790
1791    // Signature line
1792    doc.add_paragraph("")
1793        .border_bottom(BorderStyle::Single, 4, "4A5568");
1794    {
1795        let mut p = doc.add_paragraph("");
1796        p.add_run("Walter White").bold(true).size(12.0);
1797    }
1798    {
1799        let mut p = doc.add_paragraph("");
1800        p.add_run("Chief Executive Officer, Tensorbee")
1801            .italic(true)
1802            .size(10.0)
1803            .color("666666");
1804    }
1805    {
1806        let mut p = doc.add_paragraph("");
1807        p.add_run("walter@tensorbee.com | +1 (415) 555-0199")
1808            .size(10.0)
1809            .color("888888");
1810    }
1811
1812    doc
1813}
1814
1815// =============================================================================
1816// 7. EMPLOYMENT CONTRACT — Purple + charcoal scheme
1817// =============================================================================
1818fn generate_contract(_samples_dir: &Path) -> Document {
1819    let mut doc = Document::new();
1820
1821    // Colors: Purple #5B2C6F, Plum #8E44AD, Gray #2C3E50
1822    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1823    doc.set_margins(
1824        Length::inches(1.25),
1825        Length::inches(1.0),
1826        Length::inches(1.0),
1827        Length::inches(1.0),
1828    );
1829    doc.set_title("Employment Agreement");
1830    doc.set_author("Tensorbee HR Department");
1831    doc.set_subject("Employment Contract — Walter White");
1832    doc.set_keywords("employment, contract, agreement, Tensorbee");
1833
1834    doc.set_header("TENSORBEE — Employment Agreement");
1835    doc.set_footer("Employment Agreement — Walter White — Page");
1836
1837    // ── Title Block ──
1838    doc.add_paragraph("")
1839        .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1840    {
1841        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1842        p.add_run("EMPLOYMENT AGREEMENT")
1843            .bold(true)
1844            .size(24.0)
1845            .color("5B2C6F")
1846            .font("Georgia");
1847    }
1848    doc.add_paragraph("")
1849        .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1850    doc.add_paragraph("");
1851
1852    // ── Parties ──
1853    doc.add_paragraph(
1854        "This Employment Agreement (\"Agreement\") is entered into as of February 22, 2026 \
1855         (\"Effective Date\"), by and between:",
1856    );
1857
1858    doc.add_paragraph("");
1859
1860    {
1861        let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1862        p.add_run("EMPLOYER: ").bold(true);
1863        p.add_run(
1864            "Tensorbee, Inc., a Delaware corporation with its principal place of business at \
1865                   123 Innovation Drive, Suite 400, San Francisco, CA 94105 (\"Company\")",
1866        );
1867    }
1868    doc.add_paragraph("");
1869    {
1870        let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1871        p.add_run("EMPLOYEE: ").bold(true);
1872        p.add_run(
1873            "Walter White, residing at 308 Negra Arroyo Lane, Albuquerque, NM 87104 (\"Employee\")",
1874        );
1875    }
1876
1877    doc.add_paragraph("");
1878    doc.add_paragraph("The Company and Employee are collectively referred to as the \"Parties.\"");
1879    doc.add_paragraph("");
1880
1881    // ── Article 1: Position and Duties ──
1882    doc.add_paragraph("Article 1 — Position and Duties")
1883        .style("Heading1");
1884    {
1885        let mut p = doc.add_paragraph("");
1886        p.add_run("1.1 ").bold(true);
1887        p.add_run("The Company hereby employs the Employee as ");
1888        p.add_run("Chief Executive Officer (CEO)")
1889            .bold(true)
1890            .italic(true);
1891        p.add_run(", reporting directly to the Board of Directors.");
1892    }
1893    {
1894        let mut p = doc.add_paragraph("");
1895        p.add_run("1.2 ").bold(true);
1896        p.add_run("The Employee shall devote full working time, attention, and best efforts to \
1897                   the performance of duties as reasonably assigned by the Board, including but not limited to:");
1898    }
1899    doc.add_bullet_list_item(
1900        "Setting and executing the Company's strategic vision and business plan",
1901        0,
1902    );
1903    doc.add_bullet_list_item(
1904        "Overseeing all operations, engineering, and commercial activities",
1905        0,
1906    );
1907    doc.add_bullet_list_item(
1908        "Representing the Company to investors, customers, and the public",
1909        0,
1910    );
1911    doc.add_bullet_list_item("Recruiting, developing, and retaining key talent", 0);
1912
1913    {
1914        let mut p = doc.add_paragraph("");
1915        p.add_run("1.3 ").bold(true);
1916        p.add_run(
1917            "The Employee's primary work location shall be the Company's San Francisco \
1918                   headquarters, with reasonable travel as required by business needs.",
1919        );
1920    }
1921
1922    // ── Article 2: Compensation ──
1923    doc.add_paragraph("Article 2 — Compensation and Benefits")
1924        .style("Heading1");
1925    {
1926        let mut p = doc.add_paragraph("");
1927        p.add_run("2.1 Base Salary. ").bold(true);
1928        p.add_run("The Company shall pay the Employee an annual base salary of ");
1929        p.add_run("$375,000.00").bold(true);
1930        p.add_run(" (Three Hundred Seventy-Five Thousand Dollars), payable in accordance with the \
1931                   Company's standard payroll schedule, less applicable withholdings and deductions.");
1932    }
1933    {
1934        let mut p = doc.add_paragraph("");
1935        p.add_run("2.2 Annual Bonus. ").bold(true);
1936        p.add_run("The Employee shall be eligible for an annual performance bonus of up to ");
1937        p.add_run("40%").bold(true);
1938        p.add_run(
1939            " of base salary, based on achievement of mutually agreed performance objectives.",
1940        );
1941    }
1942    {
1943        let mut p = doc.add_paragraph("");
1944        p.add_run("2.3 Equity. ").bold(true);
1945        p.add_run("Subject to Board approval, the Employee shall receive a stock option grant of ");
1946        p.add_run("500,000 shares").bold(true);
1947        p.add_run(
1948            " of the Company's common stock, vesting over four (4) years with a one-year cliff, \
1949                   at an exercise price equal to the fair market value on the date of grant.",
1950        );
1951    }
1952
1953    // Compensation summary table
1954    doc.add_paragraph("");
1955    {
1956        let mut tbl = doc
1957            .add_table(5, 2)
1958            .borders(BorderStyle::Single, 4, "5B2C6F")
1959            .width_pct(70.0)
1960            .alignment(Alignment::Center);
1961        tbl.cell(0, 0).unwrap().set_text("Compensation Element");
1962        tbl.cell(0, 0).unwrap().shading("5B2C6F");
1963        tbl.cell(0, 1).unwrap().set_text("Value");
1964        tbl.cell(0, 1).unwrap().shading("5B2C6F");
1965        let items = [
1966            ("Base Salary", "$375,000/year"),
1967            ("Target Bonus", "Up to 40% ($150,000)"),
1968            ("Equity Grant", "500,000 shares (4yr vest)"),
1969            ("Total Target Comp", "$525,000 + equity"),
1970        ];
1971        for (i, (elem, val)) in items.iter().enumerate() {
1972            tbl.cell(i + 1, 0).unwrap().set_text(elem);
1973            tbl.cell(i + 1, 1).unwrap().set_text(val);
1974            if i == 3 {
1975                tbl.cell(i + 1, 0).unwrap().shading("E8DAEF");
1976                tbl.cell(i + 1, 1).unwrap().shading("E8DAEF");
1977            }
1978        }
1979    }
1980
1981    // ── Article 3: Benefits ──
1982    doc.add_paragraph("").page_break_before(true);
1983    doc.add_paragraph("Article 3 — Benefits").style("Heading1");
1984    {
1985        let mut p = doc.add_paragraph("");
1986        p.add_run("3.1 ").bold(true);
1987        p.add_run(
1988            "The Employee shall be entitled to participate in all benefit programs \
1989                   generally available to senior executives, including:",
1990        );
1991    }
1992    doc.add_bullet_list_item(
1993        "Medical, dental, and vision insurance (100% premium coverage for employee and dependents)",
1994        0,
1995    );
1996    doc.add_bullet_list_item("401(k) retirement plan with 6% company match", 0);
1997    doc.add_bullet_list_item("Life insurance and long-term disability coverage", 0);
1998    doc.add_bullet_list_item("Annual professional development allowance of $10,000", 0);
1999
2000    {
2001        let mut p = doc.add_paragraph("");
2002        p.add_run("3.2 Paid Time Off. ").bold(true);
2003        p.add_run(
2004            "The Employee shall receive 25 days of paid vacation per year, plus Company holidays, \
2005                   accruing on a monthly basis.",
2006        );
2007    }
2008
2009    // ── Article 4: Term and Termination ──
2010    doc.add_paragraph("Article 4 — Term and Termination")
2011        .style("Heading1");
2012    {
2013        let mut p = doc.add_paragraph("");
2014        p.add_run("4.1 At-Will Employment. ").bold(true);
2015        p.add_run(
2016            "This Agreement is for at-will employment and may be terminated by either Party \
2017                   at any time, with or without cause, subject to the notice provisions herein.",
2018        );
2019    }
2020    {
2021        let mut p = doc.add_paragraph("");
2022        p.add_run("4.2 Notice Period. ").bold(true);
2023        p.add_run("Either Party shall provide ");
2024        p.add_run("ninety (90) days").bold(true);
2025        p.add_run(" written notice of termination, or pay in lieu thereof.");
2026    }
2027    {
2028        let mut p = doc.add_paragraph("");
2029        p.add_run("4.3 Severance. ").bold(true);
2030        p.add_run("In the event of termination by the Company without Cause, the Employee shall \
2031                   receive (a) twelve (12) months of base salary continuation, (b) pro-rata bonus \
2032                   for the year of termination, and (c) twelve (12) months of COBRA premium coverage.");
2033    }
2034
2035    // ── Article 5: Confidentiality ──
2036    doc.add_paragraph("Article 5 — Confidentiality and IP")
2037        .style("Heading1");
2038    {
2039        let mut p = doc.add_paragraph("");
2040        p.add_run("5.1 ").bold(true);
2041        p.add_run(
2042            "The Employee agrees to maintain strict confidentiality of all proprietary \
2043                   information, trade secrets, and business plans of the Company during and after \
2044                   employment.",
2045        );
2046    }
2047    {
2048        let mut p = doc.add_paragraph("");
2049        p.add_run("5.2 ").bold(true);
2050        p.add_run(
2051            "All intellectual property, inventions, and works of authorship created by the \
2052                   Employee during the term of employment and within the scope of duties shall be \
2053                   the sole and exclusive property of the Company.",
2054        );
2055    }
2056
2057    // ── Article 6: Non-Compete ──
2058    doc.add_paragraph("Article 6 — Non-Competition")
2059        .style("Heading1");
2060    {
2061        let mut p = doc.add_paragraph("");
2062        p.add_run("6.1 ").bold(true);
2063        p.add_run("For a period of twelve (12) months following termination, the Employee agrees \
2064                   not to engage in any business that directly competes with the Company's core \
2065                   business of AI infrastructure and ML operations services, within the United States.");
2066    }
2067
2068    // ── Article 7: General Provisions ──
2069    doc.add_paragraph("Article 7 — General Provisions")
2070        .style("Heading1");
2071    {
2072        let mut p = doc.add_paragraph("");
2073        p.add_run("7.1 Governing Law. ").bold(true);
2074        p.add_run(
2075            "This Agreement shall be governed by and construed in accordance with the laws of \
2076                   the State of California.",
2077        );
2078    }
2079    {
2080        let mut p = doc.add_paragraph("");
2081        p.add_run("7.2 Entire Agreement. ").bold(true);
2082        p.add_run(
2083            "This Agreement constitutes the entire agreement between the Parties and supersedes \
2084                   all prior negotiations, representations, and agreements.",
2085        );
2086    }
2087    {
2088        let mut p = doc.add_paragraph("");
2089        p.add_run("7.3 Amendment. ").bold(true);
2090        p.add_run(
2091            "This Agreement may only be amended by a written instrument signed by both Parties.",
2092        );
2093    }
2094
2095    // ── Signature Block ──
2096    doc.add_paragraph("").page_break_before(true);
2097    doc.add_paragraph("IN WITNESS WHEREOF, the Parties have executed this Employment Agreement as of the Effective Date.")
2098        .space_after(Length::pt(24.0));
2099
2100    // Signature table
2101    {
2102        let mut tbl = doc.add_table(6, 2).width_pct(100.0);
2103        tbl.cell(0, 0).unwrap().set_text("FOR THE COMPANY:");
2104        tbl.cell(0, 0).unwrap().shading("5B2C6F");
2105        tbl.cell(0, 1).unwrap().set_text("EMPLOYEE:");
2106        tbl.cell(0, 1).unwrap().shading("5B2C6F");
2107
2108        tbl.cell(1, 0).unwrap().set_text("");
2109        tbl.cell(1, 1).unwrap().set_text("");
2110        tbl.row(1).unwrap().height(Length::pt(40.0));
2111
2112        tbl.cell(2, 0)
2113            .unwrap()
2114            .set_text("Signature: ___________________________");
2115        tbl.cell(2, 1)
2116            .unwrap()
2117            .set_text("Signature: ___________________________");
2118        tbl.cell(3, 0)
2119            .unwrap()
2120            .set_text("Name: Board Representative");
2121        tbl.cell(3, 1).unwrap().set_text("Name: Walter White");
2122        tbl.cell(4, 0)
2123            .unwrap()
2124            .set_text("Title: Chair, Board of Directors");
2125        tbl.cell(4, 1)
2126            .unwrap()
2127            .set_text("Title: Chief Executive Officer");
2128        tbl.cell(5, 0).unwrap().set_text("Date: _______________");
2129        tbl.cell(5, 1).unwrap().set_text("Date: _______________");
2130    }
2131
2132    doc
2133}
Source

pub fn append(&mut self, other: &Document)

Append the content of another document to this document.

Copies all body content (paragraphs and tables) from the other document. Handles style deduplication and numbering remapping.

Examples found in repository?
examples/generate_all_samples.rs (line 648)
97fn generate_feature_showcase(_samples_dir: &Path) -> Document {
98    let mut doc = Document::new();
99
100    // ── Page Setup & Metadata ──
101    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
102    doc.set_margins(
103        Length::inches(1.0),
104        Length::inches(1.0),
105        Length::inches(1.0),
106        Length::inches(1.0),
107    );
108    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
109    doc.set_gutter(Length::twips(0));
110    doc.set_title("rdocx Feature Showcase");
111    doc.set_author("rdocx Sample Generator");
112    doc.set_subject("Comprehensive feature demonstration");
113    doc.set_keywords("rdocx, docx, rust, sample, showcase");
114
115    // Headers & Footers with different first page
116    doc.set_different_first_page(true);
117    doc.set_first_page_header("rdocx");
118    doc.set_first_page_footer("Feature Showcase — Cover Page");
119    doc.set_header("rdocx Feature Showcase");
120    doc.set_footer("Generated by rdocx");
121
122    // ── COVER PAGE ──
123    let bg = create_sample_png(612, 792, [20, 45, 90]);
124    doc.add_background_image(&bg, "cover_bg.png");
125
126    for _ in 0..3 {
127        doc.add_paragraph("");
128    }
129    {
130        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
131        p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
132    }
133    {
134        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
135        p.add_run("Complete Feature Showcase")
136            .size(28.0)
137            .color("FFFFFF")
138            .italic(true);
139    }
140    doc.add_paragraph("");
141    {
142        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
143        p.add_run("Every feature of the rdocx Rust library")
144            .size(13.0)
145            .color("B0C4DE");
146    }
147    {
148        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
149        p.add_run("demonstrated in a single document.")
150            .size(13.0)
151            .color("B0C4DE");
152    }
153
154    // ── TABLE OF CONTENTS ──
155    doc.add_paragraph("").page_break_before(true);
156    doc.insert_toc(doc.content_count(), 3);
157
158    // ── SECTION 1: TEXT FORMATTING ──
159    doc.add_paragraph("").page_break_before(true);
160    doc.add_paragraph("1. Text Formatting").style("Heading1");
161
162    // Paragraph Alignment
163    doc.add_paragraph("1.1 Paragraph Alignment")
164        .style("Heading2");
165    doc.add_paragraph("Left-aligned paragraph (default).")
166        .alignment(Alignment::Left);
167    doc.add_paragraph("Center-aligned paragraph.")
168        .alignment(Alignment::Center);
169    doc.add_paragraph("Right-aligned paragraph.")
170        .alignment(Alignment::Right);
171    doc.add_paragraph(
172        "Justified paragraph — this text is long enough to demonstrate how justified alignment \
173         distributes extra space across word gaps so lines fill the full width of the text area.",
174    )
175    .alignment(Alignment::Justify);
176
177    doc.add_paragraph("");
178
179    // Run Formatting
180    doc.add_paragraph("1.2 Run Formatting").style("Heading2");
181    {
182        let mut p = doc.add_paragraph("");
183        p.add_run("Bold").bold(true);
184        p.add_run(" | ");
185        p.add_run("Italic").italic(true);
186        p.add_run(" | ");
187        p.add_run("Bold Italic").bold(true).italic(true);
188        p.add_run(" | ");
189        p.add_run("Underline").underline(true);
190        p.add_run(" | ");
191        p.add_run("Strikethrough").strike(true);
192        p.add_run(" | ");
193        p.add_run("Double Strike").double_strike(true);
194    }
195    {
196        let mut p = doc.add_paragraph("");
197        p.add_run("Red").color("FF0000");
198        p.add_run(" | ");
199        p.add_run("Blue").color("0000FF");
200        p.add_run(" | ");
201        p.add_run("Green").color("00AA00");
202        p.add_run(" | ");
203        p.add_run("Highlighted").highlight("FFFF00");
204    }
205    {
206        let mut p = doc.add_paragraph("");
207        p.add_run("8pt").size(8.0);
208        p.add_run(" | ");
209        p.add_run("11pt").size(11.0);
210        p.add_run(" | ");
211        p.add_run("16pt").size(16.0);
212        p.add_run(" | ");
213        p.add_run("24pt").size(24.0);
214    }
215    {
216        let mut p = doc.add_paragraph("");
217        p.add_run("Arial").font("Arial");
218        p.add_run(" | ");
219        p.add_run("Times New Roman").font("Times New Roman");
220        p.add_run(" | ");
221        p.add_run("Courier New").font("Courier New");
222    }
223    {
224        let mut p = doc.add_paragraph("");
225        p.add_run("H");
226        p.add_run("2").subscript();
227        p.add_run("O (subscript) | E=mc");
228        p.add_run("2").superscript();
229        p.add_run(" (superscript)");
230    }
231    {
232        let mut p = doc.add_paragraph("");
233        p.add_run("ALL CAPS").all_caps(true);
234        p.add_run(" | ");
235        p.add_run("Small Caps").small_caps(true);
236        p.add_run(" | ");
237        p.add_run("Expanded").character_spacing(Length::twips(40));
238        p.add_run(" | ");
239        p.add_run("Hidden text (not visible)").hidden(true);
240    }
241
242    doc.add_paragraph("");
243
244    // Underline Styles
245    doc.add_paragraph("1.3 Underline Styles").style("Heading2");
246    {
247        let mut p = doc.add_paragraph("");
248        p.add_run("Single ")
249            .underline_style(rdocx::UnderlineStyle::Single);
250        p.add_run("Double ")
251            .underline_style(rdocx::UnderlineStyle::Double);
252        p.add_run("Thick ")
253            .underline_style(rdocx::UnderlineStyle::Thick);
254        p.add_run("Dotted ")
255            .underline_style(rdocx::UnderlineStyle::Dotted);
256        p.add_run("Dash ")
257            .underline_style(rdocx::UnderlineStyle::Dash);
258        p.add_run("Wave ")
259            .underline_style(rdocx::UnderlineStyle::Wave);
260    }
261
262    // ── SECTION 2: PARAGRAPH FORMATTING ──
263    doc.add_paragraph("").page_break_before(true);
264    doc.add_paragraph("2. Paragraph Formatting")
265        .style("Heading1");
266
267    doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
268    doc.add_paragraph("Paragraph with light green shading")
269        .shading("E2EFDA");
270    doc.add_paragraph("Paragraph with bottom border")
271        .border_bottom(BorderStyle::Single, 6, "2E75B6");
272    doc.add_paragraph("Paragraph with all borders (red)")
273        .border_all(BorderStyle::Single, 4, "FF0000");
274
275    doc.add_paragraph("2.2 Indentation").style("Heading2");
276    doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
277        .indent_left(Length::inches(1.0))
278        .hanging_indent(Length::inches(0.5));
279    doc.add_paragraph("0.5-inch first-line indent on this paragraph")
280        .first_line_indent(Length::inches(0.5));
281    doc.add_paragraph("Both left and right indent (0.75 inches each)")
282        .indent_left(Length::inches(0.75))
283        .indent_right(Length::inches(0.75));
284
285    doc.add_paragraph("2.3 Spacing & Line Height")
286        .style("Heading2");
287    doc.add_paragraph("Extra space before (24pt) and after (12pt)")
288        .space_before(Length::pt(24.0))
289        .space_after(Length::pt(12.0));
290    doc.add_paragraph(
291        "Double line spacing paragraph. This text should have extra vertical space between \
292         lines to demonstrate line_spacing_multiple(2.0).",
293    )
294    .line_spacing_multiple(2.0);
295    doc.add_paragraph("Exact 20pt line spacing (fixed height).")
296        .line_spacing(20.0);
297
298    doc.add_paragraph("2.4 Pagination Controls")
299        .style("Heading2");
300    doc.add_paragraph("keep_with_next — this paragraph stays with the next")
301        .keep_with_next(true);
302    doc.add_paragraph("(This paragraph was kept with the one above.)");
303    doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
304        .keep_together(true);
305    doc.add_paragraph("widow_control — prevents widow/orphan lines")
306        .widow_control(true);
307
308    // ── SECTION 3: LISTS ──
309    doc.add_paragraph("").page_break_before(true);
310    doc.add_paragraph("3. Lists").style("Heading1");
311
312    doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
313    doc.add_bullet_list_item("First item", 0);
314    doc.add_bullet_list_item("Second item", 0);
315    doc.add_bullet_list_item("Nested level 1", 1);
316    doc.add_bullet_list_item("Nested level 2", 2);
317    doc.add_bullet_list_item("Back to level 1", 1);
318    doc.add_bullet_list_item("Third item", 0);
319
320    doc.add_paragraph("");
321
322    doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
323    doc.add_numbered_list_item("First numbered item", 0);
324    doc.add_numbered_list_item("Second numbered item", 0);
325    doc.add_numbered_list_item("Sub-item A", 1);
326    doc.add_numbered_list_item("Sub-item B", 1);
327    doc.add_numbered_list_item("Third numbered item", 0);
328
329    // ── SECTION 4: TAB STOPS ──
330    doc.add_paragraph("");
331    doc.add_paragraph("4. Tab Stops").style("Heading1");
332
333    doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
334    doc.add_paragraph("Left\tCenter\tRight\tDecimal")
335        .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
336        .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
337        .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
338        .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
339
340    doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
341    doc.add_paragraph("Item\t\tPrice")
342        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
343        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
344        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
345    doc.add_paragraph("Widget\t\t$19.99")
346        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
347        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
348        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
349    doc.add_paragraph("Gadget\t\t$249.50")
350        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
351        .add_tab_stop_with_leader(
352            TabAlignment::Right,
353            Length::inches(4.0),
354            TabLeader::Underscore,
355        )
356        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
357
358    // ── SECTION 5: TABLES ──
359    doc.add_paragraph("").page_break_before(true);
360    doc.add_paragraph("5. Tables").style("Heading1");
361
362    doc.add_paragraph("5.1 Basic Table").style("Heading2");
363    {
364        let mut tbl = doc
365            .add_table(4, 3)
366            .borders(BorderStyle::Single, 4, "000000");
367        for col in 0..3 {
368            tbl.cell(0, col).unwrap().shading("2E75B6");
369        }
370        tbl.cell(0, 0).unwrap().set_text("Name");
371        tbl.cell(0, 1).unwrap().set_text("Role");
372        tbl.cell(0, 2).unwrap().set_text("Location");
373        tbl.cell(1, 0).unwrap().set_text("Walter White");
374        tbl.cell(1, 1).unwrap().set_text("CEO");
375        tbl.cell(1, 2).unwrap().set_text("Albuquerque");
376        tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
377        tbl.cell(2, 1).unwrap().set_text("CTO");
378        tbl.cell(2, 2).unwrap().set_text("Remote");
379        tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
380        tbl.cell(3, 1).unwrap().set_text("Security");
381        tbl.cell(3, 2).unwrap().set_text("Washington");
382    }
383
384    doc.add_paragraph("");
385
386    doc.add_paragraph("5.2 Column Span & Cell Shading")
387        .style("Heading2");
388    {
389        let mut tbl = doc
390            .add_table(3, 4)
391            .borders(BorderStyle::Single, 4, "000000")
392            .width_pct(100.0);
393        tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
394        tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
395        tbl.cell(1, 0).unwrap().set_text("Region");
396        tbl.cell(1, 0).unwrap().shading("D6E4F0");
397        tbl.cell(1, 1).unwrap().set_text("Q1");
398        tbl.cell(1, 1).unwrap().shading("D6E4F0");
399        tbl.cell(1, 2).unwrap().set_text("Q2");
400        tbl.cell(1, 2).unwrap().shading("D6E4F0");
401        tbl.cell(1, 3).unwrap().set_text("Total");
402        tbl.cell(1, 3).unwrap().shading("D6E4F0");
403        tbl.cell(2, 0).unwrap().set_text("Americas");
404        tbl.cell(2, 1).unwrap().set_text("$2.4M");
405        tbl.cell(2, 2).unwrap().set_text("$2.7M");
406        tbl.cell(2, 3).unwrap().set_text("$5.1M");
407    }
408
409    doc.add_paragraph("");
410
411    doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
412    {
413        let mut tbl = doc
414            .add_table(4, 3)
415            .borders(BorderStyle::Single, 4, "000000");
416        tbl.cell(0, 0).unwrap().set_text("Category");
417        tbl.cell(0, 0).unwrap().shading("E2EFDA");
418        tbl.cell(0, 1).unwrap().set_text("Item");
419        tbl.cell(0, 1).unwrap().shading("E2EFDA");
420        tbl.cell(0, 2).unwrap().set_text("Price");
421        tbl.cell(0, 2).unwrap().shading("E2EFDA");
422        tbl.cell(1, 0).unwrap().set_text("Hardware");
423        tbl.cell(1, 0).unwrap().v_merge_restart();
424        tbl.cell(1, 1).unwrap().set_text("Laptop");
425        tbl.cell(1, 2).unwrap().set_text("$1,200");
426        tbl.cell(2, 0).unwrap().v_merge_continue();
427        tbl.cell(2, 1).unwrap().set_text("Monitor");
428        tbl.cell(2, 2).unwrap().set_text("$450");
429        tbl.cell(3, 0).unwrap().set_text("Software");
430        tbl.cell(3, 1).unwrap().set_text("IDE License");
431        tbl.cell(3, 2).unwrap().set_text("$200/yr");
432    }
433
434    doc.add_paragraph("");
435
436    doc.add_paragraph("5.4 Nested Table").style("Heading2");
437    {
438        let mut tbl = doc
439            .add_table(2, 2)
440            .borders(BorderStyle::Single, 6, "2E75B6");
441        tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
442        tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
443        tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
444        {
445            let mut cell = tbl.cell(1, 1).unwrap();
446            cell.set_text("Nested table below:");
447            let mut nested = cell
448                .add_table(2, 2)
449                .borders(BorderStyle::Single, 2, "FF6600");
450            nested.cell(0, 0).unwrap().set_text("A");
451            nested.cell(0, 1).unwrap().set_text("B");
452            nested.cell(1, 0).unwrap().set_text("C");
453            nested.cell(1, 1).unwrap().set_text("D");
454        }
455    }
456
457    doc.add_paragraph("");
458
459    doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
460        .style("Heading2");
461    {
462        let mut tbl = doc
463            .add_table(2, 3)
464            .borders(BorderStyle::Single, 4, "666666");
465        tbl.row(0).unwrap().height(Length::pt(50.0)).header();
466        tbl.cell(0, 0).unwrap().set_text("Top");
467        tbl.cell(0, 0)
468            .unwrap()
469            .vertical_alignment(VerticalAlignment::Top)
470            .shading("FFF2CC");
471        tbl.cell(0, 1).unwrap().set_text("Center");
472        tbl.cell(0, 1)
473            .unwrap()
474            .vertical_alignment(VerticalAlignment::Center)
475            .shading("D9E2F3");
476        tbl.cell(0, 2).unwrap().set_text("Bottom");
477        tbl.cell(0, 2)
478            .unwrap()
479            .vertical_alignment(VerticalAlignment::Bottom)
480            .shading("E2EFDA");
481        tbl.row(1).unwrap().cant_split();
482        tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
483        tbl.cell(1, 0).unwrap().no_wrap();
484        tbl.cell(1, 1).unwrap().set_text("Fixed width");
485        tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
486        tbl.cell(1, 2).unwrap().set_text("Normal");
487    }
488
489    // ── SECTION 6: IMAGES ──
490    doc.add_paragraph("").page_break_before(true);
491    doc.add_paragraph("6. Images").style("Heading1");
492
493    doc.add_paragraph("6.1 Inline Image").style("Heading2");
494    doc.add_paragraph("A blue gradient image below:");
495    let img = create_sample_png(200, 50, [0, 80, 200]);
496    doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
497
498    doc.add_paragraph("");
499    doc.add_paragraph("6.2 Header Image").style("Heading2");
500    let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
501    doc.set_header_image(
502        &hdr_img,
503        "header_logo.png",
504        Length::inches(2.0),
505        Length::inches(0.2),
506    );
507    doc.add_paragraph("The header now contains an inline image (check the top of this page).");
508
509    doc.add_paragraph("");
510    doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
511
512    // ── SECTION 7: CONTENT MANIPULATION ──
513    doc.add_paragraph("").page_break_before(true);
514    doc.add_paragraph("7. Content Manipulation")
515        .style("Heading1");
516
517    doc.add_paragraph("7.1 Placeholder Replacement")
518        .style("Heading2");
519    doc.add_paragraph("Customer: {{customer}}");
520    doc.add_paragraph("Date: {{date}}");
521    doc.add_paragraph("Reference: {{ref_number}}");
522    {
523        let mut tbl = doc
524            .add_table(2, 2)
525            .borders(BorderStyle::Single, 4, "000000");
526        tbl.cell(0, 0).unwrap().set_text("Project");
527        tbl.cell(0, 0).unwrap().shading("D6E4F0");
528        tbl.cell(0, 1).unwrap().set_text("{{project}}");
529        tbl.cell(1, 0).unwrap().set_text("Status");
530        tbl.cell(1, 0).unwrap().shading("D6E4F0");
531        tbl.cell(1, 1).unwrap().set_text("{{status}}");
532    }
533
534    let mut replacements = HashMap::new();
535    replacements.insert("{{customer}}", "Tensorbee Inc.");
536    replacements.insert("{{date}}", "February 22, 2026");
537    replacements.insert("{{ref_number}}", "TB-2026-001");
538    replacements.insert("{{project}}", "Infrastructure Upgrade");
539    replacements.insert("{{status}}", "In Progress");
540    let n = doc.replace_all(&replacements);
541    doc.add_paragraph(&format!("({n} placeholders replaced above)"));
542
543    doc.add_paragraph("");
544
545    doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
546    doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
547    let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
548    doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
549
550    doc.add_paragraph("");
551
552    doc.add_paragraph("7.3 Content Insertion").style("Heading2");
553    doc.add_paragraph("Section A: First content.");
554    doc.add_paragraph("Section C: Third content.");
555    if let Some(idx) = doc.find_content_index("Section C") {
556        doc.insert_paragraph(
557            idx,
558            "Section B: Inserted between A and C via find_content_index().",
559        );
560    }
561
562    // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
563    doc.add_paragraph("").section_break(SectionBreak::NextPage);
564    doc.add_paragraph("8. Section Breaks & Orientation")
565        .style("Heading1");
566    doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
567
568    {
569        let mut tbl = doc
570            .add_table(3, 7)
571            .borders(BorderStyle::Single, 4, "2E75B6");
572        let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
573        for (c, h) in headers.iter().enumerate() {
574            tbl.cell(0, c).unwrap().set_text(h);
575            tbl.cell(0, c).unwrap().shading("2E75B6");
576        }
577        let data = [
578            [
579                "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
580            ],
581            [
582                "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
583            ],
584        ];
585        for (r, row) in data.iter().enumerate() {
586            for (c, v) in row.iter().enumerate() {
587                tbl.cell(r + 1, c).unwrap().set_text(v);
588            }
589        }
590    }
591
592    doc.add_paragraph("")
593        .section_break(SectionBreak::NextPage)
594        .section_landscape();
595
596    // ── SECTION 9: CUSTOM STYLES ──
597    doc.add_paragraph("9. Custom Styles").style("Heading1");
598    doc.add_style(
599        StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
600            .based_on("Normal")
601            .paragraph_properties(rdocx_oxml::properties::CT_PPr {
602                shading: Some(rdocx_oxml::properties::CT_Shd {
603                    val: "clear".to_string(),
604                    color: None,
605                    fill: Some("FFF2CC".to_string()),
606                }),
607                ..Default::default()
608            })
609            .run_properties(rdocx_oxml::properties::CT_RPr {
610                bold: Some(true),
611                color: Some("C45911".to_string()),
612                ..Default::default()
613            }),
614    );
615    doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
616        .style("CustomHighlight");
617
618    doc.add_paragraph("");
619
620    // ── SECTION 10: DOCUMENT INTELLIGENCE ──
621    doc.add_paragraph("10. Document Intelligence API")
622        .style("Heading1");
623
624    let wc = doc.word_count();
625    let hc = doc.headings().len();
626    let ic = doc.images().len();
627    let lc = doc.links().len();
628    doc.add_paragraph(&format!("Word count: {wc}"));
629    doc.add_paragraph(&format!("Heading count: {hc}"));
630    doc.add_paragraph(&format!("Image count: {ic}"));
631    doc.add_paragraph(&format!("Link count: {lc}"));
632
633    let outline = doc.document_outline();
634    doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
635
636    let issues = doc.audit_accessibility();
637    doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
638    for issue in &issues {
639        doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
640    }
641
642    // ── SECTION 11: DOCUMENT MERGING ──
643    doc.add_paragraph("");
644    doc.add_paragraph("11. Document Merging").style("Heading1");
645    {
646        let mut other = Document::new();
647        other.add_paragraph("This paragraph was merged from another document using append().");
648        doc.append(&other);
649    }
650
651    doc.add_paragraph("");
652
653    // ── SUMMARY ──
654    doc.add_paragraph("Summary of Demonstrated Features")
655        .style("Heading1");
656    let features = [
657        "Page setup: size, margins, header/footer distance, gutter",
658        "Document metadata: title, author, subject, keywords",
659        "Headers and footers: text, images, different first page",
660        "Background images (full-page behind text)",
661        "Table of Contents generation with bookmarks",
662        "Text formatting: bold, italic, underline styles, strike, color, size, font",
663        "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
664        "Paragraph formatting: alignment, borders, shading, spacing, indentation",
665        "Pagination controls: keep-with-next, keep-together, widow control",
666        "Bullet and numbered lists with nesting",
667        "Tab stops with dot/underscore/hyphen leaders",
668        "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
669        "Row properties: header rows, exact height, cant-split",
670        "Cell properties: width, no-wrap, vertical alignment",
671        "Inline images and header images",
672        "Placeholder replacement (single and batch)",
673        "Regex-based find and replace",
674        "Content insertion at specific positions",
675        "Section breaks with mixed portrait/landscape",
676        "Custom paragraph and character styles",
677        "Document intelligence: word count, headings, outline, images, links",
678        "Accessibility audit",
679        "Document merging (append)",
680        "Export: DOCX, PDF, HTML, Markdown",
681    ];
682    for f in &features {
683        doc.add_bullet_list_item(f, 0);
684    }
685    doc.add_paragraph("");
686    doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
687        .alignment(Alignment::Center)
688        .shading("E2EFDA")
689        .border_all(BorderStyle::Single, 2, "00AA00");
690
691    doc
692}
Source

pub fn append_with_break(&mut self, other: &Document, break_type: SectionBreak)

Append the content of another document with a section break.

Source

pub fn insert_document(&mut self, index: usize, other: &Document)

Insert the content of another document at a specified body index.

An index past the end is clamped to the end rather than panicking.

Source

pub fn insert_toc(&mut self, index: usize, max_level: u32)

Insert a Table of Contents at the given body content index.

Scans the document for heading paragraphs (Heading1..HeadingN where N <= max_level), inserts bookmark markers at each heading, and generates TOC entry paragraphs with internal hyperlinks and dot-leader tab stops.

§Arguments
  • index - Body content index at which to insert the TOC
  • max_level - Maximum heading level to include (1-9, typically 3)
Examples found in repository?
examples/generate_all_samples.rs (line 156)
97fn generate_feature_showcase(_samples_dir: &Path) -> Document {
98    let mut doc = Document::new();
99
100    // ── Page Setup & Metadata ──
101    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
102    doc.set_margins(
103        Length::inches(1.0),
104        Length::inches(1.0),
105        Length::inches(1.0),
106        Length::inches(1.0),
107    );
108    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
109    doc.set_gutter(Length::twips(0));
110    doc.set_title("rdocx Feature Showcase");
111    doc.set_author("rdocx Sample Generator");
112    doc.set_subject("Comprehensive feature demonstration");
113    doc.set_keywords("rdocx, docx, rust, sample, showcase");
114
115    // Headers & Footers with different first page
116    doc.set_different_first_page(true);
117    doc.set_first_page_header("rdocx");
118    doc.set_first_page_footer("Feature Showcase — Cover Page");
119    doc.set_header("rdocx Feature Showcase");
120    doc.set_footer("Generated by rdocx");
121
122    // ── COVER PAGE ──
123    let bg = create_sample_png(612, 792, [20, 45, 90]);
124    doc.add_background_image(&bg, "cover_bg.png");
125
126    for _ in 0..3 {
127        doc.add_paragraph("");
128    }
129    {
130        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
131        p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
132    }
133    {
134        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
135        p.add_run("Complete Feature Showcase")
136            .size(28.0)
137            .color("FFFFFF")
138            .italic(true);
139    }
140    doc.add_paragraph("");
141    {
142        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
143        p.add_run("Every feature of the rdocx Rust library")
144            .size(13.0)
145            .color("B0C4DE");
146    }
147    {
148        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
149        p.add_run("demonstrated in a single document.")
150            .size(13.0)
151            .color("B0C4DE");
152    }
153
154    // ── TABLE OF CONTENTS ──
155    doc.add_paragraph("").page_break_before(true);
156    doc.insert_toc(doc.content_count(), 3);
157
158    // ── SECTION 1: TEXT FORMATTING ──
159    doc.add_paragraph("").page_break_before(true);
160    doc.add_paragraph("1. Text Formatting").style("Heading1");
161
162    // Paragraph Alignment
163    doc.add_paragraph("1.1 Paragraph Alignment")
164        .style("Heading2");
165    doc.add_paragraph("Left-aligned paragraph (default).")
166        .alignment(Alignment::Left);
167    doc.add_paragraph("Center-aligned paragraph.")
168        .alignment(Alignment::Center);
169    doc.add_paragraph("Right-aligned paragraph.")
170        .alignment(Alignment::Right);
171    doc.add_paragraph(
172        "Justified paragraph — this text is long enough to demonstrate how justified alignment \
173         distributes extra space across word gaps so lines fill the full width of the text area.",
174    )
175    .alignment(Alignment::Justify);
176
177    doc.add_paragraph("");
178
179    // Run Formatting
180    doc.add_paragraph("1.2 Run Formatting").style("Heading2");
181    {
182        let mut p = doc.add_paragraph("");
183        p.add_run("Bold").bold(true);
184        p.add_run(" | ");
185        p.add_run("Italic").italic(true);
186        p.add_run(" | ");
187        p.add_run("Bold Italic").bold(true).italic(true);
188        p.add_run(" | ");
189        p.add_run("Underline").underline(true);
190        p.add_run(" | ");
191        p.add_run("Strikethrough").strike(true);
192        p.add_run(" | ");
193        p.add_run("Double Strike").double_strike(true);
194    }
195    {
196        let mut p = doc.add_paragraph("");
197        p.add_run("Red").color("FF0000");
198        p.add_run(" | ");
199        p.add_run("Blue").color("0000FF");
200        p.add_run(" | ");
201        p.add_run("Green").color("00AA00");
202        p.add_run(" | ");
203        p.add_run("Highlighted").highlight("FFFF00");
204    }
205    {
206        let mut p = doc.add_paragraph("");
207        p.add_run("8pt").size(8.0);
208        p.add_run(" | ");
209        p.add_run("11pt").size(11.0);
210        p.add_run(" | ");
211        p.add_run("16pt").size(16.0);
212        p.add_run(" | ");
213        p.add_run("24pt").size(24.0);
214    }
215    {
216        let mut p = doc.add_paragraph("");
217        p.add_run("Arial").font("Arial");
218        p.add_run(" | ");
219        p.add_run("Times New Roman").font("Times New Roman");
220        p.add_run(" | ");
221        p.add_run("Courier New").font("Courier New");
222    }
223    {
224        let mut p = doc.add_paragraph("");
225        p.add_run("H");
226        p.add_run("2").subscript();
227        p.add_run("O (subscript) | E=mc");
228        p.add_run("2").superscript();
229        p.add_run(" (superscript)");
230    }
231    {
232        let mut p = doc.add_paragraph("");
233        p.add_run("ALL CAPS").all_caps(true);
234        p.add_run(" | ");
235        p.add_run("Small Caps").small_caps(true);
236        p.add_run(" | ");
237        p.add_run("Expanded").character_spacing(Length::twips(40));
238        p.add_run(" | ");
239        p.add_run("Hidden text (not visible)").hidden(true);
240    }
241
242    doc.add_paragraph("");
243
244    // Underline Styles
245    doc.add_paragraph("1.3 Underline Styles").style("Heading2");
246    {
247        let mut p = doc.add_paragraph("");
248        p.add_run("Single ")
249            .underline_style(rdocx::UnderlineStyle::Single);
250        p.add_run("Double ")
251            .underline_style(rdocx::UnderlineStyle::Double);
252        p.add_run("Thick ")
253            .underline_style(rdocx::UnderlineStyle::Thick);
254        p.add_run("Dotted ")
255            .underline_style(rdocx::UnderlineStyle::Dotted);
256        p.add_run("Dash ")
257            .underline_style(rdocx::UnderlineStyle::Dash);
258        p.add_run("Wave ")
259            .underline_style(rdocx::UnderlineStyle::Wave);
260    }
261
262    // ── SECTION 2: PARAGRAPH FORMATTING ──
263    doc.add_paragraph("").page_break_before(true);
264    doc.add_paragraph("2. Paragraph Formatting")
265        .style("Heading1");
266
267    doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
268    doc.add_paragraph("Paragraph with light green shading")
269        .shading("E2EFDA");
270    doc.add_paragraph("Paragraph with bottom border")
271        .border_bottom(BorderStyle::Single, 6, "2E75B6");
272    doc.add_paragraph("Paragraph with all borders (red)")
273        .border_all(BorderStyle::Single, 4, "FF0000");
274
275    doc.add_paragraph("2.2 Indentation").style("Heading2");
276    doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
277        .indent_left(Length::inches(1.0))
278        .hanging_indent(Length::inches(0.5));
279    doc.add_paragraph("0.5-inch first-line indent on this paragraph")
280        .first_line_indent(Length::inches(0.5));
281    doc.add_paragraph("Both left and right indent (0.75 inches each)")
282        .indent_left(Length::inches(0.75))
283        .indent_right(Length::inches(0.75));
284
285    doc.add_paragraph("2.3 Spacing & Line Height")
286        .style("Heading2");
287    doc.add_paragraph("Extra space before (24pt) and after (12pt)")
288        .space_before(Length::pt(24.0))
289        .space_after(Length::pt(12.0));
290    doc.add_paragraph(
291        "Double line spacing paragraph. This text should have extra vertical space between \
292         lines to demonstrate line_spacing_multiple(2.0).",
293    )
294    .line_spacing_multiple(2.0);
295    doc.add_paragraph("Exact 20pt line spacing (fixed height).")
296        .line_spacing(20.0);
297
298    doc.add_paragraph("2.4 Pagination Controls")
299        .style("Heading2");
300    doc.add_paragraph("keep_with_next — this paragraph stays with the next")
301        .keep_with_next(true);
302    doc.add_paragraph("(This paragraph was kept with the one above.)");
303    doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
304        .keep_together(true);
305    doc.add_paragraph("widow_control — prevents widow/orphan lines")
306        .widow_control(true);
307
308    // ── SECTION 3: LISTS ──
309    doc.add_paragraph("").page_break_before(true);
310    doc.add_paragraph("3. Lists").style("Heading1");
311
312    doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
313    doc.add_bullet_list_item("First item", 0);
314    doc.add_bullet_list_item("Second item", 0);
315    doc.add_bullet_list_item("Nested level 1", 1);
316    doc.add_bullet_list_item("Nested level 2", 2);
317    doc.add_bullet_list_item("Back to level 1", 1);
318    doc.add_bullet_list_item("Third item", 0);
319
320    doc.add_paragraph("");
321
322    doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
323    doc.add_numbered_list_item("First numbered item", 0);
324    doc.add_numbered_list_item("Second numbered item", 0);
325    doc.add_numbered_list_item("Sub-item A", 1);
326    doc.add_numbered_list_item("Sub-item B", 1);
327    doc.add_numbered_list_item("Third numbered item", 0);
328
329    // ── SECTION 4: TAB STOPS ──
330    doc.add_paragraph("");
331    doc.add_paragraph("4. Tab Stops").style("Heading1");
332
333    doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
334    doc.add_paragraph("Left\tCenter\tRight\tDecimal")
335        .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
336        .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
337        .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
338        .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
339
340    doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
341    doc.add_paragraph("Item\t\tPrice")
342        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
343        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
344        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
345    doc.add_paragraph("Widget\t\t$19.99")
346        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
347        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
348        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
349    doc.add_paragraph("Gadget\t\t$249.50")
350        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
351        .add_tab_stop_with_leader(
352            TabAlignment::Right,
353            Length::inches(4.0),
354            TabLeader::Underscore,
355        )
356        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
357
358    // ── SECTION 5: TABLES ──
359    doc.add_paragraph("").page_break_before(true);
360    doc.add_paragraph("5. Tables").style("Heading1");
361
362    doc.add_paragraph("5.1 Basic Table").style("Heading2");
363    {
364        let mut tbl = doc
365            .add_table(4, 3)
366            .borders(BorderStyle::Single, 4, "000000");
367        for col in 0..3 {
368            tbl.cell(0, col).unwrap().shading("2E75B6");
369        }
370        tbl.cell(0, 0).unwrap().set_text("Name");
371        tbl.cell(0, 1).unwrap().set_text("Role");
372        tbl.cell(0, 2).unwrap().set_text("Location");
373        tbl.cell(1, 0).unwrap().set_text("Walter White");
374        tbl.cell(1, 1).unwrap().set_text("CEO");
375        tbl.cell(1, 2).unwrap().set_text("Albuquerque");
376        tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
377        tbl.cell(2, 1).unwrap().set_text("CTO");
378        tbl.cell(2, 2).unwrap().set_text("Remote");
379        tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
380        tbl.cell(3, 1).unwrap().set_text("Security");
381        tbl.cell(3, 2).unwrap().set_text("Washington");
382    }
383
384    doc.add_paragraph("");
385
386    doc.add_paragraph("5.2 Column Span & Cell Shading")
387        .style("Heading2");
388    {
389        let mut tbl = doc
390            .add_table(3, 4)
391            .borders(BorderStyle::Single, 4, "000000")
392            .width_pct(100.0);
393        tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
394        tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
395        tbl.cell(1, 0).unwrap().set_text("Region");
396        tbl.cell(1, 0).unwrap().shading("D6E4F0");
397        tbl.cell(1, 1).unwrap().set_text("Q1");
398        tbl.cell(1, 1).unwrap().shading("D6E4F0");
399        tbl.cell(1, 2).unwrap().set_text("Q2");
400        tbl.cell(1, 2).unwrap().shading("D6E4F0");
401        tbl.cell(1, 3).unwrap().set_text("Total");
402        tbl.cell(1, 3).unwrap().shading("D6E4F0");
403        tbl.cell(2, 0).unwrap().set_text("Americas");
404        tbl.cell(2, 1).unwrap().set_text("$2.4M");
405        tbl.cell(2, 2).unwrap().set_text("$2.7M");
406        tbl.cell(2, 3).unwrap().set_text("$5.1M");
407    }
408
409    doc.add_paragraph("");
410
411    doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
412    {
413        let mut tbl = doc
414            .add_table(4, 3)
415            .borders(BorderStyle::Single, 4, "000000");
416        tbl.cell(0, 0).unwrap().set_text("Category");
417        tbl.cell(0, 0).unwrap().shading("E2EFDA");
418        tbl.cell(0, 1).unwrap().set_text("Item");
419        tbl.cell(0, 1).unwrap().shading("E2EFDA");
420        tbl.cell(0, 2).unwrap().set_text("Price");
421        tbl.cell(0, 2).unwrap().shading("E2EFDA");
422        tbl.cell(1, 0).unwrap().set_text("Hardware");
423        tbl.cell(1, 0).unwrap().v_merge_restart();
424        tbl.cell(1, 1).unwrap().set_text("Laptop");
425        tbl.cell(1, 2).unwrap().set_text("$1,200");
426        tbl.cell(2, 0).unwrap().v_merge_continue();
427        tbl.cell(2, 1).unwrap().set_text("Monitor");
428        tbl.cell(2, 2).unwrap().set_text("$450");
429        tbl.cell(3, 0).unwrap().set_text("Software");
430        tbl.cell(3, 1).unwrap().set_text("IDE License");
431        tbl.cell(3, 2).unwrap().set_text("$200/yr");
432    }
433
434    doc.add_paragraph("");
435
436    doc.add_paragraph("5.4 Nested Table").style("Heading2");
437    {
438        let mut tbl = doc
439            .add_table(2, 2)
440            .borders(BorderStyle::Single, 6, "2E75B6");
441        tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
442        tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
443        tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
444        {
445            let mut cell = tbl.cell(1, 1).unwrap();
446            cell.set_text("Nested table below:");
447            let mut nested = cell
448                .add_table(2, 2)
449                .borders(BorderStyle::Single, 2, "FF6600");
450            nested.cell(0, 0).unwrap().set_text("A");
451            nested.cell(0, 1).unwrap().set_text("B");
452            nested.cell(1, 0).unwrap().set_text("C");
453            nested.cell(1, 1).unwrap().set_text("D");
454        }
455    }
456
457    doc.add_paragraph("");
458
459    doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
460        .style("Heading2");
461    {
462        let mut tbl = doc
463            .add_table(2, 3)
464            .borders(BorderStyle::Single, 4, "666666");
465        tbl.row(0).unwrap().height(Length::pt(50.0)).header();
466        tbl.cell(0, 0).unwrap().set_text("Top");
467        tbl.cell(0, 0)
468            .unwrap()
469            .vertical_alignment(VerticalAlignment::Top)
470            .shading("FFF2CC");
471        tbl.cell(0, 1).unwrap().set_text("Center");
472        tbl.cell(0, 1)
473            .unwrap()
474            .vertical_alignment(VerticalAlignment::Center)
475            .shading("D9E2F3");
476        tbl.cell(0, 2).unwrap().set_text("Bottom");
477        tbl.cell(0, 2)
478            .unwrap()
479            .vertical_alignment(VerticalAlignment::Bottom)
480            .shading("E2EFDA");
481        tbl.row(1).unwrap().cant_split();
482        tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
483        tbl.cell(1, 0).unwrap().no_wrap();
484        tbl.cell(1, 1).unwrap().set_text("Fixed width");
485        tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
486        tbl.cell(1, 2).unwrap().set_text("Normal");
487    }
488
489    // ── SECTION 6: IMAGES ──
490    doc.add_paragraph("").page_break_before(true);
491    doc.add_paragraph("6. Images").style("Heading1");
492
493    doc.add_paragraph("6.1 Inline Image").style("Heading2");
494    doc.add_paragraph("A blue gradient image below:");
495    let img = create_sample_png(200, 50, [0, 80, 200]);
496    doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
497
498    doc.add_paragraph("");
499    doc.add_paragraph("6.2 Header Image").style("Heading2");
500    let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
501    doc.set_header_image(
502        &hdr_img,
503        "header_logo.png",
504        Length::inches(2.0),
505        Length::inches(0.2),
506    );
507    doc.add_paragraph("The header now contains an inline image (check the top of this page).");
508
509    doc.add_paragraph("");
510    doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
511
512    // ── SECTION 7: CONTENT MANIPULATION ──
513    doc.add_paragraph("").page_break_before(true);
514    doc.add_paragraph("7. Content Manipulation")
515        .style("Heading1");
516
517    doc.add_paragraph("7.1 Placeholder Replacement")
518        .style("Heading2");
519    doc.add_paragraph("Customer: {{customer}}");
520    doc.add_paragraph("Date: {{date}}");
521    doc.add_paragraph("Reference: {{ref_number}}");
522    {
523        let mut tbl = doc
524            .add_table(2, 2)
525            .borders(BorderStyle::Single, 4, "000000");
526        tbl.cell(0, 0).unwrap().set_text("Project");
527        tbl.cell(0, 0).unwrap().shading("D6E4F0");
528        tbl.cell(0, 1).unwrap().set_text("{{project}}");
529        tbl.cell(1, 0).unwrap().set_text("Status");
530        tbl.cell(1, 0).unwrap().shading("D6E4F0");
531        tbl.cell(1, 1).unwrap().set_text("{{status}}");
532    }
533
534    let mut replacements = HashMap::new();
535    replacements.insert("{{customer}}", "Tensorbee Inc.");
536    replacements.insert("{{date}}", "February 22, 2026");
537    replacements.insert("{{ref_number}}", "TB-2026-001");
538    replacements.insert("{{project}}", "Infrastructure Upgrade");
539    replacements.insert("{{status}}", "In Progress");
540    let n = doc.replace_all(&replacements);
541    doc.add_paragraph(&format!("({n} placeholders replaced above)"));
542
543    doc.add_paragraph("");
544
545    doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
546    doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
547    let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
548    doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
549
550    doc.add_paragraph("");
551
552    doc.add_paragraph("7.3 Content Insertion").style("Heading2");
553    doc.add_paragraph("Section A: First content.");
554    doc.add_paragraph("Section C: Third content.");
555    if let Some(idx) = doc.find_content_index("Section C") {
556        doc.insert_paragraph(
557            idx,
558            "Section B: Inserted between A and C via find_content_index().",
559        );
560    }
561
562    // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
563    doc.add_paragraph("").section_break(SectionBreak::NextPage);
564    doc.add_paragraph("8. Section Breaks & Orientation")
565        .style("Heading1");
566    doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
567
568    {
569        let mut tbl = doc
570            .add_table(3, 7)
571            .borders(BorderStyle::Single, 4, "2E75B6");
572        let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
573        for (c, h) in headers.iter().enumerate() {
574            tbl.cell(0, c).unwrap().set_text(h);
575            tbl.cell(0, c).unwrap().shading("2E75B6");
576        }
577        let data = [
578            [
579                "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
580            ],
581            [
582                "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
583            ],
584        ];
585        for (r, row) in data.iter().enumerate() {
586            for (c, v) in row.iter().enumerate() {
587                tbl.cell(r + 1, c).unwrap().set_text(v);
588            }
589        }
590    }
591
592    doc.add_paragraph("")
593        .section_break(SectionBreak::NextPage)
594        .section_landscape();
595
596    // ── SECTION 9: CUSTOM STYLES ──
597    doc.add_paragraph("9. Custom Styles").style("Heading1");
598    doc.add_style(
599        StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
600            .based_on("Normal")
601            .paragraph_properties(rdocx_oxml::properties::CT_PPr {
602                shading: Some(rdocx_oxml::properties::CT_Shd {
603                    val: "clear".to_string(),
604                    color: None,
605                    fill: Some("FFF2CC".to_string()),
606                }),
607                ..Default::default()
608            })
609            .run_properties(rdocx_oxml::properties::CT_RPr {
610                bold: Some(true),
611                color: Some("C45911".to_string()),
612                ..Default::default()
613            }),
614    );
615    doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
616        .style("CustomHighlight");
617
618    doc.add_paragraph("");
619
620    // ── SECTION 10: DOCUMENT INTELLIGENCE ──
621    doc.add_paragraph("10. Document Intelligence API")
622        .style("Heading1");
623
624    let wc = doc.word_count();
625    let hc = doc.headings().len();
626    let ic = doc.images().len();
627    let lc = doc.links().len();
628    doc.add_paragraph(&format!("Word count: {wc}"));
629    doc.add_paragraph(&format!("Heading count: {hc}"));
630    doc.add_paragraph(&format!("Image count: {ic}"));
631    doc.add_paragraph(&format!("Link count: {lc}"));
632
633    let outline = doc.document_outline();
634    doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
635
636    let issues = doc.audit_accessibility();
637    doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
638    for issue in &issues {
639        doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
640    }
641
642    // ── SECTION 11: DOCUMENT MERGING ──
643    doc.add_paragraph("");
644    doc.add_paragraph("11. Document Merging").style("Heading1");
645    {
646        let mut other = Document::new();
647        other.add_paragraph("This paragraph was merged from another document using append().");
648        doc.append(&other);
649    }
650
651    doc.add_paragraph("");
652
653    // ── SUMMARY ──
654    doc.add_paragraph("Summary of Demonstrated Features")
655        .style("Heading1");
656    let features = [
657        "Page setup: size, margins, header/footer distance, gutter",
658        "Document metadata: title, author, subject, keywords",
659        "Headers and footers: text, images, different first page",
660        "Background images (full-page behind text)",
661        "Table of Contents generation with bookmarks",
662        "Text formatting: bold, italic, underline styles, strike, color, size, font",
663        "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
664        "Paragraph formatting: alignment, borders, shading, spacing, indentation",
665        "Pagination controls: keep-with-next, keep-together, widow control",
666        "Bullet and numbered lists with nesting",
667        "Tab stops with dot/underscore/hyphen leaders",
668        "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
669        "Row properties: header rows, exact height, cant-split",
670        "Cell properties: width, no-wrap, vertical alignment",
671        "Inline images and header images",
672        "Placeholder replacement (single and batch)",
673        "Regex-based find and replace",
674        "Content insertion at specific positions",
675        "Section breaks with mixed portrait/landscape",
676        "Custom paragraph and character styles",
677        "Document intelligence: word count, headings, outline, images, links",
678        "Accessibility audit",
679        "Document merging (append)",
680        "Export: DOCX, PDF, HTML, Markdown",
681    ];
682    for f in &features {
683        doc.add_bullet_list_item(f, 0);
684    }
685    doc.add_paragraph("");
686    doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
687        .alignment(Alignment::Center)
688        .shading("E2EFDA")
689        .border_all(BorderStyle::Single, 2, "00AA00");
690
691    doc
692}
693
694// =============================================================================
695// 2. PROPOSAL DOCUMENT — Deep navy + gold scheme
696// =============================================================================
697fn generate_proposal(_samples_dir: &Path) -> Document {
698    let mut doc = Document::new();
699
700    // Colors: Navy #1B2A4A, Gold #C5922E, Light #F4F1EB
701    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
702    doc.set_margins(
703        Length::inches(1.0),
704        Length::inches(1.0),
705        Length::inches(1.0),
706        Length::inches(1.0),
707    );
708    doc.set_title("AI Platform Modernization Proposal");
709    doc.set_author("Walter White");
710    doc.set_subject("Technology Proposal");
711    doc.set_keywords("proposal, AI, modernization, Tensorbee");
712
713    // Banner header
714    let logo_img = create_logo_png(220, 48);
715    let banner = build_header_banner_xml(
716        "rId1",
717        &BannerOpts {
718            bg_color: "1B2A4A",
719            banner_width: 7772400,
720            banner_height: 914400,
721            logo_width: 2011680,
722            logo_height: 438912,
723            logo_x_offset: 295125,
724            logo_y_offset: 237744,
725        },
726    );
727    doc.set_raw_header_with_images(
728        banner,
729        &[("rId1", &logo_img, "logo.png")],
730        rdocx_oxml::header_footer::HdrFtrType::Default,
731    );
732    doc.set_different_first_page(true);
733    let cover_banner = build_header_banner_xml(
734        "rId1",
735        &BannerOpts {
736            bg_color: "C5922E",
737            banner_width: 7772400,
738            banner_height: 914400,
739            logo_width: 2011680,
740            logo_height: 438912,
741            logo_x_offset: 295125,
742            logo_y_offset: 237744,
743        },
744    );
745    doc.set_raw_header_with_images(
746        cover_banner,
747        &[("rId1", &logo_img, "logo.png")],
748        rdocx_oxml::header_footer::HdrFtrType::First,
749    );
750    doc.set_margins(
751        Length::twips(2292),
752        Length::twips(1440),
753        Length::twips(1440),
754        Length::twips(1440),
755    );
756    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
757    doc.set_footer("Tensorbee — Confidential");
758
759    // Custom styles
760    doc.add_style(
761        StyleBuilder::paragraph("ProposalTitle", "Proposal Title")
762            .based_on("Normal")
763            .run_properties(rdocx_oxml::properties::CT_RPr {
764                bold: Some(true),
765                font_ascii: Some("Georgia".to_string()),
766                font_hansi: Some("Georgia".to_string()),
767                sz: Some(rdocx_oxml::HalfPoint(56)), // half-points → 28pt
768                color: Some("1B2A4A".to_string()),
769                ..Default::default()
770            }),
771    );
772
773    // ── Cover Page ──
774    for _ in 0..4 {
775        doc.add_paragraph("");
776    }
777    doc.add_paragraph("AI Platform Modernization")
778        .style("ProposalTitle")
779        .alignment(Alignment::Center);
780    doc.add_paragraph("")
781        .alignment(Alignment::Center)
782        .border_bottom(BorderStyle::Single, 8, "C5922E");
783    {
784        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
785        p.add_run("Prepared for Global Financial Corp.")
786            .size(14.0)
787            .color("666666")
788            .italic(true);
789    }
790    {
791        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
792        p.add_run("Prepared by: Walter White, CEO — Tensorbee")
793            .size(12.0)
794            .color("888888");
795    }
796    {
797        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
798        p.add_run("February 22, 2026").size(12.0).color("888888");
799    }
800
801    // ── TOC ──
802    doc.add_paragraph("").page_break_before(true);
803    doc.add_paragraph("Table of Contents").style("Heading1");
804    // We'll add a manual-style TOC since the insert_toc goes at a specific position
805    doc.insert_toc(doc.content_count(), 2);
806
807    // ── Executive Summary ──
808    doc.add_paragraph("").page_break_before(true);
809    doc.add_paragraph("Executive Summary").style("Heading1");
810    doc.add_paragraph(
811        "Tensorbee proposes a comprehensive modernization of Global Financial Corp's AI platform, \
812         transitioning from legacy batch-processing systems to a real-time inference architecture. \
813         This transformation will reduce model deployment time from weeks to hours, improve \
814         prediction accuracy by an estimated 23%, and deliver $4.2M in annual operational savings.",
815    )
816    .first_line_indent(Length::inches(0.3));
817    doc.add_paragraph(
818        "The project spans three phases over 18 months, with a total investment of $2.8M. \
819         Tensorbee brings deep expertise in ML infrastructure, having successfully completed \
820         similar transformations for three Fortune 500 financial institutions.",
821    )
822    .first_line_indent(Length::inches(0.3));
823
824    // Key metrics table
825    doc.add_paragraph("");
826    {
827        let mut tbl = doc
828            .add_table(5, 2)
829            .borders(BorderStyle::Single, 4, "1B2A4A")
830            .width_pct(80.0);
831        tbl.cell(0, 0).unwrap().set_text("Key Metric");
832        tbl.cell(0, 0).unwrap().shading("1B2A4A");
833        tbl.cell(0, 1).unwrap().set_text("Value");
834        tbl.cell(0, 1).unwrap().shading("1B2A4A");
835        let rows = [
836            ("Total Investment", "$2.8M"),
837            ("Annual Savings", "$4.2M"),
838            ("ROI (Year 1)", "150%"),
839            ("Timeline", "18 months"),
840        ];
841        for (i, (k, v)) in rows.iter().enumerate() {
842            tbl.cell(i + 1, 0).unwrap().set_text(k);
843            if i % 2 == 0 {
844                tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
845            }
846            tbl.cell(i + 1, 1).unwrap().set_text(v);
847            if i % 2 == 0 {
848                tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
849            }
850        }
851    }
852
853    // ── Problem Statement ──
854    doc.add_paragraph("").page_break_before(true);
855    doc.add_paragraph("Problem Statement").style("Heading1");
856    doc.add_paragraph(
857        "Global Financial Corp's current AI infrastructure faces three critical challenges:",
858    );
859    doc.add_numbered_list_item("Deployment Latency — New models take 3-4 weeks to deploy to production, compared to the industry benchmark of 2-3 days.", 0);
860    doc.add_numbered_list_item("Scalability Constraints — The batch processing architecture cannot handle real-time inference demands during peak trading hours.", 0);
861    doc.add_numbered_list_item("Technical Debt — Legacy Python 2.7 codebases and manual deployment scripts create reliability risks and slow iteration speed.", 0);
862
863    // ── Proposed Solution ──
864    doc.add_paragraph("").page_break_before(true);
865    doc.add_paragraph("Proposed Solution").style("Heading1");
866
867    doc.add_paragraph("Phase 1: Foundation (Months 1-6)")
868        .style("Heading2");
869    doc.add_bullet_list_item("Deploy Kubernetes-based ML serving infrastructure", 0);
870    doc.add_bullet_list_item("Implement CI/CD pipeline for model deployment", 0);
871    doc.add_bullet_list_item("Migrate top 5 production models to new platform", 0);
872
873    doc.add_paragraph("Phase 2: Expansion (Months 7-12)")
874        .style("Heading2");
875    doc.add_bullet_list_item(
876        "Real-time feature engineering pipeline (Apache Kafka + Flink)",
877        0,
878    );
879    doc.add_bullet_list_item("A/B testing framework for model variants", 0);
880    doc.add_bullet_list_item("Automated model monitoring and drift detection", 0);
881
882    doc.add_paragraph("Phase 3: Optimization (Months 13-18)")
883        .style("Heading2");
884    doc.add_bullet_list_item("GPU inference optimization (TensorRT/ONNX Runtime)", 0);
885    doc.add_bullet_list_item("Multi-region deployment for latency reduction", 0);
886    doc.add_bullet_list_item(
887        "Self-service model deployment portal for data science teams",
888        0,
889    );
890
891    // ── Budget ──
892    doc.add_paragraph("Budget Breakdown").style("Heading1");
893    {
894        let mut tbl = doc
895            .add_table(6, 3)
896            .borders(BorderStyle::Single, 4, "1B2A4A")
897            .width_pct(100.0);
898        tbl.cell(0, 0).unwrap().set_text("Category");
899        tbl.cell(0, 0).unwrap().shading("1B2A4A");
900        tbl.cell(0, 1).unwrap().set_text("Cost");
901        tbl.cell(0, 1).unwrap().shading("1B2A4A");
902        tbl.cell(0, 2).unwrap().set_text("Notes");
903        tbl.cell(0, 2).unwrap().shading("1B2A4A");
904        let items = [
905            (
906                "Infrastructure",
907                "$450,000",
908                "Cloud compute + GPU instances",
909            ),
910            ("Engineering Services", "$1,600,000", "12 FTE x 18 months"),
911            (
912                "Software Licenses",
913                "$350,000",
914                "Kafka, monitoring, CI/CD tools",
915            ),
916            (
917                "Training & Enablement",
918                "$200,000",
919                "Team training, documentation",
920            ),
921            ("Contingency (10%)", "$200,000", "Risk buffer"),
922        ];
923        for (i, (cat, cost, note)) in items.iter().enumerate() {
924            tbl.cell(i + 1, 0).unwrap().set_text(cat);
925            tbl.cell(i + 1, 1).unwrap().set_text(cost);
926            tbl.cell(i + 1, 2).unwrap().set_text(note);
927            if i % 2 == 0 {
928                tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
929                tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
930                tbl.cell(i + 1, 2).unwrap().shading("F4F1EB");
931            }
932        }
933    }
934
935    // ── Closing ──
936    doc.add_paragraph("");
937    doc.add_paragraph("Next Steps").style("Heading1");
938    doc.add_numbered_list_item(
939        "Schedule technical deep-dive with GFC engineering team (Week 1)",
940        0,
941    );
942    doc.add_numbered_list_item("Finalize scope and sign SOW (Week 2-3)", 0);
943    doc.add_numbered_list_item("Kick off Phase 1 with joint planning session (Week 4)", 0);
944
945    doc.add_paragraph("");
946    {
947        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
948        p.add_run("Walter White")
949            .bold(true)
950            .size(14.0)
951            .color("1B2A4A");
952    }
953    {
954        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
955        p.add_run("CEO, Tensorbee | walter@tensorbee.com")
956            .size(10.0)
957            .color("888888");
958    }
959
960    doc
961}
962
963// =============================================================================
964// 3. QUOTE / BILL OF MATERIALS — Teal + orange scheme
965// =============================================================================
966fn generate_quote(_samples_dir: &Path) -> Document {
967    let mut doc = Document::new();
968
969    // Colors: Teal #008B8B, Dark #1A3C3C, Orange #E07020, Light #F0F8F8
970    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
971    doc.set_margins(
972        Length::inches(0.75),
973        Length::inches(0.75),
974        Length::inches(0.75),
975        Length::inches(0.75),
976    );
977    doc.set_title("Quotation QT-2026-0147");
978    doc.set_author("Walter White");
979    doc.set_keywords("quote, BOM, Tensorbee");
980
981    doc.set_header("Tensorbee — Quotation");
982    doc.set_footer("QT-2026-0147 | Page");
983
984    // ── Header Block ──
985    {
986        let mut p = doc.add_paragraph("").alignment(Alignment::Left);
987        p.add_run("TENSORBEE")
988            .bold(true)
989            .size(28.0)
990            .color("008B8B")
991            .font("Helvetica");
992    }
993    doc.add_paragraph("123 Innovation Drive, Suite 400")
994        .alignment(Alignment::Left);
995    doc.add_paragraph("San Francisco, CA 94105 | +1 (415) 555-0199")
996        .alignment(Alignment::Left);
997    doc.add_paragraph("")
998        .border_bottom(BorderStyle::Single, 8, "008B8B");
999
1000    // Quote meta
1001    doc.add_paragraph("");
1002    {
1003        let mut tbl = doc.add_table(4, 4).width_pct(100.0);
1004        tbl.cell(0, 0).unwrap().set_text("QUOTATION");
1005        tbl.cell(0, 0).unwrap().shading("008B8B").grid_span(2);
1006        tbl.cell(0, 2).unwrap().set_text("DATE");
1007        tbl.cell(0, 2).unwrap().shading("008B8B");
1008        tbl.cell(0, 3).unwrap().set_text("VALID UNTIL");
1009        tbl.cell(0, 3).unwrap().shading("008B8B");
1010        tbl.cell(1, 0).unwrap().set_text("Quote #:");
1011        tbl.cell(1, 1).unwrap().set_text("QT-2026-0147");
1012        tbl.cell(1, 2).unwrap().set_text("Feb 22, 2026");
1013        tbl.cell(1, 3).unwrap().set_text("Mar 22, 2026");
1014        tbl.cell(2, 0).unwrap().set_text("Prepared by:");
1015        tbl.cell(2, 1).unwrap().set_text("Walter White");
1016        tbl.cell(2, 2).unwrap().set_text("Payment:");
1017        tbl.cell(2, 3).unwrap().set_text("Net 30");
1018        tbl.cell(3, 0).unwrap().set_text("Customer:");
1019        tbl.cell(3, 1).unwrap().set_text("Meridian Dynamics LLC");
1020        tbl.cell(3, 2).unwrap().set_text("Currency:");
1021        tbl.cell(3, 3).unwrap().set_text("USD");
1022    }
1023
1024    doc.add_paragraph("");
1025
1026    // ── Bill of Materials ──
1027    {
1028        let mut p = doc.add_paragraph("");
1029        p.add_run("Bill of Materials")
1030            .bold(true)
1031            .size(16.0)
1032            .color("1A3C3C");
1033    }
1034    doc.add_paragraph("");
1035
1036    {
1037        let mut tbl = doc
1038            .add_table(10, 6)
1039            .borders(BorderStyle::Single, 2, "008B8B")
1040            .width_pct(100.0)
1041            .layout_fixed();
1042        // Headers
1043        let hdrs = [
1044            "#",
1045            "Part Number",
1046            "Description",
1047            "Qty",
1048            "Unit Price",
1049            "Total",
1050        ];
1051        for (c, h) in hdrs.iter().enumerate() {
1052            tbl.cell(0, c).unwrap().set_text(h);
1053            tbl.cell(0, c).unwrap().shading("008B8B");
1054        }
1055
1056        let items: Vec<(&str, &str, &str, &str, &str)> = vec![
1057            (
1058                "TB-GPU-A100",
1059                "NVIDIA A100 80GB GPU",
1060                "4",
1061                "$12,500.00",
1062                "$50,000.00",
1063            ),
1064            (
1065                "TB-SRV-R750",
1066                "Dell R750xa Server Chassis",
1067                "2",
1068                "$8,200.00",
1069                "$16,400.00",
1070            ),
1071            (
1072                "TB-RAM-256G",
1073                "256GB DDR5 ECC Memory Module",
1074                "8",
1075                "$890.00",
1076                "$7,120.00",
1077            ),
1078            (
1079                "TB-SSD-3840",
1080                "3.84TB NVMe U.2 SSD",
1081                "8",
1082                "$1,150.00",
1083                "$9,200.00",
1084            ),
1085            (
1086                "TB-NET-CX7",
1087                "ConnectX-7 200GbE NIC",
1088                "4",
1089                "$1,800.00",
1090                "$7,200.00",
1091            ),
1092            (
1093                "TB-SW-48P",
1094                "48-Port 100GbE Switch",
1095                "1",
1096                "$22,000.00",
1097                "$22,000.00",
1098            ),
1099            (
1100                "TB-CAB-RACK",
1101                "42U Server Rack + PDU",
1102                "1",
1103                "$4,500.00",
1104                "$4,500.00",
1105            ),
1106            (
1107                "TB-SVC-INST",
1108                "Installation & Configuration",
1109                "1",
1110                "$8,500.00",
1111                "$8,500.00",
1112            ),
1113            (
1114                "TB-SVC-SUPP",
1115                "3-Year Premium Support",
1116                "1",
1117                "$15,000.00",
1118                "$15,000.00",
1119            ),
1120        ];
1121
1122        for (i, (pn, desc, qty, unit, total)) in items.iter().enumerate() {
1123            let row = i + 1;
1124            tbl.cell(row, 0).unwrap().set_text(&format!("{}", i + 1));
1125            tbl.cell(row, 1).unwrap().set_text(pn);
1126            tbl.cell(row, 2).unwrap().set_text(desc);
1127            tbl.cell(row, 3).unwrap().set_text(qty);
1128            tbl.cell(row, 4).unwrap().set_text(unit);
1129            tbl.cell(row, 5).unwrap().set_text(total);
1130            if i % 2 == 0 {
1131                for c in 0..6 {
1132                    tbl.cell(row, c).unwrap().shading("F0F8F8");
1133                }
1134            }
1135        }
1136    }
1137
1138    doc.add_paragraph("");
1139
1140    // ── Totals ──
1141    {
1142        let mut tbl = doc
1143            .add_table(4, 2)
1144            .borders(BorderStyle::Single, 2, "008B8B")
1145            .width(Length::inches(3.5))
1146            .alignment(Alignment::Right);
1147        tbl.cell(0, 0).unwrap().set_text("Subtotal");
1148        tbl.cell(0, 1).unwrap().set_text("$139,920.00");
1149        tbl.cell(1, 0).unwrap().set_text("Shipping & Handling");
1150        tbl.cell(1, 1).unwrap().set_text("$2,500.00");
1151        tbl.cell(2, 0).unwrap().set_text("Tax (8.625%)");
1152        tbl.cell(2, 1).unwrap().set_text("$12,068.10");
1153        tbl.cell(3, 0).unwrap().set_text("TOTAL");
1154        tbl.cell(3, 0).unwrap().shading("E07020");
1155        tbl.cell(3, 1).unwrap().set_text("$154,488.10");
1156        tbl.cell(3, 1).unwrap().shading("E07020");
1157    }
1158
1159    doc.add_paragraph("");
1160
1161    // ── Terms & Conditions ──
1162    {
1163        let mut p = doc.add_paragraph("");
1164        p.add_run("Terms & Conditions")
1165            .bold(true)
1166            .size(14.0)
1167            .color("1A3C3C");
1168    }
1169    doc.add_numbered_list_item(
1170        "This quotation is valid for 30 calendar days from the date of issue.",
1171        0,
1172    );
1173    doc.add_numbered_list_item(
1174        "All prices are in USD and exclusive of applicable taxes unless stated.",
1175        0,
1176    );
1177    doc.add_numbered_list_item("Standard lead time is 4-6 weeks from PO receipt.", 0);
1178    doc.add_numbered_list_item(
1179        "Payment terms: Net 30 from invoice date. 2% discount for payment within 10 days.",
1180        0,
1181    );
1182    doc.add_numbered_list_item(
1183        "Warranty: 3-year manufacturer warranty on all hardware components.",
1184        0,
1185    );
1186    doc.add_numbered_list_item(
1187        "Returns subject to 15% restocking fee if initiated after 14 days.",
1188        0,
1189    );
1190
1191    doc.add_paragraph("");
1192    doc.add_paragraph("")
1193        .border_bottom(BorderStyle::Single, 4, "008B8B");
1194    doc.add_paragraph("");
1195    {
1196        let mut p = doc.add_paragraph("");
1197        p.add_run("Accepted by: ").bold(true);
1198        p.add_run("___________________________ Date: ___________");
1199    }
1200    {
1201        let mut p = doc.add_paragraph("");
1202        p.add_run("Print Name: ").bold(true);
1203        p.add_run("___________________________ Title: ___________");
1204    }
1205
1206    doc
1207}
1208
1209// =============================================================================
1210// 4. INVOICE — Crimson + charcoal scheme
1211// =============================================================================
1212fn generate_invoice(_samples_dir: &Path) -> Document {
1213    let mut doc = Document::new();
1214
1215    // Colors: Crimson #B22222, Charcoal #333333, Light #FAF0F0
1216    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1217    doc.set_margins(
1218        Length::inches(0.75),
1219        Length::inches(0.75),
1220        Length::inches(0.75),
1221        Length::inches(0.75),
1222    );
1223    doc.set_title("Invoice INV-2026-0392");
1224    doc.set_author("Walter White");
1225    doc.set_keywords("invoice, Tensorbee");
1226
1227    doc.set_footer("Tensorbee — Thank you for your business!");
1228
1229    // ── Company & Invoice Header ──
1230    {
1231        let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1232        // Company name left, INVOICE right
1233        {
1234            let mut cell = tbl.cell(0, 0).unwrap();
1235            let mut p = cell.add_paragraph("");
1236            p.add_run("TENSORBEE")
1237                .bold(true)
1238                .size(32.0)
1239                .color("B22222")
1240                .font("Helvetica");
1241        }
1242        {
1243            let mut cell = tbl.cell(0, 1).unwrap();
1244            let mut p = cell.add_paragraph("");
1245            p.add_run("INVOICE").bold(true).size(32.0).color("333333");
1246        }
1247        tbl.cell(1, 0)
1248            .unwrap()
1249            .set_text("123 Innovation Drive, Suite 400");
1250        tbl.cell(1, 1).unwrap().set_text("Invoice #: INV-2026-0392");
1251        tbl.cell(2, 0).unwrap().set_text("San Francisco, CA 94105");
1252        tbl.cell(2, 1).unwrap().set_text("Date: February 22, 2026");
1253        tbl.cell(3, 0).unwrap().set_text("walter@tensorbee.com");
1254        tbl.cell(3, 1).unwrap().set_text("Due Date: March 24, 2026");
1255    }
1256
1257    doc.add_paragraph("")
1258        .border_bottom(BorderStyle::Thick, 8, "B22222");
1259    doc.add_paragraph("");
1260
1261    // ── Bill To / Ship To ──
1262    {
1263        let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1264        tbl.cell(0, 0).unwrap().set_text("BILL TO");
1265        tbl.cell(0, 0).unwrap().shading("B22222");
1266        tbl.cell(0, 1).unwrap().set_text("SHIP TO");
1267        tbl.cell(0, 1).unwrap().shading("B22222");
1268        tbl.cell(1, 0).unwrap().set_text("Meridian Dynamics LLC");
1269        tbl.cell(1, 1).unwrap().set_text("Meridian Dynamics LLC");
1270        tbl.cell(2, 0).unwrap().set_text("456 Enterprise Blvd");
1271        tbl.cell(2, 1).unwrap().set_text("Attn: Server Room B");
1272        tbl.cell(3, 0).unwrap().set_text("Austin, TX 78701");
1273        tbl.cell(3, 1)
1274            .unwrap()
1275            .set_text("456 Enterprise Blvd, Austin, TX 78701");
1276    }
1277
1278    doc.add_paragraph("");
1279
1280    // ── Line Items ──
1281    {
1282        let mut tbl = doc
1283            .add_table(8, 5)
1284            .borders(BorderStyle::Single, 2, "B22222")
1285            .width_pct(100.0);
1286        let hdrs = ["Description", "Qty", "Unit Price", "Tax", "Amount"];
1287        for (c, h) in hdrs.iter().enumerate() {
1288            tbl.cell(0, c).unwrap().set_text(h);
1289            tbl.cell(0, c).unwrap().shading("B22222");
1290        }
1291        let items = [
1292            (
1293                "ML Infrastructure Setup — Phase 1",
1294                "1",
1295                "$45,000.00",
1296                "$3,881.25",
1297                "$48,881.25",
1298            ),
1299            (
1300                "Data Pipeline Development (160 hrs)",
1301                "160",
1302                "$225.00",
1303                "$3,105.00",
1304                "$39,105.00",
1305            ),
1306            (
1307                "GPU Cluster Configuration",
1308                "1",
1309                "$12,000.00",
1310                "$1,035.00",
1311                "$13,035.00",
1312            ),
1313            (
1314                "API Gateway Implementation",
1315                "1",
1316                "$18,500.00",
1317                "$1,595.63",
1318                "$20,095.63",
1319            ),
1320            (
1321                "Load Testing & QA (80 hrs)",
1322                "80",
1323                "$195.00",
1324                "$1,345.50",
1325                "$16,945.50",
1326            ),
1327            (
1328                "Documentation & Training",
1329                "1",
1330                "$8,000.00",
1331                "$690.00",
1332                "$8,690.00",
1333            ),
1334            (
1335                "Project Management (3 months)",
1336                "3",
1337                "$6,500.00",
1338                "$1,679.63",
1339                "$21,179.63",
1340            ),
1341        ];
1342        for (i, (desc, qty, unit, tax, amt)) in items.iter().enumerate() {
1343            let r = i + 1;
1344            tbl.cell(r, 0).unwrap().set_text(desc);
1345            tbl.cell(r, 1).unwrap().set_text(qty);
1346            tbl.cell(r, 2).unwrap().set_text(unit);
1347            tbl.cell(r, 3).unwrap().set_text(tax);
1348            tbl.cell(r, 4).unwrap().set_text(amt);
1349            if i % 2 == 0 {
1350                for c in 0..5 {
1351                    tbl.cell(r, c).unwrap().shading("FAF0F0");
1352                }
1353            }
1354        }
1355    }
1356
1357    doc.add_paragraph("");
1358
1359    // ── Totals ──
1360    {
1361        let mut tbl = doc
1362            .add_table(4, 2)
1363            .borders(BorderStyle::Single, 2, "B22222")
1364            .width(Length::inches(3.0))
1365            .alignment(Alignment::Right);
1366        tbl.cell(0, 0).unwrap().set_text("Subtotal");
1367        tbl.cell(0, 1).unwrap().set_text("$154,600.00");
1368        tbl.cell(1, 0).unwrap().set_text("Tax (8.625%)");
1369        tbl.cell(1, 1).unwrap().set_text("$13,334.25");
1370        tbl.cell(2, 0).unwrap().set_text("Discount (5%)");
1371        tbl.cell(2, 1).unwrap().set_text("-$7,730.00");
1372        tbl.cell(3, 0).unwrap().set_text("AMOUNT DUE");
1373        tbl.cell(3, 0).unwrap().shading("B22222");
1374        tbl.cell(3, 1).unwrap().set_text("$160,204.25");
1375        tbl.cell(3, 1).unwrap().shading("B22222");
1376    }
1377
1378    doc.add_paragraph("");
1379    doc.add_paragraph("");
1380
1381    // ── Payment Details ──
1382    {
1383        let mut p = doc.add_paragraph("");
1384        p.add_run("Payment Details")
1385            .bold(true)
1386            .size(14.0)
1387            .color("333333");
1388    }
1389    doc.add_paragraph("Bank: Silicon Valley Bank")
1390        .indent_left(Length::inches(0.3));
1391    doc.add_paragraph("Account: Tensorbee Inc. — 0847-2953-1120")
1392        .indent_left(Length::inches(0.3));
1393    doc.add_paragraph("Routing: 121140399")
1394        .indent_left(Length::inches(0.3));
1395    doc.add_paragraph("Swift: SVBKUS6S")
1396        .indent_left(Length::inches(0.3));
1397
1398    doc.add_paragraph("");
1399
1400    doc.add_paragraph("Please include invoice number INV-2026-0392 in the payment reference.")
1401        .shading("FAF0F0")
1402        .border_all(BorderStyle::Single, 2, "B22222");
1403
1404    doc
1405}
1406
1407// =============================================================================
1408// 5. REPORT — Forest green + earth tones, with images & hierarchical sections
1409// =============================================================================
1410fn generate_report(_samples_dir: &Path) -> Document {
1411    let mut doc = Document::new();
1412
1413    // Colors: Forest #2D5016, Sage #6B8E23, Earth #8B7355, Cream #FFFAF0
1414    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1415    doc.set_margins(
1416        Length::inches(1.0),
1417        Length::inches(1.0),
1418        Length::inches(1.0),
1419        Length::inches(1.0),
1420    );
1421    doc.set_title("Q4 2025 Environmental Impact Report");
1422    doc.set_author("Walter White");
1423    doc.set_subject("Quarterly Environmental Report");
1424    doc.set_keywords("environment, sustainability, report, Tensorbee");
1425
1426    doc.set_different_first_page(true);
1427    doc.set_first_page_header("");
1428    doc.set_header("Tensorbee — Q4 2025 Environmental Impact Report");
1429    doc.set_footer("CONFIDENTIAL — Page");
1430
1431    // ── Cover ──
1432    let cover_bg = create_sample_png(612, 792, [20, 50, 15]);
1433    doc.add_background_image(&cover_bg, "report_cover.png");
1434
1435    for _ in 0..5 {
1436        doc.add_paragraph("");
1437    }
1438    {
1439        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1440        p.add_run("Q4 2025")
1441            .bold(true)
1442            .size(48.0)
1443            .color("FFFFFF")
1444            .font("Georgia");
1445    }
1446    {
1447        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1448        p.add_run("Environmental Impact Report")
1449            .size(24.0)
1450            .color("90EE90")
1451            .font("Georgia")
1452            .italic(true);
1453    }
1454    doc.add_paragraph("");
1455    {
1456        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1457        p.add_run("Tensorbee — Sustainability Division")
1458            .size(14.0)
1459            .color("C0C0C0");
1460    }
1461    {
1462        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1463        p.add_run("Prepared by Walter White, Chief Sustainability Officer")
1464            .size(11.0)
1465            .color("AAAAAA");
1466    }
1467
1468    // ── TOC ──
1469    doc.add_paragraph("").page_break_before(true);
1470    doc.insert_toc(doc.content_count(), 3);
1471
1472    // ── Section 1: Executive Overview ──
1473    doc.add_paragraph("").page_break_before(true);
1474    doc.add_paragraph("Executive Overview").style("Heading1");
1475    doc.add_paragraph(
1476        "This report presents Tensorbee's environmental performance for Q4 2025. Our \
1477         sustainability initiatives have yielded a 34% reduction in carbon emissions compared \
1478         to Q4 2024, exceeding our target of 25%. Key achievements include the transition to \
1479         100% renewable energy in our primary data centers and the launch of our carbon offset \
1480         marketplace.",
1481    )
1482    .first_line_indent(Length::inches(0.3));
1483
1484    // Image: performance chart
1485    let chart_img = create_chart_png(400, 200);
1486    doc.add_paragraph("");
1487    doc.add_picture(
1488        &chart_img,
1489        "performance_chart.png",
1490        Length::inches(5.0),
1491        Length::inches(2.5),
1492    )
1493    .alignment(Alignment::Center);
1494    {
1495        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1496        p.add_run("Figure 1: Quarterly Carbon Emissions (tonnes CO2e)")
1497            .italic(true)
1498            .size(9.0)
1499            .color("666666");
1500    }
1501
1502    // ── Section 2: Energy Consumption ──
1503    doc.add_paragraph("").page_break_before(true);
1504    doc.add_paragraph("Energy Consumption").style("Heading1");
1505
1506    doc.add_paragraph("Data Center Operations")
1507        .style("Heading2");
1508    doc.add_paragraph(
1509        "Our three primary data centers consumed a combined 4.2 GWh in Q4 2025, \
1510         a 12% reduction from Q3 2025 driven by improved cooling efficiency and \
1511         server consolidation.",
1512    );
1513
1514    // Data table
1515    {
1516        let mut tbl = doc
1517            .add_table(5, 4)
1518            .borders(BorderStyle::Single, 4, "2D5016")
1519            .width_pct(100.0);
1520        let hdrs = ["Data Center", "Capacity (MW)", "Usage (GWh)", "PUE"];
1521        for (c, h) in hdrs.iter().enumerate() {
1522            tbl.cell(0, c).unwrap().set_text(h);
1523            tbl.cell(0, c).unwrap().shading("2D5016");
1524        }
1525        let rows = [
1526            ("San Francisco (Primary)", "3.2", "1.8", "1.12"),
1527            ("Dublin (EU)", "2.1", "1.2", "1.18"),
1528            ("Singapore (APAC)", "1.8", "1.2", "1.24"),
1529            ("TOTAL", "7.1", "4.2", "1.17 avg"),
1530        ];
1531        for (i, (dc, cap, use_, pue)) in rows.iter().enumerate() {
1532            tbl.cell(i + 1, 0).unwrap().set_text(dc);
1533            tbl.cell(i + 1, 1).unwrap().set_text(cap);
1534            tbl.cell(i + 1, 2).unwrap().set_text(use_);
1535            tbl.cell(i + 1, 3).unwrap().set_text(pue);
1536            if i == 3 {
1537                for c in 0..4 {
1538                    tbl.cell(i + 1, c).unwrap().shading("E2EFDA");
1539                }
1540            }
1541        }
1542    }
1543
1544    doc.add_paragraph("");
1545
1546    doc.add_paragraph("Renewable Energy Mix").style("Heading2");
1547    doc.add_paragraph("Breakdown of energy sources across all facilities:");
1548
1549    doc.add_bullet_list_item("Solar PV: 42% (1.76 GWh)", 0);
1550    doc.add_bullet_list_item("Wind Power Purchase Agreements: 38% (1.60 GWh)", 0);
1551    doc.add_bullet_list_item("Hydroelectric: 12% (0.50 GWh)", 0);
1552    doc.add_bullet_list_item("Grid (non-renewable): 8% (0.34 GWh)", 0);
1553
1554    // ── Section 3: Water & Waste ──
1555    doc.add_paragraph("Water & Waste Management")
1556        .style("Heading1");
1557
1558    doc.add_paragraph("Water Usage").style("Heading2");
1559    doc.add_paragraph(
1560        "Total water consumption was 12.4 million gallons, a 15% reduction from Q3. \
1561         Our closed-loop cooling systems now recycle 78% of water used in cooling operations.",
1562    );
1563
1564    doc.add_paragraph("Waste Reduction").style("Heading2");
1565    doc.add_paragraph("E-waste management results for Q4:")
1566        .keep_with_next(true);
1567    {
1568        let mut tbl = doc
1569            .add_table(4, 3)
1570            .borders(BorderStyle::Single, 2, "6B8E23")
1571            .width_pct(80.0);
1572        tbl.cell(0, 0).unwrap().set_text("Category");
1573        tbl.cell(0, 0).unwrap().shading("6B8E23");
1574        tbl.cell(0, 1).unwrap().set_text("Weight (kg)");
1575        tbl.cell(0, 1).unwrap().shading("6B8E23");
1576        tbl.cell(0, 2).unwrap().set_text("Recycled %");
1577        tbl.cell(0, 2).unwrap().shading("6B8E23");
1578        let rows = [
1579            ("Server Hardware", "2,450", "94%"),
1580            ("Networking Equipment", "820", "91%"),
1581            ("Storage Media", "340", "99%"),
1582        ];
1583        for (i, (cat, wt, pct)) in rows.iter().enumerate() {
1584            tbl.cell(i + 1, 0).unwrap().set_text(cat);
1585            tbl.cell(i + 1, 1).unwrap().set_text(wt);
1586            tbl.cell(i + 1, 2).unwrap().set_text(pct);
1587        }
1588    }
1589
1590    // ── Section 4: Initiatives ──
1591    doc.add_paragraph("").page_break_before(true);
1592    doc.add_paragraph("Q1 2026 Initiatives").style("Heading1");
1593
1594    doc.add_paragraph("Planned Programs").style("Heading2");
1595    doc.add_numbered_list_item(
1596        "Deploy on-site battery storage at SF data center (2 MWh capacity)",
1597        0,
1598    );
1599    doc.add_numbered_list_item("Pilot immersion cooling in Dublin facility", 0);
1600    doc.add_numbered_list_item("Launch employee commute carbon offset program", 0);
1601    doc.add_numbered_list_item("Achieve ISO 14001 certification for Singapore facility", 0);
1602
1603    doc.add_paragraph("Investment Targets").style("Heading2");
1604    doc.add_paragraph("Sustainability CapEx allocation for FY2026:")
1605        .keep_with_next(true);
1606    {
1607        let mut tbl = doc
1608            .add_table(5, 2)
1609            .borders(BorderStyle::Single, 4, "2D5016");
1610        tbl.cell(0, 0).unwrap().set_text("Initiative");
1611        tbl.cell(0, 0).unwrap().shading("2D5016");
1612        tbl.cell(0, 1).unwrap().set_text("Budget");
1613        tbl.cell(0, 1).unwrap().shading("2D5016");
1614        let rows = [
1615            ("Battery Storage", "$1.2M"),
1616            ("Immersion Cooling Pilot", "$800K"),
1617            ("Solar Panel Expansion", "$2.1M"),
1618            ("Carbon Credits", "$500K"),
1619        ];
1620        for (i, (init, budget)) in rows.iter().enumerate() {
1621            tbl.cell(i + 1, 0).unwrap().set_text(init);
1622            tbl.cell(i + 1, 1).unwrap().set_text(budget);
1623            if i % 2 == 0 {
1624                tbl.cell(i + 1, 0).unwrap().shading("FFFAF0");
1625                tbl.cell(i + 1, 1).unwrap().shading("FFFAF0");
1626            }
1627        }
1628    }
1629
1630    // Image: sustainability roadmap
1631    let roadmap_img = create_sample_png(500, 100, [40, 80, 30]);
1632    doc.add_paragraph("");
1633    doc.add_picture(
1634        &roadmap_img,
1635        "roadmap.png",
1636        Length::inches(6.0),
1637        Length::inches(1.2),
1638    )
1639    .alignment(Alignment::Center);
1640    {
1641        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1642        p.add_run("Figure 2: 2026 Sustainability Roadmap")
1643            .italic(true)
1644            .size(9.0)
1645            .color("666666");
1646    }
1647
1648    doc.add_paragraph("");
1649    doc.add_paragraph(
1650        "For questions about this report, contact Walter White at sustainability@tensorbee.com.",
1651    )
1652    .shading("FFFAF0")
1653    .border_all(BorderStyle::Single, 2, "2D5016");
1654
1655    doc
1656}
Source

pub fn replace_text(&mut self, placeholder: &str, replacement: &str) -> usize

Replace all occurrences of placeholder with replacement throughout the document.

Searches body paragraphs, tables (including nested), headers, footers, text boxes and chart labels. Handles placeholders split across multiple runs. Returns the total number of replacements made.

A replacement that contains placeholder is substituted once, not repeatedly.

Source

pub fn replace_all(&mut self, replacements: &HashMap<&str, &str>) -> usize

Replace multiple placeholders at once. Returns total replacements.

Cheaper than calling Self::replace_text per entry: the document is serialised and re-parsed once for the whole batch rather than once per placeholder.

Examples found in repository?
examples/template_replace.rs (line 189)
162fn fill_template(template_path: &Path, output_path: &Path) {
163    let mut doc = Document::open(template_path).unwrap();
164
165    // ── Batch replacement ──
166    let mut replacements = HashMap::new();
167    replacements.insert("{{company_name}}", "Riverside Medical Center");
168    replacements.insert("{{project_name}}", "Network Security Upgrade");
169    replacements.insert("{{contact_name}}", "Dr. Sarah Chen");
170    replacements.insert("{{contact_email}}", "s.chen@riverside.org");
171    replacements.insert("{{start_date}}", "March 1, 2026");
172    replacements.insert("{{duration}}", "12 weeks");
173    replacements.insert("{{budget}}", "$185,000");
174    replacements.insert("{{status}}", "Pending Approval");
175    replacements.insert("{{author_name}}", "James Wilson");
176    replacements.insert("{{date}}", "February 22, 2026");
177
178    // Team members
179    replacements.insert("{{member1_name}}", "James Wilson");
180    replacements.insert("{{member1_role}}", "Project Lead");
181    replacements.insert("{{member1_email}}", "j.wilson@provider.com");
182    replacements.insert("{{member2_name}}", "Maria Garcia");
183    replacements.insert("{{member2_role}}", "Security Architect");
184    replacements.insert("{{member2_email}}", "m.garcia@provider.com");
185    replacements.insert("{{member3_name}}", "David Park");
186    replacements.insert("{{member3_role}}", "Network Engineer");
187    replacements.insert("{{member3_email}}", "d.park@provider.com");
188
189    let count = doc.replace_all(&replacements);
190    println!("  Replaced {} placeholders", count);
191
192    // ── Insert deliverables at the insertion point ──
193    if let Some(idx) = doc.find_content_index("INSERTION_POINT") {
194        // Remove the placeholder paragraph
195        doc.remove_content(idx);
196
197        // Insert deliverables list
198        doc.insert_paragraph(idx, "The following deliverables are included:");
199
200        // Insert a deliverables table
201        let mut tbl = doc.insert_table(idx + 1, 5, 3);
202        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
203
204        for col in 0..3 {
205            tbl.cell(0, col).unwrap().shading("E2EFDA");
206        }
207        tbl.cell(0, 0).unwrap().set_text("Phase");
208        tbl.cell(0, 1).unwrap().set_text("Description");
209        tbl.cell(0, 2).unwrap().set_text("Timeline");
210
211        tbl.cell(1, 0).unwrap().set_text("1. Discovery");
212        tbl.cell(1, 1)
213            .unwrap()
214            .set_text("Network assessment and asset inventory");
215        tbl.cell(1, 2).unwrap().set_text("Weeks 1-3");
216
217        tbl.cell(2, 0).unwrap().set_text("2. Design");
218        tbl.cell(2, 1)
219            .unwrap()
220            .set_text("Security architecture and policy design");
221        tbl.cell(2, 2).unwrap().set_text("Weeks 4-6");
222
223        tbl.cell(3, 0).unwrap().set_text("3. Implementation");
224        tbl.cell(3, 1)
225            .unwrap()
226            .set_text("Deploy monitoring and access controls");
227        tbl.cell(3, 2).unwrap().set_text("Weeks 7-10");
228
229        tbl.cell(4, 0).unwrap().set_text("4. Validation");
230        tbl.cell(4, 1)
231            .unwrap()
232            .set_text("Testing, training, and handover");
233        tbl.cell(4, 2).unwrap().set_text("Weeks 11-12");
234
235        println!("  Inserted deliverables table at position {}", idx);
236    }
237
238    // ── Update metadata ──
239    doc.set_title("Riverside Medical Center — Network Security Upgrade Proposal");
240    doc.set_author("James Wilson");
241    doc.set_subject("Project Proposal");
242    doc.set_keywords("security, network, medical, proposal");
243
244    doc.save(output_path).unwrap();
245}
More examples
Hide additional examples
examples/generate_all_samples.rs (line 540)
97fn generate_feature_showcase(_samples_dir: &Path) -> Document {
98    let mut doc = Document::new();
99
100    // ── Page Setup & Metadata ──
101    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
102    doc.set_margins(
103        Length::inches(1.0),
104        Length::inches(1.0),
105        Length::inches(1.0),
106        Length::inches(1.0),
107    );
108    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
109    doc.set_gutter(Length::twips(0));
110    doc.set_title("rdocx Feature Showcase");
111    doc.set_author("rdocx Sample Generator");
112    doc.set_subject("Comprehensive feature demonstration");
113    doc.set_keywords("rdocx, docx, rust, sample, showcase");
114
115    // Headers & Footers with different first page
116    doc.set_different_first_page(true);
117    doc.set_first_page_header("rdocx");
118    doc.set_first_page_footer("Feature Showcase — Cover Page");
119    doc.set_header("rdocx Feature Showcase");
120    doc.set_footer("Generated by rdocx");
121
122    // ── COVER PAGE ──
123    let bg = create_sample_png(612, 792, [20, 45, 90]);
124    doc.add_background_image(&bg, "cover_bg.png");
125
126    for _ in 0..3 {
127        doc.add_paragraph("");
128    }
129    {
130        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
131        p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
132    }
133    {
134        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
135        p.add_run("Complete Feature Showcase")
136            .size(28.0)
137            .color("FFFFFF")
138            .italic(true);
139    }
140    doc.add_paragraph("");
141    {
142        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
143        p.add_run("Every feature of the rdocx Rust library")
144            .size(13.0)
145            .color("B0C4DE");
146    }
147    {
148        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
149        p.add_run("demonstrated in a single document.")
150            .size(13.0)
151            .color("B0C4DE");
152    }
153
154    // ── TABLE OF CONTENTS ──
155    doc.add_paragraph("").page_break_before(true);
156    doc.insert_toc(doc.content_count(), 3);
157
158    // ── SECTION 1: TEXT FORMATTING ──
159    doc.add_paragraph("").page_break_before(true);
160    doc.add_paragraph("1. Text Formatting").style("Heading1");
161
162    // Paragraph Alignment
163    doc.add_paragraph("1.1 Paragraph Alignment")
164        .style("Heading2");
165    doc.add_paragraph("Left-aligned paragraph (default).")
166        .alignment(Alignment::Left);
167    doc.add_paragraph("Center-aligned paragraph.")
168        .alignment(Alignment::Center);
169    doc.add_paragraph("Right-aligned paragraph.")
170        .alignment(Alignment::Right);
171    doc.add_paragraph(
172        "Justified paragraph — this text is long enough to demonstrate how justified alignment \
173         distributes extra space across word gaps so lines fill the full width of the text area.",
174    )
175    .alignment(Alignment::Justify);
176
177    doc.add_paragraph("");
178
179    // Run Formatting
180    doc.add_paragraph("1.2 Run Formatting").style("Heading2");
181    {
182        let mut p = doc.add_paragraph("");
183        p.add_run("Bold").bold(true);
184        p.add_run(" | ");
185        p.add_run("Italic").italic(true);
186        p.add_run(" | ");
187        p.add_run("Bold Italic").bold(true).italic(true);
188        p.add_run(" | ");
189        p.add_run("Underline").underline(true);
190        p.add_run(" | ");
191        p.add_run("Strikethrough").strike(true);
192        p.add_run(" | ");
193        p.add_run("Double Strike").double_strike(true);
194    }
195    {
196        let mut p = doc.add_paragraph("");
197        p.add_run("Red").color("FF0000");
198        p.add_run(" | ");
199        p.add_run("Blue").color("0000FF");
200        p.add_run(" | ");
201        p.add_run("Green").color("00AA00");
202        p.add_run(" | ");
203        p.add_run("Highlighted").highlight("FFFF00");
204    }
205    {
206        let mut p = doc.add_paragraph("");
207        p.add_run("8pt").size(8.0);
208        p.add_run(" | ");
209        p.add_run("11pt").size(11.0);
210        p.add_run(" | ");
211        p.add_run("16pt").size(16.0);
212        p.add_run(" | ");
213        p.add_run("24pt").size(24.0);
214    }
215    {
216        let mut p = doc.add_paragraph("");
217        p.add_run("Arial").font("Arial");
218        p.add_run(" | ");
219        p.add_run("Times New Roman").font("Times New Roman");
220        p.add_run(" | ");
221        p.add_run("Courier New").font("Courier New");
222    }
223    {
224        let mut p = doc.add_paragraph("");
225        p.add_run("H");
226        p.add_run("2").subscript();
227        p.add_run("O (subscript) | E=mc");
228        p.add_run("2").superscript();
229        p.add_run(" (superscript)");
230    }
231    {
232        let mut p = doc.add_paragraph("");
233        p.add_run("ALL CAPS").all_caps(true);
234        p.add_run(" | ");
235        p.add_run("Small Caps").small_caps(true);
236        p.add_run(" | ");
237        p.add_run("Expanded").character_spacing(Length::twips(40));
238        p.add_run(" | ");
239        p.add_run("Hidden text (not visible)").hidden(true);
240    }
241
242    doc.add_paragraph("");
243
244    // Underline Styles
245    doc.add_paragraph("1.3 Underline Styles").style("Heading2");
246    {
247        let mut p = doc.add_paragraph("");
248        p.add_run("Single ")
249            .underline_style(rdocx::UnderlineStyle::Single);
250        p.add_run("Double ")
251            .underline_style(rdocx::UnderlineStyle::Double);
252        p.add_run("Thick ")
253            .underline_style(rdocx::UnderlineStyle::Thick);
254        p.add_run("Dotted ")
255            .underline_style(rdocx::UnderlineStyle::Dotted);
256        p.add_run("Dash ")
257            .underline_style(rdocx::UnderlineStyle::Dash);
258        p.add_run("Wave ")
259            .underline_style(rdocx::UnderlineStyle::Wave);
260    }
261
262    // ── SECTION 2: PARAGRAPH FORMATTING ──
263    doc.add_paragraph("").page_break_before(true);
264    doc.add_paragraph("2. Paragraph Formatting")
265        .style("Heading1");
266
267    doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
268    doc.add_paragraph("Paragraph with light green shading")
269        .shading("E2EFDA");
270    doc.add_paragraph("Paragraph with bottom border")
271        .border_bottom(BorderStyle::Single, 6, "2E75B6");
272    doc.add_paragraph("Paragraph with all borders (red)")
273        .border_all(BorderStyle::Single, 4, "FF0000");
274
275    doc.add_paragraph("2.2 Indentation").style("Heading2");
276    doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
277        .indent_left(Length::inches(1.0))
278        .hanging_indent(Length::inches(0.5));
279    doc.add_paragraph("0.5-inch first-line indent on this paragraph")
280        .first_line_indent(Length::inches(0.5));
281    doc.add_paragraph("Both left and right indent (0.75 inches each)")
282        .indent_left(Length::inches(0.75))
283        .indent_right(Length::inches(0.75));
284
285    doc.add_paragraph("2.3 Spacing & Line Height")
286        .style("Heading2");
287    doc.add_paragraph("Extra space before (24pt) and after (12pt)")
288        .space_before(Length::pt(24.0))
289        .space_after(Length::pt(12.0));
290    doc.add_paragraph(
291        "Double line spacing paragraph. This text should have extra vertical space between \
292         lines to demonstrate line_spacing_multiple(2.0).",
293    )
294    .line_spacing_multiple(2.0);
295    doc.add_paragraph("Exact 20pt line spacing (fixed height).")
296        .line_spacing(20.0);
297
298    doc.add_paragraph("2.4 Pagination Controls")
299        .style("Heading2");
300    doc.add_paragraph("keep_with_next — this paragraph stays with the next")
301        .keep_with_next(true);
302    doc.add_paragraph("(This paragraph was kept with the one above.)");
303    doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
304        .keep_together(true);
305    doc.add_paragraph("widow_control — prevents widow/orphan lines")
306        .widow_control(true);
307
308    // ── SECTION 3: LISTS ──
309    doc.add_paragraph("").page_break_before(true);
310    doc.add_paragraph("3. Lists").style("Heading1");
311
312    doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
313    doc.add_bullet_list_item("First item", 0);
314    doc.add_bullet_list_item("Second item", 0);
315    doc.add_bullet_list_item("Nested level 1", 1);
316    doc.add_bullet_list_item("Nested level 2", 2);
317    doc.add_bullet_list_item("Back to level 1", 1);
318    doc.add_bullet_list_item("Third item", 0);
319
320    doc.add_paragraph("");
321
322    doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
323    doc.add_numbered_list_item("First numbered item", 0);
324    doc.add_numbered_list_item("Second numbered item", 0);
325    doc.add_numbered_list_item("Sub-item A", 1);
326    doc.add_numbered_list_item("Sub-item B", 1);
327    doc.add_numbered_list_item("Third numbered item", 0);
328
329    // ── SECTION 4: TAB STOPS ──
330    doc.add_paragraph("");
331    doc.add_paragraph("4. Tab Stops").style("Heading1");
332
333    doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
334    doc.add_paragraph("Left\tCenter\tRight\tDecimal")
335        .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
336        .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
337        .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
338        .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
339
340    doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
341    doc.add_paragraph("Item\t\tPrice")
342        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
343        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
344        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
345    doc.add_paragraph("Widget\t\t$19.99")
346        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
347        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
348        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
349    doc.add_paragraph("Gadget\t\t$249.50")
350        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
351        .add_tab_stop_with_leader(
352            TabAlignment::Right,
353            Length::inches(4.0),
354            TabLeader::Underscore,
355        )
356        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
357
358    // ── SECTION 5: TABLES ──
359    doc.add_paragraph("").page_break_before(true);
360    doc.add_paragraph("5. Tables").style("Heading1");
361
362    doc.add_paragraph("5.1 Basic Table").style("Heading2");
363    {
364        let mut tbl = doc
365            .add_table(4, 3)
366            .borders(BorderStyle::Single, 4, "000000");
367        for col in 0..3 {
368            tbl.cell(0, col).unwrap().shading("2E75B6");
369        }
370        tbl.cell(0, 0).unwrap().set_text("Name");
371        tbl.cell(0, 1).unwrap().set_text("Role");
372        tbl.cell(0, 2).unwrap().set_text("Location");
373        tbl.cell(1, 0).unwrap().set_text("Walter White");
374        tbl.cell(1, 1).unwrap().set_text("CEO");
375        tbl.cell(1, 2).unwrap().set_text("Albuquerque");
376        tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
377        tbl.cell(2, 1).unwrap().set_text("CTO");
378        tbl.cell(2, 2).unwrap().set_text("Remote");
379        tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
380        tbl.cell(3, 1).unwrap().set_text("Security");
381        tbl.cell(3, 2).unwrap().set_text("Washington");
382    }
383
384    doc.add_paragraph("");
385
386    doc.add_paragraph("5.2 Column Span & Cell Shading")
387        .style("Heading2");
388    {
389        let mut tbl = doc
390            .add_table(3, 4)
391            .borders(BorderStyle::Single, 4, "000000")
392            .width_pct(100.0);
393        tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
394        tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
395        tbl.cell(1, 0).unwrap().set_text("Region");
396        tbl.cell(1, 0).unwrap().shading("D6E4F0");
397        tbl.cell(1, 1).unwrap().set_text("Q1");
398        tbl.cell(1, 1).unwrap().shading("D6E4F0");
399        tbl.cell(1, 2).unwrap().set_text("Q2");
400        tbl.cell(1, 2).unwrap().shading("D6E4F0");
401        tbl.cell(1, 3).unwrap().set_text("Total");
402        tbl.cell(1, 3).unwrap().shading("D6E4F0");
403        tbl.cell(2, 0).unwrap().set_text("Americas");
404        tbl.cell(2, 1).unwrap().set_text("$2.4M");
405        tbl.cell(2, 2).unwrap().set_text("$2.7M");
406        tbl.cell(2, 3).unwrap().set_text("$5.1M");
407    }
408
409    doc.add_paragraph("");
410
411    doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
412    {
413        let mut tbl = doc
414            .add_table(4, 3)
415            .borders(BorderStyle::Single, 4, "000000");
416        tbl.cell(0, 0).unwrap().set_text("Category");
417        tbl.cell(0, 0).unwrap().shading("E2EFDA");
418        tbl.cell(0, 1).unwrap().set_text("Item");
419        tbl.cell(0, 1).unwrap().shading("E2EFDA");
420        tbl.cell(0, 2).unwrap().set_text("Price");
421        tbl.cell(0, 2).unwrap().shading("E2EFDA");
422        tbl.cell(1, 0).unwrap().set_text("Hardware");
423        tbl.cell(1, 0).unwrap().v_merge_restart();
424        tbl.cell(1, 1).unwrap().set_text("Laptop");
425        tbl.cell(1, 2).unwrap().set_text("$1,200");
426        tbl.cell(2, 0).unwrap().v_merge_continue();
427        tbl.cell(2, 1).unwrap().set_text("Monitor");
428        tbl.cell(2, 2).unwrap().set_text("$450");
429        tbl.cell(3, 0).unwrap().set_text("Software");
430        tbl.cell(3, 1).unwrap().set_text("IDE License");
431        tbl.cell(3, 2).unwrap().set_text("$200/yr");
432    }
433
434    doc.add_paragraph("");
435
436    doc.add_paragraph("5.4 Nested Table").style("Heading2");
437    {
438        let mut tbl = doc
439            .add_table(2, 2)
440            .borders(BorderStyle::Single, 6, "2E75B6");
441        tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
442        tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
443        tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
444        {
445            let mut cell = tbl.cell(1, 1).unwrap();
446            cell.set_text("Nested table below:");
447            let mut nested = cell
448                .add_table(2, 2)
449                .borders(BorderStyle::Single, 2, "FF6600");
450            nested.cell(0, 0).unwrap().set_text("A");
451            nested.cell(0, 1).unwrap().set_text("B");
452            nested.cell(1, 0).unwrap().set_text("C");
453            nested.cell(1, 1).unwrap().set_text("D");
454        }
455    }
456
457    doc.add_paragraph("");
458
459    doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
460        .style("Heading2");
461    {
462        let mut tbl = doc
463            .add_table(2, 3)
464            .borders(BorderStyle::Single, 4, "666666");
465        tbl.row(0).unwrap().height(Length::pt(50.0)).header();
466        tbl.cell(0, 0).unwrap().set_text("Top");
467        tbl.cell(0, 0)
468            .unwrap()
469            .vertical_alignment(VerticalAlignment::Top)
470            .shading("FFF2CC");
471        tbl.cell(0, 1).unwrap().set_text("Center");
472        tbl.cell(0, 1)
473            .unwrap()
474            .vertical_alignment(VerticalAlignment::Center)
475            .shading("D9E2F3");
476        tbl.cell(0, 2).unwrap().set_text("Bottom");
477        tbl.cell(0, 2)
478            .unwrap()
479            .vertical_alignment(VerticalAlignment::Bottom)
480            .shading("E2EFDA");
481        tbl.row(1).unwrap().cant_split();
482        tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
483        tbl.cell(1, 0).unwrap().no_wrap();
484        tbl.cell(1, 1).unwrap().set_text("Fixed width");
485        tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
486        tbl.cell(1, 2).unwrap().set_text("Normal");
487    }
488
489    // ── SECTION 6: IMAGES ──
490    doc.add_paragraph("").page_break_before(true);
491    doc.add_paragraph("6. Images").style("Heading1");
492
493    doc.add_paragraph("6.1 Inline Image").style("Heading2");
494    doc.add_paragraph("A blue gradient image below:");
495    let img = create_sample_png(200, 50, [0, 80, 200]);
496    doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
497
498    doc.add_paragraph("");
499    doc.add_paragraph("6.2 Header Image").style("Heading2");
500    let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
501    doc.set_header_image(
502        &hdr_img,
503        "header_logo.png",
504        Length::inches(2.0),
505        Length::inches(0.2),
506    );
507    doc.add_paragraph("The header now contains an inline image (check the top of this page).");
508
509    doc.add_paragraph("");
510    doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
511
512    // ── SECTION 7: CONTENT MANIPULATION ──
513    doc.add_paragraph("").page_break_before(true);
514    doc.add_paragraph("7. Content Manipulation")
515        .style("Heading1");
516
517    doc.add_paragraph("7.1 Placeholder Replacement")
518        .style("Heading2");
519    doc.add_paragraph("Customer: {{customer}}");
520    doc.add_paragraph("Date: {{date}}");
521    doc.add_paragraph("Reference: {{ref_number}}");
522    {
523        let mut tbl = doc
524            .add_table(2, 2)
525            .borders(BorderStyle::Single, 4, "000000");
526        tbl.cell(0, 0).unwrap().set_text("Project");
527        tbl.cell(0, 0).unwrap().shading("D6E4F0");
528        tbl.cell(0, 1).unwrap().set_text("{{project}}");
529        tbl.cell(1, 0).unwrap().set_text("Status");
530        tbl.cell(1, 0).unwrap().shading("D6E4F0");
531        tbl.cell(1, 1).unwrap().set_text("{{status}}");
532    }
533
534    let mut replacements = HashMap::new();
535    replacements.insert("{{customer}}", "Tensorbee Inc.");
536    replacements.insert("{{date}}", "February 22, 2026");
537    replacements.insert("{{ref_number}}", "TB-2026-001");
538    replacements.insert("{{project}}", "Infrastructure Upgrade");
539    replacements.insert("{{status}}", "In Progress");
540    let n = doc.replace_all(&replacements);
541    doc.add_paragraph(&format!("({n} placeholders replaced above)"));
542
543    doc.add_paragraph("");
544
545    doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
546    doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
547    let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
548    doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
549
550    doc.add_paragraph("");
551
552    doc.add_paragraph("7.3 Content Insertion").style("Heading2");
553    doc.add_paragraph("Section A: First content.");
554    doc.add_paragraph("Section C: Third content.");
555    if let Some(idx) = doc.find_content_index("Section C") {
556        doc.insert_paragraph(
557            idx,
558            "Section B: Inserted between A and C via find_content_index().",
559        );
560    }
561
562    // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
563    doc.add_paragraph("").section_break(SectionBreak::NextPage);
564    doc.add_paragraph("8. Section Breaks & Orientation")
565        .style("Heading1");
566    doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
567
568    {
569        let mut tbl = doc
570            .add_table(3, 7)
571            .borders(BorderStyle::Single, 4, "2E75B6");
572        let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
573        for (c, h) in headers.iter().enumerate() {
574            tbl.cell(0, c).unwrap().set_text(h);
575            tbl.cell(0, c).unwrap().shading("2E75B6");
576        }
577        let data = [
578            [
579                "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
580            ],
581            [
582                "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
583            ],
584        ];
585        for (r, row) in data.iter().enumerate() {
586            for (c, v) in row.iter().enumerate() {
587                tbl.cell(r + 1, c).unwrap().set_text(v);
588            }
589        }
590    }
591
592    doc.add_paragraph("")
593        .section_break(SectionBreak::NextPage)
594        .section_landscape();
595
596    // ── SECTION 9: CUSTOM STYLES ──
597    doc.add_paragraph("9. Custom Styles").style("Heading1");
598    doc.add_style(
599        StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
600            .based_on("Normal")
601            .paragraph_properties(rdocx_oxml::properties::CT_PPr {
602                shading: Some(rdocx_oxml::properties::CT_Shd {
603                    val: "clear".to_string(),
604                    color: None,
605                    fill: Some("FFF2CC".to_string()),
606                }),
607                ..Default::default()
608            })
609            .run_properties(rdocx_oxml::properties::CT_RPr {
610                bold: Some(true),
611                color: Some("C45911".to_string()),
612                ..Default::default()
613            }),
614    );
615    doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
616        .style("CustomHighlight");
617
618    doc.add_paragraph("");
619
620    // ── SECTION 10: DOCUMENT INTELLIGENCE ──
621    doc.add_paragraph("10. Document Intelligence API")
622        .style("Heading1");
623
624    let wc = doc.word_count();
625    let hc = doc.headings().len();
626    let ic = doc.images().len();
627    let lc = doc.links().len();
628    doc.add_paragraph(&format!("Word count: {wc}"));
629    doc.add_paragraph(&format!("Heading count: {hc}"));
630    doc.add_paragraph(&format!("Image count: {ic}"));
631    doc.add_paragraph(&format!("Link count: {lc}"));
632
633    let outline = doc.document_outline();
634    doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
635
636    let issues = doc.audit_accessibility();
637    doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
638    for issue in &issues {
639        doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
640    }
641
642    // ── SECTION 11: DOCUMENT MERGING ──
643    doc.add_paragraph("");
644    doc.add_paragraph("11. Document Merging").style("Heading1");
645    {
646        let mut other = Document::new();
647        other.add_paragraph("This paragraph was merged from another document using append().");
648        doc.append(&other);
649    }
650
651    doc.add_paragraph("");
652
653    // ── SUMMARY ──
654    doc.add_paragraph("Summary of Demonstrated Features")
655        .style("Heading1");
656    let features = [
657        "Page setup: size, margins, header/footer distance, gutter",
658        "Document metadata: title, author, subject, keywords",
659        "Headers and footers: text, images, different first page",
660        "Background images (full-page behind text)",
661        "Table of Contents generation with bookmarks",
662        "Text formatting: bold, italic, underline styles, strike, color, size, font",
663        "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
664        "Paragraph formatting: alignment, borders, shading, spacing, indentation",
665        "Pagination controls: keep-with-next, keep-together, widow control",
666        "Bullet and numbered lists with nesting",
667        "Tab stops with dot/underscore/hyphen leaders",
668        "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
669        "Row properties: header rows, exact height, cant-split",
670        "Cell properties: width, no-wrap, vertical alignment",
671        "Inline images and header images",
672        "Placeholder replacement (single and batch)",
673        "Regex-based find and replace",
674        "Content insertion at specific positions",
675        "Section breaks with mixed portrait/landscape",
676        "Custom paragraph and character styles",
677        "Document intelligence: word count, headings, outline, images, links",
678        "Accessibility audit",
679        "Document merging (append)",
680        "Export: DOCX, PDF, HTML, Markdown",
681    ];
682    for f in &features {
683        doc.add_bullet_list_item(f, 0);
684    }
685    doc.add_paragraph("");
686    doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
687        .alignment(Alignment::Center)
688        .shading("E2EFDA")
689        .border_all(BorderStyle::Single, 2, "00AA00");
690
691    doc
692}
Source

pub fn replace_regex( &mut self, pattern: &str, replacement: &str, ) -> Result<usize>

Replace all regex matches with replacement throughout the document.

The replacement string supports capture groups: $1, $2, etc. Searches body paragraphs, tables (including nested), headers, and footers. Returns the total number of replacements made, or an error if the regex is invalid.

Examples found in repository?
examples/generate_all_samples.rs (line 547)
97fn generate_feature_showcase(_samples_dir: &Path) -> Document {
98    let mut doc = Document::new();
99
100    // ── Page Setup & Metadata ──
101    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
102    doc.set_margins(
103        Length::inches(1.0),
104        Length::inches(1.0),
105        Length::inches(1.0),
106        Length::inches(1.0),
107    );
108    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
109    doc.set_gutter(Length::twips(0));
110    doc.set_title("rdocx Feature Showcase");
111    doc.set_author("rdocx Sample Generator");
112    doc.set_subject("Comprehensive feature demonstration");
113    doc.set_keywords("rdocx, docx, rust, sample, showcase");
114
115    // Headers & Footers with different first page
116    doc.set_different_first_page(true);
117    doc.set_first_page_header("rdocx");
118    doc.set_first_page_footer("Feature Showcase — Cover Page");
119    doc.set_header("rdocx Feature Showcase");
120    doc.set_footer("Generated by rdocx");
121
122    // ── COVER PAGE ──
123    let bg = create_sample_png(612, 792, [20, 45, 90]);
124    doc.add_background_image(&bg, "cover_bg.png");
125
126    for _ in 0..3 {
127        doc.add_paragraph("");
128    }
129    {
130        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
131        p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
132    }
133    {
134        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
135        p.add_run("Complete Feature Showcase")
136            .size(28.0)
137            .color("FFFFFF")
138            .italic(true);
139    }
140    doc.add_paragraph("");
141    {
142        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
143        p.add_run("Every feature of the rdocx Rust library")
144            .size(13.0)
145            .color("B0C4DE");
146    }
147    {
148        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
149        p.add_run("demonstrated in a single document.")
150            .size(13.0)
151            .color("B0C4DE");
152    }
153
154    // ── TABLE OF CONTENTS ──
155    doc.add_paragraph("").page_break_before(true);
156    doc.insert_toc(doc.content_count(), 3);
157
158    // ── SECTION 1: TEXT FORMATTING ──
159    doc.add_paragraph("").page_break_before(true);
160    doc.add_paragraph("1. Text Formatting").style("Heading1");
161
162    // Paragraph Alignment
163    doc.add_paragraph("1.1 Paragraph Alignment")
164        .style("Heading2");
165    doc.add_paragraph("Left-aligned paragraph (default).")
166        .alignment(Alignment::Left);
167    doc.add_paragraph("Center-aligned paragraph.")
168        .alignment(Alignment::Center);
169    doc.add_paragraph("Right-aligned paragraph.")
170        .alignment(Alignment::Right);
171    doc.add_paragraph(
172        "Justified paragraph — this text is long enough to demonstrate how justified alignment \
173         distributes extra space across word gaps so lines fill the full width of the text area.",
174    )
175    .alignment(Alignment::Justify);
176
177    doc.add_paragraph("");
178
179    // Run Formatting
180    doc.add_paragraph("1.2 Run Formatting").style("Heading2");
181    {
182        let mut p = doc.add_paragraph("");
183        p.add_run("Bold").bold(true);
184        p.add_run(" | ");
185        p.add_run("Italic").italic(true);
186        p.add_run(" | ");
187        p.add_run("Bold Italic").bold(true).italic(true);
188        p.add_run(" | ");
189        p.add_run("Underline").underline(true);
190        p.add_run(" | ");
191        p.add_run("Strikethrough").strike(true);
192        p.add_run(" | ");
193        p.add_run("Double Strike").double_strike(true);
194    }
195    {
196        let mut p = doc.add_paragraph("");
197        p.add_run("Red").color("FF0000");
198        p.add_run(" | ");
199        p.add_run("Blue").color("0000FF");
200        p.add_run(" | ");
201        p.add_run("Green").color("00AA00");
202        p.add_run(" | ");
203        p.add_run("Highlighted").highlight("FFFF00");
204    }
205    {
206        let mut p = doc.add_paragraph("");
207        p.add_run("8pt").size(8.0);
208        p.add_run(" | ");
209        p.add_run("11pt").size(11.0);
210        p.add_run(" | ");
211        p.add_run("16pt").size(16.0);
212        p.add_run(" | ");
213        p.add_run("24pt").size(24.0);
214    }
215    {
216        let mut p = doc.add_paragraph("");
217        p.add_run("Arial").font("Arial");
218        p.add_run(" | ");
219        p.add_run("Times New Roman").font("Times New Roman");
220        p.add_run(" | ");
221        p.add_run("Courier New").font("Courier New");
222    }
223    {
224        let mut p = doc.add_paragraph("");
225        p.add_run("H");
226        p.add_run("2").subscript();
227        p.add_run("O (subscript) | E=mc");
228        p.add_run("2").superscript();
229        p.add_run(" (superscript)");
230    }
231    {
232        let mut p = doc.add_paragraph("");
233        p.add_run("ALL CAPS").all_caps(true);
234        p.add_run(" | ");
235        p.add_run("Small Caps").small_caps(true);
236        p.add_run(" | ");
237        p.add_run("Expanded").character_spacing(Length::twips(40));
238        p.add_run(" | ");
239        p.add_run("Hidden text (not visible)").hidden(true);
240    }
241
242    doc.add_paragraph("");
243
244    // Underline Styles
245    doc.add_paragraph("1.3 Underline Styles").style("Heading2");
246    {
247        let mut p = doc.add_paragraph("");
248        p.add_run("Single ")
249            .underline_style(rdocx::UnderlineStyle::Single);
250        p.add_run("Double ")
251            .underline_style(rdocx::UnderlineStyle::Double);
252        p.add_run("Thick ")
253            .underline_style(rdocx::UnderlineStyle::Thick);
254        p.add_run("Dotted ")
255            .underline_style(rdocx::UnderlineStyle::Dotted);
256        p.add_run("Dash ")
257            .underline_style(rdocx::UnderlineStyle::Dash);
258        p.add_run("Wave ")
259            .underline_style(rdocx::UnderlineStyle::Wave);
260    }
261
262    // ── SECTION 2: PARAGRAPH FORMATTING ──
263    doc.add_paragraph("").page_break_before(true);
264    doc.add_paragraph("2. Paragraph Formatting")
265        .style("Heading1");
266
267    doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
268    doc.add_paragraph("Paragraph with light green shading")
269        .shading("E2EFDA");
270    doc.add_paragraph("Paragraph with bottom border")
271        .border_bottom(BorderStyle::Single, 6, "2E75B6");
272    doc.add_paragraph("Paragraph with all borders (red)")
273        .border_all(BorderStyle::Single, 4, "FF0000");
274
275    doc.add_paragraph("2.2 Indentation").style("Heading2");
276    doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
277        .indent_left(Length::inches(1.0))
278        .hanging_indent(Length::inches(0.5));
279    doc.add_paragraph("0.5-inch first-line indent on this paragraph")
280        .first_line_indent(Length::inches(0.5));
281    doc.add_paragraph("Both left and right indent (0.75 inches each)")
282        .indent_left(Length::inches(0.75))
283        .indent_right(Length::inches(0.75));
284
285    doc.add_paragraph("2.3 Spacing & Line Height")
286        .style("Heading2");
287    doc.add_paragraph("Extra space before (24pt) and after (12pt)")
288        .space_before(Length::pt(24.0))
289        .space_after(Length::pt(12.0));
290    doc.add_paragraph(
291        "Double line spacing paragraph. This text should have extra vertical space between \
292         lines to demonstrate line_spacing_multiple(2.0).",
293    )
294    .line_spacing_multiple(2.0);
295    doc.add_paragraph("Exact 20pt line spacing (fixed height).")
296        .line_spacing(20.0);
297
298    doc.add_paragraph("2.4 Pagination Controls")
299        .style("Heading2");
300    doc.add_paragraph("keep_with_next — this paragraph stays with the next")
301        .keep_with_next(true);
302    doc.add_paragraph("(This paragraph was kept with the one above.)");
303    doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
304        .keep_together(true);
305    doc.add_paragraph("widow_control — prevents widow/orphan lines")
306        .widow_control(true);
307
308    // ── SECTION 3: LISTS ──
309    doc.add_paragraph("").page_break_before(true);
310    doc.add_paragraph("3. Lists").style("Heading1");
311
312    doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
313    doc.add_bullet_list_item("First item", 0);
314    doc.add_bullet_list_item("Second item", 0);
315    doc.add_bullet_list_item("Nested level 1", 1);
316    doc.add_bullet_list_item("Nested level 2", 2);
317    doc.add_bullet_list_item("Back to level 1", 1);
318    doc.add_bullet_list_item("Third item", 0);
319
320    doc.add_paragraph("");
321
322    doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
323    doc.add_numbered_list_item("First numbered item", 0);
324    doc.add_numbered_list_item("Second numbered item", 0);
325    doc.add_numbered_list_item("Sub-item A", 1);
326    doc.add_numbered_list_item("Sub-item B", 1);
327    doc.add_numbered_list_item("Third numbered item", 0);
328
329    // ── SECTION 4: TAB STOPS ──
330    doc.add_paragraph("");
331    doc.add_paragraph("4. Tab Stops").style("Heading1");
332
333    doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
334    doc.add_paragraph("Left\tCenter\tRight\tDecimal")
335        .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
336        .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
337        .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
338        .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
339
340    doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
341    doc.add_paragraph("Item\t\tPrice")
342        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
343        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
344        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
345    doc.add_paragraph("Widget\t\t$19.99")
346        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
347        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
348        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
349    doc.add_paragraph("Gadget\t\t$249.50")
350        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
351        .add_tab_stop_with_leader(
352            TabAlignment::Right,
353            Length::inches(4.0),
354            TabLeader::Underscore,
355        )
356        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
357
358    // ── SECTION 5: TABLES ──
359    doc.add_paragraph("").page_break_before(true);
360    doc.add_paragraph("5. Tables").style("Heading1");
361
362    doc.add_paragraph("5.1 Basic Table").style("Heading2");
363    {
364        let mut tbl = doc
365            .add_table(4, 3)
366            .borders(BorderStyle::Single, 4, "000000");
367        for col in 0..3 {
368            tbl.cell(0, col).unwrap().shading("2E75B6");
369        }
370        tbl.cell(0, 0).unwrap().set_text("Name");
371        tbl.cell(0, 1).unwrap().set_text("Role");
372        tbl.cell(0, 2).unwrap().set_text("Location");
373        tbl.cell(1, 0).unwrap().set_text("Walter White");
374        tbl.cell(1, 1).unwrap().set_text("CEO");
375        tbl.cell(1, 2).unwrap().set_text("Albuquerque");
376        tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
377        tbl.cell(2, 1).unwrap().set_text("CTO");
378        tbl.cell(2, 2).unwrap().set_text("Remote");
379        tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
380        tbl.cell(3, 1).unwrap().set_text("Security");
381        tbl.cell(3, 2).unwrap().set_text("Washington");
382    }
383
384    doc.add_paragraph("");
385
386    doc.add_paragraph("5.2 Column Span & Cell Shading")
387        .style("Heading2");
388    {
389        let mut tbl = doc
390            .add_table(3, 4)
391            .borders(BorderStyle::Single, 4, "000000")
392            .width_pct(100.0);
393        tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
394        tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
395        tbl.cell(1, 0).unwrap().set_text("Region");
396        tbl.cell(1, 0).unwrap().shading("D6E4F0");
397        tbl.cell(1, 1).unwrap().set_text("Q1");
398        tbl.cell(1, 1).unwrap().shading("D6E4F0");
399        tbl.cell(1, 2).unwrap().set_text("Q2");
400        tbl.cell(1, 2).unwrap().shading("D6E4F0");
401        tbl.cell(1, 3).unwrap().set_text("Total");
402        tbl.cell(1, 3).unwrap().shading("D6E4F0");
403        tbl.cell(2, 0).unwrap().set_text("Americas");
404        tbl.cell(2, 1).unwrap().set_text("$2.4M");
405        tbl.cell(2, 2).unwrap().set_text("$2.7M");
406        tbl.cell(2, 3).unwrap().set_text("$5.1M");
407    }
408
409    doc.add_paragraph("");
410
411    doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
412    {
413        let mut tbl = doc
414            .add_table(4, 3)
415            .borders(BorderStyle::Single, 4, "000000");
416        tbl.cell(0, 0).unwrap().set_text("Category");
417        tbl.cell(0, 0).unwrap().shading("E2EFDA");
418        tbl.cell(0, 1).unwrap().set_text("Item");
419        tbl.cell(0, 1).unwrap().shading("E2EFDA");
420        tbl.cell(0, 2).unwrap().set_text("Price");
421        tbl.cell(0, 2).unwrap().shading("E2EFDA");
422        tbl.cell(1, 0).unwrap().set_text("Hardware");
423        tbl.cell(1, 0).unwrap().v_merge_restart();
424        tbl.cell(1, 1).unwrap().set_text("Laptop");
425        tbl.cell(1, 2).unwrap().set_text("$1,200");
426        tbl.cell(2, 0).unwrap().v_merge_continue();
427        tbl.cell(2, 1).unwrap().set_text("Monitor");
428        tbl.cell(2, 2).unwrap().set_text("$450");
429        tbl.cell(3, 0).unwrap().set_text("Software");
430        tbl.cell(3, 1).unwrap().set_text("IDE License");
431        tbl.cell(3, 2).unwrap().set_text("$200/yr");
432    }
433
434    doc.add_paragraph("");
435
436    doc.add_paragraph("5.4 Nested Table").style("Heading2");
437    {
438        let mut tbl = doc
439            .add_table(2, 2)
440            .borders(BorderStyle::Single, 6, "2E75B6");
441        tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
442        tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
443        tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
444        {
445            let mut cell = tbl.cell(1, 1).unwrap();
446            cell.set_text("Nested table below:");
447            let mut nested = cell
448                .add_table(2, 2)
449                .borders(BorderStyle::Single, 2, "FF6600");
450            nested.cell(0, 0).unwrap().set_text("A");
451            nested.cell(0, 1).unwrap().set_text("B");
452            nested.cell(1, 0).unwrap().set_text("C");
453            nested.cell(1, 1).unwrap().set_text("D");
454        }
455    }
456
457    doc.add_paragraph("");
458
459    doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
460        .style("Heading2");
461    {
462        let mut tbl = doc
463            .add_table(2, 3)
464            .borders(BorderStyle::Single, 4, "666666");
465        tbl.row(0).unwrap().height(Length::pt(50.0)).header();
466        tbl.cell(0, 0).unwrap().set_text("Top");
467        tbl.cell(0, 0)
468            .unwrap()
469            .vertical_alignment(VerticalAlignment::Top)
470            .shading("FFF2CC");
471        tbl.cell(0, 1).unwrap().set_text("Center");
472        tbl.cell(0, 1)
473            .unwrap()
474            .vertical_alignment(VerticalAlignment::Center)
475            .shading("D9E2F3");
476        tbl.cell(0, 2).unwrap().set_text("Bottom");
477        tbl.cell(0, 2)
478            .unwrap()
479            .vertical_alignment(VerticalAlignment::Bottom)
480            .shading("E2EFDA");
481        tbl.row(1).unwrap().cant_split();
482        tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
483        tbl.cell(1, 0).unwrap().no_wrap();
484        tbl.cell(1, 1).unwrap().set_text("Fixed width");
485        tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
486        tbl.cell(1, 2).unwrap().set_text("Normal");
487    }
488
489    // ── SECTION 6: IMAGES ──
490    doc.add_paragraph("").page_break_before(true);
491    doc.add_paragraph("6. Images").style("Heading1");
492
493    doc.add_paragraph("6.1 Inline Image").style("Heading2");
494    doc.add_paragraph("A blue gradient image below:");
495    let img = create_sample_png(200, 50, [0, 80, 200]);
496    doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
497
498    doc.add_paragraph("");
499    doc.add_paragraph("6.2 Header Image").style("Heading2");
500    let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
501    doc.set_header_image(
502        &hdr_img,
503        "header_logo.png",
504        Length::inches(2.0),
505        Length::inches(0.2),
506    );
507    doc.add_paragraph("The header now contains an inline image (check the top of this page).");
508
509    doc.add_paragraph("");
510    doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
511
512    // ── SECTION 7: CONTENT MANIPULATION ──
513    doc.add_paragraph("").page_break_before(true);
514    doc.add_paragraph("7. Content Manipulation")
515        .style("Heading1");
516
517    doc.add_paragraph("7.1 Placeholder Replacement")
518        .style("Heading2");
519    doc.add_paragraph("Customer: {{customer}}");
520    doc.add_paragraph("Date: {{date}}");
521    doc.add_paragraph("Reference: {{ref_number}}");
522    {
523        let mut tbl = doc
524            .add_table(2, 2)
525            .borders(BorderStyle::Single, 4, "000000");
526        tbl.cell(0, 0).unwrap().set_text("Project");
527        tbl.cell(0, 0).unwrap().shading("D6E4F0");
528        tbl.cell(0, 1).unwrap().set_text("{{project}}");
529        tbl.cell(1, 0).unwrap().set_text("Status");
530        tbl.cell(1, 0).unwrap().shading("D6E4F0");
531        tbl.cell(1, 1).unwrap().set_text("{{status}}");
532    }
533
534    let mut replacements = HashMap::new();
535    replacements.insert("{{customer}}", "Tensorbee Inc.");
536    replacements.insert("{{date}}", "February 22, 2026");
537    replacements.insert("{{ref_number}}", "TB-2026-001");
538    replacements.insert("{{project}}", "Infrastructure Upgrade");
539    replacements.insert("{{status}}", "In Progress");
540    let n = doc.replace_all(&replacements);
541    doc.add_paragraph(&format!("({n} placeholders replaced above)"));
542
543    doc.add_paragraph("");
544
545    doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
546    doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
547    let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
548    doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
549
550    doc.add_paragraph("");
551
552    doc.add_paragraph("7.3 Content Insertion").style("Heading2");
553    doc.add_paragraph("Section A: First content.");
554    doc.add_paragraph("Section C: Third content.");
555    if let Some(idx) = doc.find_content_index("Section C") {
556        doc.insert_paragraph(
557            idx,
558            "Section B: Inserted between A and C via find_content_index().",
559        );
560    }
561
562    // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
563    doc.add_paragraph("").section_break(SectionBreak::NextPage);
564    doc.add_paragraph("8. Section Breaks & Orientation")
565        .style("Heading1");
566    doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
567
568    {
569        let mut tbl = doc
570            .add_table(3, 7)
571            .borders(BorderStyle::Single, 4, "2E75B6");
572        let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
573        for (c, h) in headers.iter().enumerate() {
574            tbl.cell(0, c).unwrap().set_text(h);
575            tbl.cell(0, c).unwrap().shading("2E75B6");
576        }
577        let data = [
578            [
579                "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
580            ],
581            [
582                "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
583            ],
584        ];
585        for (r, row) in data.iter().enumerate() {
586            for (c, v) in row.iter().enumerate() {
587                tbl.cell(r + 1, c).unwrap().set_text(v);
588            }
589        }
590    }
591
592    doc.add_paragraph("")
593        .section_break(SectionBreak::NextPage)
594        .section_landscape();
595
596    // ── SECTION 9: CUSTOM STYLES ──
597    doc.add_paragraph("9. Custom Styles").style("Heading1");
598    doc.add_style(
599        StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
600            .based_on("Normal")
601            .paragraph_properties(rdocx_oxml::properties::CT_PPr {
602                shading: Some(rdocx_oxml::properties::CT_Shd {
603                    val: "clear".to_string(),
604                    color: None,
605                    fill: Some("FFF2CC".to_string()),
606                }),
607                ..Default::default()
608            })
609            .run_properties(rdocx_oxml::properties::CT_RPr {
610                bold: Some(true),
611                color: Some("C45911".to_string()),
612                ..Default::default()
613            }),
614    );
615    doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
616        .style("CustomHighlight");
617
618    doc.add_paragraph("");
619
620    // ── SECTION 10: DOCUMENT INTELLIGENCE ──
621    doc.add_paragraph("10. Document Intelligence API")
622        .style("Heading1");
623
624    let wc = doc.word_count();
625    let hc = doc.headings().len();
626    let ic = doc.images().len();
627    let lc = doc.links().len();
628    doc.add_paragraph(&format!("Word count: {wc}"));
629    doc.add_paragraph(&format!("Heading count: {hc}"));
630    doc.add_paragraph(&format!("Image count: {ic}"));
631    doc.add_paragraph(&format!("Link count: {lc}"));
632
633    let outline = doc.document_outline();
634    doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
635
636    let issues = doc.audit_accessibility();
637    doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
638    for issue in &issues {
639        doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
640    }
641
642    // ── SECTION 11: DOCUMENT MERGING ──
643    doc.add_paragraph("");
644    doc.add_paragraph("11. Document Merging").style("Heading1");
645    {
646        let mut other = Document::new();
647        other.add_paragraph("This paragraph was merged from another document using append().");
648        doc.append(&other);
649    }
650
651    doc.add_paragraph("");
652
653    // ── SUMMARY ──
654    doc.add_paragraph("Summary of Demonstrated Features")
655        .style("Heading1");
656    let features = [
657        "Page setup: size, margins, header/footer distance, gutter",
658        "Document metadata: title, author, subject, keywords",
659        "Headers and footers: text, images, different first page",
660        "Background images (full-page behind text)",
661        "Table of Contents generation with bookmarks",
662        "Text formatting: bold, italic, underline styles, strike, color, size, font",
663        "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
664        "Paragraph formatting: alignment, borders, shading, spacing, indentation",
665        "Pagination controls: keep-with-next, keep-together, widow control",
666        "Bullet and numbered lists with nesting",
667        "Tab stops with dot/underscore/hyphen leaders",
668        "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
669        "Row properties: header rows, exact height, cant-split",
670        "Cell properties: width, no-wrap, vertical alignment",
671        "Inline images and header images",
672        "Placeholder replacement (single and batch)",
673        "Regex-based find and replace",
674        "Content insertion at specific positions",
675        "Section breaks with mixed portrait/landscape",
676        "Custom paragraph and character styles",
677        "Document intelligence: word count, headings, outline, images, links",
678        "Accessibility audit",
679        "Document merging (append)",
680        "Export: DOCX, PDF, HTML, Markdown",
681    ];
682    for f in &features {
683        doc.add_bullet_list_item(f, 0);
684    }
685    doc.add_paragraph("");
686    doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
687        .alignment(Alignment::Center)
688        .shading("E2EFDA")
689        .border_all(BorderStyle::Single, 2, "00AA00");
690
691    doc
692}
Source

pub fn replace_all_regex( &mut self, patterns: &[(String, String)], ) -> Result<usize>

Replace multiple regex patterns at once. Returns total replacements.

Source

pub fn to_pdf(&self) -> Result<Vec<u8>>

Render the document to PDF bytes.

This performs a full layout pass (font shaping, line breaking, pagination) and then renders the result to a PDF document.

Font resolution order:

  1. Fonts embedded in the DOCX file (word/fonts/)
  2. System fonts when the default system-fonts feature is enabled
  3. Always-available bundled metric-compatible fonts
Examples found in repository?
examples/generate_pdf.rs (line 10)
7fn main() {
8    // Test 1: Simple document
9    let doc = Document::new();
10    match doc.to_pdf() {
11        Ok(bytes) => {
12            std::fs::write("/tmp/rdocx_simple.pdf", &bytes).unwrap();
13            println!("Simple PDF: {} bytes -> /tmp/rdocx_simple.pdf", bytes.len());
14        }
15        Err(e) => println!("Simple PDF failed: {e}"),
16    }
17
18    // Test 2: Document with content
19    let mut doc = Document::new();
20    doc.set_title("Test PDF Document");
21    doc.set_author("rdocx-pdf");
22    doc.add_paragraph("Chapter 1: Introduction")
23        .style("Heading1");
24    doc.add_paragraph(
25        "This is a test document generated by rdocx and rendered to PDF. \
26         It demonstrates text rendering with proper font shaping and pagination.",
27    );
28    doc.add_paragraph("Section 1.1").style("Heading2");
29    doc.add_paragraph("More content in a sub-section.");
30
31    {
32        let mut table = doc.add_table(2, 3);
33        for r in 0..2 {
34            for c in 0..3 {
35                if let Some(mut cell) = table.cell(r, c) {
36                    cell.set_text(&format!("R{}C{}", r + 1, c + 1));
37                }
38            }
39        }
40    }
41
42    doc.add_paragraph("After the table.");
43
44    match doc.to_pdf() {
45        Ok(bytes) => {
46            std::fs::write("/tmp/rdocx_content.pdf", &bytes).unwrap();
47            println!(
48                "Content PDF: {} bytes -> /tmp/rdocx_content.pdf",
49                bytes.len()
50            );
51        }
52        Err(e) => println!("Content PDF failed: {e}"),
53    }
54
55    // Test 3: From feature_showcase.docx
56    let showcase_path = concat!(
57        env!("CARGO_MANIFEST_DIR"),
58        "/../../samples/feature_showcase.docx"
59    );
60    match Document::open(showcase_path) {
61        Ok(doc) => match doc.to_pdf() {
62            Ok(bytes) => {
63                std::fs::write("/tmp/rdocx_showcase.pdf", &bytes).unwrap();
64                println!(
65                    "Showcase PDF: {} bytes -> /tmp/rdocx_showcase.pdf",
66                    bytes.len()
67                );
68            }
69            Err(e) => println!("Showcase PDF failed: {e}"),
70        },
71        Err(e) => println!("Failed to open showcase: {e}"),
72    }
73}
Source

pub fn to_pdf_deterministic(&self) -> Result<Vec<u8>>

Render the document to PDF bytes using bundled fonts without system font discovery.

The deterministic layout is cached independently from the normal-font layout and is suitable for reproducible render baselines.

Examples found in repository?
examples/generate_all_samples.rs (line 74)
61fn export_all(dir: &Path, name: &str, mut doc: Document) {
62    let docx_path = dir.join(format!("{name}.docx"));
63    doc.save(&docx_path).unwrap();
64    println!("  {name}.docx");
65
66    let png = doc
67        .render_page_to_png_deterministic(0, 150.0)
68        .unwrap()
69        .expect("every generated sample should have a first page");
70    let png_path = dir.join(format!("{name}.png"));
71    std::fs::write(&png_path, &png).unwrap();
72    println!("  {name}.png ({} bytes)", png.len());
73
74    match doc.to_pdf_deterministic() {
75        Ok(pdf) => {
76            let pdf_path = dir.join(format!("{name}.pdf"));
77            std::fs::write(&pdf_path, &pdf).unwrap();
78            println!("  {name}.pdf ({} bytes)", pdf.len());
79        }
80        Err(e) => println!("  {name}.pdf — skipped: {e}"),
81    }
82
83    let html = doc.to_html();
84    let html_path = dir.join(format!("{name}.html"));
85    std::fs::write(&html_path, &html).unwrap();
86    println!("  {name}.html ({} bytes)", html.len());
87
88    let md = doc.to_markdown();
89    let md_path = dir.join(format!("{name}.md"));
90    std::fs::write(&md_path, &md).unwrap();
91    println!("  {name}.md ({} bytes)", md.len());
92}
Source

pub fn to_pdf_with_fonts(&self, font_files: &[(&str, &[u8])]) -> Result<Vec<u8>>

Render the document to PDF bytes with user-provided font files.

User-provided fonts take highest priority in font resolution.

§Arguments
  • font_files - Additional font files to use. Each entry is (family_name, font_bytes).

Font resolution order:

  1. User-provided fonts (this parameter)
  2. Fonts embedded in the DOCX file (word/fonts/)
  3. System fonts when the default system-fonts feature is enabled
  4. Always-available bundled metric-compatible fonts
Source

pub fn save_pdf<P: AsRef<Path>>(&self, path: P) -> Result<()>

Save the document as a PDF file.

Source

pub fn to_html(&self) -> String

Convert the document to a complete HTML document string.

Examples found in repository?
examples/convert_html_md.rs (line 6)
3fn main() {
4    let doc = Document::open("samples/feature_showcase.docx").expect("Failed to open document");
5
6    let html = doc.to_html();
7    std::fs::write("/tmp/feature_showcase.html", &html).expect("Failed to write HTML");
8    println!("HTML: {} bytes -> /tmp/feature_showcase.html", html.len());
9
10    let md = doc.to_markdown();
11    std::fs::write("/tmp/feature_showcase.md", &md).expect("Failed to write Markdown");
12    println!("Markdown: {} bytes -> /tmp/feature_showcase.md", md.len());
13
14    // Simple document
15    let mut simple = Document::new();
16    simple.add_paragraph("Hello, World!");
17    let html = simple.to_html();
18    println!("\n--- Simple HTML ---\n{html}");
19
20    let md = simple.to_markdown();
21    println!("--- Simple Markdown ---\n{md}");
22}
More examples
Hide additional examples
examples/generate_all_samples.rs (line 83)
61fn export_all(dir: &Path, name: &str, mut doc: Document) {
62    let docx_path = dir.join(format!("{name}.docx"));
63    doc.save(&docx_path).unwrap();
64    println!("  {name}.docx");
65
66    let png = doc
67        .render_page_to_png_deterministic(0, 150.0)
68        .unwrap()
69        .expect("every generated sample should have a first page");
70    let png_path = dir.join(format!("{name}.png"));
71    std::fs::write(&png_path, &png).unwrap();
72    println!("  {name}.png ({} bytes)", png.len());
73
74    match doc.to_pdf_deterministic() {
75        Ok(pdf) => {
76            let pdf_path = dir.join(format!("{name}.pdf"));
77            std::fs::write(&pdf_path, &pdf).unwrap();
78            println!("  {name}.pdf ({} bytes)", pdf.len());
79        }
80        Err(e) => println!("  {name}.pdf — skipped: {e}"),
81    }
82
83    let html = doc.to_html();
84    let html_path = dir.join(format!("{name}.html"));
85    std::fs::write(&html_path, &html).unwrap();
86    println!("  {name}.html ({} bytes)", html.len());
87
88    let md = doc.to_markdown();
89    let md_path = dir.join(format!("{name}.md"));
90    std::fs::write(&md_path, &md).unwrap();
91    println!("  {name}.md ({} bytes)", md.len());
92}
Source

pub fn to_html_fragment(&self) -> String

Convert the document to an HTML fragment (body content only, no <html> wrapper).

Source

pub fn to_markdown(&self) -> String

Convert the document to Markdown.

Examples found in repository?
examples/convert_html_md.rs (line 10)
3fn main() {
4    let doc = Document::open("samples/feature_showcase.docx").expect("Failed to open document");
5
6    let html = doc.to_html();
7    std::fs::write("/tmp/feature_showcase.html", &html).expect("Failed to write HTML");
8    println!("HTML: {} bytes -> /tmp/feature_showcase.html", html.len());
9
10    let md = doc.to_markdown();
11    std::fs::write("/tmp/feature_showcase.md", &md).expect("Failed to write Markdown");
12    println!("Markdown: {} bytes -> /tmp/feature_showcase.md", md.len());
13
14    // Simple document
15    let mut simple = Document::new();
16    simple.add_paragraph("Hello, World!");
17    let html = simple.to_html();
18    println!("\n--- Simple HTML ---\n{html}");
19
20    let md = simple.to_markdown();
21    println!("--- Simple Markdown ---\n{md}");
22}
More examples
Hide additional examples
examples/generate_all_samples.rs (line 88)
61fn export_all(dir: &Path, name: &str, mut doc: Document) {
62    let docx_path = dir.join(format!("{name}.docx"));
63    doc.save(&docx_path).unwrap();
64    println!("  {name}.docx");
65
66    let png = doc
67        .render_page_to_png_deterministic(0, 150.0)
68        .unwrap()
69        .expect("every generated sample should have a first page");
70    let png_path = dir.join(format!("{name}.png"));
71    std::fs::write(&png_path, &png).unwrap();
72    println!("  {name}.png ({} bytes)", png.len());
73
74    match doc.to_pdf_deterministic() {
75        Ok(pdf) => {
76            let pdf_path = dir.join(format!("{name}.pdf"));
77            std::fs::write(&pdf_path, &pdf).unwrap();
78            println!("  {name}.pdf ({} bytes)", pdf.len());
79        }
80        Err(e) => println!("  {name}.pdf — skipped: {e}"),
81    }
82
83    let html = doc.to_html();
84    let html_path = dir.join(format!("{name}.html"));
85    std::fs::write(&html_path, &html).unwrap();
86    println!("  {name}.html ({} bytes)", html.len());
87
88    let md = doc.to_markdown();
89    let md_path = dir.join(format!("{name}.md"));
90    std::fs::write(&md_path, &md).unwrap();
91    println!("  {name}.md ({} bytes)", md.len());
92}
Source

pub fn render_page_to_png( &self, page_index: usize, dpi: f64, ) -> Result<Option<Vec<u8>>>

Render a single page of the document to PNG bytes.

§Arguments
  • page_index - 0-based page index
  • dpi - Resolution (72 = 1:1, 150 = standard, 300 = high quality)
Source

pub fn render_page_to_png_deterministic( &self, page_index: usize, dpi: f64, ) -> Result<Option<Vec<u8>>>

Render a single page to PNG using bundled fonts without system font discovery.

§Arguments
  • page_index - 0-based page index
  • dpi - Resolution (72 = 1:1, 150 = standard, 300 = high quality)
Examples found in repository?
examples/generate_all_samples.rs (line 67)
61fn export_all(dir: &Path, name: &str, mut doc: Document) {
62    let docx_path = dir.join(format!("{name}.docx"));
63    doc.save(&docx_path).unwrap();
64    println!("  {name}.docx");
65
66    let png = doc
67        .render_page_to_png_deterministic(0, 150.0)
68        .unwrap()
69        .expect("every generated sample should have a first page");
70    let png_path = dir.join(format!("{name}.png"));
71    std::fs::write(&png_path, &png).unwrap();
72    println!("  {name}.png ({} bytes)", png.len());
73
74    match doc.to_pdf_deterministic() {
75        Ok(pdf) => {
76            let pdf_path = dir.join(format!("{name}.pdf"));
77            std::fs::write(&pdf_path, &pdf).unwrap();
78            println!("  {name}.pdf ({} bytes)", pdf.len());
79        }
80        Err(e) => println!("  {name}.pdf — skipped: {e}"),
81    }
82
83    let html = doc.to_html();
84    let html_path = dir.join(format!("{name}.html"));
85    std::fs::write(&html_path, &html).unwrap();
86    println!("  {name}.html ({} bytes)", html.len());
87
88    let md = doc.to_markdown();
89    let md_path = dir.join(format!("{name}.md"));
90    std::fs::write(&md_path, &md).unwrap();
91    println!("  {name}.md ({} bytes)", md.len());
92}
Source

pub fn render_all_pages(&self, dpi: f64) -> Result<Vec<Vec<u8>>>

Render all pages of the document to PNG bytes.

Source

pub fn layout_page(&self, page_index: usize) -> Result<Option<PageFrame>>

Return a cloned positioned page from the cached normal-font layout.

page_index is zero-based. An index beyond the document returns None.

Source

pub fn load_fonts_from_dir<P: AsRef<Path>>(dir: P) -> Vec<FontFile>

Load font files from a directory and return them as FontFile entries.

This is useful for CLI tools that accept a --font-dir argument. Supports .ttf, .otf, and .ttc files.

Source

pub fn headings(&self) -> Vec<(u32, String)>

Get all headings in the document as (level, text) pairs.

Detects heading paragraphs by their style ID (e.g. “Heading1”, “Heading2”).

Examples found in repository?
examples/generate_all_samples.rs (line 625)
97fn generate_feature_showcase(_samples_dir: &Path) -> Document {
98    let mut doc = Document::new();
99
100    // ── Page Setup & Metadata ──
101    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
102    doc.set_margins(
103        Length::inches(1.0),
104        Length::inches(1.0),
105        Length::inches(1.0),
106        Length::inches(1.0),
107    );
108    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
109    doc.set_gutter(Length::twips(0));
110    doc.set_title("rdocx Feature Showcase");
111    doc.set_author("rdocx Sample Generator");
112    doc.set_subject("Comprehensive feature demonstration");
113    doc.set_keywords("rdocx, docx, rust, sample, showcase");
114
115    // Headers & Footers with different first page
116    doc.set_different_first_page(true);
117    doc.set_first_page_header("rdocx");
118    doc.set_first_page_footer("Feature Showcase — Cover Page");
119    doc.set_header("rdocx Feature Showcase");
120    doc.set_footer("Generated by rdocx");
121
122    // ── COVER PAGE ──
123    let bg = create_sample_png(612, 792, [20, 45, 90]);
124    doc.add_background_image(&bg, "cover_bg.png");
125
126    for _ in 0..3 {
127        doc.add_paragraph("");
128    }
129    {
130        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
131        p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
132    }
133    {
134        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
135        p.add_run("Complete Feature Showcase")
136            .size(28.0)
137            .color("FFFFFF")
138            .italic(true);
139    }
140    doc.add_paragraph("");
141    {
142        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
143        p.add_run("Every feature of the rdocx Rust library")
144            .size(13.0)
145            .color("B0C4DE");
146    }
147    {
148        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
149        p.add_run("demonstrated in a single document.")
150            .size(13.0)
151            .color("B0C4DE");
152    }
153
154    // ── TABLE OF CONTENTS ──
155    doc.add_paragraph("").page_break_before(true);
156    doc.insert_toc(doc.content_count(), 3);
157
158    // ── SECTION 1: TEXT FORMATTING ──
159    doc.add_paragraph("").page_break_before(true);
160    doc.add_paragraph("1. Text Formatting").style("Heading1");
161
162    // Paragraph Alignment
163    doc.add_paragraph("1.1 Paragraph Alignment")
164        .style("Heading2");
165    doc.add_paragraph("Left-aligned paragraph (default).")
166        .alignment(Alignment::Left);
167    doc.add_paragraph("Center-aligned paragraph.")
168        .alignment(Alignment::Center);
169    doc.add_paragraph("Right-aligned paragraph.")
170        .alignment(Alignment::Right);
171    doc.add_paragraph(
172        "Justified paragraph — this text is long enough to demonstrate how justified alignment \
173         distributes extra space across word gaps so lines fill the full width of the text area.",
174    )
175    .alignment(Alignment::Justify);
176
177    doc.add_paragraph("");
178
179    // Run Formatting
180    doc.add_paragraph("1.2 Run Formatting").style("Heading2");
181    {
182        let mut p = doc.add_paragraph("");
183        p.add_run("Bold").bold(true);
184        p.add_run(" | ");
185        p.add_run("Italic").italic(true);
186        p.add_run(" | ");
187        p.add_run("Bold Italic").bold(true).italic(true);
188        p.add_run(" | ");
189        p.add_run("Underline").underline(true);
190        p.add_run(" | ");
191        p.add_run("Strikethrough").strike(true);
192        p.add_run(" | ");
193        p.add_run("Double Strike").double_strike(true);
194    }
195    {
196        let mut p = doc.add_paragraph("");
197        p.add_run("Red").color("FF0000");
198        p.add_run(" | ");
199        p.add_run("Blue").color("0000FF");
200        p.add_run(" | ");
201        p.add_run("Green").color("00AA00");
202        p.add_run(" | ");
203        p.add_run("Highlighted").highlight("FFFF00");
204    }
205    {
206        let mut p = doc.add_paragraph("");
207        p.add_run("8pt").size(8.0);
208        p.add_run(" | ");
209        p.add_run("11pt").size(11.0);
210        p.add_run(" | ");
211        p.add_run("16pt").size(16.0);
212        p.add_run(" | ");
213        p.add_run("24pt").size(24.0);
214    }
215    {
216        let mut p = doc.add_paragraph("");
217        p.add_run("Arial").font("Arial");
218        p.add_run(" | ");
219        p.add_run("Times New Roman").font("Times New Roman");
220        p.add_run(" | ");
221        p.add_run("Courier New").font("Courier New");
222    }
223    {
224        let mut p = doc.add_paragraph("");
225        p.add_run("H");
226        p.add_run("2").subscript();
227        p.add_run("O (subscript) | E=mc");
228        p.add_run("2").superscript();
229        p.add_run(" (superscript)");
230    }
231    {
232        let mut p = doc.add_paragraph("");
233        p.add_run("ALL CAPS").all_caps(true);
234        p.add_run(" | ");
235        p.add_run("Small Caps").small_caps(true);
236        p.add_run(" | ");
237        p.add_run("Expanded").character_spacing(Length::twips(40));
238        p.add_run(" | ");
239        p.add_run("Hidden text (not visible)").hidden(true);
240    }
241
242    doc.add_paragraph("");
243
244    // Underline Styles
245    doc.add_paragraph("1.3 Underline Styles").style("Heading2");
246    {
247        let mut p = doc.add_paragraph("");
248        p.add_run("Single ")
249            .underline_style(rdocx::UnderlineStyle::Single);
250        p.add_run("Double ")
251            .underline_style(rdocx::UnderlineStyle::Double);
252        p.add_run("Thick ")
253            .underline_style(rdocx::UnderlineStyle::Thick);
254        p.add_run("Dotted ")
255            .underline_style(rdocx::UnderlineStyle::Dotted);
256        p.add_run("Dash ")
257            .underline_style(rdocx::UnderlineStyle::Dash);
258        p.add_run("Wave ")
259            .underline_style(rdocx::UnderlineStyle::Wave);
260    }
261
262    // ── SECTION 2: PARAGRAPH FORMATTING ──
263    doc.add_paragraph("").page_break_before(true);
264    doc.add_paragraph("2. Paragraph Formatting")
265        .style("Heading1");
266
267    doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
268    doc.add_paragraph("Paragraph with light green shading")
269        .shading("E2EFDA");
270    doc.add_paragraph("Paragraph with bottom border")
271        .border_bottom(BorderStyle::Single, 6, "2E75B6");
272    doc.add_paragraph("Paragraph with all borders (red)")
273        .border_all(BorderStyle::Single, 4, "FF0000");
274
275    doc.add_paragraph("2.2 Indentation").style("Heading2");
276    doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
277        .indent_left(Length::inches(1.0))
278        .hanging_indent(Length::inches(0.5));
279    doc.add_paragraph("0.5-inch first-line indent on this paragraph")
280        .first_line_indent(Length::inches(0.5));
281    doc.add_paragraph("Both left and right indent (0.75 inches each)")
282        .indent_left(Length::inches(0.75))
283        .indent_right(Length::inches(0.75));
284
285    doc.add_paragraph("2.3 Spacing & Line Height")
286        .style("Heading2");
287    doc.add_paragraph("Extra space before (24pt) and after (12pt)")
288        .space_before(Length::pt(24.0))
289        .space_after(Length::pt(12.0));
290    doc.add_paragraph(
291        "Double line spacing paragraph. This text should have extra vertical space between \
292         lines to demonstrate line_spacing_multiple(2.0).",
293    )
294    .line_spacing_multiple(2.0);
295    doc.add_paragraph("Exact 20pt line spacing (fixed height).")
296        .line_spacing(20.0);
297
298    doc.add_paragraph("2.4 Pagination Controls")
299        .style("Heading2");
300    doc.add_paragraph("keep_with_next — this paragraph stays with the next")
301        .keep_with_next(true);
302    doc.add_paragraph("(This paragraph was kept with the one above.)");
303    doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
304        .keep_together(true);
305    doc.add_paragraph("widow_control — prevents widow/orphan lines")
306        .widow_control(true);
307
308    // ── SECTION 3: LISTS ──
309    doc.add_paragraph("").page_break_before(true);
310    doc.add_paragraph("3. Lists").style("Heading1");
311
312    doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
313    doc.add_bullet_list_item("First item", 0);
314    doc.add_bullet_list_item("Second item", 0);
315    doc.add_bullet_list_item("Nested level 1", 1);
316    doc.add_bullet_list_item("Nested level 2", 2);
317    doc.add_bullet_list_item("Back to level 1", 1);
318    doc.add_bullet_list_item("Third item", 0);
319
320    doc.add_paragraph("");
321
322    doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
323    doc.add_numbered_list_item("First numbered item", 0);
324    doc.add_numbered_list_item("Second numbered item", 0);
325    doc.add_numbered_list_item("Sub-item A", 1);
326    doc.add_numbered_list_item("Sub-item B", 1);
327    doc.add_numbered_list_item("Third numbered item", 0);
328
329    // ── SECTION 4: TAB STOPS ──
330    doc.add_paragraph("");
331    doc.add_paragraph("4. Tab Stops").style("Heading1");
332
333    doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
334    doc.add_paragraph("Left\tCenter\tRight\tDecimal")
335        .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
336        .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
337        .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
338        .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
339
340    doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
341    doc.add_paragraph("Item\t\tPrice")
342        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
343        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
344        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
345    doc.add_paragraph("Widget\t\t$19.99")
346        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
347        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
348        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
349    doc.add_paragraph("Gadget\t\t$249.50")
350        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
351        .add_tab_stop_with_leader(
352            TabAlignment::Right,
353            Length::inches(4.0),
354            TabLeader::Underscore,
355        )
356        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
357
358    // ── SECTION 5: TABLES ──
359    doc.add_paragraph("").page_break_before(true);
360    doc.add_paragraph("5. Tables").style("Heading1");
361
362    doc.add_paragraph("5.1 Basic Table").style("Heading2");
363    {
364        let mut tbl = doc
365            .add_table(4, 3)
366            .borders(BorderStyle::Single, 4, "000000");
367        for col in 0..3 {
368            tbl.cell(0, col).unwrap().shading("2E75B6");
369        }
370        tbl.cell(0, 0).unwrap().set_text("Name");
371        tbl.cell(0, 1).unwrap().set_text("Role");
372        tbl.cell(0, 2).unwrap().set_text("Location");
373        tbl.cell(1, 0).unwrap().set_text("Walter White");
374        tbl.cell(1, 1).unwrap().set_text("CEO");
375        tbl.cell(1, 2).unwrap().set_text("Albuquerque");
376        tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
377        tbl.cell(2, 1).unwrap().set_text("CTO");
378        tbl.cell(2, 2).unwrap().set_text("Remote");
379        tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
380        tbl.cell(3, 1).unwrap().set_text("Security");
381        tbl.cell(3, 2).unwrap().set_text("Washington");
382    }
383
384    doc.add_paragraph("");
385
386    doc.add_paragraph("5.2 Column Span & Cell Shading")
387        .style("Heading2");
388    {
389        let mut tbl = doc
390            .add_table(3, 4)
391            .borders(BorderStyle::Single, 4, "000000")
392            .width_pct(100.0);
393        tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
394        tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
395        tbl.cell(1, 0).unwrap().set_text("Region");
396        tbl.cell(1, 0).unwrap().shading("D6E4F0");
397        tbl.cell(1, 1).unwrap().set_text("Q1");
398        tbl.cell(1, 1).unwrap().shading("D6E4F0");
399        tbl.cell(1, 2).unwrap().set_text("Q2");
400        tbl.cell(1, 2).unwrap().shading("D6E4F0");
401        tbl.cell(1, 3).unwrap().set_text("Total");
402        tbl.cell(1, 3).unwrap().shading("D6E4F0");
403        tbl.cell(2, 0).unwrap().set_text("Americas");
404        tbl.cell(2, 1).unwrap().set_text("$2.4M");
405        tbl.cell(2, 2).unwrap().set_text("$2.7M");
406        tbl.cell(2, 3).unwrap().set_text("$5.1M");
407    }
408
409    doc.add_paragraph("");
410
411    doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
412    {
413        let mut tbl = doc
414            .add_table(4, 3)
415            .borders(BorderStyle::Single, 4, "000000");
416        tbl.cell(0, 0).unwrap().set_text("Category");
417        tbl.cell(0, 0).unwrap().shading("E2EFDA");
418        tbl.cell(0, 1).unwrap().set_text("Item");
419        tbl.cell(0, 1).unwrap().shading("E2EFDA");
420        tbl.cell(0, 2).unwrap().set_text("Price");
421        tbl.cell(0, 2).unwrap().shading("E2EFDA");
422        tbl.cell(1, 0).unwrap().set_text("Hardware");
423        tbl.cell(1, 0).unwrap().v_merge_restart();
424        tbl.cell(1, 1).unwrap().set_text("Laptop");
425        tbl.cell(1, 2).unwrap().set_text("$1,200");
426        tbl.cell(2, 0).unwrap().v_merge_continue();
427        tbl.cell(2, 1).unwrap().set_text("Monitor");
428        tbl.cell(2, 2).unwrap().set_text("$450");
429        tbl.cell(3, 0).unwrap().set_text("Software");
430        tbl.cell(3, 1).unwrap().set_text("IDE License");
431        tbl.cell(3, 2).unwrap().set_text("$200/yr");
432    }
433
434    doc.add_paragraph("");
435
436    doc.add_paragraph("5.4 Nested Table").style("Heading2");
437    {
438        let mut tbl = doc
439            .add_table(2, 2)
440            .borders(BorderStyle::Single, 6, "2E75B6");
441        tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
442        tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
443        tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
444        {
445            let mut cell = tbl.cell(1, 1).unwrap();
446            cell.set_text("Nested table below:");
447            let mut nested = cell
448                .add_table(2, 2)
449                .borders(BorderStyle::Single, 2, "FF6600");
450            nested.cell(0, 0).unwrap().set_text("A");
451            nested.cell(0, 1).unwrap().set_text("B");
452            nested.cell(1, 0).unwrap().set_text("C");
453            nested.cell(1, 1).unwrap().set_text("D");
454        }
455    }
456
457    doc.add_paragraph("");
458
459    doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
460        .style("Heading2");
461    {
462        let mut tbl = doc
463            .add_table(2, 3)
464            .borders(BorderStyle::Single, 4, "666666");
465        tbl.row(0).unwrap().height(Length::pt(50.0)).header();
466        tbl.cell(0, 0).unwrap().set_text("Top");
467        tbl.cell(0, 0)
468            .unwrap()
469            .vertical_alignment(VerticalAlignment::Top)
470            .shading("FFF2CC");
471        tbl.cell(0, 1).unwrap().set_text("Center");
472        tbl.cell(0, 1)
473            .unwrap()
474            .vertical_alignment(VerticalAlignment::Center)
475            .shading("D9E2F3");
476        tbl.cell(0, 2).unwrap().set_text("Bottom");
477        tbl.cell(0, 2)
478            .unwrap()
479            .vertical_alignment(VerticalAlignment::Bottom)
480            .shading("E2EFDA");
481        tbl.row(1).unwrap().cant_split();
482        tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
483        tbl.cell(1, 0).unwrap().no_wrap();
484        tbl.cell(1, 1).unwrap().set_text("Fixed width");
485        tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
486        tbl.cell(1, 2).unwrap().set_text("Normal");
487    }
488
489    // ── SECTION 6: IMAGES ──
490    doc.add_paragraph("").page_break_before(true);
491    doc.add_paragraph("6. Images").style("Heading1");
492
493    doc.add_paragraph("6.1 Inline Image").style("Heading2");
494    doc.add_paragraph("A blue gradient image below:");
495    let img = create_sample_png(200, 50, [0, 80, 200]);
496    doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
497
498    doc.add_paragraph("");
499    doc.add_paragraph("6.2 Header Image").style("Heading2");
500    let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
501    doc.set_header_image(
502        &hdr_img,
503        "header_logo.png",
504        Length::inches(2.0),
505        Length::inches(0.2),
506    );
507    doc.add_paragraph("The header now contains an inline image (check the top of this page).");
508
509    doc.add_paragraph("");
510    doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
511
512    // ── SECTION 7: CONTENT MANIPULATION ──
513    doc.add_paragraph("").page_break_before(true);
514    doc.add_paragraph("7. Content Manipulation")
515        .style("Heading1");
516
517    doc.add_paragraph("7.1 Placeholder Replacement")
518        .style("Heading2");
519    doc.add_paragraph("Customer: {{customer}}");
520    doc.add_paragraph("Date: {{date}}");
521    doc.add_paragraph("Reference: {{ref_number}}");
522    {
523        let mut tbl = doc
524            .add_table(2, 2)
525            .borders(BorderStyle::Single, 4, "000000");
526        tbl.cell(0, 0).unwrap().set_text("Project");
527        tbl.cell(0, 0).unwrap().shading("D6E4F0");
528        tbl.cell(0, 1).unwrap().set_text("{{project}}");
529        tbl.cell(1, 0).unwrap().set_text("Status");
530        tbl.cell(1, 0).unwrap().shading("D6E4F0");
531        tbl.cell(1, 1).unwrap().set_text("{{status}}");
532    }
533
534    let mut replacements = HashMap::new();
535    replacements.insert("{{customer}}", "Tensorbee Inc.");
536    replacements.insert("{{date}}", "February 22, 2026");
537    replacements.insert("{{ref_number}}", "TB-2026-001");
538    replacements.insert("{{project}}", "Infrastructure Upgrade");
539    replacements.insert("{{status}}", "In Progress");
540    let n = doc.replace_all(&replacements);
541    doc.add_paragraph(&format!("({n} placeholders replaced above)"));
542
543    doc.add_paragraph("");
544
545    doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
546    doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
547    let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
548    doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
549
550    doc.add_paragraph("");
551
552    doc.add_paragraph("7.3 Content Insertion").style("Heading2");
553    doc.add_paragraph("Section A: First content.");
554    doc.add_paragraph("Section C: Third content.");
555    if let Some(idx) = doc.find_content_index("Section C") {
556        doc.insert_paragraph(
557            idx,
558            "Section B: Inserted between A and C via find_content_index().",
559        );
560    }
561
562    // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
563    doc.add_paragraph("").section_break(SectionBreak::NextPage);
564    doc.add_paragraph("8. Section Breaks & Orientation")
565        .style("Heading1");
566    doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
567
568    {
569        let mut tbl = doc
570            .add_table(3, 7)
571            .borders(BorderStyle::Single, 4, "2E75B6");
572        let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
573        for (c, h) in headers.iter().enumerate() {
574            tbl.cell(0, c).unwrap().set_text(h);
575            tbl.cell(0, c).unwrap().shading("2E75B6");
576        }
577        let data = [
578            [
579                "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
580            ],
581            [
582                "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
583            ],
584        ];
585        for (r, row) in data.iter().enumerate() {
586            for (c, v) in row.iter().enumerate() {
587                tbl.cell(r + 1, c).unwrap().set_text(v);
588            }
589        }
590    }
591
592    doc.add_paragraph("")
593        .section_break(SectionBreak::NextPage)
594        .section_landscape();
595
596    // ── SECTION 9: CUSTOM STYLES ──
597    doc.add_paragraph("9. Custom Styles").style("Heading1");
598    doc.add_style(
599        StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
600            .based_on("Normal")
601            .paragraph_properties(rdocx_oxml::properties::CT_PPr {
602                shading: Some(rdocx_oxml::properties::CT_Shd {
603                    val: "clear".to_string(),
604                    color: None,
605                    fill: Some("FFF2CC".to_string()),
606                }),
607                ..Default::default()
608            })
609            .run_properties(rdocx_oxml::properties::CT_RPr {
610                bold: Some(true),
611                color: Some("C45911".to_string()),
612                ..Default::default()
613            }),
614    );
615    doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
616        .style("CustomHighlight");
617
618    doc.add_paragraph("");
619
620    // ── SECTION 10: DOCUMENT INTELLIGENCE ──
621    doc.add_paragraph("10. Document Intelligence API")
622        .style("Heading1");
623
624    let wc = doc.word_count();
625    let hc = doc.headings().len();
626    let ic = doc.images().len();
627    let lc = doc.links().len();
628    doc.add_paragraph(&format!("Word count: {wc}"));
629    doc.add_paragraph(&format!("Heading count: {hc}"));
630    doc.add_paragraph(&format!("Image count: {ic}"));
631    doc.add_paragraph(&format!("Link count: {lc}"));
632
633    let outline = doc.document_outline();
634    doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
635
636    let issues = doc.audit_accessibility();
637    doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
638    for issue in &issues {
639        doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
640    }
641
642    // ── SECTION 11: DOCUMENT MERGING ──
643    doc.add_paragraph("");
644    doc.add_paragraph("11. Document Merging").style("Heading1");
645    {
646        let mut other = Document::new();
647        other.add_paragraph("This paragraph was merged from another document using append().");
648        doc.append(&other);
649    }
650
651    doc.add_paragraph("");
652
653    // ── SUMMARY ──
654    doc.add_paragraph("Summary of Demonstrated Features")
655        .style("Heading1");
656    let features = [
657        "Page setup: size, margins, header/footer distance, gutter",
658        "Document metadata: title, author, subject, keywords",
659        "Headers and footers: text, images, different first page",
660        "Background images (full-page behind text)",
661        "Table of Contents generation with bookmarks",
662        "Text formatting: bold, italic, underline styles, strike, color, size, font",
663        "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
664        "Paragraph formatting: alignment, borders, shading, spacing, indentation",
665        "Pagination controls: keep-with-next, keep-together, widow control",
666        "Bullet and numbered lists with nesting",
667        "Tab stops with dot/underscore/hyphen leaders",
668        "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
669        "Row properties: header rows, exact height, cant-split",
670        "Cell properties: width, no-wrap, vertical alignment",
671        "Inline images and header images",
672        "Placeholder replacement (single and batch)",
673        "Regex-based find and replace",
674        "Content insertion at specific positions",
675        "Section breaks with mixed portrait/landscape",
676        "Custom paragraph and character styles",
677        "Document intelligence: word count, headings, outline, images, links",
678        "Accessibility audit",
679        "Document merging (append)",
680        "Export: DOCX, PDF, HTML, Markdown",
681    ];
682    for f in &features {
683        doc.add_bullet_list_item(f, 0);
684    }
685    doc.add_paragraph("");
686    doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
687        .alignment(Alignment::Center)
688        .shading("E2EFDA")
689        .border_all(BorderStyle::Single, 2, "00AA00");
690
691    doc
692}
Source

pub fn document_outline(&self) -> Vec<OutlineNode>

Get a hierarchical outline of the document headings.

Returns a tree structure where each node contains the heading level, text, and children (sub-headings).

Examples found in repository?
examples/generate_all_samples.rs (line 633)
97fn generate_feature_showcase(_samples_dir: &Path) -> Document {
98    let mut doc = Document::new();
99
100    // ── Page Setup & Metadata ──
101    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
102    doc.set_margins(
103        Length::inches(1.0),
104        Length::inches(1.0),
105        Length::inches(1.0),
106        Length::inches(1.0),
107    );
108    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
109    doc.set_gutter(Length::twips(0));
110    doc.set_title("rdocx Feature Showcase");
111    doc.set_author("rdocx Sample Generator");
112    doc.set_subject("Comprehensive feature demonstration");
113    doc.set_keywords("rdocx, docx, rust, sample, showcase");
114
115    // Headers & Footers with different first page
116    doc.set_different_first_page(true);
117    doc.set_first_page_header("rdocx");
118    doc.set_first_page_footer("Feature Showcase — Cover Page");
119    doc.set_header("rdocx Feature Showcase");
120    doc.set_footer("Generated by rdocx");
121
122    // ── COVER PAGE ──
123    let bg = create_sample_png(612, 792, [20, 45, 90]);
124    doc.add_background_image(&bg, "cover_bg.png");
125
126    for _ in 0..3 {
127        doc.add_paragraph("");
128    }
129    {
130        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
131        p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
132    }
133    {
134        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
135        p.add_run("Complete Feature Showcase")
136            .size(28.0)
137            .color("FFFFFF")
138            .italic(true);
139    }
140    doc.add_paragraph("");
141    {
142        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
143        p.add_run("Every feature of the rdocx Rust library")
144            .size(13.0)
145            .color("B0C4DE");
146    }
147    {
148        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
149        p.add_run("demonstrated in a single document.")
150            .size(13.0)
151            .color("B0C4DE");
152    }
153
154    // ── TABLE OF CONTENTS ──
155    doc.add_paragraph("").page_break_before(true);
156    doc.insert_toc(doc.content_count(), 3);
157
158    // ── SECTION 1: TEXT FORMATTING ──
159    doc.add_paragraph("").page_break_before(true);
160    doc.add_paragraph("1. Text Formatting").style("Heading1");
161
162    // Paragraph Alignment
163    doc.add_paragraph("1.1 Paragraph Alignment")
164        .style("Heading2");
165    doc.add_paragraph("Left-aligned paragraph (default).")
166        .alignment(Alignment::Left);
167    doc.add_paragraph("Center-aligned paragraph.")
168        .alignment(Alignment::Center);
169    doc.add_paragraph("Right-aligned paragraph.")
170        .alignment(Alignment::Right);
171    doc.add_paragraph(
172        "Justified paragraph — this text is long enough to demonstrate how justified alignment \
173         distributes extra space across word gaps so lines fill the full width of the text area.",
174    )
175    .alignment(Alignment::Justify);
176
177    doc.add_paragraph("");
178
179    // Run Formatting
180    doc.add_paragraph("1.2 Run Formatting").style("Heading2");
181    {
182        let mut p = doc.add_paragraph("");
183        p.add_run("Bold").bold(true);
184        p.add_run(" | ");
185        p.add_run("Italic").italic(true);
186        p.add_run(" | ");
187        p.add_run("Bold Italic").bold(true).italic(true);
188        p.add_run(" | ");
189        p.add_run("Underline").underline(true);
190        p.add_run(" | ");
191        p.add_run("Strikethrough").strike(true);
192        p.add_run(" | ");
193        p.add_run("Double Strike").double_strike(true);
194    }
195    {
196        let mut p = doc.add_paragraph("");
197        p.add_run("Red").color("FF0000");
198        p.add_run(" | ");
199        p.add_run("Blue").color("0000FF");
200        p.add_run(" | ");
201        p.add_run("Green").color("00AA00");
202        p.add_run(" | ");
203        p.add_run("Highlighted").highlight("FFFF00");
204    }
205    {
206        let mut p = doc.add_paragraph("");
207        p.add_run("8pt").size(8.0);
208        p.add_run(" | ");
209        p.add_run("11pt").size(11.0);
210        p.add_run(" | ");
211        p.add_run("16pt").size(16.0);
212        p.add_run(" | ");
213        p.add_run("24pt").size(24.0);
214    }
215    {
216        let mut p = doc.add_paragraph("");
217        p.add_run("Arial").font("Arial");
218        p.add_run(" | ");
219        p.add_run("Times New Roman").font("Times New Roman");
220        p.add_run(" | ");
221        p.add_run("Courier New").font("Courier New");
222    }
223    {
224        let mut p = doc.add_paragraph("");
225        p.add_run("H");
226        p.add_run("2").subscript();
227        p.add_run("O (subscript) | E=mc");
228        p.add_run("2").superscript();
229        p.add_run(" (superscript)");
230    }
231    {
232        let mut p = doc.add_paragraph("");
233        p.add_run("ALL CAPS").all_caps(true);
234        p.add_run(" | ");
235        p.add_run("Small Caps").small_caps(true);
236        p.add_run(" | ");
237        p.add_run("Expanded").character_spacing(Length::twips(40));
238        p.add_run(" | ");
239        p.add_run("Hidden text (not visible)").hidden(true);
240    }
241
242    doc.add_paragraph("");
243
244    // Underline Styles
245    doc.add_paragraph("1.3 Underline Styles").style("Heading2");
246    {
247        let mut p = doc.add_paragraph("");
248        p.add_run("Single ")
249            .underline_style(rdocx::UnderlineStyle::Single);
250        p.add_run("Double ")
251            .underline_style(rdocx::UnderlineStyle::Double);
252        p.add_run("Thick ")
253            .underline_style(rdocx::UnderlineStyle::Thick);
254        p.add_run("Dotted ")
255            .underline_style(rdocx::UnderlineStyle::Dotted);
256        p.add_run("Dash ")
257            .underline_style(rdocx::UnderlineStyle::Dash);
258        p.add_run("Wave ")
259            .underline_style(rdocx::UnderlineStyle::Wave);
260    }
261
262    // ── SECTION 2: PARAGRAPH FORMATTING ──
263    doc.add_paragraph("").page_break_before(true);
264    doc.add_paragraph("2. Paragraph Formatting")
265        .style("Heading1");
266
267    doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
268    doc.add_paragraph("Paragraph with light green shading")
269        .shading("E2EFDA");
270    doc.add_paragraph("Paragraph with bottom border")
271        .border_bottom(BorderStyle::Single, 6, "2E75B6");
272    doc.add_paragraph("Paragraph with all borders (red)")
273        .border_all(BorderStyle::Single, 4, "FF0000");
274
275    doc.add_paragraph("2.2 Indentation").style("Heading2");
276    doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
277        .indent_left(Length::inches(1.0))
278        .hanging_indent(Length::inches(0.5));
279    doc.add_paragraph("0.5-inch first-line indent on this paragraph")
280        .first_line_indent(Length::inches(0.5));
281    doc.add_paragraph("Both left and right indent (0.75 inches each)")
282        .indent_left(Length::inches(0.75))
283        .indent_right(Length::inches(0.75));
284
285    doc.add_paragraph("2.3 Spacing & Line Height")
286        .style("Heading2");
287    doc.add_paragraph("Extra space before (24pt) and after (12pt)")
288        .space_before(Length::pt(24.0))
289        .space_after(Length::pt(12.0));
290    doc.add_paragraph(
291        "Double line spacing paragraph. This text should have extra vertical space between \
292         lines to demonstrate line_spacing_multiple(2.0).",
293    )
294    .line_spacing_multiple(2.0);
295    doc.add_paragraph("Exact 20pt line spacing (fixed height).")
296        .line_spacing(20.0);
297
298    doc.add_paragraph("2.4 Pagination Controls")
299        .style("Heading2");
300    doc.add_paragraph("keep_with_next — this paragraph stays with the next")
301        .keep_with_next(true);
302    doc.add_paragraph("(This paragraph was kept with the one above.)");
303    doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
304        .keep_together(true);
305    doc.add_paragraph("widow_control — prevents widow/orphan lines")
306        .widow_control(true);
307
308    // ── SECTION 3: LISTS ──
309    doc.add_paragraph("").page_break_before(true);
310    doc.add_paragraph("3. Lists").style("Heading1");
311
312    doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
313    doc.add_bullet_list_item("First item", 0);
314    doc.add_bullet_list_item("Second item", 0);
315    doc.add_bullet_list_item("Nested level 1", 1);
316    doc.add_bullet_list_item("Nested level 2", 2);
317    doc.add_bullet_list_item("Back to level 1", 1);
318    doc.add_bullet_list_item("Third item", 0);
319
320    doc.add_paragraph("");
321
322    doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
323    doc.add_numbered_list_item("First numbered item", 0);
324    doc.add_numbered_list_item("Second numbered item", 0);
325    doc.add_numbered_list_item("Sub-item A", 1);
326    doc.add_numbered_list_item("Sub-item B", 1);
327    doc.add_numbered_list_item("Third numbered item", 0);
328
329    // ── SECTION 4: TAB STOPS ──
330    doc.add_paragraph("");
331    doc.add_paragraph("4. Tab Stops").style("Heading1");
332
333    doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
334    doc.add_paragraph("Left\tCenter\tRight\tDecimal")
335        .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
336        .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
337        .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
338        .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
339
340    doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
341    doc.add_paragraph("Item\t\tPrice")
342        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
343        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
344        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
345    doc.add_paragraph("Widget\t\t$19.99")
346        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
347        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
348        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
349    doc.add_paragraph("Gadget\t\t$249.50")
350        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
351        .add_tab_stop_with_leader(
352            TabAlignment::Right,
353            Length::inches(4.0),
354            TabLeader::Underscore,
355        )
356        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
357
358    // ── SECTION 5: TABLES ──
359    doc.add_paragraph("").page_break_before(true);
360    doc.add_paragraph("5. Tables").style("Heading1");
361
362    doc.add_paragraph("5.1 Basic Table").style("Heading2");
363    {
364        let mut tbl = doc
365            .add_table(4, 3)
366            .borders(BorderStyle::Single, 4, "000000");
367        for col in 0..3 {
368            tbl.cell(0, col).unwrap().shading("2E75B6");
369        }
370        tbl.cell(0, 0).unwrap().set_text("Name");
371        tbl.cell(0, 1).unwrap().set_text("Role");
372        tbl.cell(0, 2).unwrap().set_text("Location");
373        tbl.cell(1, 0).unwrap().set_text("Walter White");
374        tbl.cell(1, 1).unwrap().set_text("CEO");
375        tbl.cell(1, 2).unwrap().set_text("Albuquerque");
376        tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
377        tbl.cell(2, 1).unwrap().set_text("CTO");
378        tbl.cell(2, 2).unwrap().set_text("Remote");
379        tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
380        tbl.cell(3, 1).unwrap().set_text("Security");
381        tbl.cell(3, 2).unwrap().set_text("Washington");
382    }
383
384    doc.add_paragraph("");
385
386    doc.add_paragraph("5.2 Column Span & Cell Shading")
387        .style("Heading2");
388    {
389        let mut tbl = doc
390            .add_table(3, 4)
391            .borders(BorderStyle::Single, 4, "000000")
392            .width_pct(100.0);
393        tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
394        tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
395        tbl.cell(1, 0).unwrap().set_text("Region");
396        tbl.cell(1, 0).unwrap().shading("D6E4F0");
397        tbl.cell(1, 1).unwrap().set_text("Q1");
398        tbl.cell(1, 1).unwrap().shading("D6E4F0");
399        tbl.cell(1, 2).unwrap().set_text("Q2");
400        tbl.cell(1, 2).unwrap().shading("D6E4F0");
401        tbl.cell(1, 3).unwrap().set_text("Total");
402        tbl.cell(1, 3).unwrap().shading("D6E4F0");
403        tbl.cell(2, 0).unwrap().set_text("Americas");
404        tbl.cell(2, 1).unwrap().set_text("$2.4M");
405        tbl.cell(2, 2).unwrap().set_text("$2.7M");
406        tbl.cell(2, 3).unwrap().set_text("$5.1M");
407    }
408
409    doc.add_paragraph("");
410
411    doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
412    {
413        let mut tbl = doc
414            .add_table(4, 3)
415            .borders(BorderStyle::Single, 4, "000000");
416        tbl.cell(0, 0).unwrap().set_text("Category");
417        tbl.cell(0, 0).unwrap().shading("E2EFDA");
418        tbl.cell(0, 1).unwrap().set_text("Item");
419        tbl.cell(0, 1).unwrap().shading("E2EFDA");
420        tbl.cell(0, 2).unwrap().set_text("Price");
421        tbl.cell(0, 2).unwrap().shading("E2EFDA");
422        tbl.cell(1, 0).unwrap().set_text("Hardware");
423        tbl.cell(1, 0).unwrap().v_merge_restart();
424        tbl.cell(1, 1).unwrap().set_text("Laptop");
425        tbl.cell(1, 2).unwrap().set_text("$1,200");
426        tbl.cell(2, 0).unwrap().v_merge_continue();
427        tbl.cell(2, 1).unwrap().set_text("Monitor");
428        tbl.cell(2, 2).unwrap().set_text("$450");
429        tbl.cell(3, 0).unwrap().set_text("Software");
430        tbl.cell(3, 1).unwrap().set_text("IDE License");
431        tbl.cell(3, 2).unwrap().set_text("$200/yr");
432    }
433
434    doc.add_paragraph("");
435
436    doc.add_paragraph("5.4 Nested Table").style("Heading2");
437    {
438        let mut tbl = doc
439            .add_table(2, 2)
440            .borders(BorderStyle::Single, 6, "2E75B6");
441        tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
442        tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
443        tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
444        {
445            let mut cell = tbl.cell(1, 1).unwrap();
446            cell.set_text("Nested table below:");
447            let mut nested = cell
448                .add_table(2, 2)
449                .borders(BorderStyle::Single, 2, "FF6600");
450            nested.cell(0, 0).unwrap().set_text("A");
451            nested.cell(0, 1).unwrap().set_text("B");
452            nested.cell(1, 0).unwrap().set_text("C");
453            nested.cell(1, 1).unwrap().set_text("D");
454        }
455    }
456
457    doc.add_paragraph("");
458
459    doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
460        .style("Heading2");
461    {
462        let mut tbl = doc
463            .add_table(2, 3)
464            .borders(BorderStyle::Single, 4, "666666");
465        tbl.row(0).unwrap().height(Length::pt(50.0)).header();
466        tbl.cell(0, 0).unwrap().set_text("Top");
467        tbl.cell(0, 0)
468            .unwrap()
469            .vertical_alignment(VerticalAlignment::Top)
470            .shading("FFF2CC");
471        tbl.cell(0, 1).unwrap().set_text("Center");
472        tbl.cell(0, 1)
473            .unwrap()
474            .vertical_alignment(VerticalAlignment::Center)
475            .shading("D9E2F3");
476        tbl.cell(0, 2).unwrap().set_text("Bottom");
477        tbl.cell(0, 2)
478            .unwrap()
479            .vertical_alignment(VerticalAlignment::Bottom)
480            .shading("E2EFDA");
481        tbl.row(1).unwrap().cant_split();
482        tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
483        tbl.cell(1, 0).unwrap().no_wrap();
484        tbl.cell(1, 1).unwrap().set_text("Fixed width");
485        tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
486        tbl.cell(1, 2).unwrap().set_text("Normal");
487    }
488
489    // ── SECTION 6: IMAGES ──
490    doc.add_paragraph("").page_break_before(true);
491    doc.add_paragraph("6. Images").style("Heading1");
492
493    doc.add_paragraph("6.1 Inline Image").style("Heading2");
494    doc.add_paragraph("A blue gradient image below:");
495    let img = create_sample_png(200, 50, [0, 80, 200]);
496    doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
497
498    doc.add_paragraph("");
499    doc.add_paragraph("6.2 Header Image").style("Heading2");
500    let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
501    doc.set_header_image(
502        &hdr_img,
503        "header_logo.png",
504        Length::inches(2.0),
505        Length::inches(0.2),
506    );
507    doc.add_paragraph("The header now contains an inline image (check the top of this page).");
508
509    doc.add_paragraph("");
510    doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
511
512    // ── SECTION 7: CONTENT MANIPULATION ──
513    doc.add_paragraph("").page_break_before(true);
514    doc.add_paragraph("7. Content Manipulation")
515        .style("Heading1");
516
517    doc.add_paragraph("7.1 Placeholder Replacement")
518        .style("Heading2");
519    doc.add_paragraph("Customer: {{customer}}");
520    doc.add_paragraph("Date: {{date}}");
521    doc.add_paragraph("Reference: {{ref_number}}");
522    {
523        let mut tbl = doc
524            .add_table(2, 2)
525            .borders(BorderStyle::Single, 4, "000000");
526        tbl.cell(0, 0).unwrap().set_text("Project");
527        tbl.cell(0, 0).unwrap().shading("D6E4F0");
528        tbl.cell(0, 1).unwrap().set_text("{{project}}");
529        tbl.cell(1, 0).unwrap().set_text("Status");
530        tbl.cell(1, 0).unwrap().shading("D6E4F0");
531        tbl.cell(1, 1).unwrap().set_text("{{status}}");
532    }
533
534    let mut replacements = HashMap::new();
535    replacements.insert("{{customer}}", "Tensorbee Inc.");
536    replacements.insert("{{date}}", "February 22, 2026");
537    replacements.insert("{{ref_number}}", "TB-2026-001");
538    replacements.insert("{{project}}", "Infrastructure Upgrade");
539    replacements.insert("{{status}}", "In Progress");
540    let n = doc.replace_all(&replacements);
541    doc.add_paragraph(&format!("({n} placeholders replaced above)"));
542
543    doc.add_paragraph("");
544
545    doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
546    doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
547    let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
548    doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
549
550    doc.add_paragraph("");
551
552    doc.add_paragraph("7.3 Content Insertion").style("Heading2");
553    doc.add_paragraph("Section A: First content.");
554    doc.add_paragraph("Section C: Third content.");
555    if let Some(idx) = doc.find_content_index("Section C") {
556        doc.insert_paragraph(
557            idx,
558            "Section B: Inserted between A and C via find_content_index().",
559        );
560    }
561
562    // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
563    doc.add_paragraph("").section_break(SectionBreak::NextPage);
564    doc.add_paragraph("8. Section Breaks & Orientation")
565        .style("Heading1");
566    doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
567
568    {
569        let mut tbl = doc
570            .add_table(3, 7)
571            .borders(BorderStyle::Single, 4, "2E75B6");
572        let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
573        for (c, h) in headers.iter().enumerate() {
574            tbl.cell(0, c).unwrap().set_text(h);
575            tbl.cell(0, c).unwrap().shading("2E75B6");
576        }
577        let data = [
578            [
579                "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
580            ],
581            [
582                "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
583            ],
584        ];
585        for (r, row) in data.iter().enumerate() {
586            for (c, v) in row.iter().enumerate() {
587                tbl.cell(r + 1, c).unwrap().set_text(v);
588            }
589        }
590    }
591
592    doc.add_paragraph("")
593        .section_break(SectionBreak::NextPage)
594        .section_landscape();
595
596    // ── SECTION 9: CUSTOM STYLES ──
597    doc.add_paragraph("9. Custom Styles").style("Heading1");
598    doc.add_style(
599        StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
600            .based_on("Normal")
601            .paragraph_properties(rdocx_oxml::properties::CT_PPr {
602                shading: Some(rdocx_oxml::properties::CT_Shd {
603                    val: "clear".to_string(),
604                    color: None,
605                    fill: Some("FFF2CC".to_string()),
606                }),
607                ..Default::default()
608            })
609            .run_properties(rdocx_oxml::properties::CT_RPr {
610                bold: Some(true),
611                color: Some("C45911".to_string()),
612                ..Default::default()
613            }),
614    );
615    doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
616        .style("CustomHighlight");
617
618    doc.add_paragraph("");
619
620    // ── SECTION 10: DOCUMENT INTELLIGENCE ──
621    doc.add_paragraph("10. Document Intelligence API")
622        .style("Heading1");
623
624    let wc = doc.word_count();
625    let hc = doc.headings().len();
626    let ic = doc.images().len();
627    let lc = doc.links().len();
628    doc.add_paragraph(&format!("Word count: {wc}"));
629    doc.add_paragraph(&format!("Heading count: {hc}"));
630    doc.add_paragraph(&format!("Image count: {ic}"));
631    doc.add_paragraph(&format!("Link count: {lc}"));
632
633    let outline = doc.document_outline();
634    doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
635
636    let issues = doc.audit_accessibility();
637    doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
638    for issue in &issues {
639        doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
640    }
641
642    // ── SECTION 11: DOCUMENT MERGING ──
643    doc.add_paragraph("");
644    doc.add_paragraph("11. Document Merging").style("Heading1");
645    {
646        let mut other = Document::new();
647        other.add_paragraph("This paragraph was merged from another document using append().");
648        doc.append(&other);
649    }
650
651    doc.add_paragraph("");
652
653    // ── SUMMARY ──
654    doc.add_paragraph("Summary of Demonstrated Features")
655        .style("Heading1");
656    let features = [
657        "Page setup: size, margins, header/footer distance, gutter",
658        "Document metadata: title, author, subject, keywords",
659        "Headers and footers: text, images, different first page",
660        "Background images (full-page behind text)",
661        "Table of Contents generation with bookmarks",
662        "Text formatting: bold, italic, underline styles, strike, color, size, font",
663        "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
664        "Paragraph formatting: alignment, borders, shading, spacing, indentation",
665        "Pagination controls: keep-with-next, keep-together, widow control",
666        "Bullet and numbered lists with nesting",
667        "Tab stops with dot/underscore/hyphen leaders",
668        "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
669        "Row properties: header rows, exact height, cant-split",
670        "Cell properties: width, no-wrap, vertical alignment",
671        "Inline images and header images",
672        "Placeholder replacement (single and batch)",
673        "Regex-based find and replace",
674        "Content insertion at specific positions",
675        "Section breaks with mixed portrait/landscape",
676        "Custom paragraph and character styles",
677        "Document intelligence: word count, headings, outline, images, links",
678        "Accessibility audit",
679        "Document merging (append)",
680        "Export: DOCX, PDF, HTML, Markdown",
681    ];
682    for f in &features {
683        doc.add_bullet_list_item(f, 0);
684    }
685    doc.add_paragraph("");
686    doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
687        .alignment(Alignment::Center)
688        .shading("E2EFDA")
689        .border_all(BorderStyle::Single, 2, "00AA00");
690
691    doc
692}
Source

pub fn images(&self) -> Vec<ImageInfo>

Get information about all images in the document.

Returns metadata for each inline and anchored image found in body paragraphs.

Examples found in repository?
examples/generate_all_samples.rs (line 626)
97fn generate_feature_showcase(_samples_dir: &Path) -> Document {
98    let mut doc = Document::new();
99
100    // ── Page Setup & Metadata ──
101    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
102    doc.set_margins(
103        Length::inches(1.0),
104        Length::inches(1.0),
105        Length::inches(1.0),
106        Length::inches(1.0),
107    );
108    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
109    doc.set_gutter(Length::twips(0));
110    doc.set_title("rdocx Feature Showcase");
111    doc.set_author("rdocx Sample Generator");
112    doc.set_subject("Comprehensive feature demonstration");
113    doc.set_keywords("rdocx, docx, rust, sample, showcase");
114
115    // Headers & Footers with different first page
116    doc.set_different_first_page(true);
117    doc.set_first_page_header("rdocx");
118    doc.set_first_page_footer("Feature Showcase — Cover Page");
119    doc.set_header("rdocx Feature Showcase");
120    doc.set_footer("Generated by rdocx");
121
122    // ── COVER PAGE ──
123    let bg = create_sample_png(612, 792, [20, 45, 90]);
124    doc.add_background_image(&bg, "cover_bg.png");
125
126    for _ in 0..3 {
127        doc.add_paragraph("");
128    }
129    {
130        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
131        p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
132    }
133    {
134        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
135        p.add_run("Complete Feature Showcase")
136            .size(28.0)
137            .color("FFFFFF")
138            .italic(true);
139    }
140    doc.add_paragraph("");
141    {
142        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
143        p.add_run("Every feature of the rdocx Rust library")
144            .size(13.0)
145            .color("B0C4DE");
146    }
147    {
148        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
149        p.add_run("demonstrated in a single document.")
150            .size(13.0)
151            .color("B0C4DE");
152    }
153
154    // ── TABLE OF CONTENTS ──
155    doc.add_paragraph("").page_break_before(true);
156    doc.insert_toc(doc.content_count(), 3);
157
158    // ── SECTION 1: TEXT FORMATTING ──
159    doc.add_paragraph("").page_break_before(true);
160    doc.add_paragraph("1. Text Formatting").style("Heading1");
161
162    // Paragraph Alignment
163    doc.add_paragraph("1.1 Paragraph Alignment")
164        .style("Heading2");
165    doc.add_paragraph("Left-aligned paragraph (default).")
166        .alignment(Alignment::Left);
167    doc.add_paragraph("Center-aligned paragraph.")
168        .alignment(Alignment::Center);
169    doc.add_paragraph("Right-aligned paragraph.")
170        .alignment(Alignment::Right);
171    doc.add_paragraph(
172        "Justified paragraph — this text is long enough to demonstrate how justified alignment \
173         distributes extra space across word gaps so lines fill the full width of the text area.",
174    )
175    .alignment(Alignment::Justify);
176
177    doc.add_paragraph("");
178
179    // Run Formatting
180    doc.add_paragraph("1.2 Run Formatting").style("Heading2");
181    {
182        let mut p = doc.add_paragraph("");
183        p.add_run("Bold").bold(true);
184        p.add_run(" | ");
185        p.add_run("Italic").italic(true);
186        p.add_run(" | ");
187        p.add_run("Bold Italic").bold(true).italic(true);
188        p.add_run(" | ");
189        p.add_run("Underline").underline(true);
190        p.add_run(" | ");
191        p.add_run("Strikethrough").strike(true);
192        p.add_run(" | ");
193        p.add_run("Double Strike").double_strike(true);
194    }
195    {
196        let mut p = doc.add_paragraph("");
197        p.add_run("Red").color("FF0000");
198        p.add_run(" | ");
199        p.add_run("Blue").color("0000FF");
200        p.add_run(" | ");
201        p.add_run("Green").color("00AA00");
202        p.add_run(" | ");
203        p.add_run("Highlighted").highlight("FFFF00");
204    }
205    {
206        let mut p = doc.add_paragraph("");
207        p.add_run("8pt").size(8.0);
208        p.add_run(" | ");
209        p.add_run("11pt").size(11.0);
210        p.add_run(" | ");
211        p.add_run("16pt").size(16.0);
212        p.add_run(" | ");
213        p.add_run("24pt").size(24.0);
214    }
215    {
216        let mut p = doc.add_paragraph("");
217        p.add_run("Arial").font("Arial");
218        p.add_run(" | ");
219        p.add_run("Times New Roman").font("Times New Roman");
220        p.add_run(" | ");
221        p.add_run("Courier New").font("Courier New");
222    }
223    {
224        let mut p = doc.add_paragraph("");
225        p.add_run("H");
226        p.add_run("2").subscript();
227        p.add_run("O (subscript) | E=mc");
228        p.add_run("2").superscript();
229        p.add_run(" (superscript)");
230    }
231    {
232        let mut p = doc.add_paragraph("");
233        p.add_run("ALL CAPS").all_caps(true);
234        p.add_run(" | ");
235        p.add_run("Small Caps").small_caps(true);
236        p.add_run(" | ");
237        p.add_run("Expanded").character_spacing(Length::twips(40));
238        p.add_run(" | ");
239        p.add_run("Hidden text (not visible)").hidden(true);
240    }
241
242    doc.add_paragraph("");
243
244    // Underline Styles
245    doc.add_paragraph("1.3 Underline Styles").style("Heading2");
246    {
247        let mut p = doc.add_paragraph("");
248        p.add_run("Single ")
249            .underline_style(rdocx::UnderlineStyle::Single);
250        p.add_run("Double ")
251            .underline_style(rdocx::UnderlineStyle::Double);
252        p.add_run("Thick ")
253            .underline_style(rdocx::UnderlineStyle::Thick);
254        p.add_run("Dotted ")
255            .underline_style(rdocx::UnderlineStyle::Dotted);
256        p.add_run("Dash ")
257            .underline_style(rdocx::UnderlineStyle::Dash);
258        p.add_run("Wave ")
259            .underline_style(rdocx::UnderlineStyle::Wave);
260    }
261
262    // ── SECTION 2: PARAGRAPH FORMATTING ──
263    doc.add_paragraph("").page_break_before(true);
264    doc.add_paragraph("2. Paragraph Formatting")
265        .style("Heading1");
266
267    doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
268    doc.add_paragraph("Paragraph with light green shading")
269        .shading("E2EFDA");
270    doc.add_paragraph("Paragraph with bottom border")
271        .border_bottom(BorderStyle::Single, 6, "2E75B6");
272    doc.add_paragraph("Paragraph with all borders (red)")
273        .border_all(BorderStyle::Single, 4, "FF0000");
274
275    doc.add_paragraph("2.2 Indentation").style("Heading2");
276    doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
277        .indent_left(Length::inches(1.0))
278        .hanging_indent(Length::inches(0.5));
279    doc.add_paragraph("0.5-inch first-line indent on this paragraph")
280        .first_line_indent(Length::inches(0.5));
281    doc.add_paragraph("Both left and right indent (0.75 inches each)")
282        .indent_left(Length::inches(0.75))
283        .indent_right(Length::inches(0.75));
284
285    doc.add_paragraph("2.3 Spacing & Line Height")
286        .style("Heading2");
287    doc.add_paragraph("Extra space before (24pt) and after (12pt)")
288        .space_before(Length::pt(24.0))
289        .space_after(Length::pt(12.0));
290    doc.add_paragraph(
291        "Double line spacing paragraph. This text should have extra vertical space between \
292         lines to demonstrate line_spacing_multiple(2.0).",
293    )
294    .line_spacing_multiple(2.0);
295    doc.add_paragraph("Exact 20pt line spacing (fixed height).")
296        .line_spacing(20.0);
297
298    doc.add_paragraph("2.4 Pagination Controls")
299        .style("Heading2");
300    doc.add_paragraph("keep_with_next — this paragraph stays with the next")
301        .keep_with_next(true);
302    doc.add_paragraph("(This paragraph was kept with the one above.)");
303    doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
304        .keep_together(true);
305    doc.add_paragraph("widow_control — prevents widow/orphan lines")
306        .widow_control(true);
307
308    // ── SECTION 3: LISTS ──
309    doc.add_paragraph("").page_break_before(true);
310    doc.add_paragraph("3. Lists").style("Heading1");
311
312    doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
313    doc.add_bullet_list_item("First item", 0);
314    doc.add_bullet_list_item("Second item", 0);
315    doc.add_bullet_list_item("Nested level 1", 1);
316    doc.add_bullet_list_item("Nested level 2", 2);
317    doc.add_bullet_list_item("Back to level 1", 1);
318    doc.add_bullet_list_item("Third item", 0);
319
320    doc.add_paragraph("");
321
322    doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
323    doc.add_numbered_list_item("First numbered item", 0);
324    doc.add_numbered_list_item("Second numbered item", 0);
325    doc.add_numbered_list_item("Sub-item A", 1);
326    doc.add_numbered_list_item("Sub-item B", 1);
327    doc.add_numbered_list_item("Third numbered item", 0);
328
329    // ── SECTION 4: TAB STOPS ──
330    doc.add_paragraph("");
331    doc.add_paragraph("4. Tab Stops").style("Heading1");
332
333    doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
334    doc.add_paragraph("Left\tCenter\tRight\tDecimal")
335        .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
336        .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
337        .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
338        .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
339
340    doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
341    doc.add_paragraph("Item\t\tPrice")
342        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
343        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
344        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
345    doc.add_paragraph("Widget\t\t$19.99")
346        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
347        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
348        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
349    doc.add_paragraph("Gadget\t\t$249.50")
350        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
351        .add_tab_stop_with_leader(
352            TabAlignment::Right,
353            Length::inches(4.0),
354            TabLeader::Underscore,
355        )
356        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
357
358    // ── SECTION 5: TABLES ──
359    doc.add_paragraph("").page_break_before(true);
360    doc.add_paragraph("5. Tables").style("Heading1");
361
362    doc.add_paragraph("5.1 Basic Table").style("Heading2");
363    {
364        let mut tbl = doc
365            .add_table(4, 3)
366            .borders(BorderStyle::Single, 4, "000000");
367        for col in 0..3 {
368            tbl.cell(0, col).unwrap().shading("2E75B6");
369        }
370        tbl.cell(0, 0).unwrap().set_text("Name");
371        tbl.cell(0, 1).unwrap().set_text("Role");
372        tbl.cell(0, 2).unwrap().set_text("Location");
373        tbl.cell(1, 0).unwrap().set_text("Walter White");
374        tbl.cell(1, 1).unwrap().set_text("CEO");
375        tbl.cell(1, 2).unwrap().set_text("Albuquerque");
376        tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
377        tbl.cell(2, 1).unwrap().set_text("CTO");
378        tbl.cell(2, 2).unwrap().set_text("Remote");
379        tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
380        tbl.cell(3, 1).unwrap().set_text("Security");
381        tbl.cell(3, 2).unwrap().set_text("Washington");
382    }
383
384    doc.add_paragraph("");
385
386    doc.add_paragraph("5.2 Column Span & Cell Shading")
387        .style("Heading2");
388    {
389        let mut tbl = doc
390            .add_table(3, 4)
391            .borders(BorderStyle::Single, 4, "000000")
392            .width_pct(100.0);
393        tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
394        tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
395        tbl.cell(1, 0).unwrap().set_text("Region");
396        tbl.cell(1, 0).unwrap().shading("D6E4F0");
397        tbl.cell(1, 1).unwrap().set_text("Q1");
398        tbl.cell(1, 1).unwrap().shading("D6E4F0");
399        tbl.cell(1, 2).unwrap().set_text("Q2");
400        tbl.cell(1, 2).unwrap().shading("D6E4F0");
401        tbl.cell(1, 3).unwrap().set_text("Total");
402        tbl.cell(1, 3).unwrap().shading("D6E4F0");
403        tbl.cell(2, 0).unwrap().set_text("Americas");
404        tbl.cell(2, 1).unwrap().set_text("$2.4M");
405        tbl.cell(2, 2).unwrap().set_text("$2.7M");
406        tbl.cell(2, 3).unwrap().set_text("$5.1M");
407    }
408
409    doc.add_paragraph("");
410
411    doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
412    {
413        let mut tbl = doc
414            .add_table(4, 3)
415            .borders(BorderStyle::Single, 4, "000000");
416        tbl.cell(0, 0).unwrap().set_text("Category");
417        tbl.cell(0, 0).unwrap().shading("E2EFDA");
418        tbl.cell(0, 1).unwrap().set_text("Item");
419        tbl.cell(0, 1).unwrap().shading("E2EFDA");
420        tbl.cell(0, 2).unwrap().set_text("Price");
421        tbl.cell(0, 2).unwrap().shading("E2EFDA");
422        tbl.cell(1, 0).unwrap().set_text("Hardware");
423        tbl.cell(1, 0).unwrap().v_merge_restart();
424        tbl.cell(1, 1).unwrap().set_text("Laptop");
425        tbl.cell(1, 2).unwrap().set_text("$1,200");
426        tbl.cell(2, 0).unwrap().v_merge_continue();
427        tbl.cell(2, 1).unwrap().set_text("Monitor");
428        tbl.cell(2, 2).unwrap().set_text("$450");
429        tbl.cell(3, 0).unwrap().set_text("Software");
430        tbl.cell(3, 1).unwrap().set_text("IDE License");
431        tbl.cell(3, 2).unwrap().set_text("$200/yr");
432    }
433
434    doc.add_paragraph("");
435
436    doc.add_paragraph("5.4 Nested Table").style("Heading2");
437    {
438        let mut tbl = doc
439            .add_table(2, 2)
440            .borders(BorderStyle::Single, 6, "2E75B6");
441        tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
442        tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
443        tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
444        {
445            let mut cell = tbl.cell(1, 1).unwrap();
446            cell.set_text("Nested table below:");
447            let mut nested = cell
448                .add_table(2, 2)
449                .borders(BorderStyle::Single, 2, "FF6600");
450            nested.cell(0, 0).unwrap().set_text("A");
451            nested.cell(0, 1).unwrap().set_text("B");
452            nested.cell(1, 0).unwrap().set_text("C");
453            nested.cell(1, 1).unwrap().set_text("D");
454        }
455    }
456
457    doc.add_paragraph("");
458
459    doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
460        .style("Heading2");
461    {
462        let mut tbl = doc
463            .add_table(2, 3)
464            .borders(BorderStyle::Single, 4, "666666");
465        tbl.row(0).unwrap().height(Length::pt(50.0)).header();
466        tbl.cell(0, 0).unwrap().set_text("Top");
467        tbl.cell(0, 0)
468            .unwrap()
469            .vertical_alignment(VerticalAlignment::Top)
470            .shading("FFF2CC");
471        tbl.cell(0, 1).unwrap().set_text("Center");
472        tbl.cell(0, 1)
473            .unwrap()
474            .vertical_alignment(VerticalAlignment::Center)
475            .shading("D9E2F3");
476        tbl.cell(0, 2).unwrap().set_text("Bottom");
477        tbl.cell(0, 2)
478            .unwrap()
479            .vertical_alignment(VerticalAlignment::Bottom)
480            .shading("E2EFDA");
481        tbl.row(1).unwrap().cant_split();
482        tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
483        tbl.cell(1, 0).unwrap().no_wrap();
484        tbl.cell(1, 1).unwrap().set_text("Fixed width");
485        tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
486        tbl.cell(1, 2).unwrap().set_text("Normal");
487    }
488
489    // ── SECTION 6: IMAGES ──
490    doc.add_paragraph("").page_break_before(true);
491    doc.add_paragraph("6. Images").style("Heading1");
492
493    doc.add_paragraph("6.1 Inline Image").style("Heading2");
494    doc.add_paragraph("A blue gradient image below:");
495    let img = create_sample_png(200, 50, [0, 80, 200]);
496    doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
497
498    doc.add_paragraph("");
499    doc.add_paragraph("6.2 Header Image").style("Heading2");
500    let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
501    doc.set_header_image(
502        &hdr_img,
503        "header_logo.png",
504        Length::inches(2.0),
505        Length::inches(0.2),
506    );
507    doc.add_paragraph("The header now contains an inline image (check the top of this page).");
508
509    doc.add_paragraph("");
510    doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
511
512    // ── SECTION 7: CONTENT MANIPULATION ──
513    doc.add_paragraph("").page_break_before(true);
514    doc.add_paragraph("7. Content Manipulation")
515        .style("Heading1");
516
517    doc.add_paragraph("7.1 Placeholder Replacement")
518        .style("Heading2");
519    doc.add_paragraph("Customer: {{customer}}");
520    doc.add_paragraph("Date: {{date}}");
521    doc.add_paragraph("Reference: {{ref_number}}");
522    {
523        let mut tbl = doc
524            .add_table(2, 2)
525            .borders(BorderStyle::Single, 4, "000000");
526        tbl.cell(0, 0).unwrap().set_text("Project");
527        tbl.cell(0, 0).unwrap().shading("D6E4F0");
528        tbl.cell(0, 1).unwrap().set_text("{{project}}");
529        tbl.cell(1, 0).unwrap().set_text("Status");
530        tbl.cell(1, 0).unwrap().shading("D6E4F0");
531        tbl.cell(1, 1).unwrap().set_text("{{status}}");
532    }
533
534    let mut replacements = HashMap::new();
535    replacements.insert("{{customer}}", "Tensorbee Inc.");
536    replacements.insert("{{date}}", "February 22, 2026");
537    replacements.insert("{{ref_number}}", "TB-2026-001");
538    replacements.insert("{{project}}", "Infrastructure Upgrade");
539    replacements.insert("{{status}}", "In Progress");
540    let n = doc.replace_all(&replacements);
541    doc.add_paragraph(&format!("({n} placeholders replaced above)"));
542
543    doc.add_paragraph("");
544
545    doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
546    doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
547    let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
548    doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
549
550    doc.add_paragraph("");
551
552    doc.add_paragraph("7.3 Content Insertion").style("Heading2");
553    doc.add_paragraph("Section A: First content.");
554    doc.add_paragraph("Section C: Third content.");
555    if let Some(idx) = doc.find_content_index("Section C") {
556        doc.insert_paragraph(
557            idx,
558            "Section B: Inserted between A and C via find_content_index().",
559        );
560    }
561
562    // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
563    doc.add_paragraph("").section_break(SectionBreak::NextPage);
564    doc.add_paragraph("8. Section Breaks & Orientation")
565        .style("Heading1");
566    doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
567
568    {
569        let mut tbl = doc
570            .add_table(3, 7)
571            .borders(BorderStyle::Single, 4, "2E75B6");
572        let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
573        for (c, h) in headers.iter().enumerate() {
574            tbl.cell(0, c).unwrap().set_text(h);
575            tbl.cell(0, c).unwrap().shading("2E75B6");
576        }
577        let data = [
578            [
579                "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
580            ],
581            [
582                "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
583            ],
584        ];
585        for (r, row) in data.iter().enumerate() {
586            for (c, v) in row.iter().enumerate() {
587                tbl.cell(r + 1, c).unwrap().set_text(v);
588            }
589        }
590    }
591
592    doc.add_paragraph("")
593        .section_break(SectionBreak::NextPage)
594        .section_landscape();
595
596    // ── SECTION 9: CUSTOM STYLES ──
597    doc.add_paragraph("9. Custom Styles").style("Heading1");
598    doc.add_style(
599        StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
600            .based_on("Normal")
601            .paragraph_properties(rdocx_oxml::properties::CT_PPr {
602                shading: Some(rdocx_oxml::properties::CT_Shd {
603                    val: "clear".to_string(),
604                    color: None,
605                    fill: Some("FFF2CC".to_string()),
606                }),
607                ..Default::default()
608            })
609            .run_properties(rdocx_oxml::properties::CT_RPr {
610                bold: Some(true),
611                color: Some("C45911".to_string()),
612                ..Default::default()
613            }),
614    );
615    doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
616        .style("CustomHighlight");
617
618    doc.add_paragraph("");
619
620    // ── SECTION 10: DOCUMENT INTELLIGENCE ──
621    doc.add_paragraph("10. Document Intelligence API")
622        .style("Heading1");
623
624    let wc = doc.word_count();
625    let hc = doc.headings().len();
626    let ic = doc.images().len();
627    let lc = doc.links().len();
628    doc.add_paragraph(&format!("Word count: {wc}"));
629    doc.add_paragraph(&format!("Heading count: {hc}"));
630    doc.add_paragraph(&format!("Image count: {ic}"));
631    doc.add_paragraph(&format!("Link count: {lc}"));
632
633    let outline = doc.document_outline();
634    doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
635
636    let issues = doc.audit_accessibility();
637    doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
638    for issue in &issues {
639        doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
640    }
641
642    // ── SECTION 11: DOCUMENT MERGING ──
643    doc.add_paragraph("");
644    doc.add_paragraph("11. Document Merging").style("Heading1");
645    {
646        let mut other = Document::new();
647        other.add_paragraph("This paragraph was merged from another document using append().");
648        doc.append(&other);
649    }
650
651    doc.add_paragraph("");
652
653    // ── SUMMARY ──
654    doc.add_paragraph("Summary of Demonstrated Features")
655        .style("Heading1");
656    let features = [
657        "Page setup: size, margins, header/footer distance, gutter",
658        "Document metadata: title, author, subject, keywords",
659        "Headers and footers: text, images, different first page",
660        "Background images (full-page behind text)",
661        "Table of Contents generation with bookmarks",
662        "Text formatting: bold, italic, underline styles, strike, color, size, font",
663        "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
664        "Paragraph formatting: alignment, borders, shading, spacing, indentation",
665        "Pagination controls: keep-with-next, keep-together, widow control",
666        "Bullet and numbered lists with nesting",
667        "Tab stops with dot/underscore/hyphen leaders",
668        "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
669        "Row properties: header rows, exact height, cant-split",
670        "Cell properties: width, no-wrap, vertical alignment",
671        "Inline images and header images",
672        "Placeholder replacement (single and batch)",
673        "Regex-based find and replace",
674        "Content insertion at specific positions",
675        "Section breaks with mixed portrait/landscape",
676        "Custom paragraph and character styles",
677        "Document intelligence: word count, headings, outline, images, links",
678        "Accessibility audit",
679        "Document merging (append)",
680        "Export: DOCX, PDF, HTML, Markdown",
681    ];
682    for f in &features {
683        doc.add_bullet_list_item(f, 0);
684    }
685    doc.add_paragraph("");
686    doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
687        .alignment(Alignment::Center)
688        .shading("E2EFDA")
689        .border_all(BorderStyle::Single, 2, "00AA00");
690
691    doc
692}

Get information about all hyperlinks in the document.

Resolves hyperlink relationship IDs to their target URLs where possible.

Examples found in repository?
examples/generate_all_samples.rs (line 627)
97fn generate_feature_showcase(_samples_dir: &Path) -> Document {
98    let mut doc = Document::new();
99
100    // ── Page Setup & Metadata ──
101    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
102    doc.set_margins(
103        Length::inches(1.0),
104        Length::inches(1.0),
105        Length::inches(1.0),
106        Length::inches(1.0),
107    );
108    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
109    doc.set_gutter(Length::twips(0));
110    doc.set_title("rdocx Feature Showcase");
111    doc.set_author("rdocx Sample Generator");
112    doc.set_subject("Comprehensive feature demonstration");
113    doc.set_keywords("rdocx, docx, rust, sample, showcase");
114
115    // Headers & Footers with different first page
116    doc.set_different_first_page(true);
117    doc.set_first_page_header("rdocx");
118    doc.set_first_page_footer("Feature Showcase — Cover Page");
119    doc.set_header("rdocx Feature Showcase");
120    doc.set_footer("Generated by rdocx");
121
122    // ── COVER PAGE ──
123    let bg = create_sample_png(612, 792, [20, 45, 90]);
124    doc.add_background_image(&bg, "cover_bg.png");
125
126    for _ in 0..3 {
127        doc.add_paragraph("");
128    }
129    {
130        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
131        p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
132    }
133    {
134        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
135        p.add_run("Complete Feature Showcase")
136            .size(28.0)
137            .color("FFFFFF")
138            .italic(true);
139    }
140    doc.add_paragraph("");
141    {
142        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
143        p.add_run("Every feature of the rdocx Rust library")
144            .size(13.0)
145            .color("B0C4DE");
146    }
147    {
148        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
149        p.add_run("demonstrated in a single document.")
150            .size(13.0)
151            .color("B0C4DE");
152    }
153
154    // ── TABLE OF CONTENTS ──
155    doc.add_paragraph("").page_break_before(true);
156    doc.insert_toc(doc.content_count(), 3);
157
158    // ── SECTION 1: TEXT FORMATTING ──
159    doc.add_paragraph("").page_break_before(true);
160    doc.add_paragraph("1. Text Formatting").style("Heading1");
161
162    // Paragraph Alignment
163    doc.add_paragraph("1.1 Paragraph Alignment")
164        .style("Heading2");
165    doc.add_paragraph("Left-aligned paragraph (default).")
166        .alignment(Alignment::Left);
167    doc.add_paragraph("Center-aligned paragraph.")
168        .alignment(Alignment::Center);
169    doc.add_paragraph("Right-aligned paragraph.")
170        .alignment(Alignment::Right);
171    doc.add_paragraph(
172        "Justified paragraph — this text is long enough to demonstrate how justified alignment \
173         distributes extra space across word gaps so lines fill the full width of the text area.",
174    )
175    .alignment(Alignment::Justify);
176
177    doc.add_paragraph("");
178
179    // Run Formatting
180    doc.add_paragraph("1.2 Run Formatting").style("Heading2");
181    {
182        let mut p = doc.add_paragraph("");
183        p.add_run("Bold").bold(true);
184        p.add_run(" | ");
185        p.add_run("Italic").italic(true);
186        p.add_run(" | ");
187        p.add_run("Bold Italic").bold(true).italic(true);
188        p.add_run(" | ");
189        p.add_run("Underline").underline(true);
190        p.add_run(" | ");
191        p.add_run("Strikethrough").strike(true);
192        p.add_run(" | ");
193        p.add_run("Double Strike").double_strike(true);
194    }
195    {
196        let mut p = doc.add_paragraph("");
197        p.add_run("Red").color("FF0000");
198        p.add_run(" | ");
199        p.add_run("Blue").color("0000FF");
200        p.add_run(" | ");
201        p.add_run("Green").color("00AA00");
202        p.add_run(" | ");
203        p.add_run("Highlighted").highlight("FFFF00");
204    }
205    {
206        let mut p = doc.add_paragraph("");
207        p.add_run("8pt").size(8.0);
208        p.add_run(" | ");
209        p.add_run("11pt").size(11.0);
210        p.add_run(" | ");
211        p.add_run("16pt").size(16.0);
212        p.add_run(" | ");
213        p.add_run("24pt").size(24.0);
214    }
215    {
216        let mut p = doc.add_paragraph("");
217        p.add_run("Arial").font("Arial");
218        p.add_run(" | ");
219        p.add_run("Times New Roman").font("Times New Roman");
220        p.add_run(" | ");
221        p.add_run("Courier New").font("Courier New");
222    }
223    {
224        let mut p = doc.add_paragraph("");
225        p.add_run("H");
226        p.add_run("2").subscript();
227        p.add_run("O (subscript) | E=mc");
228        p.add_run("2").superscript();
229        p.add_run(" (superscript)");
230    }
231    {
232        let mut p = doc.add_paragraph("");
233        p.add_run("ALL CAPS").all_caps(true);
234        p.add_run(" | ");
235        p.add_run("Small Caps").small_caps(true);
236        p.add_run(" | ");
237        p.add_run("Expanded").character_spacing(Length::twips(40));
238        p.add_run(" | ");
239        p.add_run("Hidden text (not visible)").hidden(true);
240    }
241
242    doc.add_paragraph("");
243
244    // Underline Styles
245    doc.add_paragraph("1.3 Underline Styles").style("Heading2");
246    {
247        let mut p = doc.add_paragraph("");
248        p.add_run("Single ")
249            .underline_style(rdocx::UnderlineStyle::Single);
250        p.add_run("Double ")
251            .underline_style(rdocx::UnderlineStyle::Double);
252        p.add_run("Thick ")
253            .underline_style(rdocx::UnderlineStyle::Thick);
254        p.add_run("Dotted ")
255            .underline_style(rdocx::UnderlineStyle::Dotted);
256        p.add_run("Dash ")
257            .underline_style(rdocx::UnderlineStyle::Dash);
258        p.add_run("Wave ")
259            .underline_style(rdocx::UnderlineStyle::Wave);
260    }
261
262    // ── SECTION 2: PARAGRAPH FORMATTING ──
263    doc.add_paragraph("").page_break_before(true);
264    doc.add_paragraph("2. Paragraph Formatting")
265        .style("Heading1");
266
267    doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
268    doc.add_paragraph("Paragraph with light green shading")
269        .shading("E2EFDA");
270    doc.add_paragraph("Paragraph with bottom border")
271        .border_bottom(BorderStyle::Single, 6, "2E75B6");
272    doc.add_paragraph("Paragraph with all borders (red)")
273        .border_all(BorderStyle::Single, 4, "FF0000");
274
275    doc.add_paragraph("2.2 Indentation").style("Heading2");
276    doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
277        .indent_left(Length::inches(1.0))
278        .hanging_indent(Length::inches(0.5));
279    doc.add_paragraph("0.5-inch first-line indent on this paragraph")
280        .first_line_indent(Length::inches(0.5));
281    doc.add_paragraph("Both left and right indent (0.75 inches each)")
282        .indent_left(Length::inches(0.75))
283        .indent_right(Length::inches(0.75));
284
285    doc.add_paragraph("2.3 Spacing & Line Height")
286        .style("Heading2");
287    doc.add_paragraph("Extra space before (24pt) and after (12pt)")
288        .space_before(Length::pt(24.0))
289        .space_after(Length::pt(12.0));
290    doc.add_paragraph(
291        "Double line spacing paragraph. This text should have extra vertical space between \
292         lines to demonstrate line_spacing_multiple(2.0).",
293    )
294    .line_spacing_multiple(2.0);
295    doc.add_paragraph("Exact 20pt line spacing (fixed height).")
296        .line_spacing(20.0);
297
298    doc.add_paragraph("2.4 Pagination Controls")
299        .style("Heading2");
300    doc.add_paragraph("keep_with_next — this paragraph stays with the next")
301        .keep_with_next(true);
302    doc.add_paragraph("(This paragraph was kept with the one above.)");
303    doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
304        .keep_together(true);
305    doc.add_paragraph("widow_control — prevents widow/orphan lines")
306        .widow_control(true);
307
308    // ── SECTION 3: LISTS ──
309    doc.add_paragraph("").page_break_before(true);
310    doc.add_paragraph("3. Lists").style("Heading1");
311
312    doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
313    doc.add_bullet_list_item("First item", 0);
314    doc.add_bullet_list_item("Second item", 0);
315    doc.add_bullet_list_item("Nested level 1", 1);
316    doc.add_bullet_list_item("Nested level 2", 2);
317    doc.add_bullet_list_item("Back to level 1", 1);
318    doc.add_bullet_list_item("Third item", 0);
319
320    doc.add_paragraph("");
321
322    doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
323    doc.add_numbered_list_item("First numbered item", 0);
324    doc.add_numbered_list_item("Second numbered item", 0);
325    doc.add_numbered_list_item("Sub-item A", 1);
326    doc.add_numbered_list_item("Sub-item B", 1);
327    doc.add_numbered_list_item("Third numbered item", 0);
328
329    // ── SECTION 4: TAB STOPS ──
330    doc.add_paragraph("");
331    doc.add_paragraph("4. Tab Stops").style("Heading1");
332
333    doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
334    doc.add_paragraph("Left\tCenter\tRight\tDecimal")
335        .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
336        .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
337        .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
338        .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
339
340    doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
341    doc.add_paragraph("Item\t\tPrice")
342        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
343        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
344        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
345    doc.add_paragraph("Widget\t\t$19.99")
346        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
347        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
348        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
349    doc.add_paragraph("Gadget\t\t$249.50")
350        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
351        .add_tab_stop_with_leader(
352            TabAlignment::Right,
353            Length::inches(4.0),
354            TabLeader::Underscore,
355        )
356        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
357
358    // ── SECTION 5: TABLES ──
359    doc.add_paragraph("").page_break_before(true);
360    doc.add_paragraph("5. Tables").style("Heading1");
361
362    doc.add_paragraph("5.1 Basic Table").style("Heading2");
363    {
364        let mut tbl = doc
365            .add_table(4, 3)
366            .borders(BorderStyle::Single, 4, "000000");
367        for col in 0..3 {
368            tbl.cell(0, col).unwrap().shading("2E75B6");
369        }
370        tbl.cell(0, 0).unwrap().set_text("Name");
371        tbl.cell(0, 1).unwrap().set_text("Role");
372        tbl.cell(0, 2).unwrap().set_text("Location");
373        tbl.cell(1, 0).unwrap().set_text("Walter White");
374        tbl.cell(1, 1).unwrap().set_text("CEO");
375        tbl.cell(1, 2).unwrap().set_text("Albuquerque");
376        tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
377        tbl.cell(2, 1).unwrap().set_text("CTO");
378        tbl.cell(2, 2).unwrap().set_text("Remote");
379        tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
380        tbl.cell(3, 1).unwrap().set_text("Security");
381        tbl.cell(3, 2).unwrap().set_text("Washington");
382    }
383
384    doc.add_paragraph("");
385
386    doc.add_paragraph("5.2 Column Span & Cell Shading")
387        .style("Heading2");
388    {
389        let mut tbl = doc
390            .add_table(3, 4)
391            .borders(BorderStyle::Single, 4, "000000")
392            .width_pct(100.0);
393        tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
394        tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
395        tbl.cell(1, 0).unwrap().set_text("Region");
396        tbl.cell(1, 0).unwrap().shading("D6E4F0");
397        tbl.cell(1, 1).unwrap().set_text("Q1");
398        tbl.cell(1, 1).unwrap().shading("D6E4F0");
399        tbl.cell(1, 2).unwrap().set_text("Q2");
400        tbl.cell(1, 2).unwrap().shading("D6E4F0");
401        tbl.cell(1, 3).unwrap().set_text("Total");
402        tbl.cell(1, 3).unwrap().shading("D6E4F0");
403        tbl.cell(2, 0).unwrap().set_text("Americas");
404        tbl.cell(2, 1).unwrap().set_text("$2.4M");
405        tbl.cell(2, 2).unwrap().set_text("$2.7M");
406        tbl.cell(2, 3).unwrap().set_text("$5.1M");
407    }
408
409    doc.add_paragraph("");
410
411    doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
412    {
413        let mut tbl = doc
414            .add_table(4, 3)
415            .borders(BorderStyle::Single, 4, "000000");
416        tbl.cell(0, 0).unwrap().set_text("Category");
417        tbl.cell(0, 0).unwrap().shading("E2EFDA");
418        tbl.cell(0, 1).unwrap().set_text("Item");
419        tbl.cell(0, 1).unwrap().shading("E2EFDA");
420        tbl.cell(0, 2).unwrap().set_text("Price");
421        tbl.cell(0, 2).unwrap().shading("E2EFDA");
422        tbl.cell(1, 0).unwrap().set_text("Hardware");
423        tbl.cell(1, 0).unwrap().v_merge_restart();
424        tbl.cell(1, 1).unwrap().set_text("Laptop");
425        tbl.cell(1, 2).unwrap().set_text("$1,200");
426        tbl.cell(2, 0).unwrap().v_merge_continue();
427        tbl.cell(2, 1).unwrap().set_text("Monitor");
428        tbl.cell(2, 2).unwrap().set_text("$450");
429        tbl.cell(3, 0).unwrap().set_text("Software");
430        tbl.cell(3, 1).unwrap().set_text("IDE License");
431        tbl.cell(3, 2).unwrap().set_text("$200/yr");
432    }
433
434    doc.add_paragraph("");
435
436    doc.add_paragraph("5.4 Nested Table").style("Heading2");
437    {
438        let mut tbl = doc
439            .add_table(2, 2)
440            .borders(BorderStyle::Single, 6, "2E75B6");
441        tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
442        tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
443        tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
444        {
445            let mut cell = tbl.cell(1, 1).unwrap();
446            cell.set_text("Nested table below:");
447            let mut nested = cell
448                .add_table(2, 2)
449                .borders(BorderStyle::Single, 2, "FF6600");
450            nested.cell(0, 0).unwrap().set_text("A");
451            nested.cell(0, 1).unwrap().set_text("B");
452            nested.cell(1, 0).unwrap().set_text("C");
453            nested.cell(1, 1).unwrap().set_text("D");
454        }
455    }
456
457    doc.add_paragraph("");
458
459    doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
460        .style("Heading2");
461    {
462        let mut tbl = doc
463            .add_table(2, 3)
464            .borders(BorderStyle::Single, 4, "666666");
465        tbl.row(0).unwrap().height(Length::pt(50.0)).header();
466        tbl.cell(0, 0).unwrap().set_text("Top");
467        tbl.cell(0, 0)
468            .unwrap()
469            .vertical_alignment(VerticalAlignment::Top)
470            .shading("FFF2CC");
471        tbl.cell(0, 1).unwrap().set_text("Center");
472        tbl.cell(0, 1)
473            .unwrap()
474            .vertical_alignment(VerticalAlignment::Center)
475            .shading("D9E2F3");
476        tbl.cell(0, 2).unwrap().set_text("Bottom");
477        tbl.cell(0, 2)
478            .unwrap()
479            .vertical_alignment(VerticalAlignment::Bottom)
480            .shading("E2EFDA");
481        tbl.row(1).unwrap().cant_split();
482        tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
483        tbl.cell(1, 0).unwrap().no_wrap();
484        tbl.cell(1, 1).unwrap().set_text("Fixed width");
485        tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
486        tbl.cell(1, 2).unwrap().set_text("Normal");
487    }
488
489    // ── SECTION 6: IMAGES ──
490    doc.add_paragraph("").page_break_before(true);
491    doc.add_paragraph("6. Images").style("Heading1");
492
493    doc.add_paragraph("6.1 Inline Image").style("Heading2");
494    doc.add_paragraph("A blue gradient image below:");
495    let img = create_sample_png(200, 50, [0, 80, 200]);
496    doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
497
498    doc.add_paragraph("");
499    doc.add_paragraph("6.2 Header Image").style("Heading2");
500    let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
501    doc.set_header_image(
502        &hdr_img,
503        "header_logo.png",
504        Length::inches(2.0),
505        Length::inches(0.2),
506    );
507    doc.add_paragraph("The header now contains an inline image (check the top of this page).");
508
509    doc.add_paragraph("");
510    doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
511
512    // ── SECTION 7: CONTENT MANIPULATION ──
513    doc.add_paragraph("").page_break_before(true);
514    doc.add_paragraph("7. Content Manipulation")
515        .style("Heading1");
516
517    doc.add_paragraph("7.1 Placeholder Replacement")
518        .style("Heading2");
519    doc.add_paragraph("Customer: {{customer}}");
520    doc.add_paragraph("Date: {{date}}");
521    doc.add_paragraph("Reference: {{ref_number}}");
522    {
523        let mut tbl = doc
524            .add_table(2, 2)
525            .borders(BorderStyle::Single, 4, "000000");
526        tbl.cell(0, 0).unwrap().set_text("Project");
527        tbl.cell(0, 0).unwrap().shading("D6E4F0");
528        tbl.cell(0, 1).unwrap().set_text("{{project}}");
529        tbl.cell(1, 0).unwrap().set_text("Status");
530        tbl.cell(1, 0).unwrap().shading("D6E4F0");
531        tbl.cell(1, 1).unwrap().set_text("{{status}}");
532    }
533
534    let mut replacements = HashMap::new();
535    replacements.insert("{{customer}}", "Tensorbee Inc.");
536    replacements.insert("{{date}}", "February 22, 2026");
537    replacements.insert("{{ref_number}}", "TB-2026-001");
538    replacements.insert("{{project}}", "Infrastructure Upgrade");
539    replacements.insert("{{status}}", "In Progress");
540    let n = doc.replace_all(&replacements);
541    doc.add_paragraph(&format!("({n} placeholders replaced above)"));
542
543    doc.add_paragraph("");
544
545    doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
546    doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
547    let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
548    doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
549
550    doc.add_paragraph("");
551
552    doc.add_paragraph("7.3 Content Insertion").style("Heading2");
553    doc.add_paragraph("Section A: First content.");
554    doc.add_paragraph("Section C: Third content.");
555    if let Some(idx) = doc.find_content_index("Section C") {
556        doc.insert_paragraph(
557            idx,
558            "Section B: Inserted between A and C via find_content_index().",
559        );
560    }
561
562    // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
563    doc.add_paragraph("").section_break(SectionBreak::NextPage);
564    doc.add_paragraph("8. Section Breaks & Orientation")
565        .style("Heading1");
566    doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
567
568    {
569        let mut tbl = doc
570            .add_table(3, 7)
571            .borders(BorderStyle::Single, 4, "2E75B6");
572        let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
573        for (c, h) in headers.iter().enumerate() {
574            tbl.cell(0, c).unwrap().set_text(h);
575            tbl.cell(0, c).unwrap().shading("2E75B6");
576        }
577        let data = [
578            [
579                "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
580            ],
581            [
582                "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
583            ],
584        ];
585        for (r, row) in data.iter().enumerate() {
586            for (c, v) in row.iter().enumerate() {
587                tbl.cell(r + 1, c).unwrap().set_text(v);
588            }
589        }
590    }
591
592    doc.add_paragraph("")
593        .section_break(SectionBreak::NextPage)
594        .section_landscape();
595
596    // ── SECTION 9: CUSTOM STYLES ──
597    doc.add_paragraph("9. Custom Styles").style("Heading1");
598    doc.add_style(
599        StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
600            .based_on("Normal")
601            .paragraph_properties(rdocx_oxml::properties::CT_PPr {
602                shading: Some(rdocx_oxml::properties::CT_Shd {
603                    val: "clear".to_string(),
604                    color: None,
605                    fill: Some("FFF2CC".to_string()),
606                }),
607                ..Default::default()
608            })
609            .run_properties(rdocx_oxml::properties::CT_RPr {
610                bold: Some(true),
611                color: Some("C45911".to_string()),
612                ..Default::default()
613            }),
614    );
615    doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
616        .style("CustomHighlight");
617
618    doc.add_paragraph("");
619
620    // ── SECTION 10: DOCUMENT INTELLIGENCE ──
621    doc.add_paragraph("10. Document Intelligence API")
622        .style("Heading1");
623
624    let wc = doc.word_count();
625    let hc = doc.headings().len();
626    let ic = doc.images().len();
627    let lc = doc.links().len();
628    doc.add_paragraph(&format!("Word count: {wc}"));
629    doc.add_paragraph(&format!("Heading count: {hc}"));
630    doc.add_paragraph(&format!("Image count: {ic}"));
631    doc.add_paragraph(&format!("Link count: {lc}"));
632
633    let outline = doc.document_outline();
634    doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
635
636    let issues = doc.audit_accessibility();
637    doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
638    for issue in &issues {
639        doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
640    }
641
642    // ── SECTION 11: DOCUMENT MERGING ──
643    doc.add_paragraph("");
644    doc.add_paragraph("11. Document Merging").style("Heading1");
645    {
646        let mut other = Document::new();
647        other.add_paragraph("This paragraph was merged from another document using append().");
648        doc.append(&other);
649    }
650
651    doc.add_paragraph("");
652
653    // ── SUMMARY ──
654    doc.add_paragraph("Summary of Demonstrated Features")
655        .style("Heading1");
656    let features = [
657        "Page setup: size, margins, header/footer distance, gutter",
658        "Document metadata: title, author, subject, keywords",
659        "Headers and footers: text, images, different first page",
660        "Background images (full-page behind text)",
661        "Table of Contents generation with bookmarks",
662        "Text formatting: bold, italic, underline styles, strike, color, size, font",
663        "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
664        "Paragraph formatting: alignment, borders, shading, spacing, indentation",
665        "Pagination controls: keep-with-next, keep-together, widow control",
666        "Bullet and numbered lists with nesting",
667        "Tab stops with dot/underscore/hyphen leaders",
668        "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
669        "Row properties: header rows, exact height, cant-split",
670        "Cell properties: width, no-wrap, vertical alignment",
671        "Inline images and header images",
672        "Placeholder replacement (single and batch)",
673        "Regex-based find and replace",
674        "Content insertion at specific positions",
675        "Section breaks with mixed portrait/landscape",
676        "Custom paragraph and character styles",
677        "Document intelligence: word count, headings, outline, images, links",
678        "Accessibility audit",
679        "Document merging (append)",
680        "Export: DOCX, PDF, HTML, Markdown",
681    ];
682    for f in &features {
683        doc.add_bullet_list_item(f, 0);
684    }
685    doc.add_paragraph("");
686    doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
687        .alignment(Alignment::Center)
688        .shading("E2EFDA")
689        .border_all(BorderStyle::Single, 2, "00AA00");
690
691    doc
692}
Source

pub fn word_count(&self) -> usize

Count the number of words in the document.

Counts whitespace-separated tokens across all paragraphs (including paragraphs inside table cells).

Examples found in repository?
examples/generate_all_samples.rs (line 624)
97fn generate_feature_showcase(_samples_dir: &Path) -> Document {
98    let mut doc = Document::new();
99
100    // ── Page Setup & Metadata ──
101    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
102    doc.set_margins(
103        Length::inches(1.0),
104        Length::inches(1.0),
105        Length::inches(1.0),
106        Length::inches(1.0),
107    );
108    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
109    doc.set_gutter(Length::twips(0));
110    doc.set_title("rdocx Feature Showcase");
111    doc.set_author("rdocx Sample Generator");
112    doc.set_subject("Comprehensive feature demonstration");
113    doc.set_keywords("rdocx, docx, rust, sample, showcase");
114
115    // Headers & Footers with different first page
116    doc.set_different_first_page(true);
117    doc.set_first_page_header("rdocx");
118    doc.set_first_page_footer("Feature Showcase — Cover Page");
119    doc.set_header("rdocx Feature Showcase");
120    doc.set_footer("Generated by rdocx");
121
122    // ── COVER PAGE ──
123    let bg = create_sample_png(612, 792, [20, 45, 90]);
124    doc.add_background_image(&bg, "cover_bg.png");
125
126    for _ in 0..3 {
127        doc.add_paragraph("");
128    }
129    {
130        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
131        p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
132    }
133    {
134        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
135        p.add_run("Complete Feature Showcase")
136            .size(28.0)
137            .color("FFFFFF")
138            .italic(true);
139    }
140    doc.add_paragraph("");
141    {
142        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
143        p.add_run("Every feature of the rdocx Rust library")
144            .size(13.0)
145            .color("B0C4DE");
146    }
147    {
148        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
149        p.add_run("demonstrated in a single document.")
150            .size(13.0)
151            .color("B0C4DE");
152    }
153
154    // ── TABLE OF CONTENTS ──
155    doc.add_paragraph("").page_break_before(true);
156    doc.insert_toc(doc.content_count(), 3);
157
158    // ── SECTION 1: TEXT FORMATTING ──
159    doc.add_paragraph("").page_break_before(true);
160    doc.add_paragraph("1. Text Formatting").style("Heading1");
161
162    // Paragraph Alignment
163    doc.add_paragraph("1.1 Paragraph Alignment")
164        .style("Heading2");
165    doc.add_paragraph("Left-aligned paragraph (default).")
166        .alignment(Alignment::Left);
167    doc.add_paragraph("Center-aligned paragraph.")
168        .alignment(Alignment::Center);
169    doc.add_paragraph("Right-aligned paragraph.")
170        .alignment(Alignment::Right);
171    doc.add_paragraph(
172        "Justified paragraph — this text is long enough to demonstrate how justified alignment \
173         distributes extra space across word gaps so lines fill the full width of the text area.",
174    )
175    .alignment(Alignment::Justify);
176
177    doc.add_paragraph("");
178
179    // Run Formatting
180    doc.add_paragraph("1.2 Run Formatting").style("Heading2");
181    {
182        let mut p = doc.add_paragraph("");
183        p.add_run("Bold").bold(true);
184        p.add_run(" | ");
185        p.add_run("Italic").italic(true);
186        p.add_run(" | ");
187        p.add_run("Bold Italic").bold(true).italic(true);
188        p.add_run(" | ");
189        p.add_run("Underline").underline(true);
190        p.add_run(" | ");
191        p.add_run("Strikethrough").strike(true);
192        p.add_run(" | ");
193        p.add_run("Double Strike").double_strike(true);
194    }
195    {
196        let mut p = doc.add_paragraph("");
197        p.add_run("Red").color("FF0000");
198        p.add_run(" | ");
199        p.add_run("Blue").color("0000FF");
200        p.add_run(" | ");
201        p.add_run("Green").color("00AA00");
202        p.add_run(" | ");
203        p.add_run("Highlighted").highlight("FFFF00");
204    }
205    {
206        let mut p = doc.add_paragraph("");
207        p.add_run("8pt").size(8.0);
208        p.add_run(" | ");
209        p.add_run("11pt").size(11.0);
210        p.add_run(" | ");
211        p.add_run("16pt").size(16.0);
212        p.add_run(" | ");
213        p.add_run("24pt").size(24.0);
214    }
215    {
216        let mut p = doc.add_paragraph("");
217        p.add_run("Arial").font("Arial");
218        p.add_run(" | ");
219        p.add_run("Times New Roman").font("Times New Roman");
220        p.add_run(" | ");
221        p.add_run("Courier New").font("Courier New");
222    }
223    {
224        let mut p = doc.add_paragraph("");
225        p.add_run("H");
226        p.add_run("2").subscript();
227        p.add_run("O (subscript) | E=mc");
228        p.add_run("2").superscript();
229        p.add_run(" (superscript)");
230    }
231    {
232        let mut p = doc.add_paragraph("");
233        p.add_run("ALL CAPS").all_caps(true);
234        p.add_run(" | ");
235        p.add_run("Small Caps").small_caps(true);
236        p.add_run(" | ");
237        p.add_run("Expanded").character_spacing(Length::twips(40));
238        p.add_run(" | ");
239        p.add_run("Hidden text (not visible)").hidden(true);
240    }
241
242    doc.add_paragraph("");
243
244    // Underline Styles
245    doc.add_paragraph("1.3 Underline Styles").style("Heading2");
246    {
247        let mut p = doc.add_paragraph("");
248        p.add_run("Single ")
249            .underline_style(rdocx::UnderlineStyle::Single);
250        p.add_run("Double ")
251            .underline_style(rdocx::UnderlineStyle::Double);
252        p.add_run("Thick ")
253            .underline_style(rdocx::UnderlineStyle::Thick);
254        p.add_run("Dotted ")
255            .underline_style(rdocx::UnderlineStyle::Dotted);
256        p.add_run("Dash ")
257            .underline_style(rdocx::UnderlineStyle::Dash);
258        p.add_run("Wave ")
259            .underline_style(rdocx::UnderlineStyle::Wave);
260    }
261
262    // ── SECTION 2: PARAGRAPH FORMATTING ──
263    doc.add_paragraph("").page_break_before(true);
264    doc.add_paragraph("2. Paragraph Formatting")
265        .style("Heading1");
266
267    doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
268    doc.add_paragraph("Paragraph with light green shading")
269        .shading("E2EFDA");
270    doc.add_paragraph("Paragraph with bottom border")
271        .border_bottom(BorderStyle::Single, 6, "2E75B6");
272    doc.add_paragraph("Paragraph with all borders (red)")
273        .border_all(BorderStyle::Single, 4, "FF0000");
274
275    doc.add_paragraph("2.2 Indentation").style("Heading2");
276    doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
277        .indent_left(Length::inches(1.0))
278        .hanging_indent(Length::inches(0.5));
279    doc.add_paragraph("0.5-inch first-line indent on this paragraph")
280        .first_line_indent(Length::inches(0.5));
281    doc.add_paragraph("Both left and right indent (0.75 inches each)")
282        .indent_left(Length::inches(0.75))
283        .indent_right(Length::inches(0.75));
284
285    doc.add_paragraph("2.3 Spacing & Line Height")
286        .style("Heading2");
287    doc.add_paragraph("Extra space before (24pt) and after (12pt)")
288        .space_before(Length::pt(24.0))
289        .space_after(Length::pt(12.0));
290    doc.add_paragraph(
291        "Double line spacing paragraph. This text should have extra vertical space between \
292         lines to demonstrate line_spacing_multiple(2.0).",
293    )
294    .line_spacing_multiple(2.0);
295    doc.add_paragraph("Exact 20pt line spacing (fixed height).")
296        .line_spacing(20.0);
297
298    doc.add_paragraph("2.4 Pagination Controls")
299        .style("Heading2");
300    doc.add_paragraph("keep_with_next — this paragraph stays with the next")
301        .keep_with_next(true);
302    doc.add_paragraph("(This paragraph was kept with the one above.)");
303    doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
304        .keep_together(true);
305    doc.add_paragraph("widow_control — prevents widow/orphan lines")
306        .widow_control(true);
307
308    // ── SECTION 3: LISTS ──
309    doc.add_paragraph("").page_break_before(true);
310    doc.add_paragraph("3. Lists").style("Heading1");
311
312    doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
313    doc.add_bullet_list_item("First item", 0);
314    doc.add_bullet_list_item("Second item", 0);
315    doc.add_bullet_list_item("Nested level 1", 1);
316    doc.add_bullet_list_item("Nested level 2", 2);
317    doc.add_bullet_list_item("Back to level 1", 1);
318    doc.add_bullet_list_item("Third item", 0);
319
320    doc.add_paragraph("");
321
322    doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
323    doc.add_numbered_list_item("First numbered item", 0);
324    doc.add_numbered_list_item("Second numbered item", 0);
325    doc.add_numbered_list_item("Sub-item A", 1);
326    doc.add_numbered_list_item("Sub-item B", 1);
327    doc.add_numbered_list_item("Third numbered item", 0);
328
329    // ── SECTION 4: TAB STOPS ──
330    doc.add_paragraph("");
331    doc.add_paragraph("4. Tab Stops").style("Heading1");
332
333    doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
334    doc.add_paragraph("Left\tCenter\tRight\tDecimal")
335        .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
336        .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
337        .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
338        .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
339
340    doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
341    doc.add_paragraph("Item\t\tPrice")
342        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
343        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
344        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
345    doc.add_paragraph("Widget\t\t$19.99")
346        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
347        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
348        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
349    doc.add_paragraph("Gadget\t\t$249.50")
350        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
351        .add_tab_stop_with_leader(
352            TabAlignment::Right,
353            Length::inches(4.0),
354            TabLeader::Underscore,
355        )
356        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
357
358    // ── SECTION 5: TABLES ──
359    doc.add_paragraph("").page_break_before(true);
360    doc.add_paragraph("5. Tables").style("Heading1");
361
362    doc.add_paragraph("5.1 Basic Table").style("Heading2");
363    {
364        let mut tbl = doc
365            .add_table(4, 3)
366            .borders(BorderStyle::Single, 4, "000000");
367        for col in 0..3 {
368            tbl.cell(0, col).unwrap().shading("2E75B6");
369        }
370        tbl.cell(0, 0).unwrap().set_text("Name");
371        tbl.cell(0, 1).unwrap().set_text("Role");
372        tbl.cell(0, 2).unwrap().set_text("Location");
373        tbl.cell(1, 0).unwrap().set_text("Walter White");
374        tbl.cell(1, 1).unwrap().set_text("CEO");
375        tbl.cell(1, 2).unwrap().set_text("Albuquerque");
376        tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
377        tbl.cell(2, 1).unwrap().set_text("CTO");
378        tbl.cell(2, 2).unwrap().set_text("Remote");
379        tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
380        tbl.cell(3, 1).unwrap().set_text("Security");
381        tbl.cell(3, 2).unwrap().set_text("Washington");
382    }
383
384    doc.add_paragraph("");
385
386    doc.add_paragraph("5.2 Column Span & Cell Shading")
387        .style("Heading2");
388    {
389        let mut tbl = doc
390            .add_table(3, 4)
391            .borders(BorderStyle::Single, 4, "000000")
392            .width_pct(100.0);
393        tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
394        tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
395        tbl.cell(1, 0).unwrap().set_text("Region");
396        tbl.cell(1, 0).unwrap().shading("D6E4F0");
397        tbl.cell(1, 1).unwrap().set_text("Q1");
398        tbl.cell(1, 1).unwrap().shading("D6E4F0");
399        tbl.cell(1, 2).unwrap().set_text("Q2");
400        tbl.cell(1, 2).unwrap().shading("D6E4F0");
401        tbl.cell(1, 3).unwrap().set_text("Total");
402        tbl.cell(1, 3).unwrap().shading("D6E4F0");
403        tbl.cell(2, 0).unwrap().set_text("Americas");
404        tbl.cell(2, 1).unwrap().set_text("$2.4M");
405        tbl.cell(2, 2).unwrap().set_text("$2.7M");
406        tbl.cell(2, 3).unwrap().set_text("$5.1M");
407    }
408
409    doc.add_paragraph("");
410
411    doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
412    {
413        let mut tbl = doc
414            .add_table(4, 3)
415            .borders(BorderStyle::Single, 4, "000000");
416        tbl.cell(0, 0).unwrap().set_text("Category");
417        tbl.cell(0, 0).unwrap().shading("E2EFDA");
418        tbl.cell(0, 1).unwrap().set_text("Item");
419        tbl.cell(0, 1).unwrap().shading("E2EFDA");
420        tbl.cell(0, 2).unwrap().set_text("Price");
421        tbl.cell(0, 2).unwrap().shading("E2EFDA");
422        tbl.cell(1, 0).unwrap().set_text("Hardware");
423        tbl.cell(1, 0).unwrap().v_merge_restart();
424        tbl.cell(1, 1).unwrap().set_text("Laptop");
425        tbl.cell(1, 2).unwrap().set_text("$1,200");
426        tbl.cell(2, 0).unwrap().v_merge_continue();
427        tbl.cell(2, 1).unwrap().set_text("Monitor");
428        tbl.cell(2, 2).unwrap().set_text("$450");
429        tbl.cell(3, 0).unwrap().set_text("Software");
430        tbl.cell(3, 1).unwrap().set_text("IDE License");
431        tbl.cell(3, 2).unwrap().set_text("$200/yr");
432    }
433
434    doc.add_paragraph("");
435
436    doc.add_paragraph("5.4 Nested Table").style("Heading2");
437    {
438        let mut tbl = doc
439            .add_table(2, 2)
440            .borders(BorderStyle::Single, 6, "2E75B6");
441        tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
442        tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
443        tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
444        {
445            let mut cell = tbl.cell(1, 1).unwrap();
446            cell.set_text("Nested table below:");
447            let mut nested = cell
448                .add_table(2, 2)
449                .borders(BorderStyle::Single, 2, "FF6600");
450            nested.cell(0, 0).unwrap().set_text("A");
451            nested.cell(0, 1).unwrap().set_text("B");
452            nested.cell(1, 0).unwrap().set_text("C");
453            nested.cell(1, 1).unwrap().set_text("D");
454        }
455    }
456
457    doc.add_paragraph("");
458
459    doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
460        .style("Heading2");
461    {
462        let mut tbl = doc
463            .add_table(2, 3)
464            .borders(BorderStyle::Single, 4, "666666");
465        tbl.row(0).unwrap().height(Length::pt(50.0)).header();
466        tbl.cell(0, 0).unwrap().set_text("Top");
467        tbl.cell(0, 0)
468            .unwrap()
469            .vertical_alignment(VerticalAlignment::Top)
470            .shading("FFF2CC");
471        tbl.cell(0, 1).unwrap().set_text("Center");
472        tbl.cell(0, 1)
473            .unwrap()
474            .vertical_alignment(VerticalAlignment::Center)
475            .shading("D9E2F3");
476        tbl.cell(0, 2).unwrap().set_text("Bottom");
477        tbl.cell(0, 2)
478            .unwrap()
479            .vertical_alignment(VerticalAlignment::Bottom)
480            .shading("E2EFDA");
481        tbl.row(1).unwrap().cant_split();
482        tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
483        tbl.cell(1, 0).unwrap().no_wrap();
484        tbl.cell(1, 1).unwrap().set_text("Fixed width");
485        tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
486        tbl.cell(1, 2).unwrap().set_text("Normal");
487    }
488
489    // ── SECTION 6: IMAGES ──
490    doc.add_paragraph("").page_break_before(true);
491    doc.add_paragraph("6. Images").style("Heading1");
492
493    doc.add_paragraph("6.1 Inline Image").style("Heading2");
494    doc.add_paragraph("A blue gradient image below:");
495    let img = create_sample_png(200, 50, [0, 80, 200]);
496    doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
497
498    doc.add_paragraph("");
499    doc.add_paragraph("6.2 Header Image").style("Heading2");
500    let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
501    doc.set_header_image(
502        &hdr_img,
503        "header_logo.png",
504        Length::inches(2.0),
505        Length::inches(0.2),
506    );
507    doc.add_paragraph("The header now contains an inline image (check the top of this page).");
508
509    doc.add_paragraph("");
510    doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
511
512    // ── SECTION 7: CONTENT MANIPULATION ──
513    doc.add_paragraph("").page_break_before(true);
514    doc.add_paragraph("7. Content Manipulation")
515        .style("Heading1");
516
517    doc.add_paragraph("7.1 Placeholder Replacement")
518        .style("Heading2");
519    doc.add_paragraph("Customer: {{customer}}");
520    doc.add_paragraph("Date: {{date}}");
521    doc.add_paragraph("Reference: {{ref_number}}");
522    {
523        let mut tbl = doc
524            .add_table(2, 2)
525            .borders(BorderStyle::Single, 4, "000000");
526        tbl.cell(0, 0).unwrap().set_text("Project");
527        tbl.cell(0, 0).unwrap().shading("D6E4F0");
528        tbl.cell(0, 1).unwrap().set_text("{{project}}");
529        tbl.cell(1, 0).unwrap().set_text("Status");
530        tbl.cell(1, 0).unwrap().shading("D6E4F0");
531        tbl.cell(1, 1).unwrap().set_text("{{status}}");
532    }
533
534    let mut replacements = HashMap::new();
535    replacements.insert("{{customer}}", "Tensorbee Inc.");
536    replacements.insert("{{date}}", "February 22, 2026");
537    replacements.insert("{{ref_number}}", "TB-2026-001");
538    replacements.insert("{{project}}", "Infrastructure Upgrade");
539    replacements.insert("{{status}}", "In Progress");
540    let n = doc.replace_all(&replacements);
541    doc.add_paragraph(&format!("({n} placeholders replaced above)"));
542
543    doc.add_paragraph("");
544
545    doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
546    doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
547    let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
548    doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
549
550    doc.add_paragraph("");
551
552    doc.add_paragraph("7.3 Content Insertion").style("Heading2");
553    doc.add_paragraph("Section A: First content.");
554    doc.add_paragraph("Section C: Third content.");
555    if let Some(idx) = doc.find_content_index("Section C") {
556        doc.insert_paragraph(
557            idx,
558            "Section B: Inserted between A and C via find_content_index().",
559        );
560    }
561
562    // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
563    doc.add_paragraph("").section_break(SectionBreak::NextPage);
564    doc.add_paragraph("8. Section Breaks & Orientation")
565        .style("Heading1");
566    doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
567
568    {
569        let mut tbl = doc
570            .add_table(3, 7)
571            .borders(BorderStyle::Single, 4, "2E75B6");
572        let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
573        for (c, h) in headers.iter().enumerate() {
574            tbl.cell(0, c).unwrap().set_text(h);
575            tbl.cell(0, c).unwrap().shading("2E75B6");
576        }
577        let data = [
578            [
579                "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
580            ],
581            [
582                "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
583            ],
584        ];
585        for (r, row) in data.iter().enumerate() {
586            for (c, v) in row.iter().enumerate() {
587                tbl.cell(r + 1, c).unwrap().set_text(v);
588            }
589        }
590    }
591
592    doc.add_paragraph("")
593        .section_break(SectionBreak::NextPage)
594        .section_landscape();
595
596    // ── SECTION 9: CUSTOM STYLES ──
597    doc.add_paragraph("9. Custom Styles").style("Heading1");
598    doc.add_style(
599        StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
600            .based_on("Normal")
601            .paragraph_properties(rdocx_oxml::properties::CT_PPr {
602                shading: Some(rdocx_oxml::properties::CT_Shd {
603                    val: "clear".to_string(),
604                    color: None,
605                    fill: Some("FFF2CC".to_string()),
606                }),
607                ..Default::default()
608            })
609            .run_properties(rdocx_oxml::properties::CT_RPr {
610                bold: Some(true),
611                color: Some("C45911".to_string()),
612                ..Default::default()
613            }),
614    );
615    doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
616        .style("CustomHighlight");
617
618    doc.add_paragraph("");
619
620    // ── SECTION 10: DOCUMENT INTELLIGENCE ──
621    doc.add_paragraph("10. Document Intelligence API")
622        .style("Heading1");
623
624    let wc = doc.word_count();
625    let hc = doc.headings().len();
626    let ic = doc.images().len();
627    let lc = doc.links().len();
628    doc.add_paragraph(&format!("Word count: {wc}"));
629    doc.add_paragraph(&format!("Heading count: {hc}"));
630    doc.add_paragraph(&format!("Image count: {ic}"));
631    doc.add_paragraph(&format!("Link count: {lc}"));
632
633    let outline = doc.document_outline();
634    doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
635
636    let issues = doc.audit_accessibility();
637    doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
638    for issue in &issues {
639        doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
640    }
641
642    // ── SECTION 11: DOCUMENT MERGING ──
643    doc.add_paragraph("");
644    doc.add_paragraph("11. Document Merging").style("Heading1");
645    {
646        let mut other = Document::new();
647        other.add_paragraph("This paragraph was merged from another document using append().");
648        doc.append(&other);
649    }
650
651    doc.add_paragraph("");
652
653    // ── SUMMARY ──
654    doc.add_paragraph("Summary of Demonstrated Features")
655        .style("Heading1");
656    let features = [
657        "Page setup: size, margins, header/footer distance, gutter",
658        "Document metadata: title, author, subject, keywords",
659        "Headers and footers: text, images, different first page",
660        "Background images (full-page behind text)",
661        "Table of Contents generation with bookmarks",
662        "Text formatting: bold, italic, underline styles, strike, color, size, font",
663        "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
664        "Paragraph formatting: alignment, borders, shading, spacing, indentation",
665        "Pagination controls: keep-with-next, keep-together, widow control",
666        "Bullet and numbered lists with nesting",
667        "Tab stops with dot/underscore/hyphen leaders",
668        "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
669        "Row properties: header rows, exact height, cant-split",
670        "Cell properties: width, no-wrap, vertical alignment",
671        "Inline images and header images",
672        "Placeholder replacement (single and batch)",
673        "Regex-based find and replace",
674        "Content insertion at specific positions",
675        "Section breaks with mixed portrait/landscape",
676        "Custom paragraph and character styles",
677        "Document intelligence: word count, headings, outline, images, links",
678        "Accessibility audit",
679        "Document merging (append)",
680        "Export: DOCX, PDF, HTML, Markdown",
681    ];
682    for f in &features {
683        doc.add_bullet_list_item(f, 0);
684    }
685    doc.add_paragraph("");
686    doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
687        .alignment(Alignment::Center)
688        .shading("E2EFDA")
689        .border_all(BorderStyle::Single, 2, "00AA00");
690
691    doc
692}
Source

pub fn audit_accessibility(&self) -> Vec<AccessibilityIssue>

Audit the document for accessibility issues.

Checks for common problems: missing image alt text, heading level gaps, empty paragraphs, missing document metadata.

Examples found in repository?
examples/generate_all_samples.rs (line 636)
97fn generate_feature_showcase(_samples_dir: &Path) -> Document {
98    let mut doc = Document::new();
99
100    // ── Page Setup & Metadata ──
101    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
102    doc.set_margins(
103        Length::inches(1.0),
104        Length::inches(1.0),
105        Length::inches(1.0),
106        Length::inches(1.0),
107    );
108    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
109    doc.set_gutter(Length::twips(0));
110    doc.set_title("rdocx Feature Showcase");
111    doc.set_author("rdocx Sample Generator");
112    doc.set_subject("Comprehensive feature demonstration");
113    doc.set_keywords("rdocx, docx, rust, sample, showcase");
114
115    // Headers & Footers with different first page
116    doc.set_different_first_page(true);
117    doc.set_first_page_header("rdocx");
118    doc.set_first_page_footer("Feature Showcase — Cover Page");
119    doc.set_header("rdocx Feature Showcase");
120    doc.set_footer("Generated by rdocx");
121
122    // ── COVER PAGE ──
123    let bg = create_sample_png(612, 792, [20, 45, 90]);
124    doc.add_background_image(&bg, "cover_bg.png");
125
126    for _ in 0..3 {
127        doc.add_paragraph("");
128    }
129    {
130        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
131        p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
132    }
133    {
134        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
135        p.add_run("Complete Feature Showcase")
136            .size(28.0)
137            .color("FFFFFF")
138            .italic(true);
139    }
140    doc.add_paragraph("");
141    {
142        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
143        p.add_run("Every feature of the rdocx Rust library")
144            .size(13.0)
145            .color("B0C4DE");
146    }
147    {
148        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
149        p.add_run("demonstrated in a single document.")
150            .size(13.0)
151            .color("B0C4DE");
152    }
153
154    // ── TABLE OF CONTENTS ──
155    doc.add_paragraph("").page_break_before(true);
156    doc.insert_toc(doc.content_count(), 3);
157
158    // ── SECTION 1: TEXT FORMATTING ──
159    doc.add_paragraph("").page_break_before(true);
160    doc.add_paragraph("1. Text Formatting").style("Heading1");
161
162    // Paragraph Alignment
163    doc.add_paragraph("1.1 Paragraph Alignment")
164        .style("Heading2");
165    doc.add_paragraph("Left-aligned paragraph (default).")
166        .alignment(Alignment::Left);
167    doc.add_paragraph("Center-aligned paragraph.")
168        .alignment(Alignment::Center);
169    doc.add_paragraph("Right-aligned paragraph.")
170        .alignment(Alignment::Right);
171    doc.add_paragraph(
172        "Justified paragraph — this text is long enough to demonstrate how justified alignment \
173         distributes extra space across word gaps so lines fill the full width of the text area.",
174    )
175    .alignment(Alignment::Justify);
176
177    doc.add_paragraph("");
178
179    // Run Formatting
180    doc.add_paragraph("1.2 Run Formatting").style("Heading2");
181    {
182        let mut p = doc.add_paragraph("");
183        p.add_run("Bold").bold(true);
184        p.add_run(" | ");
185        p.add_run("Italic").italic(true);
186        p.add_run(" | ");
187        p.add_run("Bold Italic").bold(true).italic(true);
188        p.add_run(" | ");
189        p.add_run("Underline").underline(true);
190        p.add_run(" | ");
191        p.add_run("Strikethrough").strike(true);
192        p.add_run(" | ");
193        p.add_run("Double Strike").double_strike(true);
194    }
195    {
196        let mut p = doc.add_paragraph("");
197        p.add_run("Red").color("FF0000");
198        p.add_run(" | ");
199        p.add_run("Blue").color("0000FF");
200        p.add_run(" | ");
201        p.add_run("Green").color("00AA00");
202        p.add_run(" | ");
203        p.add_run("Highlighted").highlight("FFFF00");
204    }
205    {
206        let mut p = doc.add_paragraph("");
207        p.add_run("8pt").size(8.0);
208        p.add_run(" | ");
209        p.add_run("11pt").size(11.0);
210        p.add_run(" | ");
211        p.add_run("16pt").size(16.0);
212        p.add_run(" | ");
213        p.add_run("24pt").size(24.0);
214    }
215    {
216        let mut p = doc.add_paragraph("");
217        p.add_run("Arial").font("Arial");
218        p.add_run(" | ");
219        p.add_run("Times New Roman").font("Times New Roman");
220        p.add_run(" | ");
221        p.add_run("Courier New").font("Courier New");
222    }
223    {
224        let mut p = doc.add_paragraph("");
225        p.add_run("H");
226        p.add_run("2").subscript();
227        p.add_run("O (subscript) | E=mc");
228        p.add_run("2").superscript();
229        p.add_run(" (superscript)");
230    }
231    {
232        let mut p = doc.add_paragraph("");
233        p.add_run("ALL CAPS").all_caps(true);
234        p.add_run(" | ");
235        p.add_run("Small Caps").small_caps(true);
236        p.add_run(" | ");
237        p.add_run("Expanded").character_spacing(Length::twips(40));
238        p.add_run(" | ");
239        p.add_run("Hidden text (not visible)").hidden(true);
240    }
241
242    doc.add_paragraph("");
243
244    // Underline Styles
245    doc.add_paragraph("1.3 Underline Styles").style("Heading2");
246    {
247        let mut p = doc.add_paragraph("");
248        p.add_run("Single ")
249            .underline_style(rdocx::UnderlineStyle::Single);
250        p.add_run("Double ")
251            .underline_style(rdocx::UnderlineStyle::Double);
252        p.add_run("Thick ")
253            .underline_style(rdocx::UnderlineStyle::Thick);
254        p.add_run("Dotted ")
255            .underline_style(rdocx::UnderlineStyle::Dotted);
256        p.add_run("Dash ")
257            .underline_style(rdocx::UnderlineStyle::Dash);
258        p.add_run("Wave ")
259            .underline_style(rdocx::UnderlineStyle::Wave);
260    }
261
262    // ── SECTION 2: PARAGRAPH FORMATTING ──
263    doc.add_paragraph("").page_break_before(true);
264    doc.add_paragraph("2. Paragraph Formatting")
265        .style("Heading1");
266
267    doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
268    doc.add_paragraph("Paragraph with light green shading")
269        .shading("E2EFDA");
270    doc.add_paragraph("Paragraph with bottom border")
271        .border_bottom(BorderStyle::Single, 6, "2E75B6");
272    doc.add_paragraph("Paragraph with all borders (red)")
273        .border_all(BorderStyle::Single, 4, "FF0000");
274
275    doc.add_paragraph("2.2 Indentation").style("Heading2");
276    doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
277        .indent_left(Length::inches(1.0))
278        .hanging_indent(Length::inches(0.5));
279    doc.add_paragraph("0.5-inch first-line indent on this paragraph")
280        .first_line_indent(Length::inches(0.5));
281    doc.add_paragraph("Both left and right indent (0.75 inches each)")
282        .indent_left(Length::inches(0.75))
283        .indent_right(Length::inches(0.75));
284
285    doc.add_paragraph("2.3 Spacing & Line Height")
286        .style("Heading2");
287    doc.add_paragraph("Extra space before (24pt) and after (12pt)")
288        .space_before(Length::pt(24.0))
289        .space_after(Length::pt(12.0));
290    doc.add_paragraph(
291        "Double line spacing paragraph. This text should have extra vertical space between \
292         lines to demonstrate line_spacing_multiple(2.0).",
293    )
294    .line_spacing_multiple(2.0);
295    doc.add_paragraph("Exact 20pt line spacing (fixed height).")
296        .line_spacing(20.0);
297
298    doc.add_paragraph("2.4 Pagination Controls")
299        .style("Heading2");
300    doc.add_paragraph("keep_with_next — this paragraph stays with the next")
301        .keep_with_next(true);
302    doc.add_paragraph("(This paragraph was kept with the one above.)");
303    doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
304        .keep_together(true);
305    doc.add_paragraph("widow_control — prevents widow/orphan lines")
306        .widow_control(true);
307
308    // ── SECTION 3: LISTS ──
309    doc.add_paragraph("").page_break_before(true);
310    doc.add_paragraph("3. Lists").style("Heading1");
311
312    doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
313    doc.add_bullet_list_item("First item", 0);
314    doc.add_bullet_list_item("Second item", 0);
315    doc.add_bullet_list_item("Nested level 1", 1);
316    doc.add_bullet_list_item("Nested level 2", 2);
317    doc.add_bullet_list_item("Back to level 1", 1);
318    doc.add_bullet_list_item("Third item", 0);
319
320    doc.add_paragraph("");
321
322    doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
323    doc.add_numbered_list_item("First numbered item", 0);
324    doc.add_numbered_list_item("Second numbered item", 0);
325    doc.add_numbered_list_item("Sub-item A", 1);
326    doc.add_numbered_list_item("Sub-item B", 1);
327    doc.add_numbered_list_item("Third numbered item", 0);
328
329    // ── SECTION 4: TAB STOPS ──
330    doc.add_paragraph("");
331    doc.add_paragraph("4. Tab Stops").style("Heading1");
332
333    doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
334    doc.add_paragraph("Left\tCenter\tRight\tDecimal")
335        .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
336        .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
337        .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
338        .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
339
340    doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
341    doc.add_paragraph("Item\t\tPrice")
342        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
343        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
344        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
345    doc.add_paragraph("Widget\t\t$19.99")
346        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
347        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
348        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
349    doc.add_paragraph("Gadget\t\t$249.50")
350        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
351        .add_tab_stop_with_leader(
352            TabAlignment::Right,
353            Length::inches(4.0),
354            TabLeader::Underscore,
355        )
356        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
357
358    // ── SECTION 5: TABLES ──
359    doc.add_paragraph("").page_break_before(true);
360    doc.add_paragraph("5. Tables").style("Heading1");
361
362    doc.add_paragraph("5.1 Basic Table").style("Heading2");
363    {
364        let mut tbl = doc
365            .add_table(4, 3)
366            .borders(BorderStyle::Single, 4, "000000");
367        for col in 0..3 {
368            tbl.cell(0, col).unwrap().shading("2E75B6");
369        }
370        tbl.cell(0, 0).unwrap().set_text("Name");
371        tbl.cell(0, 1).unwrap().set_text("Role");
372        tbl.cell(0, 2).unwrap().set_text("Location");
373        tbl.cell(1, 0).unwrap().set_text("Walter White");
374        tbl.cell(1, 1).unwrap().set_text("CEO");
375        tbl.cell(1, 2).unwrap().set_text("Albuquerque");
376        tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
377        tbl.cell(2, 1).unwrap().set_text("CTO");
378        tbl.cell(2, 2).unwrap().set_text("Remote");
379        tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
380        tbl.cell(3, 1).unwrap().set_text("Security");
381        tbl.cell(3, 2).unwrap().set_text("Washington");
382    }
383
384    doc.add_paragraph("");
385
386    doc.add_paragraph("5.2 Column Span & Cell Shading")
387        .style("Heading2");
388    {
389        let mut tbl = doc
390            .add_table(3, 4)
391            .borders(BorderStyle::Single, 4, "000000")
392            .width_pct(100.0);
393        tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
394        tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
395        tbl.cell(1, 0).unwrap().set_text("Region");
396        tbl.cell(1, 0).unwrap().shading("D6E4F0");
397        tbl.cell(1, 1).unwrap().set_text("Q1");
398        tbl.cell(1, 1).unwrap().shading("D6E4F0");
399        tbl.cell(1, 2).unwrap().set_text("Q2");
400        tbl.cell(1, 2).unwrap().shading("D6E4F0");
401        tbl.cell(1, 3).unwrap().set_text("Total");
402        tbl.cell(1, 3).unwrap().shading("D6E4F0");
403        tbl.cell(2, 0).unwrap().set_text("Americas");
404        tbl.cell(2, 1).unwrap().set_text("$2.4M");
405        tbl.cell(2, 2).unwrap().set_text("$2.7M");
406        tbl.cell(2, 3).unwrap().set_text("$5.1M");
407    }
408
409    doc.add_paragraph("");
410
411    doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
412    {
413        let mut tbl = doc
414            .add_table(4, 3)
415            .borders(BorderStyle::Single, 4, "000000");
416        tbl.cell(0, 0).unwrap().set_text("Category");
417        tbl.cell(0, 0).unwrap().shading("E2EFDA");
418        tbl.cell(0, 1).unwrap().set_text("Item");
419        tbl.cell(0, 1).unwrap().shading("E2EFDA");
420        tbl.cell(0, 2).unwrap().set_text("Price");
421        tbl.cell(0, 2).unwrap().shading("E2EFDA");
422        tbl.cell(1, 0).unwrap().set_text("Hardware");
423        tbl.cell(1, 0).unwrap().v_merge_restart();
424        tbl.cell(1, 1).unwrap().set_text("Laptop");
425        tbl.cell(1, 2).unwrap().set_text("$1,200");
426        tbl.cell(2, 0).unwrap().v_merge_continue();
427        tbl.cell(2, 1).unwrap().set_text("Monitor");
428        tbl.cell(2, 2).unwrap().set_text("$450");
429        tbl.cell(3, 0).unwrap().set_text("Software");
430        tbl.cell(3, 1).unwrap().set_text("IDE License");
431        tbl.cell(3, 2).unwrap().set_text("$200/yr");
432    }
433
434    doc.add_paragraph("");
435
436    doc.add_paragraph("5.4 Nested Table").style("Heading2");
437    {
438        let mut tbl = doc
439            .add_table(2, 2)
440            .borders(BorderStyle::Single, 6, "2E75B6");
441        tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
442        tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
443        tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
444        {
445            let mut cell = tbl.cell(1, 1).unwrap();
446            cell.set_text("Nested table below:");
447            let mut nested = cell
448                .add_table(2, 2)
449                .borders(BorderStyle::Single, 2, "FF6600");
450            nested.cell(0, 0).unwrap().set_text("A");
451            nested.cell(0, 1).unwrap().set_text("B");
452            nested.cell(1, 0).unwrap().set_text("C");
453            nested.cell(1, 1).unwrap().set_text("D");
454        }
455    }
456
457    doc.add_paragraph("");
458
459    doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
460        .style("Heading2");
461    {
462        let mut tbl = doc
463            .add_table(2, 3)
464            .borders(BorderStyle::Single, 4, "666666");
465        tbl.row(0).unwrap().height(Length::pt(50.0)).header();
466        tbl.cell(0, 0).unwrap().set_text("Top");
467        tbl.cell(0, 0)
468            .unwrap()
469            .vertical_alignment(VerticalAlignment::Top)
470            .shading("FFF2CC");
471        tbl.cell(0, 1).unwrap().set_text("Center");
472        tbl.cell(0, 1)
473            .unwrap()
474            .vertical_alignment(VerticalAlignment::Center)
475            .shading("D9E2F3");
476        tbl.cell(0, 2).unwrap().set_text("Bottom");
477        tbl.cell(0, 2)
478            .unwrap()
479            .vertical_alignment(VerticalAlignment::Bottom)
480            .shading("E2EFDA");
481        tbl.row(1).unwrap().cant_split();
482        tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
483        tbl.cell(1, 0).unwrap().no_wrap();
484        tbl.cell(1, 1).unwrap().set_text("Fixed width");
485        tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
486        tbl.cell(1, 2).unwrap().set_text("Normal");
487    }
488
489    // ── SECTION 6: IMAGES ──
490    doc.add_paragraph("").page_break_before(true);
491    doc.add_paragraph("6. Images").style("Heading1");
492
493    doc.add_paragraph("6.1 Inline Image").style("Heading2");
494    doc.add_paragraph("A blue gradient image below:");
495    let img = create_sample_png(200, 50, [0, 80, 200]);
496    doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
497
498    doc.add_paragraph("");
499    doc.add_paragraph("6.2 Header Image").style("Heading2");
500    let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
501    doc.set_header_image(
502        &hdr_img,
503        "header_logo.png",
504        Length::inches(2.0),
505        Length::inches(0.2),
506    );
507    doc.add_paragraph("The header now contains an inline image (check the top of this page).");
508
509    doc.add_paragraph("");
510    doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
511
512    // ── SECTION 7: CONTENT MANIPULATION ──
513    doc.add_paragraph("").page_break_before(true);
514    doc.add_paragraph("7. Content Manipulation")
515        .style("Heading1");
516
517    doc.add_paragraph("7.1 Placeholder Replacement")
518        .style("Heading2");
519    doc.add_paragraph("Customer: {{customer}}");
520    doc.add_paragraph("Date: {{date}}");
521    doc.add_paragraph("Reference: {{ref_number}}");
522    {
523        let mut tbl = doc
524            .add_table(2, 2)
525            .borders(BorderStyle::Single, 4, "000000");
526        tbl.cell(0, 0).unwrap().set_text("Project");
527        tbl.cell(0, 0).unwrap().shading("D6E4F0");
528        tbl.cell(0, 1).unwrap().set_text("{{project}}");
529        tbl.cell(1, 0).unwrap().set_text("Status");
530        tbl.cell(1, 0).unwrap().shading("D6E4F0");
531        tbl.cell(1, 1).unwrap().set_text("{{status}}");
532    }
533
534    let mut replacements = HashMap::new();
535    replacements.insert("{{customer}}", "Tensorbee Inc.");
536    replacements.insert("{{date}}", "February 22, 2026");
537    replacements.insert("{{ref_number}}", "TB-2026-001");
538    replacements.insert("{{project}}", "Infrastructure Upgrade");
539    replacements.insert("{{status}}", "In Progress");
540    let n = doc.replace_all(&replacements);
541    doc.add_paragraph(&format!("({n} placeholders replaced above)"));
542
543    doc.add_paragraph("");
544
545    doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
546    doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
547    let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
548    doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
549
550    doc.add_paragraph("");
551
552    doc.add_paragraph("7.3 Content Insertion").style("Heading2");
553    doc.add_paragraph("Section A: First content.");
554    doc.add_paragraph("Section C: Third content.");
555    if let Some(idx) = doc.find_content_index("Section C") {
556        doc.insert_paragraph(
557            idx,
558            "Section B: Inserted between A and C via find_content_index().",
559        );
560    }
561
562    // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
563    doc.add_paragraph("").section_break(SectionBreak::NextPage);
564    doc.add_paragraph("8. Section Breaks & Orientation")
565        .style("Heading1");
566    doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
567
568    {
569        let mut tbl = doc
570            .add_table(3, 7)
571            .borders(BorderStyle::Single, 4, "2E75B6");
572        let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
573        for (c, h) in headers.iter().enumerate() {
574            tbl.cell(0, c).unwrap().set_text(h);
575            tbl.cell(0, c).unwrap().shading("2E75B6");
576        }
577        let data = [
578            [
579                "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
580            ],
581            [
582                "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
583            ],
584        ];
585        for (r, row) in data.iter().enumerate() {
586            for (c, v) in row.iter().enumerate() {
587                tbl.cell(r + 1, c).unwrap().set_text(v);
588            }
589        }
590    }
591
592    doc.add_paragraph("")
593        .section_break(SectionBreak::NextPage)
594        .section_landscape();
595
596    // ── SECTION 9: CUSTOM STYLES ──
597    doc.add_paragraph("9. Custom Styles").style("Heading1");
598    doc.add_style(
599        StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
600            .based_on("Normal")
601            .paragraph_properties(rdocx_oxml::properties::CT_PPr {
602                shading: Some(rdocx_oxml::properties::CT_Shd {
603                    val: "clear".to_string(),
604                    color: None,
605                    fill: Some("FFF2CC".to_string()),
606                }),
607                ..Default::default()
608            })
609            .run_properties(rdocx_oxml::properties::CT_RPr {
610                bold: Some(true),
611                color: Some("C45911".to_string()),
612                ..Default::default()
613            }),
614    );
615    doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
616        .style("CustomHighlight");
617
618    doc.add_paragraph("");
619
620    // ── SECTION 10: DOCUMENT INTELLIGENCE ──
621    doc.add_paragraph("10. Document Intelligence API")
622        .style("Heading1");
623
624    let wc = doc.word_count();
625    let hc = doc.headings().len();
626    let ic = doc.images().len();
627    let lc = doc.links().len();
628    doc.add_paragraph(&format!("Word count: {wc}"));
629    doc.add_paragraph(&format!("Heading count: {hc}"));
630    doc.add_paragraph(&format!("Image count: {ic}"));
631    doc.add_paragraph(&format!("Link count: {lc}"));
632
633    let outline = doc.document_outline();
634    doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
635
636    let issues = doc.audit_accessibility();
637    doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
638    for issue in &issues {
639        doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
640    }
641
642    // ── SECTION 11: DOCUMENT MERGING ──
643    doc.add_paragraph("");
644    doc.add_paragraph("11. Document Merging").style("Heading1");
645    {
646        let mut other = Document::new();
647        other.add_paragraph("This paragraph was merged from another document using append().");
648        doc.append(&other);
649    }
650
651    doc.add_paragraph("");
652
653    // ── SUMMARY ──
654    doc.add_paragraph("Summary of Demonstrated Features")
655        .style("Heading1");
656    let features = [
657        "Page setup: size, margins, header/footer distance, gutter",
658        "Document metadata: title, author, subject, keywords",
659        "Headers and footers: text, images, different first page",
660        "Background images (full-page behind text)",
661        "Table of Contents generation with bookmarks",
662        "Text formatting: bold, italic, underline styles, strike, color, size, font",
663        "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
664        "Paragraph formatting: alignment, borders, shading, spacing, indentation",
665        "Pagination controls: keep-with-next, keep-together, widow control",
666        "Bullet and numbered lists with nesting",
667        "Tab stops with dot/underscore/hyphen leaders",
668        "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
669        "Row properties: header rows, exact height, cant-split",
670        "Cell properties: width, no-wrap, vertical alignment",
671        "Inline images and header images",
672        "Placeholder replacement (single and batch)",
673        "Regex-based find and replace",
674        "Content insertion at specific positions",
675        "Section breaks with mixed portrait/landscape",
676        "Custom paragraph and character styles",
677        "Document intelligence: word count, headings, outline, images, links",
678        "Accessibility audit",
679        "Document merging (append)",
680        "Export: DOCX, PDF, HTML, Markdown",
681    ];
682    for f in &features {
683        doc.add_bullet_list_item(f, 0);
684    }
685    doc.add_paragraph("");
686    doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
687        .alignment(Alignment::Center)
688        .shading("E2EFDA")
689        .border_all(BorderStyle::Single, 2, "00AA00");
690
691    doc
692}

Trait Implementations§

Source§

impl Default for Document

Source§

fn default() -> Self

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> Finish for T

Source§

fn finish(self)

Does nothing but move self, equivalent to drop.
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<U, T> ToOwnedObj<U> for T
where U: FromObjRef<T>,

Source§

fn to_owned_obj(&self, data: FontData<'_>) -> U

Convert this type into T, using the provided data to resolve any offsets.
Source§

impl<U, T> ToOwnedTable<U> for T
where U: FromTableRef<T>,

Source§

fn to_owned_table(&self) -> U

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.