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_samples.rs (line 35)
34fn generate_feature_showcase(path: &Path) {
35    let mut doc = Document::new();
36
37    // =========================================================================
38    // PAGE SETUP & METADATA
39    // =========================================================================
40    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
41    doc.set_margins(
42        Length::inches(1.0), // top
43        Length::inches(1.0), // right
44        Length::inches(1.0), // bottom
45        Length::inches(1.0), // left
46    );
47    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
48    doc.set_gutter(Length::twips(0));
49
50    doc.set_title("rdocx Feature Showcase");
51    doc.set_author("rdocx Sample Generator");
52    doc.set_subject("Comprehensive feature demonstration");
53    doc.set_keywords("rdocx, docx, rust, sample");
54
55    // Header & Footer
56    doc.set_header("rdocx Feature Showcase");
57    doc.set_footer("Generated by rdocx — Page");
58
59    // Different first page header
60    doc.set_different_first_page(true);
61    doc.set_first_page_header("rdocx");
62    doc.set_first_page_footer("Feature Showcase — Cover Page");
63
64    // =========================================================================
65    // PAGE 1: COVER PAGE — background image, run formatting
66    // =========================================================================
67    let bg_cover = create_sample_png(612, 792, [30, 60, 120]);
68    doc.add_background_image(&bg_cover, "cover_bg.png");
69
70    doc.add_paragraph(""); // spacer
71    doc.add_paragraph(""); // spacer
72    doc.add_paragraph(""); // spacer
73
74    {
75        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
76        p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
77    }
78    {
79        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
80        p.add_run("Feature Showcase")
81            .size(28.0)
82            .color("FFFFFF")
83            .italic(true);
84    }
85
86    doc.add_paragraph(""); // spacer
87
88    {
89        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
90        p.add_run("A comprehensive demonstration of every feature")
91            .size(14.0)
92            .color("CCDDFF");
93    }
94    {
95        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
96        p.add_run("provided by the rdocx Rust crate for DOCX generation.")
97            .size(14.0)
98            .color("CCDDFF");
99    }
100
101    // =========================================================================
102    // PAGE 2: TEXT FORMATTING
103    // =========================================================================
104    doc.add_paragraph("").page_break_before(true);
105
106    doc.add_paragraph("1. Text Formatting").style("Heading1");
107
108    doc.add_paragraph("This section demonstrates paragraph and run-level formatting options.");
109    doc.add_paragraph("");
110
111    // --- Paragraph alignment ---
112    doc.add_paragraph("Paragraph Alignment").style("Heading2");
113
114    doc.add_paragraph("This paragraph is left-aligned (the default).")
115        .alignment(Alignment::Left);
116    doc.add_paragraph("This paragraph is center-aligned.")
117        .alignment(Alignment::Center);
118    doc.add_paragraph("This paragraph is right-aligned.")
119        .alignment(Alignment::Right);
120    doc.add_paragraph(
121        "This paragraph is justified. To demonstrate justified text properly, it needs \
122         to be long enough to span multiple lines so the word spacing adjustment is visible \
123         across the full width of the text area on the page.",
124    )
125    .alignment(Alignment::Justify);
126
127    doc.add_paragraph("");
128
129    // --- Run formatting ---
130    doc.add_paragraph("Run Formatting").style("Heading2");
131
132    {
133        let mut p = doc.add_paragraph("");
134        p.add_run("Bold text").bold(true);
135        p.add_run(" | ");
136        p.add_run("Italic text").italic(true);
137        p.add_run(" | ");
138        p.add_run("Bold + Italic").bold(true).italic(true);
139    }
140    {
141        let mut p = doc.add_paragraph("");
142        p.add_run("Single underline").underline(true);
143        p.add_run(" | ");
144        p.add_run("Strikethrough").strike(true);
145        p.add_run(" | ");
146        p.add_run("Double strikethrough").double_strike(true);
147    }
148    {
149        let mut p = doc.add_paragraph("");
150        p.add_run("Red text").color("FF0000");
151        p.add_run(" | ");
152        p.add_run("Blue text").color("0000FF");
153        p.add_run(" | ");
154        p.add_run("Green text").color("00AA00");
155        p.add_run(" | ");
156        p.add_run("Highlighted").highlight("FFFF00");
157    }
158    {
159        let mut p = doc.add_paragraph("");
160        p.add_run("8pt small").size(8.0);
161        p.add_run(" | ");
162        p.add_run("11pt normal").size(11.0);
163        p.add_run(" | ");
164        p.add_run("16pt large").size(16.0);
165        p.add_run(" | ");
166        p.add_run("24pt extra-large").size(24.0);
167    }
168    {
169        let mut p = doc.add_paragraph("");
170        p.add_run("Arial font").font("Arial");
171        p.add_run(" | ");
172        p.add_run("Times New Roman font").font("Times New Roman");
173        p.add_run(" | ");
174        p.add_run("Courier New font").font("Courier New");
175    }
176    {
177        let mut p = doc.add_paragraph("");
178        p.add_run("Normal");
179        p.add_run(" H").size(11.0);
180        p.add_run("2").subscript();
181        p.add_run("O (subscript)").size(11.0);
182        p.add_run(" | E = mc").size(11.0);
183        p.add_run("2").superscript();
184        p.add_run(" (superscript)").size(11.0);
185    }
186    {
187        let mut p = doc.add_paragraph("");
188        p.add_run("ALL CAPS").all_caps(true);
189        p.add_run(" | ");
190        p.add_run("Small Caps").small_caps(true);
191        p.add_run(" | ");
192        p.add_run("Expanded spacing")
193            .character_spacing(Length::twips(40));
194    }
195
196    doc.add_paragraph("");
197
198    // --- Paragraph formatting ---
199    doc.add_paragraph("Paragraph Formatting").style("Heading2");
200
201    doc.add_paragraph("Paragraph with shading (light green background)")
202        .shading("E2EFDA");
203
204    doc.add_paragraph("Paragraph with bottom border")
205        .border_bottom(BorderStyle::Single, 6, "2E75B6");
206
207    doc.add_paragraph("Paragraph with all borders")
208        .border_all(BorderStyle::Single, 4, "FF0000");
209
210    doc.add_paragraph("Paragraph with 1-inch left indent and hanging indent")
211        .indent_left(Length::inches(1.0))
212        .hanging_indent(Length::inches(0.5));
213
214    doc.add_paragraph("Paragraph with first-line indent of 0.5 inches")
215        .first_line_indent(Length::inches(0.5));
216
217    doc.add_paragraph("Paragraph with extra space before (24pt) and after (12pt)")
218        .space_before(Length::pt(24.0))
219        .space_after(Length::pt(12.0));
220
221    doc.add_paragraph(
222        "Paragraph with double line spacing. This text should have extra vertical \
223         space between lines to demonstrate the line_spacing_multiple setting.",
224    )
225    .line_spacing_multiple(2.0);
226
227    doc.add_paragraph("Paragraph with keep-with-next (won't break from the next paragraph)")
228        .keep_with_next(true);
229    doc.add_paragraph("(This stays with the paragraph above.)");
230
231    // =========================================================================
232    // PAGE 3: LISTS & TAB STOPS
233    // =========================================================================
234    doc.add_paragraph("").page_break_before(true);
235
236    doc.add_paragraph("2. Lists").style("Heading1");
237
238    doc.add_paragraph("Bullet List").style("Heading2");
239
240    doc.add_bullet_list_item("First bullet item", 0);
241    doc.add_bullet_list_item("Second bullet item", 0);
242    doc.add_bullet_list_item("Nested level 1", 1);
243    doc.add_bullet_list_item("Nested level 2", 2);
244    doc.add_bullet_list_item("Back to level 1", 1);
245    doc.add_bullet_list_item("Third bullet item", 0);
246
247    doc.add_paragraph("");
248
249    doc.add_paragraph("Numbered List").style("Heading2");
250
251    doc.add_numbered_list_item("First numbered item", 0);
252    doc.add_numbered_list_item("Second numbered item", 0);
253    doc.add_numbered_list_item("Sub-item A", 1);
254    doc.add_numbered_list_item("Sub-item B", 1);
255    doc.add_numbered_list_item("Third numbered item", 0);
256
257    doc.add_paragraph("");
258
259    // --- Tab stops ---
260    doc.add_paragraph("Tab Stops").style("Heading2");
261
262    doc.add_paragraph("Left\tCenter\tRight\tDecimal")
263        .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
264        .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
265        .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
266        .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
267
268    doc.add_paragraph("Item\t........\tPrice")
269        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
270        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
271        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
272
273    doc.add_paragraph("Widget A\t........\t$19.99")
274        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
275        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
276        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
277
278    doc.add_paragraph("Gadget B\t________\t$249.50")
279        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
280        .add_tab_stop_with_leader(
281            TabAlignment::Right,
282            Length::inches(4.0),
283            TabLeader::Underscore,
284        )
285        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
286
287    // =========================================================================
288    // PAGE 4: TABLES
289    // =========================================================================
290    doc.add_paragraph("").page_break_before(true);
291
292    doc.add_paragraph("3. Tables").style("Heading1");
293
294    // --- Basic table with borders ---
295    doc.add_paragraph("Basic Table with Borders")
296        .style("Heading2");
297
298    {
299        let mut tbl = doc.add_table(4, 3);
300        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
301
302        // Header row
303        for col in 0..3 {
304            tbl.cell(0, col).unwrap().shading("2E75B6");
305        }
306        tbl.cell(0, 0).unwrap().set_text("Name");
307        tbl.cell(0, 1).unwrap().set_text("Role");
308        tbl.cell(0, 2).unwrap().set_text("Location");
309
310        tbl.cell(1, 0).unwrap().set_text("Alice Johnson");
311        tbl.cell(1, 1).unwrap().set_text("Engineering Lead");
312        tbl.cell(1, 2).unwrap().set_text("New York");
313
314        tbl.cell(2, 0).unwrap().set_text("Bob Smith");
315        tbl.cell(2, 1).unwrap().set_text("Product Manager");
316        tbl.cell(2, 2).unwrap().set_text("San Francisco");
317
318        tbl.cell(3, 0).unwrap().set_text("Carol Davis");
319        tbl.cell(3, 1).unwrap().set_text("Designer");
320        tbl.cell(3, 2).unwrap().set_text("London");
321    }
322
323    doc.add_paragraph("");
324
325    // --- Table with cell merging ---
326    doc.add_paragraph("Table with Cell Merging & Vertical Alignment")
327        .style("Heading2");
328
329    {
330        let mut tbl = doc.add_table(4, 4);
331        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
332        tbl = tbl.width_pct(100.0);
333
334        // Header spanning all columns
335        tbl.cell(0, 0).unwrap().set_text("Quarterly Revenue Report");
336        tbl.cell(0, 0).unwrap().shading("1F4E79");
337        tbl.cell(0, 0).unwrap().grid_span(4);
338
339        // Sub-header
340        tbl.cell(1, 0).unwrap().set_text("Region");
341        tbl.cell(1, 0).unwrap().shading("D6E4F0");
342        tbl.cell(1, 1).unwrap().set_text("Q1");
343        tbl.cell(1, 1).unwrap().shading("D6E4F0");
344        tbl.cell(1, 2).unwrap().set_text("Q2");
345        tbl.cell(1, 2).unwrap().shading("D6E4F0");
346        tbl.cell(1, 3).unwrap().set_text("Total");
347        tbl.cell(1, 3).unwrap().shading("D6E4F0");
348
349        // Data
350        tbl.cell(2, 0).unwrap().set_text("North America");
351        tbl.cell(2, 1).unwrap().set_text("$2.4M");
352        tbl.cell(2, 2).unwrap().set_text("$2.7M");
353        tbl.cell(2, 3).unwrap().set_text("$5.1M");
354
355        tbl.cell(3, 0).unwrap().set_text("Europe");
356        tbl.cell(3, 1).unwrap().set_text("$1.8M");
357        tbl.cell(3, 2).unwrap().set_text("$2.0M");
358        tbl.cell(3, 3).unwrap().set_text("$3.8M");
359
360        // Vertical alignment on data cells
361        tbl.cell(2, 3)
362            .unwrap()
363            .vertical_alignment(VerticalAlignment::Center);
364        tbl.cell(3, 3)
365            .unwrap()
366            .vertical_alignment(VerticalAlignment::Bottom);
367    }
368
369    doc.add_paragraph("");
370
371    // --- Table with vertical merge ---
372    doc.add_paragraph("Table with Vertical Merge")
373        .style("Heading2");
374
375    {
376        let mut tbl = doc.add_table(4, 3);
377        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
378
379        tbl.cell(0, 0).unwrap().set_text("Category");
380        tbl.cell(0, 0).unwrap().shading("E2EFDA");
381        tbl.cell(0, 1).unwrap().set_text("Item");
382        tbl.cell(0, 1).unwrap().shading("E2EFDA");
383        tbl.cell(0, 2).unwrap().set_text("Price");
384        tbl.cell(0, 2).unwrap().shading("E2EFDA");
385
386        // "Hardware" spans rows 1-2
387        tbl.cell(1, 0).unwrap().set_text("Hardware");
388        tbl.cell(1, 0).unwrap().v_merge_restart();
389        tbl.cell(1, 1).unwrap().set_text("Laptop");
390        tbl.cell(1, 2).unwrap().set_text("$1,200");
391
392        tbl.cell(2, 0).unwrap().v_merge_continue();
393        tbl.cell(2, 1).unwrap().set_text("Monitor");
394        tbl.cell(2, 2).unwrap().set_text("$450");
395
396        // "Software" on row 3
397        tbl.cell(3, 0).unwrap().set_text("Software");
398        tbl.cell(3, 1).unwrap().set_text("IDE License");
399        tbl.cell(3, 2).unwrap().set_text("$200/yr");
400    }
401
402    doc.add_paragraph("");
403
404    // --- Nested table ---
405    doc.add_paragraph("Nested Table").style("Heading2");
406
407    {
408        let mut tbl = doc.add_table(2, 2);
409        tbl = tbl.borders(BorderStyle::Single, 6, "2E75B6");
410
411        tbl.cell(0, 0).unwrap().set_text("Outer Cell (0,0)");
412        tbl.cell(0, 1).unwrap().set_text("Outer Cell (0,1)");
413        tbl.cell(1, 0).unwrap().set_text("Outer Cell (1,0)");
414
415        // Nested table inside cell (1,1)
416        {
417            let mut cell = tbl.cell(1, 1).unwrap();
418            cell.set_text("Contains nested table:");
419            let mut nested = cell.add_table(2, 2);
420            nested = nested.borders(BorderStyle::Single, 2, "FF6600");
421            nested.cell(0, 0).unwrap().set_text("Inner A");
422            nested.cell(0, 1).unwrap().set_text("Inner B");
423            nested.cell(1, 0).unwrap().set_text("Inner C");
424            nested.cell(1, 1).unwrap().set_text("Inner D");
425        }
426    }
427
428    // =========================================================================
429    // PAGE 5: IMAGES
430    // =========================================================================
431    doc.add_paragraph("").page_break_before(true);
432
433    doc.add_paragraph("4. Images").style("Heading1");
434
435    doc.add_paragraph("Inline Image").style("Heading2");
436
437    doc.add_paragraph("Below is an inline image (200x50 pixels, blue gradient):");
438    let inline_img = create_sample_png(200, 50, [0, 80, 200]);
439    doc.add_picture(
440        &inline_img,
441        "inline_chart.png",
442        Length::inches(3.0),
443        Length::inches(0.75),
444    );
445
446    doc.add_paragraph("");
447
448    doc.add_paragraph("Header Image").style("Heading2");
449
450    // Replace the text-only header with an image header
451    let header_img = create_sample_png(400, 40, [40, 40, 40]);
452    doc.set_header_image(
453        &header_img,
454        "header_logo.png",
455        Length::inches(2.0),
456        Length::inches(0.2),
457    );
458
459    doc.add_paragraph(
460        "The document header has been replaced with an inline image. \
461         Check the header area at the top of this page.",
462    );
463
464    doc.add_paragraph("");
465    doc.add_paragraph(
466        "Note: The cover page uses a full-page background image behind the text, \
467         demonstrated on page 1 via add_background_image().",
468    );
469
470    // =========================================================================
471    // PAGE 6: CONTENT MANIPULATION — placeholder replacement, insertion
472    // =========================================================================
473    doc.add_paragraph("").page_break_before(true);
474
475    doc.add_paragraph("5. Content Manipulation")
476        .style("Heading1");
477
478    // --- Placeholder replacement ---
479    doc.add_paragraph("Placeholder Replacement")
480        .style("Heading2");
481
482    doc.add_paragraph(
483        "Before replacement, this document contained {{customer}} and {{date}} placeholders.",
484    );
485
486    {
487        let mut p = doc.add_paragraph("");
488        p.add_run("Customer: ").bold(true);
489        p.add_run("{{customer}}");
490    }
491    {
492        let mut p = doc.add_paragraph("");
493        p.add_run("Date: ").bold(true);
494        p.add_run("{{date}}");
495    }
496    doc.add_paragraph("Reference: {{ref_number}}");
497
498    // Table with placeholders
499    {
500        let mut tbl = doc.add_table(3, 2);
501        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
502        tbl.cell(0, 0).unwrap().set_text("Field");
503        tbl.cell(0, 0).unwrap().shading("D6E4F0");
504        tbl.cell(0, 1).unwrap().set_text("Value");
505        tbl.cell(0, 1).unwrap().shading("D6E4F0");
506        tbl.cell(1, 0).unwrap().set_text("Project");
507        tbl.cell(1, 1).unwrap().set_text("{{project}}");
508        tbl.cell(2, 0).unwrap().set_text("Status");
509        tbl.cell(2, 1).unwrap().set_text("{{status}}");
510    }
511
512    // Perform replacements
513    let mut replacements = HashMap::new();
514    replacements.insert("{{customer}}", "Acme Corporation");
515    replacements.insert("{{date}}", "February 22, 2026");
516    replacements.insert("{{ref_number}}", "REF-2026-001");
517    replacements.insert("{{project}}", "Infrastructure Upgrade");
518    replacements.insert("{{status}}", "In Progress");
519    let replace_count = doc.replace_all(&replacements);
520
521    doc.add_paragraph("");
522    doc.add_paragraph(&format!(
523        "(Replaced {} placeholders above — in body text and table cells)",
524        replace_count
525    ));
526
527    doc.add_paragraph("");
528
529    // --- Content insertion ---
530    doc.add_paragraph("Content Insertion").style("Heading2");
531
532    doc.add_paragraph("Section A: First section of content.");
533    doc.add_paragraph("Section C: Third section of content.");
534
535    // Insert "Section B" between A and C
536    if let Some(idx) = doc.find_content_index("Section C") {
537        doc.insert_paragraph(
538            idx,
539            "Section B: Inserted between A and C using find_content_index().",
540        );
541    }
542
543    doc.add_paragraph("");
544    doc.add_paragraph(
545        "The paragraph above ('Section B') was inserted at a specific position \
546         using find_content_index() + insert_paragraph().",
547    );
548
549    // =========================================================================
550    // PAGE 7: LANDSCAPE — section break, wide table
551    // =========================================================================
552    doc.add_paragraph("").section_break(SectionBreak::NextPage);
553
554    doc.add_paragraph("6. Mixed Page Orientation")
555        .style("Heading1");
556
557    doc.add_paragraph(
558        "This page is in LANDSCAPE orientation. It was created using a section break \
559         followed by section_landscape(). This is useful for wide tables or charts.",
560    );
561
562    doc.add_paragraph("");
563
564    // Wide table for landscape
565    {
566        let mut tbl = doc.add_table(4, 7);
567        tbl = tbl.borders(BorderStyle::Single, 4, "2E75B6");
568
569        let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
570        for (col, h) in headers.iter().enumerate() {
571            tbl.cell(0, col).unwrap().set_text(h);
572            tbl.cell(0, col).unwrap().shading("2E75B6");
573        }
574
575        let data = [
576            [
577                "North America",
578                "$1.2M",
579                "$1.3M",
580                "$1.4M",
581                "$1.5M",
582                "$1.6M",
583                "$7.0M",
584            ],
585            [
586                "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
587            ],
588            [
589                "Asia Pacific",
590                "$0.5M",
591                "$0.6M",
592                "$0.7M",
593                "$0.7M",
594                "$0.8M",
595                "$3.3M",
596            ],
597        ];
598        for (row_idx, row_data) in data.iter().enumerate() {
599            for (col, val) in row_data.iter().enumerate() {
600                tbl.cell(row_idx + 1, col).unwrap().set_text(val);
601            }
602        }
603    }
604
605    // End landscape, return to portrait
606    doc.add_paragraph("")
607        .section_break(SectionBreak::NextPage)
608        .section_landscape();
609
610    // =========================================================================
611    // PAGE 8: BACK TO PORTRAIT — styles, final notes
612    // =========================================================================
613    doc.add_paragraph("7. Custom Styles & Summary")
614        .style("Heading1");
615
616    doc.add_paragraph(
617        "This final page is back in portrait orientation after a section break. \
618         The document has demonstrated:",
619    );
620
621    doc.add_bullet_list_item(
622        "Page setup: size, margins, header/footer distance, gutter",
623        0,
624    );
625    doc.add_bullet_list_item("Document metadata: title, author, subject, keywords", 0);
626    doc.add_bullet_list_item("Headers and footers: text, images, different first page", 0);
627    doc.add_bullet_list_item("Background images: full-page behind text", 0);
628    doc.add_bullet_list_item(
629        "Text formatting: bold, italic, underline, strike, color, size, font",
630        0,
631    );
632    doc.add_bullet_list_item(
633        "Advanced run formatting: superscript, subscript, caps, spacing",
634        0,
635    );
636    doc.add_bullet_list_item(
637        "Paragraph formatting: alignment, borders, shading, spacing, indentation",
638        0,
639    );
640    doc.add_bullet_list_item("Bullet and numbered lists with nesting levels", 0);
641    doc.add_bullet_list_item("Tab stops with dot/underscore leaders", 0);
642    doc.add_bullet_list_item(
643        "Tables: borders, shading, column spans, row spans, nesting",
644        0,
645    );
646    doc.add_bullet_list_item("Vertical alignment in table cells", 0);
647    doc.add_bullet_list_item("Inline images", 0);
648    doc.add_bullet_list_item("Placeholder replacement in body and table cells", 0);
649    doc.add_bullet_list_item("Content insertion at specific positions", 0);
650    doc.add_bullet_list_item(
651        "Section breaks with mixed portrait/landscape orientation",
652        0,
653    );
654
655    doc.add_paragraph("");
656    doc.add_paragraph("All features above were built entirely from scratch using the rdocx API.")
657        .alignment(Alignment::Center)
658        .shading("E2EFDA")
659        .border_all(BorderStyle::Single, 2, "00AA00");
660
661    doc.save(path).unwrap();
662}
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 60)
58fn export_all(dir: &Path, name: &str, mut doc: Document) {
59    let docx_path = dir.join(format!("{name}.docx"));
60    doc.save(&docx_path).unwrap();
61    println!("  {name}.docx");
62
63    match doc.to_pdf() {
64        Ok(pdf) => {
65            let pdf_path = dir.join(format!("{name}.pdf"));
66            std::fs::write(&pdf_path, &pdf).unwrap();
67            println!("  {name}.pdf ({} bytes)", pdf.len());
68        }
69        Err(e) => println!("  {name}.pdf — skipped: {e}"),
70    }
71
72    let html = doc.to_html();
73    let html_path = dir.join(format!("{name}.html"));
74    std::fs::write(&html_path, &html).unwrap();
75    println!("  {name}.html ({} bytes)", html.len());
76
77    let md = doc.to_markdown();
78    let md_path = dir.join(format!("{name}.md"));
79    std::fs::write(&md_path, &md).unwrap();
80    println!("  {name}.md ({} bytes)", md.len());
81}
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}
examples/generate_samples.rs (line 661)
34fn generate_feature_showcase(path: &Path) {
35    let mut doc = Document::new();
36
37    // =========================================================================
38    // PAGE SETUP & METADATA
39    // =========================================================================
40    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
41    doc.set_margins(
42        Length::inches(1.0), // top
43        Length::inches(1.0), // right
44        Length::inches(1.0), // bottom
45        Length::inches(1.0), // left
46    );
47    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
48    doc.set_gutter(Length::twips(0));
49
50    doc.set_title("rdocx Feature Showcase");
51    doc.set_author("rdocx Sample Generator");
52    doc.set_subject("Comprehensive feature demonstration");
53    doc.set_keywords("rdocx, docx, rust, sample");
54
55    // Header & Footer
56    doc.set_header("rdocx Feature Showcase");
57    doc.set_footer("Generated by rdocx — Page");
58
59    // Different first page header
60    doc.set_different_first_page(true);
61    doc.set_first_page_header("rdocx");
62    doc.set_first_page_footer("Feature Showcase — Cover Page");
63
64    // =========================================================================
65    // PAGE 1: COVER PAGE — background image, run formatting
66    // =========================================================================
67    let bg_cover = create_sample_png(612, 792, [30, 60, 120]);
68    doc.add_background_image(&bg_cover, "cover_bg.png");
69
70    doc.add_paragraph(""); // spacer
71    doc.add_paragraph(""); // spacer
72    doc.add_paragraph(""); // spacer
73
74    {
75        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
76        p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
77    }
78    {
79        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
80        p.add_run("Feature Showcase")
81            .size(28.0)
82            .color("FFFFFF")
83            .italic(true);
84    }
85
86    doc.add_paragraph(""); // spacer
87
88    {
89        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
90        p.add_run("A comprehensive demonstration of every feature")
91            .size(14.0)
92            .color("CCDDFF");
93    }
94    {
95        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
96        p.add_run("provided by the rdocx Rust crate for DOCX generation.")
97            .size(14.0)
98            .color("CCDDFF");
99    }
100
101    // =========================================================================
102    // PAGE 2: TEXT FORMATTING
103    // =========================================================================
104    doc.add_paragraph("").page_break_before(true);
105
106    doc.add_paragraph("1. Text Formatting").style("Heading1");
107
108    doc.add_paragraph("This section demonstrates paragraph and run-level formatting options.");
109    doc.add_paragraph("");
110
111    // --- Paragraph alignment ---
112    doc.add_paragraph("Paragraph Alignment").style("Heading2");
113
114    doc.add_paragraph("This paragraph is left-aligned (the default).")
115        .alignment(Alignment::Left);
116    doc.add_paragraph("This paragraph is center-aligned.")
117        .alignment(Alignment::Center);
118    doc.add_paragraph("This paragraph is right-aligned.")
119        .alignment(Alignment::Right);
120    doc.add_paragraph(
121        "This paragraph is justified. To demonstrate justified text properly, it needs \
122         to be long enough to span multiple lines so the word spacing adjustment is visible \
123         across the full width of the text area on the page.",
124    )
125    .alignment(Alignment::Justify);
126
127    doc.add_paragraph("");
128
129    // --- Run formatting ---
130    doc.add_paragraph("Run Formatting").style("Heading2");
131
132    {
133        let mut p = doc.add_paragraph("");
134        p.add_run("Bold text").bold(true);
135        p.add_run(" | ");
136        p.add_run("Italic text").italic(true);
137        p.add_run(" | ");
138        p.add_run("Bold + Italic").bold(true).italic(true);
139    }
140    {
141        let mut p = doc.add_paragraph("");
142        p.add_run("Single underline").underline(true);
143        p.add_run(" | ");
144        p.add_run("Strikethrough").strike(true);
145        p.add_run(" | ");
146        p.add_run("Double strikethrough").double_strike(true);
147    }
148    {
149        let mut p = doc.add_paragraph("");
150        p.add_run("Red text").color("FF0000");
151        p.add_run(" | ");
152        p.add_run("Blue text").color("0000FF");
153        p.add_run(" | ");
154        p.add_run("Green text").color("00AA00");
155        p.add_run(" | ");
156        p.add_run("Highlighted").highlight("FFFF00");
157    }
158    {
159        let mut p = doc.add_paragraph("");
160        p.add_run("8pt small").size(8.0);
161        p.add_run(" | ");
162        p.add_run("11pt normal").size(11.0);
163        p.add_run(" | ");
164        p.add_run("16pt large").size(16.0);
165        p.add_run(" | ");
166        p.add_run("24pt extra-large").size(24.0);
167    }
168    {
169        let mut p = doc.add_paragraph("");
170        p.add_run("Arial font").font("Arial");
171        p.add_run(" | ");
172        p.add_run("Times New Roman font").font("Times New Roman");
173        p.add_run(" | ");
174        p.add_run("Courier New font").font("Courier New");
175    }
176    {
177        let mut p = doc.add_paragraph("");
178        p.add_run("Normal");
179        p.add_run(" H").size(11.0);
180        p.add_run("2").subscript();
181        p.add_run("O (subscript)").size(11.0);
182        p.add_run(" | E = mc").size(11.0);
183        p.add_run("2").superscript();
184        p.add_run(" (superscript)").size(11.0);
185    }
186    {
187        let mut p = doc.add_paragraph("");
188        p.add_run("ALL CAPS").all_caps(true);
189        p.add_run(" | ");
190        p.add_run("Small Caps").small_caps(true);
191        p.add_run(" | ");
192        p.add_run("Expanded spacing")
193            .character_spacing(Length::twips(40));
194    }
195
196    doc.add_paragraph("");
197
198    // --- Paragraph formatting ---
199    doc.add_paragraph("Paragraph Formatting").style("Heading2");
200
201    doc.add_paragraph("Paragraph with shading (light green background)")
202        .shading("E2EFDA");
203
204    doc.add_paragraph("Paragraph with bottom border")
205        .border_bottom(BorderStyle::Single, 6, "2E75B6");
206
207    doc.add_paragraph("Paragraph with all borders")
208        .border_all(BorderStyle::Single, 4, "FF0000");
209
210    doc.add_paragraph("Paragraph with 1-inch left indent and hanging indent")
211        .indent_left(Length::inches(1.0))
212        .hanging_indent(Length::inches(0.5));
213
214    doc.add_paragraph("Paragraph with first-line indent of 0.5 inches")
215        .first_line_indent(Length::inches(0.5));
216
217    doc.add_paragraph("Paragraph with extra space before (24pt) and after (12pt)")
218        .space_before(Length::pt(24.0))
219        .space_after(Length::pt(12.0));
220
221    doc.add_paragraph(
222        "Paragraph with double line spacing. This text should have extra vertical \
223         space between lines to demonstrate the line_spacing_multiple setting.",
224    )
225    .line_spacing_multiple(2.0);
226
227    doc.add_paragraph("Paragraph with keep-with-next (won't break from the next paragraph)")
228        .keep_with_next(true);
229    doc.add_paragraph("(This stays with the paragraph above.)");
230
231    // =========================================================================
232    // PAGE 3: LISTS & TAB STOPS
233    // =========================================================================
234    doc.add_paragraph("").page_break_before(true);
235
236    doc.add_paragraph("2. Lists").style("Heading1");
237
238    doc.add_paragraph("Bullet List").style("Heading2");
239
240    doc.add_bullet_list_item("First bullet item", 0);
241    doc.add_bullet_list_item("Second bullet item", 0);
242    doc.add_bullet_list_item("Nested level 1", 1);
243    doc.add_bullet_list_item("Nested level 2", 2);
244    doc.add_bullet_list_item("Back to level 1", 1);
245    doc.add_bullet_list_item("Third bullet item", 0);
246
247    doc.add_paragraph("");
248
249    doc.add_paragraph("Numbered List").style("Heading2");
250
251    doc.add_numbered_list_item("First numbered item", 0);
252    doc.add_numbered_list_item("Second numbered item", 0);
253    doc.add_numbered_list_item("Sub-item A", 1);
254    doc.add_numbered_list_item("Sub-item B", 1);
255    doc.add_numbered_list_item("Third numbered item", 0);
256
257    doc.add_paragraph("");
258
259    // --- Tab stops ---
260    doc.add_paragraph("Tab Stops").style("Heading2");
261
262    doc.add_paragraph("Left\tCenter\tRight\tDecimal")
263        .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
264        .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
265        .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
266        .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
267
268    doc.add_paragraph("Item\t........\tPrice")
269        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
270        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
271        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
272
273    doc.add_paragraph("Widget A\t........\t$19.99")
274        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
275        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
276        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
277
278    doc.add_paragraph("Gadget B\t________\t$249.50")
279        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
280        .add_tab_stop_with_leader(
281            TabAlignment::Right,
282            Length::inches(4.0),
283            TabLeader::Underscore,
284        )
285        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
286
287    // =========================================================================
288    // PAGE 4: TABLES
289    // =========================================================================
290    doc.add_paragraph("").page_break_before(true);
291
292    doc.add_paragraph("3. Tables").style("Heading1");
293
294    // --- Basic table with borders ---
295    doc.add_paragraph("Basic Table with Borders")
296        .style("Heading2");
297
298    {
299        let mut tbl = doc.add_table(4, 3);
300        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
301
302        // Header row
303        for col in 0..3 {
304            tbl.cell(0, col).unwrap().shading("2E75B6");
305        }
306        tbl.cell(0, 0).unwrap().set_text("Name");
307        tbl.cell(0, 1).unwrap().set_text("Role");
308        tbl.cell(0, 2).unwrap().set_text("Location");
309
310        tbl.cell(1, 0).unwrap().set_text("Alice Johnson");
311        tbl.cell(1, 1).unwrap().set_text("Engineering Lead");
312        tbl.cell(1, 2).unwrap().set_text("New York");
313
314        tbl.cell(2, 0).unwrap().set_text("Bob Smith");
315        tbl.cell(2, 1).unwrap().set_text("Product Manager");
316        tbl.cell(2, 2).unwrap().set_text("San Francisco");
317
318        tbl.cell(3, 0).unwrap().set_text("Carol Davis");
319        tbl.cell(3, 1).unwrap().set_text("Designer");
320        tbl.cell(3, 2).unwrap().set_text("London");
321    }
322
323    doc.add_paragraph("");
324
325    // --- Table with cell merging ---
326    doc.add_paragraph("Table with Cell Merging & Vertical Alignment")
327        .style("Heading2");
328
329    {
330        let mut tbl = doc.add_table(4, 4);
331        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
332        tbl = tbl.width_pct(100.0);
333
334        // Header spanning all columns
335        tbl.cell(0, 0).unwrap().set_text("Quarterly Revenue Report");
336        tbl.cell(0, 0).unwrap().shading("1F4E79");
337        tbl.cell(0, 0).unwrap().grid_span(4);
338
339        // Sub-header
340        tbl.cell(1, 0).unwrap().set_text("Region");
341        tbl.cell(1, 0).unwrap().shading("D6E4F0");
342        tbl.cell(1, 1).unwrap().set_text("Q1");
343        tbl.cell(1, 1).unwrap().shading("D6E4F0");
344        tbl.cell(1, 2).unwrap().set_text("Q2");
345        tbl.cell(1, 2).unwrap().shading("D6E4F0");
346        tbl.cell(1, 3).unwrap().set_text("Total");
347        tbl.cell(1, 3).unwrap().shading("D6E4F0");
348
349        // Data
350        tbl.cell(2, 0).unwrap().set_text("North America");
351        tbl.cell(2, 1).unwrap().set_text("$2.4M");
352        tbl.cell(2, 2).unwrap().set_text("$2.7M");
353        tbl.cell(2, 3).unwrap().set_text("$5.1M");
354
355        tbl.cell(3, 0).unwrap().set_text("Europe");
356        tbl.cell(3, 1).unwrap().set_text("$1.8M");
357        tbl.cell(3, 2).unwrap().set_text("$2.0M");
358        tbl.cell(3, 3).unwrap().set_text("$3.8M");
359
360        // Vertical alignment on data cells
361        tbl.cell(2, 3)
362            .unwrap()
363            .vertical_alignment(VerticalAlignment::Center);
364        tbl.cell(3, 3)
365            .unwrap()
366            .vertical_alignment(VerticalAlignment::Bottom);
367    }
368
369    doc.add_paragraph("");
370
371    // --- Table with vertical merge ---
372    doc.add_paragraph("Table with Vertical Merge")
373        .style("Heading2");
374
375    {
376        let mut tbl = doc.add_table(4, 3);
377        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
378
379        tbl.cell(0, 0).unwrap().set_text("Category");
380        tbl.cell(0, 0).unwrap().shading("E2EFDA");
381        tbl.cell(0, 1).unwrap().set_text("Item");
382        tbl.cell(0, 1).unwrap().shading("E2EFDA");
383        tbl.cell(0, 2).unwrap().set_text("Price");
384        tbl.cell(0, 2).unwrap().shading("E2EFDA");
385
386        // "Hardware" spans rows 1-2
387        tbl.cell(1, 0).unwrap().set_text("Hardware");
388        tbl.cell(1, 0).unwrap().v_merge_restart();
389        tbl.cell(1, 1).unwrap().set_text("Laptop");
390        tbl.cell(1, 2).unwrap().set_text("$1,200");
391
392        tbl.cell(2, 0).unwrap().v_merge_continue();
393        tbl.cell(2, 1).unwrap().set_text("Monitor");
394        tbl.cell(2, 2).unwrap().set_text("$450");
395
396        // "Software" on row 3
397        tbl.cell(3, 0).unwrap().set_text("Software");
398        tbl.cell(3, 1).unwrap().set_text("IDE License");
399        tbl.cell(3, 2).unwrap().set_text("$200/yr");
400    }
401
402    doc.add_paragraph("");
403
404    // --- Nested table ---
405    doc.add_paragraph("Nested Table").style("Heading2");
406
407    {
408        let mut tbl = doc.add_table(2, 2);
409        tbl = tbl.borders(BorderStyle::Single, 6, "2E75B6");
410
411        tbl.cell(0, 0).unwrap().set_text("Outer Cell (0,0)");
412        tbl.cell(0, 1).unwrap().set_text("Outer Cell (0,1)");
413        tbl.cell(1, 0).unwrap().set_text("Outer Cell (1,0)");
414
415        // Nested table inside cell (1,1)
416        {
417            let mut cell = tbl.cell(1, 1).unwrap();
418            cell.set_text("Contains nested table:");
419            let mut nested = cell.add_table(2, 2);
420            nested = nested.borders(BorderStyle::Single, 2, "FF6600");
421            nested.cell(0, 0).unwrap().set_text("Inner A");
422            nested.cell(0, 1).unwrap().set_text("Inner B");
423            nested.cell(1, 0).unwrap().set_text("Inner C");
424            nested.cell(1, 1).unwrap().set_text("Inner D");
425        }
426    }
427
428    // =========================================================================
429    // PAGE 5: IMAGES
430    // =========================================================================
431    doc.add_paragraph("").page_break_before(true);
432
433    doc.add_paragraph("4. Images").style("Heading1");
434
435    doc.add_paragraph("Inline Image").style("Heading2");
436
437    doc.add_paragraph("Below is an inline image (200x50 pixels, blue gradient):");
438    let inline_img = create_sample_png(200, 50, [0, 80, 200]);
439    doc.add_picture(
440        &inline_img,
441        "inline_chart.png",
442        Length::inches(3.0),
443        Length::inches(0.75),
444    );
445
446    doc.add_paragraph("");
447
448    doc.add_paragraph("Header Image").style("Heading2");
449
450    // Replace the text-only header with an image header
451    let header_img = create_sample_png(400, 40, [40, 40, 40]);
452    doc.set_header_image(
453        &header_img,
454        "header_logo.png",
455        Length::inches(2.0),
456        Length::inches(0.2),
457    );
458
459    doc.add_paragraph(
460        "The document header has been replaced with an inline image. \
461         Check the header area at the top of this page.",
462    );
463
464    doc.add_paragraph("");
465    doc.add_paragraph(
466        "Note: The cover page uses a full-page background image behind the text, \
467         demonstrated on page 1 via add_background_image().",
468    );
469
470    // =========================================================================
471    // PAGE 6: CONTENT MANIPULATION — placeholder replacement, insertion
472    // =========================================================================
473    doc.add_paragraph("").page_break_before(true);
474
475    doc.add_paragraph("5. Content Manipulation")
476        .style("Heading1");
477
478    // --- Placeholder replacement ---
479    doc.add_paragraph("Placeholder Replacement")
480        .style("Heading2");
481
482    doc.add_paragraph(
483        "Before replacement, this document contained {{customer}} and {{date}} placeholders.",
484    );
485
486    {
487        let mut p = doc.add_paragraph("");
488        p.add_run("Customer: ").bold(true);
489        p.add_run("{{customer}}");
490    }
491    {
492        let mut p = doc.add_paragraph("");
493        p.add_run("Date: ").bold(true);
494        p.add_run("{{date}}");
495    }
496    doc.add_paragraph("Reference: {{ref_number}}");
497
498    // Table with placeholders
499    {
500        let mut tbl = doc.add_table(3, 2);
501        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
502        tbl.cell(0, 0).unwrap().set_text("Field");
503        tbl.cell(0, 0).unwrap().shading("D6E4F0");
504        tbl.cell(0, 1).unwrap().set_text("Value");
505        tbl.cell(0, 1).unwrap().shading("D6E4F0");
506        tbl.cell(1, 0).unwrap().set_text("Project");
507        tbl.cell(1, 1).unwrap().set_text("{{project}}");
508        tbl.cell(2, 0).unwrap().set_text("Status");
509        tbl.cell(2, 1).unwrap().set_text("{{status}}");
510    }
511
512    // Perform replacements
513    let mut replacements = HashMap::new();
514    replacements.insert("{{customer}}", "Acme Corporation");
515    replacements.insert("{{date}}", "February 22, 2026");
516    replacements.insert("{{ref_number}}", "REF-2026-001");
517    replacements.insert("{{project}}", "Infrastructure Upgrade");
518    replacements.insert("{{status}}", "In Progress");
519    let replace_count = doc.replace_all(&replacements);
520
521    doc.add_paragraph("");
522    doc.add_paragraph(&format!(
523        "(Replaced {} placeholders above — in body text and table cells)",
524        replace_count
525    ));
526
527    doc.add_paragraph("");
528
529    // --- Content insertion ---
530    doc.add_paragraph("Content Insertion").style("Heading2");
531
532    doc.add_paragraph("Section A: First section of content.");
533    doc.add_paragraph("Section C: Third section of content.");
534
535    // Insert "Section B" between A and C
536    if let Some(idx) = doc.find_content_index("Section C") {
537        doc.insert_paragraph(
538            idx,
539            "Section B: Inserted between A and C using find_content_index().",
540        );
541    }
542
543    doc.add_paragraph("");
544    doc.add_paragraph(
545        "The paragraph above ('Section B') was inserted at a specific position \
546         using find_content_index() + insert_paragraph().",
547    );
548
549    // =========================================================================
550    // PAGE 7: LANDSCAPE — section break, wide table
551    // =========================================================================
552    doc.add_paragraph("").section_break(SectionBreak::NextPage);
553
554    doc.add_paragraph("6. Mixed Page Orientation")
555        .style("Heading1");
556
557    doc.add_paragraph(
558        "This page is in LANDSCAPE orientation. It was created using a section break \
559         followed by section_landscape(). This is useful for wide tables or charts.",
560    );
561
562    doc.add_paragraph("");
563
564    // Wide table for landscape
565    {
566        let mut tbl = doc.add_table(4, 7);
567        tbl = tbl.borders(BorderStyle::Single, 4, "2E75B6");
568
569        let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
570        for (col, h) in headers.iter().enumerate() {
571            tbl.cell(0, col).unwrap().set_text(h);
572            tbl.cell(0, col).unwrap().shading("2E75B6");
573        }
574
575        let data = [
576            [
577                "North America",
578                "$1.2M",
579                "$1.3M",
580                "$1.4M",
581                "$1.5M",
582                "$1.6M",
583                "$7.0M",
584            ],
585            [
586                "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
587            ],
588            [
589                "Asia Pacific",
590                "$0.5M",
591                "$0.6M",
592                "$0.7M",
593                "$0.7M",
594                "$0.8M",
595                "$3.3M",
596            ],
597        ];
598        for (row_idx, row_data) in data.iter().enumerate() {
599            for (col, val) in row_data.iter().enumerate() {
600                tbl.cell(row_idx + 1, col).unwrap().set_text(val);
601            }
602        }
603    }
604
605    // End landscape, return to portrait
606    doc.add_paragraph("")
607        .section_break(SectionBreak::NextPage)
608        .section_landscape();
609
610    // =========================================================================
611    // PAGE 8: BACK TO PORTRAIT — styles, final notes
612    // =========================================================================
613    doc.add_paragraph("7. Custom Styles & Summary")
614        .style("Heading1");
615
616    doc.add_paragraph(
617        "This final page is back in portrait orientation after a section break. \
618         The document has demonstrated:",
619    );
620
621    doc.add_bullet_list_item(
622        "Page setup: size, margins, header/footer distance, gutter",
623        0,
624    );
625    doc.add_bullet_list_item("Document metadata: title, author, subject, keywords", 0);
626    doc.add_bullet_list_item("Headers and footers: text, images, different first page", 0);
627    doc.add_bullet_list_item("Background images: full-page behind text", 0);
628    doc.add_bullet_list_item(
629        "Text formatting: bold, italic, underline, strike, color, size, font",
630        0,
631    );
632    doc.add_bullet_list_item(
633        "Advanced run formatting: superscript, subscript, caps, spacing",
634        0,
635    );
636    doc.add_bullet_list_item(
637        "Paragraph formatting: alignment, borders, shading, spacing, indentation",
638        0,
639    );
640    doc.add_bullet_list_item("Bullet and numbered lists with nesting levels", 0);
641    doc.add_bullet_list_item("Tab stops with dot/underscore leaders", 0);
642    doc.add_bullet_list_item(
643        "Tables: borders, shading, column spans, row spans, nesting",
644        0,
645    );
646    doc.add_bullet_list_item("Vertical alignment in table cells", 0);
647    doc.add_bullet_list_item("Inline images", 0);
648    doc.add_bullet_list_item("Placeholder replacement in body and table cells", 0);
649    doc.add_bullet_list_item("Content insertion at specific positions", 0);
650    doc.add_bullet_list_item(
651        "Section breaks with mixed portrait/landscape orientation",
652        0,
653    );
654
655    doc.add_paragraph("");
656    doc.add_paragraph("All features above were built entirely from scratch using the rdocx API.")
657        .alignment(Alignment::Center)
658        .shading("E2EFDA")
659        .border_all(BorderStyle::Single, 2, "00AA00");
660
661    doc.save(path).unwrap();
662}
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 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_samples.rs (line 70)
34fn generate_feature_showcase(path: &Path) {
35    let mut doc = Document::new();
36
37    // =========================================================================
38    // PAGE SETUP & METADATA
39    // =========================================================================
40    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
41    doc.set_margins(
42        Length::inches(1.0), // top
43        Length::inches(1.0), // right
44        Length::inches(1.0), // bottom
45        Length::inches(1.0), // left
46    );
47    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
48    doc.set_gutter(Length::twips(0));
49
50    doc.set_title("rdocx Feature Showcase");
51    doc.set_author("rdocx Sample Generator");
52    doc.set_subject("Comprehensive feature demonstration");
53    doc.set_keywords("rdocx, docx, rust, sample");
54
55    // Header & Footer
56    doc.set_header("rdocx Feature Showcase");
57    doc.set_footer("Generated by rdocx — Page");
58
59    // Different first page header
60    doc.set_different_first_page(true);
61    doc.set_first_page_header("rdocx");
62    doc.set_first_page_footer("Feature Showcase — Cover Page");
63
64    // =========================================================================
65    // PAGE 1: COVER PAGE — background image, run formatting
66    // =========================================================================
67    let bg_cover = create_sample_png(612, 792, [30, 60, 120]);
68    doc.add_background_image(&bg_cover, "cover_bg.png");
69
70    doc.add_paragraph(""); // spacer
71    doc.add_paragraph(""); // spacer
72    doc.add_paragraph(""); // spacer
73
74    {
75        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
76        p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
77    }
78    {
79        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
80        p.add_run("Feature Showcase")
81            .size(28.0)
82            .color("FFFFFF")
83            .italic(true);
84    }
85
86    doc.add_paragraph(""); // spacer
87
88    {
89        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
90        p.add_run("A comprehensive demonstration of every feature")
91            .size(14.0)
92            .color("CCDDFF");
93    }
94    {
95        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
96        p.add_run("provided by the rdocx Rust crate for DOCX generation.")
97            .size(14.0)
98            .color("CCDDFF");
99    }
100
101    // =========================================================================
102    // PAGE 2: TEXT FORMATTING
103    // =========================================================================
104    doc.add_paragraph("").page_break_before(true);
105
106    doc.add_paragraph("1. Text Formatting").style("Heading1");
107
108    doc.add_paragraph("This section demonstrates paragraph and run-level formatting options.");
109    doc.add_paragraph("");
110
111    // --- Paragraph alignment ---
112    doc.add_paragraph("Paragraph Alignment").style("Heading2");
113
114    doc.add_paragraph("This paragraph is left-aligned (the default).")
115        .alignment(Alignment::Left);
116    doc.add_paragraph("This paragraph is center-aligned.")
117        .alignment(Alignment::Center);
118    doc.add_paragraph("This paragraph is right-aligned.")
119        .alignment(Alignment::Right);
120    doc.add_paragraph(
121        "This paragraph is justified. To demonstrate justified text properly, it needs \
122         to be long enough to span multiple lines so the word spacing adjustment is visible \
123         across the full width of the text area on the page.",
124    )
125    .alignment(Alignment::Justify);
126
127    doc.add_paragraph("");
128
129    // --- Run formatting ---
130    doc.add_paragraph("Run Formatting").style("Heading2");
131
132    {
133        let mut p = doc.add_paragraph("");
134        p.add_run("Bold text").bold(true);
135        p.add_run(" | ");
136        p.add_run("Italic text").italic(true);
137        p.add_run(" | ");
138        p.add_run("Bold + Italic").bold(true).italic(true);
139    }
140    {
141        let mut p = doc.add_paragraph("");
142        p.add_run("Single underline").underline(true);
143        p.add_run(" | ");
144        p.add_run("Strikethrough").strike(true);
145        p.add_run(" | ");
146        p.add_run("Double strikethrough").double_strike(true);
147    }
148    {
149        let mut p = doc.add_paragraph("");
150        p.add_run("Red text").color("FF0000");
151        p.add_run(" | ");
152        p.add_run("Blue text").color("0000FF");
153        p.add_run(" | ");
154        p.add_run("Green text").color("00AA00");
155        p.add_run(" | ");
156        p.add_run("Highlighted").highlight("FFFF00");
157    }
158    {
159        let mut p = doc.add_paragraph("");
160        p.add_run("8pt small").size(8.0);
161        p.add_run(" | ");
162        p.add_run("11pt normal").size(11.0);
163        p.add_run(" | ");
164        p.add_run("16pt large").size(16.0);
165        p.add_run(" | ");
166        p.add_run("24pt extra-large").size(24.0);
167    }
168    {
169        let mut p = doc.add_paragraph("");
170        p.add_run("Arial font").font("Arial");
171        p.add_run(" | ");
172        p.add_run("Times New Roman font").font("Times New Roman");
173        p.add_run(" | ");
174        p.add_run("Courier New font").font("Courier New");
175    }
176    {
177        let mut p = doc.add_paragraph("");
178        p.add_run("Normal");
179        p.add_run(" H").size(11.0);
180        p.add_run("2").subscript();
181        p.add_run("O (subscript)").size(11.0);
182        p.add_run(" | E = mc").size(11.0);
183        p.add_run("2").superscript();
184        p.add_run(" (superscript)").size(11.0);
185    }
186    {
187        let mut p = doc.add_paragraph("");
188        p.add_run("ALL CAPS").all_caps(true);
189        p.add_run(" | ");
190        p.add_run("Small Caps").small_caps(true);
191        p.add_run(" | ");
192        p.add_run("Expanded spacing")
193            .character_spacing(Length::twips(40));
194    }
195
196    doc.add_paragraph("");
197
198    // --- Paragraph formatting ---
199    doc.add_paragraph("Paragraph Formatting").style("Heading2");
200
201    doc.add_paragraph("Paragraph with shading (light green background)")
202        .shading("E2EFDA");
203
204    doc.add_paragraph("Paragraph with bottom border")
205        .border_bottom(BorderStyle::Single, 6, "2E75B6");
206
207    doc.add_paragraph("Paragraph with all borders")
208        .border_all(BorderStyle::Single, 4, "FF0000");
209
210    doc.add_paragraph("Paragraph with 1-inch left indent and hanging indent")
211        .indent_left(Length::inches(1.0))
212        .hanging_indent(Length::inches(0.5));
213
214    doc.add_paragraph("Paragraph with first-line indent of 0.5 inches")
215        .first_line_indent(Length::inches(0.5));
216
217    doc.add_paragraph("Paragraph with extra space before (24pt) and after (12pt)")
218        .space_before(Length::pt(24.0))
219        .space_after(Length::pt(12.0));
220
221    doc.add_paragraph(
222        "Paragraph with double line spacing. This text should have extra vertical \
223         space between lines to demonstrate the line_spacing_multiple setting.",
224    )
225    .line_spacing_multiple(2.0);
226
227    doc.add_paragraph("Paragraph with keep-with-next (won't break from the next paragraph)")
228        .keep_with_next(true);
229    doc.add_paragraph("(This stays with the paragraph above.)");
230
231    // =========================================================================
232    // PAGE 3: LISTS & TAB STOPS
233    // =========================================================================
234    doc.add_paragraph("").page_break_before(true);
235
236    doc.add_paragraph("2. Lists").style("Heading1");
237
238    doc.add_paragraph("Bullet List").style("Heading2");
239
240    doc.add_bullet_list_item("First bullet item", 0);
241    doc.add_bullet_list_item("Second bullet item", 0);
242    doc.add_bullet_list_item("Nested level 1", 1);
243    doc.add_bullet_list_item("Nested level 2", 2);
244    doc.add_bullet_list_item("Back to level 1", 1);
245    doc.add_bullet_list_item("Third bullet item", 0);
246
247    doc.add_paragraph("");
248
249    doc.add_paragraph("Numbered List").style("Heading2");
250
251    doc.add_numbered_list_item("First numbered item", 0);
252    doc.add_numbered_list_item("Second numbered item", 0);
253    doc.add_numbered_list_item("Sub-item A", 1);
254    doc.add_numbered_list_item("Sub-item B", 1);
255    doc.add_numbered_list_item("Third numbered item", 0);
256
257    doc.add_paragraph("");
258
259    // --- Tab stops ---
260    doc.add_paragraph("Tab Stops").style("Heading2");
261
262    doc.add_paragraph("Left\tCenter\tRight\tDecimal")
263        .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
264        .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
265        .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
266        .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
267
268    doc.add_paragraph("Item\t........\tPrice")
269        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
270        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
271        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
272
273    doc.add_paragraph("Widget A\t........\t$19.99")
274        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
275        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
276        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
277
278    doc.add_paragraph("Gadget B\t________\t$249.50")
279        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
280        .add_tab_stop_with_leader(
281            TabAlignment::Right,
282            Length::inches(4.0),
283            TabLeader::Underscore,
284        )
285        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
286
287    // =========================================================================
288    // PAGE 4: TABLES
289    // =========================================================================
290    doc.add_paragraph("").page_break_before(true);
291
292    doc.add_paragraph("3. Tables").style("Heading1");
293
294    // --- Basic table with borders ---
295    doc.add_paragraph("Basic Table with Borders")
296        .style("Heading2");
297
298    {
299        let mut tbl = doc.add_table(4, 3);
300        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
301
302        // Header row
303        for col in 0..3 {
304            tbl.cell(0, col).unwrap().shading("2E75B6");
305        }
306        tbl.cell(0, 0).unwrap().set_text("Name");
307        tbl.cell(0, 1).unwrap().set_text("Role");
308        tbl.cell(0, 2).unwrap().set_text("Location");
309
310        tbl.cell(1, 0).unwrap().set_text("Alice Johnson");
311        tbl.cell(1, 1).unwrap().set_text("Engineering Lead");
312        tbl.cell(1, 2).unwrap().set_text("New York");
313
314        tbl.cell(2, 0).unwrap().set_text("Bob Smith");
315        tbl.cell(2, 1).unwrap().set_text("Product Manager");
316        tbl.cell(2, 2).unwrap().set_text("San Francisco");
317
318        tbl.cell(3, 0).unwrap().set_text("Carol Davis");
319        tbl.cell(3, 1).unwrap().set_text("Designer");
320        tbl.cell(3, 2).unwrap().set_text("London");
321    }
322
323    doc.add_paragraph("");
324
325    // --- Table with cell merging ---
326    doc.add_paragraph("Table with Cell Merging & Vertical Alignment")
327        .style("Heading2");
328
329    {
330        let mut tbl = doc.add_table(4, 4);
331        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
332        tbl = tbl.width_pct(100.0);
333
334        // Header spanning all columns
335        tbl.cell(0, 0).unwrap().set_text("Quarterly Revenue Report");
336        tbl.cell(0, 0).unwrap().shading("1F4E79");
337        tbl.cell(0, 0).unwrap().grid_span(4);
338
339        // Sub-header
340        tbl.cell(1, 0).unwrap().set_text("Region");
341        tbl.cell(1, 0).unwrap().shading("D6E4F0");
342        tbl.cell(1, 1).unwrap().set_text("Q1");
343        tbl.cell(1, 1).unwrap().shading("D6E4F0");
344        tbl.cell(1, 2).unwrap().set_text("Q2");
345        tbl.cell(1, 2).unwrap().shading("D6E4F0");
346        tbl.cell(1, 3).unwrap().set_text("Total");
347        tbl.cell(1, 3).unwrap().shading("D6E4F0");
348
349        // Data
350        tbl.cell(2, 0).unwrap().set_text("North America");
351        tbl.cell(2, 1).unwrap().set_text("$2.4M");
352        tbl.cell(2, 2).unwrap().set_text("$2.7M");
353        tbl.cell(2, 3).unwrap().set_text("$5.1M");
354
355        tbl.cell(3, 0).unwrap().set_text("Europe");
356        tbl.cell(3, 1).unwrap().set_text("$1.8M");
357        tbl.cell(3, 2).unwrap().set_text("$2.0M");
358        tbl.cell(3, 3).unwrap().set_text("$3.8M");
359
360        // Vertical alignment on data cells
361        tbl.cell(2, 3)
362            .unwrap()
363            .vertical_alignment(VerticalAlignment::Center);
364        tbl.cell(3, 3)
365            .unwrap()
366            .vertical_alignment(VerticalAlignment::Bottom);
367    }
368
369    doc.add_paragraph("");
370
371    // --- Table with vertical merge ---
372    doc.add_paragraph("Table with Vertical Merge")
373        .style("Heading2");
374
375    {
376        let mut tbl = doc.add_table(4, 3);
377        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
378
379        tbl.cell(0, 0).unwrap().set_text("Category");
380        tbl.cell(0, 0).unwrap().shading("E2EFDA");
381        tbl.cell(0, 1).unwrap().set_text("Item");
382        tbl.cell(0, 1).unwrap().shading("E2EFDA");
383        tbl.cell(0, 2).unwrap().set_text("Price");
384        tbl.cell(0, 2).unwrap().shading("E2EFDA");
385
386        // "Hardware" spans rows 1-2
387        tbl.cell(1, 0).unwrap().set_text("Hardware");
388        tbl.cell(1, 0).unwrap().v_merge_restart();
389        tbl.cell(1, 1).unwrap().set_text("Laptop");
390        tbl.cell(1, 2).unwrap().set_text("$1,200");
391
392        tbl.cell(2, 0).unwrap().v_merge_continue();
393        tbl.cell(2, 1).unwrap().set_text("Monitor");
394        tbl.cell(2, 2).unwrap().set_text("$450");
395
396        // "Software" on row 3
397        tbl.cell(3, 0).unwrap().set_text("Software");
398        tbl.cell(3, 1).unwrap().set_text("IDE License");
399        tbl.cell(3, 2).unwrap().set_text("$200/yr");
400    }
401
402    doc.add_paragraph("");
403
404    // --- Nested table ---
405    doc.add_paragraph("Nested Table").style("Heading2");
406
407    {
408        let mut tbl = doc.add_table(2, 2);
409        tbl = tbl.borders(BorderStyle::Single, 6, "2E75B6");
410
411        tbl.cell(0, 0).unwrap().set_text("Outer Cell (0,0)");
412        tbl.cell(0, 1).unwrap().set_text("Outer Cell (0,1)");
413        tbl.cell(1, 0).unwrap().set_text("Outer Cell (1,0)");
414
415        // Nested table inside cell (1,1)
416        {
417            let mut cell = tbl.cell(1, 1).unwrap();
418            cell.set_text("Contains nested table:");
419            let mut nested = cell.add_table(2, 2);
420            nested = nested.borders(BorderStyle::Single, 2, "FF6600");
421            nested.cell(0, 0).unwrap().set_text("Inner A");
422            nested.cell(0, 1).unwrap().set_text("Inner B");
423            nested.cell(1, 0).unwrap().set_text("Inner C");
424            nested.cell(1, 1).unwrap().set_text("Inner D");
425        }
426    }
427
428    // =========================================================================
429    // PAGE 5: IMAGES
430    // =========================================================================
431    doc.add_paragraph("").page_break_before(true);
432
433    doc.add_paragraph("4. Images").style("Heading1");
434
435    doc.add_paragraph("Inline Image").style("Heading2");
436
437    doc.add_paragraph("Below is an inline image (200x50 pixels, blue gradient):");
438    let inline_img = create_sample_png(200, 50, [0, 80, 200]);
439    doc.add_picture(
440        &inline_img,
441        "inline_chart.png",
442        Length::inches(3.0),
443        Length::inches(0.75),
444    );
445
446    doc.add_paragraph("");
447
448    doc.add_paragraph("Header Image").style("Heading2");
449
450    // Replace the text-only header with an image header
451    let header_img = create_sample_png(400, 40, [40, 40, 40]);
452    doc.set_header_image(
453        &header_img,
454        "header_logo.png",
455        Length::inches(2.0),
456        Length::inches(0.2),
457    );
458
459    doc.add_paragraph(
460        "The document header has been replaced with an inline image. \
461         Check the header area at the top of this page.",
462    );
463
464    doc.add_paragraph("");
465    doc.add_paragraph(
466        "Note: The cover page uses a full-page background image behind the text, \
467         demonstrated on page 1 via add_background_image().",
468    );
469
470    // =========================================================================
471    // PAGE 6: CONTENT MANIPULATION — placeholder replacement, insertion
472    // =========================================================================
473    doc.add_paragraph("").page_break_before(true);
474
475    doc.add_paragraph("5. Content Manipulation")
476        .style("Heading1");
477
478    // --- Placeholder replacement ---
479    doc.add_paragraph("Placeholder Replacement")
480        .style("Heading2");
481
482    doc.add_paragraph(
483        "Before replacement, this document contained {{customer}} and {{date}} placeholders.",
484    );
485
486    {
487        let mut p = doc.add_paragraph("");
488        p.add_run("Customer: ").bold(true);
489        p.add_run("{{customer}}");
490    }
491    {
492        let mut p = doc.add_paragraph("");
493        p.add_run("Date: ").bold(true);
494        p.add_run("{{date}}");
495    }
496    doc.add_paragraph("Reference: {{ref_number}}");
497
498    // Table with placeholders
499    {
500        let mut tbl = doc.add_table(3, 2);
501        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
502        tbl.cell(0, 0).unwrap().set_text("Field");
503        tbl.cell(0, 0).unwrap().shading("D6E4F0");
504        tbl.cell(0, 1).unwrap().set_text("Value");
505        tbl.cell(0, 1).unwrap().shading("D6E4F0");
506        tbl.cell(1, 0).unwrap().set_text("Project");
507        tbl.cell(1, 1).unwrap().set_text("{{project}}");
508        tbl.cell(2, 0).unwrap().set_text("Status");
509        tbl.cell(2, 1).unwrap().set_text("{{status}}");
510    }
511
512    // Perform replacements
513    let mut replacements = HashMap::new();
514    replacements.insert("{{customer}}", "Acme Corporation");
515    replacements.insert("{{date}}", "February 22, 2026");
516    replacements.insert("{{ref_number}}", "REF-2026-001");
517    replacements.insert("{{project}}", "Infrastructure Upgrade");
518    replacements.insert("{{status}}", "In Progress");
519    let replace_count = doc.replace_all(&replacements);
520
521    doc.add_paragraph("");
522    doc.add_paragraph(&format!(
523        "(Replaced {} placeholders above — in body text and table cells)",
524        replace_count
525    ));
526
527    doc.add_paragraph("");
528
529    // --- Content insertion ---
530    doc.add_paragraph("Content Insertion").style("Heading2");
531
532    doc.add_paragraph("Section A: First section of content.");
533    doc.add_paragraph("Section C: Third section of content.");
534
535    // Insert "Section B" between A and C
536    if let Some(idx) = doc.find_content_index("Section C") {
537        doc.insert_paragraph(
538            idx,
539            "Section B: Inserted between A and C using find_content_index().",
540        );
541    }
542
543    doc.add_paragraph("");
544    doc.add_paragraph(
545        "The paragraph above ('Section B') was inserted at a specific position \
546         using find_content_index() + insert_paragraph().",
547    );
548
549    // =========================================================================
550    // PAGE 7: LANDSCAPE — section break, wide table
551    // =========================================================================
552    doc.add_paragraph("").section_break(SectionBreak::NextPage);
553
554    doc.add_paragraph("6. Mixed Page Orientation")
555        .style("Heading1");
556
557    doc.add_paragraph(
558        "This page is in LANDSCAPE orientation. It was created using a section break \
559         followed by section_landscape(). This is useful for wide tables or charts.",
560    );
561
562    doc.add_paragraph("");
563
564    // Wide table for landscape
565    {
566        let mut tbl = doc.add_table(4, 7);
567        tbl = tbl.borders(BorderStyle::Single, 4, "2E75B6");
568
569        let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
570        for (col, h) in headers.iter().enumerate() {
571            tbl.cell(0, col).unwrap().set_text(h);
572            tbl.cell(0, col).unwrap().shading("2E75B6");
573        }
574
575        let data = [
576            [
577                "North America",
578                "$1.2M",
579                "$1.3M",
580                "$1.4M",
581                "$1.5M",
582                "$1.6M",
583                "$7.0M",
584            ],
585            [
586                "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
587            ],
588            [
589                "Asia Pacific",
590                "$0.5M",
591                "$0.6M",
592                "$0.7M",
593                "$0.7M",
594                "$0.8M",
595                "$3.3M",
596            ],
597        ];
598        for (row_idx, row_data) in data.iter().enumerate() {
599            for (col, val) in row_data.iter().enumerate() {
600                tbl.cell(row_idx + 1, col).unwrap().set_text(val);
601            }
602        }
603    }
604
605    // End landscape, return to portrait
606    doc.add_paragraph("")
607        .section_break(SectionBreak::NextPage)
608        .section_landscape();
609
610    // =========================================================================
611    // PAGE 8: BACK TO PORTRAIT — styles, final notes
612    // =========================================================================
613    doc.add_paragraph("7. Custom Styles & Summary")
614        .style("Heading1");
615
616    doc.add_paragraph(
617        "This final page is back in portrait orientation after a section break. \
618         The document has demonstrated:",
619    );
620
621    doc.add_bullet_list_item(
622        "Page setup: size, margins, header/footer distance, gutter",
623        0,
624    );
625    doc.add_bullet_list_item("Document metadata: title, author, subject, keywords", 0);
626    doc.add_bullet_list_item("Headers and footers: text, images, different first page", 0);
627    doc.add_bullet_list_item("Background images: full-page behind text", 0);
628    doc.add_bullet_list_item(
629        "Text formatting: bold, italic, underline, strike, color, size, font",
630        0,
631    );
632    doc.add_bullet_list_item(
633        "Advanced run formatting: superscript, subscript, caps, spacing",
634        0,
635    );
636    doc.add_bullet_list_item(
637        "Paragraph formatting: alignment, borders, shading, spacing, indentation",
638        0,
639    );
640    doc.add_bullet_list_item("Bullet and numbered lists with nesting levels", 0);
641    doc.add_bullet_list_item("Tab stops with dot/underscore leaders", 0);
642    doc.add_bullet_list_item(
643        "Tables: borders, shading, column spans, row spans, nesting",
644        0,
645    );
646    doc.add_bullet_list_item("Vertical alignment in table cells", 0);
647    doc.add_bullet_list_item("Inline images", 0);
648    doc.add_bullet_list_item("Placeholder replacement in body and table cells", 0);
649    doc.add_bullet_list_item("Content insertion at specific positions", 0);
650    doc.add_bullet_list_item(
651        "Section breaks with mixed portrait/landscape orientation",
652        0,
653    );
654
655    doc.add_paragraph("");
656    doc.add_paragraph("All features above were built entirely from scratch using the rdocx API.")
657        .alignment(Alignment::Center)
658        .shading("E2EFDA")
659        .border_all(BorderStyle::Single, 2, "00AA00");
660
661    doc.save(path).unwrap();
662}
Source

pub fn paragraph_count(&self) -> usize

Get the number of paragraphs.

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

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

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

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

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

Set the first-page header text.

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

Set the first-page footer text.

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

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

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_samples.rs (line 240)
34fn generate_feature_showcase(path: &Path) {
35    let mut doc = Document::new();
36
37    // =========================================================================
38    // PAGE SETUP & METADATA
39    // =========================================================================
40    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
41    doc.set_margins(
42        Length::inches(1.0), // top
43        Length::inches(1.0), // right
44        Length::inches(1.0), // bottom
45        Length::inches(1.0), // left
46    );
47    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
48    doc.set_gutter(Length::twips(0));
49
50    doc.set_title("rdocx Feature Showcase");
51    doc.set_author("rdocx Sample Generator");
52    doc.set_subject("Comprehensive feature demonstration");
53    doc.set_keywords("rdocx, docx, rust, sample");
54
55    // Header & Footer
56    doc.set_header("rdocx Feature Showcase");
57    doc.set_footer("Generated by rdocx — Page");
58
59    // Different first page header
60    doc.set_different_first_page(true);
61    doc.set_first_page_header("rdocx");
62    doc.set_first_page_footer("Feature Showcase — Cover Page");
63
64    // =========================================================================
65    // PAGE 1: COVER PAGE — background image, run formatting
66    // =========================================================================
67    let bg_cover = create_sample_png(612, 792, [30, 60, 120]);
68    doc.add_background_image(&bg_cover, "cover_bg.png");
69
70    doc.add_paragraph(""); // spacer
71    doc.add_paragraph(""); // spacer
72    doc.add_paragraph(""); // spacer
73
74    {
75        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
76        p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
77    }
78    {
79        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
80        p.add_run("Feature Showcase")
81            .size(28.0)
82            .color("FFFFFF")
83            .italic(true);
84    }
85
86    doc.add_paragraph(""); // spacer
87
88    {
89        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
90        p.add_run("A comprehensive demonstration of every feature")
91            .size(14.0)
92            .color("CCDDFF");
93    }
94    {
95        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
96        p.add_run("provided by the rdocx Rust crate for DOCX generation.")
97            .size(14.0)
98            .color("CCDDFF");
99    }
100
101    // =========================================================================
102    // PAGE 2: TEXT FORMATTING
103    // =========================================================================
104    doc.add_paragraph("").page_break_before(true);
105
106    doc.add_paragraph("1. Text Formatting").style("Heading1");
107
108    doc.add_paragraph("This section demonstrates paragraph and run-level formatting options.");
109    doc.add_paragraph("");
110
111    // --- Paragraph alignment ---
112    doc.add_paragraph("Paragraph Alignment").style("Heading2");
113
114    doc.add_paragraph("This paragraph is left-aligned (the default).")
115        .alignment(Alignment::Left);
116    doc.add_paragraph("This paragraph is center-aligned.")
117        .alignment(Alignment::Center);
118    doc.add_paragraph("This paragraph is right-aligned.")
119        .alignment(Alignment::Right);
120    doc.add_paragraph(
121        "This paragraph is justified. To demonstrate justified text properly, it needs \
122         to be long enough to span multiple lines so the word spacing adjustment is visible \
123         across the full width of the text area on the page.",
124    )
125    .alignment(Alignment::Justify);
126
127    doc.add_paragraph("");
128
129    // --- Run formatting ---
130    doc.add_paragraph("Run Formatting").style("Heading2");
131
132    {
133        let mut p = doc.add_paragraph("");
134        p.add_run("Bold text").bold(true);
135        p.add_run(" | ");
136        p.add_run("Italic text").italic(true);
137        p.add_run(" | ");
138        p.add_run("Bold + Italic").bold(true).italic(true);
139    }
140    {
141        let mut p = doc.add_paragraph("");
142        p.add_run("Single underline").underline(true);
143        p.add_run(" | ");
144        p.add_run("Strikethrough").strike(true);
145        p.add_run(" | ");
146        p.add_run("Double strikethrough").double_strike(true);
147    }
148    {
149        let mut p = doc.add_paragraph("");
150        p.add_run("Red text").color("FF0000");
151        p.add_run(" | ");
152        p.add_run("Blue text").color("0000FF");
153        p.add_run(" | ");
154        p.add_run("Green text").color("00AA00");
155        p.add_run(" | ");
156        p.add_run("Highlighted").highlight("FFFF00");
157    }
158    {
159        let mut p = doc.add_paragraph("");
160        p.add_run("8pt small").size(8.0);
161        p.add_run(" | ");
162        p.add_run("11pt normal").size(11.0);
163        p.add_run(" | ");
164        p.add_run("16pt large").size(16.0);
165        p.add_run(" | ");
166        p.add_run("24pt extra-large").size(24.0);
167    }
168    {
169        let mut p = doc.add_paragraph("");
170        p.add_run("Arial font").font("Arial");
171        p.add_run(" | ");
172        p.add_run("Times New Roman font").font("Times New Roman");
173        p.add_run(" | ");
174        p.add_run("Courier New font").font("Courier New");
175    }
176    {
177        let mut p = doc.add_paragraph("");
178        p.add_run("Normal");
179        p.add_run(" H").size(11.0);
180        p.add_run("2").subscript();
181        p.add_run("O (subscript)").size(11.0);
182        p.add_run(" | E = mc").size(11.0);
183        p.add_run("2").superscript();
184        p.add_run(" (superscript)").size(11.0);
185    }
186    {
187        let mut p = doc.add_paragraph("");
188        p.add_run("ALL CAPS").all_caps(true);
189        p.add_run(" | ");
190        p.add_run("Small Caps").small_caps(true);
191        p.add_run(" | ");
192        p.add_run("Expanded spacing")
193            .character_spacing(Length::twips(40));
194    }
195
196    doc.add_paragraph("");
197
198    // --- Paragraph formatting ---
199    doc.add_paragraph("Paragraph Formatting").style("Heading2");
200
201    doc.add_paragraph("Paragraph with shading (light green background)")
202        .shading("E2EFDA");
203
204    doc.add_paragraph("Paragraph with bottom border")
205        .border_bottom(BorderStyle::Single, 6, "2E75B6");
206
207    doc.add_paragraph("Paragraph with all borders")
208        .border_all(BorderStyle::Single, 4, "FF0000");
209
210    doc.add_paragraph("Paragraph with 1-inch left indent and hanging indent")
211        .indent_left(Length::inches(1.0))
212        .hanging_indent(Length::inches(0.5));
213
214    doc.add_paragraph("Paragraph with first-line indent of 0.5 inches")
215        .first_line_indent(Length::inches(0.5));
216
217    doc.add_paragraph("Paragraph with extra space before (24pt) and after (12pt)")
218        .space_before(Length::pt(24.0))
219        .space_after(Length::pt(12.0));
220
221    doc.add_paragraph(
222        "Paragraph with double line spacing. This text should have extra vertical \
223         space between lines to demonstrate the line_spacing_multiple setting.",
224    )
225    .line_spacing_multiple(2.0);
226
227    doc.add_paragraph("Paragraph with keep-with-next (won't break from the next paragraph)")
228        .keep_with_next(true);
229    doc.add_paragraph("(This stays with the paragraph above.)");
230
231    // =========================================================================
232    // PAGE 3: LISTS & TAB STOPS
233    // =========================================================================
234    doc.add_paragraph("").page_break_before(true);
235
236    doc.add_paragraph("2. Lists").style("Heading1");
237
238    doc.add_paragraph("Bullet List").style("Heading2");
239
240    doc.add_bullet_list_item("First bullet item", 0);
241    doc.add_bullet_list_item("Second bullet item", 0);
242    doc.add_bullet_list_item("Nested level 1", 1);
243    doc.add_bullet_list_item("Nested level 2", 2);
244    doc.add_bullet_list_item("Back to level 1", 1);
245    doc.add_bullet_list_item("Third bullet item", 0);
246
247    doc.add_paragraph("");
248
249    doc.add_paragraph("Numbered List").style("Heading2");
250
251    doc.add_numbered_list_item("First numbered item", 0);
252    doc.add_numbered_list_item("Second numbered item", 0);
253    doc.add_numbered_list_item("Sub-item A", 1);
254    doc.add_numbered_list_item("Sub-item B", 1);
255    doc.add_numbered_list_item("Third numbered item", 0);
256
257    doc.add_paragraph("");
258
259    // --- Tab stops ---
260    doc.add_paragraph("Tab Stops").style("Heading2");
261
262    doc.add_paragraph("Left\tCenter\tRight\tDecimal")
263        .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
264        .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
265        .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
266        .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
267
268    doc.add_paragraph("Item\t........\tPrice")
269        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
270        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
271        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
272
273    doc.add_paragraph("Widget A\t........\t$19.99")
274        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
275        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
276        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
277
278    doc.add_paragraph("Gadget B\t________\t$249.50")
279        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
280        .add_tab_stop_with_leader(
281            TabAlignment::Right,
282            Length::inches(4.0),
283            TabLeader::Underscore,
284        )
285        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
286
287    // =========================================================================
288    // PAGE 4: TABLES
289    // =========================================================================
290    doc.add_paragraph("").page_break_before(true);
291
292    doc.add_paragraph("3. Tables").style("Heading1");
293
294    // --- Basic table with borders ---
295    doc.add_paragraph("Basic Table with Borders")
296        .style("Heading2");
297
298    {
299        let mut tbl = doc.add_table(4, 3);
300        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
301
302        // Header row
303        for col in 0..3 {
304            tbl.cell(0, col).unwrap().shading("2E75B6");
305        }
306        tbl.cell(0, 0).unwrap().set_text("Name");
307        tbl.cell(0, 1).unwrap().set_text("Role");
308        tbl.cell(0, 2).unwrap().set_text("Location");
309
310        tbl.cell(1, 0).unwrap().set_text("Alice Johnson");
311        tbl.cell(1, 1).unwrap().set_text("Engineering Lead");
312        tbl.cell(1, 2).unwrap().set_text("New York");
313
314        tbl.cell(2, 0).unwrap().set_text("Bob Smith");
315        tbl.cell(2, 1).unwrap().set_text("Product Manager");
316        tbl.cell(2, 2).unwrap().set_text("San Francisco");
317
318        tbl.cell(3, 0).unwrap().set_text("Carol Davis");
319        tbl.cell(3, 1).unwrap().set_text("Designer");
320        tbl.cell(3, 2).unwrap().set_text("London");
321    }
322
323    doc.add_paragraph("");
324
325    // --- Table with cell merging ---
326    doc.add_paragraph("Table with Cell Merging & Vertical Alignment")
327        .style("Heading2");
328
329    {
330        let mut tbl = doc.add_table(4, 4);
331        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
332        tbl = tbl.width_pct(100.0);
333
334        // Header spanning all columns
335        tbl.cell(0, 0).unwrap().set_text("Quarterly Revenue Report");
336        tbl.cell(0, 0).unwrap().shading("1F4E79");
337        tbl.cell(0, 0).unwrap().grid_span(4);
338
339        // Sub-header
340        tbl.cell(1, 0).unwrap().set_text("Region");
341        tbl.cell(1, 0).unwrap().shading("D6E4F0");
342        tbl.cell(1, 1).unwrap().set_text("Q1");
343        tbl.cell(1, 1).unwrap().shading("D6E4F0");
344        tbl.cell(1, 2).unwrap().set_text("Q2");
345        tbl.cell(1, 2).unwrap().shading("D6E4F0");
346        tbl.cell(1, 3).unwrap().set_text("Total");
347        tbl.cell(1, 3).unwrap().shading("D6E4F0");
348
349        // Data
350        tbl.cell(2, 0).unwrap().set_text("North America");
351        tbl.cell(2, 1).unwrap().set_text("$2.4M");
352        tbl.cell(2, 2).unwrap().set_text("$2.7M");
353        tbl.cell(2, 3).unwrap().set_text("$5.1M");
354
355        tbl.cell(3, 0).unwrap().set_text("Europe");
356        tbl.cell(3, 1).unwrap().set_text("$1.8M");
357        tbl.cell(3, 2).unwrap().set_text("$2.0M");
358        tbl.cell(3, 3).unwrap().set_text("$3.8M");
359
360        // Vertical alignment on data cells
361        tbl.cell(2, 3)
362            .unwrap()
363            .vertical_alignment(VerticalAlignment::Center);
364        tbl.cell(3, 3)
365            .unwrap()
366            .vertical_alignment(VerticalAlignment::Bottom);
367    }
368
369    doc.add_paragraph("");
370
371    // --- Table with vertical merge ---
372    doc.add_paragraph("Table with Vertical Merge")
373        .style("Heading2");
374
375    {
376        let mut tbl = doc.add_table(4, 3);
377        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
378
379        tbl.cell(0, 0).unwrap().set_text("Category");
380        tbl.cell(0, 0).unwrap().shading("E2EFDA");
381        tbl.cell(0, 1).unwrap().set_text("Item");
382        tbl.cell(0, 1).unwrap().shading("E2EFDA");
383        tbl.cell(0, 2).unwrap().set_text("Price");
384        tbl.cell(0, 2).unwrap().shading("E2EFDA");
385
386        // "Hardware" spans rows 1-2
387        tbl.cell(1, 0).unwrap().set_text("Hardware");
388        tbl.cell(1, 0).unwrap().v_merge_restart();
389        tbl.cell(1, 1).unwrap().set_text("Laptop");
390        tbl.cell(1, 2).unwrap().set_text("$1,200");
391
392        tbl.cell(2, 0).unwrap().v_merge_continue();
393        tbl.cell(2, 1).unwrap().set_text("Monitor");
394        tbl.cell(2, 2).unwrap().set_text("$450");
395
396        // "Software" on row 3
397        tbl.cell(3, 0).unwrap().set_text("Software");
398        tbl.cell(3, 1).unwrap().set_text("IDE License");
399        tbl.cell(3, 2).unwrap().set_text("$200/yr");
400    }
401
402    doc.add_paragraph("");
403
404    // --- Nested table ---
405    doc.add_paragraph("Nested Table").style("Heading2");
406
407    {
408        let mut tbl = doc.add_table(2, 2);
409        tbl = tbl.borders(BorderStyle::Single, 6, "2E75B6");
410
411        tbl.cell(0, 0).unwrap().set_text("Outer Cell (0,0)");
412        tbl.cell(0, 1).unwrap().set_text("Outer Cell (0,1)");
413        tbl.cell(1, 0).unwrap().set_text("Outer Cell (1,0)");
414
415        // Nested table inside cell (1,1)
416        {
417            let mut cell = tbl.cell(1, 1).unwrap();
418            cell.set_text("Contains nested table:");
419            let mut nested = cell.add_table(2, 2);
420            nested = nested.borders(BorderStyle::Single, 2, "FF6600");
421            nested.cell(0, 0).unwrap().set_text("Inner A");
422            nested.cell(0, 1).unwrap().set_text("Inner B");
423            nested.cell(1, 0).unwrap().set_text("Inner C");
424            nested.cell(1, 1).unwrap().set_text("Inner D");
425        }
426    }
427
428    // =========================================================================
429    // PAGE 5: IMAGES
430    // =========================================================================
431    doc.add_paragraph("").page_break_before(true);
432
433    doc.add_paragraph("4. Images").style("Heading1");
434
435    doc.add_paragraph("Inline Image").style("Heading2");
436
437    doc.add_paragraph("Below is an inline image (200x50 pixels, blue gradient):");
438    let inline_img = create_sample_png(200, 50, [0, 80, 200]);
439    doc.add_picture(
440        &inline_img,
441        "inline_chart.png",
442        Length::inches(3.0),
443        Length::inches(0.75),
444    );
445
446    doc.add_paragraph("");
447
448    doc.add_paragraph("Header Image").style("Heading2");
449
450    // Replace the text-only header with an image header
451    let header_img = create_sample_png(400, 40, [40, 40, 40]);
452    doc.set_header_image(
453        &header_img,
454        "header_logo.png",
455        Length::inches(2.0),
456        Length::inches(0.2),
457    );
458
459    doc.add_paragraph(
460        "The document header has been replaced with an inline image. \
461         Check the header area at the top of this page.",
462    );
463
464    doc.add_paragraph("");
465    doc.add_paragraph(
466        "Note: The cover page uses a full-page background image behind the text, \
467         demonstrated on page 1 via add_background_image().",
468    );
469
470    // =========================================================================
471    // PAGE 6: CONTENT MANIPULATION — placeholder replacement, insertion
472    // =========================================================================
473    doc.add_paragraph("").page_break_before(true);
474
475    doc.add_paragraph("5. Content Manipulation")
476        .style("Heading1");
477
478    // --- Placeholder replacement ---
479    doc.add_paragraph("Placeholder Replacement")
480        .style("Heading2");
481
482    doc.add_paragraph(
483        "Before replacement, this document contained {{customer}} and {{date}} placeholders.",
484    );
485
486    {
487        let mut p = doc.add_paragraph("");
488        p.add_run("Customer: ").bold(true);
489        p.add_run("{{customer}}");
490    }
491    {
492        let mut p = doc.add_paragraph("");
493        p.add_run("Date: ").bold(true);
494        p.add_run("{{date}}");
495    }
496    doc.add_paragraph("Reference: {{ref_number}}");
497
498    // Table with placeholders
499    {
500        let mut tbl = doc.add_table(3, 2);
501        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
502        tbl.cell(0, 0).unwrap().set_text("Field");
503        tbl.cell(0, 0).unwrap().shading("D6E4F0");
504        tbl.cell(0, 1).unwrap().set_text("Value");
505        tbl.cell(0, 1).unwrap().shading("D6E4F0");
506        tbl.cell(1, 0).unwrap().set_text("Project");
507        tbl.cell(1, 1).unwrap().set_text("{{project}}");
508        tbl.cell(2, 0).unwrap().set_text("Status");
509        tbl.cell(2, 1).unwrap().set_text("{{status}}");
510    }
511
512    // Perform replacements
513    let mut replacements = HashMap::new();
514    replacements.insert("{{customer}}", "Acme Corporation");
515    replacements.insert("{{date}}", "February 22, 2026");
516    replacements.insert("{{ref_number}}", "REF-2026-001");
517    replacements.insert("{{project}}", "Infrastructure Upgrade");
518    replacements.insert("{{status}}", "In Progress");
519    let replace_count = doc.replace_all(&replacements);
520
521    doc.add_paragraph("");
522    doc.add_paragraph(&format!(
523        "(Replaced {} placeholders above — in body text and table cells)",
524        replace_count
525    ));
526
527    doc.add_paragraph("");
528
529    // --- Content insertion ---
530    doc.add_paragraph("Content Insertion").style("Heading2");
531
532    doc.add_paragraph("Section A: First section of content.");
533    doc.add_paragraph("Section C: Third section of content.");
534
535    // Insert "Section B" between A and C
536    if let Some(idx) = doc.find_content_index("Section C") {
537        doc.insert_paragraph(
538            idx,
539            "Section B: Inserted between A and C using find_content_index().",
540        );
541    }
542
543    doc.add_paragraph("");
544    doc.add_paragraph(
545        "The paragraph above ('Section B') was inserted at a specific position \
546         using find_content_index() + insert_paragraph().",
547    );
548
549    // =========================================================================
550    // PAGE 7: LANDSCAPE — section break, wide table
551    // =========================================================================
552    doc.add_paragraph("").section_break(SectionBreak::NextPage);
553
554    doc.add_paragraph("6. Mixed Page Orientation")
555        .style("Heading1");
556
557    doc.add_paragraph(
558        "This page is in LANDSCAPE orientation. It was created using a section break \
559         followed by section_landscape(). This is useful for wide tables or charts.",
560    );
561
562    doc.add_paragraph("");
563
564    // Wide table for landscape
565    {
566        let mut tbl = doc.add_table(4, 7);
567        tbl = tbl.borders(BorderStyle::Single, 4, "2E75B6");
568
569        let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
570        for (col, h) in headers.iter().enumerate() {
571            tbl.cell(0, col).unwrap().set_text(h);
572            tbl.cell(0, col).unwrap().shading("2E75B6");
573        }
574
575        let data = [
576            [
577                "North America",
578                "$1.2M",
579                "$1.3M",
580                "$1.4M",
581                "$1.5M",
582                "$1.6M",
583                "$7.0M",
584            ],
585            [
586                "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
587            ],
588            [
589                "Asia Pacific",
590                "$0.5M",
591                "$0.6M",
592                "$0.7M",
593                "$0.7M",
594                "$0.8M",
595                "$3.3M",
596            ],
597        ];
598        for (row_idx, row_data) in data.iter().enumerate() {
599            for (col, val) in row_data.iter().enumerate() {
600                tbl.cell(row_idx + 1, col).unwrap().set_text(val);
601            }
602        }
603    }
604
605    // End landscape, return to portrait
606    doc.add_paragraph("")
607        .section_break(SectionBreak::NextPage)
608        .section_landscape();
609
610    // =========================================================================
611    // PAGE 8: BACK TO PORTRAIT — styles, final notes
612    // =========================================================================
613    doc.add_paragraph("7. Custom Styles & Summary")
614        .style("Heading1");
615
616    doc.add_paragraph(
617        "This final page is back in portrait orientation after a section break. \
618         The document has demonstrated:",
619    );
620
621    doc.add_bullet_list_item(
622        "Page setup: size, margins, header/footer distance, gutter",
623        0,
624    );
625    doc.add_bullet_list_item("Document metadata: title, author, subject, keywords", 0);
626    doc.add_bullet_list_item("Headers and footers: text, images, different first page", 0);
627    doc.add_bullet_list_item("Background images: full-page behind text", 0);
628    doc.add_bullet_list_item(
629        "Text formatting: bold, italic, underline, strike, color, size, font",
630        0,
631    );
632    doc.add_bullet_list_item(
633        "Advanced run formatting: superscript, subscript, caps, spacing",
634        0,
635    );
636    doc.add_bullet_list_item(
637        "Paragraph formatting: alignment, borders, shading, spacing, indentation",
638        0,
639    );
640    doc.add_bullet_list_item("Bullet and numbered lists with nesting levels", 0);
641    doc.add_bullet_list_item("Tab stops with dot/underscore leaders", 0);
642    doc.add_bullet_list_item(
643        "Tables: borders, shading, column spans, row spans, nesting",
644        0,
645    );
646    doc.add_bullet_list_item("Vertical alignment in table cells", 0);
647    doc.add_bullet_list_item("Inline images", 0);
648    doc.add_bullet_list_item("Placeholder replacement in body and table cells", 0);
649    doc.add_bullet_list_item("Content insertion at specific positions", 0);
650    doc.add_bullet_list_item(
651        "Section breaks with mixed portrait/landscape orientation",
652        0,
653    );
654
655    doc.add_paragraph("");
656    doc.add_paragraph("All features above were built entirely from scratch using the rdocx API.")
657        .alignment(Alignment::Center)
658        .shading("E2EFDA")
659        .border_all(BorderStyle::Single, 2, "00AA00");
660
661    doc.save(path).unwrap();
662}
examples/generate_all_samples.rs (line 302)
86fn generate_feature_showcase(_samples_dir: &Path) -> Document {
87    let mut doc = Document::new();
88
89    // ── Page Setup & Metadata ──
90    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
91    doc.set_margins(
92        Length::inches(1.0),
93        Length::inches(1.0),
94        Length::inches(1.0),
95        Length::inches(1.0),
96    );
97    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
98    doc.set_gutter(Length::twips(0));
99    doc.set_title("rdocx Feature Showcase");
100    doc.set_author("rdocx Sample Generator");
101    doc.set_subject("Comprehensive feature demonstration");
102    doc.set_keywords("rdocx, docx, rust, sample, showcase");
103
104    // Headers & Footers with different first page
105    doc.set_different_first_page(true);
106    doc.set_first_page_header("rdocx");
107    doc.set_first_page_footer("Feature Showcase — Cover Page");
108    doc.set_header("rdocx Feature Showcase");
109    doc.set_footer("Generated by rdocx");
110
111    // ── COVER PAGE ──
112    let bg = create_sample_png(612, 792, [20, 45, 90]);
113    doc.add_background_image(&bg, "cover_bg.png");
114
115    for _ in 0..3 {
116        doc.add_paragraph("");
117    }
118    {
119        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
120        p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
121    }
122    {
123        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
124        p.add_run("Complete Feature Showcase")
125            .size(28.0)
126            .color("FFFFFF")
127            .italic(true);
128    }
129    doc.add_paragraph("");
130    {
131        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
132        p.add_run("Every feature of the rdocx Rust library")
133            .size(13.0)
134            .color("B0C4DE");
135    }
136    {
137        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
138        p.add_run("demonstrated in a single document.")
139            .size(13.0)
140            .color("B0C4DE");
141    }
142
143    // ── TABLE OF CONTENTS ──
144    doc.add_paragraph("").page_break_before(true);
145    doc.insert_toc(doc.content_count(), 3);
146
147    // ── SECTION 1: TEXT FORMATTING ──
148    doc.add_paragraph("").page_break_before(true);
149    doc.add_paragraph("1. Text Formatting").style("Heading1");
150
151    // Paragraph Alignment
152    doc.add_paragraph("1.1 Paragraph Alignment")
153        .style("Heading2");
154    doc.add_paragraph("Left-aligned paragraph (default).")
155        .alignment(Alignment::Left);
156    doc.add_paragraph("Center-aligned paragraph.")
157        .alignment(Alignment::Center);
158    doc.add_paragraph("Right-aligned paragraph.")
159        .alignment(Alignment::Right);
160    doc.add_paragraph(
161        "Justified paragraph — this text is long enough to demonstrate how justified alignment \
162         distributes extra space across word gaps so lines fill the full width of the text area.",
163    )
164    .alignment(Alignment::Justify);
165
166    doc.add_paragraph("");
167
168    // Run Formatting
169    doc.add_paragraph("1.2 Run Formatting").style("Heading2");
170    {
171        let mut p = doc.add_paragraph("");
172        p.add_run("Bold").bold(true);
173        p.add_run(" | ");
174        p.add_run("Italic").italic(true);
175        p.add_run(" | ");
176        p.add_run("Bold Italic").bold(true).italic(true);
177        p.add_run(" | ");
178        p.add_run("Underline").underline(true);
179        p.add_run(" | ");
180        p.add_run("Strikethrough").strike(true);
181        p.add_run(" | ");
182        p.add_run("Double Strike").double_strike(true);
183    }
184    {
185        let mut p = doc.add_paragraph("");
186        p.add_run("Red").color("FF0000");
187        p.add_run(" | ");
188        p.add_run("Blue").color("0000FF");
189        p.add_run(" | ");
190        p.add_run("Green").color("00AA00");
191        p.add_run(" | ");
192        p.add_run("Highlighted").highlight("FFFF00");
193    }
194    {
195        let mut p = doc.add_paragraph("");
196        p.add_run("8pt").size(8.0);
197        p.add_run(" | ");
198        p.add_run("11pt").size(11.0);
199        p.add_run(" | ");
200        p.add_run("16pt").size(16.0);
201        p.add_run(" | ");
202        p.add_run("24pt").size(24.0);
203    }
204    {
205        let mut p = doc.add_paragraph("");
206        p.add_run("Arial").font("Arial");
207        p.add_run(" | ");
208        p.add_run("Times New Roman").font("Times New Roman");
209        p.add_run(" | ");
210        p.add_run("Courier New").font("Courier New");
211    }
212    {
213        let mut p = doc.add_paragraph("");
214        p.add_run("H");
215        p.add_run("2").subscript();
216        p.add_run("O (subscript) | E=mc");
217        p.add_run("2").superscript();
218        p.add_run(" (superscript)");
219    }
220    {
221        let mut p = doc.add_paragraph("");
222        p.add_run("ALL CAPS").all_caps(true);
223        p.add_run(" | ");
224        p.add_run("Small Caps").small_caps(true);
225        p.add_run(" | ");
226        p.add_run("Expanded").character_spacing(Length::twips(40));
227        p.add_run(" | ");
228        p.add_run("Hidden text (not visible)").hidden(true);
229    }
230
231    doc.add_paragraph("");
232
233    // Underline Styles
234    doc.add_paragraph("1.3 Underline Styles").style("Heading2");
235    {
236        let mut p = doc.add_paragraph("");
237        p.add_run("Single ")
238            .underline_style(rdocx::UnderlineStyle::Single);
239        p.add_run("Double ")
240            .underline_style(rdocx::UnderlineStyle::Double);
241        p.add_run("Thick ")
242            .underline_style(rdocx::UnderlineStyle::Thick);
243        p.add_run("Dotted ")
244            .underline_style(rdocx::UnderlineStyle::Dotted);
245        p.add_run("Dash ")
246            .underline_style(rdocx::UnderlineStyle::Dash);
247        p.add_run("Wave ")
248            .underline_style(rdocx::UnderlineStyle::Wave);
249    }
250
251    // ── SECTION 2: PARAGRAPH FORMATTING ──
252    doc.add_paragraph("").page_break_before(true);
253    doc.add_paragraph("2. Paragraph Formatting")
254        .style("Heading1");
255
256    doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
257    doc.add_paragraph("Paragraph with light green shading")
258        .shading("E2EFDA");
259    doc.add_paragraph("Paragraph with bottom border")
260        .border_bottom(BorderStyle::Single, 6, "2E75B6");
261    doc.add_paragraph("Paragraph with all borders (red)")
262        .border_all(BorderStyle::Single, 4, "FF0000");
263
264    doc.add_paragraph("2.2 Indentation").style("Heading2");
265    doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
266        .indent_left(Length::inches(1.0))
267        .hanging_indent(Length::inches(0.5));
268    doc.add_paragraph("0.5-inch first-line indent on this paragraph")
269        .first_line_indent(Length::inches(0.5));
270    doc.add_paragraph("Both left and right indent (0.75 inches each)")
271        .indent_left(Length::inches(0.75))
272        .indent_right(Length::inches(0.75));
273
274    doc.add_paragraph("2.3 Spacing & Line Height")
275        .style("Heading2");
276    doc.add_paragraph("Extra space before (24pt) and after (12pt)")
277        .space_before(Length::pt(24.0))
278        .space_after(Length::pt(12.0));
279    doc.add_paragraph(
280        "Double line spacing paragraph. This text should have extra vertical space between \
281         lines to demonstrate line_spacing_multiple(2.0).",
282    )
283    .line_spacing_multiple(2.0);
284    doc.add_paragraph("Exact 20pt line spacing (fixed height).")
285        .line_spacing(20.0);
286
287    doc.add_paragraph("2.4 Pagination Controls")
288        .style("Heading2");
289    doc.add_paragraph("keep_with_next — this paragraph stays with the next")
290        .keep_with_next(true);
291    doc.add_paragraph("(This paragraph was kept with the one above.)");
292    doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
293        .keep_together(true);
294    doc.add_paragraph("widow_control — prevents widow/orphan lines")
295        .widow_control(true);
296
297    // ── SECTION 3: LISTS ──
298    doc.add_paragraph("").page_break_before(true);
299    doc.add_paragraph("3. Lists").style("Heading1");
300
301    doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
302    doc.add_bullet_list_item("First item", 0);
303    doc.add_bullet_list_item("Second item", 0);
304    doc.add_bullet_list_item("Nested level 1", 1);
305    doc.add_bullet_list_item("Nested level 2", 2);
306    doc.add_bullet_list_item("Back to level 1", 1);
307    doc.add_bullet_list_item("Third item", 0);
308
309    doc.add_paragraph("");
310
311    doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
312    doc.add_numbered_list_item("First numbered item", 0);
313    doc.add_numbered_list_item("Second numbered item", 0);
314    doc.add_numbered_list_item("Sub-item A", 1);
315    doc.add_numbered_list_item("Sub-item B", 1);
316    doc.add_numbered_list_item("Third numbered item", 0);
317
318    // ── SECTION 4: TAB STOPS ──
319    doc.add_paragraph("");
320    doc.add_paragraph("4. Tab Stops").style("Heading1");
321
322    doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
323    doc.add_paragraph("Left\tCenter\tRight\tDecimal")
324        .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
325        .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
326        .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
327        .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
328
329    doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
330    doc.add_paragraph("Item\t\tPrice")
331        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
332        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
333        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
334    doc.add_paragraph("Widget\t\t$19.99")
335        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
336        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
337        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
338    doc.add_paragraph("Gadget\t\t$249.50")
339        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
340        .add_tab_stop_with_leader(
341            TabAlignment::Right,
342            Length::inches(4.0),
343            TabLeader::Underscore,
344        )
345        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
346
347    // ── SECTION 5: TABLES ──
348    doc.add_paragraph("").page_break_before(true);
349    doc.add_paragraph("5. Tables").style("Heading1");
350
351    doc.add_paragraph("5.1 Basic Table").style("Heading2");
352    {
353        let mut tbl = doc
354            .add_table(4, 3)
355            .borders(BorderStyle::Single, 4, "000000");
356        for col in 0..3 {
357            tbl.cell(0, col).unwrap().shading("2E75B6");
358        }
359        tbl.cell(0, 0).unwrap().set_text("Name");
360        tbl.cell(0, 1).unwrap().set_text("Role");
361        tbl.cell(0, 2).unwrap().set_text("Location");
362        tbl.cell(1, 0).unwrap().set_text("Walter White");
363        tbl.cell(1, 1).unwrap().set_text("CEO");
364        tbl.cell(1, 2).unwrap().set_text("Albuquerque");
365        tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
366        tbl.cell(2, 1).unwrap().set_text("CTO");
367        tbl.cell(2, 2).unwrap().set_text("Remote");
368        tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
369        tbl.cell(3, 1).unwrap().set_text("Security");
370        tbl.cell(3, 2).unwrap().set_text("Washington");
371    }
372
373    doc.add_paragraph("");
374
375    doc.add_paragraph("5.2 Column Span & Cell Shading")
376        .style("Heading2");
377    {
378        let mut tbl = doc
379            .add_table(3, 4)
380            .borders(BorderStyle::Single, 4, "000000")
381            .width_pct(100.0);
382        tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
383        tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
384        tbl.cell(1, 0).unwrap().set_text("Region");
385        tbl.cell(1, 0).unwrap().shading("D6E4F0");
386        tbl.cell(1, 1).unwrap().set_text("Q1");
387        tbl.cell(1, 1).unwrap().shading("D6E4F0");
388        tbl.cell(1, 2).unwrap().set_text("Q2");
389        tbl.cell(1, 2).unwrap().shading("D6E4F0");
390        tbl.cell(1, 3).unwrap().set_text("Total");
391        tbl.cell(1, 3).unwrap().shading("D6E4F0");
392        tbl.cell(2, 0).unwrap().set_text("Americas");
393        tbl.cell(2, 1).unwrap().set_text("$2.4M");
394        tbl.cell(2, 2).unwrap().set_text("$2.7M");
395        tbl.cell(2, 3).unwrap().set_text("$5.1M");
396    }
397
398    doc.add_paragraph("");
399
400    doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
401    {
402        let mut tbl = doc
403            .add_table(4, 3)
404            .borders(BorderStyle::Single, 4, "000000");
405        tbl.cell(0, 0).unwrap().set_text("Category");
406        tbl.cell(0, 0).unwrap().shading("E2EFDA");
407        tbl.cell(0, 1).unwrap().set_text("Item");
408        tbl.cell(0, 1).unwrap().shading("E2EFDA");
409        tbl.cell(0, 2).unwrap().set_text("Price");
410        tbl.cell(0, 2).unwrap().shading("E2EFDA");
411        tbl.cell(1, 0).unwrap().set_text("Hardware");
412        tbl.cell(1, 0).unwrap().v_merge_restart();
413        tbl.cell(1, 1).unwrap().set_text("Laptop");
414        tbl.cell(1, 2).unwrap().set_text("$1,200");
415        tbl.cell(2, 0).unwrap().v_merge_continue();
416        tbl.cell(2, 1).unwrap().set_text("Monitor");
417        tbl.cell(2, 2).unwrap().set_text("$450");
418        tbl.cell(3, 0).unwrap().set_text("Software");
419        tbl.cell(3, 1).unwrap().set_text("IDE License");
420        tbl.cell(3, 2).unwrap().set_text("$200/yr");
421    }
422
423    doc.add_paragraph("");
424
425    doc.add_paragraph("5.4 Nested Table").style("Heading2");
426    {
427        let mut tbl = doc
428            .add_table(2, 2)
429            .borders(BorderStyle::Single, 6, "2E75B6");
430        tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
431        tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
432        tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
433        {
434            let mut cell = tbl.cell(1, 1).unwrap();
435            cell.set_text("Nested table below:");
436            let mut nested = cell
437                .add_table(2, 2)
438                .borders(BorderStyle::Single, 2, "FF6600");
439            nested.cell(0, 0).unwrap().set_text("A");
440            nested.cell(0, 1).unwrap().set_text("B");
441            nested.cell(1, 0).unwrap().set_text("C");
442            nested.cell(1, 1).unwrap().set_text("D");
443        }
444    }
445
446    doc.add_paragraph("");
447
448    doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
449        .style("Heading2");
450    {
451        let mut tbl = doc
452            .add_table(2, 3)
453            .borders(BorderStyle::Single, 4, "666666");
454        tbl.row(0).unwrap().height(Length::pt(50.0)).header();
455        tbl.cell(0, 0).unwrap().set_text("Top");
456        tbl.cell(0, 0)
457            .unwrap()
458            .vertical_alignment(VerticalAlignment::Top)
459            .shading("FFF2CC");
460        tbl.cell(0, 1).unwrap().set_text("Center");
461        tbl.cell(0, 1)
462            .unwrap()
463            .vertical_alignment(VerticalAlignment::Center)
464            .shading("D9E2F3");
465        tbl.cell(0, 2).unwrap().set_text("Bottom");
466        tbl.cell(0, 2)
467            .unwrap()
468            .vertical_alignment(VerticalAlignment::Bottom)
469            .shading("E2EFDA");
470        tbl.row(1).unwrap().cant_split();
471        tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
472        tbl.cell(1, 0).unwrap().no_wrap();
473        tbl.cell(1, 1).unwrap().set_text("Fixed width");
474        tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
475        tbl.cell(1, 2).unwrap().set_text("Normal");
476    }
477
478    // ── SECTION 6: IMAGES ──
479    doc.add_paragraph("").page_break_before(true);
480    doc.add_paragraph("6. Images").style("Heading1");
481
482    doc.add_paragraph("6.1 Inline Image").style("Heading2");
483    doc.add_paragraph("A blue gradient image below:");
484    let img = create_sample_png(200, 50, [0, 80, 200]);
485    doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
486
487    doc.add_paragraph("");
488    doc.add_paragraph("6.2 Header Image").style("Heading2");
489    let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
490    doc.set_header_image(
491        &hdr_img,
492        "header_logo.png",
493        Length::inches(2.0),
494        Length::inches(0.2),
495    );
496    doc.add_paragraph("The header now contains an inline image (check the top of this page).");
497
498    doc.add_paragraph("");
499    doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
500
501    // ── SECTION 7: CONTENT MANIPULATION ──
502    doc.add_paragraph("").page_break_before(true);
503    doc.add_paragraph("7. Content Manipulation")
504        .style("Heading1");
505
506    doc.add_paragraph("7.1 Placeholder Replacement")
507        .style("Heading2");
508    doc.add_paragraph("Customer: {{customer}}");
509    doc.add_paragraph("Date: {{date}}");
510    doc.add_paragraph("Reference: {{ref_number}}");
511    {
512        let mut tbl = doc
513            .add_table(2, 2)
514            .borders(BorderStyle::Single, 4, "000000");
515        tbl.cell(0, 0).unwrap().set_text("Project");
516        tbl.cell(0, 0).unwrap().shading("D6E4F0");
517        tbl.cell(0, 1).unwrap().set_text("{{project}}");
518        tbl.cell(1, 0).unwrap().set_text("Status");
519        tbl.cell(1, 0).unwrap().shading("D6E4F0");
520        tbl.cell(1, 1).unwrap().set_text("{{status}}");
521    }
522
523    let mut replacements = HashMap::new();
524    replacements.insert("{{customer}}", "Tensorbee Inc.");
525    replacements.insert("{{date}}", "February 22, 2026");
526    replacements.insert("{{ref_number}}", "TB-2026-001");
527    replacements.insert("{{project}}", "Infrastructure Upgrade");
528    replacements.insert("{{status}}", "In Progress");
529    let n = doc.replace_all(&replacements);
530    doc.add_paragraph(&format!("({n} placeholders replaced above)"));
531
532    doc.add_paragraph("");
533
534    doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
535    doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
536    let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
537    doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
538
539    doc.add_paragraph("");
540
541    doc.add_paragraph("7.3 Content Insertion").style("Heading2");
542    doc.add_paragraph("Section A: First content.");
543    doc.add_paragraph("Section C: Third content.");
544    if let Some(idx) = doc.find_content_index("Section C") {
545        doc.insert_paragraph(
546            idx,
547            "Section B: Inserted between A and C via find_content_index().",
548        );
549    }
550
551    // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
552    doc.add_paragraph("").section_break(SectionBreak::NextPage);
553    doc.add_paragraph("8. Section Breaks & Orientation")
554        .style("Heading1");
555    doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
556
557    {
558        let mut tbl = doc
559            .add_table(3, 7)
560            .borders(BorderStyle::Single, 4, "2E75B6");
561        let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
562        for (c, h) in headers.iter().enumerate() {
563            tbl.cell(0, c).unwrap().set_text(h);
564            tbl.cell(0, c).unwrap().shading("2E75B6");
565        }
566        let data = [
567            [
568                "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
569            ],
570            [
571                "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
572            ],
573        ];
574        for (r, row) in data.iter().enumerate() {
575            for (c, v) in row.iter().enumerate() {
576                tbl.cell(r + 1, c).unwrap().set_text(v);
577            }
578        }
579    }
580
581    doc.add_paragraph("")
582        .section_break(SectionBreak::NextPage)
583        .section_landscape();
584
585    // ── SECTION 9: CUSTOM STYLES ──
586    doc.add_paragraph("9. Custom Styles").style("Heading1");
587    doc.add_style(
588        StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
589            .based_on("Normal")
590            .paragraph_properties({
591                let mut ppr = rdocx_oxml::properties::CT_PPr::default();
592                ppr.shading = Some(rdocx_oxml::properties::CT_Shd {
593                    val: "clear".to_string(),
594                    color: None,
595                    fill: Some("FFF2CC".to_string()),
596                });
597                ppr
598            })
599            .run_properties({
600                let mut rpr = rdocx_oxml::properties::CT_RPr::default();
601                rpr.bold = Some(true);
602                rpr.color = Some("C45911".to_string());
603                rpr
604            }),
605    );
606    doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
607        .style("CustomHighlight");
608
609    doc.add_paragraph("");
610
611    // ── SECTION 10: DOCUMENT INTELLIGENCE ──
612    doc.add_paragraph("10. Document Intelligence API")
613        .style("Heading1");
614
615    let wc = doc.word_count();
616    let hc = doc.headings().len();
617    let ic = doc.images().len();
618    let lc = doc.links().len();
619    doc.add_paragraph(&format!("Word count: {wc}"));
620    doc.add_paragraph(&format!("Heading count: {hc}"));
621    doc.add_paragraph(&format!("Image count: {ic}"));
622    doc.add_paragraph(&format!("Link count: {lc}"));
623
624    let outline = doc.document_outline();
625    doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
626
627    let issues = doc.audit_accessibility();
628    doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
629    for issue in &issues {
630        doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
631    }
632
633    // ── SECTION 11: DOCUMENT MERGING ──
634    doc.add_paragraph("");
635    doc.add_paragraph("11. Document Merging").style("Heading1");
636    {
637        let mut other = Document::new();
638        other.add_paragraph("This paragraph was merged from another document using append().");
639        doc.append(&other);
640    }
641
642    doc.add_paragraph("");
643
644    // ── SUMMARY ──
645    doc.add_paragraph("Summary of Demonstrated Features")
646        .style("Heading1");
647    let features = [
648        "Page setup: size, margins, header/footer distance, gutter",
649        "Document metadata: title, author, subject, keywords",
650        "Headers and footers: text, images, different first page",
651        "Background images (full-page behind text)",
652        "Table of Contents generation with bookmarks",
653        "Text formatting: bold, italic, underline styles, strike, color, size, font",
654        "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
655        "Paragraph formatting: alignment, borders, shading, spacing, indentation",
656        "Pagination controls: keep-with-next, keep-together, widow control",
657        "Bullet and numbered lists with nesting",
658        "Tab stops with dot/underscore/hyphen leaders",
659        "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
660        "Row properties: header rows, exact height, cant-split",
661        "Cell properties: width, no-wrap, vertical alignment",
662        "Inline images and header images",
663        "Placeholder replacement (single and batch)",
664        "Regex-based find and replace",
665        "Content insertion at specific positions",
666        "Section breaks with mixed portrait/landscape",
667        "Custom paragraph and character styles",
668        "Document intelligence: word count, headings, outline, images, links",
669        "Accessibility audit",
670        "Document merging (append)",
671        "Export: DOCX, PDF, HTML, Markdown",
672    ];
673    for f in &features {
674        doc.add_bullet_list_item(f, 0);
675    }
676    doc.add_paragraph("");
677    doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
678        .alignment(Alignment::Center)
679        .shading("E2EFDA")
680        .border_all(BorderStyle::Single, 2, "00AA00");
681
682    doc
683}
684
685// =============================================================================
686// 2. PROPOSAL DOCUMENT — Deep navy + gold scheme
687// =============================================================================
688fn generate_proposal(_samples_dir: &Path) -> Document {
689    let mut doc = Document::new();
690
691    // Colors: Navy #1B2A4A, Gold #C5922E, Light #F4F1EB
692    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
693    doc.set_margins(
694        Length::inches(1.0),
695        Length::inches(1.0),
696        Length::inches(1.0),
697        Length::inches(1.0),
698    );
699    doc.set_title("AI Platform Modernization Proposal");
700    doc.set_author("Walter White");
701    doc.set_subject("Technology Proposal");
702    doc.set_keywords("proposal, AI, modernization, Tensorbee");
703
704    // Banner header
705    let logo_img = create_logo_png(220, 48);
706    let banner = build_header_banner_xml(
707        "rId1",
708        &BannerOpts {
709            bg_color: "1B2A4A",
710            banner_width: 7772400,
711            banner_height: 914400,
712            logo_width: 2011680,
713            logo_height: 438912,
714            logo_x_offset: 295125,
715            logo_y_offset: 237744,
716        },
717    );
718    doc.set_raw_header_with_images(
719        banner,
720        &[("rId1", &logo_img, "logo.png")],
721        rdocx_oxml::header_footer::HdrFtrType::Default,
722    );
723    doc.set_different_first_page(true);
724    let cover_banner = build_header_banner_xml(
725        "rId1",
726        &BannerOpts {
727            bg_color: "C5922E",
728            banner_width: 7772400,
729            banner_height: 914400,
730            logo_width: 2011680,
731            logo_height: 438912,
732            logo_x_offset: 295125,
733            logo_y_offset: 237744,
734        },
735    );
736    doc.set_raw_header_with_images(
737        cover_banner,
738        &[("rId1", &logo_img, "logo.png")],
739        rdocx_oxml::header_footer::HdrFtrType::First,
740    );
741    doc.set_margins(
742        Length::twips(2292),
743        Length::twips(1440),
744        Length::twips(1440),
745        Length::twips(1440),
746    );
747    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
748    doc.set_footer("Tensorbee — Confidential");
749
750    // Custom styles
751    doc.add_style(
752        StyleBuilder::paragraph("ProposalTitle", "Proposal Title")
753            .based_on("Normal")
754            .run_properties({
755                let mut rpr = rdocx_oxml::properties::CT_RPr::default();
756                rpr.bold = Some(true);
757                rpr.font_ascii = Some("Georgia".to_string());
758                rpr.font_hansi = Some("Georgia".to_string());
759                rpr.sz = Some(rdocx_oxml::HalfPoint(56)); // half-points → 28pt
760                rpr.color = Some("1B2A4A".to_string());
761                rpr
762            }),
763    );
764
765    // ── Cover Page ──
766    for _ in 0..4 {
767        doc.add_paragraph("");
768    }
769    doc.add_paragraph("AI Platform Modernization")
770        .style("ProposalTitle")
771        .alignment(Alignment::Center);
772    doc.add_paragraph("")
773        .alignment(Alignment::Center)
774        .border_bottom(BorderStyle::Single, 8, "C5922E");
775    {
776        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
777        p.add_run("Prepared for Global Financial Corp.")
778            .size(14.0)
779            .color("666666")
780            .italic(true);
781    }
782    {
783        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
784        p.add_run("Prepared by: Walter White, CEO — Tensorbee")
785            .size(12.0)
786            .color("888888");
787    }
788    {
789        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
790        p.add_run("February 22, 2026").size(12.0).color("888888");
791    }
792
793    // ── TOC ──
794    doc.add_paragraph("").page_break_before(true);
795    doc.add_paragraph("Table of Contents").style("Heading1");
796    // We'll add a manual-style TOC since the insert_toc goes at a specific position
797    doc.insert_toc(doc.content_count(), 2);
798
799    // ── Executive Summary ──
800    doc.add_paragraph("").page_break_before(true);
801    doc.add_paragraph("Executive Summary").style("Heading1");
802    doc.add_paragraph(
803        "Tensorbee proposes a comprehensive modernization of Global Financial Corp's AI platform, \
804         transitioning from legacy batch-processing systems to a real-time inference architecture. \
805         This transformation will reduce model deployment time from weeks to hours, improve \
806         prediction accuracy by an estimated 23%, and deliver $4.2M in annual operational savings.",
807    )
808    .first_line_indent(Length::inches(0.3));
809    doc.add_paragraph(
810        "The project spans three phases over 18 months, with a total investment of $2.8M. \
811         Tensorbee brings deep expertise in ML infrastructure, having successfully completed \
812         similar transformations for three Fortune 500 financial institutions.",
813    )
814    .first_line_indent(Length::inches(0.3));
815
816    // Key metrics table
817    doc.add_paragraph("");
818    {
819        let mut tbl = doc
820            .add_table(5, 2)
821            .borders(BorderStyle::Single, 4, "1B2A4A")
822            .width_pct(80.0);
823        tbl.cell(0, 0).unwrap().set_text("Key Metric");
824        tbl.cell(0, 0).unwrap().shading("1B2A4A");
825        tbl.cell(0, 1).unwrap().set_text("Value");
826        tbl.cell(0, 1).unwrap().shading("1B2A4A");
827        let rows = [
828            ("Total Investment", "$2.8M"),
829            ("Annual Savings", "$4.2M"),
830            ("ROI (Year 1)", "150%"),
831            ("Timeline", "18 months"),
832        ];
833        for (i, (k, v)) in rows.iter().enumerate() {
834            tbl.cell(i + 1, 0).unwrap().set_text(k);
835            if i % 2 == 0 {
836                tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
837            }
838            tbl.cell(i + 1, 1).unwrap().set_text(v);
839            if i % 2 == 0 {
840                tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
841            }
842        }
843    }
844
845    // ── Problem Statement ──
846    doc.add_paragraph("").page_break_before(true);
847    doc.add_paragraph("Problem Statement").style("Heading1");
848    doc.add_paragraph(
849        "Global Financial Corp's current AI infrastructure faces three critical challenges:",
850    );
851    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);
852    doc.add_numbered_list_item("Scalability Constraints — The batch processing architecture cannot handle real-time inference demands during peak trading hours.", 0);
853    doc.add_numbered_list_item("Technical Debt — Legacy Python 2.7 codebases and manual deployment scripts create reliability risks and slow iteration speed.", 0);
854
855    // ── Proposed Solution ──
856    doc.add_paragraph("").page_break_before(true);
857    doc.add_paragraph("Proposed Solution").style("Heading1");
858
859    doc.add_paragraph("Phase 1: Foundation (Months 1-6)")
860        .style("Heading2");
861    doc.add_bullet_list_item("Deploy Kubernetes-based ML serving infrastructure", 0);
862    doc.add_bullet_list_item("Implement CI/CD pipeline for model deployment", 0);
863    doc.add_bullet_list_item("Migrate top 5 production models to new platform", 0);
864
865    doc.add_paragraph("Phase 2: Expansion (Months 7-12)")
866        .style("Heading2");
867    doc.add_bullet_list_item(
868        "Real-time feature engineering pipeline (Apache Kafka + Flink)",
869        0,
870    );
871    doc.add_bullet_list_item("A/B testing framework for model variants", 0);
872    doc.add_bullet_list_item("Automated model monitoring and drift detection", 0);
873
874    doc.add_paragraph("Phase 3: Optimization (Months 13-18)")
875        .style("Heading2");
876    doc.add_bullet_list_item("GPU inference optimization (TensorRT/ONNX Runtime)", 0);
877    doc.add_bullet_list_item("Multi-region deployment for latency reduction", 0);
878    doc.add_bullet_list_item(
879        "Self-service model deployment portal for data science teams",
880        0,
881    );
882
883    // ── Budget ──
884    doc.add_paragraph("Budget Breakdown").style("Heading1");
885    {
886        let mut tbl = doc
887            .add_table(6, 3)
888            .borders(BorderStyle::Single, 4, "1B2A4A")
889            .width_pct(100.0);
890        tbl.cell(0, 0).unwrap().set_text("Category");
891        tbl.cell(0, 0).unwrap().shading("1B2A4A");
892        tbl.cell(0, 1).unwrap().set_text("Cost");
893        tbl.cell(0, 1).unwrap().shading("1B2A4A");
894        tbl.cell(0, 2).unwrap().set_text("Notes");
895        tbl.cell(0, 2).unwrap().shading("1B2A4A");
896        let items = [
897            (
898                "Infrastructure",
899                "$450,000",
900                "Cloud compute + GPU instances",
901            ),
902            ("Engineering Services", "$1,600,000", "12 FTE x 18 months"),
903            (
904                "Software Licenses",
905                "$350,000",
906                "Kafka, monitoring, CI/CD tools",
907            ),
908            (
909                "Training & Enablement",
910                "$200,000",
911                "Team training, documentation",
912            ),
913            ("Contingency (10%)", "$200,000", "Risk buffer"),
914        ];
915        for (i, (cat, cost, note)) in items.iter().enumerate() {
916            tbl.cell(i + 1, 0).unwrap().set_text(cat);
917            tbl.cell(i + 1, 1).unwrap().set_text(cost);
918            tbl.cell(i + 1, 2).unwrap().set_text(note);
919            if i % 2 == 0 {
920                tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
921                tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
922                tbl.cell(i + 1, 2).unwrap().shading("F4F1EB");
923            }
924        }
925    }
926
927    // ── Closing ──
928    doc.add_paragraph("");
929    doc.add_paragraph("Next Steps").style("Heading1");
930    doc.add_numbered_list_item(
931        "Schedule technical deep-dive with GFC engineering team (Week 1)",
932        0,
933    );
934    doc.add_numbered_list_item("Finalize scope and sign SOW (Week 2-3)", 0);
935    doc.add_numbered_list_item("Kick off Phase 1 with joint planning session (Week 4)", 0);
936
937    doc.add_paragraph("");
938    {
939        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
940        p.add_run("Walter White")
941            .bold(true)
942            .size(14.0)
943            .color("1B2A4A");
944    }
945    {
946        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
947        p.add_run("CEO, Tensorbee | walter@tensorbee.com")
948            .size(10.0)
949            .color("888888");
950    }
951
952    doc
953}
954
955// =============================================================================
956// 3. QUOTE / BILL OF MATERIALS — Teal + orange scheme
957// =============================================================================
958fn generate_quote(_samples_dir: &Path) -> Document {
959    let mut doc = Document::new();
960
961    // Colors: Teal #008B8B, Dark #1A3C3C, Orange #E07020, Light #F0F8F8
962    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
963    doc.set_margins(
964        Length::inches(0.75),
965        Length::inches(0.75),
966        Length::inches(0.75),
967        Length::inches(0.75),
968    );
969    doc.set_title("Quotation QT-2026-0147");
970    doc.set_author("Walter White");
971    doc.set_keywords("quote, BOM, Tensorbee");
972
973    doc.set_header("Tensorbee — Quotation");
974    doc.set_footer("QT-2026-0147 | Page");
975
976    // ── Header Block ──
977    {
978        let mut p = doc.add_paragraph("").alignment(Alignment::Left);
979        p.add_run("TENSORBEE")
980            .bold(true)
981            .size(28.0)
982            .color("008B8B")
983            .font("Helvetica");
984    }
985    doc.add_paragraph("123 Innovation Drive, Suite 400")
986        .alignment(Alignment::Left);
987    doc.add_paragraph("San Francisco, CA 94105 | +1 (415) 555-0199")
988        .alignment(Alignment::Left);
989    doc.add_paragraph("")
990        .border_bottom(BorderStyle::Single, 8, "008B8B");
991
992    // Quote meta
993    doc.add_paragraph("");
994    {
995        let mut tbl = doc.add_table(4, 4).width_pct(100.0);
996        tbl.cell(0, 0).unwrap().set_text("QUOTATION");
997        tbl.cell(0, 0).unwrap().shading("008B8B").grid_span(2);
998        tbl.cell(0, 2).unwrap().set_text("DATE");
999        tbl.cell(0, 2).unwrap().shading("008B8B");
1000        tbl.cell(0, 3).unwrap().set_text("VALID UNTIL");
1001        tbl.cell(0, 3).unwrap().shading("008B8B");
1002        tbl.cell(1, 0).unwrap().set_text("Quote #:");
1003        tbl.cell(1, 1).unwrap().set_text("QT-2026-0147");
1004        tbl.cell(1, 2).unwrap().set_text("Feb 22, 2026");
1005        tbl.cell(1, 3).unwrap().set_text("Mar 22, 2026");
1006        tbl.cell(2, 0).unwrap().set_text("Prepared by:");
1007        tbl.cell(2, 1).unwrap().set_text("Walter White");
1008        tbl.cell(2, 2).unwrap().set_text("Payment:");
1009        tbl.cell(2, 3).unwrap().set_text("Net 30");
1010        tbl.cell(3, 0).unwrap().set_text("Customer:");
1011        tbl.cell(3, 1).unwrap().set_text("Meridian Dynamics LLC");
1012        tbl.cell(3, 2).unwrap().set_text("Currency:");
1013        tbl.cell(3, 3).unwrap().set_text("USD");
1014    }
1015
1016    doc.add_paragraph("");
1017
1018    // ── Bill of Materials ──
1019    {
1020        let mut p = doc.add_paragraph("");
1021        p.add_run("Bill of Materials")
1022            .bold(true)
1023            .size(16.0)
1024            .color("1A3C3C");
1025    }
1026    doc.add_paragraph("");
1027
1028    {
1029        let mut tbl = doc
1030            .add_table(10, 6)
1031            .borders(BorderStyle::Single, 2, "008B8B")
1032            .width_pct(100.0)
1033            .layout_fixed();
1034        // Headers
1035        let hdrs = [
1036            "#",
1037            "Part Number",
1038            "Description",
1039            "Qty",
1040            "Unit Price",
1041            "Total",
1042        ];
1043        for (c, h) in hdrs.iter().enumerate() {
1044            tbl.cell(0, c).unwrap().set_text(h);
1045            tbl.cell(0, c).unwrap().shading("008B8B");
1046        }
1047
1048        let items: Vec<(&str, &str, &str, &str, &str)> = vec![
1049            (
1050                "TB-GPU-A100",
1051                "NVIDIA A100 80GB GPU",
1052                "4",
1053                "$12,500.00",
1054                "$50,000.00",
1055            ),
1056            (
1057                "TB-SRV-R750",
1058                "Dell R750xa Server Chassis",
1059                "2",
1060                "$8,200.00",
1061                "$16,400.00",
1062            ),
1063            (
1064                "TB-RAM-256G",
1065                "256GB DDR5 ECC Memory Module",
1066                "8",
1067                "$890.00",
1068                "$7,120.00",
1069            ),
1070            (
1071                "TB-SSD-3840",
1072                "3.84TB NVMe U.2 SSD",
1073                "8",
1074                "$1,150.00",
1075                "$9,200.00",
1076            ),
1077            (
1078                "TB-NET-CX7",
1079                "ConnectX-7 200GbE NIC",
1080                "4",
1081                "$1,800.00",
1082                "$7,200.00",
1083            ),
1084            (
1085                "TB-SW-48P",
1086                "48-Port 100GbE Switch",
1087                "1",
1088                "$22,000.00",
1089                "$22,000.00",
1090            ),
1091            (
1092                "TB-CAB-RACK",
1093                "42U Server Rack + PDU",
1094                "1",
1095                "$4,500.00",
1096                "$4,500.00",
1097            ),
1098            (
1099                "TB-SVC-INST",
1100                "Installation & Configuration",
1101                "1",
1102                "$8,500.00",
1103                "$8,500.00",
1104            ),
1105            (
1106                "TB-SVC-SUPP",
1107                "3-Year Premium Support",
1108                "1",
1109                "$15,000.00",
1110                "$15,000.00",
1111            ),
1112        ];
1113
1114        for (i, (pn, desc, qty, unit, total)) in items.iter().enumerate() {
1115            let row = i + 1;
1116            tbl.cell(row, 0).unwrap().set_text(&format!("{}", i + 1));
1117            tbl.cell(row, 1).unwrap().set_text(pn);
1118            tbl.cell(row, 2).unwrap().set_text(desc);
1119            tbl.cell(row, 3).unwrap().set_text(qty);
1120            tbl.cell(row, 4).unwrap().set_text(unit);
1121            tbl.cell(row, 5).unwrap().set_text(total);
1122            if i % 2 == 0 {
1123                for c in 0..6 {
1124                    tbl.cell(row, c).unwrap().shading("F0F8F8");
1125                }
1126            }
1127        }
1128    }
1129
1130    doc.add_paragraph("");
1131
1132    // ── Totals ──
1133    {
1134        let mut tbl = doc
1135            .add_table(4, 2)
1136            .borders(BorderStyle::Single, 2, "008B8B")
1137            .width(Length::inches(3.5))
1138            .alignment(Alignment::Right);
1139        tbl.cell(0, 0).unwrap().set_text("Subtotal");
1140        tbl.cell(0, 1).unwrap().set_text("$139,920.00");
1141        tbl.cell(1, 0).unwrap().set_text("Shipping & Handling");
1142        tbl.cell(1, 1).unwrap().set_text("$2,500.00");
1143        tbl.cell(2, 0).unwrap().set_text("Tax (8.625%)");
1144        tbl.cell(2, 1).unwrap().set_text("$12,068.10");
1145        tbl.cell(3, 0).unwrap().set_text("TOTAL");
1146        tbl.cell(3, 0).unwrap().shading("E07020");
1147        tbl.cell(3, 1).unwrap().set_text("$154,488.10");
1148        tbl.cell(3, 1).unwrap().shading("E07020");
1149    }
1150
1151    doc.add_paragraph("");
1152
1153    // ── Terms & Conditions ──
1154    {
1155        let mut p = doc.add_paragraph("");
1156        p.add_run("Terms & Conditions")
1157            .bold(true)
1158            .size(14.0)
1159            .color("1A3C3C");
1160    }
1161    doc.add_numbered_list_item(
1162        "This quotation is valid for 30 calendar days from the date of issue.",
1163        0,
1164    );
1165    doc.add_numbered_list_item(
1166        "All prices are in USD and exclusive of applicable taxes unless stated.",
1167        0,
1168    );
1169    doc.add_numbered_list_item("Standard lead time is 4-6 weeks from PO receipt.", 0);
1170    doc.add_numbered_list_item(
1171        "Payment terms: Net 30 from invoice date. 2% discount for payment within 10 days.",
1172        0,
1173    );
1174    doc.add_numbered_list_item(
1175        "Warranty: 3-year manufacturer warranty on all hardware components.",
1176        0,
1177    );
1178    doc.add_numbered_list_item(
1179        "Returns subject to 15% restocking fee if initiated after 14 days.",
1180        0,
1181    );
1182
1183    doc.add_paragraph("");
1184    doc.add_paragraph("")
1185        .border_bottom(BorderStyle::Single, 4, "008B8B");
1186    doc.add_paragraph("");
1187    {
1188        let mut p = doc.add_paragraph("");
1189        p.add_run("Accepted by: ").bold(true);
1190        p.add_run("___________________________ Date: ___________");
1191    }
1192    {
1193        let mut p = doc.add_paragraph("");
1194        p.add_run("Print Name: ").bold(true);
1195        p.add_run("___________________________ Title: ___________");
1196    }
1197
1198    doc
1199}
1200
1201// =============================================================================
1202// 4. INVOICE — Crimson + charcoal scheme
1203// =============================================================================
1204fn generate_invoice(_samples_dir: &Path) -> Document {
1205    let mut doc = Document::new();
1206
1207    // Colors: Crimson #B22222, Charcoal #333333, Light #FAF0F0
1208    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1209    doc.set_margins(
1210        Length::inches(0.75),
1211        Length::inches(0.75),
1212        Length::inches(0.75),
1213        Length::inches(0.75),
1214    );
1215    doc.set_title("Invoice INV-2026-0392");
1216    doc.set_author("Walter White");
1217    doc.set_keywords("invoice, Tensorbee");
1218
1219    doc.set_footer("Tensorbee — Thank you for your business!");
1220
1221    // ── Company & Invoice Header ──
1222    {
1223        let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1224        // Company name left, INVOICE right
1225        {
1226            let mut cell = tbl.cell(0, 0).unwrap();
1227            let mut p = cell.add_paragraph("");
1228            p.add_run("TENSORBEE")
1229                .bold(true)
1230                .size(32.0)
1231                .color("B22222")
1232                .font("Helvetica");
1233        }
1234        {
1235            let mut cell = tbl.cell(0, 1).unwrap();
1236            let mut p = cell.add_paragraph("");
1237            p.add_run("INVOICE").bold(true).size(32.0).color("333333");
1238        }
1239        tbl.cell(1, 0)
1240            .unwrap()
1241            .set_text("123 Innovation Drive, Suite 400");
1242        tbl.cell(1, 1).unwrap().set_text("Invoice #: INV-2026-0392");
1243        tbl.cell(2, 0).unwrap().set_text("San Francisco, CA 94105");
1244        tbl.cell(2, 1).unwrap().set_text("Date: February 22, 2026");
1245        tbl.cell(3, 0).unwrap().set_text("walter@tensorbee.com");
1246        tbl.cell(3, 1).unwrap().set_text("Due Date: March 24, 2026");
1247    }
1248
1249    doc.add_paragraph("")
1250        .border_bottom(BorderStyle::Thick, 8, "B22222");
1251    doc.add_paragraph("");
1252
1253    // ── Bill To / Ship To ──
1254    {
1255        let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1256        tbl.cell(0, 0).unwrap().set_text("BILL TO");
1257        tbl.cell(0, 0).unwrap().shading("B22222");
1258        tbl.cell(0, 1).unwrap().set_text("SHIP TO");
1259        tbl.cell(0, 1).unwrap().shading("B22222");
1260        tbl.cell(1, 0).unwrap().set_text("Meridian Dynamics LLC");
1261        tbl.cell(1, 1).unwrap().set_text("Meridian Dynamics LLC");
1262        tbl.cell(2, 0).unwrap().set_text("456 Enterprise Blvd");
1263        tbl.cell(2, 1).unwrap().set_text("Attn: Server Room B");
1264        tbl.cell(3, 0).unwrap().set_text("Austin, TX 78701");
1265        tbl.cell(3, 1)
1266            .unwrap()
1267            .set_text("456 Enterprise Blvd, Austin, TX 78701");
1268    }
1269
1270    doc.add_paragraph("");
1271
1272    // ── Line Items ──
1273    {
1274        let mut tbl = doc
1275            .add_table(8, 5)
1276            .borders(BorderStyle::Single, 2, "B22222")
1277            .width_pct(100.0);
1278        let hdrs = ["Description", "Qty", "Unit Price", "Tax", "Amount"];
1279        for (c, h) in hdrs.iter().enumerate() {
1280            tbl.cell(0, c).unwrap().set_text(h);
1281            tbl.cell(0, c).unwrap().shading("B22222");
1282        }
1283        let items = [
1284            (
1285                "ML Infrastructure Setup — Phase 1",
1286                "1",
1287                "$45,000.00",
1288                "$3,881.25",
1289                "$48,881.25",
1290            ),
1291            (
1292                "Data Pipeline Development (160 hrs)",
1293                "160",
1294                "$225.00",
1295                "$3,105.00",
1296                "$39,105.00",
1297            ),
1298            (
1299                "GPU Cluster Configuration",
1300                "1",
1301                "$12,000.00",
1302                "$1,035.00",
1303                "$13,035.00",
1304            ),
1305            (
1306                "API Gateway Implementation",
1307                "1",
1308                "$18,500.00",
1309                "$1,595.63",
1310                "$20,095.63",
1311            ),
1312            (
1313                "Load Testing & QA (80 hrs)",
1314                "80",
1315                "$195.00",
1316                "$1,345.50",
1317                "$16,945.50",
1318            ),
1319            (
1320                "Documentation & Training",
1321                "1",
1322                "$8,000.00",
1323                "$690.00",
1324                "$8,690.00",
1325            ),
1326            (
1327                "Project Management (3 months)",
1328                "3",
1329                "$6,500.00",
1330                "$1,679.63",
1331                "$21,179.63",
1332            ),
1333        ];
1334        for (i, (desc, qty, unit, tax, amt)) in items.iter().enumerate() {
1335            let r = i + 1;
1336            tbl.cell(r, 0).unwrap().set_text(desc);
1337            tbl.cell(r, 1).unwrap().set_text(qty);
1338            tbl.cell(r, 2).unwrap().set_text(unit);
1339            tbl.cell(r, 3).unwrap().set_text(tax);
1340            tbl.cell(r, 4).unwrap().set_text(amt);
1341            if i % 2 == 0 {
1342                for c in 0..5 {
1343                    tbl.cell(r, c).unwrap().shading("FAF0F0");
1344                }
1345            }
1346        }
1347    }
1348
1349    doc.add_paragraph("");
1350
1351    // ── Totals ──
1352    {
1353        let mut tbl = doc
1354            .add_table(4, 2)
1355            .borders(BorderStyle::Single, 2, "B22222")
1356            .width(Length::inches(3.0))
1357            .alignment(Alignment::Right);
1358        tbl.cell(0, 0).unwrap().set_text("Subtotal");
1359        tbl.cell(0, 1).unwrap().set_text("$154,600.00");
1360        tbl.cell(1, 0).unwrap().set_text("Tax (8.625%)");
1361        tbl.cell(1, 1).unwrap().set_text("$13,334.25");
1362        tbl.cell(2, 0).unwrap().set_text("Discount (5%)");
1363        tbl.cell(2, 1).unwrap().set_text("-$7,730.00");
1364        tbl.cell(3, 0).unwrap().set_text("AMOUNT DUE");
1365        tbl.cell(3, 0).unwrap().shading("B22222");
1366        tbl.cell(3, 1).unwrap().set_text("$160,204.25");
1367        tbl.cell(3, 1).unwrap().shading("B22222");
1368    }
1369
1370    doc.add_paragraph("");
1371    doc.add_paragraph("");
1372
1373    // ── Payment Details ──
1374    {
1375        let mut p = doc.add_paragraph("");
1376        p.add_run("Payment Details")
1377            .bold(true)
1378            .size(14.0)
1379            .color("333333");
1380    }
1381    doc.add_paragraph("Bank: Silicon Valley Bank")
1382        .indent_left(Length::inches(0.3));
1383    doc.add_paragraph("Account: Tensorbee Inc. — 0847-2953-1120")
1384        .indent_left(Length::inches(0.3));
1385    doc.add_paragraph("Routing: 121140399")
1386        .indent_left(Length::inches(0.3));
1387    doc.add_paragraph("Swift: SVBKUS6S")
1388        .indent_left(Length::inches(0.3));
1389
1390    doc.add_paragraph("");
1391
1392    doc.add_paragraph("Please include invoice number INV-2026-0392 in the payment reference.")
1393        .shading("FAF0F0")
1394        .border_all(BorderStyle::Single, 2, "B22222");
1395
1396    doc
1397}
1398
1399// =============================================================================
1400// 5. REPORT — Forest green + earth tones, with images & hierarchical sections
1401// =============================================================================
1402fn generate_report(_samples_dir: &Path) -> Document {
1403    let mut doc = Document::new();
1404
1405    // Colors: Forest #2D5016, Sage #6B8E23, Earth #8B7355, Cream #FFFAF0
1406    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1407    doc.set_margins(
1408        Length::inches(1.0),
1409        Length::inches(1.0),
1410        Length::inches(1.0),
1411        Length::inches(1.0),
1412    );
1413    doc.set_title("Q4 2025 Environmental Impact Report");
1414    doc.set_author("Walter White");
1415    doc.set_subject("Quarterly Environmental Report");
1416    doc.set_keywords("environment, sustainability, report, Tensorbee");
1417
1418    doc.set_different_first_page(true);
1419    doc.set_first_page_header("");
1420    doc.set_header("Tensorbee — Q4 2025 Environmental Impact Report");
1421    doc.set_footer("CONFIDENTIAL — Page");
1422
1423    // ── Cover ──
1424    let cover_bg = create_sample_png(612, 792, [20, 50, 15]);
1425    doc.add_background_image(&cover_bg, "report_cover.png");
1426
1427    for _ in 0..5 {
1428        doc.add_paragraph("");
1429    }
1430    {
1431        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1432        p.add_run("Q4 2025")
1433            .bold(true)
1434            .size(48.0)
1435            .color("FFFFFF")
1436            .font("Georgia");
1437    }
1438    {
1439        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1440        p.add_run("Environmental Impact Report")
1441            .size(24.0)
1442            .color("90EE90")
1443            .font("Georgia")
1444            .italic(true);
1445    }
1446    doc.add_paragraph("");
1447    {
1448        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1449        p.add_run("Tensorbee — Sustainability Division")
1450            .size(14.0)
1451            .color("C0C0C0");
1452    }
1453    {
1454        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1455        p.add_run("Prepared by Walter White, Chief Sustainability Officer")
1456            .size(11.0)
1457            .color("AAAAAA");
1458    }
1459
1460    // ── TOC ──
1461    doc.add_paragraph("").page_break_before(true);
1462    doc.insert_toc(doc.content_count(), 3);
1463
1464    // ── Section 1: Executive Overview ──
1465    doc.add_paragraph("").page_break_before(true);
1466    doc.add_paragraph("Executive Overview").style("Heading1");
1467    doc.add_paragraph(
1468        "This report presents Tensorbee's environmental performance for Q4 2025. Our \
1469         sustainability initiatives have yielded a 34% reduction in carbon emissions compared \
1470         to Q4 2024, exceeding our target of 25%. Key achievements include the transition to \
1471         100% renewable energy in our primary data centers and the launch of our carbon offset \
1472         marketplace.",
1473    )
1474    .first_line_indent(Length::inches(0.3));
1475
1476    // Image: performance chart
1477    let chart_img = create_chart_png(400, 200);
1478    doc.add_paragraph("");
1479    doc.add_picture(
1480        &chart_img,
1481        "performance_chart.png",
1482        Length::inches(5.0),
1483        Length::inches(2.5),
1484    )
1485    .alignment(Alignment::Center);
1486    {
1487        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1488        p.add_run("Figure 1: Quarterly Carbon Emissions (tonnes CO2e)")
1489            .italic(true)
1490            .size(9.0)
1491            .color("666666");
1492    }
1493
1494    // ── Section 2: Energy Consumption ──
1495    doc.add_paragraph("").page_break_before(true);
1496    doc.add_paragraph("Energy Consumption").style("Heading1");
1497
1498    doc.add_paragraph("Data Center Operations")
1499        .style("Heading2");
1500    doc.add_paragraph(
1501        "Our three primary data centers consumed a combined 4.2 GWh in Q4 2025, \
1502         a 12% reduction from Q3 2025 driven by improved cooling efficiency and \
1503         server consolidation.",
1504    );
1505
1506    // Data table
1507    {
1508        let mut tbl = doc
1509            .add_table(5, 4)
1510            .borders(BorderStyle::Single, 4, "2D5016")
1511            .width_pct(100.0);
1512        let hdrs = ["Data Center", "Capacity (MW)", "Usage (GWh)", "PUE"];
1513        for (c, h) in hdrs.iter().enumerate() {
1514            tbl.cell(0, c).unwrap().set_text(h);
1515            tbl.cell(0, c).unwrap().shading("2D5016");
1516        }
1517        let rows = [
1518            ("San Francisco (Primary)", "3.2", "1.8", "1.12"),
1519            ("Dublin (EU)", "2.1", "1.2", "1.18"),
1520            ("Singapore (APAC)", "1.8", "1.2", "1.24"),
1521            ("TOTAL", "7.1", "4.2", "1.17 avg"),
1522        ];
1523        for (i, (dc, cap, use_, pue)) in rows.iter().enumerate() {
1524            tbl.cell(i + 1, 0).unwrap().set_text(dc);
1525            tbl.cell(i + 1, 1).unwrap().set_text(cap);
1526            tbl.cell(i + 1, 2).unwrap().set_text(use_);
1527            tbl.cell(i + 1, 3).unwrap().set_text(pue);
1528            if i == 3 {
1529                for c in 0..4 {
1530                    tbl.cell(i + 1, c).unwrap().shading("E2EFDA");
1531                }
1532            }
1533        }
1534    }
1535
1536    doc.add_paragraph("");
1537
1538    doc.add_paragraph("Renewable Energy Mix").style("Heading2");
1539    doc.add_paragraph("Breakdown of energy sources across all facilities:");
1540
1541    doc.add_bullet_list_item("Solar PV: 42% (1.76 GWh)", 0);
1542    doc.add_bullet_list_item("Wind Power Purchase Agreements: 38% (1.60 GWh)", 0);
1543    doc.add_bullet_list_item("Hydroelectric: 12% (0.50 GWh)", 0);
1544    doc.add_bullet_list_item("Grid (non-renewable): 8% (0.34 GWh)", 0);
1545
1546    // ── Section 3: Water & Waste ──
1547    doc.add_paragraph("Water & Waste Management")
1548        .style("Heading1");
1549
1550    doc.add_paragraph("Water Usage").style("Heading2");
1551    doc.add_paragraph(
1552        "Total water consumption was 12.4 million gallons, a 15% reduction from Q3. \
1553         Our closed-loop cooling systems now recycle 78% of water used in cooling operations.",
1554    );
1555
1556    doc.add_paragraph("Waste Reduction").style("Heading2");
1557    doc.add_paragraph("E-waste management results for Q4:")
1558        .keep_with_next(true);
1559    {
1560        let mut tbl = doc
1561            .add_table(4, 3)
1562            .borders(BorderStyle::Single, 2, "6B8E23")
1563            .width_pct(80.0);
1564        tbl.cell(0, 0).unwrap().set_text("Category");
1565        tbl.cell(0, 0).unwrap().shading("6B8E23");
1566        tbl.cell(0, 1).unwrap().set_text("Weight (kg)");
1567        tbl.cell(0, 1).unwrap().shading("6B8E23");
1568        tbl.cell(0, 2).unwrap().set_text("Recycled %");
1569        tbl.cell(0, 2).unwrap().shading("6B8E23");
1570        let rows = [
1571            ("Server Hardware", "2,450", "94%"),
1572            ("Networking Equipment", "820", "91%"),
1573            ("Storage Media", "340", "99%"),
1574        ];
1575        for (i, (cat, wt, pct)) in rows.iter().enumerate() {
1576            tbl.cell(i + 1, 0).unwrap().set_text(cat);
1577            tbl.cell(i + 1, 1).unwrap().set_text(wt);
1578            tbl.cell(i + 1, 2).unwrap().set_text(pct);
1579        }
1580    }
1581
1582    // ── Section 4: Initiatives ──
1583    doc.add_paragraph("").page_break_before(true);
1584    doc.add_paragraph("Q1 2026 Initiatives").style("Heading1");
1585
1586    doc.add_paragraph("Planned Programs").style("Heading2");
1587    doc.add_numbered_list_item(
1588        "Deploy on-site battery storage at SF data center (2 MWh capacity)",
1589        0,
1590    );
1591    doc.add_numbered_list_item("Pilot immersion cooling in Dublin facility", 0);
1592    doc.add_numbered_list_item("Launch employee commute carbon offset program", 0);
1593    doc.add_numbered_list_item("Achieve ISO 14001 certification for Singapore facility", 0);
1594
1595    doc.add_paragraph("Investment Targets").style("Heading2");
1596    doc.add_paragraph("Sustainability CapEx allocation for FY2026:")
1597        .keep_with_next(true);
1598    {
1599        let mut tbl = doc
1600            .add_table(5, 2)
1601            .borders(BorderStyle::Single, 4, "2D5016");
1602        tbl.cell(0, 0).unwrap().set_text("Initiative");
1603        tbl.cell(0, 0).unwrap().shading("2D5016");
1604        tbl.cell(0, 1).unwrap().set_text("Budget");
1605        tbl.cell(0, 1).unwrap().shading("2D5016");
1606        let rows = [
1607            ("Battery Storage", "$1.2M"),
1608            ("Immersion Cooling Pilot", "$800K"),
1609            ("Solar Panel Expansion", "$2.1M"),
1610            ("Carbon Credits", "$500K"),
1611        ];
1612        for (i, (init, budget)) in rows.iter().enumerate() {
1613            tbl.cell(i + 1, 0).unwrap().set_text(init);
1614            tbl.cell(i + 1, 1).unwrap().set_text(budget);
1615            if i % 2 == 0 {
1616                tbl.cell(i + 1, 0).unwrap().shading("FFFAF0");
1617                tbl.cell(i + 1, 1).unwrap().shading("FFFAF0");
1618            }
1619        }
1620    }
1621
1622    // Image: sustainability roadmap
1623    let roadmap_img = create_sample_png(500, 100, [40, 80, 30]);
1624    doc.add_paragraph("");
1625    doc.add_picture(
1626        &roadmap_img,
1627        "roadmap.png",
1628        Length::inches(6.0),
1629        Length::inches(1.2),
1630    )
1631    .alignment(Alignment::Center);
1632    {
1633        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1634        p.add_run("Figure 2: 2026 Sustainability Roadmap")
1635            .italic(true)
1636            .size(9.0)
1637            .color("666666");
1638    }
1639
1640    doc.add_paragraph("");
1641    doc.add_paragraph(
1642        "For questions about this report, contact Walter White at sustainability@tensorbee.com.",
1643    )
1644    .shading("FFFAF0")
1645    .border_all(BorderStyle::Single, 2, "2D5016");
1646
1647    doc
1648}
1649
1650// =============================================================================
1651// 6. LETTER — Slate blue + silver scheme
1652// =============================================================================
1653fn generate_letter(_samples_dir: &Path) -> Document {
1654    let mut doc = Document::new();
1655
1656    // Colors: Slate #4A5568, Blue accent #3182CE
1657    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1658    doc.set_margins(
1659        Length::inches(1.25),
1660        Length::inches(1.25),
1661        Length::inches(1.25),
1662        Length::inches(1.25),
1663    );
1664    doc.set_title("Business Letter");
1665    doc.set_author("Walter White");
1666
1667    // Banner header using raw XML
1668    let logo_img = create_logo_png(220, 48);
1669    let banner = build_header_banner_xml(
1670        "rId1",
1671        &BannerOpts {
1672            bg_color: "4A5568",
1673            banner_width: 7772400,
1674            banner_height: 731520, // ~0.8"
1675            logo_width: 2011680,
1676            logo_height: 438912,
1677            logo_x_offset: 295125,
1678            logo_y_offset: 146304,
1679        },
1680    );
1681    doc.set_raw_header_with_images(
1682        banner,
1683        &[("rId1", &logo_img, "logo.png")],
1684        rdocx_oxml::header_footer::HdrFtrType::Default,
1685    );
1686    doc.set_margins(
1687        Length::twips(2000),
1688        Length::twips(1800),
1689        Length::twips(1440),
1690        Length::twips(1800),
1691    );
1692    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
1693
1694    // Footer with contact
1695    doc.set_footer(
1696        "Tensorbee | 123 Innovation Drive, Suite 400 | San Francisco, CA 94105 | tensorbee.com",
1697    );
1698
1699    // ── Sender Address ──
1700    doc.add_paragraph("Walter White");
1701    doc.add_paragraph("Chief Executive Officer");
1702    doc.add_paragraph("Tensorbee");
1703    doc.add_paragraph("123 Innovation Drive, Suite 400");
1704    doc.add_paragraph("San Francisco, CA 94105");
1705    doc.add_paragraph("");
1706    doc.add_paragraph("February 22, 2026");
1707
1708    doc.add_paragraph("");
1709
1710    // ── Recipient ──
1711    doc.add_paragraph("Ms. Sarah Chen");
1712    doc.add_paragraph("VP of Engineering");
1713    doc.add_paragraph("Meridian Dynamics LLC");
1714    doc.add_paragraph("456 Enterprise Boulevard");
1715    doc.add_paragraph("Austin, TX 78701");
1716
1717    doc.add_paragraph("");
1718
1719    // ── Subject ──
1720    {
1721        let mut p = doc.add_paragraph("");
1722        p.add_run("Re: Strategic Technology Partnership — Phase 2 Expansion")
1723            .bold(true);
1724    }
1725
1726    doc.add_paragraph("");
1727
1728    // ── Body ──
1729    doc.add_paragraph("Dear Ms. Chen,");
1730    doc.add_paragraph("");
1731
1732    doc.add_paragraph(
1733        "I am writing to express Tensorbee's enthusiasm for expanding our strategic technology \
1734         partnership with Meridian Dynamics. Following the successful completion of Phase 1, \
1735         which delivered a 40% improvement in your ML inference pipeline throughput, we believe \
1736         the foundation is firmly established for an ambitious Phase 2 engagement.",
1737    )
1738    .first_line_indent(Length::inches(0.5));
1739
1740    doc.add_paragraph(
1741        "During our recent executive review, your team highlighted three priority areas for \
1742         the next phase of collaboration:",
1743    )
1744    .first_line_indent(Length::inches(0.5));
1745
1746    doc.add_numbered_list_item(
1747        "Real-time anomaly detection for your financial transaction monitoring system, \
1748         targeting sub-100ms latency at 50,000 transactions per second.",
1749        0,
1750    );
1751    doc.add_numbered_list_item(
1752        "Federated learning infrastructure to enable model training across your distributed \
1753         data centers without centralizing sensitive financial data.",
1754        0,
1755    );
1756    doc.add_numbered_list_item(
1757        "MLOps automation to reduce your model deployment cycle from the current 5 days \
1758         to under 4 hours.",
1759        0,
1760    );
1761
1762    doc.add_paragraph(
1763        "Our engineering team has prepared a detailed technical proposal addressing each of \
1764         these areas. We have allocated a dedicated team of eight senior engineers, led by \
1765         our CTO, to ensure continuity with the Phase 1 team your organization has already \
1766         built a productive working relationship with.",
1767    )
1768    .first_line_indent(Length::inches(0.5));
1769
1770    doc.add_paragraph(
1771        "I would welcome the opportunity to present our Phase 2 proposal in person. My \
1772         assistant will reach out to coordinate a meeting at your convenience during the \
1773         first week of March.",
1774    )
1775    .first_line_indent(Length::inches(0.5));
1776
1777    doc.add_paragraph("");
1778
1779    doc.add_paragraph("Warm regards,");
1780    doc.add_paragraph("");
1781    doc.add_paragraph("");
1782
1783    // Signature line
1784    doc.add_paragraph("")
1785        .border_bottom(BorderStyle::Single, 4, "4A5568");
1786    {
1787        let mut p = doc.add_paragraph("");
1788        p.add_run("Walter White").bold(true).size(12.0);
1789    }
1790    {
1791        let mut p = doc.add_paragraph("");
1792        p.add_run("Chief Executive Officer, Tensorbee")
1793            .italic(true)
1794            .size(10.0)
1795            .color("666666");
1796    }
1797    {
1798        let mut p = doc.add_paragraph("");
1799        p.add_run("walter@tensorbee.com | +1 (415) 555-0199")
1800            .size(10.0)
1801            .color("888888");
1802    }
1803
1804    doc
1805}
1806
1807// =============================================================================
1808// 7. EMPLOYMENT CONTRACT — Purple + charcoal scheme
1809// =============================================================================
1810fn generate_contract(_samples_dir: &Path) -> Document {
1811    let mut doc = Document::new();
1812
1813    // Colors: Purple #5B2C6F, Plum #8E44AD, Gray #2C3E50
1814    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1815    doc.set_margins(
1816        Length::inches(1.25),
1817        Length::inches(1.0),
1818        Length::inches(1.0),
1819        Length::inches(1.0),
1820    );
1821    doc.set_title("Employment Agreement");
1822    doc.set_author("Tensorbee HR Department");
1823    doc.set_subject("Employment Contract — Walter White");
1824    doc.set_keywords("employment, contract, agreement, Tensorbee");
1825
1826    doc.set_header("TENSORBEE — Employment Agreement");
1827    doc.set_footer("Employment Agreement — Walter White — Page");
1828
1829    // ── Title Block ──
1830    doc.add_paragraph("")
1831        .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1832    {
1833        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1834        p.add_run("EMPLOYMENT AGREEMENT")
1835            .bold(true)
1836            .size(24.0)
1837            .color("5B2C6F")
1838            .font("Georgia");
1839    }
1840    doc.add_paragraph("")
1841        .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1842    doc.add_paragraph("");
1843
1844    // ── Parties ──
1845    doc.add_paragraph(
1846        "This Employment Agreement (\"Agreement\") is entered into as of February 22, 2026 \
1847         (\"Effective Date\"), by and between:",
1848    );
1849
1850    doc.add_paragraph("");
1851
1852    {
1853        let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1854        p.add_run("EMPLOYER: ").bold(true);
1855        p.add_run(
1856            "Tensorbee, Inc., a Delaware corporation with its principal place of business at \
1857                   123 Innovation Drive, Suite 400, San Francisco, CA 94105 (\"Company\")",
1858        );
1859    }
1860    doc.add_paragraph("");
1861    {
1862        let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1863        p.add_run("EMPLOYEE: ").bold(true);
1864        p.add_run(
1865            "Walter White, residing at 308 Negra Arroyo Lane, Albuquerque, NM 87104 (\"Employee\")",
1866        );
1867    }
1868
1869    doc.add_paragraph("");
1870    doc.add_paragraph("The Company and Employee are collectively referred to as the \"Parties.\"");
1871    doc.add_paragraph("");
1872
1873    // ── Article 1: Position and Duties ──
1874    doc.add_paragraph("Article 1 — Position and Duties")
1875        .style("Heading1");
1876    {
1877        let mut p = doc.add_paragraph("");
1878        p.add_run("1.1 ").bold(true);
1879        p.add_run("The Company hereby employs the Employee as ");
1880        p.add_run("Chief Executive Officer (CEO)")
1881            .bold(true)
1882            .italic(true);
1883        p.add_run(", reporting directly to the Board of Directors.");
1884    }
1885    {
1886        let mut p = doc.add_paragraph("");
1887        p.add_run("1.2 ").bold(true);
1888        p.add_run("The Employee shall devote full working time, attention, and best efforts to \
1889                   the performance of duties as reasonably assigned by the Board, including but not limited to:");
1890    }
1891    doc.add_bullet_list_item(
1892        "Setting and executing the Company's strategic vision and business plan",
1893        0,
1894    );
1895    doc.add_bullet_list_item(
1896        "Overseeing all operations, engineering, and commercial activities",
1897        0,
1898    );
1899    doc.add_bullet_list_item(
1900        "Representing the Company to investors, customers, and the public",
1901        0,
1902    );
1903    doc.add_bullet_list_item("Recruiting, developing, and retaining key talent", 0);
1904
1905    {
1906        let mut p = doc.add_paragraph("");
1907        p.add_run("1.3 ").bold(true);
1908        p.add_run(
1909            "The Employee's primary work location shall be the Company's San Francisco \
1910                   headquarters, with reasonable travel as required by business needs.",
1911        );
1912    }
1913
1914    // ── Article 2: Compensation ──
1915    doc.add_paragraph("Article 2 — Compensation and Benefits")
1916        .style("Heading1");
1917    {
1918        let mut p = doc.add_paragraph("");
1919        p.add_run("2.1 Base Salary. ").bold(true);
1920        p.add_run("The Company shall pay the Employee an annual base salary of ");
1921        p.add_run("$375,000.00").bold(true);
1922        p.add_run(" (Three Hundred Seventy-Five Thousand Dollars), payable in accordance with the \
1923                   Company's standard payroll schedule, less applicable withholdings and deductions.");
1924    }
1925    {
1926        let mut p = doc.add_paragraph("");
1927        p.add_run("2.2 Annual Bonus. ").bold(true);
1928        p.add_run("The Employee shall be eligible for an annual performance bonus of up to ");
1929        p.add_run("40%").bold(true);
1930        p.add_run(
1931            " of base salary, based on achievement of mutually agreed performance objectives.",
1932        );
1933    }
1934    {
1935        let mut p = doc.add_paragraph("");
1936        p.add_run("2.3 Equity. ").bold(true);
1937        p.add_run("Subject to Board approval, the Employee shall receive a stock option grant of ");
1938        p.add_run("500,000 shares").bold(true);
1939        p.add_run(
1940            " of the Company's common stock, vesting over four (4) years with a one-year cliff, \
1941                   at an exercise price equal to the fair market value on the date of grant.",
1942        );
1943    }
1944
1945    // Compensation summary table
1946    doc.add_paragraph("");
1947    {
1948        let mut tbl = doc
1949            .add_table(5, 2)
1950            .borders(BorderStyle::Single, 4, "5B2C6F")
1951            .width_pct(70.0)
1952            .alignment(Alignment::Center);
1953        tbl.cell(0, 0).unwrap().set_text("Compensation Element");
1954        tbl.cell(0, 0).unwrap().shading("5B2C6F");
1955        tbl.cell(0, 1).unwrap().set_text("Value");
1956        tbl.cell(0, 1).unwrap().shading("5B2C6F");
1957        let items = [
1958            ("Base Salary", "$375,000/year"),
1959            ("Target Bonus", "Up to 40% ($150,000)"),
1960            ("Equity Grant", "500,000 shares (4yr vest)"),
1961            ("Total Target Comp", "$525,000 + equity"),
1962        ];
1963        for (i, (elem, val)) in items.iter().enumerate() {
1964            tbl.cell(i + 1, 0).unwrap().set_text(elem);
1965            tbl.cell(i + 1, 1).unwrap().set_text(val);
1966            if i == 3 {
1967                tbl.cell(i + 1, 0).unwrap().shading("E8DAEF");
1968                tbl.cell(i + 1, 1).unwrap().shading("E8DAEF");
1969            }
1970        }
1971    }
1972
1973    // ── Article 3: Benefits ──
1974    doc.add_paragraph("").page_break_before(true);
1975    doc.add_paragraph("Article 3 — Benefits").style("Heading1");
1976    {
1977        let mut p = doc.add_paragraph("");
1978        p.add_run("3.1 ").bold(true);
1979        p.add_run(
1980            "The Employee shall be entitled to participate in all benefit programs \
1981                   generally available to senior executives, including:",
1982        );
1983    }
1984    doc.add_bullet_list_item(
1985        "Medical, dental, and vision insurance (100% premium coverage for employee and dependents)",
1986        0,
1987    );
1988    doc.add_bullet_list_item("401(k) retirement plan with 6% company match", 0);
1989    doc.add_bullet_list_item("Life insurance and long-term disability coverage", 0);
1990    doc.add_bullet_list_item("Annual professional development allowance of $10,000", 0);
1991
1992    {
1993        let mut p = doc.add_paragraph("");
1994        p.add_run("3.2 Paid Time Off. ").bold(true);
1995        p.add_run(
1996            "The Employee shall receive 25 days of paid vacation per year, plus Company holidays, \
1997                   accruing on a monthly basis.",
1998        );
1999    }
2000
2001    // ── Article 4: Term and Termination ──
2002    doc.add_paragraph("Article 4 — Term and Termination")
2003        .style("Heading1");
2004    {
2005        let mut p = doc.add_paragraph("");
2006        p.add_run("4.1 At-Will Employment. ").bold(true);
2007        p.add_run(
2008            "This Agreement is for at-will employment and may be terminated by either Party \
2009                   at any time, with or without cause, subject to the notice provisions herein.",
2010        );
2011    }
2012    {
2013        let mut p = doc.add_paragraph("");
2014        p.add_run("4.2 Notice Period. ").bold(true);
2015        p.add_run("Either Party shall provide ");
2016        p.add_run("ninety (90) days").bold(true);
2017        p.add_run(" written notice of termination, or pay in lieu thereof.");
2018    }
2019    {
2020        let mut p = doc.add_paragraph("");
2021        p.add_run("4.3 Severance. ").bold(true);
2022        p.add_run("In the event of termination by the Company without Cause, the Employee shall \
2023                   receive (a) twelve (12) months of base salary continuation, (b) pro-rata bonus \
2024                   for the year of termination, and (c) twelve (12) months of COBRA premium coverage.");
2025    }
2026
2027    // ── Article 5: Confidentiality ──
2028    doc.add_paragraph("Article 5 — Confidentiality and IP")
2029        .style("Heading1");
2030    {
2031        let mut p = doc.add_paragraph("");
2032        p.add_run("5.1 ").bold(true);
2033        p.add_run(
2034            "The Employee agrees to maintain strict confidentiality of all proprietary \
2035                   information, trade secrets, and business plans of the Company during and after \
2036                   employment.",
2037        );
2038    }
2039    {
2040        let mut p = doc.add_paragraph("");
2041        p.add_run("5.2 ").bold(true);
2042        p.add_run(
2043            "All intellectual property, inventions, and works of authorship created by the \
2044                   Employee during the term of employment and within the scope of duties shall be \
2045                   the sole and exclusive property of the Company.",
2046        );
2047    }
2048
2049    // ── Article 6: Non-Compete ──
2050    doc.add_paragraph("Article 6 — Non-Competition")
2051        .style("Heading1");
2052    {
2053        let mut p = doc.add_paragraph("");
2054        p.add_run("6.1 ").bold(true);
2055        p.add_run("For a period of twelve (12) months following termination, the Employee agrees \
2056                   not to engage in any business that directly competes with the Company's core \
2057                   business of AI infrastructure and ML operations services, within the United States.");
2058    }
2059
2060    // ── Article 7: General Provisions ──
2061    doc.add_paragraph("Article 7 — General Provisions")
2062        .style("Heading1");
2063    {
2064        let mut p = doc.add_paragraph("");
2065        p.add_run("7.1 Governing Law. ").bold(true);
2066        p.add_run(
2067            "This Agreement shall be governed by and construed in accordance with the laws of \
2068                   the State of California.",
2069        );
2070    }
2071    {
2072        let mut p = doc.add_paragraph("");
2073        p.add_run("7.2 Entire Agreement. ").bold(true);
2074        p.add_run(
2075            "This Agreement constitutes the entire agreement between the Parties and supersedes \
2076                   all prior negotiations, representations, and agreements.",
2077        );
2078    }
2079    {
2080        let mut p = doc.add_paragraph("");
2081        p.add_run("7.3 Amendment. ").bold(true);
2082        p.add_run(
2083            "This Agreement may only be amended by a written instrument signed by both Parties.",
2084        );
2085    }
2086
2087    // ── Signature Block ──
2088    doc.add_paragraph("").page_break_before(true);
2089    doc.add_paragraph("IN WITNESS WHEREOF, the Parties have executed this Employment Agreement as of the Effective Date.")
2090        .space_after(Length::pt(24.0));
2091
2092    // Signature table
2093    {
2094        let mut tbl = doc.add_table(6, 2).width_pct(100.0);
2095        tbl.cell(0, 0).unwrap().set_text("FOR THE COMPANY:");
2096        tbl.cell(0, 0).unwrap().shading("5B2C6F");
2097        tbl.cell(0, 1).unwrap().set_text("EMPLOYEE:");
2098        tbl.cell(0, 1).unwrap().shading("5B2C6F");
2099
2100        tbl.cell(1, 0).unwrap().set_text("");
2101        tbl.cell(1, 1).unwrap().set_text("");
2102        tbl.row(1).unwrap().height(Length::pt(40.0));
2103
2104        tbl.cell(2, 0)
2105            .unwrap()
2106            .set_text("Signature: ___________________________");
2107        tbl.cell(2, 1)
2108            .unwrap()
2109            .set_text("Signature: ___________________________");
2110        tbl.cell(3, 0)
2111            .unwrap()
2112            .set_text("Name: Board Representative");
2113        tbl.cell(3, 1).unwrap().set_text("Name: Walter White");
2114        tbl.cell(4, 0)
2115            .unwrap()
2116            .set_text("Title: Chair, Board of Directors");
2117        tbl.cell(4, 1)
2118            .unwrap()
2119            .set_text("Title: Chief Executive Officer");
2120        tbl.cell(5, 0).unwrap().set_text("Date: _______________");
2121        tbl.cell(5, 1).unwrap().set_text("Date: _______________");
2122    }
2123
2124    doc
2125}
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_samples.rs (line 251)
34fn generate_feature_showcase(path: &Path) {
35    let mut doc = Document::new();
36
37    // =========================================================================
38    // PAGE SETUP & METADATA
39    // =========================================================================
40    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
41    doc.set_margins(
42        Length::inches(1.0), // top
43        Length::inches(1.0), // right
44        Length::inches(1.0), // bottom
45        Length::inches(1.0), // left
46    );
47    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
48    doc.set_gutter(Length::twips(0));
49
50    doc.set_title("rdocx Feature Showcase");
51    doc.set_author("rdocx Sample Generator");
52    doc.set_subject("Comprehensive feature demonstration");
53    doc.set_keywords("rdocx, docx, rust, sample");
54
55    // Header & Footer
56    doc.set_header("rdocx Feature Showcase");
57    doc.set_footer("Generated by rdocx — Page");
58
59    // Different first page header
60    doc.set_different_first_page(true);
61    doc.set_first_page_header("rdocx");
62    doc.set_first_page_footer("Feature Showcase — Cover Page");
63
64    // =========================================================================
65    // PAGE 1: COVER PAGE — background image, run formatting
66    // =========================================================================
67    let bg_cover = create_sample_png(612, 792, [30, 60, 120]);
68    doc.add_background_image(&bg_cover, "cover_bg.png");
69
70    doc.add_paragraph(""); // spacer
71    doc.add_paragraph(""); // spacer
72    doc.add_paragraph(""); // spacer
73
74    {
75        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
76        p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
77    }
78    {
79        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
80        p.add_run("Feature Showcase")
81            .size(28.0)
82            .color("FFFFFF")
83            .italic(true);
84    }
85
86    doc.add_paragraph(""); // spacer
87
88    {
89        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
90        p.add_run("A comprehensive demonstration of every feature")
91            .size(14.0)
92            .color("CCDDFF");
93    }
94    {
95        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
96        p.add_run("provided by the rdocx Rust crate for DOCX generation.")
97            .size(14.0)
98            .color("CCDDFF");
99    }
100
101    // =========================================================================
102    // PAGE 2: TEXT FORMATTING
103    // =========================================================================
104    doc.add_paragraph("").page_break_before(true);
105
106    doc.add_paragraph("1. Text Formatting").style("Heading1");
107
108    doc.add_paragraph("This section demonstrates paragraph and run-level formatting options.");
109    doc.add_paragraph("");
110
111    // --- Paragraph alignment ---
112    doc.add_paragraph("Paragraph Alignment").style("Heading2");
113
114    doc.add_paragraph("This paragraph is left-aligned (the default).")
115        .alignment(Alignment::Left);
116    doc.add_paragraph("This paragraph is center-aligned.")
117        .alignment(Alignment::Center);
118    doc.add_paragraph("This paragraph is right-aligned.")
119        .alignment(Alignment::Right);
120    doc.add_paragraph(
121        "This paragraph is justified. To demonstrate justified text properly, it needs \
122         to be long enough to span multiple lines so the word spacing adjustment is visible \
123         across the full width of the text area on the page.",
124    )
125    .alignment(Alignment::Justify);
126
127    doc.add_paragraph("");
128
129    // --- Run formatting ---
130    doc.add_paragraph("Run Formatting").style("Heading2");
131
132    {
133        let mut p = doc.add_paragraph("");
134        p.add_run("Bold text").bold(true);
135        p.add_run(" | ");
136        p.add_run("Italic text").italic(true);
137        p.add_run(" | ");
138        p.add_run("Bold + Italic").bold(true).italic(true);
139    }
140    {
141        let mut p = doc.add_paragraph("");
142        p.add_run("Single underline").underline(true);
143        p.add_run(" | ");
144        p.add_run("Strikethrough").strike(true);
145        p.add_run(" | ");
146        p.add_run("Double strikethrough").double_strike(true);
147    }
148    {
149        let mut p = doc.add_paragraph("");
150        p.add_run("Red text").color("FF0000");
151        p.add_run(" | ");
152        p.add_run("Blue text").color("0000FF");
153        p.add_run(" | ");
154        p.add_run("Green text").color("00AA00");
155        p.add_run(" | ");
156        p.add_run("Highlighted").highlight("FFFF00");
157    }
158    {
159        let mut p = doc.add_paragraph("");
160        p.add_run("8pt small").size(8.0);
161        p.add_run(" | ");
162        p.add_run("11pt normal").size(11.0);
163        p.add_run(" | ");
164        p.add_run("16pt large").size(16.0);
165        p.add_run(" | ");
166        p.add_run("24pt extra-large").size(24.0);
167    }
168    {
169        let mut p = doc.add_paragraph("");
170        p.add_run("Arial font").font("Arial");
171        p.add_run(" | ");
172        p.add_run("Times New Roman font").font("Times New Roman");
173        p.add_run(" | ");
174        p.add_run("Courier New font").font("Courier New");
175    }
176    {
177        let mut p = doc.add_paragraph("");
178        p.add_run("Normal");
179        p.add_run(" H").size(11.0);
180        p.add_run("2").subscript();
181        p.add_run("O (subscript)").size(11.0);
182        p.add_run(" | E = mc").size(11.0);
183        p.add_run("2").superscript();
184        p.add_run(" (superscript)").size(11.0);
185    }
186    {
187        let mut p = doc.add_paragraph("");
188        p.add_run("ALL CAPS").all_caps(true);
189        p.add_run(" | ");
190        p.add_run("Small Caps").small_caps(true);
191        p.add_run(" | ");
192        p.add_run("Expanded spacing")
193            .character_spacing(Length::twips(40));
194    }
195
196    doc.add_paragraph("");
197
198    // --- Paragraph formatting ---
199    doc.add_paragraph("Paragraph Formatting").style("Heading2");
200
201    doc.add_paragraph("Paragraph with shading (light green background)")
202        .shading("E2EFDA");
203
204    doc.add_paragraph("Paragraph with bottom border")
205        .border_bottom(BorderStyle::Single, 6, "2E75B6");
206
207    doc.add_paragraph("Paragraph with all borders")
208        .border_all(BorderStyle::Single, 4, "FF0000");
209
210    doc.add_paragraph("Paragraph with 1-inch left indent and hanging indent")
211        .indent_left(Length::inches(1.0))
212        .hanging_indent(Length::inches(0.5));
213
214    doc.add_paragraph("Paragraph with first-line indent of 0.5 inches")
215        .first_line_indent(Length::inches(0.5));
216
217    doc.add_paragraph("Paragraph with extra space before (24pt) and after (12pt)")
218        .space_before(Length::pt(24.0))
219        .space_after(Length::pt(12.0));
220
221    doc.add_paragraph(
222        "Paragraph with double line spacing. This text should have extra vertical \
223         space between lines to demonstrate the line_spacing_multiple setting.",
224    )
225    .line_spacing_multiple(2.0);
226
227    doc.add_paragraph("Paragraph with keep-with-next (won't break from the next paragraph)")
228        .keep_with_next(true);
229    doc.add_paragraph("(This stays with the paragraph above.)");
230
231    // =========================================================================
232    // PAGE 3: LISTS & TAB STOPS
233    // =========================================================================
234    doc.add_paragraph("").page_break_before(true);
235
236    doc.add_paragraph("2. Lists").style("Heading1");
237
238    doc.add_paragraph("Bullet List").style("Heading2");
239
240    doc.add_bullet_list_item("First bullet item", 0);
241    doc.add_bullet_list_item("Second bullet item", 0);
242    doc.add_bullet_list_item("Nested level 1", 1);
243    doc.add_bullet_list_item("Nested level 2", 2);
244    doc.add_bullet_list_item("Back to level 1", 1);
245    doc.add_bullet_list_item("Third bullet item", 0);
246
247    doc.add_paragraph("");
248
249    doc.add_paragraph("Numbered List").style("Heading2");
250
251    doc.add_numbered_list_item("First numbered item", 0);
252    doc.add_numbered_list_item("Second numbered item", 0);
253    doc.add_numbered_list_item("Sub-item A", 1);
254    doc.add_numbered_list_item("Sub-item B", 1);
255    doc.add_numbered_list_item("Third numbered item", 0);
256
257    doc.add_paragraph("");
258
259    // --- Tab stops ---
260    doc.add_paragraph("Tab Stops").style("Heading2");
261
262    doc.add_paragraph("Left\tCenter\tRight\tDecimal")
263        .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
264        .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
265        .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
266        .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
267
268    doc.add_paragraph("Item\t........\tPrice")
269        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
270        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
271        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
272
273    doc.add_paragraph("Widget A\t........\t$19.99")
274        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
275        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
276        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
277
278    doc.add_paragraph("Gadget B\t________\t$249.50")
279        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
280        .add_tab_stop_with_leader(
281            TabAlignment::Right,
282            Length::inches(4.0),
283            TabLeader::Underscore,
284        )
285        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
286
287    // =========================================================================
288    // PAGE 4: TABLES
289    // =========================================================================
290    doc.add_paragraph("").page_break_before(true);
291
292    doc.add_paragraph("3. Tables").style("Heading1");
293
294    // --- Basic table with borders ---
295    doc.add_paragraph("Basic Table with Borders")
296        .style("Heading2");
297
298    {
299        let mut tbl = doc.add_table(4, 3);
300        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
301
302        // Header row
303        for col in 0..3 {
304            tbl.cell(0, col).unwrap().shading("2E75B6");
305        }
306        tbl.cell(0, 0).unwrap().set_text("Name");
307        tbl.cell(0, 1).unwrap().set_text("Role");
308        tbl.cell(0, 2).unwrap().set_text("Location");
309
310        tbl.cell(1, 0).unwrap().set_text("Alice Johnson");
311        tbl.cell(1, 1).unwrap().set_text("Engineering Lead");
312        tbl.cell(1, 2).unwrap().set_text("New York");
313
314        tbl.cell(2, 0).unwrap().set_text("Bob Smith");
315        tbl.cell(2, 1).unwrap().set_text("Product Manager");
316        tbl.cell(2, 2).unwrap().set_text("San Francisco");
317
318        tbl.cell(3, 0).unwrap().set_text("Carol Davis");
319        tbl.cell(3, 1).unwrap().set_text("Designer");
320        tbl.cell(3, 2).unwrap().set_text("London");
321    }
322
323    doc.add_paragraph("");
324
325    // --- Table with cell merging ---
326    doc.add_paragraph("Table with Cell Merging & Vertical Alignment")
327        .style("Heading2");
328
329    {
330        let mut tbl = doc.add_table(4, 4);
331        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
332        tbl = tbl.width_pct(100.0);
333
334        // Header spanning all columns
335        tbl.cell(0, 0).unwrap().set_text("Quarterly Revenue Report");
336        tbl.cell(0, 0).unwrap().shading("1F4E79");
337        tbl.cell(0, 0).unwrap().grid_span(4);
338
339        // Sub-header
340        tbl.cell(1, 0).unwrap().set_text("Region");
341        tbl.cell(1, 0).unwrap().shading("D6E4F0");
342        tbl.cell(1, 1).unwrap().set_text("Q1");
343        tbl.cell(1, 1).unwrap().shading("D6E4F0");
344        tbl.cell(1, 2).unwrap().set_text("Q2");
345        tbl.cell(1, 2).unwrap().shading("D6E4F0");
346        tbl.cell(1, 3).unwrap().set_text("Total");
347        tbl.cell(1, 3).unwrap().shading("D6E4F0");
348
349        // Data
350        tbl.cell(2, 0).unwrap().set_text("North America");
351        tbl.cell(2, 1).unwrap().set_text("$2.4M");
352        tbl.cell(2, 2).unwrap().set_text("$2.7M");
353        tbl.cell(2, 3).unwrap().set_text("$5.1M");
354
355        tbl.cell(3, 0).unwrap().set_text("Europe");
356        tbl.cell(3, 1).unwrap().set_text("$1.8M");
357        tbl.cell(3, 2).unwrap().set_text("$2.0M");
358        tbl.cell(3, 3).unwrap().set_text("$3.8M");
359
360        // Vertical alignment on data cells
361        tbl.cell(2, 3)
362            .unwrap()
363            .vertical_alignment(VerticalAlignment::Center);
364        tbl.cell(3, 3)
365            .unwrap()
366            .vertical_alignment(VerticalAlignment::Bottom);
367    }
368
369    doc.add_paragraph("");
370
371    // --- Table with vertical merge ---
372    doc.add_paragraph("Table with Vertical Merge")
373        .style("Heading2");
374
375    {
376        let mut tbl = doc.add_table(4, 3);
377        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
378
379        tbl.cell(0, 0).unwrap().set_text("Category");
380        tbl.cell(0, 0).unwrap().shading("E2EFDA");
381        tbl.cell(0, 1).unwrap().set_text("Item");
382        tbl.cell(0, 1).unwrap().shading("E2EFDA");
383        tbl.cell(0, 2).unwrap().set_text("Price");
384        tbl.cell(0, 2).unwrap().shading("E2EFDA");
385
386        // "Hardware" spans rows 1-2
387        tbl.cell(1, 0).unwrap().set_text("Hardware");
388        tbl.cell(1, 0).unwrap().v_merge_restart();
389        tbl.cell(1, 1).unwrap().set_text("Laptop");
390        tbl.cell(1, 2).unwrap().set_text("$1,200");
391
392        tbl.cell(2, 0).unwrap().v_merge_continue();
393        tbl.cell(2, 1).unwrap().set_text("Monitor");
394        tbl.cell(2, 2).unwrap().set_text("$450");
395
396        // "Software" on row 3
397        tbl.cell(3, 0).unwrap().set_text("Software");
398        tbl.cell(3, 1).unwrap().set_text("IDE License");
399        tbl.cell(3, 2).unwrap().set_text("$200/yr");
400    }
401
402    doc.add_paragraph("");
403
404    // --- Nested table ---
405    doc.add_paragraph("Nested Table").style("Heading2");
406
407    {
408        let mut tbl = doc.add_table(2, 2);
409        tbl = tbl.borders(BorderStyle::Single, 6, "2E75B6");
410
411        tbl.cell(0, 0).unwrap().set_text("Outer Cell (0,0)");
412        tbl.cell(0, 1).unwrap().set_text("Outer Cell (0,1)");
413        tbl.cell(1, 0).unwrap().set_text("Outer Cell (1,0)");
414
415        // Nested table inside cell (1,1)
416        {
417            let mut cell = tbl.cell(1, 1).unwrap();
418            cell.set_text("Contains nested table:");
419            let mut nested = cell.add_table(2, 2);
420            nested = nested.borders(BorderStyle::Single, 2, "FF6600");
421            nested.cell(0, 0).unwrap().set_text("Inner A");
422            nested.cell(0, 1).unwrap().set_text("Inner B");
423            nested.cell(1, 0).unwrap().set_text("Inner C");
424            nested.cell(1, 1).unwrap().set_text("Inner D");
425        }
426    }
427
428    // =========================================================================
429    // PAGE 5: IMAGES
430    // =========================================================================
431    doc.add_paragraph("").page_break_before(true);
432
433    doc.add_paragraph("4. Images").style("Heading1");
434
435    doc.add_paragraph("Inline Image").style("Heading2");
436
437    doc.add_paragraph("Below is an inline image (200x50 pixels, blue gradient):");
438    let inline_img = create_sample_png(200, 50, [0, 80, 200]);
439    doc.add_picture(
440        &inline_img,
441        "inline_chart.png",
442        Length::inches(3.0),
443        Length::inches(0.75),
444    );
445
446    doc.add_paragraph("");
447
448    doc.add_paragraph("Header Image").style("Heading2");
449
450    // Replace the text-only header with an image header
451    let header_img = create_sample_png(400, 40, [40, 40, 40]);
452    doc.set_header_image(
453        &header_img,
454        "header_logo.png",
455        Length::inches(2.0),
456        Length::inches(0.2),
457    );
458
459    doc.add_paragraph(
460        "The document header has been replaced with an inline image. \
461         Check the header area at the top of this page.",
462    );
463
464    doc.add_paragraph("");
465    doc.add_paragraph(
466        "Note: The cover page uses a full-page background image behind the text, \
467         demonstrated on page 1 via add_background_image().",
468    );
469
470    // =========================================================================
471    // PAGE 6: CONTENT MANIPULATION — placeholder replacement, insertion
472    // =========================================================================
473    doc.add_paragraph("").page_break_before(true);
474
475    doc.add_paragraph("5. Content Manipulation")
476        .style("Heading1");
477
478    // --- Placeholder replacement ---
479    doc.add_paragraph("Placeholder Replacement")
480        .style("Heading2");
481
482    doc.add_paragraph(
483        "Before replacement, this document contained {{customer}} and {{date}} placeholders.",
484    );
485
486    {
487        let mut p = doc.add_paragraph("");
488        p.add_run("Customer: ").bold(true);
489        p.add_run("{{customer}}");
490    }
491    {
492        let mut p = doc.add_paragraph("");
493        p.add_run("Date: ").bold(true);
494        p.add_run("{{date}}");
495    }
496    doc.add_paragraph("Reference: {{ref_number}}");
497
498    // Table with placeholders
499    {
500        let mut tbl = doc.add_table(3, 2);
501        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
502        tbl.cell(0, 0).unwrap().set_text("Field");
503        tbl.cell(0, 0).unwrap().shading("D6E4F0");
504        tbl.cell(0, 1).unwrap().set_text("Value");
505        tbl.cell(0, 1).unwrap().shading("D6E4F0");
506        tbl.cell(1, 0).unwrap().set_text("Project");
507        tbl.cell(1, 1).unwrap().set_text("{{project}}");
508        tbl.cell(2, 0).unwrap().set_text("Status");
509        tbl.cell(2, 1).unwrap().set_text("{{status}}");
510    }
511
512    // Perform replacements
513    let mut replacements = HashMap::new();
514    replacements.insert("{{customer}}", "Acme Corporation");
515    replacements.insert("{{date}}", "February 22, 2026");
516    replacements.insert("{{ref_number}}", "REF-2026-001");
517    replacements.insert("{{project}}", "Infrastructure Upgrade");
518    replacements.insert("{{status}}", "In Progress");
519    let replace_count = doc.replace_all(&replacements);
520
521    doc.add_paragraph("");
522    doc.add_paragraph(&format!(
523        "(Replaced {} placeholders above — in body text and table cells)",
524        replace_count
525    ));
526
527    doc.add_paragraph("");
528
529    // --- Content insertion ---
530    doc.add_paragraph("Content Insertion").style("Heading2");
531
532    doc.add_paragraph("Section A: First section of content.");
533    doc.add_paragraph("Section C: Third section of content.");
534
535    // Insert "Section B" between A and C
536    if let Some(idx) = doc.find_content_index("Section C") {
537        doc.insert_paragraph(
538            idx,
539            "Section B: Inserted between A and C using find_content_index().",
540        );
541    }
542
543    doc.add_paragraph("");
544    doc.add_paragraph(
545        "The paragraph above ('Section B') was inserted at a specific position \
546         using find_content_index() + insert_paragraph().",
547    );
548
549    // =========================================================================
550    // PAGE 7: LANDSCAPE — section break, wide table
551    // =========================================================================
552    doc.add_paragraph("").section_break(SectionBreak::NextPage);
553
554    doc.add_paragraph("6. Mixed Page Orientation")
555        .style("Heading1");
556
557    doc.add_paragraph(
558        "This page is in LANDSCAPE orientation. It was created using a section break \
559         followed by section_landscape(). This is useful for wide tables or charts.",
560    );
561
562    doc.add_paragraph("");
563
564    // Wide table for landscape
565    {
566        let mut tbl = doc.add_table(4, 7);
567        tbl = tbl.borders(BorderStyle::Single, 4, "2E75B6");
568
569        let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
570        for (col, h) in headers.iter().enumerate() {
571            tbl.cell(0, col).unwrap().set_text(h);
572            tbl.cell(0, col).unwrap().shading("2E75B6");
573        }
574
575        let data = [
576            [
577                "North America",
578                "$1.2M",
579                "$1.3M",
580                "$1.4M",
581                "$1.5M",
582                "$1.6M",
583                "$7.0M",
584            ],
585            [
586                "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
587            ],
588            [
589                "Asia Pacific",
590                "$0.5M",
591                "$0.6M",
592                "$0.7M",
593                "$0.7M",
594                "$0.8M",
595                "$3.3M",
596            ],
597        ];
598        for (row_idx, row_data) in data.iter().enumerate() {
599            for (col, val) in row_data.iter().enumerate() {
600                tbl.cell(row_idx + 1, col).unwrap().set_text(val);
601            }
602        }
603    }
604
605    // End landscape, return to portrait
606    doc.add_paragraph("")
607        .section_break(SectionBreak::NextPage)
608        .section_landscape();
609
610    // =========================================================================
611    // PAGE 8: BACK TO PORTRAIT — styles, final notes
612    // =========================================================================
613    doc.add_paragraph("7. Custom Styles & Summary")
614        .style("Heading1");
615
616    doc.add_paragraph(
617        "This final page is back in portrait orientation after a section break. \
618         The document has demonstrated:",
619    );
620
621    doc.add_bullet_list_item(
622        "Page setup: size, margins, header/footer distance, gutter",
623        0,
624    );
625    doc.add_bullet_list_item("Document metadata: title, author, subject, keywords", 0);
626    doc.add_bullet_list_item("Headers and footers: text, images, different first page", 0);
627    doc.add_bullet_list_item("Background images: full-page behind text", 0);
628    doc.add_bullet_list_item(
629        "Text formatting: bold, italic, underline, strike, color, size, font",
630        0,
631    );
632    doc.add_bullet_list_item(
633        "Advanced run formatting: superscript, subscript, caps, spacing",
634        0,
635    );
636    doc.add_bullet_list_item(
637        "Paragraph formatting: alignment, borders, shading, spacing, indentation",
638        0,
639    );
640    doc.add_bullet_list_item("Bullet and numbered lists with nesting levels", 0);
641    doc.add_bullet_list_item("Tab stops with dot/underscore leaders", 0);
642    doc.add_bullet_list_item(
643        "Tables: borders, shading, column spans, row spans, nesting",
644        0,
645    );
646    doc.add_bullet_list_item("Vertical alignment in table cells", 0);
647    doc.add_bullet_list_item("Inline images", 0);
648    doc.add_bullet_list_item("Placeholder replacement in body and table cells", 0);
649    doc.add_bullet_list_item("Content insertion at specific positions", 0);
650    doc.add_bullet_list_item(
651        "Section breaks with mixed portrait/landscape orientation",
652        0,
653    );
654
655    doc.add_paragraph("");
656    doc.add_paragraph("All features above were built entirely from scratch using the rdocx API.")
657        .alignment(Alignment::Center)
658        .shading("E2EFDA")
659        .border_all(BorderStyle::Single, 2, "00AA00");
660
661    doc.save(path).unwrap();
662}
More examples
Hide additional examples
examples/generate_all_samples.rs (line 312)
86fn generate_feature_showcase(_samples_dir: &Path) -> Document {
87    let mut doc = Document::new();
88
89    // ── Page Setup & Metadata ──
90    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
91    doc.set_margins(
92        Length::inches(1.0),
93        Length::inches(1.0),
94        Length::inches(1.0),
95        Length::inches(1.0),
96    );
97    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
98    doc.set_gutter(Length::twips(0));
99    doc.set_title("rdocx Feature Showcase");
100    doc.set_author("rdocx Sample Generator");
101    doc.set_subject("Comprehensive feature demonstration");
102    doc.set_keywords("rdocx, docx, rust, sample, showcase");
103
104    // Headers & Footers with different first page
105    doc.set_different_first_page(true);
106    doc.set_first_page_header("rdocx");
107    doc.set_first_page_footer("Feature Showcase — Cover Page");
108    doc.set_header("rdocx Feature Showcase");
109    doc.set_footer("Generated by rdocx");
110
111    // ── COVER PAGE ──
112    let bg = create_sample_png(612, 792, [20, 45, 90]);
113    doc.add_background_image(&bg, "cover_bg.png");
114
115    for _ in 0..3 {
116        doc.add_paragraph("");
117    }
118    {
119        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
120        p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
121    }
122    {
123        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
124        p.add_run("Complete Feature Showcase")
125            .size(28.0)
126            .color("FFFFFF")
127            .italic(true);
128    }
129    doc.add_paragraph("");
130    {
131        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
132        p.add_run("Every feature of the rdocx Rust library")
133            .size(13.0)
134            .color("B0C4DE");
135    }
136    {
137        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
138        p.add_run("demonstrated in a single document.")
139            .size(13.0)
140            .color("B0C4DE");
141    }
142
143    // ── TABLE OF CONTENTS ──
144    doc.add_paragraph("").page_break_before(true);
145    doc.insert_toc(doc.content_count(), 3);
146
147    // ── SECTION 1: TEXT FORMATTING ──
148    doc.add_paragraph("").page_break_before(true);
149    doc.add_paragraph("1. Text Formatting").style("Heading1");
150
151    // Paragraph Alignment
152    doc.add_paragraph("1.1 Paragraph Alignment")
153        .style("Heading2");
154    doc.add_paragraph("Left-aligned paragraph (default).")
155        .alignment(Alignment::Left);
156    doc.add_paragraph("Center-aligned paragraph.")
157        .alignment(Alignment::Center);
158    doc.add_paragraph("Right-aligned paragraph.")
159        .alignment(Alignment::Right);
160    doc.add_paragraph(
161        "Justified paragraph — this text is long enough to demonstrate how justified alignment \
162         distributes extra space across word gaps so lines fill the full width of the text area.",
163    )
164    .alignment(Alignment::Justify);
165
166    doc.add_paragraph("");
167
168    // Run Formatting
169    doc.add_paragraph("1.2 Run Formatting").style("Heading2");
170    {
171        let mut p = doc.add_paragraph("");
172        p.add_run("Bold").bold(true);
173        p.add_run(" | ");
174        p.add_run("Italic").italic(true);
175        p.add_run(" | ");
176        p.add_run("Bold Italic").bold(true).italic(true);
177        p.add_run(" | ");
178        p.add_run("Underline").underline(true);
179        p.add_run(" | ");
180        p.add_run("Strikethrough").strike(true);
181        p.add_run(" | ");
182        p.add_run("Double Strike").double_strike(true);
183    }
184    {
185        let mut p = doc.add_paragraph("");
186        p.add_run("Red").color("FF0000");
187        p.add_run(" | ");
188        p.add_run("Blue").color("0000FF");
189        p.add_run(" | ");
190        p.add_run("Green").color("00AA00");
191        p.add_run(" | ");
192        p.add_run("Highlighted").highlight("FFFF00");
193    }
194    {
195        let mut p = doc.add_paragraph("");
196        p.add_run("8pt").size(8.0);
197        p.add_run(" | ");
198        p.add_run("11pt").size(11.0);
199        p.add_run(" | ");
200        p.add_run("16pt").size(16.0);
201        p.add_run(" | ");
202        p.add_run("24pt").size(24.0);
203    }
204    {
205        let mut p = doc.add_paragraph("");
206        p.add_run("Arial").font("Arial");
207        p.add_run(" | ");
208        p.add_run("Times New Roman").font("Times New Roman");
209        p.add_run(" | ");
210        p.add_run("Courier New").font("Courier New");
211    }
212    {
213        let mut p = doc.add_paragraph("");
214        p.add_run("H");
215        p.add_run("2").subscript();
216        p.add_run("O (subscript) | E=mc");
217        p.add_run("2").superscript();
218        p.add_run(" (superscript)");
219    }
220    {
221        let mut p = doc.add_paragraph("");
222        p.add_run("ALL CAPS").all_caps(true);
223        p.add_run(" | ");
224        p.add_run("Small Caps").small_caps(true);
225        p.add_run(" | ");
226        p.add_run("Expanded").character_spacing(Length::twips(40));
227        p.add_run(" | ");
228        p.add_run("Hidden text (not visible)").hidden(true);
229    }
230
231    doc.add_paragraph("");
232
233    // Underline Styles
234    doc.add_paragraph("1.3 Underline Styles").style("Heading2");
235    {
236        let mut p = doc.add_paragraph("");
237        p.add_run("Single ")
238            .underline_style(rdocx::UnderlineStyle::Single);
239        p.add_run("Double ")
240            .underline_style(rdocx::UnderlineStyle::Double);
241        p.add_run("Thick ")
242            .underline_style(rdocx::UnderlineStyle::Thick);
243        p.add_run("Dotted ")
244            .underline_style(rdocx::UnderlineStyle::Dotted);
245        p.add_run("Dash ")
246            .underline_style(rdocx::UnderlineStyle::Dash);
247        p.add_run("Wave ")
248            .underline_style(rdocx::UnderlineStyle::Wave);
249    }
250
251    // ── SECTION 2: PARAGRAPH FORMATTING ──
252    doc.add_paragraph("").page_break_before(true);
253    doc.add_paragraph("2. Paragraph Formatting")
254        .style("Heading1");
255
256    doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
257    doc.add_paragraph("Paragraph with light green shading")
258        .shading("E2EFDA");
259    doc.add_paragraph("Paragraph with bottom border")
260        .border_bottom(BorderStyle::Single, 6, "2E75B6");
261    doc.add_paragraph("Paragraph with all borders (red)")
262        .border_all(BorderStyle::Single, 4, "FF0000");
263
264    doc.add_paragraph("2.2 Indentation").style("Heading2");
265    doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
266        .indent_left(Length::inches(1.0))
267        .hanging_indent(Length::inches(0.5));
268    doc.add_paragraph("0.5-inch first-line indent on this paragraph")
269        .first_line_indent(Length::inches(0.5));
270    doc.add_paragraph("Both left and right indent (0.75 inches each)")
271        .indent_left(Length::inches(0.75))
272        .indent_right(Length::inches(0.75));
273
274    doc.add_paragraph("2.3 Spacing & Line Height")
275        .style("Heading2");
276    doc.add_paragraph("Extra space before (24pt) and after (12pt)")
277        .space_before(Length::pt(24.0))
278        .space_after(Length::pt(12.0));
279    doc.add_paragraph(
280        "Double line spacing paragraph. This text should have extra vertical space between \
281         lines to demonstrate line_spacing_multiple(2.0).",
282    )
283    .line_spacing_multiple(2.0);
284    doc.add_paragraph("Exact 20pt line spacing (fixed height).")
285        .line_spacing(20.0);
286
287    doc.add_paragraph("2.4 Pagination Controls")
288        .style("Heading2");
289    doc.add_paragraph("keep_with_next — this paragraph stays with the next")
290        .keep_with_next(true);
291    doc.add_paragraph("(This paragraph was kept with the one above.)");
292    doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
293        .keep_together(true);
294    doc.add_paragraph("widow_control — prevents widow/orphan lines")
295        .widow_control(true);
296
297    // ── SECTION 3: LISTS ──
298    doc.add_paragraph("").page_break_before(true);
299    doc.add_paragraph("3. Lists").style("Heading1");
300
301    doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
302    doc.add_bullet_list_item("First item", 0);
303    doc.add_bullet_list_item("Second item", 0);
304    doc.add_bullet_list_item("Nested level 1", 1);
305    doc.add_bullet_list_item("Nested level 2", 2);
306    doc.add_bullet_list_item("Back to level 1", 1);
307    doc.add_bullet_list_item("Third item", 0);
308
309    doc.add_paragraph("");
310
311    doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
312    doc.add_numbered_list_item("First numbered item", 0);
313    doc.add_numbered_list_item("Second numbered item", 0);
314    doc.add_numbered_list_item("Sub-item A", 1);
315    doc.add_numbered_list_item("Sub-item B", 1);
316    doc.add_numbered_list_item("Third numbered item", 0);
317
318    // ── SECTION 4: TAB STOPS ──
319    doc.add_paragraph("");
320    doc.add_paragraph("4. Tab Stops").style("Heading1");
321
322    doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
323    doc.add_paragraph("Left\tCenter\tRight\tDecimal")
324        .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
325        .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
326        .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
327        .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
328
329    doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
330    doc.add_paragraph("Item\t\tPrice")
331        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
332        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
333        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
334    doc.add_paragraph("Widget\t\t$19.99")
335        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
336        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
337        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
338    doc.add_paragraph("Gadget\t\t$249.50")
339        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
340        .add_tab_stop_with_leader(
341            TabAlignment::Right,
342            Length::inches(4.0),
343            TabLeader::Underscore,
344        )
345        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
346
347    // ── SECTION 5: TABLES ──
348    doc.add_paragraph("").page_break_before(true);
349    doc.add_paragraph("5. Tables").style("Heading1");
350
351    doc.add_paragraph("5.1 Basic Table").style("Heading2");
352    {
353        let mut tbl = doc
354            .add_table(4, 3)
355            .borders(BorderStyle::Single, 4, "000000");
356        for col in 0..3 {
357            tbl.cell(0, col).unwrap().shading("2E75B6");
358        }
359        tbl.cell(0, 0).unwrap().set_text("Name");
360        tbl.cell(0, 1).unwrap().set_text("Role");
361        tbl.cell(0, 2).unwrap().set_text("Location");
362        tbl.cell(1, 0).unwrap().set_text("Walter White");
363        tbl.cell(1, 1).unwrap().set_text("CEO");
364        tbl.cell(1, 2).unwrap().set_text("Albuquerque");
365        tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
366        tbl.cell(2, 1).unwrap().set_text("CTO");
367        tbl.cell(2, 2).unwrap().set_text("Remote");
368        tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
369        tbl.cell(3, 1).unwrap().set_text("Security");
370        tbl.cell(3, 2).unwrap().set_text("Washington");
371    }
372
373    doc.add_paragraph("");
374
375    doc.add_paragraph("5.2 Column Span & Cell Shading")
376        .style("Heading2");
377    {
378        let mut tbl = doc
379            .add_table(3, 4)
380            .borders(BorderStyle::Single, 4, "000000")
381            .width_pct(100.0);
382        tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
383        tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
384        tbl.cell(1, 0).unwrap().set_text("Region");
385        tbl.cell(1, 0).unwrap().shading("D6E4F0");
386        tbl.cell(1, 1).unwrap().set_text("Q1");
387        tbl.cell(1, 1).unwrap().shading("D6E4F0");
388        tbl.cell(1, 2).unwrap().set_text("Q2");
389        tbl.cell(1, 2).unwrap().shading("D6E4F0");
390        tbl.cell(1, 3).unwrap().set_text("Total");
391        tbl.cell(1, 3).unwrap().shading("D6E4F0");
392        tbl.cell(2, 0).unwrap().set_text("Americas");
393        tbl.cell(2, 1).unwrap().set_text("$2.4M");
394        tbl.cell(2, 2).unwrap().set_text("$2.7M");
395        tbl.cell(2, 3).unwrap().set_text("$5.1M");
396    }
397
398    doc.add_paragraph("");
399
400    doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
401    {
402        let mut tbl = doc
403            .add_table(4, 3)
404            .borders(BorderStyle::Single, 4, "000000");
405        tbl.cell(0, 0).unwrap().set_text("Category");
406        tbl.cell(0, 0).unwrap().shading("E2EFDA");
407        tbl.cell(0, 1).unwrap().set_text("Item");
408        tbl.cell(0, 1).unwrap().shading("E2EFDA");
409        tbl.cell(0, 2).unwrap().set_text("Price");
410        tbl.cell(0, 2).unwrap().shading("E2EFDA");
411        tbl.cell(1, 0).unwrap().set_text("Hardware");
412        tbl.cell(1, 0).unwrap().v_merge_restart();
413        tbl.cell(1, 1).unwrap().set_text("Laptop");
414        tbl.cell(1, 2).unwrap().set_text("$1,200");
415        tbl.cell(2, 0).unwrap().v_merge_continue();
416        tbl.cell(2, 1).unwrap().set_text("Monitor");
417        tbl.cell(2, 2).unwrap().set_text("$450");
418        tbl.cell(3, 0).unwrap().set_text("Software");
419        tbl.cell(3, 1).unwrap().set_text("IDE License");
420        tbl.cell(3, 2).unwrap().set_text("$200/yr");
421    }
422
423    doc.add_paragraph("");
424
425    doc.add_paragraph("5.4 Nested Table").style("Heading2");
426    {
427        let mut tbl = doc
428            .add_table(2, 2)
429            .borders(BorderStyle::Single, 6, "2E75B6");
430        tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
431        tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
432        tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
433        {
434            let mut cell = tbl.cell(1, 1).unwrap();
435            cell.set_text("Nested table below:");
436            let mut nested = cell
437                .add_table(2, 2)
438                .borders(BorderStyle::Single, 2, "FF6600");
439            nested.cell(0, 0).unwrap().set_text("A");
440            nested.cell(0, 1).unwrap().set_text("B");
441            nested.cell(1, 0).unwrap().set_text("C");
442            nested.cell(1, 1).unwrap().set_text("D");
443        }
444    }
445
446    doc.add_paragraph("");
447
448    doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
449        .style("Heading2");
450    {
451        let mut tbl = doc
452            .add_table(2, 3)
453            .borders(BorderStyle::Single, 4, "666666");
454        tbl.row(0).unwrap().height(Length::pt(50.0)).header();
455        tbl.cell(0, 0).unwrap().set_text("Top");
456        tbl.cell(0, 0)
457            .unwrap()
458            .vertical_alignment(VerticalAlignment::Top)
459            .shading("FFF2CC");
460        tbl.cell(0, 1).unwrap().set_text("Center");
461        tbl.cell(0, 1)
462            .unwrap()
463            .vertical_alignment(VerticalAlignment::Center)
464            .shading("D9E2F3");
465        tbl.cell(0, 2).unwrap().set_text("Bottom");
466        tbl.cell(0, 2)
467            .unwrap()
468            .vertical_alignment(VerticalAlignment::Bottom)
469            .shading("E2EFDA");
470        tbl.row(1).unwrap().cant_split();
471        tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
472        tbl.cell(1, 0).unwrap().no_wrap();
473        tbl.cell(1, 1).unwrap().set_text("Fixed width");
474        tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
475        tbl.cell(1, 2).unwrap().set_text("Normal");
476    }
477
478    // ── SECTION 6: IMAGES ──
479    doc.add_paragraph("").page_break_before(true);
480    doc.add_paragraph("6. Images").style("Heading1");
481
482    doc.add_paragraph("6.1 Inline Image").style("Heading2");
483    doc.add_paragraph("A blue gradient image below:");
484    let img = create_sample_png(200, 50, [0, 80, 200]);
485    doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
486
487    doc.add_paragraph("");
488    doc.add_paragraph("6.2 Header Image").style("Heading2");
489    let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
490    doc.set_header_image(
491        &hdr_img,
492        "header_logo.png",
493        Length::inches(2.0),
494        Length::inches(0.2),
495    );
496    doc.add_paragraph("The header now contains an inline image (check the top of this page).");
497
498    doc.add_paragraph("");
499    doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
500
501    // ── SECTION 7: CONTENT MANIPULATION ──
502    doc.add_paragraph("").page_break_before(true);
503    doc.add_paragraph("7. Content Manipulation")
504        .style("Heading1");
505
506    doc.add_paragraph("7.1 Placeholder Replacement")
507        .style("Heading2");
508    doc.add_paragraph("Customer: {{customer}}");
509    doc.add_paragraph("Date: {{date}}");
510    doc.add_paragraph("Reference: {{ref_number}}");
511    {
512        let mut tbl = doc
513            .add_table(2, 2)
514            .borders(BorderStyle::Single, 4, "000000");
515        tbl.cell(0, 0).unwrap().set_text("Project");
516        tbl.cell(0, 0).unwrap().shading("D6E4F0");
517        tbl.cell(0, 1).unwrap().set_text("{{project}}");
518        tbl.cell(1, 0).unwrap().set_text("Status");
519        tbl.cell(1, 0).unwrap().shading("D6E4F0");
520        tbl.cell(1, 1).unwrap().set_text("{{status}}");
521    }
522
523    let mut replacements = HashMap::new();
524    replacements.insert("{{customer}}", "Tensorbee Inc.");
525    replacements.insert("{{date}}", "February 22, 2026");
526    replacements.insert("{{ref_number}}", "TB-2026-001");
527    replacements.insert("{{project}}", "Infrastructure Upgrade");
528    replacements.insert("{{status}}", "In Progress");
529    let n = doc.replace_all(&replacements);
530    doc.add_paragraph(&format!("({n} placeholders replaced above)"));
531
532    doc.add_paragraph("");
533
534    doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
535    doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
536    let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
537    doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
538
539    doc.add_paragraph("");
540
541    doc.add_paragraph("7.3 Content Insertion").style("Heading2");
542    doc.add_paragraph("Section A: First content.");
543    doc.add_paragraph("Section C: Third content.");
544    if let Some(idx) = doc.find_content_index("Section C") {
545        doc.insert_paragraph(
546            idx,
547            "Section B: Inserted between A and C via find_content_index().",
548        );
549    }
550
551    // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
552    doc.add_paragraph("").section_break(SectionBreak::NextPage);
553    doc.add_paragraph("8. Section Breaks & Orientation")
554        .style("Heading1");
555    doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
556
557    {
558        let mut tbl = doc
559            .add_table(3, 7)
560            .borders(BorderStyle::Single, 4, "2E75B6");
561        let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
562        for (c, h) in headers.iter().enumerate() {
563            tbl.cell(0, c).unwrap().set_text(h);
564            tbl.cell(0, c).unwrap().shading("2E75B6");
565        }
566        let data = [
567            [
568                "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
569            ],
570            [
571                "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
572            ],
573        ];
574        for (r, row) in data.iter().enumerate() {
575            for (c, v) in row.iter().enumerate() {
576                tbl.cell(r + 1, c).unwrap().set_text(v);
577            }
578        }
579    }
580
581    doc.add_paragraph("")
582        .section_break(SectionBreak::NextPage)
583        .section_landscape();
584
585    // ── SECTION 9: CUSTOM STYLES ──
586    doc.add_paragraph("9. Custom Styles").style("Heading1");
587    doc.add_style(
588        StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
589            .based_on("Normal")
590            .paragraph_properties({
591                let mut ppr = rdocx_oxml::properties::CT_PPr::default();
592                ppr.shading = Some(rdocx_oxml::properties::CT_Shd {
593                    val: "clear".to_string(),
594                    color: None,
595                    fill: Some("FFF2CC".to_string()),
596                });
597                ppr
598            })
599            .run_properties({
600                let mut rpr = rdocx_oxml::properties::CT_RPr::default();
601                rpr.bold = Some(true);
602                rpr.color = Some("C45911".to_string());
603                rpr
604            }),
605    );
606    doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
607        .style("CustomHighlight");
608
609    doc.add_paragraph("");
610
611    // ── SECTION 10: DOCUMENT INTELLIGENCE ──
612    doc.add_paragraph("10. Document Intelligence API")
613        .style("Heading1");
614
615    let wc = doc.word_count();
616    let hc = doc.headings().len();
617    let ic = doc.images().len();
618    let lc = doc.links().len();
619    doc.add_paragraph(&format!("Word count: {wc}"));
620    doc.add_paragraph(&format!("Heading count: {hc}"));
621    doc.add_paragraph(&format!("Image count: {ic}"));
622    doc.add_paragraph(&format!("Link count: {lc}"));
623
624    let outline = doc.document_outline();
625    doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
626
627    let issues = doc.audit_accessibility();
628    doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
629    for issue in &issues {
630        doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
631    }
632
633    // ── SECTION 11: DOCUMENT MERGING ──
634    doc.add_paragraph("");
635    doc.add_paragraph("11. Document Merging").style("Heading1");
636    {
637        let mut other = Document::new();
638        other.add_paragraph("This paragraph was merged from another document using append().");
639        doc.append(&other);
640    }
641
642    doc.add_paragraph("");
643
644    // ── SUMMARY ──
645    doc.add_paragraph("Summary of Demonstrated Features")
646        .style("Heading1");
647    let features = [
648        "Page setup: size, margins, header/footer distance, gutter",
649        "Document metadata: title, author, subject, keywords",
650        "Headers and footers: text, images, different first page",
651        "Background images (full-page behind text)",
652        "Table of Contents generation with bookmarks",
653        "Text formatting: bold, italic, underline styles, strike, color, size, font",
654        "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
655        "Paragraph formatting: alignment, borders, shading, spacing, indentation",
656        "Pagination controls: keep-with-next, keep-together, widow control",
657        "Bullet and numbered lists with nesting",
658        "Tab stops with dot/underscore/hyphen leaders",
659        "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
660        "Row properties: header rows, exact height, cant-split",
661        "Cell properties: width, no-wrap, vertical alignment",
662        "Inline images and header images",
663        "Placeholder replacement (single and batch)",
664        "Regex-based find and replace",
665        "Content insertion at specific positions",
666        "Section breaks with mixed portrait/landscape",
667        "Custom paragraph and character styles",
668        "Document intelligence: word count, headings, outline, images, links",
669        "Accessibility audit",
670        "Document merging (append)",
671        "Export: DOCX, PDF, HTML, Markdown",
672    ];
673    for f in &features {
674        doc.add_bullet_list_item(f, 0);
675    }
676    doc.add_paragraph("");
677    doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
678        .alignment(Alignment::Center)
679        .shading("E2EFDA")
680        .border_all(BorderStyle::Single, 2, "00AA00");
681
682    doc
683}
684
685// =============================================================================
686// 2. PROPOSAL DOCUMENT — Deep navy + gold scheme
687// =============================================================================
688fn generate_proposal(_samples_dir: &Path) -> Document {
689    let mut doc = Document::new();
690
691    // Colors: Navy #1B2A4A, Gold #C5922E, Light #F4F1EB
692    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
693    doc.set_margins(
694        Length::inches(1.0),
695        Length::inches(1.0),
696        Length::inches(1.0),
697        Length::inches(1.0),
698    );
699    doc.set_title("AI Platform Modernization Proposal");
700    doc.set_author("Walter White");
701    doc.set_subject("Technology Proposal");
702    doc.set_keywords("proposal, AI, modernization, Tensorbee");
703
704    // Banner header
705    let logo_img = create_logo_png(220, 48);
706    let banner = build_header_banner_xml(
707        "rId1",
708        &BannerOpts {
709            bg_color: "1B2A4A",
710            banner_width: 7772400,
711            banner_height: 914400,
712            logo_width: 2011680,
713            logo_height: 438912,
714            logo_x_offset: 295125,
715            logo_y_offset: 237744,
716        },
717    );
718    doc.set_raw_header_with_images(
719        banner,
720        &[("rId1", &logo_img, "logo.png")],
721        rdocx_oxml::header_footer::HdrFtrType::Default,
722    );
723    doc.set_different_first_page(true);
724    let cover_banner = build_header_banner_xml(
725        "rId1",
726        &BannerOpts {
727            bg_color: "C5922E",
728            banner_width: 7772400,
729            banner_height: 914400,
730            logo_width: 2011680,
731            logo_height: 438912,
732            logo_x_offset: 295125,
733            logo_y_offset: 237744,
734        },
735    );
736    doc.set_raw_header_with_images(
737        cover_banner,
738        &[("rId1", &logo_img, "logo.png")],
739        rdocx_oxml::header_footer::HdrFtrType::First,
740    );
741    doc.set_margins(
742        Length::twips(2292),
743        Length::twips(1440),
744        Length::twips(1440),
745        Length::twips(1440),
746    );
747    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
748    doc.set_footer("Tensorbee — Confidential");
749
750    // Custom styles
751    doc.add_style(
752        StyleBuilder::paragraph("ProposalTitle", "Proposal Title")
753            .based_on("Normal")
754            .run_properties({
755                let mut rpr = rdocx_oxml::properties::CT_RPr::default();
756                rpr.bold = Some(true);
757                rpr.font_ascii = Some("Georgia".to_string());
758                rpr.font_hansi = Some("Georgia".to_string());
759                rpr.sz = Some(rdocx_oxml::HalfPoint(56)); // half-points → 28pt
760                rpr.color = Some("1B2A4A".to_string());
761                rpr
762            }),
763    );
764
765    // ── Cover Page ──
766    for _ in 0..4 {
767        doc.add_paragraph("");
768    }
769    doc.add_paragraph("AI Platform Modernization")
770        .style("ProposalTitle")
771        .alignment(Alignment::Center);
772    doc.add_paragraph("")
773        .alignment(Alignment::Center)
774        .border_bottom(BorderStyle::Single, 8, "C5922E");
775    {
776        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
777        p.add_run("Prepared for Global Financial Corp.")
778            .size(14.0)
779            .color("666666")
780            .italic(true);
781    }
782    {
783        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
784        p.add_run("Prepared by: Walter White, CEO — Tensorbee")
785            .size(12.0)
786            .color("888888");
787    }
788    {
789        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
790        p.add_run("February 22, 2026").size(12.0).color("888888");
791    }
792
793    // ── TOC ──
794    doc.add_paragraph("").page_break_before(true);
795    doc.add_paragraph("Table of Contents").style("Heading1");
796    // We'll add a manual-style TOC since the insert_toc goes at a specific position
797    doc.insert_toc(doc.content_count(), 2);
798
799    // ── Executive Summary ──
800    doc.add_paragraph("").page_break_before(true);
801    doc.add_paragraph("Executive Summary").style("Heading1");
802    doc.add_paragraph(
803        "Tensorbee proposes a comprehensive modernization of Global Financial Corp's AI platform, \
804         transitioning from legacy batch-processing systems to a real-time inference architecture. \
805         This transformation will reduce model deployment time from weeks to hours, improve \
806         prediction accuracy by an estimated 23%, and deliver $4.2M in annual operational savings.",
807    )
808    .first_line_indent(Length::inches(0.3));
809    doc.add_paragraph(
810        "The project spans three phases over 18 months, with a total investment of $2.8M. \
811         Tensorbee brings deep expertise in ML infrastructure, having successfully completed \
812         similar transformations for three Fortune 500 financial institutions.",
813    )
814    .first_line_indent(Length::inches(0.3));
815
816    // Key metrics table
817    doc.add_paragraph("");
818    {
819        let mut tbl = doc
820            .add_table(5, 2)
821            .borders(BorderStyle::Single, 4, "1B2A4A")
822            .width_pct(80.0);
823        tbl.cell(0, 0).unwrap().set_text("Key Metric");
824        tbl.cell(0, 0).unwrap().shading("1B2A4A");
825        tbl.cell(0, 1).unwrap().set_text("Value");
826        tbl.cell(0, 1).unwrap().shading("1B2A4A");
827        let rows = [
828            ("Total Investment", "$2.8M"),
829            ("Annual Savings", "$4.2M"),
830            ("ROI (Year 1)", "150%"),
831            ("Timeline", "18 months"),
832        ];
833        for (i, (k, v)) in rows.iter().enumerate() {
834            tbl.cell(i + 1, 0).unwrap().set_text(k);
835            if i % 2 == 0 {
836                tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
837            }
838            tbl.cell(i + 1, 1).unwrap().set_text(v);
839            if i % 2 == 0 {
840                tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
841            }
842        }
843    }
844
845    // ── Problem Statement ──
846    doc.add_paragraph("").page_break_before(true);
847    doc.add_paragraph("Problem Statement").style("Heading1");
848    doc.add_paragraph(
849        "Global Financial Corp's current AI infrastructure faces three critical challenges:",
850    );
851    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);
852    doc.add_numbered_list_item("Scalability Constraints — The batch processing architecture cannot handle real-time inference demands during peak trading hours.", 0);
853    doc.add_numbered_list_item("Technical Debt — Legacy Python 2.7 codebases and manual deployment scripts create reliability risks and slow iteration speed.", 0);
854
855    // ── Proposed Solution ──
856    doc.add_paragraph("").page_break_before(true);
857    doc.add_paragraph("Proposed Solution").style("Heading1");
858
859    doc.add_paragraph("Phase 1: Foundation (Months 1-6)")
860        .style("Heading2");
861    doc.add_bullet_list_item("Deploy Kubernetes-based ML serving infrastructure", 0);
862    doc.add_bullet_list_item("Implement CI/CD pipeline for model deployment", 0);
863    doc.add_bullet_list_item("Migrate top 5 production models to new platform", 0);
864
865    doc.add_paragraph("Phase 2: Expansion (Months 7-12)")
866        .style("Heading2");
867    doc.add_bullet_list_item(
868        "Real-time feature engineering pipeline (Apache Kafka + Flink)",
869        0,
870    );
871    doc.add_bullet_list_item("A/B testing framework for model variants", 0);
872    doc.add_bullet_list_item("Automated model monitoring and drift detection", 0);
873
874    doc.add_paragraph("Phase 3: Optimization (Months 13-18)")
875        .style("Heading2");
876    doc.add_bullet_list_item("GPU inference optimization (TensorRT/ONNX Runtime)", 0);
877    doc.add_bullet_list_item("Multi-region deployment for latency reduction", 0);
878    doc.add_bullet_list_item(
879        "Self-service model deployment portal for data science teams",
880        0,
881    );
882
883    // ── Budget ──
884    doc.add_paragraph("Budget Breakdown").style("Heading1");
885    {
886        let mut tbl = doc
887            .add_table(6, 3)
888            .borders(BorderStyle::Single, 4, "1B2A4A")
889            .width_pct(100.0);
890        tbl.cell(0, 0).unwrap().set_text("Category");
891        tbl.cell(0, 0).unwrap().shading("1B2A4A");
892        tbl.cell(0, 1).unwrap().set_text("Cost");
893        tbl.cell(0, 1).unwrap().shading("1B2A4A");
894        tbl.cell(0, 2).unwrap().set_text("Notes");
895        tbl.cell(0, 2).unwrap().shading("1B2A4A");
896        let items = [
897            (
898                "Infrastructure",
899                "$450,000",
900                "Cloud compute + GPU instances",
901            ),
902            ("Engineering Services", "$1,600,000", "12 FTE x 18 months"),
903            (
904                "Software Licenses",
905                "$350,000",
906                "Kafka, monitoring, CI/CD tools",
907            ),
908            (
909                "Training & Enablement",
910                "$200,000",
911                "Team training, documentation",
912            ),
913            ("Contingency (10%)", "$200,000", "Risk buffer"),
914        ];
915        for (i, (cat, cost, note)) in items.iter().enumerate() {
916            tbl.cell(i + 1, 0).unwrap().set_text(cat);
917            tbl.cell(i + 1, 1).unwrap().set_text(cost);
918            tbl.cell(i + 1, 2).unwrap().set_text(note);
919            if i % 2 == 0 {
920                tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
921                tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
922                tbl.cell(i + 1, 2).unwrap().shading("F4F1EB");
923            }
924        }
925    }
926
927    // ── Closing ──
928    doc.add_paragraph("");
929    doc.add_paragraph("Next Steps").style("Heading1");
930    doc.add_numbered_list_item(
931        "Schedule technical deep-dive with GFC engineering team (Week 1)",
932        0,
933    );
934    doc.add_numbered_list_item("Finalize scope and sign SOW (Week 2-3)", 0);
935    doc.add_numbered_list_item("Kick off Phase 1 with joint planning session (Week 4)", 0);
936
937    doc.add_paragraph("");
938    {
939        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
940        p.add_run("Walter White")
941            .bold(true)
942            .size(14.0)
943            .color("1B2A4A");
944    }
945    {
946        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
947        p.add_run("CEO, Tensorbee | walter@tensorbee.com")
948            .size(10.0)
949            .color("888888");
950    }
951
952    doc
953}
954
955// =============================================================================
956// 3. QUOTE / BILL OF MATERIALS — Teal + orange scheme
957// =============================================================================
958fn generate_quote(_samples_dir: &Path) -> Document {
959    let mut doc = Document::new();
960
961    // Colors: Teal #008B8B, Dark #1A3C3C, Orange #E07020, Light #F0F8F8
962    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
963    doc.set_margins(
964        Length::inches(0.75),
965        Length::inches(0.75),
966        Length::inches(0.75),
967        Length::inches(0.75),
968    );
969    doc.set_title("Quotation QT-2026-0147");
970    doc.set_author("Walter White");
971    doc.set_keywords("quote, BOM, Tensorbee");
972
973    doc.set_header("Tensorbee — Quotation");
974    doc.set_footer("QT-2026-0147 | Page");
975
976    // ── Header Block ──
977    {
978        let mut p = doc.add_paragraph("").alignment(Alignment::Left);
979        p.add_run("TENSORBEE")
980            .bold(true)
981            .size(28.0)
982            .color("008B8B")
983            .font("Helvetica");
984    }
985    doc.add_paragraph("123 Innovation Drive, Suite 400")
986        .alignment(Alignment::Left);
987    doc.add_paragraph("San Francisco, CA 94105 | +1 (415) 555-0199")
988        .alignment(Alignment::Left);
989    doc.add_paragraph("")
990        .border_bottom(BorderStyle::Single, 8, "008B8B");
991
992    // Quote meta
993    doc.add_paragraph("");
994    {
995        let mut tbl = doc.add_table(4, 4).width_pct(100.0);
996        tbl.cell(0, 0).unwrap().set_text("QUOTATION");
997        tbl.cell(0, 0).unwrap().shading("008B8B").grid_span(2);
998        tbl.cell(0, 2).unwrap().set_text("DATE");
999        tbl.cell(0, 2).unwrap().shading("008B8B");
1000        tbl.cell(0, 3).unwrap().set_text("VALID UNTIL");
1001        tbl.cell(0, 3).unwrap().shading("008B8B");
1002        tbl.cell(1, 0).unwrap().set_text("Quote #:");
1003        tbl.cell(1, 1).unwrap().set_text("QT-2026-0147");
1004        tbl.cell(1, 2).unwrap().set_text("Feb 22, 2026");
1005        tbl.cell(1, 3).unwrap().set_text("Mar 22, 2026");
1006        tbl.cell(2, 0).unwrap().set_text("Prepared by:");
1007        tbl.cell(2, 1).unwrap().set_text("Walter White");
1008        tbl.cell(2, 2).unwrap().set_text("Payment:");
1009        tbl.cell(2, 3).unwrap().set_text("Net 30");
1010        tbl.cell(3, 0).unwrap().set_text("Customer:");
1011        tbl.cell(3, 1).unwrap().set_text("Meridian Dynamics LLC");
1012        tbl.cell(3, 2).unwrap().set_text("Currency:");
1013        tbl.cell(3, 3).unwrap().set_text("USD");
1014    }
1015
1016    doc.add_paragraph("");
1017
1018    // ── Bill of Materials ──
1019    {
1020        let mut p = doc.add_paragraph("");
1021        p.add_run("Bill of Materials")
1022            .bold(true)
1023            .size(16.0)
1024            .color("1A3C3C");
1025    }
1026    doc.add_paragraph("");
1027
1028    {
1029        let mut tbl = doc
1030            .add_table(10, 6)
1031            .borders(BorderStyle::Single, 2, "008B8B")
1032            .width_pct(100.0)
1033            .layout_fixed();
1034        // Headers
1035        let hdrs = [
1036            "#",
1037            "Part Number",
1038            "Description",
1039            "Qty",
1040            "Unit Price",
1041            "Total",
1042        ];
1043        for (c, h) in hdrs.iter().enumerate() {
1044            tbl.cell(0, c).unwrap().set_text(h);
1045            tbl.cell(0, c).unwrap().shading("008B8B");
1046        }
1047
1048        let items: Vec<(&str, &str, &str, &str, &str)> = vec![
1049            (
1050                "TB-GPU-A100",
1051                "NVIDIA A100 80GB GPU",
1052                "4",
1053                "$12,500.00",
1054                "$50,000.00",
1055            ),
1056            (
1057                "TB-SRV-R750",
1058                "Dell R750xa Server Chassis",
1059                "2",
1060                "$8,200.00",
1061                "$16,400.00",
1062            ),
1063            (
1064                "TB-RAM-256G",
1065                "256GB DDR5 ECC Memory Module",
1066                "8",
1067                "$890.00",
1068                "$7,120.00",
1069            ),
1070            (
1071                "TB-SSD-3840",
1072                "3.84TB NVMe U.2 SSD",
1073                "8",
1074                "$1,150.00",
1075                "$9,200.00",
1076            ),
1077            (
1078                "TB-NET-CX7",
1079                "ConnectX-7 200GbE NIC",
1080                "4",
1081                "$1,800.00",
1082                "$7,200.00",
1083            ),
1084            (
1085                "TB-SW-48P",
1086                "48-Port 100GbE Switch",
1087                "1",
1088                "$22,000.00",
1089                "$22,000.00",
1090            ),
1091            (
1092                "TB-CAB-RACK",
1093                "42U Server Rack + PDU",
1094                "1",
1095                "$4,500.00",
1096                "$4,500.00",
1097            ),
1098            (
1099                "TB-SVC-INST",
1100                "Installation & Configuration",
1101                "1",
1102                "$8,500.00",
1103                "$8,500.00",
1104            ),
1105            (
1106                "TB-SVC-SUPP",
1107                "3-Year Premium Support",
1108                "1",
1109                "$15,000.00",
1110                "$15,000.00",
1111            ),
1112        ];
1113
1114        for (i, (pn, desc, qty, unit, total)) in items.iter().enumerate() {
1115            let row = i + 1;
1116            tbl.cell(row, 0).unwrap().set_text(&format!("{}", i + 1));
1117            tbl.cell(row, 1).unwrap().set_text(pn);
1118            tbl.cell(row, 2).unwrap().set_text(desc);
1119            tbl.cell(row, 3).unwrap().set_text(qty);
1120            tbl.cell(row, 4).unwrap().set_text(unit);
1121            tbl.cell(row, 5).unwrap().set_text(total);
1122            if i % 2 == 0 {
1123                for c in 0..6 {
1124                    tbl.cell(row, c).unwrap().shading("F0F8F8");
1125                }
1126            }
1127        }
1128    }
1129
1130    doc.add_paragraph("");
1131
1132    // ── Totals ──
1133    {
1134        let mut tbl = doc
1135            .add_table(4, 2)
1136            .borders(BorderStyle::Single, 2, "008B8B")
1137            .width(Length::inches(3.5))
1138            .alignment(Alignment::Right);
1139        tbl.cell(0, 0).unwrap().set_text("Subtotal");
1140        tbl.cell(0, 1).unwrap().set_text("$139,920.00");
1141        tbl.cell(1, 0).unwrap().set_text("Shipping & Handling");
1142        tbl.cell(1, 1).unwrap().set_text("$2,500.00");
1143        tbl.cell(2, 0).unwrap().set_text("Tax (8.625%)");
1144        tbl.cell(2, 1).unwrap().set_text("$12,068.10");
1145        tbl.cell(3, 0).unwrap().set_text("TOTAL");
1146        tbl.cell(3, 0).unwrap().shading("E07020");
1147        tbl.cell(3, 1).unwrap().set_text("$154,488.10");
1148        tbl.cell(3, 1).unwrap().shading("E07020");
1149    }
1150
1151    doc.add_paragraph("");
1152
1153    // ── Terms & Conditions ──
1154    {
1155        let mut p = doc.add_paragraph("");
1156        p.add_run("Terms & Conditions")
1157            .bold(true)
1158            .size(14.0)
1159            .color("1A3C3C");
1160    }
1161    doc.add_numbered_list_item(
1162        "This quotation is valid for 30 calendar days from the date of issue.",
1163        0,
1164    );
1165    doc.add_numbered_list_item(
1166        "All prices are in USD and exclusive of applicable taxes unless stated.",
1167        0,
1168    );
1169    doc.add_numbered_list_item("Standard lead time is 4-6 weeks from PO receipt.", 0);
1170    doc.add_numbered_list_item(
1171        "Payment terms: Net 30 from invoice date. 2% discount for payment within 10 days.",
1172        0,
1173    );
1174    doc.add_numbered_list_item(
1175        "Warranty: 3-year manufacturer warranty on all hardware components.",
1176        0,
1177    );
1178    doc.add_numbered_list_item(
1179        "Returns subject to 15% restocking fee if initiated after 14 days.",
1180        0,
1181    );
1182
1183    doc.add_paragraph("");
1184    doc.add_paragraph("")
1185        .border_bottom(BorderStyle::Single, 4, "008B8B");
1186    doc.add_paragraph("");
1187    {
1188        let mut p = doc.add_paragraph("");
1189        p.add_run("Accepted by: ").bold(true);
1190        p.add_run("___________________________ Date: ___________");
1191    }
1192    {
1193        let mut p = doc.add_paragraph("");
1194        p.add_run("Print Name: ").bold(true);
1195        p.add_run("___________________________ Title: ___________");
1196    }
1197
1198    doc
1199}
1200
1201// =============================================================================
1202// 4. INVOICE — Crimson + charcoal scheme
1203// =============================================================================
1204fn generate_invoice(_samples_dir: &Path) -> Document {
1205    let mut doc = Document::new();
1206
1207    // Colors: Crimson #B22222, Charcoal #333333, Light #FAF0F0
1208    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1209    doc.set_margins(
1210        Length::inches(0.75),
1211        Length::inches(0.75),
1212        Length::inches(0.75),
1213        Length::inches(0.75),
1214    );
1215    doc.set_title("Invoice INV-2026-0392");
1216    doc.set_author("Walter White");
1217    doc.set_keywords("invoice, Tensorbee");
1218
1219    doc.set_footer("Tensorbee — Thank you for your business!");
1220
1221    // ── Company & Invoice Header ──
1222    {
1223        let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1224        // Company name left, INVOICE right
1225        {
1226            let mut cell = tbl.cell(0, 0).unwrap();
1227            let mut p = cell.add_paragraph("");
1228            p.add_run("TENSORBEE")
1229                .bold(true)
1230                .size(32.0)
1231                .color("B22222")
1232                .font("Helvetica");
1233        }
1234        {
1235            let mut cell = tbl.cell(0, 1).unwrap();
1236            let mut p = cell.add_paragraph("");
1237            p.add_run("INVOICE").bold(true).size(32.0).color("333333");
1238        }
1239        tbl.cell(1, 0)
1240            .unwrap()
1241            .set_text("123 Innovation Drive, Suite 400");
1242        tbl.cell(1, 1).unwrap().set_text("Invoice #: INV-2026-0392");
1243        tbl.cell(2, 0).unwrap().set_text("San Francisco, CA 94105");
1244        tbl.cell(2, 1).unwrap().set_text("Date: February 22, 2026");
1245        tbl.cell(3, 0).unwrap().set_text("walter@tensorbee.com");
1246        tbl.cell(3, 1).unwrap().set_text("Due Date: March 24, 2026");
1247    }
1248
1249    doc.add_paragraph("")
1250        .border_bottom(BorderStyle::Thick, 8, "B22222");
1251    doc.add_paragraph("");
1252
1253    // ── Bill To / Ship To ──
1254    {
1255        let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1256        tbl.cell(0, 0).unwrap().set_text("BILL TO");
1257        tbl.cell(0, 0).unwrap().shading("B22222");
1258        tbl.cell(0, 1).unwrap().set_text("SHIP TO");
1259        tbl.cell(0, 1).unwrap().shading("B22222");
1260        tbl.cell(1, 0).unwrap().set_text("Meridian Dynamics LLC");
1261        tbl.cell(1, 1).unwrap().set_text("Meridian Dynamics LLC");
1262        tbl.cell(2, 0).unwrap().set_text("456 Enterprise Blvd");
1263        tbl.cell(2, 1).unwrap().set_text("Attn: Server Room B");
1264        tbl.cell(3, 0).unwrap().set_text("Austin, TX 78701");
1265        tbl.cell(3, 1)
1266            .unwrap()
1267            .set_text("456 Enterprise Blvd, Austin, TX 78701");
1268    }
1269
1270    doc.add_paragraph("");
1271
1272    // ── Line Items ──
1273    {
1274        let mut tbl = doc
1275            .add_table(8, 5)
1276            .borders(BorderStyle::Single, 2, "B22222")
1277            .width_pct(100.0);
1278        let hdrs = ["Description", "Qty", "Unit Price", "Tax", "Amount"];
1279        for (c, h) in hdrs.iter().enumerate() {
1280            tbl.cell(0, c).unwrap().set_text(h);
1281            tbl.cell(0, c).unwrap().shading("B22222");
1282        }
1283        let items = [
1284            (
1285                "ML Infrastructure Setup — Phase 1",
1286                "1",
1287                "$45,000.00",
1288                "$3,881.25",
1289                "$48,881.25",
1290            ),
1291            (
1292                "Data Pipeline Development (160 hrs)",
1293                "160",
1294                "$225.00",
1295                "$3,105.00",
1296                "$39,105.00",
1297            ),
1298            (
1299                "GPU Cluster Configuration",
1300                "1",
1301                "$12,000.00",
1302                "$1,035.00",
1303                "$13,035.00",
1304            ),
1305            (
1306                "API Gateway Implementation",
1307                "1",
1308                "$18,500.00",
1309                "$1,595.63",
1310                "$20,095.63",
1311            ),
1312            (
1313                "Load Testing & QA (80 hrs)",
1314                "80",
1315                "$195.00",
1316                "$1,345.50",
1317                "$16,945.50",
1318            ),
1319            (
1320                "Documentation & Training",
1321                "1",
1322                "$8,000.00",
1323                "$690.00",
1324                "$8,690.00",
1325            ),
1326            (
1327                "Project Management (3 months)",
1328                "3",
1329                "$6,500.00",
1330                "$1,679.63",
1331                "$21,179.63",
1332            ),
1333        ];
1334        for (i, (desc, qty, unit, tax, amt)) in items.iter().enumerate() {
1335            let r = i + 1;
1336            tbl.cell(r, 0).unwrap().set_text(desc);
1337            tbl.cell(r, 1).unwrap().set_text(qty);
1338            tbl.cell(r, 2).unwrap().set_text(unit);
1339            tbl.cell(r, 3).unwrap().set_text(tax);
1340            tbl.cell(r, 4).unwrap().set_text(amt);
1341            if i % 2 == 0 {
1342                for c in 0..5 {
1343                    tbl.cell(r, c).unwrap().shading("FAF0F0");
1344                }
1345            }
1346        }
1347    }
1348
1349    doc.add_paragraph("");
1350
1351    // ── Totals ──
1352    {
1353        let mut tbl = doc
1354            .add_table(4, 2)
1355            .borders(BorderStyle::Single, 2, "B22222")
1356            .width(Length::inches(3.0))
1357            .alignment(Alignment::Right);
1358        tbl.cell(0, 0).unwrap().set_text("Subtotal");
1359        tbl.cell(0, 1).unwrap().set_text("$154,600.00");
1360        tbl.cell(1, 0).unwrap().set_text("Tax (8.625%)");
1361        tbl.cell(1, 1).unwrap().set_text("$13,334.25");
1362        tbl.cell(2, 0).unwrap().set_text("Discount (5%)");
1363        tbl.cell(2, 1).unwrap().set_text("-$7,730.00");
1364        tbl.cell(3, 0).unwrap().set_text("AMOUNT DUE");
1365        tbl.cell(3, 0).unwrap().shading("B22222");
1366        tbl.cell(3, 1).unwrap().set_text("$160,204.25");
1367        tbl.cell(3, 1).unwrap().shading("B22222");
1368    }
1369
1370    doc.add_paragraph("");
1371    doc.add_paragraph("");
1372
1373    // ── Payment Details ──
1374    {
1375        let mut p = doc.add_paragraph("");
1376        p.add_run("Payment Details")
1377            .bold(true)
1378            .size(14.0)
1379            .color("333333");
1380    }
1381    doc.add_paragraph("Bank: Silicon Valley Bank")
1382        .indent_left(Length::inches(0.3));
1383    doc.add_paragraph("Account: Tensorbee Inc. — 0847-2953-1120")
1384        .indent_left(Length::inches(0.3));
1385    doc.add_paragraph("Routing: 121140399")
1386        .indent_left(Length::inches(0.3));
1387    doc.add_paragraph("Swift: SVBKUS6S")
1388        .indent_left(Length::inches(0.3));
1389
1390    doc.add_paragraph("");
1391
1392    doc.add_paragraph("Please include invoice number INV-2026-0392 in the payment reference.")
1393        .shading("FAF0F0")
1394        .border_all(BorderStyle::Single, 2, "B22222");
1395
1396    doc
1397}
1398
1399// =============================================================================
1400// 5. REPORT — Forest green + earth tones, with images & hierarchical sections
1401// =============================================================================
1402fn generate_report(_samples_dir: &Path) -> Document {
1403    let mut doc = Document::new();
1404
1405    // Colors: Forest #2D5016, Sage #6B8E23, Earth #8B7355, Cream #FFFAF0
1406    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1407    doc.set_margins(
1408        Length::inches(1.0),
1409        Length::inches(1.0),
1410        Length::inches(1.0),
1411        Length::inches(1.0),
1412    );
1413    doc.set_title("Q4 2025 Environmental Impact Report");
1414    doc.set_author("Walter White");
1415    doc.set_subject("Quarterly Environmental Report");
1416    doc.set_keywords("environment, sustainability, report, Tensorbee");
1417
1418    doc.set_different_first_page(true);
1419    doc.set_first_page_header("");
1420    doc.set_header("Tensorbee — Q4 2025 Environmental Impact Report");
1421    doc.set_footer("CONFIDENTIAL — Page");
1422
1423    // ── Cover ──
1424    let cover_bg = create_sample_png(612, 792, [20, 50, 15]);
1425    doc.add_background_image(&cover_bg, "report_cover.png");
1426
1427    for _ in 0..5 {
1428        doc.add_paragraph("");
1429    }
1430    {
1431        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1432        p.add_run("Q4 2025")
1433            .bold(true)
1434            .size(48.0)
1435            .color("FFFFFF")
1436            .font("Georgia");
1437    }
1438    {
1439        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1440        p.add_run("Environmental Impact Report")
1441            .size(24.0)
1442            .color("90EE90")
1443            .font("Georgia")
1444            .italic(true);
1445    }
1446    doc.add_paragraph("");
1447    {
1448        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1449        p.add_run("Tensorbee — Sustainability Division")
1450            .size(14.0)
1451            .color("C0C0C0");
1452    }
1453    {
1454        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1455        p.add_run("Prepared by Walter White, Chief Sustainability Officer")
1456            .size(11.0)
1457            .color("AAAAAA");
1458    }
1459
1460    // ── TOC ──
1461    doc.add_paragraph("").page_break_before(true);
1462    doc.insert_toc(doc.content_count(), 3);
1463
1464    // ── Section 1: Executive Overview ──
1465    doc.add_paragraph("").page_break_before(true);
1466    doc.add_paragraph("Executive Overview").style("Heading1");
1467    doc.add_paragraph(
1468        "This report presents Tensorbee's environmental performance for Q4 2025. Our \
1469         sustainability initiatives have yielded a 34% reduction in carbon emissions compared \
1470         to Q4 2024, exceeding our target of 25%. Key achievements include the transition to \
1471         100% renewable energy in our primary data centers and the launch of our carbon offset \
1472         marketplace.",
1473    )
1474    .first_line_indent(Length::inches(0.3));
1475
1476    // Image: performance chart
1477    let chart_img = create_chart_png(400, 200);
1478    doc.add_paragraph("");
1479    doc.add_picture(
1480        &chart_img,
1481        "performance_chart.png",
1482        Length::inches(5.0),
1483        Length::inches(2.5),
1484    )
1485    .alignment(Alignment::Center);
1486    {
1487        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1488        p.add_run("Figure 1: Quarterly Carbon Emissions (tonnes CO2e)")
1489            .italic(true)
1490            .size(9.0)
1491            .color("666666");
1492    }
1493
1494    // ── Section 2: Energy Consumption ──
1495    doc.add_paragraph("").page_break_before(true);
1496    doc.add_paragraph("Energy Consumption").style("Heading1");
1497
1498    doc.add_paragraph("Data Center Operations")
1499        .style("Heading2");
1500    doc.add_paragraph(
1501        "Our three primary data centers consumed a combined 4.2 GWh in Q4 2025, \
1502         a 12% reduction from Q3 2025 driven by improved cooling efficiency and \
1503         server consolidation.",
1504    );
1505
1506    // Data table
1507    {
1508        let mut tbl = doc
1509            .add_table(5, 4)
1510            .borders(BorderStyle::Single, 4, "2D5016")
1511            .width_pct(100.0);
1512        let hdrs = ["Data Center", "Capacity (MW)", "Usage (GWh)", "PUE"];
1513        for (c, h) in hdrs.iter().enumerate() {
1514            tbl.cell(0, c).unwrap().set_text(h);
1515            tbl.cell(0, c).unwrap().shading("2D5016");
1516        }
1517        let rows = [
1518            ("San Francisco (Primary)", "3.2", "1.8", "1.12"),
1519            ("Dublin (EU)", "2.1", "1.2", "1.18"),
1520            ("Singapore (APAC)", "1.8", "1.2", "1.24"),
1521            ("TOTAL", "7.1", "4.2", "1.17 avg"),
1522        ];
1523        for (i, (dc, cap, use_, pue)) in rows.iter().enumerate() {
1524            tbl.cell(i + 1, 0).unwrap().set_text(dc);
1525            tbl.cell(i + 1, 1).unwrap().set_text(cap);
1526            tbl.cell(i + 1, 2).unwrap().set_text(use_);
1527            tbl.cell(i + 1, 3).unwrap().set_text(pue);
1528            if i == 3 {
1529                for c in 0..4 {
1530                    tbl.cell(i + 1, c).unwrap().shading("E2EFDA");
1531                }
1532            }
1533        }
1534    }
1535
1536    doc.add_paragraph("");
1537
1538    doc.add_paragraph("Renewable Energy Mix").style("Heading2");
1539    doc.add_paragraph("Breakdown of energy sources across all facilities:");
1540
1541    doc.add_bullet_list_item("Solar PV: 42% (1.76 GWh)", 0);
1542    doc.add_bullet_list_item("Wind Power Purchase Agreements: 38% (1.60 GWh)", 0);
1543    doc.add_bullet_list_item("Hydroelectric: 12% (0.50 GWh)", 0);
1544    doc.add_bullet_list_item("Grid (non-renewable): 8% (0.34 GWh)", 0);
1545
1546    // ── Section 3: Water & Waste ──
1547    doc.add_paragraph("Water & Waste Management")
1548        .style("Heading1");
1549
1550    doc.add_paragraph("Water Usage").style("Heading2");
1551    doc.add_paragraph(
1552        "Total water consumption was 12.4 million gallons, a 15% reduction from Q3. \
1553         Our closed-loop cooling systems now recycle 78% of water used in cooling operations.",
1554    );
1555
1556    doc.add_paragraph("Waste Reduction").style("Heading2");
1557    doc.add_paragraph("E-waste management results for Q4:")
1558        .keep_with_next(true);
1559    {
1560        let mut tbl = doc
1561            .add_table(4, 3)
1562            .borders(BorderStyle::Single, 2, "6B8E23")
1563            .width_pct(80.0);
1564        tbl.cell(0, 0).unwrap().set_text("Category");
1565        tbl.cell(0, 0).unwrap().shading("6B8E23");
1566        tbl.cell(0, 1).unwrap().set_text("Weight (kg)");
1567        tbl.cell(0, 1).unwrap().shading("6B8E23");
1568        tbl.cell(0, 2).unwrap().set_text("Recycled %");
1569        tbl.cell(0, 2).unwrap().shading("6B8E23");
1570        let rows = [
1571            ("Server Hardware", "2,450", "94%"),
1572            ("Networking Equipment", "820", "91%"),
1573            ("Storage Media", "340", "99%"),
1574        ];
1575        for (i, (cat, wt, pct)) in rows.iter().enumerate() {
1576            tbl.cell(i + 1, 0).unwrap().set_text(cat);
1577            tbl.cell(i + 1, 1).unwrap().set_text(wt);
1578            tbl.cell(i + 1, 2).unwrap().set_text(pct);
1579        }
1580    }
1581
1582    // ── Section 4: Initiatives ──
1583    doc.add_paragraph("").page_break_before(true);
1584    doc.add_paragraph("Q1 2026 Initiatives").style("Heading1");
1585
1586    doc.add_paragraph("Planned Programs").style("Heading2");
1587    doc.add_numbered_list_item(
1588        "Deploy on-site battery storage at SF data center (2 MWh capacity)",
1589        0,
1590    );
1591    doc.add_numbered_list_item("Pilot immersion cooling in Dublin facility", 0);
1592    doc.add_numbered_list_item("Launch employee commute carbon offset program", 0);
1593    doc.add_numbered_list_item("Achieve ISO 14001 certification for Singapore facility", 0);
1594
1595    doc.add_paragraph("Investment Targets").style("Heading2");
1596    doc.add_paragraph("Sustainability CapEx allocation for FY2026:")
1597        .keep_with_next(true);
1598    {
1599        let mut tbl = doc
1600            .add_table(5, 2)
1601            .borders(BorderStyle::Single, 4, "2D5016");
1602        tbl.cell(0, 0).unwrap().set_text("Initiative");
1603        tbl.cell(0, 0).unwrap().shading("2D5016");
1604        tbl.cell(0, 1).unwrap().set_text("Budget");
1605        tbl.cell(0, 1).unwrap().shading("2D5016");
1606        let rows = [
1607            ("Battery Storage", "$1.2M"),
1608            ("Immersion Cooling Pilot", "$800K"),
1609            ("Solar Panel Expansion", "$2.1M"),
1610            ("Carbon Credits", "$500K"),
1611        ];
1612        for (i, (init, budget)) in rows.iter().enumerate() {
1613            tbl.cell(i + 1, 0).unwrap().set_text(init);
1614            tbl.cell(i + 1, 1).unwrap().set_text(budget);
1615            if i % 2 == 0 {
1616                tbl.cell(i + 1, 0).unwrap().shading("FFFAF0");
1617                tbl.cell(i + 1, 1).unwrap().shading("FFFAF0");
1618            }
1619        }
1620    }
1621
1622    // Image: sustainability roadmap
1623    let roadmap_img = create_sample_png(500, 100, [40, 80, 30]);
1624    doc.add_paragraph("");
1625    doc.add_picture(
1626        &roadmap_img,
1627        "roadmap.png",
1628        Length::inches(6.0),
1629        Length::inches(1.2),
1630    )
1631    .alignment(Alignment::Center);
1632    {
1633        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1634        p.add_run("Figure 2: 2026 Sustainability Roadmap")
1635            .italic(true)
1636            .size(9.0)
1637            .color("666666");
1638    }
1639
1640    doc.add_paragraph("");
1641    doc.add_paragraph(
1642        "For questions about this report, contact Walter White at sustainability@tensorbee.com.",
1643    )
1644    .shading("FFFAF0")
1645    .border_all(BorderStyle::Single, 2, "2D5016");
1646
1647    doc
1648}
1649
1650// =============================================================================
1651// 6. LETTER — Slate blue + silver scheme
1652// =============================================================================
1653fn generate_letter(_samples_dir: &Path) -> Document {
1654    let mut doc = Document::new();
1655
1656    // Colors: Slate #4A5568, Blue accent #3182CE
1657    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1658    doc.set_margins(
1659        Length::inches(1.25),
1660        Length::inches(1.25),
1661        Length::inches(1.25),
1662        Length::inches(1.25),
1663    );
1664    doc.set_title("Business Letter");
1665    doc.set_author("Walter White");
1666
1667    // Banner header using raw XML
1668    let logo_img = create_logo_png(220, 48);
1669    let banner = build_header_banner_xml(
1670        "rId1",
1671        &BannerOpts {
1672            bg_color: "4A5568",
1673            banner_width: 7772400,
1674            banner_height: 731520, // ~0.8"
1675            logo_width: 2011680,
1676            logo_height: 438912,
1677            logo_x_offset: 295125,
1678            logo_y_offset: 146304,
1679        },
1680    );
1681    doc.set_raw_header_with_images(
1682        banner,
1683        &[("rId1", &logo_img, "logo.png")],
1684        rdocx_oxml::header_footer::HdrFtrType::Default,
1685    );
1686    doc.set_margins(
1687        Length::twips(2000),
1688        Length::twips(1800),
1689        Length::twips(1440),
1690        Length::twips(1800),
1691    );
1692    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
1693
1694    // Footer with contact
1695    doc.set_footer(
1696        "Tensorbee | 123 Innovation Drive, Suite 400 | San Francisco, CA 94105 | tensorbee.com",
1697    );
1698
1699    // ── Sender Address ──
1700    doc.add_paragraph("Walter White");
1701    doc.add_paragraph("Chief Executive Officer");
1702    doc.add_paragraph("Tensorbee");
1703    doc.add_paragraph("123 Innovation Drive, Suite 400");
1704    doc.add_paragraph("San Francisco, CA 94105");
1705    doc.add_paragraph("");
1706    doc.add_paragraph("February 22, 2026");
1707
1708    doc.add_paragraph("");
1709
1710    // ── Recipient ──
1711    doc.add_paragraph("Ms. Sarah Chen");
1712    doc.add_paragraph("VP of Engineering");
1713    doc.add_paragraph("Meridian Dynamics LLC");
1714    doc.add_paragraph("456 Enterprise Boulevard");
1715    doc.add_paragraph("Austin, TX 78701");
1716
1717    doc.add_paragraph("");
1718
1719    // ── Subject ──
1720    {
1721        let mut p = doc.add_paragraph("");
1722        p.add_run("Re: Strategic Technology Partnership — Phase 2 Expansion")
1723            .bold(true);
1724    }
1725
1726    doc.add_paragraph("");
1727
1728    // ── Body ──
1729    doc.add_paragraph("Dear Ms. Chen,");
1730    doc.add_paragraph("");
1731
1732    doc.add_paragraph(
1733        "I am writing to express Tensorbee's enthusiasm for expanding our strategic technology \
1734         partnership with Meridian Dynamics. Following the successful completion of Phase 1, \
1735         which delivered a 40% improvement in your ML inference pipeline throughput, we believe \
1736         the foundation is firmly established for an ambitious Phase 2 engagement.",
1737    )
1738    .first_line_indent(Length::inches(0.5));
1739
1740    doc.add_paragraph(
1741        "During our recent executive review, your team highlighted three priority areas for \
1742         the next phase of collaboration:",
1743    )
1744    .first_line_indent(Length::inches(0.5));
1745
1746    doc.add_numbered_list_item(
1747        "Real-time anomaly detection for your financial transaction monitoring system, \
1748         targeting sub-100ms latency at 50,000 transactions per second.",
1749        0,
1750    );
1751    doc.add_numbered_list_item(
1752        "Federated learning infrastructure to enable model training across your distributed \
1753         data centers without centralizing sensitive financial data.",
1754        0,
1755    );
1756    doc.add_numbered_list_item(
1757        "MLOps automation to reduce your model deployment cycle from the current 5 days \
1758         to under 4 hours.",
1759        0,
1760    );
1761
1762    doc.add_paragraph(
1763        "Our engineering team has prepared a detailed technical proposal addressing each of \
1764         these areas. We have allocated a dedicated team of eight senior engineers, led by \
1765         our CTO, to ensure continuity with the Phase 1 team your organization has already \
1766         built a productive working relationship with.",
1767    )
1768    .first_line_indent(Length::inches(0.5));
1769
1770    doc.add_paragraph(
1771        "I would welcome the opportunity to present our Phase 2 proposal in person. My \
1772         assistant will reach out to coordinate a meeting at your convenience during the \
1773         first week of March.",
1774    )
1775    .first_line_indent(Length::inches(0.5));
1776
1777    doc.add_paragraph("");
1778
1779    doc.add_paragraph("Warm regards,");
1780    doc.add_paragraph("");
1781    doc.add_paragraph("");
1782
1783    // Signature line
1784    doc.add_paragraph("")
1785        .border_bottom(BorderStyle::Single, 4, "4A5568");
1786    {
1787        let mut p = doc.add_paragraph("");
1788        p.add_run("Walter White").bold(true).size(12.0);
1789    }
1790    {
1791        let mut p = doc.add_paragraph("");
1792        p.add_run("Chief Executive Officer, Tensorbee")
1793            .italic(true)
1794            .size(10.0)
1795            .color("666666");
1796    }
1797    {
1798        let mut p = doc.add_paragraph("");
1799        p.add_run("walter@tensorbee.com | +1 (415) 555-0199")
1800            .size(10.0)
1801            .color("888888");
1802    }
1803
1804    doc
1805}
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 587-605)
86fn generate_feature_showcase(_samples_dir: &Path) -> Document {
87    let mut doc = Document::new();
88
89    // ── Page Setup & Metadata ──
90    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
91    doc.set_margins(
92        Length::inches(1.0),
93        Length::inches(1.0),
94        Length::inches(1.0),
95        Length::inches(1.0),
96    );
97    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
98    doc.set_gutter(Length::twips(0));
99    doc.set_title("rdocx Feature Showcase");
100    doc.set_author("rdocx Sample Generator");
101    doc.set_subject("Comprehensive feature demonstration");
102    doc.set_keywords("rdocx, docx, rust, sample, showcase");
103
104    // Headers & Footers with different first page
105    doc.set_different_first_page(true);
106    doc.set_first_page_header("rdocx");
107    doc.set_first_page_footer("Feature Showcase — Cover Page");
108    doc.set_header("rdocx Feature Showcase");
109    doc.set_footer("Generated by rdocx");
110
111    // ── COVER PAGE ──
112    let bg = create_sample_png(612, 792, [20, 45, 90]);
113    doc.add_background_image(&bg, "cover_bg.png");
114
115    for _ in 0..3 {
116        doc.add_paragraph("");
117    }
118    {
119        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
120        p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
121    }
122    {
123        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
124        p.add_run("Complete Feature Showcase")
125            .size(28.0)
126            .color("FFFFFF")
127            .italic(true);
128    }
129    doc.add_paragraph("");
130    {
131        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
132        p.add_run("Every feature of the rdocx Rust library")
133            .size(13.0)
134            .color("B0C4DE");
135    }
136    {
137        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
138        p.add_run("demonstrated in a single document.")
139            .size(13.0)
140            .color("B0C4DE");
141    }
142
143    // ── TABLE OF CONTENTS ──
144    doc.add_paragraph("").page_break_before(true);
145    doc.insert_toc(doc.content_count(), 3);
146
147    // ── SECTION 1: TEXT FORMATTING ──
148    doc.add_paragraph("").page_break_before(true);
149    doc.add_paragraph("1. Text Formatting").style("Heading1");
150
151    // Paragraph Alignment
152    doc.add_paragraph("1.1 Paragraph Alignment")
153        .style("Heading2");
154    doc.add_paragraph("Left-aligned paragraph (default).")
155        .alignment(Alignment::Left);
156    doc.add_paragraph("Center-aligned paragraph.")
157        .alignment(Alignment::Center);
158    doc.add_paragraph("Right-aligned paragraph.")
159        .alignment(Alignment::Right);
160    doc.add_paragraph(
161        "Justified paragraph — this text is long enough to demonstrate how justified alignment \
162         distributes extra space across word gaps so lines fill the full width of the text area.",
163    )
164    .alignment(Alignment::Justify);
165
166    doc.add_paragraph("");
167
168    // Run Formatting
169    doc.add_paragraph("1.2 Run Formatting").style("Heading2");
170    {
171        let mut p = doc.add_paragraph("");
172        p.add_run("Bold").bold(true);
173        p.add_run(" | ");
174        p.add_run("Italic").italic(true);
175        p.add_run(" | ");
176        p.add_run("Bold Italic").bold(true).italic(true);
177        p.add_run(" | ");
178        p.add_run("Underline").underline(true);
179        p.add_run(" | ");
180        p.add_run("Strikethrough").strike(true);
181        p.add_run(" | ");
182        p.add_run("Double Strike").double_strike(true);
183    }
184    {
185        let mut p = doc.add_paragraph("");
186        p.add_run("Red").color("FF0000");
187        p.add_run(" | ");
188        p.add_run("Blue").color("0000FF");
189        p.add_run(" | ");
190        p.add_run("Green").color("00AA00");
191        p.add_run(" | ");
192        p.add_run("Highlighted").highlight("FFFF00");
193    }
194    {
195        let mut p = doc.add_paragraph("");
196        p.add_run("8pt").size(8.0);
197        p.add_run(" | ");
198        p.add_run("11pt").size(11.0);
199        p.add_run(" | ");
200        p.add_run("16pt").size(16.0);
201        p.add_run(" | ");
202        p.add_run("24pt").size(24.0);
203    }
204    {
205        let mut p = doc.add_paragraph("");
206        p.add_run("Arial").font("Arial");
207        p.add_run(" | ");
208        p.add_run("Times New Roman").font("Times New Roman");
209        p.add_run(" | ");
210        p.add_run("Courier New").font("Courier New");
211    }
212    {
213        let mut p = doc.add_paragraph("");
214        p.add_run("H");
215        p.add_run("2").subscript();
216        p.add_run("O (subscript) | E=mc");
217        p.add_run("2").superscript();
218        p.add_run(" (superscript)");
219    }
220    {
221        let mut p = doc.add_paragraph("");
222        p.add_run("ALL CAPS").all_caps(true);
223        p.add_run(" | ");
224        p.add_run("Small Caps").small_caps(true);
225        p.add_run(" | ");
226        p.add_run("Expanded").character_spacing(Length::twips(40));
227        p.add_run(" | ");
228        p.add_run("Hidden text (not visible)").hidden(true);
229    }
230
231    doc.add_paragraph("");
232
233    // Underline Styles
234    doc.add_paragraph("1.3 Underline Styles").style("Heading2");
235    {
236        let mut p = doc.add_paragraph("");
237        p.add_run("Single ")
238            .underline_style(rdocx::UnderlineStyle::Single);
239        p.add_run("Double ")
240            .underline_style(rdocx::UnderlineStyle::Double);
241        p.add_run("Thick ")
242            .underline_style(rdocx::UnderlineStyle::Thick);
243        p.add_run("Dotted ")
244            .underline_style(rdocx::UnderlineStyle::Dotted);
245        p.add_run("Dash ")
246            .underline_style(rdocx::UnderlineStyle::Dash);
247        p.add_run("Wave ")
248            .underline_style(rdocx::UnderlineStyle::Wave);
249    }
250
251    // ── SECTION 2: PARAGRAPH FORMATTING ──
252    doc.add_paragraph("").page_break_before(true);
253    doc.add_paragraph("2. Paragraph Formatting")
254        .style("Heading1");
255
256    doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
257    doc.add_paragraph("Paragraph with light green shading")
258        .shading("E2EFDA");
259    doc.add_paragraph("Paragraph with bottom border")
260        .border_bottom(BorderStyle::Single, 6, "2E75B6");
261    doc.add_paragraph("Paragraph with all borders (red)")
262        .border_all(BorderStyle::Single, 4, "FF0000");
263
264    doc.add_paragraph("2.2 Indentation").style("Heading2");
265    doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
266        .indent_left(Length::inches(1.0))
267        .hanging_indent(Length::inches(0.5));
268    doc.add_paragraph("0.5-inch first-line indent on this paragraph")
269        .first_line_indent(Length::inches(0.5));
270    doc.add_paragraph("Both left and right indent (0.75 inches each)")
271        .indent_left(Length::inches(0.75))
272        .indent_right(Length::inches(0.75));
273
274    doc.add_paragraph("2.3 Spacing & Line Height")
275        .style("Heading2");
276    doc.add_paragraph("Extra space before (24pt) and after (12pt)")
277        .space_before(Length::pt(24.0))
278        .space_after(Length::pt(12.0));
279    doc.add_paragraph(
280        "Double line spacing paragraph. This text should have extra vertical space between \
281         lines to demonstrate line_spacing_multiple(2.0).",
282    )
283    .line_spacing_multiple(2.0);
284    doc.add_paragraph("Exact 20pt line spacing (fixed height).")
285        .line_spacing(20.0);
286
287    doc.add_paragraph("2.4 Pagination Controls")
288        .style("Heading2");
289    doc.add_paragraph("keep_with_next — this paragraph stays with the next")
290        .keep_with_next(true);
291    doc.add_paragraph("(This paragraph was kept with the one above.)");
292    doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
293        .keep_together(true);
294    doc.add_paragraph("widow_control — prevents widow/orphan lines")
295        .widow_control(true);
296
297    // ── SECTION 3: LISTS ──
298    doc.add_paragraph("").page_break_before(true);
299    doc.add_paragraph("3. Lists").style("Heading1");
300
301    doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
302    doc.add_bullet_list_item("First item", 0);
303    doc.add_bullet_list_item("Second item", 0);
304    doc.add_bullet_list_item("Nested level 1", 1);
305    doc.add_bullet_list_item("Nested level 2", 2);
306    doc.add_bullet_list_item("Back to level 1", 1);
307    doc.add_bullet_list_item("Third item", 0);
308
309    doc.add_paragraph("");
310
311    doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
312    doc.add_numbered_list_item("First numbered item", 0);
313    doc.add_numbered_list_item("Second numbered item", 0);
314    doc.add_numbered_list_item("Sub-item A", 1);
315    doc.add_numbered_list_item("Sub-item B", 1);
316    doc.add_numbered_list_item("Third numbered item", 0);
317
318    // ── SECTION 4: TAB STOPS ──
319    doc.add_paragraph("");
320    doc.add_paragraph("4. Tab Stops").style("Heading1");
321
322    doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
323    doc.add_paragraph("Left\tCenter\tRight\tDecimal")
324        .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
325        .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
326        .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
327        .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
328
329    doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
330    doc.add_paragraph("Item\t\tPrice")
331        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
332        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
333        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
334    doc.add_paragraph("Widget\t\t$19.99")
335        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
336        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
337        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
338    doc.add_paragraph("Gadget\t\t$249.50")
339        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
340        .add_tab_stop_with_leader(
341            TabAlignment::Right,
342            Length::inches(4.0),
343            TabLeader::Underscore,
344        )
345        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
346
347    // ── SECTION 5: TABLES ──
348    doc.add_paragraph("").page_break_before(true);
349    doc.add_paragraph("5. Tables").style("Heading1");
350
351    doc.add_paragraph("5.1 Basic Table").style("Heading2");
352    {
353        let mut tbl = doc
354            .add_table(4, 3)
355            .borders(BorderStyle::Single, 4, "000000");
356        for col in 0..3 {
357            tbl.cell(0, col).unwrap().shading("2E75B6");
358        }
359        tbl.cell(0, 0).unwrap().set_text("Name");
360        tbl.cell(0, 1).unwrap().set_text("Role");
361        tbl.cell(0, 2).unwrap().set_text("Location");
362        tbl.cell(1, 0).unwrap().set_text("Walter White");
363        tbl.cell(1, 1).unwrap().set_text("CEO");
364        tbl.cell(1, 2).unwrap().set_text("Albuquerque");
365        tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
366        tbl.cell(2, 1).unwrap().set_text("CTO");
367        tbl.cell(2, 2).unwrap().set_text("Remote");
368        tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
369        tbl.cell(3, 1).unwrap().set_text("Security");
370        tbl.cell(3, 2).unwrap().set_text("Washington");
371    }
372
373    doc.add_paragraph("");
374
375    doc.add_paragraph("5.2 Column Span & Cell Shading")
376        .style("Heading2");
377    {
378        let mut tbl = doc
379            .add_table(3, 4)
380            .borders(BorderStyle::Single, 4, "000000")
381            .width_pct(100.0);
382        tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
383        tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
384        tbl.cell(1, 0).unwrap().set_text("Region");
385        tbl.cell(1, 0).unwrap().shading("D6E4F0");
386        tbl.cell(1, 1).unwrap().set_text("Q1");
387        tbl.cell(1, 1).unwrap().shading("D6E4F0");
388        tbl.cell(1, 2).unwrap().set_text("Q2");
389        tbl.cell(1, 2).unwrap().shading("D6E4F0");
390        tbl.cell(1, 3).unwrap().set_text("Total");
391        tbl.cell(1, 3).unwrap().shading("D6E4F0");
392        tbl.cell(2, 0).unwrap().set_text("Americas");
393        tbl.cell(2, 1).unwrap().set_text("$2.4M");
394        tbl.cell(2, 2).unwrap().set_text("$2.7M");
395        tbl.cell(2, 3).unwrap().set_text("$5.1M");
396    }
397
398    doc.add_paragraph("");
399
400    doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
401    {
402        let mut tbl = doc
403            .add_table(4, 3)
404            .borders(BorderStyle::Single, 4, "000000");
405        tbl.cell(0, 0).unwrap().set_text("Category");
406        tbl.cell(0, 0).unwrap().shading("E2EFDA");
407        tbl.cell(0, 1).unwrap().set_text("Item");
408        tbl.cell(0, 1).unwrap().shading("E2EFDA");
409        tbl.cell(0, 2).unwrap().set_text("Price");
410        tbl.cell(0, 2).unwrap().shading("E2EFDA");
411        tbl.cell(1, 0).unwrap().set_text("Hardware");
412        tbl.cell(1, 0).unwrap().v_merge_restart();
413        tbl.cell(1, 1).unwrap().set_text("Laptop");
414        tbl.cell(1, 2).unwrap().set_text("$1,200");
415        tbl.cell(2, 0).unwrap().v_merge_continue();
416        tbl.cell(2, 1).unwrap().set_text("Monitor");
417        tbl.cell(2, 2).unwrap().set_text("$450");
418        tbl.cell(3, 0).unwrap().set_text("Software");
419        tbl.cell(3, 1).unwrap().set_text("IDE License");
420        tbl.cell(3, 2).unwrap().set_text("$200/yr");
421    }
422
423    doc.add_paragraph("");
424
425    doc.add_paragraph("5.4 Nested Table").style("Heading2");
426    {
427        let mut tbl = doc
428            .add_table(2, 2)
429            .borders(BorderStyle::Single, 6, "2E75B6");
430        tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
431        tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
432        tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
433        {
434            let mut cell = tbl.cell(1, 1).unwrap();
435            cell.set_text("Nested table below:");
436            let mut nested = cell
437                .add_table(2, 2)
438                .borders(BorderStyle::Single, 2, "FF6600");
439            nested.cell(0, 0).unwrap().set_text("A");
440            nested.cell(0, 1).unwrap().set_text("B");
441            nested.cell(1, 0).unwrap().set_text("C");
442            nested.cell(1, 1).unwrap().set_text("D");
443        }
444    }
445
446    doc.add_paragraph("");
447
448    doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
449        .style("Heading2");
450    {
451        let mut tbl = doc
452            .add_table(2, 3)
453            .borders(BorderStyle::Single, 4, "666666");
454        tbl.row(0).unwrap().height(Length::pt(50.0)).header();
455        tbl.cell(0, 0).unwrap().set_text("Top");
456        tbl.cell(0, 0)
457            .unwrap()
458            .vertical_alignment(VerticalAlignment::Top)
459            .shading("FFF2CC");
460        tbl.cell(0, 1).unwrap().set_text("Center");
461        tbl.cell(0, 1)
462            .unwrap()
463            .vertical_alignment(VerticalAlignment::Center)
464            .shading("D9E2F3");
465        tbl.cell(0, 2).unwrap().set_text("Bottom");
466        tbl.cell(0, 2)
467            .unwrap()
468            .vertical_alignment(VerticalAlignment::Bottom)
469            .shading("E2EFDA");
470        tbl.row(1).unwrap().cant_split();
471        tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
472        tbl.cell(1, 0).unwrap().no_wrap();
473        tbl.cell(1, 1).unwrap().set_text("Fixed width");
474        tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
475        tbl.cell(1, 2).unwrap().set_text("Normal");
476    }
477
478    // ── SECTION 6: IMAGES ──
479    doc.add_paragraph("").page_break_before(true);
480    doc.add_paragraph("6. Images").style("Heading1");
481
482    doc.add_paragraph("6.1 Inline Image").style("Heading2");
483    doc.add_paragraph("A blue gradient image below:");
484    let img = create_sample_png(200, 50, [0, 80, 200]);
485    doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
486
487    doc.add_paragraph("");
488    doc.add_paragraph("6.2 Header Image").style("Heading2");
489    let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
490    doc.set_header_image(
491        &hdr_img,
492        "header_logo.png",
493        Length::inches(2.0),
494        Length::inches(0.2),
495    );
496    doc.add_paragraph("The header now contains an inline image (check the top of this page).");
497
498    doc.add_paragraph("");
499    doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
500
501    // ── SECTION 7: CONTENT MANIPULATION ──
502    doc.add_paragraph("").page_break_before(true);
503    doc.add_paragraph("7. Content Manipulation")
504        .style("Heading1");
505
506    doc.add_paragraph("7.1 Placeholder Replacement")
507        .style("Heading2");
508    doc.add_paragraph("Customer: {{customer}}");
509    doc.add_paragraph("Date: {{date}}");
510    doc.add_paragraph("Reference: {{ref_number}}");
511    {
512        let mut tbl = doc
513            .add_table(2, 2)
514            .borders(BorderStyle::Single, 4, "000000");
515        tbl.cell(0, 0).unwrap().set_text("Project");
516        tbl.cell(0, 0).unwrap().shading("D6E4F0");
517        tbl.cell(0, 1).unwrap().set_text("{{project}}");
518        tbl.cell(1, 0).unwrap().set_text("Status");
519        tbl.cell(1, 0).unwrap().shading("D6E4F0");
520        tbl.cell(1, 1).unwrap().set_text("{{status}}");
521    }
522
523    let mut replacements = HashMap::new();
524    replacements.insert("{{customer}}", "Tensorbee Inc.");
525    replacements.insert("{{date}}", "February 22, 2026");
526    replacements.insert("{{ref_number}}", "TB-2026-001");
527    replacements.insert("{{project}}", "Infrastructure Upgrade");
528    replacements.insert("{{status}}", "In Progress");
529    let n = doc.replace_all(&replacements);
530    doc.add_paragraph(&format!("({n} placeholders replaced above)"));
531
532    doc.add_paragraph("");
533
534    doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
535    doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
536    let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
537    doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
538
539    doc.add_paragraph("");
540
541    doc.add_paragraph("7.3 Content Insertion").style("Heading2");
542    doc.add_paragraph("Section A: First content.");
543    doc.add_paragraph("Section C: Third content.");
544    if let Some(idx) = doc.find_content_index("Section C") {
545        doc.insert_paragraph(
546            idx,
547            "Section B: Inserted between A and C via find_content_index().",
548        );
549    }
550
551    // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
552    doc.add_paragraph("").section_break(SectionBreak::NextPage);
553    doc.add_paragraph("8. Section Breaks & Orientation")
554        .style("Heading1");
555    doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
556
557    {
558        let mut tbl = doc
559            .add_table(3, 7)
560            .borders(BorderStyle::Single, 4, "2E75B6");
561        let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
562        for (c, h) in headers.iter().enumerate() {
563            tbl.cell(0, c).unwrap().set_text(h);
564            tbl.cell(0, c).unwrap().shading("2E75B6");
565        }
566        let data = [
567            [
568                "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
569            ],
570            [
571                "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
572            ],
573        ];
574        for (r, row) in data.iter().enumerate() {
575            for (c, v) in row.iter().enumerate() {
576                tbl.cell(r + 1, c).unwrap().set_text(v);
577            }
578        }
579    }
580
581    doc.add_paragraph("")
582        .section_break(SectionBreak::NextPage)
583        .section_landscape();
584
585    // ── SECTION 9: CUSTOM STYLES ──
586    doc.add_paragraph("9. Custom Styles").style("Heading1");
587    doc.add_style(
588        StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
589            .based_on("Normal")
590            .paragraph_properties({
591                let mut ppr = rdocx_oxml::properties::CT_PPr::default();
592                ppr.shading = Some(rdocx_oxml::properties::CT_Shd {
593                    val: "clear".to_string(),
594                    color: None,
595                    fill: Some("FFF2CC".to_string()),
596                });
597                ppr
598            })
599            .run_properties({
600                let mut rpr = rdocx_oxml::properties::CT_RPr::default();
601                rpr.bold = Some(true);
602                rpr.color = Some("C45911".to_string());
603                rpr
604            }),
605    );
606    doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
607        .style("CustomHighlight");
608
609    doc.add_paragraph("");
610
611    // ── SECTION 10: DOCUMENT INTELLIGENCE ──
612    doc.add_paragraph("10. Document Intelligence API")
613        .style("Heading1");
614
615    let wc = doc.word_count();
616    let hc = doc.headings().len();
617    let ic = doc.images().len();
618    let lc = doc.links().len();
619    doc.add_paragraph(&format!("Word count: {wc}"));
620    doc.add_paragraph(&format!("Heading count: {hc}"));
621    doc.add_paragraph(&format!("Image count: {ic}"));
622    doc.add_paragraph(&format!("Link count: {lc}"));
623
624    let outline = doc.document_outline();
625    doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
626
627    let issues = doc.audit_accessibility();
628    doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
629    for issue in &issues {
630        doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
631    }
632
633    // ── SECTION 11: DOCUMENT MERGING ──
634    doc.add_paragraph("");
635    doc.add_paragraph("11. Document Merging").style("Heading1");
636    {
637        let mut other = Document::new();
638        other.add_paragraph("This paragraph was merged from another document using append().");
639        doc.append(&other);
640    }
641
642    doc.add_paragraph("");
643
644    // ── SUMMARY ──
645    doc.add_paragraph("Summary of Demonstrated Features")
646        .style("Heading1");
647    let features = [
648        "Page setup: size, margins, header/footer distance, gutter",
649        "Document metadata: title, author, subject, keywords",
650        "Headers and footers: text, images, different first page",
651        "Background images (full-page behind text)",
652        "Table of Contents generation with bookmarks",
653        "Text formatting: bold, italic, underline styles, strike, color, size, font",
654        "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
655        "Paragraph formatting: alignment, borders, shading, spacing, indentation",
656        "Pagination controls: keep-with-next, keep-together, widow control",
657        "Bullet and numbered lists with nesting",
658        "Tab stops with dot/underscore/hyphen leaders",
659        "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
660        "Row properties: header rows, exact height, cant-split",
661        "Cell properties: width, no-wrap, vertical alignment",
662        "Inline images and header images",
663        "Placeholder replacement (single and batch)",
664        "Regex-based find and replace",
665        "Content insertion at specific positions",
666        "Section breaks with mixed portrait/landscape",
667        "Custom paragraph and character styles",
668        "Document intelligence: word count, headings, outline, images, links",
669        "Accessibility audit",
670        "Document merging (append)",
671        "Export: DOCX, PDF, HTML, Markdown",
672    ];
673    for f in &features {
674        doc.add_bullet_list_item(f, 0);
675    }
676    doc.add_paragraph("");
677    doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
678        .alignment(Alignment::Center)
679        .shading("E2EFDA")
680        .border_all(BorderStyle::Single, 2, "00AA00");
681
682    doc
683}
684
685// =============================================================================
686// 2. PROPOSAL DOCUMENT — Deep navy + gold scheme
687// =============================================================================
688fn generate_proposal(_samples_dir: &Path) -> Document {
689    let mut doc = Document::new();
690
691    // Colors: Navy #1B2A4A, Gold #C5922E, Light #F4F1EB
692    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
693    doc.set_margins(
694        Length::inches(1.0),
695        Length::inches(1.0),
696        Length::inches(1.0),
697        Length::inches(1.0),
698    );
699    doc.set_title("AI Platform Modernization Proposal");
700    doc.set_author("Walter White");
701    doc.set_subject("Technology Proposal");
702    doc.set_keywords("proposal, AI, modernization, Tensorbee");
703
704    // Banner header
705    let logo_img = create_logo_png(220, 48);
706    let banner = build_header_banner_xml(
707        "rId1",
708        &BannerOpts {
709            bg_color: "1B2A4A",
710            banner_width: 7772400,
711            banner_height: 914400,
712            logo_width: 2011680,
713            logo_height: 438912,
714            logo_x_offset: 295125,
715            logo_y_offset: 237744,
716        },
717    );
718    doc.set_raw_header_with_images(
719        banner,
720        &[("rId1", &logo_img, "logo.png")],
721        rdocx_oxml::header_footer::HdrFtrType::Default,
722    );
723    doc.set_different_first_page(true);
724    let cover_banner = build_header_banner_xml(
725        "rId1",
726        &BannerOpts {
727            bg_color: "C5922E",
728            banner_width: 7772400,
729            banner_height: 914400,
730            logo_width: 2011680,
731            logo_height: 438912,
732            logo_x_offset: 295125,
733            logo_y_offset: 237744,
734        },
735    );
736    doc.set_raw_header_with_images(
737        cover_banner,
738        &[("rId1", &logo_img, "logo.png")],
739        rdocx_oxml::header_footer::HdrFtrType::First,
740    );
741    doc.set_margins(
742        Length::twips(2292),
743        Length::twips(1440),
744        Length::twips(1440),
745        Length::twips(1440),
746    );
747    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
748    doc.set_footer("Tensorbee — Confidential");
749
750    // Custom styles
751    doc.add_style(
752        StyleBuilder::paragraph("ProposalTitle", "Proposal Title")
753            .based_on("Normal")
754            .run_properties({
755                let mut rpr = rdocx_oxml::properties::CT_RPr::default();
756                rpr.bold = Some(true);
757                rpr.font_ascii = Some("Georgia".to_string());
758                rpr.font_hansi = Some("Georgia".to_string());
759                rpr.sz = Some(rdocx_oxml::HalfPoint(56)); // half-points → 28pt
760                rpr.color = Some("1B2A4A".to_string());
761                rpr
762            }),
763    );
764
765    // ── Cover Page ──
766    for _ in 0..4 {
767        doc.add_paragraph("");
768    }
769    doc.add_paragraph("AI Platform Modernization")
770        .style("ProposalTitle")
771        .alignment(Alignment::Center);
772    doc.add_paragraph("")
773        .alignment(Alignment::Center)
774        .border_bottom(BorderStyle::Single, 8, "C5922E");
775    {
776        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
777        p.add_run("Prepared for Global Financial Corp.")
778            .size(14.0)
779            .color("666666")
780            .italic(true);
781    }
782    {
783        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
784        p.add_run("Prepared by: Walter White, CEO — Tensorbee")
785            .size(12.0)
786            .color("888888");
787    }
788    {
789        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
790        p.add_run("February 22, 2026").size(12.0).color("888888");
791    }
792
793    // ── TOC ──
794    doc.add_paragraph("").page_break_before(true);
795    doc.add_paragraph("Table of Contents").style("Heading1");
796    // We'll add a manual-style TOC since the insert_toc goes at a specific position
797    doc.insert_toc(doc.content_count(), 2);
798
799    // ── Executive Summary ──
800    doc.add_paragraph("").page_break_before(true);
801    doc.add_paragraph("Executive Summary").style("Heading1");
802    doc.add_paragraph(
803        "Tensorbee proposes a comprehensive modernization of Global Financial Corp's AI platform, \
804         transitioning from legacy batch-processing systems to a real-time inference architecture. \
805         This transformation will reduce model deployment time from weeks to hours, improve \
806         prediction accuracy by an estimated 23%, and deliver $4.2M in annual operational savings.",
807    )
808    .first_line_indent(Length::inches(0.3));
809    doc.add_paragraph(
810        "The project spans three phases over 18 months, with a total investment of $2.8M. \
811         Tensorbee brings deep expertise in ML infrastructure, having successfully completed \
812         similar transformations for three Fortune 500 financial institutions.",
813    )
814    .first_line_indent(Length::inches(0.3));
815
816    // Key metrics table
817    doc.add_paragraph("");
818    {
819        let mut tbl = doc
820            .add_table(5, 2)
821            .borders(BorderStyle::Single, 4, "1B2A4A")
822            .width_pct(80.0);
823        tbl.cell(0, 0).unwrap().set_text("Key Metric");
824        tbl.cell(0, 0).unwrap().shading("1B2A4A");
825        tbl.cell(0, 1).unwrap().set_text("Value");
826        tbl.cell(0, 1).unwrap().shading("1B2A4A");
827        let rows = [
828            ("Total Investment", "$2.8M"),
829            ("Annual Savings", "$4.2M"),
830            ("ROI (Year 1)", "150%"),
831            ("Timeline", "18 months"),
832        ];
833        for (i, (k, v)) in rows.iter().enumerate() {
834            tbl.cell(i + 1, 0).unwrap().set_text(k);
835            if i % 2 == 0 {
836                tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
837            }
838            tbl.cell(i + 1, 1).unwrap().set_text(v);
839            if i % 2 == 0 {
840                tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
841            }
842        }
843    }
844
845    // ── Problem Statement ──
846    doc.add_paragraph("").page_break_before(true);
847    doc.add_paragraph("Problem Statement").style("Heading1");
848    doc.add_paragraph(
849        "Global Financial Corp's current AI infrastructure faces three critical challenges:",
850    );
851    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);
852    doc.add_numbered_list_item("Scalability Constraints — The batch processing architecture cannot handle real-time inference demands during peak trading hours.", 0);
853    doc.add_numbered_list_item("Technical Debt — Legacy Python 2.7 codebases and manual deployment scripts create reliability risks and slow iteration speed.", 0);
854
855    // ── Proposed Solution ──
856    doc.add_paragraph("").page_break_before(true);
857    doc.add_paragraph("Proposed Solution").style("Heading1");
858
859    doc.add_paragraph("Phase 1: Foundation (Months 1-6)")
860        .style("Heading2");
861    doc.add_bullet_list_item("Deploy Kubernetes-based ML serving infrastructure", 0);
862    doc.add_bullet_list_item("Implement CI/CD pipeline for model deployment", 0);
863    doc.add_bullet_list_item("Migrate top 5 production models to new platform", 0);
864
865    doc.add_paragraph("Phase 2: Expansion (Months 7-12)")
866        .style("Heading2");
867    doc.add_bullet_list_item(
868        "Real-time feature engineering pipeline (Apache Kafka + Flink)",
869        0,
870    );
871    doc.add_bullet_list_item("A/B testing framework for model variants", 0);
872    doc.add_bullet_list_item("Automated model monitoring and drift detection", 0);
873
874    doc.add_paragraph("Phase 3: Optimization (Months 13-18)")
875        .style("Heading2");
876    doc.add_bullet_list_item("GPU inference optimization (TensorRT/ONNX Runtime)", 0);
877    doc.add_bullet_list_item("Multi-region deployment for latency reduction", 0);
878    doc.add_bullet_list_item(
879        "Self-service model deployment portal for data science teams",
880        0,
881    );
882
883    // ── Budget ──
884    doc.add_paragraph("Budget Breakdown").style("Heading1");
885    {
886        let mut tbl = doc
887            .add_table(6, 3)
888            .borders(BorderStyle::Single, 4, "1B2A4A")
889            .width_pct(100.0);
890        tbl.cell(0, 0).unwrap().set_text("Category");
891        tbl.cell(0, 0).unwrap().shading("1B2A4A");
892        tbl.cell(0, 1).unwrap().set_text("Cost");
893        tbl.cell(0, 1).unwrap().shading("1B2A4A");
894        tbl.cell(0, 2).unwrap().set_text("Notes");
895        tbl.cell(0, 2).unwrap().shading("1B2A4A");
896        let items = [
897            (
898                "Infrastructure",
899                "$450,000",
900                "Cloud compute + GPU instances",
901            ),
902            ("Engineering Services", "$1,600,000", "12 FTE x 18 months"),
903            (
904                "Software Licenses",
905                "$350,000",
906                "Kafka, monitoring, CI/CD tools",
907            ),
908            (
909                "Training & Enablement",
910                "$200,000",
911                "Team training, documentation",
912            ),
913            ("Contingency (10%)", "$200,000", "Risk buffer"),
914        ];
915        for (i, (cat, cost, note)) in items.iter().enumerate() {
916            tbl.cell(i + 1, 0).unwrap().set_text(cat);
917            tbl.cell(i + 1, 1).unwrap().set_text(cost);
918            tbl.cell(i + 1, 2).unwrap().set_text(note);
919            if i % 2 == 0 {
920                tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
921                tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
922                tbl.cell(i + 1, 2).unwrap().shading("F4F1EB");
923            }
924        }
925    }
926
927    // ── Closing ──
928    doc.add_paragraph("");
929    doc.add_paragraph("Next Steps").style("Heading1");
930    doc.add_numbered_list_item(
931        "Schedule technical deep-dive with GFC engineering team (Week 1)",
932        0,
933    );
934    doc.add_numbered_list_item("Finalize scope and sign SOW (Week 2-3)", 0);
935    doc.add_numbered_list_item("Kick off Phase 1 with joint planning session (Week 4)", 0);
936
937    doc.add_paragraph("");
938    {
939        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
940        p.add_run("Walter White")
941            .bold(true)
942            .size(14.0)
943            .color("1B2A4A");
944    }
945    {
946        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
947        p.add_run("CEO, Tensorbee | walter@tensorbee.com")
948            .size(10.0)
949            .color("888888");
950    }
951
952    doc
953}
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_samples.rs (line 40)
34fn generate_feature_showcase(path: &Path) {
35    let mut doc = Document::new();
36
37    // =========================================================================
38    // PAGE SETUP & METADATA
39    // =========================================================================
40    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
41    doc.set_margins(
42        Length::inches(1.0), // top
43        Length::inches(1.0), // right
44        Length::inches(1.0), // bottom
45        Length::inches(1.0), // left
46    );
47    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
48    doc.set_gutter(Length::twips(0));
49
50    doc.set_title("rdocx Feature Showcase");
51    doc.set_author("rdocx Sample Generator");
52    doc.set_subject("Comprehensive feature demonstration");
53    doc.set_keywords("rdocx, docx, rust, sample");
54
55    // Header & Footer
56    doc.set_header("rdocx Feature Showcase");
57    doc.set_footer("Generated by rdocx — Page");
58
59    // Different first page header
60    doc.set_different_first_page(true);
61    doc.set_first_page_header("rdocx");
62    doc.set_first_page_footer("Feature Showcase — Cover Page");
63
64    // =========================================================================
65    // PAGE 1: COVER PAGE — background image, run formatting
66    // =========================================================================
67    let bg_cover = create_sample_png(612, 792, [30, 60, 120]);
68    doc.add_background_image(&bg_cover, "cover_bg.png");
69
70    doc.add_paragraph(""); // spacer
71    doc.add_paragraph(""); // spacer
72    doc.add_paragraph(""); // spacer
73
74    {
75        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
76        p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
77    }
78    {
79        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
80        p.add_run("Feature Showcase")
81            .size(28.0)
82            .color("FFFFFF")
83            .italic(true);
84    }
85
86    doc.add_paragraph(""); // spacer
87
88    {
89        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
90        p.add_run("A comprehensive demonstration of every feature")
91            .size(14.0)
92            .color("CCDDFF");
93    }
94    {
95        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
96        p.add_run("provided by the rdocx Rust crate for DOCX generation.")
97            .size(14.0)
98            .color("CCDDFF");
99    }
100
101    // =========================================================================
102    // PAGE 2: TEXT FORMATTING
103    // =========================================================================
104    doc.add_paragraph("").page_break_before(true);
105
106    doc.add_paragraph("1. Text Formatting").style("Heading1");
107
108    doc.add_paragraph("This section demonstrates paragraph and run-level formatting options.");
109    doc.add_paragraph("");
110
111    // --- Paragraph alignment ---
112    doc.add_paragraph("Paragraph Alignment").style("Heading2");
113
114    doc.add_paragraph("This paragraph is left-aligned (the default).")
115        .alignment(Alignment::Left);
116    doc.add_paragraph("This paragraph is center-aligned.")
117        .alignment(Alignment::Center);
118    doc.add_paragraph("This paragraph is right-aligned.")
119        .alignment(Alignment::Right);
120    doc.add_paragraph(
121        "This paragraph is justified. To demonstrate justified text properly, it needs \
122         to be long enough to span multiple lines so the word spacing adjustment is visible \
123         across the full width of the text area on the page.",
124    )
125    .alignment(Alignment::Justify);
126
127    doc.add_paragraph("");
128
129    // --- Run formatting ---
130    doc.add_paragraph("Run Formatting").style("Heading2");
131
132    {
133        let mut p = doc.add_paragraph("");
134        p.add_run("Bold text").bold(true);
135        p.add_run(" | ");
136        p.add_run("Italic text").italic(true);
137        p.add_run(" | ");
138        p.add_run("Bold + Italic").bold(true).italic(true);
139    }
140    {
141        let mut p = doc.add_paragraph("");
142        p.add_run("Single underline").underline(true);
143        p.add_run(" | ");
144        p.add_run("Strikethrough").strike(true);
145        p.add_run(" | ");
146        p.add_run("Double strikethrough").double_strike(true);
147    }
148    {
149        let mut p = doc.add_paragraph("");
150        p.add_run("Red text").color("FF0000");
151        p.add_run(" | ");
152        p.add_run("Blue text").color("0000FF");
153        p.add_run(" | ");
154        p.add_run("Green text").color("00AA00");
155        p.add_run(" | ");
156        p.add_run("Highlighted").highlight("FFFF00");
157    }
158    {
159        let mut p = doc.add_paragraph("");
160        p.add_run("8pt small").size(8.0);
161        p.add_run(" | ");
162        p.add_run("11pt normal").size(11.0);
163        p.add_run(" | ");
164        p.add_run("16pt large").size(16.0);
165        p.add_run(" | ");
166        p.add_run("24pt extra-large").size(24.0);
167    }
168    {
169        let mut p = doc.add_paragraph("");
170        p.add_run("Arial font").font("Arial");
171        p.add_run(" | ");
172        p.add_run("Times New Roman font").font("Times New Roman");
173        p.add_run(" | ");
174        p.add_run("Courier New font").font("Courier New");
175    }
176    {
177        let mut p = doc.add_paragraph("");
178        p.add_run("Normal");
179        p.add_run(" H").size(11.0);
180        p.add_run("2").subscript();
181        p.add_run("O (subscript)").size(11.0);
182        p.add_run(" | E = mc").size(11.0);
183        p.add_run("2").superscript();
184        p.add_run(" (superscript)").size(11.0);
185    }
186    {
187        let mut p = doc.add_paragraph("");
188        p.add_run("ALL CAPS").all_caps(true);
189        p.add_run(" | ");
190        p.add_run("Small Caps").small_caps(true);
191        p.add_run(" | ");
192        p.add_run("Expanded spacing")
193            .character_spacing(Length::twips(40));
194    }
195
196    doc.add_paragraph("");
197
198    // --- Paragraph formatting ---
199    doc.add_paragraph("Paragraph Formatting").style("Heading2");
200
201    doc.add_paragraph("Paragraph with shading (light green background)")
202        .shading("E2EFDA");
203
204    doc.add_paragraph("Paragraph with bottom border")
205        .border_bottom(BorderStyle::Single, 6, "2E75B6");
206
207    doc.add_paragraph("Paragraph with all borders")
208        .border_all(BorderStyle::Single, 4, "FF0000");
209
210    doc.add_paragraph("Paragraph with 1-inch left indent and hanging indent")
211        .indent_left(Length::inches(1.0))
212        .hanging_indent(Length::inches(0.5));
213
214    doc.add_paragraph("Paragraph with first-line indent of 0.5 inches")
215        .first_line_indent(Length::inches(0.5));
216
217    doc.add_paragraph("Paragraph with extra space before (24pt) and after (12pt)")
218        .space_before(Length::pt(24.0))
219        .space_after(Length::pt(12.0));
220
221    doc.add_paragraph(
222        "Paragraph with double line spacing. This text should have extra vertical \
223         space between lines to demonstrate the line_spacing_multiple setting.",
224    )
225    .line_spacing_multiple(2.0);
226
227    doc.add_paragraph("Paragraph with keep-with-next (won't break from the next paragraph)")
228        .keep_with_next(true);
229    doc.add_paragraph("(This stays with the paragraph above.)");
230
231    // =========================================================================
232    // PAGE 3: LISTS & TAB STOPS
233    // =========================================================================
234    doc.add_paragraph("").page_break_before(true);
235
236    doc.add_paragraph("2. Lists").style("Heading1");
237
238    doc.add_paragraph("Bullet List").style("Heading2");
239
240    doc.add_bullet_list_item("First bullet item", 0);
241    doc.add_bullet_list_item("Second bullet item", 0);
242    doc.add_bullet_list_item("Nested level 1", 1);
243    doc.add_bullet_list_item("Nested level 2", 2);
244    doc.add_bullet_list_item("Back to level 1", 1);
245    doc.add_bullet_list_item("Third bullet item", 0);
246
247    doc.add_paragraph("");
248
249    doc.add_paragraph("Numbered List").style("Heading2");
250
251    doc.add_numbered_list_item("First numbered item", 0);
252    doc.add_numbered_list_item("Second numbered item", 0);
253    doc.add_numbered_list_item("Sub-item A", 1);
254    doc.add_numbered_list_item("Sub-item B", 1);
255    doc.add_numbered_list_item("Third numbered item", 0);
256
257    doc.add_paragraph("");
258
259    // --- Tab stops ---
260    doc.add_paragraph("Tab Stops").style("Heading2");
261
262    doc.add_paragraph("Left\tCenter\tRight\tDecimal")
263        .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
264        .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
265        .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
266        .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
267
268    doc.add_paragraph("Item\t........\tPrice")
269        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
270        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
271        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
272
273    doc.add_paragraph("Widget A\t........\t$19.99")
274        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
275        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
276        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
277
278    doc.add_paragraph("Gadget B\t________\t$249.50")
279        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
280        .add_tab_stop_with_leader(
281            TabAlignment::Right,
282            Length::inches(4.0),
283            TabLeader::Underscore,
284        )
285        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
286
287    // =========================================================================
288    // PAGE 4: TABLES
289    // =========================================================================
290    doc.add_paragraph("").page_break_before(true);
291
292    doc.add_paragraph("3. Tables").style("Heading1");
293
294    // --- Basic table with borders ---
295    doc.add_paragraph("Basic Table with Borders")
296        .style("Heading2");
297
298    {
299        let mut tbl = doc.add_table(4, 3);
300        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
301
302        // Header row
303        for col in 0..3 {
304            tbl.cell(0, col).unwrap().shading("2E75B6");
305        }
306        tbl.cell(0, 0).unwrap().set_text("Name");
307        tbl.cell(0, 1).unwrap().set_text("Role");
308        tbl.cell(0, 2).unwrap().set_text("Location");
309
310        tbl.cell(1, 0).unwrap().set_text("Alice Johnson");
311        tbl.cell(1, 1).unwrap().set_text("Engineering Lead");
312        tbl.cell(1, 2).unwrap().set_text("New York");
313
314        tbl.cell(2, 0).unwrap().set_text("Bob Smith");
315        tbl.cell(2, 1).unwrap().set_text("Product Manager");
316        tbl.cell(2, 2).unwrap().set_text("San Francisco");
317
318        tbl.cell(3, 0).unwrap().set_text("Carol Davis");
319        tbl.cell(3, 1).unwrap().set_text("Designer");
320        tbl.cell(3, 2).unwrap().set_text("London");
321    }
322
323    doc.add_paragraph("");
324
325    // --- Table with cell merging ---
326    doc.add_paragraph("Table with Cell Merging & Vertical Alignment")
327        .style("Heading2");
328
329    {
330        let mut tbl = doc.add_table(4, 4);
331        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
332        tbl = tbl.width_pct(100.0);
333
334        // Header spanning all columns
335        tbl.cell(0, 0).unwrap().set_text("Quarterly Revenue Report");
336        tbl.cell(0, 0).unwrap().shading("1F4E79");
337        tbl.cell(0, 0).unwrap().grid_span(4);
338
339        // Sub-header
340        tbl.cell(1, 0).unwrap().set_text("Region");
341        tbl.cell(1, 0).unwrap().shading("D6E4F0");
342        tbl.cell(1, 1).unwrap().set_text("Q1");
343        tbl.cell(1, 1).unwrap().shading("D6E4F0");
344        tbl.cell(1, 2).unwrap().set_text("Q2");
345        tbl.cell(1, 2).unwrap().shading("D6E4F0");
346        tbl.cell(1, 3).unwrap().set_text("Total");
347        tbl.cell(1, 3).unwrap().shading("D6E4F0");
348
349        // Data
350        tbl.cell(2, 0).unwrap().set_text("North America");
351        tbl.cell(2, 1).unwrap().set_text("$2.4M");
352        tbl.cell(2, 2).unwrap().set_text("$2.7M");
353        tbl.cell(2, 3).unwrap().set_text("$5.1M");
354
355        tbl.cell(3, 0).unwrap().set_text("Europe");
356        tbl.cell(3, 1).unwrap().set_text("$1.8M");
357        tbl.cell(3, 2).unwrap().set_text("$2.0M");
358        tbl.cell(3, 3).unwrap().set_text("$3.8M");
359
360        // Vertical alignment on data cells
361        tbl.cell(2, 3)
362            .unwrap()
363            .vertical_alignment(VerticalAlignment::Center);
364        tbl.cell(3, 3)
365            .unwrap()
366            .vertical_alignment(VerticalAlignment::Bottom);
367    }
368
369    doc.add_paragraph("");
370
371    // --- Table with vertical merge ---
372    doc.add_paragraph("Table with Vertical Merge")
373        .style("Heading2");
374
375    {
376        let mut tbl = doc.add_table(4, 3);
377        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
378
379        tbl.cell(0, 0).unwrap().set_text("Category");
380        tbl.cell(0, 0).unwrap().shading("E2EFDA");
381        tbl.cell(0, 1).unwrap().set_text("Item");
382        tbl.cell(0, 1).unwrap().shading("E2EFDA");
383        tbl.cell(0, 2).unwrap().set_text("Price");
384        tbl.cell(0, 2).unwrap().shading("E2EFDA");
385
386        // "Hardware" spans rows 1-2
387        tbl.cell(1, 0).unwrap().set_text("Hardware");
388        tbl.cell(1, 0).unwrap().v_merge_restart();
389        tbl.cell(1, 1).unwrap().set_text("Laptop");
390        tbl.cell(1, 2).unwrap().set_text("$1,200");
391
392        tbl.cell(2, 0).unwrap().v_merge_continue();
393        tbl.cell(2, 1).unwrap().set_text("Monitor");
394        tbl.cell(2, 2).unwrap().set_text("$450");
395
396        // "Software" on row 3
397        tbl.cell(3, 0).unwrap().set_text("Software");
398        tbl.cell(3, 1).unwrap().set_text("IDE License");
399        tbl.cell(3, 2).unwrap().set_text("$200/yr");
400    }
401
402    doc.add_paragraph("");
403
404    // --- Nested table ---
405    doc.add_paragraph("Nested Table").style("Heading2");
406
407    {
408        let mut tbl = doc.add_table(2, 2);
409        tbl = tbl.borders(BorderStyle::Single, 6, "2E75B6");
410
411        tbl.cell(0, 0).unwrap().set_text("Outer Cell (0,0)");
412        tbl.cell(0, 1).unwrap().set_text("Outer Cell (0,1)");
413        tbl.cell(1, 0).unwrap().set_text("Outer Cell (1,0)");
414
415        // Nested table inside cell (1,1)
416        {
417            let mut cell = tbl.cell(1, 1).unwrap();
418            cell.set_text("Contains nested table:");
419            let mut nested = cell.add_table(2, 2);
420            nested = nested.borders(BorderStyle::Single, 2, "FF6600");
421            nested.cell(0, 0).unwrap().set_text("Inner A");
422            nested.cell(0, 1).unwrap().set_text("Inner B");
423            nested.cell(1, 0).unwrap().set_text("Inner C");
424            nested.cell(1, 1).unwrap().set_text("Inner D");
425        }
426    }
427
428    // =========================================================================
429    // PAGE 5: IMAGES
430    // =========================================================================
431    doc.add_paragraph("").page_break_before(true);
432
433    doc.add_paragraph("4. Images").style("Heading1");
434
435    doc.add_paragraph("Inline Image").style("Heading2");
436
437    doc.add_paragraph("Below is an inline image (200x50 pixels, blue gradient):");
438    let inline_img = create_sample_png(200, 50, [0, 80, 200]);
439    doc.add_picture(
440        &inline_img,
441        "inline_chart.png",
442        Length::inches(3.0),
443        Length::inches(0.75),
444    );
445
446    doc.add_paragraph("");
447
448    doc.add_paragraph("Header Image").style("Heading2");
449
450    // Replace the text-only header with an image header
451    let header_img = create_sample_png(400, 40, [40, 40, 40]);
452    doc.set_header_image(
453        &header_img,
454        "header_logo.png",
455        Length::inches(2.0),
456        Length::inches(0.2),
457    );
458
459    doc.add_paragraph(
460        "The document header has been replaced with an inline image. \
461         Check the header area at the top of this page.",
462    );
463
464    doc.add_paragraph("");
465    doc.add_paragraph(
466        "Note: The cover page uses a full-page background image behind the text, \
467         demonstrated on page 1 via add_background_image().",
468    );
469
470    // =========================================================================
471    // PAGE 6: CONTENT MANIPULATION — placeholder replacement, insertion
472    // =========================================================================
473    doc.add_paragraph("").page_break_before(true);
474
475    doc.add_paragraph("5. Content Manipulation")
476        .style("Heading1");
477
478    // --- Placeholder replacement ---
479    doc.add_paragraph("Placeholder Replacement")
480        .style("Heading2");
481
482    doc.add_paragraph(
483        "Before replacement, this document contained {{customer}} and {{date}} placeholders.",
484    );
485
486    {
487        let mut p = doc.add_paragraph("");
488        p.add_run("Customer: ").bold(true);
489        p.add_run("{{customer}}");
490    }
491    {
492        let mut p = doc.add_paragraph("");
493        p.add_run("Date: ").bold(true);
494        p.add_run("{{date}}");
495    }
496    doc.add_paragraph("Reference: {{ref_number}}");
497
498    // Table with placeholders
499    {
500        let mut tbl = doc.add_table(3, 2);
501        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
502        tbl.cell(0, 0).unwrap().set_text("Field");
503        tbl.cell(0, 0).unwrap().shading("D6E4F0");
504        tbl.cell(0, 1).unwrap().set_text("Value");
505        tbl.cell(0, 1).unwrap().shading("D6E4F0");
506        tbl.cell(1, 0).unwrap().set_text("Project");
507        tbl.cell(1, 1).unwrap().set_text("{{project}}");
508        tbl.cell(2, 0).unwrap().set_text("Status");
509        tbl.cell(2, 1).unwrap().set_text("{{status}}");
510    }
511
512    // Perform replacements
513    let mut replacements = HashMap::new();
514    replacements.insert("{{customer}}", "Acme Corporation");
515    replacements.insert("{{date}}", "February 22, 2026");
516    replacements.insert("{{ref_number}}", "REF-2026-001");
517    replacements.insert("{{project}}", "Infrastructure Upgrade");
518    replacements.insert("{{status}}", "In Progress");
519    let replace_count = doc.replace_all(&replacements);
520
521    doc.add_paragraph("");
522    doc.add_paragraph(&format!(
523        "(Replaced {} placeholders above — in body text and table cells)",
524        replace_count
525    ));
526
527    doc.add_paragraph("");
528
529    // --- Content insertion ---
530    doc.add_paragraph("Content Insertion").style("Heading2");
531
532    doc.add_paragraph("Section A: First section of content.");
533    doc.add_paragraph("Section C: Third section of content.");
534
535    // Insert "Section B" between A and C
536    if let Some(idx) = doc.find_content_index("Section C") {
537        doc.insert_paragraph(
538            idx,
539            "Section B: Inserted between A and C using find_content_index().",
540        );
541    }
542
543    doc.add_paragraph("");
544    doc.add_paragraph(
545        "The paragraph above ('Section B') was inserted at a specific position \
546         using find_content_index() + insert_paragraph().",
547    );
548
549    // =========================================================================
550    // PAGE 7: LANDSCAPE — section break, wide table
551    // =========================================================================
552    doc.add_paragraph("").section_break(SectionBreak::NextPage);
553
554    doc.add_paragraph("6. Mixed Page Orientation")
555        .style("Heading1");
556
557    doc.add_paragraph(
558        "This page is in LANDSCAPE orientation. It was created using a section break \
559         followed by section_landscape(). This is useful for wide tables or charts.",
560    );
561
562    doc.add_paragraph("");
563
564    // Wide table for landscape
565    {
566        let mut tbl = doc.add_table(4, 7);
567        tbl = tbl.borders(BorderStyle::Single, 4, "2E75B6");
568
569        let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
570        for (col, h) in headers.iter().enumerate() {
571            tbl.cell(0, col).unwrap().set_text(h);
572            tbl.cell(0, col).unwrap().shading("2E75B6");
573        }
574
575        let data = [
576            [
577                "North America",
578                "$1.2M",
579                "$1.3M",
580                "$1.4M",
581                "$1.5M",
582                "$1.6M",
583                "$7.0M",
584            ],
585            [
586                "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
587            ],
588            [
589                "Asia Pacific",
590                "$0.5M",
591                "$0.6M",
592                "$0.7M",
593                "$0.7M",
594                "$0.8M",
595                "$3.3M",
596            ],
597        ];
598        for (row_idx, row_data) in data.iter().enumerate() {
599            for (col, val) in row_data.iter().enumerate() {
600                tbl.cell(row_idx + 1, col).unwrap().set_text(val);
601            }
602        }
603    }
604
605    // End landscape, return to portrait
606    doc.add_paragraph("")
607        .section_break(SectionBreak::NextPage)
608        .section_landscape();
609
610    // =========================================================================
611    // PAGE 8: BACK TO PORTRAIT — styles, final notes
612    // =========================================================================
613    doc.add_paragraph("7. Custom Styles & Summary")
614        .style("Heading1");
615
616    doc.add_paragraph(
617        "This final page is back in portrait orientation after a section break. \
618         The document has demonstrated:",
619    );
620
621    doc.add_bullet_list_item(
622        "Page setup: size, margins, header/footer distance, gutter",
623        0,
624    );
625    doc.add_bullet_list_item("Document metadata: title, author, subject, keywords", 0);
626    doc.add_bullet_list_item("Headers and footers: text, images, different first page", 0);
627    doc.add_bullet_list_item("Background images: full-page behind text", 0);
628    doc.add_bullet_list_item(
629        "Text formatting: bold, italic, underline, strike, color, size, font",
630        0,
631    );
632    doc.add_bullet_list_item(
633        "Advanced run formatting: superscript, subscript, caps, spacing",
634        0,
635    );
636    doc.add_bullet_list_item(
637        "Paragraph formatting: alignment, borders, shading, spacing, indentation",
638        0,
639    );
640    doc.add_bullet_list_item("Bullet and numbered lists with nesting levels", 0);
641    doc.add_bullet_list_item("Tab stops with dot/underscore leaders", 0);
642    doc.add_bullet_list_item(
643        "Tables: borders, shading, column spans, row spans, nesting",
644        0,
645    );
646    doc.add_bullet_list_item("Vertical alignment in table cells", 0);
647    doc.add_bullet_list_item("Inline images", 0);
648    doc.add_bullet_list_item("Placeholder replacement in body and table cells", 0);
649    doc.add_bullet_list_item("Content insertion at specific positions", 0);
650    doc.add_bullet_list_item(
651        "Section breaks with mixed portrait/landscape orientation",
652        0,
653    );
654
655    doc.add_paragraph("");
656    doc.add_paragraph("All features above were built entirely from scratch using the rdocx API.")
657        .alignment(Alignment::Center)
658        .shading("E2EFDA")
659        .border_all(BorderStyle::Single, 2, "00AA00");
660
661    doc.save(path).unwrap();
662}
examples/generate_all_samples.rs (line 90)
86fn generate_feature_showcase(_samples_dir: &Path) -> Document {
87    let mut doc = Document::new();
88
89    // ── Page Setup & Metadata ──
90    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
91    doc.set_margins(
92        Length::inches(1.0),
93        Length::inches(1.0),
94        Length::inches(1.0),
95        Length::inches(1.0),
96    );
97    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
98    doc.set_gutter(Length::twips(0));
99    doc.set_title("rdocx Feature Showcase");
100    doc.set_author("rdocx Sample Generator");
101    doc.set_subject("Comprehensive feature demonstration");
102    doc.set_keywords("rdocx, docx, rust, sample, showcase");
103
104    // Headers & Footers with different first page
105    doc.set_different_first_page(true);
106    doc.set_first_page_header("rdocx");
107    doc.set_first_page_footer("Feature Showcase — Cover Page");
108    doc.set_header("rdocx Feature Showcase");
109    doc.set_footer("Generated by rdocx");
110
111    // ── COVER PAGE ──
112    let bg = create_sample_png(612, 792, [20, 45, 90]);
113    doc.add_background_image(&bg, "cover_bg.png");
114
115    for _ in 0..3 {
116        doc.add_paragraph("");
117    }
118    {
119        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
120        p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
121    }
122    {
123        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
124        p.add_run("Complete Feature Showcase")
125            .size(28.0)
126            .color("FFFFFF")
127            .italic(true);
128    }
129    doc.add_paragraph("");
130    {
131        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
132        p.add_run("Every feature of the rdocx Rust library")
133            .size(13.0)
134            .color("B0C4DE");
135    }
136    {
137        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
138        p.add_run("demonstrated in a single document.")
139            .size(13.0)
140            .color("B0C4DE");
141    }
142
143    // ── TABLE OF CONTENTS ──
144    doc.add_paragraph("").page_break_before(true);
145    doc.insert_toc(doc.content_count(), 3);
146
147    // ── SECTION 1: TEXT FORMATTING ──
148    doc.add_paragraph("").page_break_before(true);
149    doc.add_paragraph("1. Text Formatting").style("Heading1");
150
151    // Paragraph Alignment
152    doc.add_paragraph("1.1 Paragraph Alignment")
153        .style("Heading2");
154    doc.add_paragraph("Left-aligned paragraph (default).")
155        .alignment(Alignment::Left);
156    doc.add_paragraph("Center-aligned paragraph.")
157        .alignment(Alignment::Center);
158    doc.add_paragraph("Right-aligned paragraph.")
159        .alignment(Alignment::Right);
160    doc.add_paragraph(
161        "Justified paragraph — this text is long enough to demonstrate how justified alignment \
162         distributes extra space across word gaps so lines fill the full width of the text area.",
163    )
164    .alignment(Alignment::Justify);
165
166    doc.add_paragraph("");
167
168    // Run Formatting
169    doc.add_paragraph("1.2 Run Formatting").style("Heading2");
170    {
171        let mut p = doc.add_paragraph("");
172        p.add_run("Bold").bold(true);
173        p.add_run(" | ");
174        p.add_run("Italic").italic(true);
175        p.add_run(" | ");
176        p.add_run("Bold Italic").bold(true).italic(true);
177        p.add_run(" | ");
178        p.add_run("Underline").underline(true);
179        p.add_run(" | ");
180        p.add_run("Strikethrough").strike(true);
181        p.add_run(" | ");
182        p.add_run("Double Strike").double_strike(true);
183    }
184    {
185        let mut p = doc.add_paragraph("");
186        p.add_run("Red").color("FF0000");
187        p.add_run(" | ");
188        p.add_run("Blue").color("0000FF");
189        p.add_run(" | ");
190        p.add_run("Green").color("00AA00");
191        p.add_run(" | ");
192        p.add_run("Highlighted").highlight("FFFF00");
193    }
194    {
195        let mut p = doc.add_paragraph("");
196        p.add_run("8pt").size(8.0);
197        p.add_run(" | ");
198        p.add_run("11pt").size(11.0);
199        p.add_run(" | ");
200        p.add_run("16pt").size(16.0);
201        p.add_run(" | ");
202        p.add_run("24pt").size(24.0);
203    }
204    {
205        let mut p = doc.add_paragraph("");
206        p.add_run("Arial").font("Arial");
207        p.add_run(" | ");
208        p.add_run("Times New Roman").font("Times New Roman");
209        p.add_run(" | ");
210        p.add_run("Courier New").font("Courier New");
211    }
212    {
213        let mut p = doc.add_paragraph("");
214        p.add_run("H");
215        p.add_run("2").subscript();
216        p.add_run("O (subscript) | E=mc");
217        p.add_run("2").superscript();
218        p.add_run(" (superscript)");
219    }
220    {
221        let mut p = doc.add_paragraph("");
222        p.add_run("ALL CAPS").all_caps(true);
223        p.add_run(" | ");
224        p.add_run("Small Caps").small_caps(true);
225        p.add_run(" | ");
226        p.add_run("Expanded").character_spacing(Length::twips(40));
227        p.add_run(" | ");
228        p.add_run("Hidden text (not visible)").hidden(true);
229    }
230
231    doc.add_paragraph("");
232
233    // Underline Styles
234    doc.add_paragraph("1.3 Underline Styles").style("Heading2");
235    {
236        let mut p = doc.add_paragraph("");
237        p.add_run("Single ")
238            .underline_style(rdocx::UnderlineStyle::Single);
239        p.add_run("Double ")
240            .underline_style(rdocx::UnderlineStyle::Double);
241        p.add_run("Thick ")
242            .underline_style(rdocx::UnderlineStyle::Thick);
243        p.add_run("Dotted ")
244            .underline_style(rdocx::UnderlineStyle::Dotted);
245        p.add_run("Dash ")
246            .underline_style(rdocx::UnderlineStyle::Dash);
247        p.add_run("Wave ")
248            .underline_style(rdocx::UnderlineStyle::Wave);
249    }
250
251    // ── SECTION 2: PARAGRAPH FORMATTING ──
252    doc.add_paragraph("").page_break_before(true);
253    doc.add_paragraph("2. Paragraph Formatting")
254        .style("Heading1");
255
256    doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
257    doc.add_paragraph("Paragraph with light green shading")
258        .shading("E2EFDA");
259    doc.add_paragraph("Paragraph with bottom border")
260        .border_bottom(BorderStyle::Single, 6, "2E75B6");
261    doc.add_paragraph("Paragraph with all borders (red)")
262        .border_all(BorderStyle::Single, 4, "FF0000");
263
264    doc.add_paragraph("2.2 Indentation").style("Heading2");
265    doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
266        .indent_left(Length::inches(1.0))
267        .hanging_indent(Length::inches(0.5));
268    doc.add_paragraph("0.5-inch first-line indent on this paragraph")
269        .first_line_indent(Length::inches(0.5));
270    doc.add_paragraph("Both left and right indent (0.75 inches each)")
271        .indent_left(Length::inches(0.75))
272        .indent_right(Length::inches(0.75));
273
274    doc.add_paragraph("2.3 Spacing & Line Height")
275        .style("Heading2");
276    doc.add_paragraph("Extra space before (24pt) and after (12pt)")
277        .space_before(Length::pt(24.0))
278        .space_after(Length::pt(12.0));
279    doc.add_paragraph(
280        "Double line spacing paragraph. This text should have extra vertical space between \
281         lines to demonstrate line_spacing_multiple(2.0).",
282    )
283    .line_spacing_multiple(2.0);
284    doc.add_paragraph("Exact 20pt line spacing (fixed height).")
285        .line_spacing(20.0);
286
287    doc.add_paragraph("2.4 Pagination Controls")
288        .style("Heading2");
289    doc.add_paragraph("keep_with_next — this paragraph stays with the next")
290        .keep_with_next(true);
291    doc.add_paragraph("(This paragraph was kept with the one above.)");
292    doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
293        .keep_together(true);
294    doc.add_paragraph("widow_control — prevents widow/orphan lines")
295        .widow_control(true);
296
297    // ── SECTION 3: LISTS ──
298    doc.add_paragraph("").page_break_before(true);
299    doc.add_paragraph("3. Lists").style("Heading1");
300
301    doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
302    doc.add_bullet_list_item("First item", 0);
303    doc.add_bullet_list_item("Second item", 0);
304    doc.add_bullet_list_item("Nested level 1", 1);
305    doc.add_bullet_list_item("Nested level 2", 2);
306    doc.add_bullet_list_item("Back to level 1", 1);
307    doc.add_bullet_list_item("Third item", 0);
308
309    doc.add_paragraph("");
310
311    doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
312    doc.add_numbered_list_item("First numbered item", 0);
313    doc.add_numbered_list_item("Second numbered item", 0);
314    doc.add_numbered_list_item("Sub-item A", 1);
315    doc.add_numbered_list_item("Sub-item B", 1);
316    doc.add_numbered_list_item("Third numbered item", 0);
317
318    // ── SECTION 4: TAB STOPS ──
319    doc.add_paragraph("");
320    doc.add_paragraph("4. Tab Stops").style("Heading1");
321
322    doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
323    doc.add_paragraph("Left\tCenter\tRight\tDecimal")
324        .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
325        .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
326        .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
327        .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
328
329    doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
330    doc.add_paragraph("Item\t\tPrice")
331        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
332        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
333        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
334    doc.add_paragraph("Widget\t\t$19.99")
335        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
336        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
337        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
338    doc.add_paragraph("Gadget\t\t$249.50")
339        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
340        .add_tab_stop_with_leader(
341            TabAlignment::Right,
342            Length::inches(4.0),
343            TabLeader::Underscore,
344        )
345        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
346
347    // ── SECTION 5: TABLES ──
348    doc.add_paragraph("").page_break_before(true);
349    doc.add_paragraph("5. Tables").style("Heading1");
350
351    doc.add_paragraph("5.1 Basic Table").style("Heading2");
352    {
353        let mut tbl = doc
354            .add_table(4, 3)
355            .borders(BorderStyle::Single, 4, "000000");
356        for col in 0..3 {
357            tbl.cell(0, col).unwrap().shading("2E75B6");
358        }
359        tbl.cell(0, 0).unwrap().set_text("Name");
360        tbl.cell(0, 1).unwrap().set_text("Role");
361        tbl.cell(0, 2).unwrap().set_text("Location");
362        tbl.cell(1, 0).unwrap().set_text("Walter White");
363        tbl.cell(1, 1).unwrap().set_text("CEO");
364        tbl.cell(1, 2).unwrap().set_text("Albuquerque");
365        tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
366        tbl.cell(2, 1).unwrap().set_text("CTO");
367        tbl.cell(2, 2).unwrap().set_text("Remote");
368        tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
369        tbl.cell(3, 1).unwrap().set_text("Security");
370        tbl.cell(3, 2).unwrap().set_text("Washington");
371    }
372
373    doc.add_paragraph("");
374
375    doc.add_paragraph("5.2 Column Span & Cell Shading")
376        .style("Heading2");
377    {
378        let mut tbl = doc
379            .add_table(3, 4)
380            .borders(BorderStyle::Single, 4, "000000")
381            .width_pct(100.0);
382        tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
383        tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
384        tbl.cell(1, 0).unwrap().set_text("Region");
385        tbl.cell(1, 0).unwrap().shading("D6E4F0");
386        tbl.cell(1, 1).unwrap().set_text("Q1");
387        tbl.cell(1, 1).unwrap().shading("D6E4F0");
388        tbl.cell(1, 2).unwrap().set_text("Q2");
389        tbl.cell(1, 2).unwrap().shading("D6E4F0");
390        tbl.cell(1, 3).unwrap().set_text("Total");
391        tbl.cell(1, 3).unwrap().shading("D6E4F0");
392        tbl.cell(2, 0).unwrap().set_text("Americas");
393        tbl.cell(2, 1).unwrap().set_text("$2.4M");
394        tbl.cell(2, 2).unwrap().set_text("$2.7M");
395        tbl.cell(2, 3).unwrap().set_text("$5.1M");
396    }
397
398    doc.add_paragraph("");
399
400    doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
401    {
402        let mut tbl = doc
403            .add_table(4, 3)
404            .borders(BorderStyle::Single, 4, "000000");
405        tbl.cell(0, 0).unwrap().set_text("Category");
406        tbl.cell(0, 0).unwrap().shading("E2EFDA");
407        tbl.cell(0, 1).unwrap().set_text("Item");
408        tbl.cell(0, 1).unwrap().shading("E2EFDA");
409        tbl.cell(0, 2).unwrap().set_text("Price");
410        tbl.cell(0, 2).unwrap().shading("E2EFDA");
411        tbl.cell(1, 0).unwrap().set_text("Hardware");
412        tbl.cell(1, 0).unwrap().v_merge_restart();
413        tbl.cell(1, 1).unwrap().set_text("Laptop");
414        tbl.cell(1, 2).unwrap().set_text("$1,200");
415        tbl.cell(2, 0).unwrap().v_merge_continue();
416        tbl.cell(2, 1).unwrap().set_text("Monitor");
417        tbl.cell(2, 2).unwrap().set_text("$450");
418        tbl.cell(3, 0).unwrap().set_text("Software");
419        tbl.cell(3, 1).unwrap().set_text("IDE License");
420        tbl.cell(3, 2).unwrap().set_text("$200/yr");
421    }
422
423    doc.add_paragraph("");
424
425    doc.add_paragraph("5.4 Nested Table").style("Heading2");
426    {
427        let mut tbl = doc
428            .add_table(2, 2)
429            .borders(BorderStyle::Single, 6, "2E75B6");
430        tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
431        tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
432        tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
433        {
434            let mut cell = tbl.cell(1, 1).unwrap();
435            cell.set_text("Nested table below:");
436            let mut nested = cell
437                .add_table(2, 2)
438                .borders(BorderStyle::Single, 2, "FF6600");
439            nested.cell(0, 0).unwrap().set_text("A");
440            nested.cell(0, 1).unwrap().set_text("B");
441            nested.cell(1, 0).unwrap().set_text("C");
442            nested.cell(1, 1).unwrap().set_text("D");
443        }
444    }
445
446    doc.add_paragraph("");
447
448    doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
449        .style("Heading2");
450    {
451        let mut tbl = doc
452            .add_table(2, 3)
453            .borders(BorderStyle::Single, 4, "666666");
454        tbl.row(0).unwrap().height(Length::pt(50.0)).header();
455        tbl.cell(0, 0).unwrap().set_text("Top");
456        tbl.cell(0, 0)
457            .unwrap()
458            .vertical_alignment(VerticalAlignment::Top)
459            .shading("FFF2CC");
460        tbl.cell(0, 1).unwrap().set_text("Center");
461        tbl.cell(0, 1)
462            .unwrap()
463            .vertical_alignment(VerticalAlignment::Center)
464            .shading("D9E2F3");
465        tbl.cell(0, 2).unwrap().set_text("Bottom");
466        tbl.cell(0, 2)
467            .unwrap()
468            .vertical_alignment(VerticalAlignment::Bottom)
469            .shading("E2EFDA");
470        tbl.row(1).unwrap().cant_split();
471        tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
472        tbl.cell(1, 0).unwrap().no_wrap();
473        tbl.cell(1, 1).unwrap().set_text("Fixed width");
474        tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
475        tbl.cell(1, 2).unwrap().set_text("Normal");
476    }
477
478    // ── SECTION 6: IMAGES ──
479    doc.add_paragraph("").page_break_before(true);
480    doc.add_paragraph("6. Images").style("Heading1");
481
482    doc.add_paragraph("6.1 Inline Image").style("Heading2");
483    doc.add_paragraph("A blue gradient image below:");
484    let img = create_sample_png(200, 50, [0, 80, 200]);
485    doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
486
487    doc.add_paragraph("");
488    doc.add_paragraph("6.2 Header Image").style("Heading2");
489    let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
490    doc.set_header_image(
491        &hdr_img,
492        "header_logo.png",
493        Length::inches(2.0),
494        Length::inches(0.2),
495    );
496    doc.add_paragraph("The header now contains an inline image (check the top of this page).");
497
498    doc.add_paragraph("");
499    doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
500
501    // ── SECTION 7: CONTENT MANIPULATION ──
502    doc.add_paragraph("").page_break_before(true);
503    doc.add_paragraph("7. Content Manipulation")
504        .style("Heading1");
505
506    doc.add_paragraph("7.1 Placeholder Replacement")
507        .style("Heading2");
508    doc.add_paragraph("Customer: {{customer}}");
509    doc.add_paragraph("Date: {{date}}");
510    doc.add_paragraph("Reference: {{ref_number}}");
511    {
512        let mut tbl = doc
513            .add_table(2, 2)
514            .borders(BorderStyle::Single, 4, "000000");
515        tbl.cell(0, 0).unwrap().set_text("Project");
516        tbl.cell(0, 0).unwrap().shading("D6E4F0");
517        tbl.cell(0, 1).unwrap().set_text("{{project}}");
518        tbl.cell(1, 0).unwrap().set_text("Status");
519        tbl.cell(1, 0).unwrap().shading("D6E4F0");
520        tbl.cell(1, 1).unwrap().set_text("{{status}}");
521    }
522
523    let mut replacements = HashMap::new();
524    replacements.insert("{{customer}}", "Tensorbee Inc.");
525    replacements.insert("{{date}}", "February 22, 2026");
526    replacements.insert("{{ref_number}}", "TB-2026-001");
527    replacements.insert("{{project}}", "Infrastructure Upgrade");
528    replacements.insert("{{status}}", "In Progress");
529    let n = doc.replace_all(&replacements);
530    doc.add_paragraph(&format!("({n} placeholders replaced above)"));
531
532    doc.add_paragraph("");
533
534    doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
535    doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
536    let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
537    doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
538
539    doc.add_paragraph("");
540
541    doc.add_paragraph("7.3 Content Insertion").style("Heading2");
542    doc.add_paragraph("Section A: First content.");
543    doc.add_paragraph("Section C: Third content.");
544    if let Some(idx) = doc.find_content_index("Section C") {
545        doc.insert_paragraph(
546            idx,
547            "Section B: Inserted between A and C via find_content_index().",
548        );
549    }
550
551    // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
552    doc.add_paragraph("").section_break(SectionBreak::NextPage);
553    doc.add_paragraph("8. Section Breaks & Orientation")
554        .style("Heading1");
555    doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
556
557    {
558        let mut tbl = doc
559            .add_table(3, 7)
560            .borders(BorderStyle::Single, 4, "2E75B6");
561        let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
562        for (c, h) in headers.iter().enumerate() {
563            tbl.cell(0, c).unwrap().set_text(h);
564            tbl.cell(0, c).unwrap().shading("2E75B6");
565        }
566        let data = [
567            [
568                "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
569            ],
570            [
571                "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
572            ],
573        ];
574        for (r, row) in data.iter().enumerate() {
575            for (c, v) in row.iter().enumerate() {
576                tbl.cell(r + 1, c).unwrap().set_text(v);
577            }
578        }
579    }
580
581    doc.add_paragraph("")
582        .section_break(SectionBreak::NextPage)
583        .section_landscape();
584
585    // ── SECTION 9: CUSTOM STYLES ──
586    doc.add_paragraph("9. Custom Styles").style("Heading1");
587    doc.add_style(
588        StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
589            .based_on("Normal")
590            .paragraph_properties({
591                let mut ppr = rdocx_oxml::properties::CT_PPr::default();
592                ppr.shading = Some(rdocx_oxml::properties::CT_Shd {
593                    val: "clear".to_string(),
594                    color: None,
595                    fill: Some("FFF2CC".to_string()),
596                });
597                ppr
598            })
599            .run_properties({
600                let mut rpr = rdocx_oxml::properties::CT_RPr::default();
601                rpr.bold = Some(true);
602                rpr.color = Some("C45911".to_string());
603                rpr
604            }),
605    );
606    doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
607        .style("CustomHighlight");
608
609    doc.add_paragraph("");
610
611    // ── SECTION 10: DOCUMENT INTELLIGENCE ──
612    doc.add_paragraph("10. Document Intelligence API")
613        .style("Heading1");
614
615    let wc = doc.word_count();
616    let hc = doc.headings().len();
617    let ic = doc.images().len();
618    let lc = doc.links().len();
619    doc.add_paragraph(&format!("Word count: {wc}"));
620    doc.add_paragraph(&format!("Heading count: {hc}"));
621    doc.add_paragraph(&format!("Image count: {ic}"));
622    doc.add_paragraph(&format!("Link count: {lc}"));
623
624    let outline = doc.document_outline();
625    doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
626
627    let issues = doc.audit_accessibility();
628    doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
629    for issue in &issues {
630        doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
631    }
632
633    // ── SECTION 11: DOCUMENT MERGING ──
634    doc.add_paragraph("");
635    doc.add_paragraph("11. Document Merging").style("Heading1");
636    {
637        let mut other = Document::new();
638        other.add_paragraph("This paragraph was merged from another document using append().");
639        doc.append(&other);
640    }
641
642    doc.add_paragraph("");
643
644    // ── SUMMARY ──
645    doc.add_paragraph("Summary of Demonstrated Features")
646        .style("Heading1");
647    let features = [
648        "Page setup: size, margins, header/footer distance, gutter",
649        "Document metadata: title, author, subject, keywords",
650        "Headers and footers: text, images, different first page",
651        "Background images (full-page behind text)",
652        "Table of Contents generation with bookmarks",
653        "Text formatting: bold, italic, underline styles, strike, color, size, font",
654        "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
655        "Paragraph formatting: alignment, borders, shading, spacing, indentation",
656        "Pagination controls: keep-with-next, keep-together, widow control",
657        "Bullet and numbered lists with nesting",
658        "Tab stops with dot/underscore/hyphen leaders",
659        "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
660        "Row properties: header rows, exact height, cant-split",
661        "Cell properties: width, no-wrap, vertical alignment",
662        "Inline images and header images",
663        "Placeholder replacement (single and batch)",
664        "Regex-based find and replace",
665        "Content insertion at specific positions",
666        "Section breaks with mixed portrait/landscape",
667        "Custom paragraph and character styles",
668        "Document intelligence: word count, headings, outline, images, links",
669        "Accessibility audit",
670        "Document merging (append)",
671        "Export: DOCX, PDF, HTML, Markdown",
672    ];
673    for f in &features {
674        doc.add_bullet_list_item(f, 0);
675    }
676    doc.add_paragraph("");
677    doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
678        .alignment(Alignment::Center)
679        .shading("E2EFDA")
680        .border_all(BorderStyle::Single, 2, "00AA00");
681
682    doc
683}
684
685// =============================================================================
686// 2. PROPOSAL DOCUMENT — Deep navy + gold scheme
687// =============================================================================
688fn generate_proposal(_samples_dir: &Path) -> Document {
689    let mut doc = Document::new();
690
691    // Colors: Navy #1B2A4A, Gold #C5922E, Light #F4F1EB
692    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
693    doc.set_margins(
694        Length::inches(1.0),
695        Length::inches(1.0),
696        Length::inches(1.0),
697        Length::inches(1.0),
698    );
699    doc.set_title("AI Platform Modernization Proposal");
700    doc.set_author("Walter White");
701    doc.set_subject("Technology Proposal");
702    doc.set_keywords("proposal, AI, modernization, Tensorbee");
703
704    // Banner header
705    let logo_img = create_logo_png(220, 48);
706    let banner = build_header_banner_xml(
707        "rId1",
708        &BannerOpts {
709            bg_color: "1B2A4A",
710            banner_width: 7772400,
711            banner_height: 914400,
712            logo_width: 2011680,
713            logo_height: 438912,
714            logo_x_offset: 295125,
715            logo_y_offset: 237744,
716        },
717    );
718    doc.set_raw_header_with_images(
719        banner,
720        &[("rId1", &logo_img, "logo.png")],
721        rdocx_oxml::header_footer::HdrFtrType::Default,
722    );
723    doc.set_different_first_page(true);
724    let cover_banner = build_header_banner_xml(
725        "rId1",
726        &BannerOpts {
727            bg_color: "C5922E",
728            banner_width: 7772400,
729            banner_height: 914400,
730            logo_width: 2011680,
731            logo_height: 438912,
732            logo_x_offset: 295125,
733            logo_y_offset: 237744,
734        },
735    );
736    doc.set_raw_header_with_images(
737        cover_banner,
738        &[("rId1", &logo_img, "logo.png")],
739        rdocx_oxml::header_footer::HdrFtrType::First,
740    );
741    doc.set_margins(
742        Length::twips(2292),
743        Length::twips(1440),
744        Length::twips(1440),
745        Length::twips(1440),
746    );
747    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
748    doc.set_footer("Tensorbee — Confidential");
749
750    // Custom styles
751    doc.add_style(
752        StyleBuilder::paragraph("ProposalTitle", "Proposal Title")
753            .based_on("Normal")
754            .run_properties({
755                let mut rpr = rdocx_oxml::properties::CT_RPr::default();
756                rpr.bold = Some(true);
757                rpr.font_ascii = Some("Georgia".to_string());
758                rpr.font_hansi = Some("Georgia".to_string());
759                rpr.sz = Some(rdocx_oxml::HalfPoint(56)); // half-points → 28pt
760                rpr.color = Some("1B2A4A".to_string());
761                rpr
762            }),
763    );
764
765    // ── Cover Page ──
766    for _ in 0..4 {
767        doc.add_paragraph("");
768    }
769    doc.add_paragraph("AI Platform Modernization")
770        .style("ProposalTitle")
771        .alignment(Alignment::Center);
772    doc.add_paragraph("")
773        .alignment(Alignment::Center)
774        .border_bottom(BorderStyle::Single, 8, "C5922E");
775    {
776        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
777        p.add_run("Prepared for Global Financial Corp.")
778            .size(14.0)
779            .color("666666")
780            .italic(true);
781    }
782    {
783        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
784        p.add_run("Prepared by: Walter White, CEO — Tensorbee")
785            .size(12.0)
786            .color("888888");
787    }
788    {
789        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
790        p.add_run("February 22, 2026").size(12.0).color("888888");
791    }
792
793    // ── TOC ──
794    doc.add_paragraph("").page_break_before(true);
795    doc.add_paragraph("Table of Contents").style("Heading1");
796    // We'll add a manual-style TOC since the insert_toc goes at a specific position
797    doc.insert_toc(doc.content_count(), 2);
798
799    // ── Executive Summary ──
800    doc.add_paragraph("").page_break_before(true);
801    doc.add_paragraph("Executive Summary").style("Heading1");
802    doc.add_paragraph(
803        "Tensorbee proposes a comprehensive modernization of Global Financial Corp's AI platform, \
804         transitioning from legacy batch-processing systems to a real-time inference architecture. \
805         This transformation will reduce model deployment time from weeks to hours, improve \
806         prediction accuracy by an estimated 23%, and deliver $4.2M in annual operational savings.",
807    )
808    .first_line_indent(Length::inches(0.3));
809    doc.add_paragraph(
810        "The project spans three phases over 18 months, with a total investment of $2.8M. \
811         Tensorbee brings deep expertise in ML infrastructure, having successfully completed \
812         similar transformations for three Fortune 500 financial institutions.",
813    )
814    .first_line_indent(Length::inches(0.3));
815
816    // Key metrics table
817    doc.add_paragraph("");
818    {
819        let mut tbl = doc
820            .add_table(5, 2)
821            .borders(BorderStyle::Single, 4, "1B2A4A")
822            .width_pct(80.0);
823        tbl.cell(0, 0).unwrap().set_text("Key Metric");
824        tbl.cell(0, 0).unwrap().shading("1B2A4A");
825        tbl.cell(0, 1).unwrap().set_text("Value");
826        tbl.cell(0, 1).unwrap().shading("1B2A4A");
827        let rows = [
828            ("Total Investment", "$2.8M"),
829            ("Annual Savings", "$4.2M"),
830            ("ROI (Year 1)", "150%"),
831            ("Timeline", "18 months"),
832        ];
833        for (i, (k, v)) in rows.iter().enumerate() {
834            tbl.cell(i + 1, 0).unwrap().set_text(k);
835            if i % 2 == 0 {
836                tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
837            }
838            tbl.cell(i + 1, 1).unwrap().set_text(v);
839            if i % 2 == 0 {
840                tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
841            }
842        }
843    }
844
845    // ── Problem Statement ──
846    doc.add_paragraph("").page_break_before(true);
847    doc.add_paragraph("Problem Statement").style("Heading1");
848    doc.add_paragraph(
849        "Global Financial Corp's current AI infrastructure faces three critical challenges:",
850    );
851    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);
852    doc.add_numbered_list_item("Scalability Constraints — The batch processing architecture cannot handle real-time inference demands during peak trading hours.", 0);
853    doc.add_numbered_list_item("Technical Debt — Legacy Python 2.7 codebases and manual deployment scripts create reliability risks and slow iteration speed.", 0);
854
855    // ── Proposed Solution ──
856    doc.add_paragraph("").page_break_before(true);
857    doc.add_paragraph("Proposed Solution").style("Heading1");
858
859    doc.add_paragraph("Phase 1: Foundation (Months 1-6)")
860        .style("Heading2");
861    doc.add_bullet_list_item("Deploy Kubernetes-based ML serving infrastructure", 0);
862    doc.add_bullet_list_item("Implement CI/CD pipeline for model deployment", 0);
863    doc.add_bullet_list_item("Migrate top 5 production models to new platform", 0);
864
865    doc.add_paragraph("Phase 2: Expansion (Months 7-12)")
866        .style("Heading2");
867    doc.add_bullet_list_item(
868        "Real-time feature engineering pipeline (Apache Kafka + Flink)",
869        0,
870    );
871    doc.add_bullet_list_item("A/B testing framework for model variants", 0);
872    doc.add_bullet_list_item("Automated model monitoring and drift detection", 0);
873
874    doc.add_paragraph("Phase 3: Optimization (Months 13-18)")
875        .style("Heading2");
876    doc.add_bullet_list_item("GPU inference optimization (TensorRT/ONNX Runtime)", 0);
877    doc.add_bullet_list_item("Multi-region deployment for latency reduction", 0);
878    doc.add_bullet_list_item(
879        "Self-service model deployment portal for data science teams",
880        0,
881    );
882
883    // ── Budget ──
884    doc.add_paragraph("Budget Breakdown").style("Heading1");
885    {
886        let mut tbl = doc
887            .add_table(6, 3)
888            .borders(BorderStyle::Single, 4, "1B2A4A")
889            .width_pct(100.0);
890        tbl.cell(0, 0).unwrap().set_text("Category");
891        tbl.cell(0, 0).unwrap().shading("1B2A4A");
892        tbl.cell(0, 1).unwrap().set_text("Cost");
893        tbl.cell(0, 1).unwrap().shading("1B2A4A");
894        tbl.cell(0, 2).unwrap().set_text("Notes");
895        tbl.cell(0, 2).unwrap().shading("1B2A4A");
896        let items = [
897            (
898                "Infrastructure",
899                "$450,000",
900                "Cloud compute + GPU instances",
901            ),
902            ("Engineering Services", "$1,600,000", "12 FTE x 18 months"),
903            (
904                "Software Licenses",
905                "$350,000",
906                "Kafka, monitoring, CI/CD tools",
907            ),
908            (
909                "Training & Enablement",
910                "$200,000",
911                "Team training, documentation",
912            ),
913            ("Contingency (10%)", "$200,000", "Risk buffer"),
914        ];
915        for (i, (cat, cost, note)) in items.iter().enumerate() {
916            tbl.cell(i + 1, 0).unwrap().set_text(cat);
917            tbl.cell(i + 1, 1).unwrap().set_text(cost);
918            tbl.cell(i + 1, 2).unwrap().set_text(note);
919            if i % 2 == 0 {
920                tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
921                tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
922                tbl.cell(i + 1, 2).unwrap().shading("F4F1EB");
923            }
924        }
925    }
926
927    // ── Closing ──
928    doc.add_paragraph("");
929    doc.add_paragraph("Next Steps").style("Heading1");
930    doc.add_numbered_list_item(
931        "Schedule technical deep-dive with GFC engineering team (Week 1)",
932        0,
933    );
934    doc.add_numbered_list_item("Finalize scope and sign SOW (Week 2-3)", 0);
935    doc.add_numbered_list_item("Kick off Phase 1 with joint planning session (Week 4)", 0);
936
937    doc.add_paragraph("");
938    {
939        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
940        p.add_run("Walter White")
941            .bold(true)
942            .size(14.0)
943            .color("1B2A4A");
944    }
945    {
946        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
947        p.add_run("CEO, Tensorbee | walter@tensorbee.com")
948            .size(10.0)
949            .color("888888");
950    }
951
952    doc
953}
954
955// =============================================================================
956// 3. QUOTE / BILL OF MATERIALS — Teal + orange scheme
957// =============================================================================
958fn generate_quote(_samples_dir: &Path) -> Document {
959    let mut doc = Document::new();
960
961    // Colors: Teal #008B8B, Dark #1A3C3C, Orange #E07020, Light #F0F8F8
962    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
963    doc.set_margins(
964        Length::inches(0.75),
965        Length::inches(0.75),
966        Length::inches(0.75),
967        Length::inches(0.75),
968    );
969    doc.set_title("Quotation QT-2026-0147");
970    doc.set_author("Walter White");
971    doc.set_keywords("quote, BOM, Tensorbee");
972
973    doc.set_header("Tensorbee — Quotation");
974    doc.set_footer("QT-2026-0147 | Page");
975
976    // ── Header Block ──
977    {
978        let mut p = doc.add_paragraph("").alignment(Alignment::Left);
979        p.add_run("TENSORBEE")
980            .bold(true)
981            .size(28.0)
982            .color("008B8B")
983            .font("Helvetica");
984    }
985    doc.add_paragraph("123 Innovation Drive, Suite 400")
986        .alignment(Alignment::Left);
987    doc.add_paragraph("San Francisco, CA 94105 | +1 (415) 555-0199")
988        .alignment(Alignment::Left);
989    doc.add_paragraph("")
990        .border_bottom(BorderStyle::Single, 8, "008B8B");
991
992    // Quote meta
993    doc.add_paragraph("");
994    {
995        let mut tbl = doc.add_table(4, 4).width_pct(100.0);
996        tbl.cell(0, 0).unwrap().set_text("QUOTATION");
997        tbl.cell(0, 0).unwrap().shading("008B8B").grid_span(2);
998        tbl.cell(0, 2).unwrap().set_text("DATE");
999        tbl.cell(0, 2).unwrap().shading("008B8B");
1000        tbl.cell(0, 3).unwrap().set_text("VALID UNTIL");
1001        tbl.cell(0, 3).unwrap().shading("008B8B");
1002        tbl.cell(1, 0).unwrap().set_text("Quote #:");
1003        tbl.cell(1, 1).unwrap().set_text("QT-2026-0147");
1004        tbl.cell(1, 2).unwrap().set_text("Feb 22, 2026");
1005        tbl.cell(1, 3).unwrap().set_text("Mar 22, 2026");
1006        tbl.cell(2, 0).unwrap().set_text("Prepared by:");
1007        tbl.cell(2, 1).unwrap().set_text("Walter White");
1008        tbl.cell(2, 2).unwrap().set_text("Payment:");
1009        tbl.cell(2, 3).unwrap().set_text("Net 30");
1010        tbl.cell(3, 0).unwrap().set_text("Customer:");
1011        tbl.cell(3, 1).unwrap().set_text("Meridian Dynamics LLC");
1012        tbl.cell(3, 2).unwrap().set_text("Currency:");
1013        tbl.cell(3, 3).unwrap().set_text("USD");
1014    }
1015
1016    doc.add_paragraph("");
1017
1018    // ── Bill of Materials ──
1019    {
1020        let mut p = doc.add_paragraph("");
1021        p.add_run("Bill of Materials")
1022            .bold(true)
1023            .size(16.0)
1024            .color("1A3C3C");
1025    }
1026    doc.add_paragraph("");
1027
1028    {
1029        let mut tbl = doc
1030            .add_table(10, 6)
1031            .borders(BorderStyle::Single, 2, "008B8B")
1032            .width_pct(100.0)
1033            .layout_fixed();
1034        // Headers
1035        let hdrs = [
1036            "#",
1037            "Part Number",
1038            "Description",
1039            "Qty",
1040            "Unit Price",
1041            "Total",
1042        ];
1043        for (c, h) in hdrs.iter().enumerate() {
1044            tbl.cell(0, c).unwrap().set_text(h);
1045            tbl.cell(0, c).unwrap().shading("008B8B");
1046        }
1047
1048        let items: Vec<(&str, &str, &str, &str, &str)> = vec![
1049            (
1050                "TB-GPU-A100",
1051                "NVIDIA A100 80GB GPU",
1052                "4",
1053                "$12,500.00",
1054                "$50,000.00",
1055            ),
1056            (
1057                "TB-SRV-R750",
1058                "Dell R750xa Server Chassis",
1059                "2",
1060                "$8,200.00",
1061                "$16,400.00",
1062            ),
1063            (
1064                "TB-RAM-256G",
1065                "256GB DDR5 ECC Memory Module",
1066                "8",
1067                "$890.00",
1068                "$7,120.00",
1069            ),
1070            (
1071                "TB-SSD-3840",
1072                "3.84TB NVMe U.2 SSD",
1073                "8",
1074                "$1,150.00",
1075                "$9,200.00",
1076            ),
1077            (
1078                "TB-NET-CX7",
1079                "ConnectX-7 200GbE NIC",
1080                "4",
1081                "$1,800.00",
1082                "$7,200.00",
1083            ),
1084            (
1085                "TB-SW-48P",
1086                "48-Port 100GbE Switch",
1087                "1",
1088                "$22,000.00",
1089                "$22,000.00",
1090            ),
1091            (
1092                "TB-CAB-RACK",
1093                "42U Server Rack + PDU",
1094                "1",
1095                "$4,500.00",
1096                "$4,500.00",
1097            ),
1098            (
1099                "TB-SVC-INST",
1100                "Installation & Configuration",
1101                "1",
1102                "$8,500.00",
1103                "$8,500.00",
1104            ),
1105            (
1106                "TB-SVC-SUPP",
1107                "3-Year Premium Support",
1108                "1",
1109                "$15,000.00",
1110                "$15,000.00",
1111            ),
1112        ];
1113
1114        for (i, (pn, desc, qty, unit, total)) in items.iter().enumerate() {
1115            let row = i + 1;
1116            tbl.cell(row, 0).unwrap().set_text(&format!("{}", i + 1));
1117            tbl.cell(row, 1).unwrap().set_text(pn);
1118            tbl.cell(row, 2).unwrap().set_text(desc);
1119            tbl.cell(row, 3).unwrap().set_text(qty);
1120            tbl.cell(row, 4).unwrap().set_text(unit);
1121            tbl.cell(row, 5).unwrap().set_text(total);
1122            if i % 2 == 0 {
1123                for c in 0..6 {
1124                    tbl.cell(row, c).unwrap().shading("F0F8F8");
1125                }
1126            }
1127        }
1128    }
1129
1130    doc.add_paragraph("");
1131
1132    // ── Totals ──
1133    {
1134        let mut tbl = doc
1135            .add_table(4, 2)
1136            .borders(BorderStyle::Single, 2, "008B8B")
1137            .width(Length::inches(3.5))
1138            .alignment(Alignment::Right);
1139        tbl.cell(0, 0).unwrap().set_text("Subtotal");
1140        tbl.cell(0, 1).unwrap().set_text("$139,920.00");
1141        tbl.cell(1, 0).unwrap().set_text("Shipping & Handling");
1142        tbl.cell(1, 1).unwrap().set_text("$2,500.00");
1143        tbl.cell(2, 0).unwrap().set_text("Tax (8.625%)");
1144        tbl.cell(2, 1).unwrap().set_text("$12,068.10");
1145        tbl.cell(3, 0).unwrap().set_text("TOTAL");
1146        tbl.cell(3, 0).unwrap().shading("E07020");
1147        tbl.cell(3, 1).unwrap().set_text("$154,488.10");
1148        tbl.cell(3, 1).unwrap().shading("E07020");
1149    }
1150
1151    doc.add_paragraph("");
1152
1153    // ── Terms & Conditions ──
1154    {
1155        let mut p = doc.add_paragraph("");
1156        p.add_run("Terms & Conditions")
1157            .bold(true)
1158            .size(14.0)
1159            .color("1A3C3C");
1160    }
1161    doc.add_numbered_list_item(
1162        "This quotation is valid for 30 calendar days from the date of issue.",
1163        0,
1164    );
1165    doc.add_numbered_list_item(
1166        "All prices are in USD and exclusive of applicable taxes unless stated.",
1167        0,
1168    );
1169    doc.add_numbered_list_item("Standard lead time is 4-6 weeks from PO receipt.", 0);
1170    doc.add_numbered_list_item(
1171        "Payment terms: Net 30 from invoice date. 2% discount for payment within 10 days.",
1172        0,
1173    );
1174    doc.add_numbered_list_item(
1175        "Warranty: 3-year manufacturer warranty on all hardware components.",
1176        0,
1177    );
1178    doc.add_numbered_list_item(
1179        "Returns subject to 15% restocking fee if initiated after 14 days.",
1180        0,
1181    );
1182
1183    doc.add_paragraph("");
1184    doc.add_paragraph("")
1185        .border_bottom(BorderStyle::Single, 4, "008B8B");
1186    doc.add_paragraph("");
1187    {
1188        let mut p = doc.add_paragraph("");
1189        p.add_run("Accepted by: ").bold(true);
1190        p.add_run("___________________________ Date: ___________");
1191    }
1192    {
1193        let mut p = doc.add_paragraph("");
1194        p.add_run("Print Name: ").bold(true);
1195        p.add_run("___________________________ Title: ___________");
1196    }
1197
1198    doc
1199}
1200
1201// =============================================================================
1202// 4. INVOICE — Crimson + charcoal scheme
1203// =============================================================================
1204fn generate_invoice(_samples_dir: &Path) -> Document {
1205    let mut doc = Document::new();
1206
1207    // Colors: Crimson #B22222, Charcoal #333333, Light #FAF0F0
1208    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1209    doc.set_margins(
1210        Length::inches(0.75),
1211        Length::inches(0.75),
1212        Length::inches(0.75),
1213        Length::inches(0.75),
1214    );
1215    doc.set_title("Invoice INV-2026-0392");
1216    doc.set_author("Walter White");
1217    doc.set_keywords("invoice, Tensorbee");
1218
1219    doc.set_footer("Tensorbee — Thank you for your business!");
1220
1221    // ── Company & Invoice Header ──
1222    {
1223        let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1224        // Company name left, INVOICE right
1225        {
1226            let mut cell = tbl.cell(0, 0).unwrap();
1227            let mut p = cell.add_paragraph("");
1228            p.add_run("TENSORBEE")
1229                .bold(true)
1230                .size(32.0)
1231                .color("B22222")
1232                .font("Helvetica");
1233        }
1234        {
1235            let mut cell = tbl.cell(0, 1).unwrap();
1236            let mut p = cell.add_paragraph("");
1237            p.add_run("INVOICE").bold(true).size(32.0).color("333333");
1238        }
1239        tbl.cell(1, 0)
1240            .unwrap()
1241            .set_text("123 Innovation Drive, Suite 400");
1242        tbl.cell(1, 1).unwrap().set_text("Invoice #: INV-2026-0392");
1243        tbl.cell(2, 0).unwrap().set_text("San Francisco, CA 94105");
1244        tbl.cell(2, 1).unwrap().set_text("Date: February 22, 2026");
1245        tbl.cell(3, 0).unwrap().set_text("walter@tensorbee.com");
1246        tbl.cell(3, 1).unwrap().set_text("Due Date: March 24, 2026");
1247    }
1248
1249    doc.add_paragraph("")
1250        .border_bottom(BorderStyle::Thick, 8, "B22222");
1251    doc.add_paragraph("");
1252
1253    // ── Bill To / Ship To ──
1254    {
1255        let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1256        tbl.cell(0, 0).unwrap().set_text("BILL TO");
1257        tbl.cell(0, 0).unwrap().shading("B22222");
1258        tbl.cell(0, 1).unwrap().set_text("SHIP TO");
1259        tbl.cell(0, 1).unwrap().shading("B22222");
1260        tbl.cell(1, 0).unwrap().set_text("Meridian Dynamics LLC");
1261        tbl.cell(1, 1).unwrap().set_text("Meridian Dynamics LLC");
1262        tbl.cell(2, 0).unwrap().set_text("456 Enterprise Blvd");
1263        tbl.cell(2, 1).unwrap().set_text("Attn: Server Room B");
1264        tbl.cell(3, 0).unwrap().set_text("Austin, TX 78701");
1265        tbl.cell(3, 1)
1266            .unwrap()
1267            .set_text("456 Enterprise Blvd, Austin, TX 78701");
1268    }
1269
1270    doc.add_paragraph("");
1271
1272    // ── Line Items ──
1273    {
1274        let mut tbl = doc
1275            .add_table(8, 5)
1276            .borders(BorderStyle::Single, 2, "B22222")
1277            .width_pct(100.0);
1278        let hdrs = ["Description", "Qty", "Unit Price", "Tax", "Amount"];
1279        for (c, h) in hdrs.iter().enumerate() {
1280            tbl.cell(0, c).unwrap().set_text(h);
1281            tbl.cell(0, c).unwrap().shading("B22222");
1282        }
1283        let items = [
1284            (
1285                "ML Infrastructure Setup — Phase 1",
1286                "1",
1287                "$45,000.00",
1288                "$3,881.25",
1289                "$48,881.25",
1290            ),
1291            (
1292                "Data Pipeline Development (160 hrs)",
1293                "160",
1294                "$225.00",
1295                "$3,105.00",
1296                "$39,105.00",
1297            ),
1298            (
1299                "GPU Cluster Configuration",
1300                "1",
1301                "$12,000.00",
1302                "$1,035.00",
1303                "$13,035.00",
1304            ),
1305            (
1306                "API Gateway Implementation",
1307                "1",
1308                "$18,500.00",
1309                "$1,595.63",
1310                "$20,095.63",
1311            ),
1312            (
1313                "Load Testing & QA (80 hrs)",
1314                "80",
1315                "$195.00",
1316                "$1,345.50",
1317                "$16,945.50",
1318            ),
1319            (
1320                "Documentation & Training",
1321                "1",
1322                "$8,000.00",
1323                "$690.00",
1324                "$8,690.00",
1325            ),
1326            (
1327                "Project Management (3 months)",
1328                "3",
1329                "$6,500.00",
1330                "$1,679.63",
1331                "$21,179.63",
1332            ),
1333        ];
1334        for (i, (desc, qty, unit, tax, amt)) in items.iter().enumerate() {
1335            let r = i + 1;
1336            tbl.cell(r, 0).unwrap().set_text(desc);
1337            tbl.cell(r, 1).unwrap().set_text(qty);
1338            tbl.cell(r, 2).unwrap().set_text(unit);
1339            tbl.cell(r, 3).unwrap().set_text(tax);
1340            tbl.cell(r, 4).unwrap().set_text(amt);
1341            if i % 2 == 0 {
1342                for c in 0..5 {
1343                    tbl.cell(r, c).unwrap().shading("FAF0F0");
1344                }
1345            }
1346        }
1347    }
1348
1349    doc.add_paragraph("");
1350
1351    // ── Totals ──
1352    {
1353        let mut tbl = doc
1354            .add_table(4, 2)
1355            .borders(BorderStyle::Single, 2, "B22222")
1356            .width(Length::inches(3.0))
1357            .alignment(Alignment::Right);
1358        tbl.cell(0, 0).unwrap().set_text("Subtotal");
1359        tbl.cell(0, 1).unwrap().set_text("$154,600.00");
1360        tbl.cell(1, 0).unwrap().set_text("Tax (8.625%)");
1361        tbl.cell(1, 1).unwrap().set_text("$13,334.25");
1362        tbl.cell(2, 0).unwrap().set_text("Discount (5%)");
1363        tbl.cell(2, 1).unwrap().set_text("-$7,730.00");
1364        tbl.cell(3, 0).unwrap().set_text("AMOUNT DUE");
1365        tbl.cell(3, 0).unwrap().shading("B22222");
1366        tbl.cell(3, 1).unwrap().set_text("$160,204.25");
1367        tbl.cell(3, 1).unwrap().shading("B22222");
1368    }
1369
1370    doc.add_paragraph("");
1371    doc.add_paragraph("");
1372
1373    // ── Payment Details ──
1374    {
1375        let mut p = doc.add_paragraph("");
1376        p.add_run("Payment Details")
1377            .bold(true)
1378            .size(14.0)
1379            .color("333333");
1380    }
1381    doc.add_paragraph("Bank: Silicon Valley Bank")
1382        .indent_left(Length::inches(0.3));
1383    doc.add_paragraph("Account: Tensorbee Inc. — 0847-2953-1120")
1384        .indent_left(Length::inches(0.3));
1385    doc.add_paragraph("Routing: 121140399")
1386        .indent_left(Length::inches(0.3));
1387    doc.add_paragraph("Swift: SVBKUS6S")
1388        .indent_left(Length::inches(0.3));
1389
1390    doc.add_paragraph("");
1391
1392    doc.add_paragraph("Please include invoice number INV-2026-0392 in the payment reference.")
1393        .shading("FAF0F0")
1394        .border_all(BorderStyle::Single, 2, "B22222");
1395
1396    doc
1397}
1398
1399// =============================================================================
1400// 5. REPORT — Forest green + earth tones, with images & hierarchical sections
1401// =============================================================================
1402fn generate_report(_samples_dir: &Path) -> Document {
1403    let mut doc = Document::new();
1404
1405    // Colors: Forest #2D5016, Sage #6B8E23, Earth #8B7355, Cream #FFFAF0
1406    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1407    doc.set_margins(
1408        Length::inches(1.0),
1409        Length::inches(1.0),
1410        Length::inches(1.0),
1411        Length::inches(1.0),
1412    );
1413    doc.set_title("Q4 2025 Environmental Impact Report");
1414    doc.set_author("Walter White");
1415    doc.set_subject("Quarterly Environmental Report");
1416    doc.set_keywords("environment, sustainability, report, Tensorbee");
1417
1418    doc.set_different_first_page(true);
1419    doc.set_first_page_header("");
1420    doc.set_header("Tensorbee — Q4 2025 Environmental Impact Report");
1421    doc.set_footer("CONFIDENTIAL — Page");
1422
1423    // ── Cover ──
1424    let cover_bg = create_sample_png(612, 792, [20, 50, 15]);
1425    doc.add_background_image(&cover_bg, "report_cover.png");
1426
1427    for _ in 0..5 {
1428        doc.add_paragraph("");
1429    }
1430    {
1431        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1432        p.add_run("Q4 2025")
1433            .bold(true)
1434            .size(48.0)
1435            .color("FFFFFF")
1436            .font("Georgia");
1437    }
1438    {
1439        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1440        p.add_run("Environmental Impact Report")
1441            .size(24.0)
1442            .color("90EE90")
1443            .font("Georgia")
1444            .italic(true);
1445    }
1446    doc.add_paragraph("");
1447    {
1448        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1449        p.add_run("Tensorbee — Sustainability Division")
1450            .size(14.0)
1451            .color("C0C0C0");
1452    }
1453    {
1454        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1455        p.add_run("Prepared by Walter White, Chief Sustainability Officer")
1456            .size(11.0)
1457            .color("AAAAAA");
1458    }
1459
1460    // ── TOC ──
1461    doc.add_paragraph("").page_break_before(true);
1462    doc.insert_toc(doc.content_count(), 3);
1463
1464    // ── Section 1: Executive Overview ──
1465    doc.add_paragraph("").page_break_before(true);
1466    doc.add_paragraph("Executive Overview").style("Heading1");
1467    doc.add_paragraph(
1468        "This report presents Tensorbee's environmental performance for Q4 2025. Our \
1469         sustainability initiatives have yielded a 34% reduction in carbon emissions compared \
1470         to Q4 2024, exceeding our target of 25%. Key achievements include the transition to \
1471         100% renewable energy in our primary data centers and the launch of our carbon offset \
1472         marketplace.",
1473    )
1474    .first_line_indent(Length::inches(0.3));
1475
1476    // Image: performance chart
1477    let chart_img = create_chart_png(400, 200);
1478    doc.add_paragraph("");
1479    doc.add_picture(
1480        &chart_img,
1481        "performance_chart.png",
1482        Length::inches(5.0),
1483        Length::inches(2.5),
1484    )
1485    .alignment(Alignment::Center);
1486    {
1487        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1488        p.add_run("Figure 1: Quarterly Carbon Emissions (tonnes CO2e)")
1489            .italic(true)
1490            .size(9.0)
1491            .color("666666");
1492    }
1493
1494    // ── Section 2: Energy Consumption ──
1495    doc.add_paragraph("").page_break_before(true);
1496    doc.add_paragraph("Energy Consumption").style("Heading1");
1497
1498    doc.add_paragraph("Data Center Operations")
1499        .style("Heading2");
1500    doc.add_paragraph(
1501        "Our three primary data centers consumed a combined 4.2 GWh in Q4 2025, \
1502         a 12% reduction from Q3 2025 driven by improved cooling efficiency and \
1503         server consolidation.",
1504    );
1505
1506    // Data table
1507    {
1508        let mut tbl = doc
1509            .add_table(5, 4)
1510            .borders(BorderStyle::Single, 4, "2D5016")
1511            .width_pct(100.0);
1512        let hdrs = ["Data Center", "Capacity (MW)", "Usage (GWh)", "PUE"];
1513        for (c, h) in hdrs.iter().enumerate() {
1514            tbl.cell(0, c).unwrap().set_text(h);
1515            tbl.cell(0, c).unwrap().shading("2D5016");
1516        }
1517        let rows = [
1518            ("San Francisco (Primary)", "3.2", "1.8", "1.12"),
1519            ("Dublin (EU)", "2.1", "1.2", "1.18"),
1520            ("Singapore (APAC)", "1.8", "1.2", "1.24"),
1521            ("TOTAL", "7.1", "4.2", "1.17 avg"),
1522        ];
1523        for (i, (dc, cap, use_, pue)) in rows.iter().enumerate() {
1524            tbl.cell(i + 1, 0).unwrap().set_text(dc);
1525            tbl.cell(i + 1, 1).unwrap().set_text(cap);
1526            tbl.cell(i + 1, 2).unwrap().set_text(use_);
1527            tbl.cell(i + 1, 3).unwrap().set_text(pue);
1528            if i == 3 {
1529                for c in 0..4 {
1530                    tbl.cell(i + 1, c).unwrap().shading("E2EFDA");
1531                }
1532            }
1533        }
1534    }
1535
1536    doc.add_paragraph("");
1537
1538    doc.add_paragraph("Renewable Energy Mix").style("Heading2");
1539    doc.add_paragraph("Breakdown of energy sources across all facilities:");
1540
1541    doc.add_bullet_list_item("Solar PV: 42% (1.76 GWh)", 0);
1542    doc.add_bullet_list_item("Wind Power Purchase Agreements: 38% (1.60 GWh)", 0);
1543    doc.add_bullet_list_item("Hydroelectric: 12% (0.50 GWh)", 0);
1544    doc.add_bullet_list_item("Grid (non-renewable): 8% (0.34 GWh)", 0);
1545
1546    // ── Section 3: Water & Waste ──
1547    doc.add_paragraph("Water & Waste Management")
1548        .style("Heading1");
1549
1550    doc.add_paragraph("Water Usage").style("Heading2");
1551    doc.add_paragraph(
1552        "Total water consumption was 12.4 million gallons, a 15% reduction from Q3. \
1553         Our closed-loop cooling systems now recycle 78% of water used in cooling operations.",
1554    );
1555
1556    doc.add_paragraph("Waste Reduction").style("Heading2");
1557    doc.add_paragraph("E-waste management results for Q4:")
1558        .keep_with_next(true);
1559    {
1560        let mut tbl = doc
1561            .add_table(4, 3)
1562            .borders(BorderStyle::Single, 2, "6B8E23")
1563            .width_pct(80.0);
1564        tbl.cell(0, 0).unwrap().set_text("Category");
1565        tbl.cell(0, 0).unwrap().shading("6B8E23");
1566        tbl.cell(0, 1).unwrap().set_text("Weight (kg)");
1567        tbl.cell(0, 1).unwrap().shading("6B8E23");
1568        tbl.cell(0, 2).unwrap().set_text("Recycled %");
1569        tbl.cell(0, 2).unwrap().shading("6B8E23");
1570        let rows = [
1571            ("Server Hardware", "2,450", "94%"),
1572            ("Networking Equipment", "820", "91%"),
1573            ("Storage Media", "340", "99%"),
1574        ];
1575        for (i, (cat, wt, pct)) in rows.iter().enumerate() {
1576            tbl.cell(i + 1, 0).unwrap().set_text(cat);
1577            tbl.cell(i + 1, 1).unwrap().set_text(wt);
1578            tbl.cell(i + 1, 2).unwrap().set_text(pct);
1579        }
1580    }
1581
1582    // ── Section 4: Initiatives ──
1583    doc.add_paragraph("").page_break_before(true);
1584    doc.add_paragraph("Q1 2026 Initiatives").style("Heading1");
1585
1586    doc.add_paragraph("Planned Programs").style("Heading2");
1587    doc.add_numbered_list_item(
1588        "Deploy on-site battery storage at SF data center (2 MWh capacity)",
1589        0,
1590    );
1591    doc.add_numbered_list_item("Pilot immersion cooling in Dublin facility", 0);
1592    doc.add_numbered_list_item("Launch employee commute carbon offset program", 0);
1593    doc.add_numbered_list_item("Achieve ISO 14001 certification for Singapore facility", 0);
1594
1595    doc.add_paragraph("Investment Targets").style("Heading2");
1596    doc.add_paragraph("Sustainability CapEx allocation for FY2026:")
1597        .keep_with_next(true);
1598    {
1599        let mut tbl = doc
1600            .add_table(5, 2)
1601            .borders(BorderStyle::Single, 4, "2D5016");
1602        tbl.cell(0, 0).unwrap().set_text("Initiative");
1603        tbl.cell(0, 0).unwrap().shading("2D5016");
1604        tbl.cell(0, 1).unwrap().set_text("Budget");
1605        tbl.cell(0, 1).unwrap().shading("2D5016");
1606        let rows = [
1607            ("Battery Storage", "$1.2M"),
1608            ("Immersion Cooling Pilot", "$800K"),
1609            ("Solar Panel Expansion", "$2.1M"),
1610            ("Carbon Credits", "$500K"),
1611        ];
1612        for (i, (init, budget)) in rows.iter().enumerate() {
1613            tbl.cell(i + 1, 0).unwrap().set_text(init);
1614            tbl.cell(i + 1, 1).unwrap().set_text(budget);
1615            if i % 2 == 0 {
1616                tbl.cell(i + 1, 0).unwrap().shading("FFFAF0");
1617                tbl.cell(i + 1, 1).unwrap().shading("FFFAF0");
1618            }
1619        }
1620    }
1621
1622    // Image: sustainability roadmap
1623    let roadmap_img = create_sample_png(500, 100, [40, 80, 30]);
1624    doc.add_paragraph("");
1625    doc.add_picture(
1626        &roadmap_img,
1627        "roadmap.png",
1628        Length::inches(6.0),
1629        Length::inches(1.2),
1630    )
1631    .alignment(Alignment::Center);
1632    {
1633        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1634        p.add_run("Figure 2: 2026 Sustainability Roadmap")
1635            .italic(true)
1636            .size(9.0)
1637            .color("666666");
1638    }
1639
1640    doc.add_paragraph("");
1641    doc.add_paragraph(
1642        "For questions about this report, contact Walter White at sustainability@tensorbee.com.",
1643    )
1644    .shading("FFFAF0")
1645    .border_all(BorderStyle::Single, 2, "2D5016");
1646
1647    doc
1648}
1649
1650// =============================================================================
1651// 6. LETTER — Slate blue + silver scheme
1652// =============================================================================
1653fn generate_letter(_samples_dir: &Path) -> Document {
1654    let mut doc = Document::new();
1655
1656    // Colors: Slate #4A5568, Blue accent #3182CE
1657    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1658    doc.set_margins(
1659        Length::inches(1.25),
1660        Length::inches(1.25),
1661        Length::inches(1.25),
1662        Length::inches(1.25),
1663    );
1664    doc.set_title("Business Letter");
1665    doc.set_author("Walter White");
1666
1667    // Banner header using raw XML
1668    let logo_img = create_logo_png(220, 48);
1669    let banner = build_header_banner_xml(
1670        "rId1",
1671        &BannerOpts {
1672            bg_color: "4A5568",
1673            banner_width: 7772400,
1674            banner_height: 731520, // ~0.8"
1675            logo_width: 2011680,
1676            logo_height: 438912,
1677            logo_x_offset: 295125,
1678            logo_y_offset: 146304,
1679        },
1680    );
1681    doc.set_raw_header_with_images(
1682        banner,
1683        &[("rId1", &logo_img, "logo.png")],
1684        rdocx_oxml::header_footer::HdrFtrType::Default,
1685    );
1686    doc.set_margins(
1687        Length::twips(2000),
1688        Length::twips(1800),
1689        Length::twips(1440),
1690        Length::twips(1800),
1691    );
1692    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
1693
1694    // Footer with contact
1695    doc.set_footer(
1696        "Tensorbee | 123 Innovation Drive, Suite 400 | San Francisco, CA 94105 | tensorbee.com",
1697    );
1698
1699    // ── Sender Address ──
1700    doc.add_paragraph("Walter White");
1701    doc.add_paragraph("Chief Executive Officer");
1702    doc.add_paragraph("Tensorbee");
1703    doc.add_paragraph("123 Innovation Drive, Suite 400");
1704    doc.add_paragraph("San Francisco, CA 94105");
1705    doc.add_paragraph("");
1706    doc.add_paragraph("February 22, 2026");
1707
1708    doc.add_paragraph("");
1709
1710    // ── Recipient ──
1711    doc.add_paragraph("Ms. Sarah Chen");
1712    doc.add_paragraph("VP of Engineering");
1713    doc.add_paragraph("Meridian Dynamics LLC");
1714    doc.add_paragraph("456 Enterprise Boulevard");
1715    doc.add_paragraph("Austin, TX 78701");
1716
1717    doc.add_paragraph("");
1718
1719    // ── Subject ──
1720    {
1721        let mut p = doc.add_paragraph("");
1722        p.add_run("Re: Strategic Technology Partnership — Phase 2 Expansion")
1723            .bold(true);
1724    }
1725
1726    doc.add_paragraph("");
1727
1728    // ── Body ──
1729    doc.add_paragraph("Dear Ms. Chen,");
1730    doc.add_paragraph("");
1731
1732    doc.add_paragraph(
1733        "I am writing to express Tensorbee's enthusiasm for expanding our strategic technology \
1734         partnership with Meridian Dynamics. Following the successful completion of Phase 1, \
1735         which delivered a 40% improvement in your ML inference pipeline throughput, we believe \
1736         the foundation is firmly established for an ambitious Phase 2 engagement.",
1737    )
1738    .first_line_indent(Length::inches(0.5));
1739
1740    doc.add_paragraph(
1741        "During our recent executive review, your team highlighted three priority areas for \
1742         the next phase of collaboration:",
1743    )
1744    .first_line_indent(Length::inches(0.5));
1745
1746    doc.add_numbered_list_item(
1747        "Real-time anomaly detection for your financial transaction monitoring system, \
1748         targeting sub-100ms latency at 50,000 transactions per second.",
1749        0,
1750    );
1751    doc.add_numbered_list_item(
1752        "Federated learning infrastructure to enable model training across your distributed \
1753         data centers without centralizing sensitive financial data.",
1754        0,
1755    );
1756    doc.add_numbered_list_item(
1757        "MLOps automation to reduce your model deployment cycle from the current 5 days \
1758         to under 4 hours.",
1759        0,
1760    );
1761
1762    doc.add_paragraph(
1763        "Our engineering team has prepared a detailed technical proposal addressing each of \
1764         these areas. We have allocated a dedicated team of eight senior engineers, led by \
1765         our CTO, to ensure continuity with the Phase 1 team your organization has already \
1766         built a productive working relationship with.",
1767    )
1768    .first_line_indent(Length::inches(0.5));
1769
1770    doc.add_paragraph(
1771        "I would welcome the opportunity to present our Phase 2 proposal in person. My \
1772         assistant will reach out to coordinate a meeting at your convenience during the \
1773         first week of March.",
1774    )
1775    .first_line_indent(Length::inches(0.5));
1776
1777    doc.add_paragraph("");
1778
1779    doc.add_paragraph("Warm regards,");
1780    doc.add_paragraph("");
1781    doc.add_paragraph("");
1782
1783    // Signature line
1784    doc.add_paragraph("")
1785        .border_bottom(BorderStyle::Single, 4, "4A5568");
1786    {
1787        let mut p = doc.add_paragraph("");
1788        p.add_run("Walter White").bold(true).size(12.0);
1789    }
1790    {
1791        let mut p = doc.add_paragraph("");
1792        p.add_run("Chief Executive Officer, Tensorbee")
1793            .italic(true)
1794            .size(10.0)
1795            .color("666666");
1796    }
1797    {
1798        let mut p = doc.add_paragraph("");
1799        p.add_run("walter@tensorbee.com | +1 (415) 555-0199")
1800            .size(10.0)
1801            .color("888888");
1802    }
1803
1804    doc
1805}
1806
1807// =============================================================================
1808// 7. EMPLOYMENT CONTRACT — Purple + charcoal scheme
1809// =============================================================================
1810fn generate_contract(_samples_dir: &Path) -> Document {
1811    let mut doc = Document::new();
1812
1813    // Colors: Purple #5B2C6F, Plum #8E44AD, Gray #2C3E50
1814    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1815    doc.set_margins(
1816        Length::inches(1.25),
1817        Length::inches(1.0),
1818        Length::inches(1.0),
1819        Length::inches(1.0),
1820    );
1821    doc.set_title("Employment Agreement");
1822    doc.set_author("Tensorbee HR Department");
1823    doc.set_subject("Employment Contract — Walter White");
1824    doc.set_keywords("employment, contract, agreement, Tensorbee");
1825
1826    doc.set_header("TENSORBEE — Employment Agreement");
1827    doc.set_footer("Employment Agreement — Walter White — Page");
1828
1829    // ── Title Block ──
1830    doc.add_paragraph("")
1831        .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1832    {
1833        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1834        p.add_run("EMPLOYMENT AGREEMENT")
1835            .bold(true)
1836            .size(24.0)
1837            .color("5B2C6F")
1838            .font("Georgia");
1839    }
1840    doc.add_paragraph("")
1841        .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1842    doc.add_paragraph("");
1843
1844    // ── Parties ──
1845    doc.add_paragraph(
1846        "This Employment Agreement (\"Agreement\") is entered into as of February 22, 2026 \
1847         (\"Effective Date\"), by and between:",
1848    );
1849
1850    doc.add_paragraph("");
1851
1852    {
1853        let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1854        p.add_run("EMPLOYER: ").bold(true);
1855        p.add_run(
1856            "Tensorbee, Inc., a Delaware corporation with its principal place of business at \
1857                   123 Innovation Drive, Suite 400, San Francisco, CA 94105 (\"Company\")",
1858        );
1859    }
1860    doc.add_paragraph("");
1861    {
1862        let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1863        p.add_run("EMPLOYEE: ").bold(true);
1864        p.add_run(
1865            "Walter White, residing at 308 Negra Arroyo Lane, Albuquerque, NM 87104 (\"Employee\")",
1866        );
1867    }
1868
1869    doc.add_paragraph("");
1870    doc.add_paragraph("The Company and Employee are collectively referred to as the \"Parties.\"");
1871    doc.add_paragraph("");
1872
1873    // ── Article 1: Position and Duties ──
1874    doc.add_paragraph("Article 1 — Position and Duties")
1875        .style("Heading1");
1876    {
1877        let mut p = doc.add_paragraph("");
1878        p.add_run("1.1 ").bold(true);
1879        p.add_run("The Company hereby employs the Employee as ");
1880        p.add_run("Chief Executive Officer (CEO)")
1881            .bold(true)
1882            .italic(true);
1883        p.add_run(", reporting directly to the Board of Directors.");
1884    }
1885    {
1886        let mut p = doc.add_paragraph("");
1887        p.add_run("1.2 ").bold(true);
1888        p.add_run("The Employee shall devote full working time, attention, and best efforts to \
1889                   the performance of duties as reasonably assigned by the Board, including but not limited to:");
1890    }
1891    doc.add_bullet_list_item(
1892        "Setting and executing the Company's strategic vision and business plan",
1893        0,
1894    );
1895    doc.add_bullet_list_item(
1896        "Overseeing all operations, engineering, and commercial activities",
1897        0,
1898    );
1899    doc.add_bullet_list_item(
1900        "Representing the Company to investors, customers, and the public",
1901        0,
1902    );
1903    doc.add_bullet_list_item("Recruiting, developing, and retaining key talent", 0);
1904
1905    {
1906        let mut p = doc.add_paragraph("");
1907        p.add_run("1.3 ").bold(true);
1908        p.add_run(
1909            "The Employee's primary work location shall be the Company's San Francisco \
1910                   headquarters, with reasonable travel as required by business needs.",
1911        );
1912    }
1913
1914    // ── Article 2: Compensation ──
1915    doc.add_paragraph("Article 2 — Compensation and Benefits")
1916        .style("Heading1");
1917    {
1918        let mut p = doc.add_paragraph("");
1919        p.add_run("2.1 Base Salary. ").bold(true);
1920        p.add_run("The Company shall pay the Employee an annual base salary of ");
1921        p.add_run("$375,000.00").bold(true);
1922        p.add_run(" (Three Hundred Seventy-Five Thousand Dollars), payable in accordance with the \
1923                   Company's standard payroll schedule, less applicable withholdings and deductions.");
1924    }
1925    {
1926        let mut p = doc.add_paragraph("");
1927        p.add_run("2.2 Annual Bonus. ").bold(true);
1928        p.add_run("The Employee shall be eligible for an annual performance bonus of up to ");
1929        p.add_run("40%").bold(true);
1930        p.add_run(
1931            " of base salary, based on achievement of mutually agreed performance objectives.",
1932        );
1933    }
1934    {
1935        let mut p = doc.add_paragraph("");
1936        p.add_run("2.3 Equity. ").bold(true);
1937        p.add_run("Subject to Board approval, the Employee shall receive a stock option grant of ");
1938        p.add_run("500,000 shares").bold(true);
1939        p.add_run(
1940            " of the Company's common stock, vesting over four (4) years with a one-year cliff, \
1941                   at an exercise price equal to the fair market value on the date of grant.",
1942        );
1943    }
1944
1945    // Compensation summary table
1946    doc.add_paragraph("");
1947    {
1948        let mut tbl = doc
1949            .add_table(5, 2)
1950            .borders(BorderStyle::Single, 4, "5B2C6F")
1951            .width_pct(70.0)
1952            .alignment(Alignment::Center);
1953        tbl.cell(0, 0).unwrap().set_text("Compensation Element");
1954        tbl.cell(0, 0).unwrap().shading("5B2C6F");
1955        tbl.cell(0, 1).unwrap().set_text("Value");
1956        tbl.cell(0, 1).unwrap().shading("5B2C6F");
1957        let items = [
1958            ("Base Salary", "$375,000/year"),
1959            ("Target Bonus", "Up to 40% ($150,000)"),
1960            ("Equity Grant", "500,000 shares (4yr vest)"),
1961            ("Total Target Comp", "$525,000 + equity"),
1962        ];
1963        for (i, (elem, val)) in items.iter().enumerate() {
1964            tbl.cell(i + 1, 0).unwrap().set_text(elem);
1965            tbl.cell(i + 1, 1).unwrap().set_text(val);
1966            if i == 3 {
1967                tbl.cell(i + 1, 0).unwrap().shading("E8DAEF");
1968                tbl.cell(i + 1, 1).unwrap().shading("E8DAEF");
1969            }
1970        }
1971    }
1972
1973    // ── Article 3: Benefits ──
1974    doc.add_paragraph("").page_break_before(true);
1975    doc.add_paragraph("Article 3 — Benefits").style("Heading1");
1976    {
1977        let mut p = doc.add_paragraph("");
1978        p.add_run("3.1 ").bold(true);
1979        p.add_run(
1980            "The Employee shall be entitled to participate in all benefit programs \
1981                   generally available to senior executives, including:",
1982        );
1983    }
1984    doc.add_bullet_list_item(
1985        "Medical, dental, and vision insurance (100% premium coverage for employee and dependents)",
1986        0,
1987    );
1988    doc.add_bullet_list_item("401(k) retirement plan with 6% company match", 0);
1989    doc.add_bullet_list_item("Life insurance and long-term disability coverage", 0);
1990    doc.add_bullet_list_item("Annual professional development allowance of $10,000", 0);
1991
1992    {
1993        let mut p = doc.add_paragraph("");
1994        p.add_run("3.2 Paid Time Off. ").bold(true);
1995        p.add_run(
1996            "The Employee shall receive 25 days of paid vacation per year, plus Company holidays, \
1997                   accruing on a monthly basis.",
1998        );
1999    }
2000
2001    // ── Article 4: Term and Termination ──
2002    doc.add_paragraph("Article 4 — Term and Termination")
2003        .style("Heading1");
2004    {
2005        let mut p = doc.add_paragraph("");
2006        p.add_run("4.1 At-Will Employment. ").bold(true);
2007        p.add_run(
2008            "This Agreement is for at-will employment and may be terminated by either Party \
2009                   at any time, with or without cause, subject to the notice provisions herein.",
2010        );
2011    }
2012    {
2013        let mut p = doc.add_paragraph("");
2014        p.add_run("4.2 Notice Period. ").bold(true);
2015        p.add_run("Either Party shall provide ");
2016        p.add_run("ninety (90) days").bold(true);
2017        p.add_run(" written notice of termination, or pay in lieu thereof.");
2018    }
2019    {
2020        let mut p = doc.add_paragraph("");
2021        p.add_run("4.3 Severance. ").bold(true);
2022        p.add_run("In the event of termination by the Company without Cause, the Employee shall \
2023                   receive (a) twelve (12) months of base salary continuation, (b) pro-rata bonus \
2024                   for the year of termination, and (c) twelve (12) months of COBRA premium coverage.");
2025    }
2026
2027    // ── Article 5: Confidentiality ──
2028    doc.add_paragraph("Article 5 — Confidentiality and IP")
2029        .style("Heading1");
2030    {
2031        let mut p = doc.add_paragraph("");
2032        p.add_run("5.1 ").bold(true);
2033        p.add_run(
2034            "The Employee agrees to maintain strict confidentiality of all proprietary \
2035                   information, trade secrets, and business plans of the Company during and after \
2036                   employment.",
2037        );
2038    }
2039    {
2040        let mut p = doc.add_paragraph("");
2041        p.add_run("5.2 ").bold(true);
2042        p.add_run(
2043            "All intellectual property, inventions, and works of authorship created by the \
2044                   Employee during the term of employment and within the scope of duties shall be \
2045                   the sole and exclusive property of the Company.",
2046        );
2047    }
2048
2049    // ── Article 6: Non-Compete ──
2050    doc.add_paragraph("Article 6 — Non-Competition")
2051        .style("Heading1");
2052    {
2053        let mut p = doc.add_paragraph("");
2054        p.add_run("6.1 ").bold(true);
2055        p.add_run("For a period of twelve (12) months following termination, the Employee agrees \
2056                   not to engage in any business that directly competes with the Company's core \
2057                   business of AI infrastructure and ML operations services, within the United States.");
2058    }
2059
2060    // ── Article 7: General Provisions ──
2061    doc.add_paragraph("Article 7 — General Provisions")
2062        .style("Heading1");
2063    {
2064        let mut p = doc.add_paragraph("");
2065        p.add_run("7.1 Governing Law. ").bold(true);
2066        p.add_run(
2067            "This Agreement shall be governed by and construed in accordance with the laws of \
2068                   the State of California.",
2069        );
2070    }
2071    {
2072        let mut p = doc.add_paragraph("");
2073        p.add_run("7.2 Entire Agreement. ").bold(true);
2074        p.add_run(
2075            "This Agreement constitutes the entire agreement between the Parties and supersedes \
2076                   all prior negotiations, representations, and agreements.",
2077        );
2078    }
2079    {
2080        let mut p = doc.add_paragraph("");
2081        p.add_run("7.3 Amendment. ").bold(true);
2082        p.add_run(
2083            "This Agreement may only be amended by a written instrument signed by both Parties.",
2084        );
2085    }
2086
2087    // ── Signature Block ──
2088    doc.add_paragraph("").page_break_before(true);
2089    doc.add_paragraph("IN WITNESS WHEREOF, the Parties have executed this Employment Agreement as of the Effective Date.")
2090        .space_after(Length::pt(24.0));
2091
2092    // Signature table
2093    {
2094        let mut tbl = doc.add_table(6, 2).width_pct(100.0);
2095        tbl.cell(0, 0).unwrap().set_text("FOR THE COMPANY:");
2096        tbl.cell(0, 0).unwrap().shading("5B2C6F");
2097        tbl.cell(0, 1).unwrap().set_text("EMPLOYEE:");
2098        tbl.cell(0, 1).unwrap().shading("5B2C6F");
2099
2100        tbl.cell(1, 0).unwrap().set_text("");
2101        tbl.cell(1, 1).unwrap().set_text("");
2102        tbl.row(1).unwrap().height(Length::pt(40.0));
2103
2104        tbl.cell(2, 0)
2105            .unwrap()
2106            .set_text("Signature: ___________________________");
2107        tbl.cell(2, 1)
2108            .unwrap()
2109            .set_text("Signature: ___________________________");
2110        tbl.cell(3, 0)
2111            .unwrap()
2112            .set_text("Name: Board Representative");
2113        tbl.cell(3, 1).unwrap().set_text("Name: Walter White");
2114        tbl.cell(4, 0)
2115            .unwrap()
2116            .set_text("Title: Chair, Board of Directors");
2117        tbl.cell(4, 1)
2118            .unwrap()
2119            .set_text("Title: Chief Executive Officer");
2120        tbl.cell(5, 0).unwrap().set_text("Date: _______________");
2121        tbl.cell(5, 1).unwrap().set_text("Date: _______________");
2122    }
2123
2124    doc
2125}
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_samples.rs (lines 41-46)
34fn generate_feature_showcase(path: &Path) {
35    let mut doc = Document::new();
36
37    // =========================================================================
38    // PAGE SETUP & METADATA
39    // =========================================================================
40    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
41    doc.set_margins(
42        Length::inches(1.0), // top
43        Length::inches(1.0), // right
44        Length::inches(1.0), // bottom
45        Length::inches(1.0), // left
46    );
47    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
48    doc.set_gutter(Length::twips(0));
49
50    doc.set_title("rdocx Feature Showcase");
51    doc.set_author("rdocx Sample Generator");
52    doc.set_subject("Comprehensive feature demonstration");
53    doc.set_keywords("rdocx, docx, rust, sample");
54
55    // Header & Footer
56    doc.set_header("rdocx Feature Showcase");
57    doc.set_footer("Generated by rdocx — Page");
58
59    // Different first page header
60    doc.set_different_first_page(true);
61    doc.set_first_page_header("rdocx");
62    doc.set_first_page_footer("Feature Showcase — Cover Page");
63
64    // =========================================================================
65    // PAGE 1: COVER PAGE — background image, run formatting
66    // =========================================================================
67    let bg_cover = create_sample_png(612, 792, [30, 60, 120]);
68    doc.add_background_image(&bg_cover, "cover_bg.png");
69
70    doc.add_paragraph(""); // spacer
71    doc.add_paragraph(""); // spacer
72    doc.add_paragraph(""); // spacer
73
74    {
75        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
76        p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
77    }
78    {
79        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
80        p.add_run("Feature Showcase")
81            .size(28.0)
82            .color("FFFFFF")
83            .italic(true);
84    }
85
86    doc.add_paragraph(""); // spacer
87
88    {
89        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
90        p.add_run("A comprehensive demonstration of every feature")
91            .size(14.0)
92            .color("CCDDFF");
93    }
94    {
95        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
96        p.add_run("provided by the rdocx Rust crate for DOCX generation.")
97            .size(14.0)
98            .color("CCDDFF");
99    }
100
101    // =========================================================================
102    // PAGE 2: TEXT FORMATTING
103    // =========================================================================
104    doc.add_paragraph("").page_break_before(true);
105
106    doc.add_paragraph("1. Text Formatting").style("Heading1");
107
108    doc.add_paragraph("This section demonstrates paragraph and run-level formatting options.");
109    doc.add_paragraph("");
110
111    // --- Paragraph alignment ---
112    doc.add_paragraph("Paragraph Alignment").style("Heading2");
113
114    doc.add_paragraph("This paragraph is left-aligned (the default).")
115        .alignment(Alignment::Left);
116    doc.add_paragraph("This paragraph is center-aligned.")
117        .alignment(Alignment::Center);
118    doc.add_paragraph("This paragraph is right-aligned.")
119        .alignment(Alignment::Right);
120    doc.add_paragraph(
121        "This paragraph is justified. To demonstrate justified text properly, it needs \
122         to be long enough to span multiple lines so the word spacing adjustment is visible \
123         across the full width of the text area on the page.",
124    )
125    .alignment(Alignment::Justify);
126
127    doc.add_paragraph("");
128
129    // --- Run formatting ---
130    doc.add_paragraph("Run Formatting").style("Heading2");
131
132    {
133        let mut p = doc.add_paragraph("");
134        p.add_run("Bold text").bold(true);
135        p.add_run(" | ");
136        p.add_run("Italic text").italic(true);
137        p.add_run(" | ");
138        p.add_run("Bold + Italic").bold(true).italic(true);
139    }
140    {
141        let mut p = doc.add_paragraph("");
142        p.add_run("Single underline").underline(true);
143        p.add_run(" | ");
144        p.add_run("Strikethrough").strike(true);
145        p.add_run(" | ");
146        p.add_run("Double strikethrough").double_strike(true);
147    }
148    {
149        let mut p = doc.add_paragraph("");
150        p.add_run("Red text").color("FF0000");
151        p.add_run(" | ");
152        p.add_run("Blue text").color("0000FF");
153        p.add_run(" | ");
154        p.add_run("Green text").color("00AA00");
155        p.add_run(" | ");
156        p.add_run("Highlighted").highlight("FFFF00");
157    }
158    {
159        let mut p = doc.add_paragraph("");
160        p.add_run("8pt small").size(8.0);
161        p.add_run(" | ");
162        p.add_run("11pt normal").size(11.0);
163        p.add_run(" | ");
164        p.add_run("16pt large").size(16.0);
165        p.add_run(" | ");
166        p.add_run("24pt extra-large").size(24.0);
167    }
168    {
169        let mut p = doc.add_paragraph("");
170        p.add_run("Arial font").font("Arial");
171        p.add_run(" | ");
172        p.add_run("Times New Roman font").font("Times New Roman");
173        p.add_run(" | ");
174        p.add_run("Courier New font").font("Courier New");
175    }
176    {
177        let mut p = doc.add_paragraph("");
178        p.add_run("Normal");
179        p.add_run(" H").size(11.0);
180        p.add_run("2").subscript();
181        p.add_run("O (subscript)").size(11.0);
182        p.add_run(" | E = mc").size(11.0);
183        p.add_run("2").superscript();
184        p.add_run(" (superscript)").size(11.0);
185    }
186    {
187        let mut p = doc.add_paragraph("");
188        p.add_run("ALL CAPS").all_caps(true);
189        p.add_run(" | ");
190        p.add_run("Small Caps").small_caps(true);
191        p.add_run(" | ");
192        p.add_run("Expanded spacing")
193            .character_spacing(Length::twips(40));
194    }
195
196    doc.add_paragraph("");
197
198    // --- Paragraph formatting ---
199    doc.add_paragraph("Paragraph Formatting").style("Heading2");
200
201    doc.add_paragraph("Paragraph with shading (light green background)")
202        .shading("E2EFDA");
203
204    doc.add_paragraph("Paragraph with bottom border")
205        .border_bottom(BorderStyle::Single, 6, "2E75B6");
206
207    doc.add_paragraph("Paragraph with all borders")
208        .border_all(BorderStyle::Single, 4, "FF0000");
209
210    doc.add_paragraph("Paragraph with 1-inch left indent and hanging indent")
211        .indent_left(Length::inches(1.0))
212        .hanging_indent(Length::inches(0.5));
213
214    doc.add_paragraph("Paragraph with first-line indent of 0.5 inches")
215        .first_line_indent(Length::inches(0.5));
216
217    doc.add_paragraph("Paragraph with extra space before (24pt) and after (12pt)")
218        .space_before(Length::pt(24.0))
219        .space_after(Length::pt(12.0));
220
221    doc.add_paragraph(
222        "Paragraph with double line spacing. This text should have extra vertical \
223         space between lines to demonstrate the line_spacing_multiple setting.",
224    )
225    .line_spacing_multiple(2.0);
226
227    doc.add_paragraph("Paragraph with keep-with-next (won't break from the next paragraph)")
228        .keep_with_next(true);
229    doc.add_paragraph("(This stays with the paragraph above.)");
230
231    // =========================================================================
232    // PAGE 3: LISTS & TAB STOPS
233    // =========================================================================
234    doc.add_paragraph("").page_break_before(true);
235
236    doc.add_paragraph("2. Lists").style("Heading1");
237
238    doc.add_paragraph("Bullet List").style("Heading2");
239
240    doc.add_bullet_list_item("First bullet item", 0);
241    doc.add_bullet_list_item("Second bullet item", 0);
242    doc.add_bullet_list_item("Nested level 1", 1);
243    doc.add_bullet_list_item("Nested level 2", 2);
244    doc.add_bullet_list_item("Back to level 1", 1);
245    doc.add_bullet_list_item("Third bullet item", 0);
246
247    doc.add_paragraph("");
248
249    doc.add_paragraph("Numbered List").style("Heading2");
250
251    doc.add_numbered_list_item("First numbered item", 0);
252    doc.add_numbered_list_item("Second numbered item", 0);
253    doc.add_numbered_list_item("Sub-item A", 1);
254    doc.add_numbered_list_item("Sub-item B", 1);
255    doc.add_numbered_list_item("Third numbered item", 0);
256
257    doc.add_paragraph("");
258
259    // --- Tab stops ---
260    doc.add_paragraph("Tab Stops").style("Heading2");
261
262    doc.add_paragraph("Left\tCenter\tRight\tDecimal")
263        .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
264        .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
265        .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
266        .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
267
268    doc.add_paragraph("Item\t........\tPrice")
269        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
270        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
271        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
272
273    doc.add_paragraph("Widget A\t........\t$19.99")
274        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
275        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
276        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
277
278    doc.add_paragraph("Gadget B\t________\t$249.50")
279        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
280        .add_tab_stop_with_leader(
281            TabAlignment::Right,
282            Length::inches(4.0),
283            TabLeader::Underscore,
284        )
285        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
286
287    // =========================================================================
288    // PAGE 4: TABLES
289    // =========================================================================
290    doc.add_paragraph("").page_break_before(true);
291
292    doc.add_paragraph("3. Tables").style("Heading1");
293
294    // --- Basic table with borders ---
295    doc.add_paragraph("Basic Table with Borders")
296        .style("Heading2");
297
298    {
299        let mut tbl = doc.add_table(4, 3);
300        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
301
302        // Header row
303        for col in 0..3 {
304            tbl.cell(0, col).unwrap().shading("2E75B6");
305        }
306        tbl.cell(0, 0).unwrap().set_text("Name");
307        tbl.cell(0, 1).unwrap().set_text("Role");
308        tbl.cell(0, 2).unwrap().set_text("Location");
309
310        tbl.cell(1, 0).unwrap().set_text("Alice Johnson");
311        tbl.cell(1, 1).unwrap().set_text("Engineering Lead");
312        tbl.cell(1, 2).unwrap().set_text("New York");
313
314        tbl.cell(2, 0).unwrap().set_text("Bob Smith");
315        tbl.cell(2, 1).unwrap().set_text("Product Manager");
316        tbl.cell(2, 2).unwrap().set_text("San Francisco");
317
318        tbl.cell(3, 0).unwrap().set_text("Carol Davis");
319        tbl.cell(3, 1).unwrap().set_text("Designer");
320        tbl.cell(3, 2).unwrap().set_text("London");
321    }
322
323    doc.add_paragraph("");
324
325    // --- Table with cell merging ---
326    doc.add_paragraph("Table with Cell Merging & Vertical Alignment")
327        .style("Heading2");
328
329    {
330        let mut tbl = doc.add_table(4, 4);
331        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
332        tbl = tbl.width_pct(100.0);
333
334        // Header spanning all columns
335        tbl.cell(0, 0).unwrap().set_text("Quarterly Revenue Report");
336        tbl.cell(0, 0).unwrap().shading("1F4E79");
337        tbl.cell(0, 0).unwrap().grid_span(4);
338
339        // Sub-header
340        tbl.cell(1, 0).unwrap().set_text("Region");
341        tbl.cell(1, 0).unwrap().shading("D6E4F0");
342        tbl.cell(1, 1).unwrap().set_text("Q1");
343        tbl.cell(1, 1).unwrap().shading("D6E4F0");
344        tbl.cell(1, 2).unwrap().set_text("Q2");
345        tbl.cell(1, 2).unwrap().shading("D6E4F0");
346        tbl.cell(1, 3).unwrap().set_text("Total");
347        tbl.cell(1, 3).unwrap().shading("D6E4F0");
348
349        // Data
350        tbl.cell(2, 0).unwrap().set_text("North America");
351        tbl.cell(2, 1).unwrap().set_text("$2.4M");
352        tbl.cell(2, 2).unwrap().set_text("$2.7M");
353        tbl.cell(2, 3).unwrap().set_text("$5.1M");
354
355        tbl.cell(3, 0).unwrap().set_text("Europe");
356        tbl.cell(3, 1).unwrap().set_text("$1.8M");
357        tbl.cell(3, 2).unwrap().set_text("$2.0M");
358        tbl.cell(3, 3).unwrap().set_text("$3.8M");
359
360        // Vertical alignment on data cells
361        tbl.cell(2, 3)
362            .unwrap()
363            .vertical_alignment(VerticalAlignment::Center);
364        tbl.cell(3, 3)
365            .unwrap()
366            .vertical_alignment(VerticalAlignment::Bottom);
367    }
368
369    doc.add_paragraph("");
370
371    // --- Table with vertical merge ---
372    doc.add_paragraph("Table with Vertical Merge")
373        .style("Heading2");
374
375    {
376        let mut tbl = doc.add_table(4, 3);
377        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
378
379        tbl.cell(0, 0).unwrap().set_text("Category");
380        tbl.cell(0, 0).unwrap().shading("E2EFDA");
381        tbl.cell(0, 1).unwrap().set_text("Item");
382        tbl.cell(0, 1).unwrap().shading("E2EFDA");
383        tbl.cell(0, 2).unwrap().set_text("Price");
384        tbl.cell(0, 2).unwrap().shading("E2EFDA");
385
386        // "Hardware" spans rows 1-2
387        tbl.cell(1, 0).unwrap().set_text("Hardware");
388        tbl.cell(1, 0).unwrap().v_merge_restart();
389        tbl.cell(1, 1).unwrap().set_text("Laptop");
390        tbl.cell(1, 2).unwrap().set_text("$1,200");
391
392        tbl.cell(2, 0).unwrap().v_merge_continue();
393        tbl.cell(2, 1).unwrap().set_text("Monitor");
394        tbl.cell(2, 2).unwrap().set_text("$450");
395
396        // "Software" on row 3
397        tbl.cell(3, 0).unwrap().set_text("Software");
398        tbl.cell(3, 1).unwrap().set_text("IDE License");
399        tbl.cell(3, 2).unwrap().set_text("$200/yr");
400    }
401
402    doc.add_paragraph("");
403
404    // --- Nested table ---
405    doc.add_paragraph("Nested Table").style("Heading2");
406
407    {
408        let mut tbl = doc.add_table(2, 2);
409        tbl = tbl.borders(BorderStyle::Single, 6, "2E75B6");
410
411        tbl.cell(0, 0).unwrap().set_text("Outer Cell (0,0)");
412        tbl.cell(0, 1).unwrap().set_text("Outer Cell (0,1)");
413        tbl.cell(1, 0).unwrap().set_text("Outer Cell (1,0)");
414
415        // Nested table inside cell (1,1)
416        {
417            let mut cell = tbl.cell(1, 1).unwrap();
418            cell.set_text("Contains nested table:");
419            let mut nested = cell.add_table(2, 2);
420            nested = nested.borders(BorderStyle::Single, 2, "FF6600");
421            nested.cell(0, 0).unwrap().set_text("Inner A");
422            nested.cell(0, 1).unwrap().set_text("Inner B");
423            nested.cell(1, 0).unwrap().set_text("Inner C");
424            nested.cell(1, 1).unwrap().set_text("Inner D");
425        }
426    }
427
428    // =========================================================================
429    // PAGE 5: IMAGES
430    // =========================================================================
431    doc.add_paragraph("").page_break_before(true);
432
433    doc.add_paragraph("4. Images").style("Heading1");
434
435    doc.add_paragraph("Inline Image").style("Heading2");
436
437    doc.add_paragraph("Below is an inline image (200x50 pixels, blue gradient):");
438    let inline_img = create_sample_png(200, 50, [0, 80, 200]);
439    doc.add_picture(
440        &inline_img,
441        "inline_chart.png",
442        Length::inches(3.0),
443        Length::inches(0.75),
444    );
445
446    doc.add_paragraph("");
447
448    doc.add_paragraph("Header Image").style("Heading2");
449
450    // Replace the text-only header with an image header
451    let header_img = create_sample_png(400, 40, [40, 40, 40]);
452    doc.set_header_image(
453        &header_img,
454        "header_logo.png",
455        Length::inches(2.0),
456        Length::inches(0.2),
457    );
458
459    doc.add_paragraph(
460        "The document header has been replaced with an inline image. \
461         Check the header area at the top of this page.",
462    );
463
464    doc.add_paragraph("");
465    doc.add_paragraph(
466        "Note: The cover page uses a full-page background image behind the text, \
467         demonstrated on page 1 via add_background_image().",
468    );
469
470    // =========================================================================
471    // PAGE 6: CONTENT MANIPULATION — placeholder replacement, insertion
472    // =========================================================================
473    doc.add_paragraph("").page_break_before(true);
474
475    doc.add_paragraph("5. Content Manipulation")
476        .style("Heading1");
477
478    // --- Placeholder replacement ---
479    doc.add_paragraph("Placeholder Replacement")
480        .style("Heading2");
481
482    doc.add_paragraph(
483        "Before replacement, this document contained {{customer}} and {{date}} placeholders.",
484    );
485
486    {
487        let mut p = doc.add_paragraph("");
488        p.add_run("Customer: ").bold(true);
489        p.add_run("{{customer}}");
490    }
491    {
492        let mut p = doc.add_paragraph("");
493        p.add_run("Date: ").bold(true);
494        p.add_run("{{date}}");
495    }
496    doc.add_paragraph("Reference: {{ref_number}}");
497
498    // Table with placeholders
499    {
500        let mut tbl = doc.add_table(3, 2);
501        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
502        tbl.cell(0, 0).unwrap().set_text("Field");
503        tbl.cell(0, 0).unwrap().shading("D6E4F0");
504        tbl.cell(0, 1).unwrap().set_text("Value");
505        tbl.cell(0, 1).unwrap().shading("D6E4F0");
506        tbl.cell(1, 0).unwrap().set_text("Project");
507        tbl.cell(1, 1).unwrap().set_text("{{project}}");
508        tbl.cell(2, 0).unwrap().set_text("Status");
509        tbl.cell(2, 1).unwrap().set_text("{{status}}");
510    }
511
512    // Perform replacements
513    let mut replacements = HashMap::new();
514    replacements.insert("{{customer}}", "Acme Corporation");
515    replacements.insert("{{date}}", "February 22, 2026");
516    replacements.insert("{{ref_number}}", "REF-2026-001");
517    replacements.insert("{{project}}", "Infrastructure Upgrade");
518    replacements.insert("{{status}}", "In Progress");
519    let replace_count = doc.replace_all(&replacements);
520
521    doc.add_paragraph("");
522    doc.add_paragraph(&format!(
523        "(Replaced {} placeholders above — in body text and table cells)",
524        replace_count
525    ));
526
527    doc.add_paragraph("");
528
529    // --- Content insertion ---
530    doc.add_paragraph("Content Insertion").style("Heading2");
531
532    doc.add_paragraph("Section A: First section of content.");
533    doc.add_paragraph("Section C: Third section of content.");
534
535    // Insert "Section B" between A and C
536    if let Some(idx) = doc.find_content_index("Section C") {
537        doc.insert_paragraph(
538            idx,
539            "Section B: Inserted between A and C using find_content_index().",
540        );
541    }
542
543    doc.add_paragraph("");
544    doc.add_paragraph(
545        "The paragraph above ('Section B') was inserted at a specific position \
546         using find_content_index() + insert_paragraph().",
547    );
548
549    // =========================================================================
550    // PAGE 7: LANDSCAPE — section break, wide table
551    // =========================================================================
552    doc.add_paragraph("").section_break(SectionBreak::NextPage);
553
554    doc.add_paragraph("6. Mixed Page Orientation")
555        .style("Heading1");
556
557    doc.add_paragraph(
558        "This page is in LANDSCAPE orientation. It was created using a section break \
559         followed by section_landscape(). This is useful for wide tables or charts.",
560    );
561
562    doc.add_paragraph("");
563
564    // Wide table for landscape
565    {
566        let mut tbl = doc.add_table(4, 7);
567        tbl = tbl.borders(BorderStyle::Single, 4, "2E75B6");
568
569        let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
570        for (col, h) in headers.iter().enumerate() {
571            tbl.cell(0, col).unwrap().set_text(h);
572            tbl.cell(0, col).unwrap().shading("2E75B6");
573        }
574
575        let data = [
576            [
577                "North America",
578                "$1.2M",
579                "$1.3M",
580                "$1.4M",
581                "$1.5M",
582                "$1.6M",
583                "$7.0M",
584            ],
585            [
586                "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
587            ],
588            [
589                "Asia Pacific",
590                "$0.5M",
591                "$0.6M",
592                "$0.7M",
593                "$0.7M",
594                "$0.8M",
595                "$3.3M",
596            ],
597        ];
598        for (row_idx, row_data) in data.iter().enumerate() {
599            for (col, val) in row_data.iter().enumerate() {
600                tbl.cell(row_idx + 1, col).unwrap().set_text(val);
601            }
602        }
603    }
604
605    // End landscape, return to portrait
606    doc.add_paragraph("")
607        .section_break(SectionBreak::NextPage)
608        .section_landscape();
609
610    // =========================================================================
611    // PAGE 8: BACK TO PORTRAIT — styles, final notes
612    // =========================================================================
613    doc.add_paragraph("7. Custom Styles & Summary")
614        .style("Heading1");
615
616    doc.add_paragraph(
617        "This final page is back in portrait orientation after a section break. \
618         The document has demonstrated:",
619    );
620
621    doc.add_bullet_list_item(
622        "Page setup: size, margins, header/footer distance, gutter",
623        0,
624    );
625    doc.add_bullet_list_item("Document metadata: title, author, subject, keywords", 0);
626    doc.add_bullet_list_item("Headers and footers: text, images, different first page", 0);
627    doc.add_bullet_list_item("Background images: full-page behind text", 0);
628    doc.add_bullet_list_item(
629        "Text formatting: bold, italic, underline, strike, color, size, font",
630        0,
631    );
632    doc.add_bullet_list_item(
633        "Advanced run formatting: superscript, subscript, caps, spacing",
634        0,
635    );
636    doc.add_bullet_list_item(
637        "Paragraph formatting: alignment, borders, shading, spacing, indentation",
638        0,
639    );
640    doc.add_bullet_list_item("Bullet and numbered lists with nesting levels", 0);
641    doc.add_bullet_list_item("Tab stops with dot/underscore leaders", 0);
642    doc.add_bullet_list_item(
643        "Tables: borders, shading, column spans, row spans, nesting",
644        0,
645    );
646    doc.add_bullet_list_item("Vertical alignment in table cells", 0);
647    doc.add_bullet_list_item("Inline images", 0);
648    doc.add_bullet_list_item("Placeholder replacement in body and table cells", 0);
649    doc.add_bullet_list_item("Content insertion at specific positions", 0);
650    doc.add_bullet_list_item(
651        "Section breaks with mixed portrait/landscape orientation",
652        0,
653    );
654
655    doc.add_paragraph("");
656    doc.add_paragraph("All features above were built entirely from scratch using the rdocx API.")
657        .alignment(Alignment::Center)
658        .shading("E2EFDA")
659        .border_all(BorderStyle::Single, 2, "00AA00");
660
661    doc.save(path).unwrap();
662}
examples/generate_all_samples.rs (lines 91-96)
86fn generate_feature_showcase(_samples_dir: &Path) -> Document {
87    let mut doc = Document::new();
88
89    // ── Page Setup & Metadata ──
90    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
91    doc.set_margins(
92        Length::inches(1.0),
93        Length::inches(1.0),
94        Length::inches(1.0),
95        Length::inches(1.0),
96    );
97    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
98    doc.set_gutter(Length::twips(0));
99    doc.set_title("rdocx Feature Showcase");
100    doc.set_author("rdocx Sample Generator");
101    doc.set_subject("Comprehensive feature demonstration");
102    doc.set_keywords("rdocx, docx, rust, sample, showcase");
103
104    // Headers & Footers with different first page
105    doc.set_different_first_page(true);
106    doc.set_first_page_header("rdocx");
107    doc.set_first_page_footer("Feature Showcase — Cover Page");
108    doc.set_header("rdocx Feature Showcase");
109    doc.set_footer("Generated by rdocx");
110
111    // ── COVER PAGE ──
112    let bg = create_sample_png(612, 792, [20, 45, 90]);
113    doc.add_background_image(&bg, "cover_bg.png");
114
115    for _ in 0..3 {
116        doc.add_paragraph("");
117    }
118    {
119        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
120        p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
121    }
122    {
123        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
124        p.add_run("Complete Feature Showcase")
125            .size(28.0)
126            .color("FFFFFF")
127            .italic(true);
128    }
129    doc.add_paragraph("");
130    {
131        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
132        p.add_run("Every feature of the rdocx Rust library")
133            .size(13.0)
134            .color("B0C4DE");
135    }
136    {
137        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
138        p.add_run("demonstrated in a single document.")
139            .size(13.0)
140            .color("B0C4DE");
141    }
142
143    // ── TABLE OF CONTENTS ──
144    doc.add_paragraph("").page_break_before(true);
145    doc.insert_toc(doc.content_count(), 3);
146
147    // ── SECTION 1: TEXT FORMATTING ──
148    doc.add_paragraph("").page_break_before(true);
149    doc.add_paragraph("1. Text Formatting").style("Heading1");
150
151    // Paragraph Alignment
152    doc.add_paragraph("1.1 Paragraph Alignment")
153        .style("Heading2");
154    doc.add_paragraph("Left-aligned paragraph (default).")
155        .alignment(Alignment::Left);
156    doc.add_paragraph("Center-aligned paragraph.")
157        .alignment(Alignment::Center);
158    doc.add_paragraph("Right-aligned paragraph.")
159        .alignment(Alignment::Right);
160    doc.add_paragraph(
161        "Justified paragraph — this text is long enough to demonstrate how justified alignment \
162         distributes extra space across word gaps so lines fill the full width of the text area.",
163    )
164    .alignment(Alignment::Justify);
165
166    doc.add_paragraph("");
167
168    // Run Formatting
169    doc.add_paragraph("1.2 Run Formatting").style("Heading2");
170    {
171        let mut p = doc.add_paragraph("");
172        p.add_run("Bold").bold(true);
173        p.add_run(" | ");
174        p.add_run("Italic").italic(true);
175        p.add_run(" | ");
176        p.add_run("Bold Italic").bold(true).italic(true);
177        p.add_run(" | ");
178        p.add_run("Underline").underline(true);
179        p.add_run(" | ");
180        p.add_run("Strikethrough").strike(true);
181        p.add_run(" | ");
182        p.add_run("Double Strike").double_strike(true);
183    }
184    {
185        let mut p = doc.add_paragraph("");
186        p.add_run("Red").color("FF0000");
187        p.add_run(" | ");
188        p.add_run("Blue").color("0000FF");
189        p.add_run(" | ");
190        p.add_run("Green").color("00AA00");
191        p.add_run(" | ");
192        p.add_run("Highlighted").highlight("FFFF00");
193    }
194    {
195        let mut p = doc.add_paragraph("");
196        p.add_run("8pt").size(8.0);
197        p.add_run(" | ");
198        p.add_run("11pt").size(11.0);
199        p.add_run(" | ");
200        p.add_run("16pt").size(16.0);
201        p.add_run(" | ");
202        p.add_run("24pt").size(24.0);
203    }
204    {
205        let mut p = doc.add_paragraph("");
206        p.add_run("Arial").font("Arial");
207        p.add_run(" | ");
208        p.add_run("Times New Roman").font("Times New Roman");
209        p.add_run(" | ");
210        p.add_run("Courier New").font("Courier New");
211    }
212    {
213        let mut p = doc.add_paragraph("");
214        p.add_run("H");
215        p.add_run("2").subscript();
216        p.add_run("O (subscript) | E=mc");
217        p.add_run("2").superscript();
218        p.add_run(" (superscript)");
219    }
220    {
221        let mut p = doc.add_paragraph("");
222        p.add_run("ALL CAPS").all_caps(true);
223        p.add_run(" | ");
224        p.add_run("Small Caps").small_caps(true);
225        p.add_run(" | ");
226        p.add_run("Expanded").character_spacing(Length::twips(40));
227        p.add_run(" | ");
228        p.add_run("Hidden text (not visible)").hidden(true);
229    }
230
231    doc.add_paragraph("");
232
233    // Underline Styles
234    doc.add_paragraph("1.3 Underline Styles").style("Heading2");
235    {
236        let mut p = doc.add_paragraph("");
237        p.add_run("Single ")
238            .underline_style(rdocx::UnderlineStyle::Single);
239        p.add_run("Double ")
240            .underline_style(rdocx::UnderlineStyle::Double);
241        p.add_run("Thick ")
242            .underline_style(rdocx::UnderlineStyle::Thick);
243        p.add_run("Dotted ")
244            .underline_style(rdocx::UnderlineStyle::Dotted);
245        p.add_run("Dash ")
246            .underline_style(rdocx::UnderlineStyle::Dash);
247        p.add_run("Wave ")
248            .underline_style(rdocx::UnderlineStyle::Wave);
249    }
250
251    // ── SECTION 2: PARAGRAPH FORMATTING ──
252    doc.add_paragraph("").page_break_before(true);
253    doc.add_paragraph("2. Paragraph Formatting")
254        .style("Heading1");
255
256    doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
257    doc.add_paragraph("Paragraph with light green shading")
258        .shading("E2EFDA");
259    doc.add_paragraph("Paragraph with bottom border")
260        .border_bottom(BorderStyle::Single, 6, "2E75B6");
261    doc.add_paragraph("Paragraph with all borders (red)")
262        .border_all(BorderStyle::Single, 4, "FF0000");
263
264    doc.add_paragraph("2.2 Indentation").style("Heading2");
265    doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
266        .indent_left(Length::inches(1.0))
267        .hanging_indent(Length::inches(0.5));
268    doc.add_paragraph("0.5-inch first-line indent on this paragraph")
269        .first_line_indent(Length::inches(0.5));
270    doc.add_paragraph("Both left and right indent (0.75 inches each)")
271        .indent_left(Length::inches(0.75))
272        .indent_right(Length::inches(0.75));
273
274    doc.add_paragraph("2.3 Spacing & Line Height")
275        .style("Heading2");
276    doc.add_paragraph("Extra space before (24pt) and after (12pt)")
277        .space_before(Length::pt(24.0))
278        .space_after(Length::pt(12.0));
279    doc.add_paragraph(
280        "Double line spacing paragraph. This text should have extra vertical space between \
281         lines to demonstrate line_spacing_multiple(2.0).",
282    )
283    .line_spacing_multiple(2.0);
284    doc.add_paragraph("Exact 20pt line spacing (fixed height).")
285        .line_spacing(20.0);
286
287    doc.add_paragraph("2.4 Pagination Controls")
288        .style("Heading2");
289    doc.add_paragraph("keep_with_next — this paragraph stays with the next")
290        .keep_with_next(true);
291    doc.add_paragraph("(This paragraph was kept with the one above.)");
292    doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
293        .keep_together(true);
294    doc.add_paragraph("widow_control — prevents widow/orphan lines")
295        .widow_control(true);
296
297    // ── SECTION 3: LISTS ──
298    doc.add_paragraph("").page_break_before(true);
299    doc.add_paragraph("3. Lists").style("Heading1");
300
301    doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
302    doc.add_bullet_list_item("First item", 0);
303    doc.add_bullet_list_item("Second item", 0);
304    doc.add_bullet_list_item("Nested level 1", 1);
305    doc.add_bullet_list_item("Nested level 2", 2);
306    doc.add_bullet_list_item("Back to level 1", 1);
307    doc.add_bullet_list_item("Third item", 0);
308
309    doc.add_paragraph("");
310
311    doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
312    doc.add_numbered_list_item("First numbered item", 0);
313    doc.add_numbered_list_item("Second numbered item", 0);
314    doc.add_numbered_list_item("Sub-item A", 1);
315    doc.add_numbered_list_item("Sub-item B", 1);
316    doc.add_numbered_list_item("Third numbered item", 0);
317
318    // ── SECTION 4: TAB STOPS ──
319    doc.add_paragraph("");
320    doc.add_paragraph("4. Tab Stops").style("Heading1");
321
322    doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
323    doc.add_paragraph("Left\tCenter\tRight\tDecimal")
324        .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
325        .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
326        .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
327        .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
328
329    doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
330    doc.add_paragraph("Item\t\tPrice")
331        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
332        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
333        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
334    doc.add_paragraph("Widget\t\t$19.99")
335        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
336        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
337        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
338    doc.add_paragraph("Gadget\t\t$249.50")
339        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
340        .add_tab_stop_with_leader(
341            TabAlignment::Right,
342            Length::inches(4.0),
343            TabLeader::Underscore,
344        )
345        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
346
347    // ── SECTION 5: TABLES ──
348    doc.add_paragraph("").page_break_before(true);
349    doc.add_paragraph("5. Tables").style("Heading1");
350
351    doc.add_paragraph("5.1 Basic Table").style("Heading2");
352    {
353        let mut tbl = doc
354            .add_table(4, 3)
355            .borders(BorderStyle::Single, 4, "000000");
356        for col in 0..3 {
357            tbl.cell(0, col).unwrap().shading("2E75B6");
358        }
359        tbl.cell(0, 0).unwrap().set_text("Name");
360        tbl.cell(0, 1).unwrap().set_text("Role");
361        tbl.cell(0, 2).unwrap().set_text("Location");
362        tbl.cell(1, 0).unwrap().set_text("Walter White");
363        tbl.cell(1, 1).unwrap().set_text("CEO");
364        tbl.cell(1, 2).unwrap().set_text("Albuquerque");
365        tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
366        tbl.cell(2, 1).unwrap().set_text("CTO");
367        tbl.cell(2, 2).unwrap().set_text("Remote");
368        tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
369        tbl.cell(3, 1).unwrap().set_text("Security");
370        tbl.cell(3, 2).unwrap().set_text("Washington");
371    }
372
373    doc.add_paragraph("");
374
375    doc.add_paragraph("5.2 Column Span & Cell Shading")
376        .style("Heading2");
377    {
378        let mut tbl = doc
379            .add_table(3, 4)
380            .borders(BorderStyle::Single, 4, "000000")
381            .width_pct(100.0);
382        tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
383        tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
384        tbl.cell(1, 0).unwrap().set_text("Region");
385        tbl.cell(1, 0).unwrap().shading("D6E4F0");
386        tbl.cell(1, 1).unwrap().set_text("Q1");
387        tbl.cell(1, 1).unwrap().shading("D6E4F0");
388        tbl.cell(1, 2).unwrap().set_text("Q2");
389        tbl.cell(1, 2).unwrap().shading("D6E4F0");
390        tbl.cell(1, 3).unwrap().set_text("Total");
391        tbl.cell(1, 3).unwrap().shading("D6E4F0");
392        tbl.cell(2, 0).unwrap().set_text("Americas");
393        tbl.cell(2, 1).unwrap().set_text("$2.4M");
394        tbl.cell(2, 2).unwrap().set_text("$2.7M");
395        tbl.cell(2, 3).unwrap().set_text("$5.1M");
396    }
397
398    doc.add_paragraph("");
399
400    doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
401    {
402        let mut tbl = doc
403            .add_table(4, 3)
404            .borders(BorderStyle::Single, 4, "000000");
405        tbl.cell(0, 0).unwrap().set_text("Category");
406        tbl.cell(0, 0).unwrap().shading("E2EFDA");
407        tbl.cell(0, 1).unwrap().set_text("Item");
408        tbl.cell(0, 1).unwrap().shading("E2EFDA");
409        tbl.cell(0, 2).unwrap().set_text("Price");
410        tbl.cell(0, 2).unwrap().shading("E2EFDA");
411        tbl.cell(1, 0).unwrap().set_text("Hardware");
412        tbl.cell(1, 0).unwrap().v_merge_restart();
413        tbl.cell(1, 1).unwrap().set_text("Laptop");
414        tbl.cell(1, 2).unwrap().set_text("$1,200");
415        tbl.cell(2, 0).unwrap().v_merge_continue();
416        tbl.cell(2, 1).unwrap().set_text("Monitor");
417        tbl.cell(2, 2).unwrap().set_text("$450");
418        tbl.cell(3, 0).unwrap().set_text("Software");
419        tbl.cell(3, 1).unwrap().set_text("IDE License");
420        tbl.cell(3, 2).unwrap().set_text("$200/yr");
421    }
422
423    doc.add_paragraph("");
424
425    doc.add_paragraph("5.4 Nested Table").style("Heading2");
426    {
427        let mut tbl = doc
428            .add_table(2, 2)
429            .borders(BorderStyle::Single, 6, "2E75B6");
430        tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
431        tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
432        tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
433        {
434            let mut cell = tbl.cell(1, 1).unwrap();
435            cell.set_text("Nested table below:");
436            let mut nested = cell
437                .add_table(2, 2)
438                .borders(BorderStyle::Single, 2, "FF6600");
439            nested.cell(0, 0).unwrap().set_text("A");
440            nested.cell(0, 1).unwrap().set_text("B");
441            nested.cell(1, 0).unwrap().set_text("C");
442            nested.cell(1, 1).unwrap().set_text("D");
443        }
444    }
445
446    doc.add_paragraph("");
447
448    doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
449        .style("Heading2");
450    {
451        let mut tbl = doc
452            .add_table(2, 3)
453            .borders(BorderStyle::Single, 4, "666666");
454        tbl.row(0).unwrap().height(Length::pt(50.0)).header();
455        tbl.cell(0, 0).unwrap().set_text("Top");
456        tbl.cell(0, 0)
457            .unwrap()
458            .vertical_alignment(VerticalAlignment::Top)
459            .shading("FFF2CC");
460        tbl.cell(0, 1).unwrap().set_text("Center");
461        tbl.cell(0, 1)
462            .unwrap()
463            .vertical_alignment(VerticalAlignment::Center)
464            .shading("D9E2F3");
465        tbl.cell(0, 2).unwrap().set_text("Bottom");
466        tbl.cell(0, 2)
467            .unwrap()
468            .vertical_alignment(VerticalAlignment::Bottom)
469            .shading("E2EFDA");
470        tbl.row(1).unwrap().cant_split();
471        tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
472        tbl.cell(1, 0).unwrap().no_wrap();
473        tbl.cell(1, 1).unwrap().set_text("Fixed width");
474        tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
475        tbl.cell(1, 2).unwrap().set_text("Normal");
476    }
477
478    // ── SECTION 6: IMAGES ──
479    doc.add_paragraph("").page_break_before(true);
480    doc.add_paragraph("6. Images").style("Heading1");
481
482    doc.add_paragraph("6.1 Inline Image").style("Heading2");
483    doc.add_paragraph("A blue gradient image below:");
484    let img = create_sample_png(200, 50, [0, 80, 200]);
485    doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
486
487    doc.add_paragraph("");
488    doc.add_paragraph("6.2 Header Image").style("Heading2");
489    let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
490    doc.set_header_image(
491        &hdr_img,
492        "header_logo.png",
493        Length::inches(2.0),
494        Length::inches(0.2),
495    );
496    doc.add_paragraph("The header now contains an inline image (check the top of this page).");
497
498    doc.add_paragraph("");
499    doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
500
501    // ── SECTION 7: CONTENT MANIPULATION ──
502    doc.add_paragraph("").page_break_before(true);
503    doc.add_paragraph("7. Content Manipulation")
504        .style("Heading1");
505
506    doc.add_paragraph("7.1 Placeholder Replacement")
507        .style("Heading2");
508    doc.add_paragraph("Customer: {{customer}}");
509    doc.add_paragraph("Date: {{date}}");
510    doc.add_paragraph("Reference: {{ref_number}}");
511    {
512        let mut tbl = doc
513            .add_table(2, 2)
514            .borders(BorderStyle::Single, 4, "000000");
515        tbl.cell(0, 0).unwrap().set_text("Project");
516        tbl.cell(0, 0).unwrap().shading("D6E4F0");
517        tbl.cell(0, 1).unwrap().set_text("{{project}}");
518        tbl.cell(1, 0).unwrap().set_text("Status");
519        tbl.cell(1, 0).unwrap().shading("D6E4F0");
520        tbl.cell(1, 1).unwrap().set_text("{{status}}");
521    }
522
523    let mut replacements = HashMap::new();
524    replacements.insert("{{customer}}", "Tensorbee Inc.");
525    replacements.insert("{{date}}", "February 22, 2026");
526    replacements.insert("{{ref_number}}", "TB-2026-001");
527    replacements.insert("{{project}}", "Infrastructure Upgrade");
528    replacements.insert("{{status}}", "In Progress");
529    let n = doc.replace_all(&replacements);
530    doc.add_paragraph(&format!("({n} placeholders replaced above)"));
531
532    doc.add_paragraph("");
533
534    doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
535    doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
536    let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
537    doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
538
539    doc.add_paragraph("");
540
541    doc.add_paragraph("7.3 Content Insertion").style("Heading2");
542    doc.add_paragraph("Section A: First content.");
543    doc.add_paragraph("Section C: Third content.");
544    if let Some(idx) = doc.find_content_index("Section C") {
545        doc.insert_paragraph(
546            idx,
547            "Section B: Inserted between A and C via find_content_index().",
548        );
549    }
550
551    // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
552    doc.add_paragraph("").section_break(SectionBreak::NextPage);
553    doc.add_paragraph("8. Section Breaks & Orientation")
554        .style("Heading1");
555    doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
556
557    {
558        let mut tbl = doc
559            .add_table(3, 7)
560            .borders(BorderStyle::Single, 4, "2E75B6");
561        let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
562        for (c, h) in headers.iter().enumerate() {
563            tbl.cell(0, c).unwrap().set_text(h);
564            tbl.cell(0, c).unwrap().shading("2E75B6");
565        }
566        let data = [
567            [
568                "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
569            ],
570            [
571                "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
572            ],
573        ];
574        for (r, row) in data.iter().enumerate() {
575            for (c, v) in row.iter().enumerate() {
576                tbl.cell(r + 1, c).unwrap().set_text(v);
577            }
578        }
579    }
580
581    doc.add_paragraph("")
582        .section_break(SectionBreak::NextPage)
583        .section_landscape();
584
585    // ── SECTION 9: CUSTOM STYLES ──
586    doc.add_paragraph("9. Custom Styles").style("Heading1");
587    doc.add_style(
588        StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
589            .based_on("Normal")
590            .paragraph_properties({
591                let mut ppr = rdocx_oxml::properties::CT_PPr::default();
592                ppr.shading = Some(rdocx_oxml::properties::CT_Shd {
593                    val: "clear".to_string(),
594                    color: None,
595                    fill: Some("FFF2CC".to_string()),
596                });
597                ppr
598            })
599            .run_properties({
600                let mut rpr = rdocx_oxml::properties::CT_RPr::default();
601                rpr.bold = Some(true);
602                rpr.color = Some("C45911".to_string());
603                rpr
604            }),
605    );
606    doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
607        .style("CustomHighlight");
608
609    doc.add_paragraph("");
610
611    // ── SECTION 10: DOCUMENT INTELLIGENCE ──
612    doc.add_paragraph("10. Document Intelligence API")
613        .style("Heading1");
614
615    let wc = doc.word_count();
616    let hc = doc.headings().len();
617    let ic = doc.images().len();
618    let lc = doc.links().len();
619    doc.add_paragraph(&format!("Word count: {wc}"));
620    doc.add_paragraph(&format!("Heading count: {hc}"));
621    doc.add_paragraph(&format!("Image count: {ic}"));
622    doc.add_paragraph(&format!("Link count: {lc}"));
623
624    let outline = doc.document_outline();
625    doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
626
627    let issues = doc.audit_accessibility();
628    doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
629    for issue in &issues {
630        doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
631    }
632
633    // ── SECTION 11: DOCUMENT MERGING ──
634    doc.add_paragraph("");
635    doc.add_paragraph("11. Document Merging").style("Heading1");
636    {
637        let mut other = Document::new();
638        other.add_paragraph("This paragraph was merged from another document using append().");
639        doc.append(&other);
640    }
641
642    doc.add_paragraph("");
643
644    // ── SUMMARY ──
645    doc.add_paragraph("Summary of Demonstrated Features")
646        .style("Heading1");
647    let features = [
648        "Page setup: size, margins, header/footer distance, gutter",
649        "Document metadata: title, author, subject, keywords",
650        "Headers and footers: text, images, different first page",
651        "Background images (full-page behind text)",
652        "Table of Contents generation with bookmarks",
653        "Text formatting: bold, italic, underline styles, strike, color, size, font",
654        "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
655        "Paragraph formatting: alignment, borders, shading, spacing, indentation",
656        "Pagination controls: keep-with-next, keep-together, widow control",
657        "Bullet and numbered lists with nesting",
658        "Tab stops with dot/underscore/hyphen leaders",
659        "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
660        "Row properties: header rows, exact height, cant-split",
661        "Cell properties: width, no-wrap, vertical alignment",
662        "Inline images and header images",
663        "Placeholder replacement (single and batch)",
664        "Regex-based find and replace",
665        "Content insertion at specific positions",
666        "Section breaks with mixed portrait/landscape",
667        "Custom paragraph and character styles",
668        "Document intelligence: word count, headings, outline, images, links",
669        "Accessibility audit",
670        "Document merging (append)",
671        "Export: DOCX, PDF, HTML, Markdown",
672    ];
673    for f in &features {
674        doc.add_bullet_list_item(f, 0);
675    }
676    doc.add_paragraph("");
677    doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
678        .alignment(Alignment::Center)
679        .shading("E2EFDA")
680        .border_all(BorderStyle::Single, 2, "00AA00");
681
682    doc
683}
684
685// =============================================================================
686// 2. PROPOSAL DOCUMENT — Deep navy + gold scheme
687// =============================================================================
688fn generate_proposal(_samples_dir: &Path) -> Document {
689    let mut doc = Document::new();
690
691    // Colors: Navy #1B2A4A, Gold #C5922E, Light #F4F1EB
692    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
693    doc.set_margins(
694        Length::inches(1.0),
695        Length::inches(1.0),
696        Length::inches(1.0),
697        Length::inches(1.0),
698    );
699    doc.set_title("AI Platform Modernization Proposal");
700    doc.set_author("Walter White");
701    doc.set_subject("Technology Proposal");
702    doc.set_keywords("proposal, AI, modernization, Tensorbee");
703
704    // Banner header
705    let logo_img = create_logo_png(220, 48);
706    let banner = build_header_banner_xml(
707        "rId1",
708        &BannerOpts {
709            bg_color: "1B2A4A",
710            banner_width: 7772400,
711            banner_height: 914400,
712            logo_width: 2011680,
713            logo_height: 438912,
714            logo_x_offset: 295125,
715            logo_y_offset: 237744,
716        },
717    );
718    doc.set_raw_header_with_images(
719        banner,
720        &[("rId1", &logo_img, "logo.png")],
721        rdocx_oxml::header_footer::HdrFtrType::Default,
722    );
723    doc.set_different_first_page(true);
724    let cover_banner = build_header_banner_xml(
725        "rId1",
726        &BannerOpts {
727            bg_color: "C5922E",
728            banner_width: 7772400,
729            banner_height: 914400,
730            logo_width: 2011680,
731            logo_height: 438912,
732            logo_x_offset: 295125,
733            logo_y_offset: 237744,
734        },
735    );
736    doc.set_raw_header_with_images(
737        cover_banner,
738        &[("rId1", &logo_img, "logo.png")],
739        rdocx_oxml::header_footer::HdrFtrType::First,
740    );
741    doc.set_margins(
742        Length::twips(2292),
743        Length::twips(1440),
744        Length::twips(1440),
745        Length::twips(1440),
746    );
747    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
748    doc.set_footer("Tensorbee — Confidential");
749
750    // Custom styles
751    doc.add_style(
752        StyleBuilder::paragraph("ProposalTitle", "Proposal Title")
753            .based_on("Normal")
754            .run_properties({
755                let mut rpr = rdocx_oxml::properties::CT_RPr::default();
756                rpr.bold = Some(true);
757                rpr.font_ascii = Some("Georgia".to_string());
758                rpr.font_hansi = Some("Georgia".to_string());
759                rpr.sz = Some(rdocx_oxml::HalfPoint(56)); // half-points → 28pt
760                rpr.color = Some("1B2A4A".to_string());
761                rpr
762            }),
763    );
764
765    // ── Cover Page ──
766    for _ in 0..4 {
767        doc.add_paragraph("");
768    }
769    doc.add_paragraph("AI Platform Modernization")
770        .style("ProposalTitle")
771        .alignment(Alignment::Center);
772    doc.add_paragraph("")
773        .alignment(Alignment::Center)
774        .border_bottom(BorderStyle::Single, 8, "C5922E");
775    {
776        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
777        p.add_run("Prepared for Global Financial Corp.")
778            .size(14.0)
779            .color("666666")
780            .italic(true);
781    }
782    {
783        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
784        p.add_run("Prepared by: Walter White, CEO — Tensorbee")
785            .size(12.0)
786            .color("888888");
787    }
788    {
789        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
790        p.add_run("February 22, 2026").size(12.0).color("888888");
791    }
792
793    // ── TOC ──
794    doc.add_paragraph("").page_break_before(true);
795    doc.add_paragraph("Table of Contents").style("Heading1");
796    // We'll add a manual-style TOC since the insert_toc goes at a specific position
797    doc.insert_toc(doc.content_count(), 2);
798
799    // ── Executive Summary ──
800    doc.add_paragraph("").page_break_before(true);
801    doc.add_paragraph("Executive Summary").style("Heading1");
802    doc.add_paragraph(
803        "Tensorbee proposes a comprehensive modernization of Global Financial Corp's AI platform, \
804         transitioning from legacy batch-processing systems to a real-time inference architecture. \
805         This transformation will reduce model deployment time from weeks to hours, improve \
806         prediction accuracy by an estimated 23%, and deliver $4.2M in annual operational savings.",
807    )
808    .first_line_indent(Length::inches(0.3));
809    doc.add_paragraph(
810        "The project spans three phases over 18 months, with a total investment of $2.8M. \
811         Tensorbee brings deep expertise in ML infrastructure, having successfully completed \
812         similar transformations for three Fortune 500 financial institutions.",
813    )
814    .first_line_indent(Length::inches(0.3));
815
816    // Key metrics table
817    doc.add_paragraph("");
818    {
819        let mut tbl = doc
820            .add_table(5, 2)
821            .borders(BorderStyle::Single, 4, "1B2A4A")
822            .width_pct(80.0);
823        tbl.cell(0, 0).unwrap().set_text("Key Metric");
824        tbl.cell(0, 0).unwrap().shading("1B2A4A");
825        tbl.cell(0, 1).unwrap().set_text("Value");
826        tbl.cell(0, 1).unwrap().shading("1B2A4A");
827        let rows = [
828            ("Total Investment", "$2.8M"),
829            ("Annual Savings", "$4.2M"),
830            ("ROI (Year 1)", "150%"),
831            ("Timeline", "18 months"),
832        ];
833        for (i, (k, v)) in rows.iter().enumerate() {
834            tbl.cell(i + 1, 0).unwrap().set_text(k);
835            if i % 2 == 0 {
836                tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
837            }
838            tbl.cell(i + 1, 1).unwrap().set_text(v);
839            if i % 2 == 0 {
840                tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
841            }
842        }
843    }
844
845    // ── Problem Statement ──
846    doc.add_paragraph("").page_break_before(true);
847    doc.add_paragraph("Problem Statement").style("Heading1");
848    doc.add_paragraph(
849        "Global Financial Corp's current AI infrastructure faces three critical challenges:",
850    );
851    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);
852    doc.add_numbered_list_item("Scalability Constraints — The batch processing architecture cannot handle real-time inference demands during peak trading hours.", 0);
853    doc.add_numbered_list_item("Technical Debt — Legacy Python 2.7 codebases and manual deployment scripts create reliability risks and slow iteration speed.", 0);
854
855    // ── Proposed Solution ──
856    doc.add_paragraph("").page_break_before(true);
857    doc.add_paragraph("Proposed Solution").style("Heading1");
858
859    doc.add_paragraph("Phase 1: Foundation (Months 1-6)")
860        .style("Heading2");
861    doc.add_bullet_list_item("Deploy Kubernetes-based ML serving infrastructure", 0);
862    doc.add_bullet_list_item("Implement CI/CD pipeline for model deployment", 0);
863    doc.add_bullet_list_item("Migrate top 5 production models to new platform", 0);
864
865    doc.add_paragraph("Phase 2: Expansion (Months 7-12)")
866        .style("Heading2");
867    doc.add_bullet_list_item(
868        "Real-time feature engineering pipeline (Apache Kafka + Flink)",
869        0,
870    );
871    doc.add_bullet_list_item("A/B testing framework for model variants", 0);
872    doc.add_bullet_list_item("Automated model monitoring and drift detection", 0);
873
874    doc.add_paragraph("Phase 3: Optimization (Months 13-18)")
875        .style("Heading2");
876    doc.add_bullet_list_item("GPU inference optimization (TensorRT/ONNX Runtime)", 0);
877    doc.add_bullet_list_item("Multi-region deployment for latency reduction", 0);
878    doc.add_bullet_list_item(
879        "Self-service model deployment portal for data science teams",
880        0,
881    );
882
883    // ── Budget ──
884    doc.add_paragraph("Budget Breakdown").style("Heading1");
885    {
886        let mut tbl = doc
887            .add_table(6, 3)
888            .borders(BorderStyle::Single, 4, "1B2A4A")
889            .width_pct(100.0);
890        tbl.cell(0, 0).unwrap().set_text("Category");
891        tbl.cell(0, 0).unwrap().shading("1B2A4A");
892        tbl.cell(0, 1).unwrap().set_text("Cost");
893        tbl.cell(0, 1).unwrap().shading("1B2A4A");
894        tbl.cell(0, 2).unwrap().set_text("Notes");
895        tbl.cell(0, 2).unwrap().shading("1B2A4A");
896        let items = [
897            (
898                "Infrastructure",
899                "$450,000",
900                "Cloud compute + GPU instances",
901            ),
902            ("Engineering Services", "$1,600,000", "12 FTE x 18 months"),
903            (
904                "Software Licenses",
905                "$350,000",
906                "Kafka, monitoring, CI/CD tools",
907            ),
908            (
909                "Training & Enablement",
910                "$200,000",
911                "Team training, documentation",
912            ),
913            ("Contingency (10%)", "$200,000", "Risk buffer"),
914        ];
915        for (i, (cat, cost, note)) in items.iter().enumerate() {
916            tbl.cell(i + 1, 0).unwrap().set_text(cat);
917            tbl.cell(i + 1, 1).unwrap().set_text(cost);
918            tbl.cell(i + 1, 2).unwrap().set_text(note);
919            if i % 2 == 0 {
920                tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
921                tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
922                tbl.cell(i + 1, 2).unwrap().shading("F4F1EB");
923            }
924        }
925    }
926
927    // ── Closing ──
928    doc.add_paragraph("");
929    doc.add_paragraph("Next Steps").style("Heading1");
930    doc.add_numbered_list_item(
931        "Schedule technical deep-dive with GFC engineering team (Week 1)",
932        0,
933    );
934    doc.add_numbered_list_item("Finalize scope and sign SOW (Week 2-3)", 0);
935    doc.add_numbered_list_item("Kick off Phase 1 with joint planning session (Week 4)", 0);
936
937    doc.add_paragraph("");
938    {
939        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
940        p.add_run("Walter White")
941            .bold(true)
942            .size(14.0)
943            .color("1B2A4A");
944    }
945    {
946        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
947        p.add_run("CEO, Tensorbee | walter@tensorbee.com")
948            .size(10.0)
949            .color("888888");
950    }
951
952    doc
953}
954
955// =============================================================================
956// 3. QUOTE / BILL OF MATERIALS — Teal + orange scheme
957// =============================================================================
958fn generate_quote(_samples_dir: &Path) -> Document {
959    let mut doc = Document::new();
960
961    // Colors: Teal #008B8B, Dark #1A3C3C, Orange #E07020, Light #F0F8F8
962    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
963    doc.set_margins(
964        Length::inches(0.75),
965        Length::inches(0.75),
966        Length::inches(0.75),
967        Length::inches(0.75),
968    );
969    doc.set_title("Quotation QT-2026-0147");
970    doc.set_author("Walter White");
971    doc.set_keywords("quote, BOM, Tensorbee");
972
973    doc.set_header("Tensorbee — Quotation");
974    doc.set_footer("QT-2026-0147 | Page");
975
976    // ── Header Block ──
977    {
978        let mut p = doc.add_paragraph("").alignment(Alignment::Left);
979        p.add_run("TENSORBEE")
980            .bold(true)
981            .size(28.0)
982            .color("008B8B")
983            .font("Helvetica");
984    }
985    doc.add_paragraph("123 Innovation Drive, Suite 400")
986        .alignment(Alignment::Left);
987    doc.add_paragraph("San Francisco, CA 94105 | +1 (415) 555-0199")
988        .alignment(Alignment::Left);
989    doc.add_paragraph("")
990        .border_bottom(BorderStyle::Single, 8, "008B8B");
991
992    // Quote meta
993    doc.add_paragraph("");
994    {
995        let mut tbl = doc.add_table(4, 4).width_pct(100.0);
996        tbl.cell(0, 0).unwrap().set_text("QUOTATION");
997        tbl.cell(0, 0).unwrap().shading("008B8B").grid_span(2);
998        tbl.cell(0, 2).unwrap().set_text("DATE");
999        tbl.cell(0, 2).unwrap().shading("008B8B");
1000        tbl.cell(0, 3).unwrap().set_text("VALID UNTIL");
1001        tbl.cell(0, 3).unwrap().shading("008B8B");
1002        tbl.cell(1, 0).unwrap().set_text("Quote #:");
1003        tbl.cell(1, 1).unwrap().set_text("QT-2026-0147");
1004        tbl.cell(1, 2).unwrap().set_text("Feb 22, 2026");
1005        tbl.cell(1, 3).unwrap().set_text("Mar 22, 2026");
1006        tbl.cell(2, 0).unwrap().set_text("Prepared by:");
1007        tbl.cell(2, 1).unwrap().set_text("Walter White");
1008        tbl.cell(2, 2).unwrap().set_text("Payment:");
1009        tbl.cell(2, 3).unwrap().set_text("Net 30");
1010        tbl.cell(3, 0).unwrap().set_text("Customer:");
1011        tbl.cell(3, 1).unwrap().set_text("Meridian Dynamics LLC");
1012        tbl.cell(3, 2).unwrap().set_text("Currency:");
1013        tbl.cell(3, 3).unwrap().set_text("USD");
1014    }
1015
1016    doc.add_paragraph("");
1017
1018    // ── Bill of Materials ──
1019    {
1020        let mut p = doc.add_paragraph("");
1021        p.add_run("Bill of Materials")
1022            .bold(true)
1023            .size(16.0)
1024            .color("1A3C3C");
1025    }
1026    doc.add_paragraph("");
1027
1028    {
1029        let mut tbl = doc
1030            .add_table(10, 6)
1031            .borders(BorderStyle::Single, 2, "008B8B")
1032            .width_pct(100.0)
1033            .layout_fixed();
1034        // Headers
1035        let hdrs = [
1036            "#",
1037            "Part Number",
1038            "Description",
1039            "Qty",
1040            "Unit Price",
1041            "Total",
1042        ];
1043        for (c, h) in hdrs.iter().enumerate() {
1044            tbl.cell(0, c).unwrap().set_text(h);
1045            tbl.cell(0, c).unwrap().shading("008B8B");
1046        }
1047
1048        let items: Vec<(&str, &str, &str, &str, &str)> = vec![
1049            (
1050                "TB-GPU-A100",
1051                "NVIDIA A100 80GB GPU",
1052                "4",
1053                "$12,500.00",
1054                "$50,000.00",
1055            ),
1056            (
1057                "TB-SRV-R750",
1058                "Dell R750xa Server Chassis",
1059                "2",
1060                "$8,200.00",
1061                "$16,400.00",
1062            ),
1063            (
1064                "TB-RAM-256G",
1065                "256GB DDR5 ECC Memory Module",
1066                "8",
1067                "$890.00",
1068                "$7,120.00",
1069            ),
1070            (
1071                "TB-SSD-3840",
1072                "3.84TB NVMe U.2 SSD",
1073                "8",
1074                "$1,150.00",
1075                "$9,200.00",
1076            ),
1077            (
1078                "TB-NET-CX7",
1079                "ConnectX-7 200GbE NIC",
1080                "4",
1081                "$1,800.00",
1082                "$7,200.00",
1083            ),
1084            (
1085                "TB-SW-48P",
1086                "48-Port 100GbE Switch",
1087                "1",
1088                "$22,000.00",
1089                "$22,000.00",
1090            ),
1091            (
1092                "TB-CAB-RACK",
1093                "42U Server Rack + PDU",
1094                "1",
1095                "$4,500.00",
1096                "$4,500.00",
1097            ),
1098            (
1099                "TB-SVC-INST",
1100                "Installation & Configuration",
1101                "1",
1102                "$8,500.00",
1103                "$8,500.00",
1104            ),
1105            (
1106                "TB-SVC-SUPP",
1107                "3-Year Premium Support",
1108                "1",
1109                "$15,000.00",
1110                "$15,000.00",
1111            ),
1112        ];
1113
1114        for (i, (pn, desc, qty, unit, total)) in items.iter().enumerate() {
1115            let row = i + 1;
1116            tbl.cell(row, 0).unwrap().set_text(&format!("{}", i + 1));
1117            tbl.cell(row, 1).unwrap().set_text(pn);
1118            tbl.cell(row, 2).unwrap().set_text(desc);
1119            tbl.cell(row, 3).unwrap().set_text(qty);
1120            tbl.cell(row, 4).unwrap().set_text(unit);
1121            tbl.cell(row, 5).unwrap().set_text(total);
1122            if i % 2 == 0 {
1123                for c in 0..6 {
1124                    tbl.cell(row, c).unwrap().shading("F0F8F8");
1125                }
1126            }
1127        }
1128    }
1129
1130    doc.add_paragraph("");
1131
1132    // ── Totals ──
1133    {
1134        let mut tbl = doc
1135            .add_table(4, 2)
1136            .borders(BorderStyle::Single, 2, "008B8B")
1137            .width(Length::inches(3.5))
1138            .alignment(Alignment::Right);
1139        tbl.cell(0, 0).unwrap().set_text("Subtotal");
1140        tbl.cell(0, 1).unwrap().set_text("$139,920.00");
1141        tbl.cell(1, 0).unwrap().set_text("Shipping & Handling");
1142        tbl.cell(1, 1).unwrap().set_text("$2,500.00");
1143        tbl.cell(2, 0).unwrap().set_text("Tax (8.625%)");
1144        tbl.cell(2, 1).unwrap().set_text("$12,068.10");
1145        tbl.cell(3, 0).unwrap().set_text("TOTAL");
1146        tbl.cell(3, 0).unwrap().shading("E07020");
1147        tbl.cell(3, 1).unwrap().set_text("$154,488.10");
1148        tbl.cell(3, 1).unwrap().shading("E07020");
1149    }
1150
1151    doc.add_paragraph("");
1152
1153    // ── Terms & Conditions ──
1154    {
1155        let mut p = doc.add_paragraph("");
1156        p.add_run("Terms & Conditions")
1157            .bold(true)
1158            .size(14.0)
1159            .color("1A3C3C");
1160    }
1161    doc.add_numbered_list_item(
1162        "This quotation is valid for 30 calendar days from the date of issue.",
1163        0,
1164    );
1165    doc.add_numbered_list_item(
1166        "All prices are in USD and exclusive of applicable taxes unless stated.",
1167        0,
1168    );
1169    doc.add_numbered_list_item("Standard lead time is 4-6 weeks from PO receipt.", 0);
1170    doc.add_numbered_list_item(
1171        "Payment terms: Net 30 from invoice date. 2% discount for payment within 10 days.",
1172        0,
1173    );
1174    doc.add_numbered_list_item(
1175        "Warranty: 3-year manufacturer warranty on all hardware components.",
1176        0,
1177    );
1178    doc.add_numbered_list_item(
1179        "Returns subject to 15% restocking fee if initiated after 14 days.",
1180        0,
1181    );
1182
1183    doc.add_paragraph("");
1184    doc.add_paragraph("")
1185        .border_bottom(BorderStyle::Single, 4, "008B8B");
1186    doc.add_paragraph("");
1187    {
1188        let mut p = doc.add_paragraph("");
1189        p.add_run("Accepted by: ").bold(true);
1190        p.add_run("___________________________ Date: ___________");
1191    }
1192    {
1193        let mut p = doc.add_paragraph("");
1194        p.add_run("Print Name: ").bold(true);
1195        p.add_run("___________________________ Title: ___________");
1196    }
1197
1198    doc
1199}
1200
1201// =============================================================================
1202// 4. INVOICE — Crimson + charcoal scheme
1203// =============================================================================
1204fn generate_invoice(_samples_dir: &Path) -> Document {
1205    let mut doc = Document::new();
1206
1207    // Colors: Crimson #B22222, Charcoal #333333, Light #FAF0F0
1208    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1209    doc.set_margins(
1210        Length::inches(0.75),
1211        Length::inches(0.75),
1212        Length::inches(0.75),
1213        Length::inches(0.75),
1214    );
1215    doc.set_title("Invoice INV-2026-0392");
1216    doc.set_author("Walter White");
1217    doc.set_keywords("invoice, Tensorbee");
1218
1219    doc.set_footer("Tensorbee — Thank you for your business!");
1220
1221    // ── Company & Invoice Header ──
1222    {
1223        let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1224        // Company name left, INVOICE right
1225        {
1226            let mut cell = tbl.cell(0, 0).unwrap();
1227            let mut p = cell.add_paragraph("");
1228            p.add_run("TENSORBEE")
1229                .bold(true)
1230                .size(32.0)
1231                .color("B22222")
1232                .font("Helvetica");
1233        }
1234        {
1235            let mut cell = tbl.cell(0, 1).unwrap();
1236            let mut p = cell.add_paragraph("");
1237            p.add_run("INVOICE").bold(true).size(32.0).color("333333");
1238        }
1239        tbl.cell(1, 0)
1240            .unwrap()
1241            .set_text("123 Innovation Drive, Suite 400");
1242        tbl.cell(1, 1).unwrap().set_text("Invoice #: INV-2026-0392");
1243        tbl.cell(2, 0).unwrap().set_text("San Francisco, CA 94105");
1244        tbl.cell(2, 1).unwrap().set_text("Date: February 22, 2026");
1245        tbl.cell(3, 0).unwrap().set_text("walter@tensorbee.com");
1246        tbl.cell(3, 1).unwrap().set_text("Due Date: March 24, 2026");
1247    }
1248
1249    doc.add_paragraph("")
1250        .border_bottom(BorderStyle::Thick, 8, "B22222");
1251    doc.add_paragraph("");
1252
1253    // ── Bill To / Ship To ──
1254    {
1255        let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1256        tbl.cell(0, 0).unwrap().set_text("BILL TO");
1257        tbl.cell(0, 0).unwrap().shading("B22222");
1258        tbl.cell(0, 1).unwrap().set_text("SHIP TO");
1259        tbl.cell(0, 1).unwrap().shading("B22222");
1260        tbl.cell(1, 0).unwrap().set_text("Meridian Dynamics LLC");
1261        tbl.cell(1, 1).unwrap().set_text("Meridian Dynamics LLC");
1262        tbl.cell(2, 0).unwrap().set_text("456 Enterprise Blvd");
1263        tbl.cell(2, 1).unwrap().set_text("Attn: Server Room B");
1264        tbl.cell(3, 0).unwrap().set_text("Austin, TX 78701");
1265        tbl.cell(3, 1)
1266            .unwrap()
1267            .set_text("456 Enterprise Blvd, Austin, TX 78701");
1268    }
1269
1270    doc.add_paragraph("");
1271
1272    // ── Line Items ──
1273    {
1274        let mut tbl = doc
1275            .add_table(8, 5)
1276            .borders(BorderStyle::Single, 2, "B22222")
1277            .width_pct(100.0);
1278        let hdrs = ["Description", "Qty", "Unit Price", "Tax", "Amount"];
1279        for (c, h) in hdrs.iter().enumerate() {
1280            tbl.cell(0, c).unwrap().set_text(h);
1281            tbl.cell(0, c).unwrap().shading("B22222");
1282        }
1283        let items = [
1284            (
1285                "ML Infrastructure Setup — Phase 1",
1286                "1",
1287                "$45,000.00",
1288                "$3,881.25",
1289                "$48,881.25",
1290            ),
1291            (
1292                "Data Pipeline Development (160 hrs)",
1293                "160",
1294                "$225.00",
1295                "$3,105.00",
1296                "$39,105.00",
1297            ),
1298            (
1299                "GPU Cluster Configuration",
1300                "1",
1301                "$12,000.00",
1302                "$1,035.00",
1303                "$13,035.00",
1304            ),
1305            (
1306                "API Gateway Implementation",
1307                "1",
1308                "$18,500.00",
1309                "$1,595.63",
1310                "$20,095.63",
1311            ),
1312            (
1313                "Load Testing & QA (80 hrs)",
1314                "80",
1315                "$195.00",
1316                "$1,345.50",
1317                "$16,945.50",
1318            ),
1319            (
1320                "Documentation & Training",
1321                "1",
1322                "$8,000.00",
1323                "$690.00",
1324                "$8,690.00",
1325            ),
1326            (
1327                "Project Management (3 months)",
1328                "3",
1329                "$6,500.00",
1330                "$1,679.63",
1331                "$21,179.63",
1332            ),
1333        ];
1334        for (i, (desc, qty, unit, tax, amt)) in items.iter().enumerate() {
1335            let r = i + 1;
1336            tbl.cell(r, 0).unwrap().set_text(desc);
1337            tbl.cell(r, 1).unwrap().set_text(qty);
1338            tbl.cell(r, 2).unwrap().set_text(unit);
1339            tbl.cell(r, 3).unwrap().set_text(tax);
1340            tbl.cell(r, 4).unwrap().set_text(amt);
1341            if i % 2 == 0 {
1342                for c in 0..5 {
1343                    tbl.cell(r, c).unwrap().shading("FAF0F0");
1344                }
1345            }
1346        }
1347    }
1348
1349    doc.add_paragraph("");
1350
1351    // ── Totals ──
1352    {
1353        let mut tbl = doc
1354            .add_table(4, 2)
1355            .borders(BorderStyle::Single, 2, "B22222")
1356            .width(Length::inches(3.0))
1357            .alignment(Alignment::Right);
1358        tbl.cell(0, 0).unwrap().set_text("Subtotal");
1359        tbl.cell(0, 1).unwrap().set_text("$154,600.00");
1360        tbl.cell(1, 0).unwrap().set_text("Tax (8.625%)");
1361        tbl.cell(1, 1).unwrap().set_text("$13,334.25");
1362        tbl.cell(2, 0).unwrap().set_text("Discount (5%)");
1363        tbl.cell(2, 1).unwrap().set_text("-$7,730.00");
1364        tbl.cell(3, 0).unwrap().set_text("AMOUNT DUE");
1365        tbl.cell(3, 0).unwrap().shading("B22222");
1366        tbl.cell(3, 1).unwrap().set_text("$160,204.25");
1367        tbl.cell(3, 1).unwrap().shading("B22222");
1368    }
1369
1370    doc.add_paragraph("");
1371    doc.add_paragraph("");
1372
1373    // ── Payment Details ──
1374    {
1375        let mut p = doc.add_paragraph("");
1376        p.add_run("Payment Details")
1377            .bold(true)
1378            .size(14.0)
1379            .color("333333");
1380    }
1381    doc.add_paragraph("Bank: Silicon Valley Bank")
1382        .indent_left(Length::inches(0.3));
1383    doc.add_paragraph("Account: Tensorbee Inc. — 0847-2953-1120")
1384        .indent_left(Length::inches(0.3));
1385    doc.add_paragraph("Routing: 121140399")
1386        .indent_left(Length::inches(0.3));
1387    doc.add_paragraph("Swift: SVBKUS6S")
1388        .indent_left(Length::inches(0.3));
1389
1390    doc.add_paragraph("");
1391
1392    doc.add_paragraph("Please include invoice number INV-2026-0392 in the payment reference.")
1393        .shading("FAF0F0")
1394        .border_all(BorderStyle::Single, 2, "B22222");
1395
1396    doc
1397}
1398
1399// =============================================================================
1400// 5. REPORT — Forest green + earth tones, with images & hierarchical sections
1401// =============================================================================
1402fn generate_report(_samples_dir: &Path) -> Document {
1403    let mut doc = Document::new();
1404
1405    // Colors: Forest #2D5016, Sage #6B8E23, Earth #8B7355, Cream #FFFAF0
1406    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1407    doc.set_margins(
1408        Length::inches(1.0),
1409        Length::inches(1.0),
1410        Length::inches(1.0),
1411        Length::inches(1.0),
1412    );
1413    doc.set_title("Q4 2025 Environmental Impact Report");
1414    doc.set_author("Walter White");
1415    doc.set_subject("Quarterly Environmental Report");
1416    doc.set_keywords("environment, sustainability, report, Tensorbee");
1417
1418    doc.set_different_first_page(true);
1419    doc.set_first_page_header("");
1420    doc.set_header("Tensorbee — Q4 2025 Environmental Impact Report");
1421    doc.set_footer("CONFIDENTIAL — Page");
1422
1423    // ── Cover ──
1424    let cover_bg = create_sample_png(612, 792, [20, 50, 15]);
1425    doc.add_background_image(&cover_bg, "report_cover.png");
1426
1427    for _ in 0..5 {
1428        doc.add_paragraph("");
1429    }
1430    {
1431        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1432        p.add_run("Q4 2025")
1433            .bold(true)
1434            .size(48.0)
1435            .color("FFFFFF")
1436            .font("Georgia");
1437    }
1438    {
1439        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1440        p.add_run("Environmental Impact Report")
1441            .size(24.0)
1442            .color("90EE90")
1443            .font("Georgia")
1444            .italic(true);
1445    }
1446    doc.add_paragraph("");
1447    {
1448        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1449        p.add_run("Tensorbee — Sustainability Division")
1450            .size(14.0)
1451            .color("C0C0C0");
1452    }
1453    {
1454        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1455        p.add_run("Prepared by Walter White, Chief Sustainability Officer")
1456            .size(11.0)
1457            .color("AAAAAA");
1458    }
1459
1460    // ── TOC ──
1461    doc.add_paragraph("").page_break_before(true);
1462    doc.insert_toc(doc.content_count(), 3);
1463
1464    // ── Section 1: Executive Overview ──
1465    doc.add_paragraph("").page_break_before(true);
1466    doc.add_paragraph("Executive Overview").style("Heading1");
1467    doc.add_paragraph(
1468        "This report presents Tensorbee's environmental performance for Q4 2025. Our \
1469         sustainability initiatives have yielded a 34% reduction in carbon emissions compared \
1470         to Q4 2024, exceeding our target of 25%. Key achievements include the transition to \
1471         100% renewable energy in our primary data centers and the launch of our carbon offset \
1472         marketplace.",
1473    )
1474    .first_line_indent(Length::inches(0.3));
1475
1476    // Image: performance chart
1477    let chart_img = create_chart_png(400, 200);
1478    doc.add_paragraph("");
1479    doc.add_picture(
1480        &chart_img,
1481        "performance_chart.png",
1482        Length::inches(5.0),
1483        Length::inches(2.5),
1484    )
1485    .alignment(Alignment::Center);
1486    {
1487        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1488        p.add_run("Figure 1: Quarterly Carbon Emissions (tonnes CO2e)")
1489            .italic(true)
1490            .size(9.0)
1491            .color("666666");
1492    }
1493
1494    // ── Section 2: Energy Consumption ──
1495    doc.add_paragraph("").page_break_before(true);
1496    doc.add_paragraph("Energy Consumption").style("Heading1");
1497
1498    doc.add_paragraph("Data Center Operations")
1499        .style("Heading2");
1500    doc.add_paragraph(
1501        "Our three primary data centers consumed a combined 4.2 GWh in Q4 2025, \
1502         a 12% reduction from Q3 2025 driven by improved cooling efficiency and \
1503         server consolidation.",
1504    );
1505
1506    // Data table
1507    {
1508        let mut tbl = doc
1509            .add_table(5, 4)
1510            .borders(BorderStyle::Single, 4, "2D5016")
1511            .width_pct(100.0);
1512        let hdrs = ["Data Center", "Capacity (MW)", "Usage (GWh)", "PUE"];
1513        for (c, h) in hdrs.iter().enumerate() {
1514            tbl.cell(0, c).unwrap().set_text(h);
1515            tbl.cell(0, c).unwrap().shading("2D5016");
1516        }
1517        let rows = [
1518            ("San Francisco (Primary)", "3.2", "1.8", "1.12"),
1519            ("Dublin (EU)", "2.1", "1.2", "1.18"),
1520            ("Singapore (APAC)", "1.8", "1.2", "1.24"),
1521            ("TOTAL", "7.1", "4.2", "1.17 avg"),
1522        ];
1523        for (i, (dc, cap, use_, pue)) in rows.iter().enumerate() {
1524            tbl.cell(i + 1, 0).unwrap().set_text(dc);
1525            tbl.cell(i + 1, 1).unwrap().set_text(cap);
1526            tbl.cell(i + 1, 2).unwrap().set_text(use_);
1527            tbl.cell(i + 1, 3).unwrap().set_text(pue);
1528            if i == 3 {
1529                for c in 0..4 {
1530                    tbl.cell(i + 1, c).unwrap().shading("E2EFDA");
1531                }
1532            }
1533        }
1534    }
1535
1536    doc.add_paragraph("");
1537
1538    doc.add_paragraph("Renewable Energy Mix").style("Heading2");
1539    doc.add_paragraph("Breakdown of energy sources across all facilities:");
1540
1541    doc.add_bullet_list_item("Solar PV: 42% (1.76 GWh)", 0);
1542    doc.add_bullet_list_item("Wind Power Purchase Agreements: 38% (1.60 GWh)", 0);
1543    doc.add_bullet_list_item("Hydroelectric: 12% (0.50 GWh)", 0);
1544    doc.add_bullet_list_item("Grid (non-renewable): 8% (0.34 GWh)", 0);
1545
1546    // ── Section 3: Water & Waste ──
1547    doc.add_paragraph("Water & Waste Management")
1548        .style("Heading1");
1549
1550    doc.add_paragraph("Water Usage").style("Heading2");
1551    doc.add_paragraph(
1552        "Total water consumption was 12.4 million gallons, a 15% reduction from Q3. \
1553         Our closed-loop cooling systems now recycle 78% of water used in cooling operations.",
1554    );
1555
1556    doc.add_paragraph("Waste Reduction").style("Heading2");
1557    doc.add_paragraph("E-waste management results for Q4:")
1558        .keep_with_next(true);
1559    {
1560        let mut tbl = doc
1561            .add_table(4, 3)
1562            .borders(BorderStyle::Single, 2, "6B8E23")
1563            .width_pct(80.0);
1564        tbl.cell(0, 0).unwrap().set_text("Category");
1565        tbl.cell(0, 0).unwrap().shading("6B8E23");
1566        tbl.cell(0, 1).unwrap().set_text("Weight (kg)");
1567        tbl.cell(0, 1).unwrap().shading("6B8E23");
1568        tbl.cell(0, 2).unwrap().set_text("Recycled %");
1569        tbl.cell(0, 2).unwrap().shading("6B8E23");
1570        let rows = [
1571            ("Server Hardware", "2,450", "94%"),
1572            ("Networking Equipment", "820", "91%"),
1573            ("Storage Media", "340", "99%"),
1574        ];
1575        for (i, (cat, wt, pct)) in rows.iter().enumerate() {
1576            tbl.cell(i + 1, 0).unwrap().set_text(cat);
1577            tbl.cell(i + 1, 1).unwrap().set_text(wt);
1578            tbl.cell(i + 1, 2).unwrap().set_text(pct);
1579        }
1580    }
1581
1582    // ── Section 4: Initiatives ──
1583    doc.add_paragraph("").page_break_before(true);
1584    doc.add_paragraph("Q1 2026 Initiatives").style("Heading1");
1585
1586    doc.add_paragraph("Planned Programs").style("Heading2");
1587    doc.add_numbered_list_item(
1588        "Deploy on-site battery storage at SF data center (2 MWh capacity)",
1589        0,
1590    );
1591    doc.add_numbered_list_item("Pilot immersion cooling in Dublin facility", 0);
1592    doc.add_numbered_list_item("Launch employee commute carbon offset program", 0);
1593    doc.add_numbered_list_item("Achieve ISO 14001 certification for Singapore facility", 0);
1594
1595    doc.add_paragraph("Investment Targets").style("Heading2");
1596    doc.add_paragraph("Sustainability CapEx allocation for FY2026:")
1597        .keep_with_next(true);
1598    {
1599        let mut tbl = doc
1600            .add_table(5, 2)
1601            .borders(BorderStyle::Single, 4, "2D5016");
1602        tbl.cell(0, 0).unwrap().set_text("Initiative");
1603        tbl.cell(0, 0).unwrap().shading("2D5016");
1604        tbl.cell(0, 1).unwrap().set_text("Budget");
1605        tbl.cell(0, 1).unwrap().shading("2D5016");
1606        let rows = [
1607            ("Battery Storage", "$1.2M"),
1608            ("Immersion Cooling Pilot", "$800K"),
1609            ("Solar Panel Expansion", "$2.1M"),
1610            ("Carbon Credits", "$500K"),
1611        ];
1612        for (i, (init, budget)) in rows.iter().enumerate() {
1613            tbl.cell(i + 1, 0).unwrap().set_text(init);
1614            tbl.cell(i + 1, 1).unwrap().set_text(budget);
1615            if i % 2 == 0 {
1616                tbl.cell(i + 1, 0).unwrap().shading("FFFAF0");
1617                tbl.cell(i + 1, 1).unwrap().shading("FFFAF0");
1618            }
1619        }
1620    }
1621
1622    // Image: sustainability roadmap
1623    let roadmap_img = create_sample_png(500, 100, [40, 80, 30]);
1624    doc.add_paragraph("");
1625    doc.add_picture(
1626        &roadmap_img,
1627        "roadmap.png",
1628        Length::inches(6.0),
1629        Length::inches(1.2),
1630    )
1631    .alignment(Alignment::Center);
1632    {
1633        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1634        p.add_run("Figure 2: 2026 Sustainability Roadmap")
1635            .italic(true)
1636            .size(9.0)
1637            .color("666666");
1638    }
1639
1640    doc.add_paragraph("");
1641    doc.add_paragraph(
1642        "For questions about this report, contact Walter White at sustainability@tensorbee.com.",
1643    )
1644    .shading("FFFAF0")
1645    .border_all(BorderStyle::Single, 2, "2D5016");
1646
1647    doc
1648}
1649
1650// =============================================================================
1651// 6. LETTER — Slate blue + silver scheme
1652// =============================================================================
1653fn generate_letter(_samples_dir: &Path) -> Document {
1654    let mut doc = Document::new();
1655
1656    // Colors: Slate #4A5568, Blue accent #3182CE
1657    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1658    doc.set_margins(
1659        Length::inches(1.25),
1660        Length::inches(1.25),
1661        Length::inches(1.25),
1662        Length::inches(1.25),
1663    );
1664    doc.set_title("Business Letter");
1665    doc.set_author("Walter White");
1666
1667    // Banner header using raw XML
1668    let logo_img = create_logo_png(220, 48);
1669    let banner = build_header_banner_xml(
1670        "rId1",
1671        &BannerOpts {
1672            bg_color: "4A5568",
1673            banner_width: 7772400,
1674            banner_height: 731520, // ~0.8"
1675            logo_width: 2011680,
1676            logo_height: 438912,
1677            logo_x_offset: 295125,
1678            logo_y_offset: 146304,
1679        },
1680    );
1681    doc.set_raw_header_with_images(
1682        banner,
1683        &[("rId1", &logo_img, "logo.png")],
1684        rdocx_oxml::header_footer::HdrFtrType::Default,
1685    );
1686    doc.set_margins(
1687        Length::twips(2000),
1688        Length::twips(1800),
1689        Length::twips(1440),
1690        Length::twips(1800),
1691    );
1692    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
1693
1694    // Footer with contact
1695    doc.set_footer(
1696        "Tensorbee | 123 Innovation Drive, Suite 400 | San Francisco, CA 94105 | tensorbee.com",
1697    );
1698
1699    // ── Sender Address ──
1700    doc.add_paragraph("Walter White");
1701    doc.add_paragraph("Chief Executive Officer");
1702    doc.add_paragraph("Tensorbee");
1703    doc.add_paragraph("123 Innovation Drive, Suite 400");
1704    doc.add_paragraph("San Francisco, CA 94105");
1705    doc.add_paragraph("");
1706    doc.add_paragraph("February 22, 2026");
1707
1708    doc.add_paragraph("");
1709
1710    // ── Recipient ──
1711    doc.add_paragraph("Ms. Sarah Chen");
1712    doc.add_paragraph("VP of Engineering");
1713    doc.add_paragraph("Meridian Dynamics LLC");
1714    doc.add_paragraph("456 Enterprise Boulevard");
1715    doc.add_paragraph("Austin, TX 78701");
1716
1717    doc.add_paragraph("");
1718
1719    // ── Subject ──
1720    {
1721        let mut p = doc.add_paragraph("");
1722        p.add_run("Re: Strategic Technology Partnership — Phase 2 Expansion")
1723            .bold(true);
1724    }
1725
1726    doc.add_paragraph("");
1727
1728    // ── Body ──
1729    doc.add_paragraph("Dear Ms. Chen,");
1730    doc.add_paragraph("");
1731
1732    doc.add_paragraph(
1733        "I am writing to express Tensorbee's enthusiasm for expanding our strategic technology \
1734         partnership with Meridian Dynamics. Following the successful completion of Phase 1, \
1735         which delivered a 40% improvement in your ML inference pipeline throughput, we believe \
1736         the foundation is firmly established for an ambitious Phase 2 engagement.",
1737    )
1738    .first_line_indent(Length::inches(0.5));
1739
1740    doc.add_paragraph(
1741        "During our recent executive review, your team highlighted three priority areas for \
1742         the next phase of collaboration:",
1743    )
1744    .first_line_indent(Length::inches(0.5));
1745
1746    doc.add_numbered_list_item(
1747        "Real-time anomaly detection for your financial transaction monitoring system, \
1748         targeting sub-100ms latency at 50,000 transactions per second.",
1749        0,
1750    );
1751    doc.add_numbered_list_item(
1752        "Federated learning infrastructure to enable model training across your distributed \
1753         data centers without centralizing sensitive financial data.",
1754        0,
1755    );
1756    doc.add_numbered_list_item(
1757        "MLOps automation to reduce your model deployment cycle from the current 5 days \
1758         to under 4 hours.",
1759        0,
1760    );
1761
1762    doc.add_paragraph(
1763        "Our engineering team has prepared a detailed technical proposal addressing each of \
1764         these areas. We have allocated a dedicated team of eight senior engineers, led by \
1765         our CTO, to ensure continuity with the Phase 1 team your organization has already \
1766         built a productive working relationship with.",
1767    )
1768    .first_line_indent(Length::inches(0.5));
1769
1770    doc.add_paragraph(
1771        "I would welcome the opportunity to present our Phase 2 proposal in person. My \
1772         assistant will reach out to coordinate a meeting at your convenience during the \
1773         first week of March.",
1774    )
1775    .first_line_indent(Length::inches(0.5));
1776
1777    doc.add_paragraph("");
1778
1779    doc.add_paragraph("Warm regards,");
1780    doc.add_paragraph("");
1781    doc.add_paragraph("");
1782
1783    // Signature line
1784    doc.add_paragraph("")
1785        .border_bottom(BorderStyle::Single, 4, "4A5568");
1786    {
1787        let mut p = doc.add_paragraph("");
1788        p.add_run("Walter White").bold(true).size(12.0);
1789    }
1790    {
1791        let mut p = doc.add_paragraph("");
1792        p.add_run("Chief Executive Officer, Tensorbee")
1793            .italic(true)
1794            .size(10.0)
1795            .color("666666");
1796    }
1797    {
1798        let mut p = doc.add_paragraph("");
1799        p.add_run("walter@tensorbee.com | +1 (415) 555-0199")
1800            .size(10.0)
1801            .color("888888");
1802    }
1803
1804    doc
1805}
1806
1807// =============================================================================
1808// 7. EMPLOYMENT CONTRACT — Purple + charcoal scheme
1809// =============================================================================
1810fn generate_contract(_samples_dir: &Path) -> Document {
1811    let mut doc = Document::new();
1812
1813    // Colors: Purple #5B2C6F, Plum #8E44AD, Gray #2C3E50
1814    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1815    doc.set_margins(
1816        Length::inches(1.25),
1817        Length::inches(1.0),
1818        Length::inches(1.0),
1819        Length::inches(1.0),
1820    );
1821    doc.set_title("Employment Agreement");
1822    doc.set_author("Tensorbee HR Department");
1823    doc.set_subject("Employment Contract — Walter White");
1824    doc.set_keywords("employment, contract, agreement, Tensorbee");
1825
1826    doc.set_header("TENSORBEE — Employment Agreement");
1827    doc.set_footer("Employment Agreement — Walter White — Page");
1828
1829    // ── Title Block ──
1830    doc.add_paragraph("")
1831        .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1832    {
1833        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1834        p.add_run("EMPLOYMENT AGREEMENT")
1835            .bold(true)
1836            .size(24.0)
1837            .color("5B2C6F")
1838            .font("Georgia");
1839    }
1840    doc.add_paragraph("")
1841        .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1842    doc.add_paragraph("");
1843
1844    // ── Parties ──
1845    doc.add_paragraph(
1846        "This Employment Agreement (\"Agreement\") is entered into as of February 22, 2026 \
1847         (\"Effective Date\"), by and between:",
1848    );
1849
1850    doc.add_paragraph("");
1851
1852    {
1853        let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1854        p.add_run("EMPLOYER: ").bold(true);
1855        p.add_run(
1856            "Tensorbee, Inc., a Delaware corporation with its principal place of business at \
1857                   123 Innovation Drive, Suite 400, San Francisco, CA 94105 (\"Company\")",
1858        );
1859    }
1860    doc.add_paragraph("");
1861    {
1862        let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1863        p.add_run("EMPLOYEE: ").bold(true);
1864        p.add_run(
1865            "Walter White, residing at 308 Negra Arroyo Lane, Albuquerque, NM 87104 (\"Employee\")",
1866        );
1867    }
1868
1869    doc.add_paragraph("");
1870    doc.add_paragraph("The Company and Employee are collectively referred to as the \"Parties.\"");
1871    doc.add_paragraph("");
1872
1873    // ── Article 1: Position and Duties ──
1874    doc.add_paragraph("Article 1 — Position and Duties")
1875        .style("Heading1");
1876    {
1877        let mut p = doc.add_paragraph("");
1878        p.add_run("1.1 ").bold(true);
1879        p.add_run("The Company hereby employs the Employee as ");
1880        p.add_run("Chief Executive Officer (CEO)")
1881            .bold(true)
1882            .italic(true);
1883        p.add_run(", reporting directly to the Board of Directors.");
1884    }
1885    {
1886        let mut p = doc.add_paragraph("");
1887        p.add_run("1.2 ").bold(true);
1888        p.add_run("The Employee shall devote full working time, attention, and best efforts to \
1889                   the performance of duties as reasonably assigned by the Board, including but not limited to:");
1890    }
1891    doc.add_bullet_list_item(
1892        "Setting and executing the Company's strategic vision and business plan",
1893        0,
1894    );
1895    doc.add_bullet_list_item(
1896        "Overseeing all operations, engineering, and commercial activities",
1897        0,
1898    );
1899    doc.add_bullet_list_item(
1900        "Representing the Company to investors, customers, and the public",
1901        0,
1902    );
1903    doc.add_bullet_list_item("Recruiting, developing, and retaining key talent", 0);
1904
1905    {
1906        let mut p = doc.add_paragraph("");
1907        p.add_run("1.3 ").bold(true);
1908        p.add_run(
1909            "The Employee's primary work location shall be the Company's San Francisco \
1910                   headquarters, with reasonable travel as required by business needs.",
1911        );
1912    }
1913
1914    // ── Article 2: Compensation ──
1915    doc.add_paragraph("Article 2 — Compensation and Benefits")
1916        .style("Heading1");
1917    {
1918        let mut p = doc.add_paragraph("");
1919        p.add_run("2.1 Base Salary. ").bold(true);
1920        p.add_run("The Company shall pay the Employee an annual base salary of ");
1921        p.add_run("$375,000.00").bold(true);
1922        p.add_run(" (Three Hundred Seventy-Five Thousand Dollars), payable in accordance with the \
1923                   Company's standard payroll schedule, less applicable withholdings and deductions.");
1924    }
1925    {
1926        let mut p = doc.add_paragraph("");
1927        p.add_run("2.2 Annual Bonus. ").bold(true);
1928        p.add_run("The Employee shall be eligible for an annual performance bonus of up to ");
1929        p.add_run("40%").bold(true);
1930        p.add_run(
1931            " of base salary, based on achievement of mutually agreed performance objectives.",
1932        );
1933    }
1934    {
1935        let mut p = doc.add_paragraph("");
1936        p.add_run("2.3 Equity. ").bold(true);
1937        p.add_run("Subject to Board approval, the Employee shall receive a stock option grant of ");
1938        p.add_run("500,000 shares").bold(true);
1939        p.add_run(
1940            " of the Company's common stock, vesting over four (4) years with a one-year cliff, \
1941                   at an exercise price equal to the fair market value on the date of grant.",
1942        );
1943    }
1944
1945    // Compensation summary table
1946    doc.add_paragraph("");
1947    {
1948        let mut tbl = doc
1949            .add_table(5, 2)
1950            .borders(BorderStyle::Single, 4, "5B2C6F")
1951            .width_pct(70.0)
1952            .alignment(Alignment::Center);
1953        tbl.cell(0, 0).unwrap().set_text("Compensation Element");
1954        tbl.cell(0, 0).unwrap().shading("5B2C6F");
1955        tbl.cell(0, 1).unwrap().set_text("Value");
1956        tbl.cell(0, 1).unwrap().shading("5B2C6F");
1957        let items = [
1958            ("Base Salary", "$375,000/year"),
1959            ("Target Bonus", "Up to 40% ($150,000)"),
1960            ("Equity Grant", "500,000 shares (4yr vest)"),
1961            ("Total Target Comp", "$525,000 + equity"),
1962        ];
1963        for (i, (elem, val)) in items.iter().enumerate() {
1964            tbl.cell(i + 1, 0).unwrap().set_text(elem);
1965            tbl.cell(i + 1, 1).unwrap().set_text(val);
1966            if i == 3 {
1967                tbl.cell(i + 1, 0).unwrap().shading("E8DAEF");
1968                tbl.cell(i + 1, 1).unwrap().shading("E8DAEF");
1969            }
1970        }
1971    }
1972
1973    // ── Article 3: Benefits ──
1974    doc.add_paragraph("").page_break_before(true);
1975    doc.add_paragraph("Article 3 — Benefits").style("Heading1");
1976    {
1977        let mut p = doc.add_paragraph("");
1978        p.add_run("3.1 ").bold(true);
1979        p.add_run(
1980            "The Employee shall be entitled to participate in all benefit programs \
1981                   generally available to senior executives, including:",
1982        );
1983    }
1984    doc.add_bullet_list_item(
1985        "Medical, dental, and vision insurance (100% premium coverage for employee and dependents)",
1986        0,
1987    );
1988    doc.add_bullet_list_item("401(k) retirement plan with 6% company match", 0);
1989    doc.add_bullet_list_item("Life insurance and long-term disability coverage", 0);
1990    doc.add_bullet_list_item("Annual professional development allowance of $10,000", 0);
1991
1992    {
1993        let mut p = doc.add_paragraph("");
1994        p.add_run("3.2 Paid Time Off. ").bold(true);
1995        p.add_run(
1996            "The Employee shall receive 25 days of paid vacation per year, plus Company holidays, \
1997                   accruing on a monthly basis.",
1998        );
1999    }
2000
2001    // ── Article 4: Term and Termination ──
2002    doc.add_paragraph("Article 4 — Term and Termination")
2003        .style("Heading1");
2004    {
2005        let mut p = doc.add_paragraph("");
2006        p.add_run("4.1 At-Will Employment. ").bold(true);
2007        p.add_run(
2008            "This Agreement is for at-will employment and may be terminated by either Party \
2009                   at any time, with or without cause, subject to the notice provisions herein.",
2010        );
2011    }
2012    {
2013        let mut p = doc.add_paragraph("");
2014        p.add_run("4.2 Notice Period. ").bold(true);
2015        p.add_run("Either Party shall provide ");
2016        p.add_run("ninety (90) days").bold(true);
2017        p.add_run(" written notice of termination, or pay in lieu thereof.");
2018    }
2019    {
2020        let mut p = doc.add_paragraph("");
2021        p.add_run("4.3 Severance. ").bold(true);
2022        p.add_run("In the event of termination by the Company without Cause, the Employee shall \
2023                   receive (a) twelve (12) months of base salary continuation, (b) pro-rata bonus \
2024                   for the year of termination, and (c) twelve (12) months of COBRA premium coverage.");
2025    }
2026
2027    // ── Article 5: Confidentiality ──
2028    doc.add_paragraph("Article 5 — Confidentiality and IP")
2029        .style("Heading1");
2030    {
2031        let mut p = doc.add_paragraph("");
2032        p.add_run("5.1 ").bold(true);
2033        p.add_run(
2034            "The Employee agrees to maintain strict confidentiality of all proprietary \
2035                   information, trade secrets, and business plans of the Company during and after \
2036                   employment.",
2037        );
2038    }
2039    {
2040        let mut p = doc.add_paragraph("");
2041        p.add_run("5.2 ").bold(true);
2042        p.add_run(
2043            "All intellectual property, inventions, and works of authorship created by the \
2044                   Employee during the term of employment and within the scope of duties shall be \
2045                   the sole and exclusive property of the Company.",
2046        );
2047    }
2048
2049    // ── Article 6: Non-Compete ──
2050    doc.add_paragraph("Article 6 — Non-Competition")
2051        .style("Heading1");
2052    {
2053        let mut p = doc.add_paragraph("");
2054        p.add_run("6.1 ").bold(true);
2055        p.add_run("For a period of twelve (12) months following termination, the Employee agrees \
2056                   not to engage in any business that directly competes with the Company's core \
2057                   business of AI infrastructure and ML operations services, within the United States.");
2058    }
2059
2060    // ── Article 7: General Provisions ──
2061    doc.add_paragraph("Article 7 — General Provisions")
2062        .style("Heading1");
2063    {
2064        let mut p = doc.add_paragraph("");
2065        p.add_run("7.1 Governing Law. ").bold(true);
2066        p.add_run(
2067            "This Agreement shall be governed by and construed in accordance with the laws of \
2068                   the State of California.",
2069        );
2070    }
2071    {
2072        let mut p = doc.add_paragraph("");
2073        p.add_run("7.2 Entire Agreement. ").bold(true);
2074        p.add_run(
2075            "This Agreement constitutes the entire agreement between the Parties and supersedes \
2076                   all prior negotiations, representations, and agreements.",
2077        );
2078    }
2079    {
2080        let mut p = doc.add_paragraph("");
2081        p.add_run("7.3 Amendment. ").bold(true);
2082        p.add_run(
2083            "This Agreement may only be amended by a written instrument signed by both Parties.",
2084        );
2085    }
2086
2087    // ── Signature Block ──
2088    doc.add_paragraph("").page_break_before(true);
2089    doc.add_paragraph("IN WITNESS WHEREOF, the Parties have executed this Employment Agreement as of the Effective Date.")
2090        .space_after(Length::pt(24.0));
2091
2092    // Signature table
2093    {
2094        let mut tbl = doc.add_table(6, 2).width_pct(100.0);
2095        tbl.cell(0, 0).unwrap().set_text("FOR THE COMPANY:");
2096        tbl.cell(0, 0).unwrap().shading("5B2C6F");
2097        tbl.cell(0, 1).unwrap().set_text("EMPLOYEE:");
2098        tbl.cell(0, 1).unwrap().shading("5B2C6F");
2099
2100        tbl.cell(1, 0).unwrap().set_text("");
2101        tbl.cell(1, 1).unwrap().set_text("");
2102        tbl.row(1).unwrap().height(Length::pt(40.0));
2103
2104        tbl.cell(2, 0)
2105            .unwrap()
2106            .set_text("Signature: ___________________________");
2107        tbl.cell(2, 1)
2108            .unwrap()
2109            .set_text("Signature: ___________________________");
2110        tbl.cell(3, 0)
2111            .unwrap()
2112            .set_text("Name: Board Representative");
2113        tbl.cell(3, 1).unwrap().set_text("Name: Walter White");
2114        tbl.cell(4, 0)
2115            .unwrap()
2116            .set_text("Title: Chair, Board of Directors");
2117        tbl.cell(4, 1)
2118            .unwrap()
2119            .set_text("Title: Chief Executive Officer");
2120        tbl.cell(5, 0).unwrap().set_text("Date: _______________");
2121        tbl.cell(5, 1).unwrap().set_text("Date: _______________");
2122    }
2123
2124    doc
2125}
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_samples.rs (line 47)
34fn generate_feature_showcase(path: &Path) {
35    let mut doc = Document::new();
36
37    // =========================================================================
38    // PAGE SETUP & METADATA
39    // =========================================================================
40    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
41    doc.set_margins(
42        Length::inches(1.0), // top
43        Length::inches(1.0), // right
44        Length::inches(1.0), // bottom
45        Length::inches(1.0), // left
46    );
47    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
48    doc.set_gutter(Length::twips(0));
49
50    doc.set_title("rdocx Feature Showcase");
51    doc.set_author("rdocx Sample Generator");
52    doc.set_subject("Comprehensive feature demonstration");
53    doc.set_keywords("rdocx, docx, rust, sample");
54
55    // Header & Footer
56    doc.set_header("rdocx Feature Showcase");
57    doc.set_footer("Generated by rdocx — Page");
58
59    // Different first page header
60    doc.set_different_first_page(true);
61    doc.set_first_page_header("rdocx");
62    doc.set_first_page_footer("Feature Showcase — Cover Page");
63
64    // =========================================================================
65    // PAGE 1: COVER PAGE — background image, run formatting
66    // =========================================================================
67    let bg_cover = create_sample_png(612, 792, [30, 60, 120]);
68    doc.add_background_image(&bg_cover, "cover_bg.png");
69
70    doc.add_paragraph(""); // spacer
71    doc.add_paragraph(""); // spacer
72    doc.add_paragraph(""); // spacer
73
74    {
75        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
76        p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
77    }
78    {
79        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
80        p.add_run("Feature Showcase")
81            .size(28.0)
82            .color("FFFFFF")
83            .italic(true);
84    }
85
86    doc.add_paragraph(""); // spacer
87
88    {
89        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
90        p.add_run("A comprehensive demonstration of every feature")
91            .size(14.0)
92            .color("CCDDFF");
93    }
94    {
95        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
96        p.add_run("provided by the rdocx Rust crate for DOCX generation.")
97            .size(14.0)
98            .color("CCDDFF");
99    }
100
101    // =========================================================================
102    // PAGE 2: TEXT FORMATTING
103    // =========================================================================
104    doc.add_paragraph("").page_break_before(true);
105
106    doc.add_paragraph("1. Text Formatting").style("Heading1");
107
108    doc.add_paragraph("This section demonstrates paragraph and run-level formatting options.");
109    doc.add_paragraph("");
110
111    // --- Paragraph alignment ---
112    doc.add_paragraph("Paragraph Alignment").style("Heading2");
113
114    doc.add_paragraph("This paragraph is left-aligned (the default).")
115        .alignment(Alignment::Left);
116    doc.add_paragraph("This paragraph is center-aligned.")
117        .alignment(Alignment::Center);
118    doc.add_paragraph("This paragraph is right-aligned.")
119        .alignment(Alignment::Right);
120    doc.add_paragraph(
121        "This paragraph is justified. To demonstrate justified text properly, it needs \
122         to be long enough to span multiple lines so the word spacing adjustment is visible \
123         across the full width of the text area on the page.",
124    )
125    .alignment(Alignment::Justify);
126
127    doc.add_paragraph("");
128
129    // --- Run formatting ---
130    doc.add_paragraph("Run Formatting").style("Heading2");
131
132    {
133        let mut p = doc.add_paragraph("");
134        p.add_run("Bold text").bold(true);
135        p.add_run(" | ");
136        p.add_run("Italic text").italic(true);
137        p.add_run(" | ");
138        p.add_run("Bold + Italic").bold(true).italic(true);
139    }
140    {
141        let mut p = doc.add_paragraph("");
142        p.add_run("Single underline").underline(true);
143        p.add_run(" | ");
144        p.add_run("Strikethrough").strike(true);
145        p.add_run(" | ");
146        p.add_run("Double strikethrough").double_strike(true);
147    }
148    {
149        let mut p = doc.add_paragraph("");
150        p.add_run("Red text").color("FF0000");
151        p.add_run(" | ");
152        p.add_run("Blue text").color("0000FF");
153        p.add_run(" | ");
154        p.add_run("Green text").color("00AA00");
155        p.add_run(" | ");
156        p.add_run("Highlighted").highlight("FFFF00");
157    }
158    {
159        let mut p = doc.add_paragraph("");
160        p.add_run("8pt small").size(8.0);
161        p.add_run(" | ");
162        p.add_run("11pt normal").size(11.0);
163        p.add_run(" | ");
164        p.add_run("16pt large").size(16.0);
165        p.add_run(" | ");
166        p.add_run("24pt extra-large").size(24.0);
167    }
168    {
169        let mut p = doc.add_paragraph("");
170        p.add_run("Arial font").font("Arial");
171        p.add_run(" | ");
172        p.add_run("Times New Roman font").font("Times New Roman");
173        p.add_run(" | ");
174        p.add_run("Courier New font").font("Courier New");
175    }
176    {
177        let mut p = doc.add_paragraph("");
178        p.add_run("Normal");
179        p.add_run(" H").size(11.0);
180        p.add_run("2").subscript();
181        p.add_run("O (subscript)").size(11.0);
182        p.add_run(" | E = mc").size(11.0);
183        p.add_run("2").superscript();
184        p.add_run(" (superscript)").size(11.0);
185    }
186    {
187        let mut p = doc.add_paragraph("");
188        p.add_run("ALL CAPS").all_caps(true);
189        p.add_run(" | ");
190        p.add_run("Small Caps").small_caps(true);
191        p.add_run(" | ");
192        p.add_run("Expanded spacing")
193            .character_spacing(Length::twips(40));
194    }
195
196    doc.add_paragraph("");
197
198    // --- Paragraph formatting ---
199    doc.add_paragraph("Paragraph Formatting").style("Heading2");
200
201    doc.add_paragraph("Paragraph with shading (light green background)")
202        .shading("E2EFDA");
203
204    doc.add_paragraph("Paragraph with bottom border")
205        .border_bottom(BorderStyle::Single, 6, "2E75B6");
206
207    doc.add_paragraph("Paragraph with all borders")
208        .border_all(BorderStyle::Single, 4, "FF0000");
209
210    doc.add_paragraph("Paragraph with 1-inch left indent and hanging indent")
211        .indent_left(Length::inches(1.0))
212        .hanging_indent(Length::inches(0.5));
213
214    doc.add_paragraph("Paragraph with first-line indent of 0.5 inches")
215        .first_line_indent(Length::inches(0.5));
216
217    doc.add_paragraph("Paragraph with extra space before (24pt) and after (12pt)")
218        .space_before(Length::pt(24.0))
219        .space_after(Length::pt(12.0));
220
221    doc.add_paragraph(
222        "Paragraph with double line spacing. This text should have extra vertical \
223         space between lines to demonstrate the line_spacing_multiple setting.",
224    )
225    .line_spacing_multiple(2.0);
226
227    doc.add_paragraph("Paragraph with keep-with-next (won't break from the next paragraph)")
228        .keep_with_next(true);
229    doc.add_paragraph("(This stays with the paragraph above.)");
230
231    // =========================================================================
232    // PAGE 3: LISTS & TAB STOPS
233    // =========================================================================
234    doc.add_paragraph("").page_break_before(true);
235
236    doc.add_paragraph("2. Lists").style("Heading1");
237
238    doc.add_paragraph("Bullet List").style("Heading2");
239
240    doc.add_bullet_list_item("First bullet item", 0);
241    doc.add_bullet_list_item("Second bullet item", 0);
242    doc.add_bullet_list_item("Nested level 1", 1);
243    doc.add_bullet_list_item("Nested level 2", 2);
244    doc.add_bullet_list_item("Back to level 1", 1);
245    doc.add_bullet_list_item("Third bullet item", 0);
246
247    doc.add_paragraph("");
248
249    doc.add_paragraph("Numbered List").style("Heading2");
250
251    doc.add_numbered_list_item("First numbered item", 0);
252    doc.add_numbered_list_item("Second numbered item", 0);
253    doc.add_numbered_list_item("Sub-item A", 1);
254    doc.add_numbered_list_item("Sub-item B", 1);
255    doc.add_numbered_list_item("Third numbered item", 0);
256
257    doc.add_paragraph("");
258
259    // --- Tab stops ---
260    doc.add_paragraph("Tab Stops").style("Heading2");
261
262    doc.add_paragraph("Left\tCenter\tRight\tDecimal")
263        .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
264        .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
265        .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
266        .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
267
268    doc.add_paragraph("Item\t........\tPrice")
269        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
270        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
271        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
272
273    doc.add_paragraph("Widget A\t........\t$19.99")
274        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
275        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
276        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
277
278    doc.add_paragraph("Gadget B\t________\t$249.50")
279        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
280        .add_tab_stop_with_leader(
281            TabAlignment::Right,
282            Length::inches(4.0),
283            TabLeader::Underscore,
284        )
285        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
286
287    // =========================================================================
288    // PAGE 4: TABLES
289    // =========================================================================
290    doc.add_paragraph("").page_break_before(true);
291
292    doc.add_paragraph("3. Tables").style("Heading1");
293
294    // --- Basic table with borders ---
295    doc.add_paragraph("Basic Table with Borders")
296        .style("Heading2");
297
298    {
299        let mut tbl = doc.add_table(4, 3);
300        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
301
302        // Header row
303        for col in 0..3 {
304            tbl.cell(0, col).unwrap().shading("2E75B6");
305        }
306        tbl.cell(0, 0).unwrap().set_text("Name");
307        tbl.cell(0, 1).unwrap().set_text("Role");
308        tbl.cell(0, 2).unwrap().set_text("Location");
309
310        tbl.cell(1, 0).unwrap().set_text("Alice Johnson");
311        tbl.cell(1, 1).unwrap().set_text("Engineering Lead");
312        tbl.cell(1, 2).unwrap().set_text("New York");
313
314        tbl.cell(2, 0).unwrap().set_text("Bob Smith");
315        tbl.cell(2, 1).unwrap().set_text("Product Manager");
316        tbl.cell(2, 2).unwrap().set_text("San Francisco");
317
318        tbl.cell(3, 0).unwrap().set_text("Carol Davis");
319        tbl.cell(3, 1).unwrap().set_text("Designer");
320        tbl.cell(3, 2).unwrap().set_text("London");
321    }
322
323    doc.add_paragraph("");
324
325    // --- Table with cell merging ---
326    doc.add_paragraph("Table with Cell Merging & Vertical Alignment")
327        .style("Heading2");
328
329    {
330        let mut tbl = doc.add_table(4, 4);
331        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
332        tbl = tbl.width_pct(100.0);
333
334        // Header spanning all columns
335        tbl.cell(0, 0).unwrap().set_text("Quarterly Revenue Report");
336        tbl.cell(0, 0).unwrap().shading("1F4E79");
337        tbl.cell(0, 0).unwrap().grid_span(4);
338
339        // Sub-header
340        tbl.cell(1, 0).unwrap().set_text("Region");
341        tbl.cell(1, 0).unwrap().shading("D6E4F0");
342        tbl.cell(1, 1).unwrap().set_text("Q1");
343        tbl.cell(1, 1).unwrap().shading("D6E4F0");
344        tbl.cell(1, 2).unwrap().set_text("Q2");
345        tbl.cell(1, 2).unwrap().shading("D6E4F0");
346        tbl.cell(1, 3).unwrap().set_text("Total");
347        tbl.cell(1, 3).unwrap().shading("D6E4F0");
348
349        // Data
350        tbl.cell(2, 0).unwrap().set_text("North America");
351        tbl.cell(2, 1).unwrap().set_text("$2.4M");
352        tbl.cell(2, 2).unwrap().set_text("$2.7M");
353        tbl.cell(2, 3).unwrap().set_text("$5.1M");
354
355        tbl.cell(3, 0).unwrap().set_text("Europe");
356        tbl.cell(3, 1).unwrap().set_text("$1.8M");
357        tbl.cell(3, 2).unwrap().set_text("$2.0M");
358        tbl.cell(3, 3).unwrap().set_text("$3.8M");
359
360        // Vertical alignment on data cells
361        tbl.cell(2, 3)
362            .unwrap()
363            .vertical_alignment(VerticalAlignment::Center);
364        tbl.cell(3, 3)
365            .unwrap()
366            .vertical_alignment(VerticalAlignment::Bottom);
367    }
368
369    doc.add_paragraph("");
370
371    // --- Table with vertical merge ---
372    doc.add_paragraph("Table with Vertical Merge")
373        .style("Heading2");
374
375    {
376        let mut tbl = doc.add_table(4, 3);
377        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
378
379        tbl.cell(0, 0).unwrap().set_text("Category");
380        tbl.cell(0, 0).unwrap().shading("E2EFDA");
381        tbl.cell(0, 1).unwrap().set_text("Item");
382        tbl.cell(0, 1).unwrap().shading("E2EFDA");
383        tbl.cell(0, 2).unwrap().set_text("Price");
384        tbl.cell(0, 2).unwrap().shading("E2EFDA");
385
386        // "Hardware" spans rows 1-2
387        tbl.cell(1, 0).unwrap().set_text("Hardware");
388        tbl.cell(1, 0).unwrap().v_merge_restart();
389        tbl.cell(1, 1).unwrap().set_text("Laptop");
390        tbl.cell(1, 2).unwrap().set_text("$1,200");
391
392        tbl.cell(2, 0).unwrap().v_merge_continue();
393        tbl.cell(2, 1).unwrap().set_text("Monitor");
394        tbl.cell(2, 2).unwrap().set_text("$450");
395
396        // "Software" on row 3
397        tbl.cell(3, 0).unwrap().set_text("Software");
398        tbl.cell(3, 1).unwrap().set_text("IDE License");
399        tbl.cell(3, 2).unwrap().set_text("$200/yr");
400    }
401
402    doc.add_paragraph("");
403
404    // --- Nested table ---
405    doc.add_paragraph("Nested Table").style("Heading2");
406
407    {
408        let mut tbl = doc.add_table(2, 2);
409        tbl = tbl.borders(BorderStyle::Single, 6, "2E75B6");
410
411        tbl.cell(0, 0).unwrap().set_text("Outer Cell (0,0)");
412        tbl.cell(0, 1).unwrap().set_text("Outer Cell (0,1)");
413        tbl.cell(1, 0).unwrap().set_text("Outer Cell (1,0)");
414
415        // Nested table inside cell (1,1)
416        {
417            let mut cell = tbl.cell(1, 1).unwrap();
418            cell.set_text("Contains nested table:");
419            let mut nested = cell.add_table(2, 2);
420            nested = nested.borders(BorderStyle::Single, 2, "FF6600");
421            nested.cell(0, 0).unwrap().set_text("Inner A");
422            nested.cell(0, 1).unwrap().set_text("Inner B");
423            nested.cell(1, 0).unwrap().set_text("Inner C");
424            nested.cell(1, 1).unwrap().set_text("Inner D");
425        }
426    }
427
428    // =========================================================================
429    // PAGE 5: IMAGES
430    // =========================================================================
431    doc.add_paragraph("").page_break_before(true);
432
433    doc.add_paragraph("4. Images").style("Heading1");
434
435    doc.add_paragraph("Inline Image").style("Heading2");
436
437    doc.add_paragraph("Below is an inline image (200x50 pixels, blue gradient):");
438    let inline_img = create_sample_png(200, 50, [0, 80, 200]);
439    doc.add_picture(
440        &inline_img,
441        "inline_chart.png",
442        Length::inches(3.0),
443        Length::inches(0.75),
444    );
445
446    doc.add_paragraph("");
447
448    doc.add_paragraph("Header Image").style("Heading2");
449
450    // Replace the text-only header with an image header
451    let header_img = create_sample_png(400, 40, [40, 40, 40]);
452    doc.set_header_image(
453        &header_img,
454        "header_logo.png",
455        Length::inches(2.0),
456        Length::inches(0.2),
457    );
458
459    doc.add_paragraph(
460        "The document header has been replaced with an inline image. \
461         Check the header area at the top of this page.",
462    );
463
464    doc.add_paragraph("");
465    doc.add_paragraph(
466        "Note: The cover page uses a full-page background image behind the text, \
467         demonstrated on page 1 via add_background_image().",
468    );
469
470    // =========================================================================
471    // PAGE 6: CONTENT MANIPULATION — placeholder replacement, insertion
472    // =========================================================================
473    doc.add_paragraph("").page_break_before(true);
474
475    doc.add_paragraph("5. Content Manipulation")
476        .style("Heading1");
477
478    // --- Placeholder replacement ---
479    doc.add_paragraph("Placeholder Replacement")
480        .style("Heading2");
481
482    doc.add_paragraph(
483        "Before replacement, this document contained {{customer}} and {{date}} placeholders.",
484    );
485
486    {
487        let mut p = doc.add_paragraph("");
488        p.add_run("Customer: ").bold(true);
489        p.add_run("{{customer}}");
490    }
491    {
492        let mut p = doc.add_paragraph("");
493        p.add_run("Date: ").bold(true);
494        p.add_run("{{date}}");
495    }
496    doc.add_paragraph("Reference: {{ref_number}}");
497
498    // Table with placeholders
499    {
500        let mut tbl = doc.add_table(3, 2);
501        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
502        tbl.cell(0, 0).unwrap().set_text("Field");
503        tbl.cell(0, 0).unwrap().shading("D6E4F0");
504        tbl.cell(0, 1).unwrap().set_text("Value");
505        tbl.cell(0, 1).unwrap().shading("D6E4F0");
506        tbl.cell(1, 0).unwrap().set_text("Project");
507        tbl.cell(1, 1).unwrap().set_text("{{project}}");
508        tbl.cell(2, 0).unwrap().set_text("Status");
509        tbl.cell(2, 1).unwrap().set_text("{{status}}");
510    }
511
512    // Perform replacements
513    let mut replacements = HashMap::new();
514    replacements.insert("{{customer}}", "Acme Corporation");
515    replacements.insert("{{date}}", "February 22, 2026");
516    replacements.insert("{{ref_number}}", "REF-2026-001");
517    replacements.insert("{{project}}", "Infrastructure Upgrade");
518    replacements.insert("{{status}}", "In Progress");
519    let replace_count = doc.replace_all(&replacements);
520
521    doc.add_paragraph("");
522    doc.add_paragraph(&format!(
523        "(Replaced {} placeholders above — in body text and table cells)",
524        replace_count
525    ));
526
527    doc.add_paragraph("");
528
529    // --- Content insertion ---
530    doc.add_paragraph("Content Insertion").style("Heading2");
531
532    doc.add_paragraph("Section A: First section of content.");
533    doc.add_paragraph("Section C: Third section of content.");
534
535    // Insert "Section B" between A and C
536    if let Some(idx) = doc.find_content_index("Section C") {
537        doc.insert_paragraph(
538            idx,
539            "Section B: Inserted between A and C using find_content_index().",
540        );
541    }
542
543    doc.add_paragraph("");
544    doc.add_paragraph(
545        "The paragraph above ('Section B') was inserted at a specific position \
546         using find_content_index() + insert_paragraph().",
547    );
548
549    // =========================================================================
550    // PAGE 7: LANDSCAPE — section break, wide table
551    // =========================================================================
552    doc.add_paragraph("").section_break(SectionBreak::NextPage);
553
554    doc.add_paragraph("6. Mixed Page Orientation")
555        .style("Heading1");
556
557    doc.add_paragraph(
558        "This page is in LANDSCAPE orientation. It was created using a section break \
559         followed by section_landscape(). This is useful for wide tables or charts.",
560    );
561
562    doc.add_paragraph("");
563
564    // Wide table for landscape
565    {
566        let mut tbl = doc.add_table(4, 7);
567        tbl = tbl.borders(BorderStyle::Single, 4, "2E75B6");
568
569        let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
570        for (col, h) in headers.iter().enumerate() {
571            tbl.cell(0, col).unwrap().set_text(h);
572            tbl.cell(0, col).unwrap().shading("2E75B6");
573        }
574
575        let data = [
576            [
577                "North America",
578                "$1.2M",
579                "$1.3M",
580                "$1.4M",
581                "$1.5M",
582                "$1.6M",
583                "$7.0M",
584            ],
585            [
586                "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
587            ],
588            [
589                "Asia Pacific",
590                "$0.5M",
591                "$0.6M",
592                "$0.7M",
593                "$0.7M",
594                "$0.8M",
595                "$3.3M",
596            ],
597        ];
598        for (row_idx, row_data) in data.iter().enumerate() {
599            for (col, val) in row_data.iter().enumerate() {
600                tbl.cell(row_idx + 1, col).unwrap().set_text(val);
601            }
602        }
603    }
604
605    // End landscape, return to portrait
606    doc.add_paragraph("")
607        .section_break(SectionBreak::NextPage)
608        .section_landscape();
609
610    // =========================================================================
611    // PAGE 8: BACK TO PORTRAIT — styles, final notes
612    // =========================================================================
613    doc.add_paragraph("7. Custom Styles & Summary")
614        .style("Heading1");
615
616    doc.add_paragraph(
617        "This final page is back in portrait orientation after a section break. \
618         The document has demonstrated:",
619    );
620
621    doc.add_bullet_list_item(
622        "Page setup: size, margins, header/footer distance, gutter",
623        0,
624    );
625    doc.add_bullet_list_item("Document metadata: title, author, subject, keywords", 0);
626    doc.add_bullet_list_item("Headers and footers: text, images, different first page", 0);
627    doc.add_bullet_list_item("Background images: full-page behind text", 0);
628    doc.add_bullet_list_item(
629        "Text formatting: bold, italic, underline, strike, color, size, font",
630        0,
631    );
632    doc.add_bullet_list_item(
633        "Advanced run formatting: superscript, subscript, caps, spacing",
634        0,
635    );
636    doc.add_bullet_list_item(
637        "Paragraph formatting: alignment, borders, shading, spacing, indentation",
638        0,
639    );
640    doc.add_bullet_list_item("Bullet and numbered lists with nesting levels", 0);
641    doc.add_bullet_list_item("Tab stops with dot/underscore leaders", 0);
642    doc.add_bullet_list_item(
643        "Tables: borders, shading, column spans, row spans, nesting",
644        0,
645    );
646    doc.add_bullet_list_item("Vertical alignment in table cells", 0);
647    doc.add_bullet_list_item("Inline images", 0);
648    doc.add_bullet_list_item("Placeholder replacement in body and table cells", 0);
649    doc.add_bullet_list_item("Content insertion at specific positions", 0);
650    doc.add_bullet_list_item(
651        "Section breaks with mixed portrait/landscape orientation",
652        0,
653    );
654
655    doc.add_paragraph("");
656    doc.add_paragraph("All features above were built entirely from scratch using the rdocx API.")
657        .alignment(Alignment::Center)
658        .shading("E2EFDA")
659        .border_all(BorderStyle::Single, 2, "00AA00");
660
661    doc.save(path).unwrap();
662}
examples/generate_all_samples.rs (line 97)
86fn generate_feature_showcase(_samples_dir: &Path) -> Document {
87    let mut doc = Document::new();
88
89    // ── Page Setup & Metadata ──
90    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
91    doc.set_margins(
92        Length::inches(1.0),
93        Length::inches(1.0),
94        Length::inches(1.0),
95        Length::inches(1.0),
96    );
97    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
98    doc.set_gutter(Length::twips(0));
99    doc.set_title("rdocx Feature Showcase");
100    doc.set_author("rdocx Sample Generator");
101    doc.set_subject("Comprehensive feature demonstration");
102    doc.set_keywords("rdocx, docx, rust, sample, showcase");
103
104    // Headers & Footers with different first page
105    doc.set_different_first_page(true);
106    doc.set_first_page_header("rdocx");
107    doc.set_first_page_footer("Feature Showcase — Cover Page");
108    doc.set_header("rdocx Feature Showcase");
109    doc.set_footer("Generated by rdocx");
110
111    // ── COVER PAGE ──
112    let bg = create_sample_png(612, 792, [20, 45, 90]);
113    doc.add_background_image(&bg, "cover_bg.png");
114
115    for _ in 0..3 {
116        doc.add_paragraph("");
117    }
118    {
119        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
120        p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
121    }
122    {
123        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
124        p.add_run("Complete Feature Showcase")
125            .size(28.0)
126            .color("FFFFFF")
127            .italic(true);
128    }
129    doc.add_paragraph("");
130    {
131        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
132        p.add_run("Every feature of the rdocx Rust library")
133            .size(13.0)
134            .color("B0C4DE");
135    }
136    {
137        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
138        p.add_run("demonstrated in a single document.")
139            .size(13.0)
140            .color("B0C4DE");
141    }
142
143    // ── TABLE OF CONTENTS ──
144    doc.add_paragraph("").page_break_before(true);
145    doc.insert_toc(doc.content_count(), 3);
146
147    // ── SECTION 1: TEXT FORMATTING ──
148    doc.add_paragraph("").page_break_before(true);
149    doc.add_paragraph("1. Text Formatting").style("Heading1");
150
151    // Paragraph Alignment
152    doc.add_paragraph("1.1 Paragraph Alignment")
153        .style("Heading2");
154    doc.add_paragraph("Left-aligned paragraph (default).")
155        .alignment(Alignment::Left);
156    doc.add_paragraph("Center-aligned paragraph.")
157        .alignment(Alignment::Center);
158    doc.add_paragraph("Right-aligned paragraph.")
159        .alignment(Alignment::Right);
160    doc.add_paragraph(
161        "Justified paragraph — this text is long enough to demonstrate how justified alignment \
162         distributes extra space across word gaps so lines fill the full width of the text area.",
163    )
164    .alignment(Alignment::Justify);
165
166    doc.add_paragraph("");
167
168    // Run Formatting
169    doc.add_paragraph("1.2 Run Formatting").style("Heading2");
170    {
171        let mut p = doc.add_paragraph("");
172        p.add_run("Bold").bold(true);
173        p.add_run(" | ");
174        p.add_run("Italic").italic(true);
175        p.add_run(" | ");
176        p.add_run("Bold Italic").bold(true).italic(true);
177        p.add_run(" | ");
178        p.add_run("Underline").underline(true);
179        p.add_run(" | ");
180        p.add_run("Strikethrough").strike(true);
181        p.add_run(" | ");
182        p.add_run("Double Strike").double_strike(true);
183    }
184    {
185        let mut p = doc.add_paragraph("");
186        p.add_run("Red").color("FF0000");
187        p.add_run(" | ");
188        p.add_run("Blue").color("0000FF");
189        p.add_run(" | ");
190        p.add_run("Green").color("00AA00");
191        p.add_run(" | ");
192        p.add_run("Highlighted").highlight("FFFF00");
193    }
194    {
195        let mut p = doc.add_paragraph("");
196        p.add_run("8pt").size(8.0);
197        p.add_run(" | ");
198        p.add_run("11pt").size(11.0);
199        p.add_run(" | ");
200        p.add_run("16pt").size(16.0);
201        p.add_run(" | ");
202        p.add_run("24pt").size(24.0);
203    }
204    {
205        let mut p = doc.add_paragraph("");
206        p.add_run("Arial").font("Arial");
207        p.add_run(" | ");
208        p.add_run("Times New Roman").font("Times New Roman");
209        p.add_run(" | ");
210        p.add_run("Courier New").font("Courier New");
211    }
212    {
213        let mut p = doc.add_paragraph("");
214        p.add_run("H");
215        p.add_run("2").subscript();
216        p.add_run("O (subscript) | E=mc");
217        p.add_run("2").superscript();
218        p.add_run(" (superscript)");
219    }
220    {
221        let mut p = doc.add_paragraph("");
222        p.add_run("ALL CAPS").all_caps(true);
223        p.add_run(" | ");
224        p.add_run("Small Caps").small_caps(true);
225        p.add_run(" | ");
226        p.add_run("Expanded").character_spacing(Length::twips(40));
227        p.add_run(" | ");
228        p.add_run("Hidden text (not visible)").hidden(true);
229    }
230
231    doc.add_paragraph("");
232
233    // Underline Styles
234    doc.add_paragraph("1.3 Underline Styles").style("Heading2");
235    {
236        let mut p = doc.add_paragraph("");
237        p.add_run("Single ")
238            .underline_style(rdocx::UnderlineStyle::Single);
239        p.add_run("Double ")
240            .underline_style(rdocx::UnderlineStyle::Double);
241        p.add_run("Thick ")
242            .underline_style(rdocx::UnderlineStyle::Thick);
243        p.add_run("Dotted ")
244            .underline_style(rdocx::UnderlineStyle::Dotted);
245        p.add_run("Dash ")
246            .underline_style(rdocx::UnderlineStyle::Dash);
247        p.add_run("Wave ")
248            .underline_style(rdocx::UnderlineStyle::Wave);
249    }
250
251    // ── SECTION 2: PARAGRAPH FORMATTING ──
252    doc.add_paragraph("").page_break_before(true);
253    doc.add_paragraph("2. Paragraph Formatting")
254        .style("Heading1");
255
256    doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
257    doc.add_paragraph("Paragraph with light green shading")
258        .shading("E2EFDA");
259    doc.add_paragraph("Paragraph with bottom border")
260        .border_bottom(BorderStyle::Single, 6, "2E75B6");
261    doc.add_paragraph("Paragraph with all borders (red)")
262        .border_all(BorderStyle::Single, 4, "FF0000");
263
264    doc.add_paragraph("2.2 Indentation").style("Heading2");
265    doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
266        .indent_left(Length::inches(1.0))
267        .hanging_indent(Length::inches(0.5));
268    doc.add_paragraph("0.5-inch first-line indent on this paragraph")
269        .first_line_indent(Length::inches(0.5));
270    doc.add_paragraph("Both left and right indent (0.75 inches each)")
271        .indent_left(Length::inches(0.75))
272        .indent_right(Length::inches(0.75));
273
274    doc.add_paragraph("2.3 Spacing & Line Height")
275        .style("Heading2");
276    doc.add_paragraph("Extra space before (24pt) and after (12pt)")
277        .space_before(Length::pt(24.0))
278        .space_after(Length::pt(12.0));
279    doc.add_paragraph(
280        "Double line spacing paragraph. This text should have extra vertical space between \
281         lines to demonstrate line_spacing_multiple(2.0).",
282    )
283    .line_spacing_multiple(2.0);
284    doc.add_paragraph("Exact 20pt line spacing (fixed height).")
285        .line_spacing(20.0);
286
287    doc.add_paragraph("2.4 Pagination Controls")
288        .style("Heading2");
289    doc.add_paragraph("keep_with_next — this paragraph stays with the next")
290        .keep_with_next(true);
291    doc.add_paragraph("(This paragraph was kept with the one above.)");
292    doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
293        .keep_together(true);
294    doc.add_paragraph("widow_control — prevents widow/orphan lines")
295        .widow_control(true);
296
297    // ── SECTION 3: LISTS ──
298    doc.add_paragraph("").page_break_before(true);
299    doc.add_paragraph("3. Lists").style("Heading1");
300
301    doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
302    doc.add_bullet_list_item("First item", 0);
303    doc.add_bullet_list_item("Second item", 0);
304    doc.add_bullet_list_item("Nested level 1", 1);
305    doc.add_bullet_list_item("Nested level 2", 2);
306    doc.add_bullet_list_item("Back to level 1", 1);
307    doc.add_bullet_list_item("Third item", 0);
308
309    doc.add_paragraph("");
310
311    doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
312    doc.add_numbered_list_item("First numbered item", 0);
313    doc.add_numbered_list_item("Second numbered item", 0);
314    doc.add_numbered_list_item("Sub-item A", 1);
315    doc.add_numbered_list_item("Sub-item B", 1);
316    doc.add_numbered_list_item("Third numbered item", 0);
317
318    // ── SECTION 4: TAB STOPS ──
319    doc.add_paragraph("");
320    doc.add_paragraph("4. Tab Stops").style("Heading1");
321
322    doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
323    doc.add_paragraph("Left\tCenter\tRight\tDecimal")
324        .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
325        .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
326        .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
327        .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
328
329    doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
330    doc.add_paragraph("Item\t\tPrice")
331        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
332        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
333        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
334    doc.add_paragraph("Widget\t\t$19.99")
335        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
336        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
337        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
338    doc.add_paragraph("Gadget\t\t$249.50")
339        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
340        .add_tab_stop_with_leader(
341            TabAlignment::Right,
342            Length::inches(4.0),
343            TabLeader::Underscore,
344        )
345        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
346
347    // ── SECTION 5: TABLES ──
348    doc.add_paragraph("").page_break_before(true);
349    doc.add_paragraph("5. Tables").style("Heading1");
350
351    doc.add_paragraph("5.1 Basic Table").style("Heading2");
352    {
353        let mut tbl = doc
354            .add_table(4, 3)
355            .borders(BorderStyle::Single, 4, "000000");
356        for col in 0..3 {
357            tbl.cell(0, col).unwrap().shading("2E75B6");
358        }
359        tbl.cell(0, 0).unwrap().set_text("Name");
360        tbl.cell(0, 1).unwrap().set_text("Role");
361        tbl.cell(0, 2).unwrap().set_text("Location");
362        tbl.cell(1, 0).unwrap().set_text("Walter White");
363        tbl.cell(1, 1).unwrap().set_text("CEO");
364        tbl.cell(1, 2).unwrap().set_text("Albuquerque");
365        tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
366        tbl.cell(2, 1).unwrap().set_text("CTO");
367        tbl.cell(2, 2).unwrap().set_text("Remote");
368        tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
369        tbl.cell(3, 1).unwrap().set_text("Security");
370        tbl.cell(3, 2).unwrap().set_text("Washington");
371    }
372
373    doc.add_paragraph("");
374
375    doc.add_paragraph("5.2 Column Span & Cell Shading")
376        .style("Heading2");
377    {
378        let mut tbl = doc
379            .add_table(3, 4)
380            .borders(BorderStyle::Single, 4, "000000")
381            .width_pct(100.0);
382        tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
383        tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
384        tbl.cell(1, 0).unwrap().set_text("Region");
385        tbl.cell(1, 0).unwrap().shading("D6E4F0");
386        tbl.cell(1, 1).unwrap().set_text("Q1");
387        tbl.cell(1, 1).unwrap().shading("D6E4F0");
388        tbl.cell(1, 2).unwrap().set_text("Q2");
389        tbl.cell(1, 2).unwrap().shading("D6E4F0");
390        tbl.cell(1, 3).unwrap().set_text("Total");
391        tbl.cell(1, 3).unwrap().shading("D6E4F0");
392        tbl.cell(2, 0).unwrap().set_text("Americas");
393        tbl.cell(2, 1).unwrap().set_text("$2.4M");
394        tbl.cell(2, 2).unwrap().set_text("$2.7M");
395        tbl.cell(2, 3).unwrap().set_text("$5.1M");
396    }
397
398    doc.add_paragraph("");
399
400    doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
401    {
402        let mut tbl = doc
403            .add_table(4, 3)
404            .borders(BorderStyle::Single, 4, "000000");
405        tbl.cell(0, 0).unwrap().set_text("Category");
406        tbl.cell(0, 0).unwrap().shading("E2EFDA");
407        tbl.cell(0, 1).unwrap().set_text("Item");
408        tbl.cell(0, 1).unwrap().shading("E2EFDA");
409        tbl.cell(0, 2).unwrap().set_text("Price");
410        tbl.cell(0, 2).unwrap().shading("E2EFDA");
411        tbl.cell(1, 0).unwrap().set_text("Hardware");
412        tbl.cell(1, 0).unwrap().v_merge_restart();
413        tbl.cell(1, 1).unwrap().set_text("Laptop");
414        tbl.cell(1, 2).unwrap().set_text("$1,200");
415        tbl.cell(2, 0).unwrap().v_merge_continue();
416        tbl.cell(2, 1).unwrap().set_text("Monitor");
417        tbl.cell(2, 2).unwrap().set_text("$450");
418        tbl.cell(3, 0).unwrap().set_text("Software");
419        tbl.cell(3, 1).unwrap().set_text("IDE License");
420        tbl.cell(3, 2).unwrap().set_text("$200/yr");
421    }
422
423    doc.add_paragraph("");
424
425    doc.add_paragraph("5.4 Nested Table").style("Heading2");
426    {
427        let mut tbl = doc
428            .add_table(2, 2)
429            .borders(BorderStyle::Single, 6, "2E75B6");
430        tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
431        tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
432        tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
433        {
434            let mut cell = tbl.cell(1, 1).unwrap();
435            cell.set_text("Nested table below:");
436            let mut nested = cell
437                .add_table(2, 2)
438                .borders(BorderStyle::Single, 2, "FF6600");
439            nested.cell(0, 0).unwrap().set_text("A");
440            nested.cell(0, 1).unwrap().set_text("B");
441            nested.cell(1, 0).unwrap().set_text("C");
442            nested.cell(1, 1).unwrap().set_text("D");
443        }
444    }
445
446    doc.add_paragraph("");
447
448    doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
449        .style("Heading2");
450    {
451        let mut tbl = doc
452            .add_table(2, 3)
453            .borders(BorderStyle::Single, 4, "666666");
454        tbl.row(0).unwrap().height(Length::pt(50.0)).header();
455        tbl.cell(0, 0).unwrap().set_text("Top");
456        tbl.cell(0, 0)
457            .unwrap()
458            .vertical_alignment(VerticalAlignment::Top)
459            .shading("FFF2CC");
460        tbl.cell(0, 1).unwrap().set_text("Center");
461        tbl.cell(0, 1)
462            .unwrap()
463            .vertical_alignment(VerticalAlignment::Center)
464            .shading("D9E2F3");
465        tbl.cell(0, 2).unwrap().set_text("Bottom");
466        tbl.cell(0, 2)
467            .unwrap()
468            .vertical_alignment(VerticalAlignment::Bottom)
469            .shading("E2EFDA");
470        tbl.row(1).unwrap().cant_split();
471        tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
472        tbl.cell(1, 0).unwrap().no_wrap();
473        tbl.cell(1, 1).unwrap().set_text("Fixed width");
474        tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
475        tbl.cell(1, 2).unwrap().set_text("Normal");
476    }
477
478    // ── SECTION 6: IMAGES ──
479    doc.add_paragraph("").page_break_before(true);
480    doc.add_paragraph("6. Images").style("Heading1");
481
482    doc.add_paragraph("6.1 Inline Image").style("Heading2");
483    doc.add_paragraph("A blue gradient image below:");
484    let img = create_sample_png(200, 50, [0, 80, 200]);
485    doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
486
487    doc.add_paragraph("");
488    doc.add_paragraph("6.2 Header Image").style("Heading2");
489    let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
490    doc.set_header_image(
491        &hdr_img,
492        "header_logo.png",
493        Length::inches(2.0),
494        Length::inches(0.2),
495    );
496    doc.add_paragraph("The header now contains an inline image (check the top of this page).");
497
498    doc.add_paragraph("");
499    doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
500
501    // ── SECTION 7: CONTENT MANIPULATION ──
502    doc.add_paragraph("").page_break_before(true);
503    doc.add_paragraph("7. Content Manipulation")
504        .style("Heading1");
505
506    doc.add_paragraph("7.1 Placeholder Replacement")
507        .style("Heading2");
508    doc.add_paragraph("Customer: {{customer}}");
509    doc.add_paragraph("Date: {{date}}");
510    doc.add_paragraph("Reference: {{ref_number}}");
511    {
512        let mut tbl = doc
513            .add_table(2, 2)
514            .borders(BorderStyle::Single, 4, "000000");
515        tbl.cell(0, 0).unwrap().set_text("Project");
516        tbl.cell(0, 0).unwrap().shading("D6E4F0");
517        tbl.cell(0, 1).unwrap().set_text("{{project}}");
518        tbl.cell(1, 0).unwrap().set_text("Status");
519        tbl.cell(1, 0).unwrap().shading("D6E4F0");
520        tbl.cell(1, 1).unwrap().set_text("{{status}}");
521    }
522
523    let mut replacements = HashMap::new();
524    replacements.insert("{{customer}}", "Tensorbee Inc.");
525    replacements.insert("{{date}}", "February 22, 2026");
526    replacements.insert("{{ref_number}}", "TB-2026-001");
527    replacements.insert("{{project}}", "Infrastructure Upgrade");
528    replacements.insert("{{status}}", "In Progress");
529    let n = doc.replace_all(&replacements);
530    doc.add_paragraph(&format!("({n} placeholders replaced above)"));
531
532    doc.add_paragraph("");
533
534    doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
535    doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
536    let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
537    doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
538
539    doc.add_paragraph("");
540
541    doc.add_paragraph("7.3 Content Insertion").style("Heading2");
542    doc.add_paragraph("Section A: First content.");
543    doc.add_paragraph("Section C: Third content.");
544    if let Some(idx) = doc.find_content_index("Section C") {
545        doc.insert_paragraph(
546            idx,
547            "Section B: Inserted between A and C via find_content_index().",
548        );
549    }
550
551    // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
552    doc.add_paragraph("").section_break(SectionBreak::NextPage);
553    doc.add_paragraph("8. Section Breaks & Orientation")
554        .style("Heading1");
555    doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
556
557    {
558        let mut tbl = doc
559            .add_table(3, 7)
560            .borders(BorderStyle::Single, 4, "2E75B6");
561        let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
562        for (c, h) in headers.iter().enumerate() {
563            tbl.cell(0, c).unwrap().set_text(h);
564            tbl.cell(0, c).unwrap().shading("2E75B6");
565        }
566        let data = [
567            [
568                "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
569            ],
570            [
571                "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
572            ],
573        ];
574        for (r, row) in data.iter().enumerate() {
575            for (c, v) in row.iter().enumerate() {
576                tbl.cell(r + 1, c).unwrap().set_text(v);
577            }
578        }
579    }
580
581    doc.add_paragraph("")
582        .section_break(SectionBreak::NextPage)
583        .section_landscape();
584
585    // ── SECTION 9: CUSTOM STYLES ──
586    doc.add_paragraph("9. Custom Styles").style("Heading1");
587    doc.add_style(
588        StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
589            .based_on("Normal")
590            .paragraph_properties({
591                let mut ppr = rdocx_oxml::properties::CT_PPr::default();
592                ppr.shading = Some(rdocx_oxml::properties::CT_Shd {
593                    val: "clear".to_string(),
594                    color: None,
595                    fill: Some("FFF2CC".to_string()),
596                });
597                ppr
598            })
599            .run_properties({
600                let mut rpr = rdocx_oxml::properties::CT_RPr::default();
601                rpr.bold = Some(true);
602                rpr.color = Some("C45911".to_string());
603                rpr
604            }),
605    );
606    doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
607        .style("CustomHighlight");
608
609    doc.add_paragraph("");
610
611    // ── SECTION 10: DOCUMENT INTELLIGENCE ──
612    doc.add_paragraph("10. Document Intelligence API")
613        .style("Heading1");
614
615    let wc = doc.word_count();
616    let hc = doc.headings().len();
617    let ic = doc.images().len();
618    let lc = doc.links().len();
619    doc.add_paragraph(&format!("Word count: {wc}"));
620    doc.add_paragraph(&format!("Heading count: {hc}"));
621    doc.add_paragraph(&format!("Image count: {ic}"));
622    doc.add_paragraph(&format!("Link count: {lc}"));
623
624    let outline = doc.document_outline();
625    doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
626
627    let issues = doc.audit_accessibility();
628    doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
629    for issue in &issues {
630        doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
631    }
632
633    // ── SECTION 11: DOCUMENT MERGING ──
634    doc.add_paragraph("");
635    doc.add_paragraph("11. Document Merging").style("Heading1");
636    {
637        let mut other = Document::new();
638        other.add_paragraph("This paragraph was merged from another document using append().");
639        doc.append(&other);
640    }
641
642    doc.add_paragraph("");
643
644    // ── SUMMARY ──
645    doc.add_paragraph("Summary of Demonstrated Features")
646        .style("Heading1");
647    let features = [
648        "Page setup: size, margins, header/footer distance, gutter",
649        "Document metadata: title, author, subject, keywords",
650        "Headers and footers: text, images, different first page",
651        "Background images (full-page behind text)",
652        "Table of Contents generation with bookmarks",
653        "Text formatting: bold, italic, underline styles, strike, color, size, font",
654        "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
655        "Paragraph formatting: alignment, borders, shading, spacing, indentation",
656        "Pagination controls: keep-with-next, keep-together, widow control",
657        "Bullet and numbered lists with nesting",
658        "Tab stops with dot/underscore/hyphen leaders",
659        "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
660        "Row properties: header rows, exact height, cant-split",
661        "Cell properties: width, no-wrap, vertical alignment",
662        "Inline images and header images",
663        "Placeholder replacement (single and batch)",
664        "Regex-based find and replace",
665        "Content insertion at specific positions",
666        "Section breaks with mixed portrait/landscape",
667        "Custom paragraph and character styles",
668        "Document intelligence: word count, headings, outline, images, links",
669        "Accessibility audit",
670        "Document merging (append)",
671        "Export: DOCX, PDF, HTML, Markdown",
672    ];
673    for f in &features {
674        doc.add_bullet_list_item(f, 0);
675    }
676    doc.add_paragraph("");
677    doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
678        .alignment(Alignment::Center)
679        .shading("E2EFDA")
680        .border_all(BorderStyle::Single, 2, "00AA00");
681
682    doc
683}
684
685// =============================================================================
686// 2. PROPOSAL DOCUMENT — Deep navy + gold scheme
687// =============================================================================
688fn generate_proposal(_samples_dir: &Path) -> Document {
689    let mut doc = Document::new();
690
691    // Colors: Navy #1B2A4A, Gold #C5922E, Light #F4F1EB
692    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
693    doc.set_margins(
694        Length::inches(1.0),
695        Length::inches(1.0),
696        Length::inches(1.0),
697        Length::inches(1.0),
698    );
699    doc.set_title("AI Platform Modernization Proposal");
700    doc.set_author("Walter White");
701    doc.set_subject("Technology Proposal");
702    doc.set_keywords("proposal, AI, modernization, Tensorbee");
703
704    // Banner header
705    let logo_img = create_logo_png(220, 48);
706    let banner = build_header_banner_xml(
707        "rId1",
708        &BannerOpts {
709            bg_color: "1B2A4A",
710            banner_width: 7772400,
711            banner_height: 914400,
712            logo_width: 2011680,
713            logo_height: 438912,
714            logo_x_offset: 295125,
715            logo_y_offset: 237744,
716        },
717    );
718    doc.set_raw_header_with_images(
719        banner,
720        &[("rId1", &logo_img, "logo.png")],
721        rdocx_oxml::header_footer::HdrFtrType::Default,
722    );
723    doc.set_different_first_page(true);
724    let cover_banner = build_header_banner_xml(
725        "rId1",
726        &BannerOpts {
727            bg_color: "C5922E",
728            banner_width: 7772400,
729            banner_height: 914400,
730            logo_width: 2011680,
731            logo_height: 438912,
732            logo_x_offset: 295125,
733            logo_y_offset: 237744,
734        },
735    );
736    doc.set_raw_header_with_images(
737        cover_banner,
738        &[("rId1", &logo_img, "logo.png")],
739        rdocx_oxml::header_footer::HdrFtrType::First,
740    );
741    doc.set_margins(
742        Length::twips(2292),
743        Length::twips(1440),
744        Length::twips(1440),
745        Length::twips(1440),
746    );
747    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
748    doc.set_footer("Tensorbee — Confidential");
749
750    // Custom styles
751    doc.add_style(
752        StyleBuilder::paragraph("ProposalTitle", "Proposal Title")
753            .based_on("Normal")
754            .run_properties({
755                let mut rpr = rdocx_oxml::properties::CT_RPr::default();
756                rpr.bold = Some(true);
757                rpr.font_ascii = Some("Georgia".to_string());
758                rpr.font_hansi = Some("Georgia".to_string());
759                rpr.sz = Some(rdocx_oxml::HalfPoint(56)); // half-points → 28pt
760                rpr.color = Some("1B2A4A".to_string());
761                rpr
762            }),
763    );
764
765    // ── Cover Page ──
766    for _ in 0..4 {
767        doc.add_paragraph("");
768    }
769    doc.add_paragraph("AI Platform Modernization")
770        .style("ProposalTitle")
771        .alignment(Alignment::Center);
772    doc.add_paragraph("")
773        .alignment(Alignment::Center)
774        .border_bottom(BorderStyle::Single, 8, "C5922E");
775    {
776        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
777        p.add_run("Prepared for Global Financial Corp.")
778            .size(14.0)
779            .color("666666")
780            .italic(true);
781    }
782    {
783        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
784        p.add_run("Prepared by: Walter White, CEO — Tensorbee")
785            .size(12.0)
786            .color("888888");
787    }
788    {
789        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
790        p.add_run("February 22, 2026").size(12.0).color("888888");
791    }
792
793    // ── TOC ──
794    doc.add_paragraph("").page_break_before(true);
795    doc.add_paragraph("Table of Contents").style("Heading1");
796    // We'll add a manual-style TOC since the insert_toc goes at a specific position
797    doc.insert_toc(doc.content_count(), 2);
798
799    // ── Executive Summary ──
800    doc.add_paragraph("").page_break_before(true);
801    doc.add_paragraph("Executive Summary").style("Heading1");
802    doc.add_paragraph(
803        "Tensorbee proposes a comprehensive modernization of Global Financial Corp's AI platform, \
804         transitioning from legacy batch-processing systems to a real-time inference architecture. \
805         This transformation will reduce model deployment time from weeks to hours, improve \
806         prediction accuracy by an estimated 23%, and deliver $4.2M in annual operational savings.",
807    )
808    .first_line_indent(Length::inches(0.3));
809    doc.add_paragraph(
810        "The project spans three phases over 18 months, with a total investment of $2.8M. \
811         Tensorbee brings deep expertise in ML infrastructure, having successfully completed \
812         similar transformations for three Fortune 500 financial institutions.",
813    )
814    .first_line_indent(Length::inches(0.3));
815
816    // Key metrics table
817    doc.add_paragraph("");
818    {
819        let mut tbl = doc
820            .add_table(5, 2)
821            .borders(BorderStyle::Single, 4, "1B2A4A")
822            .width_pct(80.0);
823        tbl.cell(0, 0).unwrap().set_text("Key Metric");
824        tbl.cell(0, 0).unwrap().shading("1B2A4A");
825        tbl.cell(0, 1).unwrap().set_text("Value");
826        tbl.cell(0, 1).unwrap().shading("1B2A4A");
827        let rows = [
828            ("Total Investment", "$2.8M"),
829            ("Annual Savings", "$4.2M"),
830            ("ROI (Year 1)", "150%"),
831            ("Timeline", "18 months"),
832        ];
833        for (i, (k, v)) in rows.iter().enumerate() {
834            tbl.cell(i + 1, 0).unwrap().set_text(k);
835            if i % 2 == 0 {
836                tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
837            }
838            tbl.cell(i + 1, 1).unwrap().set_text(v);
839            if i % 2 == 0 {
840                tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
841            }
842        }
843    }
844
845    // ── Problem Statement ──
846    doc.add_paragraph("").page_break_before(true);
847    doc.add_paragraph("Problem Statement").style("Heading1");
848    doc.add_paragraph(
849        "Global Financial Corp's current AI infrastructure faces three critical challenges:",
850    );
851    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);
852    doc.add_numbered_list_item("Scalability Constraints — The batch processing architecture cannot handle real-time inference demands during peak trading hours.", 0);
853    doc.add_numbered_list_item("Technical Debt — Legacy Python 2.7 codebases and manual deployment scripts create reliability risks and slow iteration speed.", 0);
854
855    // ── Proposed Solution ──
856    doc.add_paragraph("").page_break_before(true);
857    doc.add_paragraph("Proposed Solution").style("Heading1");
858
859    doc.add_paragraph("Phase 1: Foundation (Months 1-6)")
860        .style("Heading2");
861    doc.add_bullet_list_item("Deploy Kubernetes-based ML serving infrastructure", 0);
862    doc.add_bullet_list_item("Implement CI/CD pipeline for model deployment", 0);
863    doc.add_bullet_list_item("Migrate top 5 production models to new platform", 0);
864
865    doc.add_paragraph("Phase 2: Expansion (Months 7-12)")
866        .style("Heading2");
867    doc.add_bullet_list_item(
868        "Real-time feature engineering pipeline (Apache Kafka + Flink)",
869        0,
870    );
871    doc.add_bullet_list_item("A/B testing framework for model variants", 0);
872    doc.add_bullet_list_item("Automated model monitoring and drift detection", 0);
873
874    doc.add_paragraph("Phase 3: Optimization (Months 13-18)")
875        .style("Heading2");
876    doc.add_bullet_list_item("GPU inference optimization (TensorRT/ONNX Runtime)", 0);
877    doc.add_bullet_list_item("Multi-region deployment for latency reduction", 0);
878    doc.add_bullet_list_item(
879        "Self-service model deployment portal for data science teams",
880        0,
881    );
882
883    // ── Budget ──
884    doc.add_paragraph("Budget Breakdown").style("Heading1");
885    {
886        let mut tbl = doc
887            .add_table(6, 3)
888            .borders(BorderStyle::Single, 4, "1B2A4A")
889            .width_pct(100.0);
890        tbl.cell(0, 0).unwrap().set_text("Category");
891        tbl.cell(0, 0).unwrap().shading("1B2A4A");
892        tbl.cell(0, 1).unwrap().set_text("Cost");
893        tbl.cell(0, 1).unwrap().shading("1B2A4A");
894        tbl.cell(0, 2).unwrap().set_text("Notes");
895        tbl.cell(0, 2).unwrap().shading("1B2A4A");
896        let items = [
897            (
898                "Infrastructure",
899                "$450,000",
900                "Cloud compute + GPU instances",
901            ),
902            ("Engineering Services", "$1,600,000", "12 FTE x 18 months"),
903            (
904                "Software Licenses",
905                "$350,000",
906                "Kafka, monitoring, CI/CD tools",
907            ),
908            (
909                "Training & Enablement",
910                "$200,000",
911                "Team training, documentation",
912            ),
913            ("Contingency (10%)", "$200,000", "Risk buffer"),
914        ];
915        for (i, (cat, cost, note)) in items.iter().enumerate() {
916            tbl.cell(i + 1, 0).unwrap().set_text(cat);
917            tbl.cell(i + 1, 1).unwrap().set_text(cost);
918            tbl.cell(i + 1, 2).unwrap().set_text(note);
919            if i % 2 == 0 {
920                tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
921                tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
922                tbl.cell(i + 1, 2).unwrap().shading("F4F1EB");
923            }
924        }
925    }
926
927    // ── Closing ──
928    doc.add_paragraph("");
929    doc.add_paragraph("Next Steps").style("Heading1");
930    doc.add_numbered_list_item(
931        "Schedule technical deep-dive with GFC engineering team (Week 1)",
932        0,
933    );
934    doc.add_numbered_list_item("Finalize scope and sign SOW (Week 2-3)", 0);
935    doc.add_numbered_list_item("Kick off Phase 1 with joint planning session (Week 4)", 0);
936
937    doc.add_paragraph("");
938    {
939        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
940        p.add_run("Walter White")
941            .bold(true)
942            .size(14.0)
943            .color("1B2A4A");
944    }
945    {
946        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
947        p.add_run("CEO, Tensorbee | walter@tensorbee.com")
948            .size(10.0)
949            .color("888888");
950    }
951
952    doc
953}
954
955// =============================================================================
956// 3. QUOTE / BILL OF MATERIALS — Teal + orange scheme
957// =============================================================================
958fn generate_quote(_samples_dir: &Path) -> Document {
959    let mut doc = Document::new();
960
961    // Colors: Teal #008B8B, Dark #1A3C3C, Orange #E07020, Light #F0F8F8
962    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
963    doc.set_margins(
964        Length::inches(0.75),
965        Length::inches(0.75),
966        Length::inches(0.75),
967        Length::inches(0.75),
968    );
969    doc.set_title("Quotation QT-2026-0147");
970    doc.set_author("Walter White");
971    doc.set_keywords("quote, BOM, Tensorbee");
972
973    doc.set_header("Tensorbee — Quotation");
974    doc.set_footer("QT-2026-0147 | Page");
975
976    // ── Header Block ──
977    {
978        let mut p = doc.add_paragraph("").alignment(Alignment::Left);
979        p.add_run("TENSORBEE")
980            .bold(true)
981            .size(28.0)
982            .color("008B8B")
983            .font("Helvetica");
984    }
985    doc.add_paragraph("123 Innovation Drive, Suite 400")
986        .alignment(Alignment::Left);
987    doc.add_paragraph("San Francisco, CA 94105 | +1 (415) 555-0199")
988        .alignment(Alignment::Left);
989    doc.add_paragraph("")
990        .border_bottom(BorderStyle::Single, 8, "008B8B");
991
992    // Quote meta
993    doc.add_paragraph("");
994    {
995        let mut tbl = doc.add_table(4, 4).width_pct(100.0);
996        tbl.cell(0, 0).unwrap().set_text("QUOTATION");
997        tbl.cell(0, 0).unwrap().shading("008B8B").grid_span(2);
998        tbl.cell(0, 2).unwrap().set_text("DATE");
999        tbl.cell(0, 2).unwrap().shading("008B8B");
1000        tbl.cell(0, 3).unwrap().set_text("VALID UNTIL");
1001        tbl.cell(0, 3).unwrap().shading("008B8B");
1002        tbl.cell(1, 0).unwrap().set_text("Quote #:");
1003        tbl.cell(1, 1).unwrap().set_text("QT-2026-0147");
1004        tbl.cell(1, 2).unwrap().set_text("Feb 22, 2026");
1005        tbl.cell(1, 3).unwrap().set_text("Mar 22, 2026");
1006        tbl.cell(2, 0).unwrap().set_text("Prepared by:");
1007        tbl.cell(2, 1).unwrap().set_text("Walter White");
1008        tbl.cell(2, 2).unwrap().set_text("Payment:");
1009        tbl.cell(2, 3).unwrap().set_text("Net 30");
1010        tbl.cell(3, 0).unwrap().set_text("Customer:");
1011        tbl.cell(3, 1).unwrap().set_text("Meridian Dynamics LLC");
1012        tbl.cell(3, 2).unwrap().set_text("Currency:");
1013        tbl.cell(3, 3).unwrap().set_text("USD");
1014    }
1015
1016    doc.add_paragraph("");
1017
1018    // ── Bill of Materials ──
1019    {
1020        let mut p = doc.add_paragraph("");
1021        p.add_run("Bill of Materials")
1022            .bold(true)
1023            .size(16.0)
1024            .color("1A3C3C");
1025    }
1026    doc.add_paragraph("");
1027
1028    {
1029        let mut tbl = doc
1030            .add_table(10, 6)
1031            .borders(BorderStyle::Single, 2, "008B8B")
1032            .width_pct(100.0)
1033            .layout_fixed();
1034        // Headers
1035        let hdrs = [
1036            "#",
1037            "Part Number",
1038            "Description",
1039            "Qty",
1040            "Unit Price",
1041            "Total",
1042        ];
1043        for (c, h) in hdrs.iter().enumerate() {
1044            tbl.cell(0, c).unwrap().set_text(h);
1045            tbl.cell(0, c).unwrap().shading("008B8B");
1046        }
1047
1048        let items: Vec<(&str, &str, &str, &str, &str)> = vec![
1049            (
1050                "TB-GPU-A100",
1051                "NVIDIA A100 80GB GPU",
1052                "4",
1053                "$12,500.00",
1054                "$50,000.00",
1055            ),
1056            (
1057                "TB-SRV-R750",
1058                "Dell R750xa Server Chassis",
1059                "2",
1060                "$8,200.00",
1061                "$16,400.00",
1062            ),
1063            (
1064                "TB-RAM-256G",
1065                "256GB DDR5 ECC Memory Module",
1066                "8",
1067                "$890.00",
1068                "$7,120.00",
1069            ),
1070            (
1071                "TB-SSD-3840",
1072                "3.84TB NVMe U.2 SSD",
1073                "8",
1074                "$1,150.00",
1075                "$9,200.00",
1076            ),
1077            (
1078                "TB-NET-CX7",
1079                "ConnectX-7 200GbE NIC",
1080                "4",
1081                "$1,800.00",
1082                "$7,200.00",
1083            ),
1084            (
1085                "TB-SW-48P",
1086                "48-Port 100GbE Switch",
1087                "1",
1088                "$22,000.00",
1089                "$22,000.00",
1090            ),
1091            (
1092                "TB-CAB-RACK",
1093                "42U Server Rack + PDU",
1094                "1",
1095                "$4,500.00",
1096                "$4,500.00",
1097            ),
1098            (
1099                "TB-SVC-INST",
1100                "Installation & Configuration",
1101                "1",
1102                "$8,500.00",
1103                "$8,500.00",
1104            ),
1105            (
1106                "TB-SVC-SUPP",
1107                "3-Year Premium Support",
1108                "1",
1109                "$15,000.00",
1110                "$15,000.00",
1111            ),
1112        ];
1113
1114        for (i, (pn, desc, qty, unit, total)) in items.iter().enumerate() {
1115            let row = i + 1;
1116            tbl.cell(row, 0).unwrap().set_text(&format!("{}", i + 1));
1117            tbl.cell(row, 1).unwrap().set_text(pn);
1118            tbl.cell(row, 2).unwrap().set_text(desc);
1119            tbl.cell(row, 3).unwrap().set_text(qty);
1120            tbl.cell(row, 4).unwrap().set_text(unit);
1121            tbl.cell(row, 5).unwrap().set_text(total);
1122            if i % 2 == 0 {
1123                for c in 0..6 {
1124                    tbl.cell(row, c).unwrap().shading("F0F8F8");
1125                }
1126            }
1127        }
1128    }
1129
1130    doc.add_paragraph("");
1131
1132    // ── Totals ──
1133    {
1134        let mut tbl = doc
1135            .add_table(4, 2)
1136            .borders(BorderStyle::Single, 2, "008B8B")
1137            .width(Length::inches(3.5))
1138            .alignment(Alignment::Right);
1139        tbl.cell(0, 0).unwrap().set_text("Subtotal");
1140        tbl.cell(0, 1).unwrap().set_text("$139,920.00");
1141        tbl.cell(1, 0).unwrap().set_text("Shipping & Handling");
1142        tbl.cell(1, 1).unwrap().set_text("$2,500.00");
1143        tbl.cell(2, 0).unwrap().set_text("Tax (8.625%)");
1144        tbl.cell(2, 1).unwrap().set_text("$12,068.10");
1145        tbl.cell(3, 0).unwrap().set_text("TOTAL");
1146        tbl.cell(3, 0).unwrap().shading("E07020");
1147        tbl.cell(3, 1).unwrap().set_text("$154,488.10");
1148        tbl.cell(3, 1).unwrap().shading("E07020");
1149    }
1150
1151    doc.add_paragraph("");
1152
1153    // ── Terms & Conditions ──
1154    {
1155        let mut p = doc.add_paragraph("");
1156        p.add_run("Terms & Conditions")
1157            .bold(true)
1158            .size(14.0)
1159            .color("1A3C3C");
1160    }
1161    doc.add_numbered_list_item(
1162        "This quotation is valid for 30 calendar days from the date of issue.",
1163        0,
1164    );
1165    doc.add_numbered_list_item(
1166        "All prices are in USD and exclusive of applicable taxes unless stated.",
1167        0,
1168    );
1169    doc.add_numbered_list_item("Standard lead time is 4-6 weeks from PO receipt.", 0);
1170    doc.add_numbered_list_item(
1171        "Payment terms: Net 30 from invoice date. 2% discount for payment within 10 days.",
1172        0,
1173    );
1174    doc.add_numbered_list_item(
1175        "Warranty: 3-year manufacturer warranty on all hardware components.",
1176        0,
1177    );
1178    doc.add_numbered_list_item(
1179        "Returns subject to 15% restocking fee if initiated after 14 days.",
1180        0,
1181    );
1182
1183    doc.add_paragraph("");
1184    doc.add_paragraph("")
1185        .border_bottom(BorderStyle::Single, 4, "008B8B");
1186    doc.add_paragraph("");
1187    {
1188        let mut p = doc.add_paragraph("");
1189        p.add_run("Accepted by: ").bold(true);
1190        p.add_run("___________________________ Date: ___________");
1191    }
1192    {
1193        let mut p = doc.add_paragraph("");
1194        p.add_run("Print Name: ").bold(true);
1195        p.add_run("___________________________ Title: ___________");
1196    }
1197
1198    doc
1199}
1200
1201// =============================================================================
1202// 4. INVOICE — Crimson + charcoal scheme
1203// =============================================================================
1204fn generate_invoice(_samples_dir: &Path) -> Document {
1205    let mut doc = Document::new();
1206
1207    // Colors: Crimson #B22222, Charcoal #333333, Light #FAF0F0
1208    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1209    doc.set_margins(
1210        Length::inches(0.75),
1211        Length::inches(0.75),
1212        Length::inches(0.75),
1213        Length::inches(0.75),
1214    );
1215    doc.set_title("Invoice INV-2026-0392");
1216    doc.set_author("Walter White");
1217    doc.set_keywords("invoice, Tensorbee");
1218
1219    doc.set_footer("Tensorbee — Thank you for your business!");
1220
1221    // ── Company & Invoice Header ──
1222    {
1223        let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1224        // Company name left, INVOICE right
1225        {
1226            let mut cell = tbl.cell(0, 0).unwrap();
1227            let mut p = cell.add_paragraph("");
1228            p.add_run("TENSORBEE")
1229                .bold(true)
1230                .size(32.0)
1231                .color("B22222")
1232                .font("Helvetica");
1233        }
1234        {
1235            let mut cell = tbl.cell(0, 1).unwrap();
1236            let mut p = cell.add_paragraph("");
1237            p.add_run("INVOICE").bold(true).size(32.0).color("333333");
1238        }
1239        tbl.cell(1, 0)
1240            .unwrap()
1241            .set_text("123 Innovation Drive, Suite 400");
1242        tbl.cell(1, 1).unwrap().set_text("Invoice #: INV-2026-0392");
1243        tbl.cell(2, 0).unwrap().set_text("San Francisco, CA 94105");
1244        tbl.cell(2, 1).unwrap().set_text("Date: February 22, 2026");
1245        tbl.cell(3, 0).unwrap().set_text("walter@tensorbee.com");
1246        tbl.cell(3, 1).unwrap().set_text("Due Date: March 24, 2026");
1247    }
1248
1249    doc.add_paragraph("")
1250        .border_bottom(BorderStyle::Thick, 8, "B22222");
1251    doc.add_paragraph("");
1252
1253    // ── Bill To / Ship To ──
1254    {
1255        let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1256        tbl.cell(0, 0).unwrap().set_text("BILL TO");
1257        tbl.cell(0, 0).unwrap().shading("B22222");
1258        tbl.cell(0, 1).unwrap().set_text("SHIP TO");
1259        tbl.cell(0, 1).unwrap().shading("B22222");
1260        tbl.cell(1, 0).unwrap().set_text("Meridian Dynamics LLC");
1261        tbl.cell(1, 1).unwrap().set_text("Meridian Dynamics LLC");
1262        tbl.cell(2, 0).unwrap().set_text("456 Enterprise Blvd");
1263        tbl.cell(2, 1).unwrap().set_text("Attn: Server Room B");
1264        tbl.cell(3, 0).unwrap().set_text("Austin, TX 78701");
1265        tbl.cell(3, 1)
1266            .unwrap()
1267            .set_text("456 Enterprise Blvd, Austin, TX 78701");
1268    }
1269
1270    doc.add_paragraph("");
1271
1272    // ── Line Items ──
1273    {
1274        let mut tbl = doc
1275            .add_table(8, 5)
1276            .borders(BorderStyle::Single, 2, "B22222")
1277            .width_pct(100.0);
1278        let hdrs = ["Description", "Qty", "Unit Price", "Tax", "Amount"];
1279        for (c, h) in hdrs.iter().enumerate() {
1280            tbl.cell(0, c).unwrap().set_text(h);
1281            tbl.cell(0, c).unwrap().shading("B22222");
1282        }
1283        let items = [
1284            (
1285                "ML Infrastructure Setup — Phase 1",
1286                "1",
1287                "$45,000.00",
1288                "$3,881.25",
1289                "$48,881.25",
1290            ),
1291            (
1292                "Data Pipeline Development (160 hrs)",
1293                "160",
1294                "$225.00",
1295                "$3,105.00",
1296                "$39,105.00",
1297            ),
1298            (
1299                "GPU Cluster Configuration",
1300                "1",
1301                "$12,000.00",
1302                "$1,035.00",
1303                "$13,035.00",
1304            ),
1305            (
1306                "API Gateway Implementation",
1307                "1",
1308                "$18,500.00",
1309                "$1,595.63",
1310                "$20,095.63",
1311            ),
1312            (
1313                "Load Testing & QA (80 hrs)",
1314                "80",
1315                "$195.00",
1316                "$1,345.50",
1317                "$16,945.50",
1318            ),
1319            (
1320                "Documentation & Training",
1321                "1",
1322                "$8,000.00",
1323                "$690.00",
1324                "$8,690.00",
1325            ),
1326            (
1327                "Project Management (3 months)",
1328                "3",
1329                "$6,500.00",
1330                "$1,679.63",
1331                "$21,179.63",
1332            ),
1333        ];
1334        for (i, (desc, qty, unit, tax, amt)) in items.iter().enumerate() {
1335            let r = i + 1;
1336            tbl.cell(r, 0).unwrap().set_text(desc);
1337            tbl.cell(r, 1).unwrap().set_text(qty);
1338            tbl.cell(r, 2).unwrap().set_text(unit);
1339            tbl.cell(r, 3).unwrap().set_text(tax);
1340            tbl.cell(r, 4).unwrap().set_text(amt);
1341            if i % 2 == 0 {
1342                for c in 0..5 {
1343                    tbl.cell(r, c).unwrap().shading("FAF0F0");
1344                }
1345            }
1346        }
1347    }
1348
1349    doc.add_paragraph("");
1350
1351    // ── Totals ──
1352    {
1353        let mut tbl = doc
1354            .add_table(4, 2)
1355            .borders(BorderStyle::Single, 2, "B22222")
1356            .width(Length::inches(3.0))
1357            .alignment(Alignment::Right);
1358        tbl.cell(0, 0).unwrap().set_text("Subtotal");
1359        tbl.cell(0, 1).unwrap().set_text("$154,600.00");
1360        tbl.cell(1, 0).unwrap().set_text("Tax (8.625%)");
1361        tbl.cell(1, 1).unwrap().set_text("$13,334.25");
1362        tbl.cell(2, 0).unwrap().set_text("Discount (5%)");
1363        tbl.cell(2, 1).unwrap().set_text("-$7,730.00");
1364        tbl.cell(3, 0).unwrap().set_text("AMOUNT DUE");
1365        tbl.cell(3, 0).unwrap().shading("B22222");
1366        tbl.cell(3, 1).unwrap().set_text("$160,204.25");
1367        tbl.cell(3, 1).unwrap().shading("B22222");
1368    }
1369
1370    doc.add_paragraph("");
1371    doc.add_paragraph("");
1372
1373    // ── Payment Details ──
1374    {
1375        let mut p = doc.add_paragraph("");
1376        p.add_run("Payment Details")
1377            .bold(true)
1378            .size(14.0)
1379            .color("333333");
1380    }
1381    doc.add_paragraph("Bank: Silicon Valley Bank")
1382        .indent_left(Length::inches(0.3));
1383    doc.add_paragraph("Account: Tensorbee Inc. — 0847-2953-1120")
1384        .indent_left(Length::inches(0.3));
1385    doc.add_paragraph("Routing: 121140399")
1386        .indent_left(Length::inches(0.3));
1387    doc.add_paragraph("Swift: SVBKUS6S")
1388        .indent_left(Length::inches(0.3));
1389
1390    doc.add_paragraph("");
1391
1392    doc.add_paragraph("Please include invoice number INV-2026-0392 in the payment reference.")
1393        .shading("FAF0F0")
1394        .border_all(BorderStyle::Single, 2, "B22222");
1395
1396    doc
1397}
1398
1399// =============================================================================
1400// 5. REPORT — Forest green + earth tones, with images & hierarchical sections
1401// =============================================================================
1402fn generate_report(_samples_dir: &Path) -> Document {
1403    let mut doc = Document::new();
1404
1405    // Colors: Forest #2D5016, Sage #6B8E23, Earth #8B7355, Cream #FFFAF0
1406    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1407    doc.set_margins(
1408        Length::inches(1.0),
1409        Length::inches(1.0),
1410        Length::inches(1.0),
1411        Length::inches(1.0),
1412    );
1413    doc.set_title("Q4 2025 Environmental Impact Report");
1414    doc.set_author("Walter White");
1415    doc.set_subject("Quarterly Environmental Report");
1416    doc.set_keywords("environment, sustainability, report, Tensorbee");
1417
1418    doc.set_different_first_page(true);
1419    doc.set_first_page_header("");
1420    doc.set_header("Tensorbee — Q4 2025 Environmental Impact Report");
1421    doc.set_footer("CONFIDENTIAL — Page");
1422
1423    // ── Cover ──
1424    let cover_bg = create_sample_png(612, 792, [20, 50, 15]);
1425    doc.add_background_image(&cover_bg, "report_cover.png");
1426
1427    for _ in 0..5 {
1428        doc.add_paragraph("");
1429    }
1430    {
1431        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1432        p.add_run("Q4 2025")
1433            .bold(true)
1434            .size(48.0)
1435            .color("FFFFFF")
1436            .font("Georgia");
1437    }
1438    {
1439        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1440        p.add_run("Environmental Impact Report")
1441            .size(24.0)
1442            .color("90EE90")
1443            .font("Georgia")
1444            .italic(true);
1445    }
1446    doc.add_paragraph("");
1447    {
1448        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1449        p.add_run("Tensorbee — Sustainability Division")
1450            .size(14.0)
1451            .color("C0C0C0");
1452    }
1453    {
1454        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1455        p.add_run("Prepared by Walter White, Chief Sustainability Officer")
1456            .size(11.0)
1457            .color("AAAAAA");
1458    }
1459
1460    // ── TOC ──
1461    doc.add_paragraph("").page_break_before(true);
1462    doc.insert_toc(doc.content_count(), 3);
1463
1464    // ── Section 1: Executive Overview ──
1465    doc.add_paragraph("").page_break_before(true);
1466    doc.add_paragraph("Executive Overview").style("Heading1");
1467    doc.add_paragraph(
1468        "This report presents Tensorbee's environmental performance for Q4 2025. Our \
1469         sustainability initiatives have yielded a 34% reduction in carbon emissions compared \
1470         to Q4 2024, exceeding our target of 25%. Key achievements include the transition to \
1471         100% renewable energy in our primary data centers and the launch of our carbon offset \
1472         marketplace.",
1473    )
1474    .first_line_indent(Length::inches(0.3));
1475
1476    // Image: performance chart
1477    let chart_img = create_chart_png(400, 200);
1478    doc.add_paragraph("");
1479    doc.add_picture(
1480        &chart_img,
1481        "performance_chart.png",
1482        Length::inches(5.0),
1483        Length::inches(2.5),
1484    )
1485    .alignment(Alignment::Center);
1486    {
1487        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1488        p.add_run("Figure 1: Quarterly Carbon Emissions (tonnes CO2e)")
1489            .italic(true)
1490            .size(9.0)
1491            .color("666666");
1492    }
1493
1494    // ── Section 2: Energy Consumption ──
1495    doc.add_paragraph("").page_break_before(true);
1496    doc.add_paragraph("Energy Consumption").style("Heading1");
1497
1498    doc.add_paragraph("Data Center Operations")
1499        .style("Heading2");
1500    doc.add_paragraph(
1501        "Our three primary data centers consumed a combined 4.2 GWh in Q4 2025, \
1502         a 12% reduction from Q3 2025 driven by improved cooling efficiency and \
1503         server consolidation.",
1504    );
1505
1506    // Data table
1507    {
1508        let mut tbl = doc
1509            .add_table(5, 4)
1510            .borders(BorderStyle::Single, 4, "2D5016")
1511            .width_pct(100.0);
1512        let hdrs = ["Data Center", "Capacity (MW)", "Usage (GWh)", "PUE"];
1513        for (c, h) in hdrs.iter().enumerate() {
1514            tbl.cell(0, c).unwrap().set_text(h);
1515            tbl.cell(0, c).unwrap().shading("2D5016");
1516        }
1517        let rows = [
1518            ("San Francisco (Primary)", "3.2", "1.8", "1.12"),
1519            ("Dublin (EU)", "2.1", "1.2", "1.18"),
1520            ("Singapore (APAC)", "1.8", "1.2", "1.24"),
1521            ("TOTAL", "7.1", "4.2", "1.17 avg"),
1522        ];
1523        for (i, (dc, cap, use_, pue)) in rows.iter().enumerate() {
1524            tbl.cell(i + 1, 0).unwrap().set_text(dc);
1525            tbl.cell(i + 1, 1).unwrap().set_text(cap);
1526            tbl.cell(i + 1, 2).unwrap().set_text(use_);
1527            tbl.cell(i + 1, 3).unwrap().set_text(pue);
1528            if i == 3 {
1529                for c in 0..4 {
1530                    tbl.cell(i + 1, c).unwrap().shading("E2EFDA");
1531                }
1532            }
1533        }
1534    }
1535
1536    doc.add_paragraph("");
1537
1538    doc.add_paragraph("Renewable Energy Mix").style("Heading2");
1539    doc.add_paragraph("Breakdown of energy sources across all facilities:");
1540
1541    doc.add_bullet_list_item("Solar PV: 42% (1.76 GWh)", 0);
1542    doc.add_bullet_list_item("Wind Power Purchase Agreements: 38% (1.60 GWh)", 0);
1543    doc.add_bullet_list_item("Hydroelectric: 12% (0.50 GWh)", 0);
1544    doc.add_bullet_list_item("Grid (non-renewable): 8% (0.34 GWh)", 0);
1545
1546    // ── Section 3: Water & Waste ──
1547    doc.add_paragraph("Water & Waste Management")
1548        .style("Heading1");
1549
1550    doc.add_paragraph("Water Usage").style("Heading2");
1551    doc.add_paragraph(
1552        "Total water consumption was 12.4 million gallons, a 15% reduction from Q3. \
1553         Our closed-loop cooling systems now recycle 78% of water used in cooling operations.",
1554    );
1555
1556    doc.add_paragraph("Waste Reduction").style("Heading2");
1557    doc.add_paragraph("E-waste management results for Q4:")
1558        .keep_with_next(true);
1559    {
1560        let mut tbl = doc
1561            .add_table(4, 3)
1562            .borders(BorderStyle::Single, 2, "6B8E23")
1563            .width_pct(80.0);
1564        tbl.cell(0, 0).unwrap().set_text("Category");
1565        tbl.cell(0, 0).unwrap().shading("6B8E23");
1566        tbl.cell(0, 1).unwrap().set_text("Weight (kg)");
1567        tbl.cell(0, 1).unwrap().shading("6B8E23");
1568        tbl.cell(0, 2).unwrap().set_text("Recycled %");
1569        tbl.cell(0, 2).unwrap().shading("6B8E23");
1570        let rows = [
1571            ("Server Hardware", "2,450", "94%"),
1572            ("Networking Equipment", "820", "91%"),
1573            ("Storage Media", "340", "99%"),
1574        ];
1575        for (i, (cat, wt, pct)) in rows.iter().enumerate() {
1576            tbl.cell(i + 1, 0).unwrap().set_text(cat);
1577            tbl.cell(i + 1, 1).unwrap().set_text(wt);
1578            tbl.cell(i + 1, 2).unwrap().set_text(pct);
1579        }
1580    }
1581
1582    // ── Section 4: Initiatives ──
1583    doc.add_paragraph("").page_break_before(true);
1584    doc.add_paragraph("Q1 2026 Initiatives").style("Heading1");
1585
1586    doc.add_paragraph("Planned Programs").style("Heading2");
1587    doc.add_numbered_list_item(
1588        "Deploy on-site battery storage at SF data center (2 MWh capacity)",
1589        0,
1590    );
1591    doc.add_numbered_list_item("Pilot immersion cooling in Dublin facility", 0);
1592    doc.add_numbered_list_item("Launch employee commute carbon offset program", 0);
1593    doc.add_numbered_list_item("Achieve ISO 14001 certification for Singapore facility", 0);
1594
1595    doc.add_paragraph("Investment Targets").style("Heading2");
1596    doc.add_paragraph("Sustainability CapEx allocation for FY2026:")
1597        .keep_with_next(true);
1598    {
1599        let mut tbl = doc
1600            .add_table(5, 2)
1601            .borders(BorderStyle::Single, 4, "2D5016");
1602        tbl.cell(0, 0).unwrap().set_text("Initiative");
1603        tbl.cell(0, 0).unwrap().shading("2D5016");
1604        tbl.cell(0, 1).unwrap().set_text("Budget");
1605        tbl.cell(0, 1).unwrap().shading("2D5016");
1606        let rows = [
1607            ("Battery Storage", "$1.2M"),
1608            ("Immersion Cooling Pilot", "$800K"),
1609            ("Solar Panel Expansion", "$2.1M"),
1610            ("Carbon Credits", "$500K"),
1611        ];
1612        for (i, (init, budget)) in rows.iter().enumerate() {
1613            tbl.cell(i + 1, 0).unwrap().set_text(init);
1614            tbl.cell(i + 1, 1).unwrap().set_text(budget);
1615            if i % 2 == 0 {
1616                tbl.cell(i + 1, 0).unwrap().shading("FFFAF0");
1617                tbl.cell(i + 1, 1).unwrap().shading("FFFAF0");
1618            }
1619        }
1620    }
1621
1622    // Image: sustainability roadmap
1623    let roadmap_img = create_sample_png(500, 100, [40, 80, 30]);
1624    doc.add_paragraph("");
1625    doc.add_picture(
1626        &roadmap_img,
1627        "roadmap.png",
1628        Length::inches(6.0),
1629        Length::inches(1.2),
1630    )
1631    .alignment(Alignment::Center);
1632    {
1633        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1634        p.add_run("Figure 2: 2026 Sustainability Roadmap")
1635            .italic(true)
1636            .size(9.0)
1637            .color("666666");
1638    }
1639
1640    doc.add_paragraph("");
1641    doc.add_paragraph(
1642        "For questions about this report, contact Walter White at sustainability@tensorbee.com.",
1643    )
1644    .shading("FFFAF0")
1645    .border_all(BorderStyle::Single, 2, "2D5016");
1646
1647    doc
1648}
1649
1650// =============================================================================
1651// 6. LETTER — Slate blue + silver scheme
1652// =============================================================================
1653fn generate_letter(_samples_dir: &Path) -> Document {
1654    let mut doc = Document::new();
1655
1656    // Colors: Slate #4A5568, Blue accent #3182CE
1657    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1658    doc.set_margins(
1659        Length::inches(1.25),
1660        Length::inches(1.25),
1661        Length::inches(1.25),
1662        Length::inches(1.25),
1663    );
1664    doc.set_title("Business Letter");
1665    doc.set_author("Walter White");
1666
1667    // Banner header using raw XML
1668    let logo_img = create_logo_png(220, 48);
1669    let banner = build_header_banner_xml(
1670        "rId1",
1671        &BannerOpts {
1672            bg_color: "4A5568",
1673            banner_width: 7772400,
1674            banner_height: 731520, // ~0.8"
1675            logo_width: 2011680,
1676            logo_height: 438912,
1677            logo_x_offset: 295125,
1678            logo_y_offset: 146304,
1679        },
1680    );
1681    doc.set_raw_header_with_images(
1682        banner,
1683        &[("rId1", &logo_img, "logo.png")],
1684        rdocx_oxml::header_footer::HdrFtrType::Default,
1685    );
1686    doc.set_margins(
1687        Length::twips(2000),
1688        Length::twips(1800),
1689        Length::twips(1440),
1690        Length::twips(1800),
1691    );
1692    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
1693
1694    // Footer with contact
1695    doc.set_footer(
1696        "Tensorbee | 123 Innovation Drive, Suite 400 | San Francisco, CA 94105 | tensorbee.com",
1697    );
1698
1699    // ── Sender Address ──
1700    doc.add_paragraph("Walter White");
1701    doc.add_paragraph("Chief Executive Officer");
1702    doc.add_paragraph("Tensorbee");
1703    doc.add_paragraph("123 Innovation Drive, Suite 400");
1704    doc.add_paragraph("San Francisco, CA 94105");
1705    doc.add_paragraph("");
1706    doc.add_paragraph("February 22, 2026");
1707
1708    doc.add_paragraph("");
1709
1710    // ── Recipient ──
1711    doc.add_paragraph("Ms. Sarah Chen");
1712    doc.add_paragraph("VP of Engineering");
1713    doc.add_paragraph("Meridian Dynamics LLC");
1714    doc.add_paragraph("456 Enterprise Boulevard");
1715    doc.add_paragraph("Austin, TX 78701");
1716
1717    doc.add_paragraph("");
1718
1719    // ── Subject ──
1720    {
1721        let mut p = doc.add_paragraph("");
1722        p.add_run("Re: Strategic Technology Partnership — Phase 2 Expansion")
1723            .bold(true);
1724    }
1725
1726    doc.add_paragraph("");
1727
1728    // ── Body ──
1729    doc.add_paragraph("Dear Ms. Chen,");
1730    doc.add_paragraph("");
1731
1732    doc.add_paragraph(
1733        "I am writing to express Tensorbee's enthusiasm for expanding our strategic technology \
1734         partnership with Meridian Dynamics. Following the successful completion of Phase 1, \
1735         which delivered a 40% improvement in your ML inference pipeline throughput, we believe \
1736         the foundation is firmly established for an ambitious Phase 2 engagement.",
1737    )
1738    .first_line_indent(Length::inches(0.5));
1739
1740    doc.add_paragraph(
1741        "During our recent executive review, your team highlighted three priority areas for \
1742         the next phase of collaboration:",
1743    )
1744    .first_line_indent(Length::inches(0.5));
1745
1746    doc.add_numbered_list_item(
1747        "Real-time anomaly detection for your financial transaction monitoring system, \
1748         targeting sub-100ms latency at 50,000 transactions per second.",
1749        0,
1750    );
1751    doc.add_numbered_list_item(
1752        "Federated learning infrastructure to enable model training across your distributed \
1753         data centers without centralizing sensitive financial data.",
1754        0,
1755    );
1756    doc.add_numbered_list_item(
1757        "MLOps automation to reduce your model deployment cycle from the current 5 days \
1758         to under 4 hours.",
1759        0,
1760    );
1761
1762    doc.add_paragraph(
1763        "Our engineering team has prepared a detailed technical proposal addressing each of \
1764         these areas. We have allocated a dedicated team of eight senior engineers, led by \
1765         our CTO, to ensure continuity with the Phase 1 team your organization has already \
1766         built a productive working relationship with.",
1767    )
1768    .first_line_indent(Length::inches(0.5));
1769
1770    doc.add_paragraph(
1771        "I would welcome the opportunity to present our Phase 2 proposal in person. My \
1772         assistant will reach out to coordinate a meeting at your convenience during the \
1773         first week of March.",
1774    )
1775    .first_line_indent(Length::inches(0.5));
1776
1777    doc.add_paragraph("");
1778
1779    doc.add_paragraph("Warm regards,");
1780    doc.add_paragraph("");
1781    doc.add_paragraph("");
1782
1783    // Signature line
1784    doc.add_paragraph("")
1785        .border_bottom(BorderStyle::Single, 4, "4A5568");
1786    {
1787        let mut p = doc.add_paragraph("");
1788        p.add_run("Walter White").bold(true).size(12.0);
1789    }
1790    {
1791        let mut p = doc.add_paragraph("");
1792        p.add_run("Chief Executive Officer, Tensorbee")
1793            .italic(true)
1794            .size(10.0)
1795            .color("666666");
1796    }
1797    {
1798        let mut p = doc.add_paragraph("");
1799        p.add_run("walter@tensorbee.com | +1 (415) 555-0199")
1800            .size(10.0)
1801            .color("888888");
1802    }
1803
1804    doc
1805}
Source

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

Set the gutter margin.

Examples found in repository?
examples/generate_samples.rs (line 48)
34fn generate_feature_showcase(path: &Path) {
35    let mut doc = Document::new();
36
37    // =========================================================================
38    // PAGE SETUP & METADATA
39    // =========================================================================
40    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
41    doc.set_margins(
42        Length::inches(1.0), // top
43        Length::inches(1.0), // right
44        Length::inches(1.0), // bottom
45        Length::inches(1.0), // left
46    );
47    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
48    doc.set_gutter(Length::twips(0));
49
50    doc.set_title("rdocx Feature Showcase");
51    doc.set_author("rdocx Sample Generator");
52    doc.set_subject("Comprehensive feature demonstration");
53    doc.set_keywords("rdocx, docx, rust, sample");
54
55    // Header & Footer
56    doc.set_header("rdocx Feature Showcase");
57    doc.set_footer("Generated by rdocx — Page");
58
59    // Different first page header
60    doc.set_different_first_page(true);
61    doc.set_first_page_header("rdocx");
62    doc.set_first_page_footer("Feature Showcase — Cover Page");
63
64    // =========================================================================
65    // PAGE 1: COVER PAGE — background image, run formatting
66    // =========================================================================
67    let bg_cover = create_sample_png(612, 792, [30, 60, 120]);
68    doc.add_background_image(&bg_cover, "cover_bg.png");
69
70    doc.add_paragraph(""); // spacer
71    doc.add_paragraph(""); // spacer
72    doc.add_paragraph(""); // spacer
73
74    {
75        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
76        p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
77    }
78    {
79        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
80        p.add_run("Feature Showcase")
81            .size(28.0)
82            .color("FFFFFF")
83            .italic(true);
84    }
85
86    doc.add_paragraph(""); // spacer
87
88    {
89        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
90        p.add_run("A comprehensive demonstration of every feature")
91            .size(14.0)
92            .color("CCDDFF");
93    }
94    {
95        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
96        p.add_run("provided by the rdocx Rust crate for DOCX generation.")
97            .size(14.0)
98            .color("CCDDFF");
99    }
100
101    // =========================================================================
102    // PAGE 2: TEXT FORMATTING
103    // =========================================================================
104    doc.add_paragraph("").page_break_before(true);
105
106    doc.add_paragraph("1. Text Formatting").style("Heading1");
107
108    doc.add_paragraph("This section demonstrates paragraph and run-level formatting options.");
109    doc.add_paragraph("");
110
111    // --- Paragraph alignment ---
112    doc.add_paragraph("Paragraph Alignment").style("Heading2");
113
114    doc.add_paragraph("This paragraph is left-aligned (the default).")
115        .alignment(Alignment::Left);
116    doc.add_paragraph("This paragraph is center-aligned.")
117        .alignment(Alignment::Center);
118    doc.add_paragraph("This paragraph is right-aligned.")
119        .alignment(Alignment::Right);
120    doc.add_paragraph(
121        "This paragraph is justified. To demonstrate justified text properly, it needs \
122         to be long enough to span multiple lines so the word spacing adjustment is visible \
123         across the full width of the text area on the page.",
124    )
125    .alignment(Alignment::Justify);
126
127    doc.add_paragraph("");
128
129    // --- Run formatting ---
130    doc.add_paragraph("Run Formatting").style("Heading2");
131
132    {
133        let mut p = doc.add_paragraph("");
134        p.add_run("Bold text").bold(true);
135        p.add_run(" | ");
136        p.add_run("Italic text").italic(true);
137        p.add_run(" | ");
138        p.add_run("Bold + Italic").bold(true).italic(true);
139    }
140    {
141        let mut p = doc.add_paragraph("");
142        p.add_run("Single underline").underline(true);
143        p.add_run(" | ");
144        p.add_run("Strikethrough").strike(true);
145        p.add_run(" | ");
146        p.add_run("Double strikethrough").double_strike(true);
147    }
148    {
149        let mut p = doc.add_paragraph("");
150        p.add_run("Red text").color("FF0000");
151        p.add_run(" | ");
152        p.add_run("Blue text").color("0000FF");
153        p.add_run(" | ");
154        p.add_run("Green text").color("00AA00");
155        p.add_run(" | ");
156        p.add_run("Highlighted").highlight("FFFF00");
157    }
158    {
159        let mut p = doc.add_paragraph("");
160        p.add_run("8pt small").size(8.0);
161        p.add_run(" | ");
162        p.add_run("11pt normal").size(11.0);
163        p.add_run(" | ");
164        p.add_run("16pt large").size(16.0);
165        p.add_run(" | ");
166        p.add_run("24pt extra-large").size(24.0);
167    }
168    {
169        let mut p = doc.add_paragraph("");
170        p.add_run("Arial font").font("Arial");
171        p.add_run(" | ");
172        p.add_run("Times New Roman font").font("Times New Roman");
173        p.add_run(" | ");
174        p.add_run("Courier New font").font("Courier New");
175    }
176    {
177        let mut p = doc.add_paragraph("");
178        p.add_run("Normal");
179        p.add_run(" H").size(11.0);
180        p.add_run("2").subscript();
181        p.add_run("O (subscript)").size(11.0);
182        p.add_run(" | E = mc").size(11.0);
183        p.add_run("2").superscript();
184        p.add_run(" (superscript)").size(11.0);
185    }
186    {
187        let mut p = doc.add_paragraph("");
188        p.add_run("ALL CAPS").all_caps(true);
189        p.add_run(" | ");
190        p.add_run("Small Caps").small_caps(true);
191        p.add_run(" | ");
192        p.add_run("Expanded spacing")
193            .character_spacing(Length::twips(40));
194    }
195
196    doc.add_paragraph("");
197
198    // --- Paragraph formatting ---
199    doc.add_paragraph("Paragraph Formatting").style("Heading2");
200
201    doc.add_paragraph("Paragraph with shading (light green background)")
202        .shading("E2EFDA");
203
204    doc.add_paragraph("Paragraph with bottom border")
205        .border_bottom(BorderStyle::Single, 6, "2E75B6");
206
207    doc.add_paragraph("Paragraph with all borders")
208        .border_all(BorderStyle::Single, 4, "FF0000");
209
210    doc.add_paragraph("Paragraph with 1-inch left indent and hanging indent")
211        .indent_left(Length::inches(1.0))
212        .hanging_indent(Length::inches(0.5));
213
214    doc.add_paragraph("Paragraph with first-line indent of 0.5 inches")
215        .first_line_indent(Length::inches(0.5));
216
217    doc.add_paragraph("Paragraph with extra space before (24pt) and after (12pt)")
218        .space_before(Length::pt(24.0))
219        .space_after(Length::pt(12.0));
220
221    doc.add_paragraph(
222        "Paragraph with double line spacing. This text should have extra vertical \
223         space between lines to demonstrate the line_spacing_multiple setting.",
224    )
225    .line_spacing_multiple(2.0);
226
227    doc.add_paragraph("Paragraph with keep-with-next (won't break from the next paragraph)")
228        .keep_with_next(true);
229    doc.add_paragraph("(This stays with the paragraph above.)");
230
231    // =========================================================================
232    // PAGE 3: LISTS & TAB STOPS
233    // =========================================================================
234    doc.add_paragraph("").page_break_before(true);
235
236    doc.add_paragraph("2. Lists").style("Heading1");
237
238    doc.add_paragraph("Bullet List").style("Heading2");
239
240    doc.add_bullet_list_item("First bullet item", 0);
241    doc.add_bullet_list_item("Second bullet item", 0);
242    doc.add_bullet_list_item("Nested level 1", 1);
243    doc.add_bullet_list_item("Nested level 2", 2);
244    doc.add_bullet_list_item("Back to level 1", 1);
245    doc.add_bullet_list_item("Third bullet item", 0);
246
247    doc.add_paragraph("");
248
249    doc.add_paragraph("Numbered List").style("Heading2");
250
251    doc.add_numbered_list_item("First numbered item", 0);
252    doc.add_numbered_list_item("Second numbered item", 0);
253    doc.add_numbered_list_item("Sub-item A", 1);
254    doc.add_numbered_list_item("Sub-item B", 1);
255    doc.add_numbered_list_item("Third numbered item", 0);
256
257    doc.add_paragraph("");
258
259    // --- Tab stops ---
260    doc.add_paragraph("Tab Stops").style("Heading2");
261
262    doc.add_paragraph("Left\tCenter\tRight\tDecimal")
263        .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
264        .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
265        .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
266        .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
267
268    doc.add_paragraph("Item\t........\tPrice")
269        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
270        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
271        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
272
273    doc.add_paragraph("Widget A\t........\t$19.99")
274        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
275        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
276        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
277
278    doc.add_paragraph("Gadget B\t________\t$249.50")
279        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
280        .add_tab_stop_with_leader(
281            TabAlignment::Right,
282            Length::inches(4.0),
283            TabLeader::Underscore,
284        )
285        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
286
287    // =========================================================================
288    // PAGE 4: TABLES
289    // =========================================================================
290    doc.add_paragraph("").page_break_before(true);
291
292    doc.add_paragraph("3. Tables").style("Heading1");
293
294    // --- Basic table with borders ---
295    doc.add_paragraph("Basic Table with Borders")
296        .style("Heading2");
297
298    {
299        let mut tbl = doc.add_table(4, 3);
300        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
301
302        // Header row
303        for col in 0..3 {
304            tbl.cell(0, col).unwrap().shading("2E75B6");
305        }
306        tbl.cell(0, 0).unwrap().set_text("Name");
307        tbl.cell(0, 1).unwrap().set_text("Role");
308        tbl.cell(0, 2).unwrap().set_text("Location");
309
310        tbl.cell(1, 0).unwrap().set_text("Alice Johnson");
311        tbl.cell(1, 1).unwrap().set_text("Engineering Lead");
312        tbl.cell(1, 2).unwrap().set_text("New York");
313
314        tbl.cell(2, 0).unwrap().set_text("Bob Smith");
315        tbl.cell(2, 1).unwrap().set_text("Product Manager");
316        tbl.cell(2, 2).unwrap().set_text("San Francisco");
317
318        tbl.cell(3, 0).unwrap().set_text("Carol Davis");
319        tbl.cell(3, 1).unwrap().set_text("Designer");
320        tbl.cell(3, 2).unwrap().set_text("London");
321    }
322
323    doc.add_paragraph("");
324
325    // --- Table with cell merging ---
326    doc.add_paragraph("Table with Cell Merging & Vertical Alignment")
327        .style("Heading2");
328
329    {
330        let mut tbl = doc.add_table(4, 4);
331        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
332        tbl = tbl.width_pct(100.0);
333
334        // Header spanning all columns
335        tbl.cell(0, 0).unwrap().set_text("Quarterly Revenue Report");
336        tbl.cell(0, 0).unwrap().shading("1F4E79");
337        tbl.cell(0, 0).unwrap().grid_span(4);
338
339        // Sub-header
340        tbl.cell(1, 0).unwrap().set_text("Region");
341        tbl.cell(1, 0).unwrap().shading("D6E4F0");
342        tbl.cell(1, 1).unwrap().set_text("Q1");
343        tbl.cell(1, 1).unwrap().shading("D6E4F0");
344        tbl.cell(1, 2).unwrap().set_text("Q2");
345        tbl.cell(1, 2).unwrap().shading("D6E4F0");
346        tbl.cell(1, 3).unwrap().set_text("Total");
347        tbl.cell(1, 3).unwrap().shading("D6E4F0");
348
349        // Data
350        tbl.cell(2, 0).unwrap().set_text("North America");
351        tbl.cell(2, 1).unwrap().set_text("$2.4M");
352        tbl.cell(2, 2).unwrap().set_text("$2.7M");
353        tbl.cell(2, 3).unwrap().set_text("$5.1M");
354
355        tbl.cell(3, 0).unwrap().set_text("Europe");
356        tbl.cell(3, 1).unwrap().set_text("$1.8M");
357        tbl.cell(3, 2).unwrap().set_text("$2.0M");
358        tbl.cell(3, 3).unwrap().set_text("$3.8M");
359
360        // Vertical alignment on data cells
361        tbl.cell(2, 3)
362            .unwrap()
363            .vertical_alignment(VerticalAlignment::Center);
364        tbl.cell(3, 3)
365            .unwrap()
366            .vertical_alignment(VerticalAlignment::Bottom);
367    }
368
369    doc.add_paragraph("");
370
371    // --- Table with vertical merge ---
372    doc.add_paragraph("Table with Vertical Merge")
373        .style("Heading2");
374
375    {
376        let mut tbl = doc.add_table(4, 3);
377        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
378
379        tbl.cell(0, 0).unwrap().set_text("Category");
380        tbl.cell(0, 0).unwrap().shading("E2EFDA");
381        tbl.cell(0, 1).unwrap().set_text("Item");
382        tbl.cell(0, 1).unwrap().shading("E2EFDA");
383        tbl.cell(0, 2).unwrap().set_text("Price");
384        tbl.cell(0, 2).unwrap().shading("E2EFDA");
385
386        // "Hardware" spans rows 1-2
387        tbl.cell(1, 0).unwrap().set_text("Hardware");
388        tbl.cell(1, 0).unwrap().v_merge_restart();
389        tbl.cell(1, 1).unwrap().set_text("Laptop");
390        tbl.cell(1, 2).unwrap().set_text("$1,200");
391
392        tbl.cell(2, 0).unwrap().v_merge_continue();
393        tbl.cell(2, 1).unwrap().set_text("Monitor");
394        tbl.cell(2, 2).unwrap().set_text("$450");
395
396        // "Software" on row 3
397        tbl.cell(3, 0).unwrap().set_text("Software");
398        tbl.cell(3, 1).unwrap().set_text("IDE License");
399        tbl.cell(3, 2).unwrap().set_text("$200/yr");
400    }
401
402    doc.add_paragraph("");
403
404    // --- Nested table ---
405    doc.add_paragraph("Nested Table").style("Heading2");
406
407    {
408        let mut tbl = doc.add_table(2, 2);
409        tbl = tbl.borders(BorderStyle::Single, 6, "2E75B6");
410
411        tbl.cell(0, 0).unwrap().set_text("Outer Cell (0,0)");
412        tbl.cell(0, 1).unwrap().set_text("Outer Cell (0,1)");
413        tbl.cell(1, 0).unwrap().set_text("Outer Cell (1,0)");
414
415        // Nested table inside cell (1,1)
416        {
417            let mut cell = tbl.cell(1, 1).unwrap();
418            cell.set_text("Contains nested table:");
419            let mut nested = cell.add_table(2, 2);
420            nested = nested.borders(BorderStyle::Single, 2, "FF6600");
421            nested.cell(0, 0).unwrap().set_text("Inner A");
422            nested.cell(0, 1).unwrap().set_text("Inner B");
423            nested.cell(1, 0).unwrap().set_text("Inner C");
424            nested.cell(1, 1).unwrap().set_text("Inner D");
425        }
426    }
427
428    // =========================================================================
429    // PAGE 5: IMAGES
430    // =========================================================================
431    doc.add_paragraph("").page_break_before(true);
432
433    doc.add_paragraph("4. Images").style("Heading1");
434
435    doc.add_paragraph("Inline Image").style("Heading2");
436
437    doc.add_paragraph("Below is an inline image (200x50 pixels, blue gradient):");
438    let inline_img = create_sample_png(200, 50, [0, 80, 200]);
439    doc.add_picture(
440        &inline_img,
441        "inline_chart.png",
442        Length::inches(3.0),
443        Length::inches(0.75),
444    );
445
446    doc.add_paragraph("");
447
448    doc.add_paragraph("Header Image").style("Heading2");
449
450    // Replace the text-only header with an image header
451    let header_img = create_sample_png(400, 40, [40, 40, 40]);
452    doc.set_header_image(
453        &header_img,
454        "header_logo.png",
455        Length::inches(2.0),
456        Length::inches(0.2),
457    );
458
459    doc.add_paragraph(
460        "The document header has been replaced with an inline image. \
461         Check the header area at the top of this page.",
462    );
463
464    doc.add_paragraph("");
465    doc.add_paragraph(
466        "Note: The cover page uses a full-page background image behind the text, \
467         demonstrated on page 1 via add_background_image().",
468    );
469
470    // =========================================================================
471    // PAGE 6: CONTENT MANIPULATION — placeholder replacement, insertion
472    // =========================================================================
473    doc.add_paragraph("").page_break_before(true);
474
475    doc.add_paragraph("5. Content Manipulation")
476        .style("Heading1");
477
478    // --- Placeholder replacement ---
479    doc.add_paragraph("Placeholder Replacement")
480        .style("Heading2");
481
482    doc.add_paragraph(
483        "Before replacement, this document contained {{customer}} and {{date}} placeholders.",
484    );
485
486    {
487        let mut p = doc.add_paragraph("");
488        p.add_run("Customer: ").bold(true);
489        p.add_run("{{customer}}");
490    }
491    {
492        let mut p = doc.add_paragraph("");
493        p.add_run("Date: ").bold(true);
494        p.add_run("{{date}}");
495    }
496    doc.add_paragraph("Reference: {{ref_number}}");
497
498    // Table with placeholders
499    {
500        let mut tbl = doc.add_table(3, 2);
501        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
502        tbl.cell(0, 0).unwrap().set_text("Field");
503        tbl.cell(0, 0).unwrap().shading("D6E4F0");
504        tbl.cell(0, 1).unwrap().set_text("Value");
505        tbl.cell(0, 1).unwrap().shading("D6E4F0");
506        tbl.cell(1, 0).unwrap().set_text("Project");
507        tbl.cell(1, 1).unwrap().set_text("{{project}}");
508        tbl.cell(2, 0).unwrap().set_text("Status");
509        tbl.cell(2, 1).unwrap().set_text("{{status}}");
510    }
511
512    // Perform replacements
513    let mut replacements = HashMap::new();
514    replacements.insert("{{customer}}", "Acme Corporation");
515    replacements.insert("{{date}}", "February 22, 2026");
516    replacements.insert("{{ref_number}}", "REF-2026-001");
517    replacements.insert("{{project}}", "Infrastructure Upgrade");
518    replacements.insert("{{status}}", "In Progress");
519    let replace_count = doc.replace_all(&replacements);
520
521    doc.add_paragraph("");
522    doc.add_paragraph(&format!(
523        "(Replaced {} placeholders above — in body text and table cells)",
524        replace_count
525    ));
526
527    doc.add_paragraph("");
528
529    // --- Content insertion ---
530    doc.add_paragraph("Content Insertion").style("Heading2");
531
532    doc.add_paragraph("Section A: First section of content.");
533    doc.add_paragraph("Section C: Third section of content.");
534
535    // Insert "Section B" between A and C
536    if let Some(idx) = doc.find_content_index("Section C") {
537        doc.insert_paragraph(
538            idx,
539            "Section B: Inserted between A and C using find_content_index().",
540        );
541    }
542
543    doc.add_paragraph("");
544    doc.add_paragraph(
545        "The paragraph above ('Section B') was inserted at a specific position \
546         using find_content_index() + insert_paragraph().",
547    );
548
549    // =========================================================================
550    // PAGE 7: LANDSCAPE — section break, wide table
551    // =========================================================================
552    doc.add_paragraph("").section_break(SectionBreak::NextPage);
553
554    doc.add_paragraph("6. Mixed Page Orientation")
555        .style("Heading1");
556
557    doc.add_paragraph(
558        "This page is in LANDSCAPE orientation. It was created using a section break \
559         followed by section_landscape(). This is useful for wide tables or charts.",
560    );
561
562    doc.add_paragraph("");
563
564    // Wide table for landscape
565    {
566        let mut tbl = doc.add_table(4, 7);
567        tbl = tbl.borders(BorderStyle::Single, 4, "2E75B6");
568
569        let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
570        for (col, h) in headers.iter().enumerate() {
571            tbl.cell(0, col).unwrap().set_text(h);
572            tbl.cell(0, col).unwrap().shading("2E75B6");
573        }
574
575        let data = [
576            [
577                "North America",
578                "$1.2M",
579                "$1.3M",
580                "$1.4M",
581                "$1.5M",
582                "$1.6M",
583                "$7.0M",
584            ],
585            [
586                "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
587            ],
588            [
589                "Asia Pacific",
590                "$0.5M",
591                "$0.6M",
592                "$0.7M",
593                "$0.7M",
594                "$0.8M",
595                "$3.3M",
596            ],
597        ];
598        for (row_idx, row_data) in data.iter().enumerate() {
599            for (col, val) in row_data.iter().enumerate() {
600                tbl.cell(row_idx + 1, col).unwrap().set_text(val);
601            }
602        }
603    }
604
605    // End landscape, return to portrait
606    doc.add_paragraph("")
607        .section_break(SectionBreak::NextPage)
608        .section_landscape();
609
610    // =========================================================================
611    // PAGE 8: BACK TO PORTRAIT — styles, final notes
612    // =========================================================================
613    doc.add_paragraph("7. Custom Styles & Summary")
614        .style("Heading1");
615
616    doc.add_paragraph(
617        "This final page is back in portrait orientation after a section break. \
618         The document has demonstrated:",
619    );
620
621    doc.add_bullet_list_item(
622        "Page setup: size, margins, header/footer distance, gutter",
623        0,
624    );
625    doc.add_bullet_list_item("Document metadata: title, author, subject, keywords", 0);
626    doc.add_bullet_list_item("Headers and footers: text, images, different first page", 0);
627    doc.add_bullet_list_item("Background images: full-page behind text", 0);
628    doc.add_bullet_list_item(
629        "Text formatting: bold, italic, underline, strike, color, size, font",
630        0,
631    );
632    doc.add_bullet_list_item(
633        "Advanced run formatting: superscript, subscript, caps, spacing",
634        0,
635    );
636    doc.add_bullet_list_item(
637        "Paragraph formatting: alignment, borders, shading, spacing, indentation",
638        0,
639    );
640    doc.add_bullet_list_item("Bullet and numbered lists with nesting levels", 0);
641    doc.add_bullet_list_item("Tab stops with dot/underscore leaders", 0);
642    doc.add_bullet_list_item(
643        "Tables: borders, shading, column spans, row spans, nesting",
644        0,
645    );
646    doc.add_bullet_list_item("Vertical alignment in table cells", 0);
647    doc.add_bullet_list_item("Inline images", 0);
648    doc.add_bullet_list_item("Placeholder replacement in body and table cells", 0);
649    doc.add_bullet_list_item("Content insertion at specific positions", 0);
650    doc.add_bullet_list_item(
651        "Section breaks with mixed portrait/landscape orientation",
652        0,
653    );
654
655    doc.add_paragraph("");
656    doc.add_paragraph("All features above were built entirely from scratch using the rdocx API.")
657        .alignment(Alignment::Center)
658        .shading("E2EFDA")
659        .border_all(BorderStyle::Single, 2, "00AA00");
660
661    doc.save(path).unwrap();
662}
More examples
Hide additional examples
examples/generate_all_samples.rs (line 98)
86fn generate_feature_showcase(_samples_dir: &Path) -> Document {
87    let mut doc = Document::new();
88
89    // ── Page Setup & Metadata ──
90    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
91    doc.set_margins(
92        Length::inches(1.0),
93        Length::inches(1.0),
94        Length::inches(1.0),
95        Length::inches(1.0),
96    );
97    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
98    doc.set_gutter(Length::twips(0));
99    doc.set_title("rdocx Feature Showcase");
100    doc.set_author("rdocx Sample Generator");
101    doc.set_subject("Comprehensive feature demonstration");
102    doc.set_keywords("rdocx, docx, rust, sample, showcase");
103
104    // Headers & Footers with different first page
105    doc.set_different_first_page(true);
106    doc.set_first_page_header("rdocx");
107    doc.set_first_page_footer("Feature Showcase — Cover Page");
108    doc.set_header("rdocx Feature Showcase");
109    doc.set_footer("Generated by rdocx");
110
111    // ── COVER PAGE ──
112    let bg = create_sample_png(612, 792, [20, 45, 90]);
113    doc.add_background_image(&bg, "cover_bg.png");
114
115    for _ in 0..3 {
116        doc.add_paragraph("");
117    }
118    {
119        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
120        p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
121    }
122    {
123        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
124        p.add_run("Complete Feature Showcase")
125            .size(28.0)
126            .color("FFFFFF")
127            .italic(true);
128    }
129    doc.add_paragraph("");
130    {
131        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
132        p.add_run("Every feature of the rdocx Rust library")
133            .size(13.0)
134            .color("B0C4DE");
135    }
136    {
137        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
138        p.add_run("demonstrated in a single document.")
139            .size(13.0)
140            .color("B0C4DE");
141    }
142
143    // ── TABLE OF CONTENTS ──
144    doc.add_paragraph("").page_break_before(true);
145    doc.insert_toc(doc.content_count(), 3);
146
147    // ── SECTION 1: TEXT FORMATTING ──
148    doc.add_paragraph("").page_break_before(true);
149    doc.add_paragraph("1. Text Formatting").style("Heading1");
150
151    // Paragraph Alignment
152    doc.add_paragraph("1.1 Paragraph Alignment")
153        .style("Heading2");
154    doc.add_paragraph("Left-aligned paragraph (default).")
155        .alignment(Alignment::Left);
156    doc.add_paragraph("Center-aligned paragraph.")
157        .alignment(Alignment::Center);
158    doc.add_paragraph("Right-aligned paragraph.")
159        .alignment(Alignment::Right);
160    doc.add_paragraph(
161        "Justified paragraph — this text is long enough to demonstrate how justified alignment \
162         distributes extra space across word gaps so lines fill the full width of the text area.",
163    )
164    .alignment(Alignment::Justify);
165
166    doc.add_paragraph("");
167
168    // Run Formatting
169    doc.add_paragraph("1.2 Run Formatting").style("Heading2");
170    {
171        let mut p = doc.add_paragraph("");
172        p.add_run("Bold").bold(true);
173        p.add_run(" | ");
174        p.add_run("Italic").italic(true);
175        p.add_run(" | ");
176        p.add_run("Bold Italic").bold(true).italic(true);
177        p.add_run(" | ");
178        p.add_run("Underline").underline(true);
179        p.add_run(" | ");
180        p.add_run("Strikethrough").strike(true);
181        p.add_run(" | ");
182        p.add_run("Double Strike").double_strike(true);
183    }
184    {
185        let mut p = doc.add_paragraph("");
186        p.add_run("Red").color("FF0000");
187        p.add_run(" | ");
188        p.add_run("Blue").color("0000FF");
189        p.add_run(" | ");
190        p.add_run("Green").color("00AA00");
191        p.add_run(" | ");
192        p.add_run("Highlighted").highlight("FFFF00");
193    }
194    {
195        let mut p = doc.add_paragraph("");
196        p.add_run("8pt").size(8.0);
197        p.add_run(" | ");
198        p.add_run("11pt").size(11.0);
199        p.add_run(" | ");
200        p.add_run("16pt").size(16.0);
201        p.add_run(" | ");
202        p.add_run("24pt").size(24.0);
203    }
204    {
205        let mut p = doc.add_paragraph("");
206        p.add_run("Arial").font("Arial");
207        p.add_run(" | ");
208        p.add_run("Times New Roman").font("Times New Roman");
209        p.add_run(" | ");
210        p.add_run("Courier New").font("Courier New");
211    }
212    {
213        let mut p = doc.add_paragraph("");
214        p.add_run("H");
215        p.add_run("2").subscript();
216        p.add_run("O (subscript) | E=mc");
217        p.add_run("2").superscript();
218        p.add_run(" (superscript)");
219    }
220    {
221        let mut p = doc.add_paragraph("");
222        p.add_run("ALL CAPS").all_caps(true);
223        p.add_run(" | ");
224        p.add_run("Small Caps").small_caps(true);
225        p.add_run(" | ");
226        p.add_run("Expanded").character_spacing(Length::twips(40));
227        p.add_run(" | ");
228        p.add_run("Hidden text (not visible)").hidden(true);
229    }
230
231    doc.add_paragraph("");
232
233    // Underline Styles
234    doc.add_paragraph("1.3 Underline Styles").style("Heading2");
235    {
236        let mut p = doc.add_paragraph("");
237        p.add_run("Single ")
238            .underline_style(rdocx::UnderlineStyle::Single);
239        p.add_run("Double ")
240            .underline_style(rdocx::UnderlineStyle::Double);
241        p.add_run("Thick ")
242            .underline_style(rdocx::UnderlineStyle::Thick);
243        p.add_run("Dotted ")
244            .underline_style(rdocx::UnderlineStyle::Dotted);
245        p.add_run("Dash ")
246            .underline_style(rdocx::UnderlineStyle::Dash);
247        p.add_run("Wave ")
248            .underline_style(rdocx::UnderlineStyle::Wave);
249    }
250
251    // ── SECTION 2: PARAGRAPH FORMATTING ──
252    doc.add_paragraph("").page_break_before(true);
253    doc.add_paragraph("2. Paragraph Formatting")
254        .style("Heading1");
255
256    doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
257    doc.add_paragraph("Paragraph with light green shading")
258        .shading("E2EFDA");
259    doc.add_paragraph("Paragraph with bottom border")
260        .border_bottom(BorderStyle::Single, 6, "2E75B6");
261    doc.add_paragraph("Paragraph with all borders (red)")
262        .border_all(BorderStyle::Single, 4, "FF0000");
263
264    doc.add_paragraph("2.2 Indentation").style("Heading2");
265    doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
266        .indent_left(Length::inches(1.0))
267        .hanging_indent(Length::inches(0.5));
268    doc.add_paragraph("0.5-inch first-line indent on this paragraph")
269        .first_line_indent(Length::inches(0.5));
270    doc.add_paragraph("Both left and right indent (0.75 inches each)")
271        .indent_left(Length::inches(0.75))
272        .indent_right(Length::inches(0.75));
273
274    doc.add_paragraph("2.3 Spacing & Line Height")
275        .style("Heading2");
276    doc.add_paragraph("Extra space before (24pt) and after (12pt)")
277        .space_before(Length::pt(24.0))
278        .space_after(Length::pt(12.0));
279    doc.add_paragraph(
280        "Double line spacing paragraph. This text should have extra vertical space between \
281         lines to demonstrate line_spacing_multiple(2.0).",
282    )
283    .line_spacing_multiple(2.0);
284    doc.add_paragraph("Exact 20pt line spacing (fixed height).")
285        .line_spacing(20.0);
286
287    doc.add_paragraph("2.4 Pagination Controls")
288        .style("Heading2");
289    doc.add_paragraph("keep_with_next — this paragraph stays with the next")
290        .keep_with_next(true);
291    doc.add_paragraph("(This paragraph was kept with the one above.)");
292    doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
293        .keep_together(true);
294    doc.add_paragraph("widow_control — prevents widow/orphan lines")
295        .widow_control(true);
296
297    // ── SECTION 3: LISTS ──
298    doc.add_paragraph("").page_break_before(true);
299    doc.add_paragraph("3. Lists").style("Heading1");
300
301    doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
302    doc.add_bullet_list_item("First item", 0);
303    doc.add_bullet_list_item("Second item", 0);
304    doc.add_bullet_list_item("Nested level 1", 1);
305    doc.add_bullet_list_item("Nested level 2", 2);
306    doc.add_bullet_list_item("Back to level 1", 1);
307    doc.add_bullet_list_item("Third item", 0);
308
309    doc.add_paragraph("");
310
311    doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
312    doc.add_numbered_list_item("First numbered item", 0);
313    doc.add_numbered_list_item("Second numbered item", 0);
314    doc.add_numbered_list_item("Sub-item A", 1);
315    doc.add_numbered_list_item("Sub-item B", 1);
316    doc.add_numbered_list_item("Third numbered item", 0);
317
318    // ── SECTION 4: TAB STOPS ──
319    doc.add_paragraph("");
320    doc.add_paragraph("4. Tab Stops").style("Heading1");
321
322    doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
323    doc.add_paragraph("Left\tCenter\tRight\tDecimal")
324        .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
325        .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
326        .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
327        .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
328
329    doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
330    doc.add_paragraph("Item\t\tPrice")
331        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
332        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
333        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
334    doc.add_paragraph("Widget\t\t$19.99")
335        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
336        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
337        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
338    doc.add_paragraph("Gadget\t\t$249.50")
339        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
340        .add_tab_stop_with_leader(
341            TabAlignment::Right,
342            Length::inches(4.0),
343            TabLeader::Underscore,
344        )
345        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
346
347    // ── SECTION 5: TABLES ──
348    doc.add_paragraph("").page_break_before(true);
349    doc.add_paragraph("5. Tables").style("Heading1");
350
351    doc.add_paragraph("5.1 Basic Table").style("Heading2");
352    {
353        let mut tbl = doc
354            .add_table(4, 3)
355            .borders(BorderStyle::Single, 4, "000000");
356        for col in 0..3 {
357            tbl.cell(0, col).unwrap().shading("2E75B6");
358        }
359        tbl.cell(0, 0).unwrap().set_text("Name");
360        tbl.cell(0, 1).unwrap().set_text("Role");
361        tbl.cell(0, 2).unwrap().set_text("Location");
362        tbl.cell(1, 0).unwrap().set_text("Walter White");
363        tbl.cell(1, 1).unwrap().set_text("CEO");
364        tbl.cell(1, 2).unwrap().set_text("Albuquerque");
365        tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
366        tbl.cell(2, 1).unwrap().set_text("CTO");
367        tbl.cell(2, 2).unwrap().set_text("Remote");
368        tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
369        tbl.cell(3, 1).unwrap().set_text("Security");
370        tbl.cell(3, 2).unwrap().set_text("Washington");
371    }
372
373    doc.add_paragraph("");
374
375    doc.add_paragraph("5.2 Column Span & Cell Shading")
376        .style("Heading2");
377    {
378        let mut tbl = doc
379            .add_table(3, 4)
380            .borders(BorderStyle::Single, 4, "000000")
381            .width_pct(100.0);
382        tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
383        tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
384        tbl.cell(1, 0).unwrap().set_text("Region");
385        tbl.cell(1, 0).unwrap().shading("D6E4F0");
386        tbl.cell(1, 1).unwrap().set_text("Q1");
387        tbl.cell(1, 1).unwrap().shading("D6E4F0");
388        tbl.cell(1, 2).unwrap().set_text("Q2");
389        tbl.cell(1, 2).unwrap().shading("D6E4F0");
390        tbl.cell(1, 3).unwrap().set_text("Total");
391        tbl.cell(1, 3).unwrap().shading("D6E4F0");
392        tbl.cell(2, 0).unwrap().set_text("Americas");
393        tbl.cell(2, 1).unwrap().set_text("$2.4M");
394        tbl.cell(2, 2).unwrap().set_text("$2.7M");
395        tbl.cell(2, 3).unwrap().set_text("$5.1M");
396    }
397
398    doc.add_paragraph("");
399
400    doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
401    {
402        let mut tbl = doc
403            .add_table(4, 3)
404            .borders(BorderStyle::Single, 4, "000000");
405        tbl.cell(0, 0).unwrap().set_text("Category");
406        tbl.cell(0, 0).unwrap().shading("E2EFDA");
407        tbl.cell(0, 1).unwrap().set_text("Item");
408        tbl.cell(0, 1).unwrap().shading("E2EFDA");
409        tbl.cell(0, 2).unwrap().set_text("Price");
410        tbl.cell(0, 2).unwrap().shading("E2EFDA");
411        tbl.cell(1, 0).unwrap().set_text("Hardware");
412        tbl.cell(1, 0).unwrap().v_merge_restart();
413        tbl.cell(1, 1).unwrap().set_text("Laptop");
414        tbl.cell(1, 2).unwrap().set_text("$1,200");
415        tbl.cell(2, 0).unwrap().v_merge_continue();
416        tbl.cell(2, 1).unwrap().set_text("Monitor");
417        tbl.cell(2, 2).unwrap().set_text("$450");
418        tbl.cell(3, 0).unwrap().set_text("Software");
419        tbl.cell(3, 1).unwrap().set_text("IDE License");
420        tbl.cell(3, 2).unwrap().set_text("$200/yr");
421    }
422
423    doc.add_paragraph("");
424
425    doc.add_paragraph("5.4 Nested Table").style("Heading2");
426    {
427        let mut tbl = doc
428            .add_table(2, 2)
429            .borders(BorderStyle::Single, 6, "2E75B6");
430        tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
431        tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
432        tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
433        {
434            let mut cell = tbl.cell(1, 1).unwrap();
435            cell.set_text("Nested table below:");
436            let mut nested = cell
437                .add_table(2, 2)
438                .borders(BorderStyle::Single, 2, "FF6600");
439            nested.cell(0, 0).unwrap().set_text("A");
440            nested.cell(0, 1).unwrap().set_text("B");
441            nested.cell(1, 0).unwrap().set_text("C");
442            nested.cell(1, 1).unwrap().set_text("D");
443        }
444    }
445
446    doc.add_paragraph("");
447
448    doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
449        .style("Heading2");
450    {
451        let mut tbl = doc
452            .add_table(2, 3)
453            .borders(BorderStyle::Single, 4, "666666");
454        tbl.row(0).unwrap().height(Length::pt(50.0)).header();
455        tbl.cell(0, 0).unwrap().set_text("Top");
456        tbl.cell(0, 0)
457            .unwrap()
458            .vertical_alignment(VerticalAlignment::Top)
459            .shading("FFF2CC");
460        tbl.cell(0, 1).unwrap().set_text("Center");
461        tbl.cell(0, 1)
462            .unwrap()
463            .vertical_alignment(VerticalAlignment::Center)
464            .shading("D9E2F3");
465        tbl.cell(0, 2).unwrap().set_text("Bottom");
466        tbl.cell(0, 2)
467            .unwrap()
468            .vertical_alignment(VerticalAlignment::Bottom)
469            .shading("E2EFDA");
470        tbl.row(1).unwrap().cant_split();
471        tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
472        tbl.cell(1, 0).unwrap().no_wrap();
473        tbl.cell(1, 1).unwrap().set_text("Fixed width");
474        tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
475        tbl.cell(1, 2).unwrap().set_text("Normal");
476    }
477
478    // ── SECTION 6: IMAGES ──
479    doc.add_paragraph("").page_break_before(true);
480    doc.add_paragraph("6. Images").style("Heading1");
481
482    doc.add_paragraph("6.1 Inline Image").style("Heading2");
483    doc.add_paragraph("A blue gradient image below:");
484    let img = create_sample_png(200, 50, [0, 80, 200]);
485    doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
486
487    doc.add_paragraph("");
488    doc.add_paragraph("6.2 Header Image").style("Heading2");
489    let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
490    doc.set_header_image(
491        &hdr_img,
492        "header_logo.png",
493        Length::inches(2.0),
494        Length::inches(0.2),
495    );
496    doc.add_paragraph("The header now contains an inline image (check the top of this page).");
497
498    doc.add_paragraph("");
499    doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
500
501    // ── SECTION 7: CONTENT MANIPULATION ──
502    doc.add_paragraph("").page_break_before(true);
503    doc.add_paragraph("7. Content Manipulation")
504        .style("Heading1");
505
506    doc.add_paragraph("7.1 Placeholder Replacement")
507        .style("Heading2");
508    doc.add_paragraph("Customer: {{customer}}");
509    doc.add_paragraph("Date: {{date}}");
510    doc.add_paragraph("Reference: {{ref_number}}");
511    {
512        let mut tbl = doc
513            .add_table(2, 2)
514            .borders(BorderStyle::Single, 4, "000000");
515        tbl.cell(0, 0).unwrap().set_text("Project");
516        tbl.cell(0, 0).unwrap().shading("D6E4F0");
517        tbl.cell(0, 1).unwrap().set_text("{{project}}");
518        tbl.cell(1, 0).unwrap().set_text("Status");
519        tbl.cell(1, 0).unwrap().shading("D6E4F0");
520        tbl.cell(1, 1).unwrap().set_text("{{status}}");
521    }
522
523    let mut replacements = HashMap::new();
524    replacements.insert("{{customer}}", "Tensorbee Inc.");
525    replacements.insert("{{date}}", "February 22, 2026");
526    replacements.insert("{{ref_number}}", "TB-2026-001");
527    replacements.insert("{{project}}", "Infrastructure Upgrade");
528    replacements.insert("{{status}}", "In Progress");
529    let n = doc.replace_all(&replacements);
530    doc.add_paragraph(&format!("({n} placeholders replaced above)"));
531
532    doc.add_paragraph("");
533
534    doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
535    doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
536    let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
537    doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
538
539    doc.add_paragraph("");
540
541    doc.add_paragraph("7.3 Content Insertion").style("Heading2");
542    doc.add_paragraph("Section A: First content.");
543    doc.add_paragraph("Section C: Third content.");
544    if let Some(idx) = doc.find_content_index("Section C") {
545        doc.insert_paragraph(
546            idx,
547            "Section B: Inserted between A and C via find_content_index().",
548        );
549    }
550
551    // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
552    doc.add_paragraph("").section_break(SectionBreak::NextPage);
553    doc.add_paragraph("8. Section Breaks & Orientation")
554        .style("Heading1");
555    doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
556
557    {
558        let mut tbl = doc
559            .add_table(3, 7)
560            .borders(BorderStyle::Single, 4, "2E75B6");
561        let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
562        for (c, h) in headers.iter().enumerate() {
563            tbl.cell(0, c).unwrap().set_text(h);
564            tbl.cell(0, c).unwrap().shading("2E75B6");
565        }
566        let data = [
567            [
568                "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
569            ],
570            [
571                "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
572            ],
573        ];
574        for (r, row) in data.iter().enumerate() {
575            for (c, v) in row.iter().enumerate() {
576                tbl.cell(r + 1, c).unwrap().set_text(v);
577            }
578        }
579    }
580
581    doc.add_paragraph("")
582        .section_break(SectionBreak::NextPage)
583        .section_landscape();
584
585    // ── SECTION 9: CUSTOM STYLES ──
586    doc.add_paragraph("9. Custom Styles").style("Heading1");
587    doc.add_style(
588        StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
589            .based_on("Normal")
590            .paragraph_properties({
591                let mut ppr = rdocx_oxml::properties::CT_PPr::default();
592                ppr.shading = Some(rdocx_oxml::properties::CT_Shd {
593                    val: "clear".to_string(),
594                    color: None,
595                    fill: Some("FFF2CC".to_string()),
596                });
597                ppr
598            })
599            .run_properties({
600                let mut rpr = rdocx_oxml::properties::CT_RPr::default();
601                rpr.bold = Some(true);
602                rpr.color = Some("C45911".to_string());
603                rpr
604            }),
605    );
606    doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
607        .style("CustomHighlight");
608
609    doc.add_paragraph("");
610
611    // ── SECTION 10: DOCUMENT INTELLIGENCE ──
612    doc.add_paragraph("10. Document Intelligence API")
613        .style("Heading1");
614
615    let wc = doc.word_count();
616    let hc = doc.headings().len();
617    let ic = doc.images().len();
618    let lc = doc.links().len();
619    doc.add_paragraph(&format!("Word count: {wc}"));
620    doc.add_paragraph(&format!("Heading count: {hc}"));
621    doc.add_paragraph(&format!("Image count: {ic}"));
622    doc.add_paragraph(&format!("Link count: {lc}"));
623
624    let outline = doc.document_outline();
625    doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
626
627    let issues = doc.audit_accessibility();
628    doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
629    for issue in &issues {
630        doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
631    }
632
633    // ── SECTION 11: DOCUMENT MERGING ──
634    doc.add_paragraph("");
635    doc.add_paragraph("11. Document Merging").style("Heading1");
636    {
637        let mut other = Document::new();
638        other.add_paragraph("This paragraph was merged from another document using append().");
639        doc.append(&other);
640    }
641
642    doc.add_paragraph("");
643
644    // ── SUMMARY ──
645    doc.add_paragraph("Summary of Demonstrated Features")
646        .style("Heading1");
647    let features = [
648        "Page setup: size, margins, header/footer distance, gutter",
649        "Document metadata: title, author, subject, keywords",
650        "Headers and footers: text, images, different first page",
651        "Background images (full-page behind text)",
652        "Table of Contents generation with bookmarks",
653        "Text formatting: bold, italic, underline styles, strike, color, size, font",
654        "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
655        "Paragraph formatting: alignment, borders, shading, spacing, indentation",
656        "Pagination controls: keep-with-next, keep-together, widow control",
657        "Bullet and numbered lists with nesting",
658        "Tab stops with dot/underscore/hyphen leaders",
659        "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
660        "Row properties: header rows, exact height, cant-split",
661        "Cell properties: width, no-wrap, vertical alignment",
662        "Inline images and header images",
663        "Placeholder replacement (single and batch)",
664        "Regex-based find and replace",
665        "Content insertion at specific positions",
666        "Section breaks with mixed portrait/landscape",
667        "Custom paragraph and character styles",
668        "Document intelligence: word count, headings, outline, images, links",
669        "Accessibility audit",
670        "Document merging (append)",
671        "Export: DOCX, PDF, HTML, Markdown",
672    ];
673    for f in &features {
674        doc.add_bullet_list_item(f, 0);
675    }
676    doc.add_paragraph("");
677    doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
678        .alignment(Alignment::Center)
679        .shading("E2EFDA")
680        .border_all(BorderStyle::Single, 2, "00AA00");
681
682    doc
683}
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_samples.rs (line 60)
34fn generate_feature_showcase(path: &Path) {
35    let mut doc = Document::new();
36
37    // =========================================================================
38    // PAGE SETUP & METADATA
39    // =========================================================================
40    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
41    doc.set_margins(
42        Length::inches(1.0), // top
43        Length::inches(1.0), // right
44        Length::inches(1.0), // bottom
45        Length::inches(1.0), // left
46    );
47    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
48    doc.set_gutter(Length::twips(0));
49
50    doc.set_title("rdocx Feature Showcase");
51    doc.set_author("rdocx Sample Generator");
52    doc.set_subject("Comprehensive feature demonstration");
53    doc.set_keywords("rdocx, docx, rust, sample");
54
55    // Header & Footer
56    doc.set_header("rdocx Feature Showcase");
57    doc.set_footer("Generated by rdocx — Page");
58
59    // Different first page header
60    doc.set_different_first_page(true);
61    doc.set_first_page_header("rdocx");
62    doc.set_first_page_footer("Feature Showcase — Cover Page");
63
64    // =========================================================================
65    // PAGE 1: COVER PAGE — background image, run formatting
66    // =========================================================================
67    let bg_cover = create_sample_png(612, 792, [30, 60, 120]);
68    doc.add_background_image(&bg_cover, "cover_bg.png");
69
70    doc.add_paragraph(""); // spacer
71    doc.add_paragraph(""); // spacer
72    doc.add_paragraph(""); // spacer
73
74    {
75        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
76        p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
77    }
78    {
79        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
80        p.add_run("Feature Showcase")
81            .size(28.0)
82            .color("FFFFFF")
83            .italic(true);
84    }
85
86    doc.add_paragraph(""); // spacer
87
88    {
89        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
90        p.add_run("A comprehensive demonstration of every feature")
91            .size(14.0)
92            .color("CCDDFF");
93    }
94    {
95        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
96        p.add_run("provided by the rdocx Rust crate for DOCX generation.")
97            .size(14.0)
98            .color("CCDDFF");
99    }
100
101    // =========================================================================
102    // PAGE 2: TEXT FORMATTING
103    // =========================================================================
104    doc.add_paragraph("").page_break_before(true);
105
106    doc.add_paragraph("1. Text Formatting").style("Heading1");
107
108    doc.add_paragraph("This section demonstrates paragraph and run-level formatting options.");
109    doc.add_paragraph("");
110
111    // --- Paragraph alignment ---
112    doc.add_paragraph("Paragraph Alignment").style("Heading2");
113
114    doc.add_paragraph("This paragraph is left-aligned (the default).")
115        .alignment(Alignment::Left);
116    doc.add_paragraph("This paragraph is center-aligned.")
117        .alignment(Alignment::Center);
118    doc.add_paragraph("This paragraph is right-aligned.")
119        .alignment(Alignment::Right);
120    doc.add_paragraph(
121        "This paragraph is justified. To demonstrate justified text properly, it needs \
122         to be long enough to span multiple lines so the word spacing adjustment is visible \
123         across the full width of the text area on the page.",
124    )
125    .alignment(Alignment::Justify);
126
127    doc.add_paragraph("");
128
129    // --- Run formatting ---
130    doc.add_paragraph("Run Formatting").style("Heading2");
131
132    {
133        let mut p = doc.add_paragraph("");
134        p.add_run("Bold text").bold(true);
135        p.add_run(" | ");
136        p.add_run("Italic text").italic(true);
137        p.add_run(" | ");
138        p.add_run("Bold + Italic").bold(true).italic(true);
139    }
140    {
141        let mut p = doc.add_paragraph("");
142        p.add_run("Single underline").underline(true);
143        p.add_run(" | ");
144        p.add_run("Strikethrough").strike(true);
145        p.add_run(" | ");
146        p.add_run("Double strikethrough").double_strike(true);
147    }
148    {
149        let mut p = doc.add_paragraph("");
150        p.add_run("Red text").color("FF0000");
151        p.add_run(" | ");
152        p.add_run("Blue text").color("0000FF");
153        p.add_run(" | ");
154        p.add_run("Green text").color("00AA00");
155        p.add_run(" | ");
156        p.add_run("Highlighted").highlight("FFFF00");
157    }
158    {
159        let mut p = doc.add_paragraph("");
160        p.add_run("8pt small").size(8.0);
161        p.add_run(" | ");
162        p.add_run("11pt normal").size(11.0);
163        p.add_run(" | ");
164        p.add_run("16pt large").size(16.0);
165        p.add_run(" | ");
166        p.add_run("24pt extra-large").size(24.0);
167    }
168    {
169        let mut p = doc.add_paragraph("");
170        p.add_run("Arial font").font("Arial");
171        p.add_run(" | ");
172        p.add_run("Times New Roman font").font("Times New Roman");
173        p.add_run(" | ");
174        p.add_run("Courier New font").font("Courier New");
175    }
176    {
177        let mut p = doc.add_paragraph("");
178        p.add_run("Normal");
179        p.add_run(" H").size(11.0);
180        p.add_run("2").subscript();
181        p.add_run("O (subscript)").size(11.0);
182        p.add_run(" | E = mc").size(11.0);
183        p.add_run("2").superscript();
184        p.add_run(" (superscript)").size(11.0);
185    }
186    {
187        let mut p = doc.add_paragraph("");
188        p.add_run("ALL CAPS").all_caps(true);
189        p.add_run(" | ");
190        p.add_run("Small Caps").small_caps(true);
191        p.add_run(" | ");
192        p.add_run("Expanded spacing")
193            .character_spacing(Length::twips(40));
194    }
195
196    doc.add_paragraph("");
197
198    // --- Paragraph formatting ---
199    doc.add_paragraph("Paragraph Formatting").style("Heading2");
200
201    doc.add_paragraph("Paragraph with shading (light green background)")
202        .shading("E2EFDA");
203
204    doc.add_paragraph("Paragraph with bottom border")
205        .border_bottom(BorderStyle::Single, 6, "2E75B6");
206
207    doc.add_paragraph("Paragraph with all borders")
208        .border_all(BorderStyle::Single, 4, "FF0000");
209
210    doc.add_paragraph("Paragraph with 1-inch left indent and hanging indent")
211        .indent_left(Length::inches(1.0))
212        .hanging_indent(Length::inches(0.5));
213
214    doc.add_paragraph("Paragraph with first-line indent of 0.5 inches")
215        .first_line_indent(Length::inches(0.5));
216
217    doc.add_paragraph("Paragraph with extra space before (24pt) and after (12pt)")
218        .space_before(Length::pt(24.0))
219        .space_after(Length::pt(12.0));
220
221    doc.add_paragraph(
222        "Paragraph with double line spacing. This text should have extra vertical \
223         space between lines to demonstrate the line_spacing_multiple setting.",
224    )
225    .line_spacing_multiple(2.0);
226
227    doc.add_paragraph("Paragraph with keep-with-next (won't break from the next paragraph)")
228        .keep_with_next(true);
229    doc.add_paragraph("(This stays with the paragraph above.)");
230
231    // =========================================================================
232    // PAGE 3: LISTS & TAB STOPS
233    // =========================================================================
234    doc.add_paragraph("").page_break_before(true);
235
236    doc.add_paragraph("2. Lists").style("Heading1");
237
238    doc.add_paragraph("Bullet List").style("Heading2");
239
240    doc.add_bullet_list_item("First bullet item", 0);
241    doc.add_bullet_list_item("Second bullet item", 0);
242    doc.add_bullet_list_item("Nested level 1", 1);
243    doc.add_bullet_list_item("Nested level 2", 2);
244    doc.add_bullet_list_item("Back to level 1", 1);
245    doc.add_bullet_list_item("Third bullet item", 0);
246
247    doc.add_paragraph("");
248
249    doc.add_paragraph("Numbered List").style("Heading2");
250
251    doc.add_numbered_list_item("First numbered item", 0);
252    doc.add_numbered_list_item("Second numbered item", 0);
253    doc.add_numbered_list_item("Sub-item A", 1);
254    doc.add_numbered_list_item("Sub-item B", 1);
255    doc.add_numbered_list_item("Third numbered item", 0);
256
257    doc.add_paragraph("");
258
259    // --- Tab stops ---
260    doc.add_paragraph("Tab Stops").style("Heading2");
261
262    doc.add_paragraph("Left\tCenter\tRight\tDecimal")
263        .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
264        .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
265        .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
266        .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
267
268    doc.add_paragraph("Item\t........\tPrice")
269        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
270        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
271        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
272
273    doc.add_paragraph("Widget A\t........\t$19.99")
274        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
275        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
276        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
277
278    doc.add_paragraph("Gadget B\t________\t$249.50")
279        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
280        .add_tab_stop_with_leader(
281            TabAlignment::Right,
282            Length::inches(4.0),
283            TabLeader::Underscore,
284        )
285        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
286
287    // =========================================================================
288    // PAGE 4: TABLES
289    // =========================================================================
290    doc.add_paragraph("").page_break_before(true);
291
292    doc.add_paragraph("3. Tables").style("Heading1");
293
294    // --- Basic table with borders ---
295    doc.add_paragraph("Basic Table with Borders")
296        .style("Heading2");
297
298    {
299        let mut tbl = doc.add_table(4, 3);
300        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
301
302        // Header row
303        for col in 0..3 {
304            tbl.cell(0, col).unwrap().shading("2E75B6");
305        }
306        tbl.cell(0, 0).unwrap().set_text("Name");
307        tbl.cell(0, 1).unwrap().set_text("Role");
308        tbl.cell(0, 2).unwrap().set_text("Location");
309
310        tbl.cell(1, 0).unwrap().set_text("Alice Johnson");
311        tbl.cell(1, 1).unwrap().set_text("Engineering Lead");
312        tbl.cell(1, 2).unwrap().set_text("New York");
313
314        tbl.cell(2, 0).unwrap().set_text("Bob Smith");
315        tbl.cell(2, 1).unwrap().set_text("Product Manager");
316        tbl.cell(2, 2).unwrap().set_text("San Francisco");
317
318        tbl.cell(3, 0).unwrap().set_text("Carol Davis");
319        tbl.cell(3, 1).unwrap().set_text("Designer");
320        tbl.cell(3, 2).unwrap().set_text("London");
321    }
322
323    doc.add_paragraph("");
324
325    // --- Table with cell merging ---
326    doc.add_paragraph("Table with Cell Merging & Vertical Alignment")
327        .style("Heading2");
328
329    {
330        let mut tbl = doc.add_table(4, 4);
331        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
332        tbl = tbl.width_pct(100.0);
333
334        // Header spanning all columns
335        tbl.cell(0, 0).unwrap().set_text("Quarterly Revenue Report");
336        tbl.cell(0, 0).unwrap().shading("1F4E79");
337        tbl.cell(0, 0).unwrap().grid_span(4);
338
339        // Sub-header
340        tbl.cell(1, 0).unwrap().set_text("Region");
341        tbl.cell(1, 0).unwrap().shading("D6E4F0");
342        tbl.cell(1, 1).unwrap().set_text("Q1");
343        tbl.cell(1, 1).unwrap().shading("D6E4F0");
344        tbl.cell(1, 2).unwrap().set_text("Q2");
345        tbl.cell(1, 2).unwrap().shading("D6E4F0");
346        tbl.cell(1, 3).unwrap().set_text("Total");
347        tbl.cell(1, 3).unwrap().shading("D6E4F0");
348
349        // Data
350        tbl.cell(2, 0).unwrap().set_text("North America");
351        tbl.cell(2, 1).unwrap().set_text("$2.4M");
352        tbl.cell(2, 2).unwrap().set_text("$2.7M");
353        tbl.cell(2, 3).unwrap().set_text("$5.1M");
354
355        tbl.cell(3, 0).unwrap().set_text("Europe");
356        tbl.cell(3, 1).unwrap().set_text("$1.8M");
357        tbl.cell(3, 2).unwrap().set_text("$2.0M");
358        tbl.cell(3, 3).unwrap().set_text("$3.8M");
359
360        // Vertical alignment on data cells
361        tbl.cell(2, 3)
362            .unwrap()
363            .vertical_alignment(VerticalAlignment::Center);
364        tbl.cell(3, 3)
365            .unwrap()
366            .vertical_alignment(VerticalAlignment::Bottom);
367    }
368
369    doc.add_paragraph("");
370
371    // --- Table with vertical merge ---
372    doc.add_paragraph("Table with Vertical Merge")
373        .style("Heading2");
374
375    {
376        let mut tbl = doc.add_table(4, 3);
377        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
378
379        tbl.cell(0, 0).unwrap().set_text("Category");
380        tbl.cell(0, 0).unwrap().shading("E2EFDA");
381        tbl.cell(0, 1).unwrap().set_text("Item");
382        tbl.cell(0, 1).unwrap().shading("E2EFDA");
383        tbl.cell(0, 2).unwrap().set_text("Price");
384        tbl.cell(0, 2).unwrap().shading("E2EFDA");
385
386        // "Hardware" spans rows 1-2
387        tbl.cell(1, 0).unwrap().set_text("Hardware");
388        tbl.cell(1, 0).unwrap().v_merge_restart();
389        tbl.cell(1, 1).unwrap().set_text("Laptop");
390        tbl.cell(1, 2).unwrap().set_text("$1,200");
391
392        tbl.cell(2, 0).unwrap().v_merge_continue();
393        tbl.cell(2, 1).unwrap().set_text("Monitor");
394        tbl.cell(2, 2).unwrap().set_text("$450");
395
396        // "Software" on row 3
397        tbl.cell(3, 0).unwrap().set_text("Software");
398        tbl.cell(3, 1).unwrap().set_text("IDE License");
399        tbl.cell(3, 2).unwrap().set_text("$200/yr");
400    }
401
402    doc.add_paragraph("");
403
404    // --- Nested table ---
405    doc.add_paragraph("Nested Table").style("Heading2");
406
407    {
408        let mut tbl = doc.add_table(2, 2);
409        tbl = tbl.borders(BorderStyle::Single, 6, "2E75B6");
410
411        tbl.cell(0, 0).unwrap().set_text("Outer Cell (0,0)");
412        tbl.cell(0, 1).unwrap().set_text("Outer Cell (0,1)");
413        tbl.cell(1, 0).unwrap().set_text("Outer Cell (1,0)");
414
415        // Nested table inside cell (1,1)
416        {
417            let mut cell = tbl.cell(1, 1).unwrap();
418            cell.set_text("Contains nested table:");
419            let mut nested = cell.add_table(2, 2);
420            nested = nested.borders(BorderStyle::Single, 2, "FF6600");
421            nested.cell(0, 0).unwrap().set_text("Inner A");
422            nested.cell(0, 1).unwrap().set_text("Inner B");
423            nested.cell(1, 0).unwrap().set_text("Inner C");
424            nested.cell(1, 1).unwrap().set_text("Inner D");
425        }
426    }
427
428    // =========================================================================
429    // PAGE 5: IMAGES
430    // =========================================================================
431    doc.add_paragraph("").page_break_before(true);
432
433    doc.add_paragraph("4. Images").style("Heading1");
434
435    doc.add_paragraph("Inline Image").style("Heading2");
436
437    doc.add_paragraph("Below is an inline image (200x50 pixels, blue gradient):");
438    let inline_img = create_sample_png(200, 50, [0, 80, 200]);
439    doc.add_picture(
440        &inline_img,
441        "inline_chart.png",
442        Length::inches(3.0),
443        Length::inches(0.75),
444    );
445
446    doc.add_paragraph("");
447
448    doc.add_paragraph("Header Image").style("Heading2");
449
450    // Replace the text-only header with an image header
451    let header_img = create_sample_png(400, 40, [40, 40, 40]);
452    doc.set_header_image(
453        &header_img,
454        "header_logo.png",
455        Length::inches(2.0),
456        Length::inches(0.2),
457    );
458
459    doc.add_paragraph(
460        "The document header has been replaced with an inline image. \
461         Check the header area at the top of this page.",
462    );
463
464    doc.add_paragraph("");
465    doc.add_paragraph(
466        "Note: The cover page uses a full-page background image behind the text, \
467         demonstrated on page 1 via add_background_image().",
468    );
469
470    // =========================================================================
471    // PAGE 6: CONTENT MANIPULATION — placeholder replacement, insertion
472    // =========================================================================
473    doc.add_paragraph("").page_break_before(true);
474
475    doc.add_paragraph("5. Content Manipulation")
476        .style("Heading1");
477
478    // --- Placeholder replacement ---
479    doc.add_paragraph("Placeholder Replacement")
480        .style("Heading2");
481
482    doc.add_paragraph(
483        "Before replacement, this document contained {{customer}} and {{date}} placeholders.",
484    );
485
486    {
487        let mut p = doc.add_paragraph("");
488        p.add_run("Customer: ").bold(true);
489        p.add_run("{{customer}}");
490    }
491    {
492        let mut p = doc.add_paragraph("");
493        p.add_run("Date: ").bold(true);
494        p.add_run("{{date}}");
495    }
496    doc.add_paragraph("Reference: {{ref_number}}");
497
498    // Table with placeholders
499    {
500        let mut tbl = doc.add_table(3, 2);
501        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
502        tbl.cell(0, 0).unwrap().set_text("Field");
503        tbl.cell(0, 0).unwrap().shading("D6E4F0");
504        tbl.cell(0, 1).unwrap().set_text("Value");
505        tbl.cell(0, 1).unwrap().shading("D6E4F0");
506        tbl.cell(1, 0).unwrap().set_text("Project");
507        tbl.cell(1, 1).unwrap().set_text("{{project}}");
508        tbl.cell(2, 0).unwrap().set_text("Status");
509        tbl.cell(2, 1).unwrap().set_text("{{status}}");
510    }
511
512    // Perform replacements
513    let mut replacements = HashMap::new();
514    replacements.insert("{{customer}}", "Acme Corporation");
515    replacements.insert("{{date}}", "February 22, 2026");
516    replacements.insert("{{ref_number}}", "REF-2026-001");
517    replacements.insert("{{project}}", "Infrastructure Upgrade");
518    replacements.insert("{{status}}", "In Progress");
519    let replace_count = doc.replace_all(&replacements);
520
521    doc.add_paragraph("");
522    doc.add_paragraph(&format!(
523        "(Replaced {} placeholders above — in body text and table cells)",
524        replace_count
525    ));
526
527    doc.add_paragraph("");
528
529    // --- Content insertion ---
530    doc.add_paragraph("Content Insertion").style("Heading2");
531
532    doc.add_paragraph("Section A: First section of content.");
533    doc.add_paragraph("Section C: Third section of content.");
534
535    // Insert "Section B" between A and C
536    if let Some(idx) = doc.find_content_index("Section C") {
537        doc.insert_paragraph(
538            idx,
539            "Section B: Inserted between A and C using find_content_index().",
540        );
541    }
542
543    doc.add_paragraph("");
544    doc.add_paragraph(
545        "The paragraph above ('Section B') was inserted at a specific position \
546         using find_content_index() + insert_paragraph().",
547    );
548
549    // =========================================================================
550    // PAGE 7: LANDSCAPE — section break, wide table
551    // =========================================================================
552    doc.add_paragraph("").section_break(SectionBreak::NextPage);
553
554    doc.add_paragraph("6. Mixed Page Orientation")
555        .style("Heading1");
556
557    doc.add_paragraph(
558        "This page is in LANDSCAPE orientation. It was created using a section break \
559         followed by section_landscape(). This is useful for wide tables or charts.",
560    );
561
562    doc.add_paragraph("");
563
564    // Wide table for landscape
565    {
566        let mut tbl = doc.add_table(4, 7);
567        tbl = tbl.borders(BorderStyle::Single, 4, "2E75B6");
568
569        let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
570        for (col, h) in headers.iter().enumerate() {
571            tbl.cell(0, col).unwrap().set_text(h);
572            tbl.cell(0, col).unwrap().shading("2E75B6");
573        }
574
575        let data = [
576            [
577                "North America",
578                "$1.2M",
579                "$1.3M",
580                "$1.4M",
581                "$1.5M",
582                "$1.6M",
583                "$7.0M",
584            ],
585            [
586                "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
587            ],
588            [
589                "Asia Pacific",
590                "$0.5M",
591                "$0.6M",
592                "$0.7M",
593                "$0.7M",
594                "$0.8M",
595                "$3.3M",
596            ],
597        ];
598        for (row_idx, row_data) in data.iter().enumerate() {
599            for (col, val) in row_data.iter().enumerate() {
600                tbl.cell(row_idx + 1, col).unwrap().set_text(val);
601            }
602        }
603    }
604
605    // End landscape, return to portrait
606    doc.add_paragraph("")
607        .section_break(SectionBreak::NextPage)
608        .section_landscape();
609
610    // =========================================================================
611    // PAGE 8: BACK TO PORTRAIT — styles, final notes
612    // =========================================================================
613    doc.add_paragraph("7. Custom Styles & Summary")
614        .style("Heading1");
615
616    doc.add_paragraph(
617        "This final page is back in portrait orientation after a section break. \
618         The document has demonstrated:",
619    );
620
621    doc.add_bullet_list_item(
622        "Page setup: size, margins, header/footer distance, gutter",
623        0,
624    );
625    doc.add_bullet_list_item("Document metadata: title, author, subject, keywords", 0);
626    doc.add_bullet_list_item("Headers and footers: text, images, different first page", 0);
627    doc.add_bullet_list_item("Background images: full-page behind text", 0);
628    doc.add_bullet_list_item(
629        "Text formatting: bold, italic, underline, strike, color, size, font",
630        0,
631    );
632    doc.add_bullet_list_item(
633        "Advanced run formatting: superscript, subscript, caps, spacing",
634        0,
635    );
636    doc.add_bullet_list_item(
637        "Paragraph formatting: alignment, borders, shading, spacing, indentation",
638        0,
639    );
640    doc.add_bullet_list_item("Bullet and numbered lists with nesting levels", 0);
641    doc.add_bullet_list_item("Tab stops with dot/underscore leaders", 0);
642    doc.add_bullet_list_item(
643        "Tables: borders, shading, column spans, row spans, nesting",
644        0,
645    );
646    doc.add_bullet_list_item("Vertical alignment in table cells", 0);
647    doc.add_bullet_list_item("Inline images", 0);
648    doc.add_bullet_list_item("Placeholder replacement in body and table cells", 0);
649    doc.add_bullet_list_item("Content insertion at specific positions", 0);
650    doc.add_bullet_list_item(
651        "Section breaks with mixed portrait/landscape orientation",
652        0,
653    );
654
655    doc.add_paragraph("");
656    doc.add_paragraph("All features above were built entirely from scratch using the rdocx API.")
657        .alignment(Alignment::Center)
658        .shading("E2EFDA")
659        .border_all(BorderStyle::Single, 2, "00AA00");
660
661    doc.save(path).unwrap();
662}
examples/generate_all_samples.rs (line 105)
86fn generate_feature_showcase(_samples_dir: &Path) -> Document {
87    let mut doc = Document::new();
88
89    // ── Page Setup & Metadata ──
90    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
91    doc.set_margins(
92        Length::inches(1.0),
93        Length::inches(1.0),
94        Length::inches(1.0),
95        Length::inches(1.0),
96    );
97    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
98    doc.set_gutter(Length::twips(0));
99    doc.set_title("rdocx Feature Showcase");
100    doc.set_author("rdocx Sample Generator");
101    doc.set_subject("Comprehensive feature demonstration");
102    doc.set_keywords("rdocx, docx, rust, sample, showcase");
103
104    // Headers & Footers with different first page
105    doc.set_different_first_page(true);
106    doc.set_first_page_header("rdocx");
107    doc.set_first_page_footer("Feature Showcase — Cover Page");
108    doc.set_header("rdocx Feature Showcase");
109    doc.set_footer("Generated by rdocx");
110
111    // ── COVER PAGE ──
112    let bg = create_sample_png(612, 792, [20, 45, 90]);
113    doc.add_background_image(&bg, "cover_bg.png");
114
115    for _ in 0..3 {
116        doc.add_paragraph("");
117    }
118    {
119        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
120        p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
121    }
122    {
123        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
124        p.add_run("Complete Feature Showcase")
125            .size(28.0)
126            .color("FFFFFF")
127            .italic(true);
128    }
129    doc.add_paragraph("");
130    {
131        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
132        p.add_run("Every feature of the rdocx Rust library")
133            .size(13.0)
134            .color("B0C4DE");
135    }
136    {
137        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
138        p.add_run("demonstrated in a single document.")
139            .size(13.0)
140            .color("B0C4DE");
141    }
142
143    // ── TABLE OF CONTENTS ──
144    doc.add_paragraph("").page_break_before(true);
145    doc.insert_toc(doc.content_count(), 3);
146
147    // ── SECTION 1: TEXT FORMATTING ──
148    doc.add_paragraph("").page_break_before(true);
149    doc.add_paragraph("1. Text Formatting").style("Heading1");
150
151    // Paragraph Alignment
152    doc.add_paragraph("1.1 Paragraph Alignment")
153        .style("Heading2");
154    doc.add_paragraph("Left-aligned paragraph (default).")
155        .alignment(Alignment::Left);
156    doc.add_paragraph("Center-aligned paragraph.")
157        .alignment(Alignment::Center);
158    doc.add_paragraph("Right-aligned paragraph.")
159        .alignment(Alignment::Right);
160    doc.add_paragraph(
161        "Justified paragraph — this text is long enough to demonstrate how justified alignment \
162         distributes extra space across word gaps so lines fill the full width of the text area.",
163    )
164    .alignment(Alignment::Justify);
165
166    doc.add_paragraph("");
167
168    // Run Formatting
169    doc.add_paragraph("1.2 Run Formatting").style("Heading2");
170    {
171        let mut p = doc.add_paragraph("");
172        p.add_run("Bold").bold(true);
173        p.add_run(" | ");
174        p.add_run("Italic").italic(true);
175        p.add_run(" | ");
176        p.add_run("Bold Italic").bold(true).italic(true);
177        p.add_run(" | ");
178        p.add_run("Underline").underline(true);
179        p.add_run(" | ");
180        p.add_run("Strikethrough").strike(true);
181        p.add_run(" | ");
182        p.add_run("Double Strike").double_strike(true);
183    }
184    {
185        let mut p = doc.add_paragraph("");
186        p.add_run("Red").color("FF0000");
187        p.add_run(" | ");
188        p.add_run("Blue").color("0000FF");
189        p.add_run(" | ");
190        p.add_run("Green").color("00AA00");
191        p.add_run(" | ");
192        p.add_run("Highlighted").highlight("FFFF00");
193    }
194    {
195        let mut p = doc.add_paragraph("");
196        p.add_run("8pt").size(8.0);
197        p.add_run(" | ");
198        p.add_run("11pt").size(11.0);
199        p.add_run(" | ");
200        p.add_run("16pt").size(16.0);
201        p.add_run(" | ");
202        p.add_run("24pt").size(24.0);
203    }
204    {
205        let mut p = doc.add_paragraph("");
206        p.add_run("Arial").font("Arial");
207        p.add_run(" | ");
208        p.add_run("Times New Roman").font("Times New Roman");
209        p.add_run(" | ");
210        p.add_run("Courier New").font("Courier New");
211    }
212    {
213        let mut p = doc.add_paragraph("");
214        p.add_run("H");
215        p.add_run("2").subscript();
216        p.add_run("O (subscript) | E=mc");
217        p.add_run("2").superscript();
218        p.add_run(" (superscript)");
219    }
220    {
221        let mut p = doc.add_paragraph("");
222        p.add_run("ALL CAPS").all_caps(true);
223        p.add_run(" | ");
224        p.add_run("Small Caps").small_caps(true);
225        p.add_run(" | ");
226        p.add_run("Expanded").character_spacing(Length::twips(40));
227        p.add_run(" | ");
228        p.add_run("Hidden text (not visible)").hidden(true);
229    }
230
231    doc.add_paragraph("");
232
233    // Underline Styles
234    doc.add_paragraph("1.3 Underline Styles").style("Heading2");
235    {
236        let mut p = doc.add_paragraph("");
237        p.add_run("Single ")
238            .underline_style(rdocx::UnderlineStyle::Single);
239        p.add_run("Double ")
240            .underline_style(rdocx::UnderlineStyle::Double);
241        p.add_run("Thick ")
242            .underline_style(rdocx::UnderlineStyle::Thick);
243        p.add_run("Dotted ")
244            .underline_style(rdocx::UnderlineStyle::Dotted);
245        p.add_run("Dash ")
246            .underline_style(rdocx::UnderlineStyle::Dash);
247        p.add_run("Wave ")
248            .underline_style(rdocx::UnderlineStyle::Wave);
249    }
250
251    // ── SECTION 2: PARAGRAPH FORMATTING ──
252    doc.add_paragraph("").page_break_before(true);
253    doc.add_paragraph("2. Paragraph Formatting")
254        .style("Heading1");
255
256    doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
257    doc.add_paragraph("Paragraph with light green shading")
258        .shading("E2EFDA");
259    doc.add_paragraph("Paragraph with bottom border")
260        .border_bottom(BorderStyle::Single, 6, "2E75B6");
261    doc.add_paragraph("Paragraph with all borders (red)")
262        .border_all(BorderStyle::Single, 4, "FF0000");
263
264    doc.add_paragraph("2.2 Indentation").style("Heading2");
265    doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
266        .indent_left(Length::inches(1.0))
267        .hanging_indent(Length::inches(0.5));
268    doc.add_paragraph("0.5-inch first-line indent on this paragraph")
269        .first_line_indent(Length::inches(0.5));
270    doc.add_paragraph("Both left and right indent (0.75 inches each)")
271        .indent_left(Length::inches(0.75))
272        .indent_right(Length::inches(0.75));
273
274    doc.add_paragraph("2.3 Spacing & Line Height")
275        .style("Heading2");
276    doc.add_paragraph("Extra space before (24pt) and after (12pt)")
277        .space_before(Length::pt(24.0))
278        .space_after(Length::pt(12.0));
279    doc.add_paragraph(
280        "Double line spacing paragraph. This text should have extra vertical space between \
281         lines to demonstrate line_spacing_multiple(2.0).",
282    )
283    .line_spacing_multiple(2.0);
284    doc.add_paragraph("Exact 20pt line spacing (fixed height).")
285        .line_spacing(20.0);
286
287    doc.add_paragraph("2.4 Pagination Controls")
288        .style("Heading2");
289    doc.add_paragraph("keep_with_next — this paragraph stays with the next")
290        .keep_with_next(true);
291    doc.add_paragraph("(This paragraph was kept with the one above.)");
292    doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
293        .keep_together(true);
294    doc.add_paragraph("widow_control — prevents widow/orphan lines")
295        .widow_control(true);
296
297    // ── SECTION 3: LISTS ──
298    doc.add_paragraph("").page_break_before(true);
299    doc.add_paragraph("3. Lists").style("Heading1");
300
301    doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
302    doc.add_bullet_list_item("First item", 0);
303    doc.add_bullet_list_item("Second item", 0);
304    doc.add_bullet_list_item("Nested level 1", 1);
305    doc.add_bullet_list_item("Nested level 2", 2);
306    doc.add_bullet_list_item("Back to level 1", 1);
307    doc.add_bullet_list_item("Third item", 0);
308
309    doc.add_paragraph("");
310
311    doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
312    doc.add_numbered_list_item("First numbered item", 0);
313    doc.add_numbered_list_item("Second numbered item", 0);
314    doc.add_numbered_list_item("Sub-item A", 1);
315    doc.add_numbered_list_item("Sub-item B", 1);
316    doc.add_numbered_list_item("Third numbered item", 0);
317
318    // ── SECTION 4: TAB STOPS ──
319    doc.add_paragraph("");
320    doc.add_paragraph("4. Tab Stops").style("Heading1");
321
322    doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
323    doc.add_paragraph("Left\tCenter\tRight\tDecimal")
324        .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
325        .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
326        .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
327        .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
328
329    doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
330    doc.add_paragraph("Item\t\tPrice")
331        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
332        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
333        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
334    doc.add_paragraph("Widget\t\t$19.99")
335        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
336        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
337        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
338    doc.add_paragraph("Gadget\t\t$249.50")
339        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
340        .add_tab_stop_with_leader(
341            TabAlignment::Right,
342            Length::inches(4.0),
343            TabLeader::Underscore,
344        )
345        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
346
347    // ── SECTION 5: TABLES ──
348    doc.add_paragraph("").page_break_before(true);
349    doc.add_paragraph("5. Tables").style("Heading1");
350
351    doc.add_paragraph("5.1 Basic Table").style("Heading2");
352    {
353        let mut tbl = doc
354            .add_table(4, 3)
355            .borders(BorderStyle::Single, 4, "000000");
356        for col in 0..3 {
357            tbl.cell(0, col).unwrap().shading("2E75B6");
358        }
359        tbl.cell(0, 0).unwrap().set_text("Name");
360        tbl.cell(0, 1).unwrap().set_text("Role");
361        tbl.cell(0, 2).unwrap().set_text("Location");
362        tbl.cell(1, 0).unwrap().set_text("Walter White");
363        tbl.cell(1, 1).unwrap().set_text("CEO");
364        tbl.cell(1, 2).unwrap().set_text("Albuquerque");
365        tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
366        tbl.cell(2, 1).unwrap().set_text("CTO");
367        tbl.cell(2, 2).unwrap().set_text("Remote");
368        tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
369        tbl.cell(3, 1).unwrap().set_text("Security");
370        tbl.cell(3, 2).unwrap().set_text("Washington");
371    }
372
373    doc.add_paragraph("");
374
375    doc.add_paragraph("5.2 Column Span & Cell Shading")
376        .style("Heading2");
377    {
378        let mut tbl = doc
379            .add_table(3, 4)
380            .borders(BorderStyle::Single, 4, "000000")
381            .width_pct(100.0);
382        tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
383        tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
384        tbl.cell(1, 0).unwrap().set_text("Region");
385        tbl.cell(1, 0).unwrap().shading("D6E4F0");
386        tbl.cell(1, 1).unwrap().set_text("Q1");
387        tbl.cell(1, 1).unwrap().shading("D6E4F0");
388        tbl.cell(1, 2).unwrap().set_text("Q2");
389        tbl.cell(1, 2).unwrap().shading("D6E4F0");
390        tbl.cell(1, 3).unwrap().set_text("Total");
391        tbl.cell(1, 3).unwrap().shading("D6E4F0");
392        tbl.cell(2, 0).unwrap().set_text("Americas");
393        tbl.cell(2, 1).unwrap().set_text("$2.4M");
394        tbl.cell(2, 2).unwrap().set_text("$2.7M");
395        tbl.cell(2, 3).unwrap().set_text("$5.1M");
396    }
397
398    doc.add_paragraph("");
399
400    doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
401    {
402        let mut tbl = doc
403            .add_table(4, 3)
404            .borders(BorderStyle::Single, 4, "000000");
405        tbl.cell(0, 0).unwrap().set_text("Category");
406        tbl.cell(0, 0).unwrap().shading("E2EFDA");
407        tbl.cell(0, 1).unwrap().set_text("Item");
408        tbl.cell(0, 1).unwrap().shading("E2EFDA");
409        tbl.cell(0, 2).unwrap().set_text("Price");
410        tbl.cell(0, 2).unwrap().shading("E2EFDA");
411        tbl.cell(1, 0).unwrap().set_text("Hardware");
412        tbl.cell(1, 0).unwrap().v_merge_restart();
413        tbl.cell(1, 1).unwrap().set_text("Laptop");
414        tbl.cell(1, 2).unwrap().set_text("$1,200");
415        tbl.cell(2, 0).unwrap().v_merge_continue();
416        tbl.cell(2, 1).unwrap().set_text("Monitor");
417        tbl.cell(2, 2).unwrap().set_text("$450");
418        tbl.cell(3, 0).unwrap().set_text("Software");
419        tbl.cell(3, 1).unwrap().set_text("IDE License");
420        tbl.cell(3, 2).unwrap().set_text("$200/yr");
421    }
422
423    doc.add_paragraph("");
424
425    doc.add_paragraph("5.4 Nested Table").style("Heading2");
426    {
427        let mut tbl = doc
428            .add_table(2, 2)
429            .borders(BorderStyle::Single, 6, "2E75B6");
430        tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
431        tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
432        tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
433        {
434            let mut cell = tbl.cell(1, 1).unwrap();
435            cell.set_text("Nested table below:");
436            let mut nested = cell
437                .add_table(2, 2)
438                .borders(BorderStyle::Single, 2, "FF6600");
439            nested.cell(0, 0).unwrap().set_text("A");
440            nested.cell(0, 1).unwrap().set_text("B");
441            nested.cell(1, 0).unwrap().set_text("C");
442            nested.cell(1, 1).unwrap().set_text("D");
443        }
444    }
445
446    doc.add_paragraph("");
447
448    doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
449        .style("Heading2");
450    {
451        let mut tbl = doc
452            .add_table(2, 3)
453            .borders(BorderStyle::Single, 4, "666666");
454        tbl.row(0).unwrap().height(Length::pt(50.0)).header();
455        tbl.cell(0, 0).unwrap().set_text("Top");
456        tbl.cell(0, 0)
457            .unwrap()
458            .vertical_alignment(VerticalAlignment::Top)
459            .shading("FFF2CC");
460        tbl.cell(0, 1).unwrap().set_text("Center");
461        tbl.cell(0, 1)
462            .unwrap()
463            .vertical_alignment(VerticalAlignment::Center)
464            .shading("D9E2F3");
465        tbl.cell(0, 2).unwrap().set_text("Bottom");
466        tbl.cell(0, 2)
467            .unwrap()
468            .vertical_alignment(VerticalAlignment::Bottom)
469            .shading("E2EFDA");
470        tbl.row(1).unwrap().cant_split();
471        tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
472        tbl.cell(1, 0).unwrap().no_wrap();
473        tbl.cell(1, 1).unwrap().set_text("Fixed width");
474        tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
475        tbl.cell(1, 2).unwrap().set_text("Normal");
476    }
477
478    // ── SECTION 6: IMAGES ──
479    doc.add_paragraph("").page_break_before(true);
480    doc.add_paragraph("6. Images").style("Heading1");
481
482    doc.add_paragraph("6.1 Inline Image").style("Heading2");
483    doc.add_paragraph("A blue gradient image below:");
484    let img = create_sample_png(200, 50, [0, 80, 200]);
485    doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
486
487    doc.add_paragraph("");
488    doc.add_paragraph("6.2 Header Image").style("Heading2");
489    let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
490    doc.set_header_image(
491        &hdr_img,
492        "header_logo.png",
493        Length::inches(2.0),
494        Length::inches(0.2),
495    );
496    doc.add_paragraph("The header now contains an inline image (check the top of this page).");
497
498    doc.add_paragraph("");
499    doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
500
501    // ── SECTION 7: CONTENT MANIPULATION ──
502    doc.add_paragraph("").page_break_before(true);
503    doc.add_paragraph("7. Content Manipulation")
504        .style("Heading1");
505
506    doc.add_paragraph("7.1 Placeholder Replacement")
507        .style("Heading2");
508    doc.add_paragraph("Customer: {{customer}}");
509    doc.add_paragraph("Date: {{date}}");
510    doc.add_paragraph("Reference: {{ref_number}}");
511    {
512        let mut tbl = doc
513            .add_table(2, 2)
514            .borders(BorderStyle::Single, 4, "000000");
515        tbl.cell(0, 0).unwrap().set_text("Project");
516        tbl.cell(0, 0).unwrap().shading("D6E4F0");
517        tbl.cell(0, 1).unwrap().set_text("{{project}}");
518        tbl.cell(1, 0).unwrap().set_text("Status");
519        tbl.cell(1, 0).unwrap().shading("D6E4F0");
520        tbl.cell(1, 1).unwrap().set_text("{{status}}");
521    }
522
523    let mut replacements = HashMap::new();
524    replacements.insert("{{customer}}", "Tensorbee Inc.");
525    replacements.insert("{{date}}", "February 22, 2026");
526    replacements.insert("{{ref_number}}", "TB-2026-001");
527    replacements.insert("{{project}}", "Infrastructure Upgrade");
528    replacements.insert("{{status}}", "In Progress");
529    let n = doc.replace_all(&replacements);
530    doc.add_paragraph(&format!("({n} placeholders replaced above)"));
531
532    doc.add_paragraph("");
533
534    doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
535    doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
536    let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
537    doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
538
539    doc.add_paragraph("");
540
541    doc.add_paragraph("7.3 Content Insertion").style("Heading2");
542    doc.add_paragraph("Section A: First content.");
543    doc.add_paragraph("Section C: Third content.");
544    if let Some(idx) = doc.find_content_index("Section C") {
545        doc.insert_paragraph(
546            idx,
547            "Section B: Inserted between A and C via find_content_index().",
548        );
549    }
550
551    // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
552    doc.add_paragraph("").section_break(SectionBreak::NextPage);
553    doc.add_paragraph("8. Section Breaks & Orientation")
554        .style("Heading1");
555    doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
556
557    {
558        let mut tbl = doc
559            .add_table(3, 7)
560            .borders(BorderStyle::Single, 4, "2E75B6");
561        let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
562        for (c, h) in headers.iter().enumerate() {
563            tbl.cell(0, c).unwrap().set_text(h);
564            tbl.cell(0, c).unwrap().shading("2E75B6");
565        }
566        let data = [
567            [
568                "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
569            ],
570            [
571                "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
572            ],
573        ];
574        for (r, row) in data.iter().enumerate() {
575            for (c, v) in row.iter().enumerate() {
576                tbl.cell(r + 1, c).unwrap().set_text(v);
577            }
578        }
579    }
580
581    doc.add_paragraph("")
582        .section_break(SectionBreak::NextPage)
583        .section_landscape();
584
585    // ── SECTION 9: CUSTOM STYLES ──
586    doc.add_paragraph("9. Custom Styles").style("Heading1");
587    doc.add_style(
588        StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
589            .based_on("Normal")
590            .paragraph_properties({
591                let mut ppr = rdocx_oxml::properties::CT_PPr::default();
592                ppr.shading = Some(rdocx_oxml::properties::CT_Shd {
593                    val: "clear".to_string(),
594                    color: None,
595                    fill: Some("FFF2CC".to_string()),
596                });
597                ppr
598            })
599            .run_properties({
600                let mut rpr = rdocx_oxml::properties::CT_RPr::default();
601                rpr.bold = Some(true);
602                rpr.color = Some("C45911".to_string());
603                rpr
604            }),
605    );
606    doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
607        .style("CustomHighlight");
608
609    doc.add_paragraph("");
610
611    // ── SECTION 10: DOCUMENT INTELLIGENCE ──
612    doc.add_paragraph("10. Document Intelligence API")
613        .style("Heading1");
614
615    let wc = doc.word_count();
616    let hc = doc.headings().len();
617    let ic = doc.images().len();
618    let lc = doc.links().len();
619    doc.add_paragraph(&format!("Word count: {wc}"));
620    doc.add_paragraph(&format!("Heading count: {hc}"));
621    doc.add_paragraph(&format!("Image count: {ic}"));
622    doc.add_paragraph(&format!("Link count: {lc}"));
623
624    let outline = doc.document_outline();
625    doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
626
627    let issues = doc.audit_accessibility();
628    doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
629    for issue in &issues {
630        doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
631    }
632
633    // ── SECTION 11: DOCUMENT MERGING ──
634    doc.add_paragraph("");
635    doc.add_paragraph("11. Document Merging").style("Heading1");
636    {
637        let mut other = Document::new();
638        other.add_paragraph("This paragraph was merged from another document using append().");
639        doc.append(&other);
640    }
641
642    doc.add_paragraph("");
643
644    // ── SUMMARY ──
645    doc.add_paragraph("Summary of Demonstrated Features")
646        .style("Heading1");
647    let features = [
648        "Page setup: size, margins, header/footer distance, gutter",
649        "Document metadata: title, author, subject, keywords",
650        "Headers and footers: text, images, different first page",
651        "Background images (full-page behind text)",
652        "Table of Contents generation with bookmarks",
653        "Text formatting: bold, italic, underline styles, strike, color, size, font",
654        "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
655        "Paragraph formatting: alignment, borders, shading, spacing, indentation",
656        "Pagination controls: keep-with-next, keep-together, widow control",
657        "Bullet and numbered lists with nesting",
658        "Tab stops with dot/underscore/hyphen leaders",
659        "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
660        "Row properties: header rows, exact height, cant-split",
661        "Cell properties: width, no-wrap, vertical alignment",
662        "Inline images and header images",
663        "Placeholder replacement (single and batch)",
664        "Regex-based find and replace",
665        "Content insertion at specific positions",
666        "Section breaks with mixed portrait/landscape",
667        "Custom paragraph and character styles",
668        "Document intelligence: word count, headings, outline, images, links",
669        "Accessibility audit",
670        "Document merging (append)",
671        "Export: DOCX, PDF, HTML, Markdown",
672    ];
673    for f in &features {
674        doc.add_bullet_list_item(f, 0);
675    }
676    doc.add_paragraph("");
677    doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
678        .alignment(Alignment::Center)
679        .shading("E2EFDA")
680        .border_all(BorderStyle::Single, 2, "00AA00");
681
682    doc
683}
684
685// =============================================================================
686// 2. PROPOSAL DOCUMENT — Deep navy + gold scheme
687// =============================================================================
688fn generate_proposal(_samples_dir: &Path) -> Document {
689    let mut doc = Document::new();
690
691    // Colors: Navy #1B2A4A, Gold #C5922E, Light #F4F1EB
692    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
693    doc.set_margins(
694        Length::inches(1.0),
695        Length::inches(1.0),
696        Length::inches(1.0),
697        Length::inches(1.0),
698    );
699    doc.set_title("AI Platform Modernization Proposal");
700    doc.set_author("Walter White");
701    doc.set_subject("Technology Proposal");
702    doc.set_keywords("proposal, AI, modernization, Tensorbee");
703
704    // Banner header
705    let logo_img = create_logo_png(220, 48);
706    let banner = build_header_banner_xml(
707        "rId1",
708        &BannerOpts {
709            bg_color: "1B2A4A",
710            banner_width: 7772400,
711            banner_height: 914400,
712            logo_width: 2011680,
713            logo_height: 438912,
714            logo_x_offset: 295125,
715            logo_y_offset: 237744,
716        },
717    );
718    doc.set_raw_header_with_images(
719        banner,
720        &[("rId1", &logo_img, "logo.png")],
721        rdocx_oxml::header_footer::HdrFtrType::Default,
722    );
723    doc.set_different_first_page(true);
724    let cover_banner = build_header_banner_xml(
725        "rId1",
726        &BannerOpts {
727            bg_color: "C5922E",
728            banner_width: 7772400,
729            banner_height: 914400,
730            logo_width: 2011680,
731            logo_height: 438912,
732            logo_x_offset: 295125,
733            logo_y_offset: 237744,
734        },
735    );
736    doc.set_raw_header_with_images(
737        cover_banner,
738        &[("rId1", &logo_img, "logo.png")],
739        rdocx_oxml::header_footer::HdrFtrType::First,
740    );
741    doc.set_margins(
742        Length::twips(2292),
743        Length::twips(1440),
744        Length::twips(1440),
745        Length::twips(1440),
746    );
747    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
748    doc.set_footer("Tensorbee — Confidential");
749
750    // Custom styles
751    doc.add_style(
752        StyleBuilder::paragraph("ProposalTitle", "Proposal Title")
753            .based_on("Normal")
754            .run_properties({
755                let mut rpr = rdocx_oxml::properties::CT_RPr::default();
756                rpr.bold = Some(true);
757                rpr.font_ascii = Some("Georgia".to_string());
758                rpr.font_hansi = Some("Georgia".to_string());
759                rpr.sz = Some(rdocx_oxml::HalfPoint(56)); // half-points → 28pt
760                rpr.color = Some("1B2A4A".to_string());
761                rpr
762            }),
763    );
764
765    // ── Cover Page ──
766    for _ in 0..4 {
767        doc.add_paragraph("");
768    }
769    doc.add_paragraph("AI Platform Modernization")
770        .style("ProposalTitle")
771        .alignment(Alignment::Center);
772    doc.add_paragraph("")
773        .alignment(Alignment::Center)
774        .border_bottom(BorderStyle::Single, 8, "C5922E");
775    {
776        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
777        p.add_run("Prepared for Global Financial Corp.")
778            .size(14.0)
779            .color("666666")
780            .italic(true);
781    }
782    {
783        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
784        p.add_run("Prepared by: Walter White, CEO — Tensorbee")
785            .size(12.0)
786            .color("888888");
787    }
788    {
789        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
790        p.add_run("February 22, 2026").size(12.0).color("888888");
791    }
792
793    // ── TOC ──
794    doc.add_paragraph("").page_break_before(true);
795    doc.add_paragraph("Table of Contents").style("Heading1");
796    // We'll add a manual-style TOC since the insert_toc goes at a specific position
797    doc.insert_toc(doc.content_count(), 2);
798
799    // ── Executive Summary ──
800    doc.add_paragraph("").page_break_before(true);
801    doc.add_paragraph("Executive Summary").style("Heading1");
802    doc.add_paragraph(
803        "Tensorbee proposes a comprehensive modernization of Global Financial Corp's AI platform, \
804         transitioning from legacy batch-processing systems to a real-time inference architecture. \
805         This transformation will reduce model deployment time from weeks to hours, improve \
806         prediction accuracy by an estimated 23%, and deliver $4.2M in annual operational savings.",
807    )
808    .first_line_indent(Length::inches(0.3));
809    doc.add_paragraph(
810        "The project spans three phases over 18 months, with a total investment of $2.8M. \
811         Tensorbee brings deep expertise in ML infrastructure, having successfully completed \
812         similar transformations for three Fortune 500 financial institutions.",
813    )
814    .first_line_indent(Length::inches(0.3));
815
816    // Key metrics table
817    doc.add_paragraph("");
818    {
819        let mut tbl = doc
820            .add_table(5, 2)
821            .borders(BorderStyle::Single, 4, "1B2A4A")
822            .width_pct(80.0);
823        tbl.cell(0, 0).unwrap().set_text("Key Metric");
824        tbl.cell(0, 0).unwrap().shading("1B2A4A");
825        tbl.cell(0, 1).unwrap().set_text("Value");
826        tbl.cell(0, 1).unwrap().shading("1B2A4A");
827        let rows = [
828            ("Total Investment", "$2.8M"),
829            ("Annual Savings", "$4.2M"),
830            ("ROI (Year 1)", "150%"),
831            ("Timeline", "18 months"),
832        ];
833        for (i, (k, v)) in rows.iter().enumerate() {
834            tbl.cell(i + 1, 0).unwrap().set_text(k);
835            if i % 2 == 0 {
836                tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
837            }
838            tbl.cell(i + 1, 1).unwrap().set_text(v);
839            if i % 2 == 0 {
840                tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
841            }
842        }
843    }
844
845    // ── Problem Statement ──
846    doc.add_paragraph("").page_break_before(true);
847    doc.add_paragraph("Problem Statement").style("Heading1");
848    doc.add_paragraph(
849        "Global Financial Corp's current AI infrastructure faces three critical challenges:",
850    );
851    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);
852    doc.add_numbered_list_item("Scalability Constraints — The batch processing architecture cannot handle real-time inference demands during peak trading hours.", 0);
853    doc.add_numbered_list_item("Technical Debt — Legacy Python 2.7 codebases and manual deployment scripts create reliability risks and slow iteration speed.", 0);
854
855    // ── Proposed Solution ──
856    doc.add_paragraph("").page_break_before(true);
857    doc.add_paragraph("Proposed Solution").style("Heading1");
858
859    doc.add_paragraph("Phase 1: Foundation (Months 1-6)")
860        .style("Heading2");
861    doc.add_bullet_list_item("Deploy Kubernetes-based ML serving infrastructure", 0);
862    doc.add_bullet_list_item("Implement CI/CD pipeline for model deployment", 0);
863    doc.add_bullet_list_item("Migrate top 5 production models to new platform", 0);
864
865    doc.add_paragraph("Phase 2: Expansion (Months 7-12)")
866        .style("Heading2");
867    doc.add_bullet_list_item(
868        "Real-time feature engineering pipeline (Apache Kafka + Flink)",
869        0,
870    );
871    doc.add_bullet_list_item("A/B testing framework for model variants", 0);
872    doc.add_bullet_list_item("Automated model monitoring and drift detection", 0);
873
874    doc.add_paragraph("Phase 3: Optimization (Months 13-18)")
875        .style("Heading2");
876    doc.add_bullet_list_item("GPU inference optimization (TensorRT/ONNX Runtime)", 0);
877    doc.add_bullet_list_item("Multi-region deployment for latency reduction", 0);
878    doc.add_bullet_list_item(
879        "Self-service model deployment portal for data science teams",
880        0,
881    );
882
883    // ── Budget ──
884    doc.add_paragraph("Budget Breakdown").style("Heading1");
885    {
886        let mut tbl = doc
887            .add_table(6, 3)
888            .borders(BorderStyle::Single, 4, "1B2A4A")
889            .width_pct(100.0);
890        tbl.cell(0, 0).unwrap().set_text("Category");
891        tbl.cell(0, 0).unwrap().shading("1B2A4A");
892        tbl.cell(0, 1).unwrap().set_text("Cost");
893        tbl.cell(0, 1).unwrap().shading("1B2A4A");
894        tbl.cell(0, 2).unwrap().set_text("Notes");
895        tbl.cell(0, 2).unwrap().shading("1B2A4A");
896        let items = [
897            (
898                "Infrastructure",
899                "$450,000",
900                "Cloud compute + GPU instances",
901            ),
902            ("Engineering Services", "$1,600,000", "12 FTE x 18 months"),
903            (
904                "Software Licenses",
905                "$350,000",
906                "Kafka, monitoring, CI/CD tools",
907            ),
908            (
909                "Training & Enablement",
910                "$200,000",
911                "Team training, documentation",
912            ),
913            ("Contingency (10%)", "$200,000", "Risk buffer"),
914        ];
915        for (i, (cat, cost, note)) in items.iter().enumerate() {
916            tbl.cell(i + 1, 0).unwrap().set_text(cat);
917            tbl.cell(i + 1, 1).unwrap().set_text(cost);
918            tbl.cell(i + 1, 2).unwrap().set_text(note);
919            if i % 2 == 0 {
920                tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
921                tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
922                tbl.cell(i + 1, 2).unwrap().shading("F4F1EB");
923            }
924        }
925    }
926
927    // ── Closing ──
928    doc.add_paragraph("");
929    doc.add_paragraph("Next Steps").style("Heading1");
930    doc.add_numbered_list_item(
931        "Schedule technical deep-dive with GFC engineering team (Week 1)",
932        0,
933    );
934    doc.add_numbered_list_item("Finalize scope and sign SOW (Week 2-3)", 0);
935    doc.add_numbered_list_item("Kick off Phase 1 with joint planning session (Week 4)", 0);
936
937    doc.add_paragraph("");
938    {
939        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
940        p.add_run("Walter White")
941            .bold(true)
942            .size(14.0)
943            .color("1B2A4A");
944    }
945    {
946        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
947        p.add_run("CEO, Tensorbee | walter@tensorbee.com")
948            .size(10.0)
949            .color("888888");
950    }
951
952    doc
953}
954
955// =============================================================================
956// 3. QUOTE / BILL OF MATERIALS — Teal + orange scheme
957// =============================================================================
958fn generate_quote(_samples_dir: &Path) -> Document {
959    let mut doc = Document::new();
960
961    // Colors: Teal #008B8B, Dark #1A3C3C, Orange #E07020, Light #F0F8F8
962    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
963    doc.set_margins(
964        Length::inches(0.75),
965        Length::inches(0.75),
966        Length::inches(0.75),
967        Length::inches(0.75),
968    );
969    doc.set_title("Quotation QT-2026-0147");
970    doc.set_author("Walter White");
971    doc.set_keywords("quote, BOM, Tensorbee");
972
973    doc.set_header("Tensorbee — Quotation");
974    doc.set_footer("QT-2026-0147 | Page");
975
976    // ── Header Block ──
977    {
978        let mut p = doc.add_paragraph("").alignment(Alignment::Left);
979        p.add_run("TENSORBEE")
980            .bold(true)
981            .size(28.0)
982            .color("008B8B")
983            .font("Helvetica");
984    }
985    doc.add_paragraph("123 Innovation Drive, Suite 400")
986        .alignment(Alignment::Left);
987    doc.add_paragraph("San Francisco, CA 94105 | +1 (415) 555-0199")
988        .alignment(Alignment::Left);
989    doc.add_paragraph("")
990        .border_bottom(BorderStyle::Single, 8, "008B8B");
991
992    // Quote meta
993    doc.add_paragraph("");
994    {
995        let mut tbl = doc.add_table(4, 4).width_pct(100.0);
996        tbl.cell(0, 0).unwrap().set_text("QUOTATION");
997        tbl.cell(0, 0).unwrap().shading("008B8B").grid_span(2);
998        tbl.cell(0, 2).unwrap().set_text("DATE");
999        tbl.cell(0, 2).unwrap().shading("008B8B");
1000        tbl.cell(0, 3).unwrap().set_text("VALID UNTIL");
1001        tbl.cell(0, 3).unwrap().shading("008B8B");
1002        tbl.cell(1, 0).unwrap().set_text("Quote #:");
1003        tbl.cell(1, 1).unwrap().set_text("QT-2026-0147");
1004        tbl.cell(1, 2).unwrap().set_text("Feb 22, 2026");
1005        tbl.cell(1, 3).unwrap().set_text("Mar 22, 2026");
1006        tbl.cell(2, 0).unwrap().set_text("Prepared by:");
1007        tbl.cell(2, 1).unwrap().set_text("Walter White");
1008        tbl.cell(2, 2).unwrap().set_text("Payment:");
1009        tbl.cell(2, 3).unwrap().set_text("Net 30");
1010        tbl.cell(3, 0).unwrap().set_text("Customer:");
1011        tbl.cell(3, 1).unwrap().set_text("Meridian Dynamics LLC");
1012        tbl.cell(3, 2).unwrap().set_text("Currency:");
1013        tbl.cell(3, 3).unwrap().set_text("USD");
1014    }
1015
1016    doc.add_paragraph("");
1017
1018    // ── Bill of Materials ──
1019    {
1020        let mut p = doc.add_paragraph("");
1021        p.add_run("Bill of Materials")
1022            .bold(true)
1023            .size(16.0)
1024            .color("1A3C3C");
1025    }
1026    doc.add_paragraph("");
1027
1028    {
1029        let mut tbl = doc
1030            .add_table(10, 6)
1031            .borders(BorderStyle::Single, 2, "008B8B")
1032            .width_pct(100.0)
1033            .layout_fixed();
1034        // Headers
1035        let hdrs = [
1036            "#",
1037            "Part Number",
1038            "Description",
1039            "Qty",
1040            "Unit Price",
1041            "Total",
1042        ];
1043        for (c, h) in hdrs.iter().enumerate() {
1044            tbl.cell(0, c).unwrap().set_text(h);
1045            tbl.cell(0, c).unwrap().shading("008B8B");
1046        }
1047
1048        let items: Vec<(&str, &str, &str, &str, &str)> = vec![
1049            (
1050                "TB-GPU-A100",
1051                "NVIDIA A100 80GB GPU",
1052                "4",
1053                "$12,500.00",
1054                "$50,000.00",
1055            ),
1056            (
1057                "TB-SRV-R750",
1058                "Dell R750xa Server Chassis",
1059                "2",
1060                "$8,200.00",
1061                "$16,400.00",
1062            ),
1063            (
1064                "TB-RAM-256G",
1065                "256GB DDR5 ECC Memory Module",
1066                "8",
1067                "$890.00",
1068                "$7,120.00",
1069            ),
1070            (
1071                "TB-SSD-3840",
1072                "3.84TB NVMe U.2 SSD",
1073                "8",
1074                "$1,150.00",
1075                "$9,200.00",
1076            ),
1077            (
1078                "TB-NET-CX7",
1079                "ConnectX-7 200GbE NIC",
1080                "4",
1081                "$1,800.00",
1082                "$7,200.00",
1083            ),
1084            (
1085                "TB-SW-48P",
1086                "48-Port 100GbE Switch",
1087                "1",
1088                "$22,000.00",
1089                "$22,000.00",
1090            ),
1091            (
1092                "TB-CAB-RACK",
1093                "42U Server Rack + PDU",
1094                "1",
1095                "$4,500.00",
1096                "$4,500.00",
1097            ),
1098            (
1099                "TB-SVC-INST",
1100                "Installation & Configuration",
1101                "1",
1102                "$8,500.00",
1103                "$8,500.00",
1104            ),
1105            (
1106                "TB-SVC-SUPP",
1107                "3-Year Premium Support",
1108                "1",
1109                "$15,000.00",
1110                "$15,000.00",
1111            ),
1112        ];
1113
1114        for (i, (pn, desc, qty, unit, total)) in items.iter().enumerate() {
1115            let row = i + 1;
1116            tbl.cell(row, 0).unwrap().set_text(&format!("{}", i + 1));
1117            tbl.cell(row, 1).unwrap().set_text(pn);
1118            tbl.cell(row, 2).unwrap().set_text(desc);
1119            tbl.cell(row, 3).unwrap().set_text(qty);
1120            tbl.cell(row, 4).unwrap().set_text(unit);
1121            tbl.cell(row, 5).unwrap().set_text(total);
1122            if i % 2 == 0 {
1123                for c in 0..6 {
1124                    tbl.cell(row, c).unwrap().shading("F0F8F8");
1125                }
1126            }
1127        }
1128    }
1129
1130    doc.add_paragraph("");
1131
1132    // ── Totals ──
1133    {
1134        let mut tbl = doc
1135            .add_table(4, 2)
1136            .borders(BorderStyle::Single, 2, "008B8B")
1137            .width(Length::inches(3.5))
1138            .alignment(Alignment::Right);
1139        tbl.cell(0, 0).unwrap().set_text("Subtotal");
1140        tbl.cell(0, 1).unwrap().set_text("$139,920.00");
1141        tbl.cell(1, 0).unwrap().set_text("Shipping & Handling");
1142        tbl.cell(1, 1).unwrap().set_text("$2,500.00");
1143        tbl.cell(2, 0).unwrap().set_text("Tax (8.625%)");
1144        tbl.cell(2, 1).unwrap().set_text("$12,068.10");
1145        tbl.cell(3, 0).unwrap().set_text("TOTAL");
1146        tbl.cell(3, 0).unwrap().shading("E07020");
1147        tbl.cell(3, 1).unwrap().set_text("$154,488.10");
1148        tbl.cell(3, 1).unwrap().shading("E07020");
1149    }
1150
1151    doc.add_paragraph("");
1152
1153    // ── Terms & Conditions ──
1154    {
1155        let mut p = doc.add_paragraph("");
1156        p.add_run("Terms & Conditions")
1157            .bold(true)
1158            .size(14.0)
1159            .color("1A3C3C");
1160    }
1161    doc.add_numbered_list_item(
1162        "This quotation is valid for 30 calendar days from the date of issue.",
1163        0,
1164    );
1165    doc.add_numbered_list_item(
1166        "All prices are in USD and exclusive of applicable taxes unless stated.",
1167        0,
1168    );
1169    doc.add_numbered_list_item("Standard lead time is 4-6 weeks from PO receipt.", 0);
1170    doc.add_numbered_list_item(
1171        "Payment terms: Net 30 from invoice date. 2% discount for payment within 10 days.",
1172        0,
1173    );
1174    doc.add_numbered_list_item(
1175        "Warranty: 3-year manufacturer warranty on all hardware components.",
1176        0,
1177    );
1178    doc.add_numbered_list_item(
1179        "Returns subject to 15% restocking fee if initiated after 14 days.",
1180        0,
1181    );
1182
1183    doc.add_paragraph("");
1184    doc.add_paragraph("")
1185        .border_bottom(BorderStyle::Single, 4, "008B8B");
1186    doc.add_paragraph("");
1187    {
1188        let mut p = doc.add_paragraph("");
1189        p.add_run("Accepted by: ").bold(true);
1190        p.add_run("___________________________ Date: ___________");
1191    }
1192    {
1193        let mut p = doc.add_paragraph("");
1194        p.add_run("Print Name: ").bold(true);
1195        p.add_run("___________________________ Title: ___________");
1196    }
1197
1198    doc
1199}
1200
1201// =============================================================================
1202// 4. INVOICE — Crimson + charcoal scheme
1203// =============================================================================
1204fn generate_invoice(_samples_dir: &Path) -> Document {
1205    let mut doc = Document::new();
1206
1207    // Colors: Crimson #B22222, Charcoal #333333, Light #FAF0F0
1208    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1209    doc.set_margins(
1210        Length::inches(0.75),
1211        Length::inches(0.75),
1212        Length::inches(0.75),
1213        Length::inches(0.75),
1214    );
1215    doc.set_title("Invoice INV-2026-0392");
1216    doc.set_author("Walter White");
1217    doc.set_keywords("invoice, Tensorbee");
1218
1219    doc.set_footer("Tensorbee — Thank you for your business!");
1220
1221    // ── Company & Invoice Header ──
1222    {
1223        let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1224        // Company name left, INVOICE right
1225        {
1226            let mut cell = tbl.cell(0, 0).unwrap();
1227            let mut p = cell.add_paragraph("");
1228            p.add_run("TENSORBEE")
1229                .bold(true)
1230                .size(32.0)
1231                .color("B22222")
1232                .font("Helvetica");
1233        }
1234        {
1235            let mut cell = tbl.cell(0, 1).unwrap();
1236            let mut p = cell.add_paragraph("");
1237            p.add_run("INVOICE").bold(true).size(32.0).color("333333");
1238        }
1239        tbl.cell(1, 0)
1240            .unwrap()
1241            .set_text("123 Innovation Drive, Suite 400");
1242        tbl.cell(1, 1).unwrap().set_text("Invoice #: INV-2026-0392");
1243        tbl.cell(2, 0).unwrap().set_text("San Francisco, CA 94105");
1244        tbl.cell(2, 1).unwrap().set_text("Date: February 22, 2026");
1245        tbl.cell(3, 0).unwrap().set_text("walter@tensorbee.com");
1246        tbl.cell(3, 1).unwrap().set_text("Due Date: March 24, 2026");
1247    }
1248
1249    doc.add_paragraph("")
1250        .border_bottom(BorderStyle::Thick, 8, "B22222");
1251    doc.add_paragraph("");
1252
1253    // ── Bill To / Ship To ──
1254    {
1255        let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1256        tbl.cell(0, 0).unwrap().set_text("BILL TO");
1257        tbl.cell(0, 0).unwrap().shading("B22222");
1258        tbl.cell(0, 1).unwrap().set_text("SHIP TO");
1259        tbl.cell(0, 1).unwrap().shading("B22222");
1260        tbl.cell(1, 0).unwrap().set_text("Meridian Dynamics LLC");
1261        tbl.cell(1, 1).unwrap().set_text("Meridian Dynamics LLC");
1262        tbl.cell(2, 0).unwrap().set_text("456 Enterprise Blvd");
1263        tbl.cell(2, 1).unwrap().set_text("Attn: Server Room B");
1264        tbl.cell(3, 0).unwrap().set_text("Austin, TX 78701");
1265        tbl.cell(3, 1)
1266            .unwrap()
1267            .set_text("456 Enterprise Blvd, Austin, TX 78701");
1268    }
1269
1270    doc.add_paragraph("");
1271
1272    // ── Line Items ──
1273    {
1274        let mut tbl = doc
1275            .add_table(8, 5)
1276            .borders(BorderStyle::Single, 2, "B22222")
1277            .width_pct(100.0);
1278        let hdrs = ["Description", "Qty", "Unit Price", "Tax", "Amount"];
1279        for (c, h) in hdrs.iter().enumerate() {
1280            tbl.cell(0, c).unwrap().set_text(h);
1281            tbl.cell(0, c).unwrap().shading("B22222");
1282        }
1283        let items = [
1284            (
1285                "ML Infrastructure Setup — Phase 1",
1286                "1",
1287                "$45,000.00",
1288                "$3,881.25",
1289                "$48,881.25",
1290            ),
1291            (
1292                "Data Pipeline Development (160 hrs)",
1293                "160",
1294                "$225.00",
1295                "$3,105.00",
1296                "$39,105.00",
1297            ),
1298            (
1299                "GPU Cluster Configuration",
1300                "1",
1301                "$12,000.00",
1302                "$1,035.00",
1303                "$13,035.00",
1304            ),
1305            (
1306                "API Gateway Implementation",
1307                "1",
1308                "$18,500.00",
1309                "$1,595.63",
1310                "$20,095.63",
1311            ),
1312            (
1313                "Load Testing & QA (80 hrs)",
1314                "80",
1315                "$195.00",
1316                "$1,345.50",
1317                "$16,945.50",
1318            ),
1319            (
1320                "Documentation & Training",
1321                "1",
1322                "$8,000.00",
1323                "$690.00",
1324                "$8,690.00",
1325            ),
1326            (
1327                "Project Management (3 months)",
1328                "3",
1329                "$6,500.00",
1330                "$1,679.63",
1331                "$21,179.63",
1332            ),
1333        ];
1334        for (i, (desc, qty, unit, tax, amt)) in items.iter().enumerate() {
1335            let r = i + 1;
1336            tbl.cell(r, 0).unwrap().set_text(desc);
1337            tbl.cell(r, 1).unwrap().set_text(qty);
1338            tbl.cell(r, 2).unwrap().set_text(unit);
1339            tbl.cell(r, 3).unwrap().set_text(tax);
1340            tbl.cell(r, 4).unwrap().set_text(amt);
1341            if i % 2 == 0 {
1342                for c in 0..5 {
1343                    tbl.cell(r, c).unwrap().shading("FAF0F0");
1344                }
1345            }
1346        }
1347    }
1348
1349    doc.add_paragraph("");
1350
1351    // ── Totals ──
1352    {
1353        let mut tbl = doc
1354            .add_table(4, 2)
1355            .borders(BorderStyle::Single, 2, "B22222")
1356            .width(Length::inches(3.0))
1357            .alignment(Alignment::Right);
1358        tbl.cell(0, 0).unwrap().set_text("Subtotal");
1359        tbl.cell(0, 1).unwrap().set_text("$154,600.00");
1360        tbl.cell(1, 0).unwrap().set_text("Tax (8.625%)");
1361        tbl.cell(1, 1).unwrap().set_text("$13,334.25");
1362        tbl.cell(2, 0).unwrap().set_text("Discount (5%)");
1363        tbl.cell(2, 1).unwrap().set_text("-$7,730.00");
1364        tbl.cell(3, 0).unwrap().set_text("AMOUNT DUE");
1365        tbl.cell(3, 0).unwrap().shading("B22222");
1366        tbl.cell(3, 1).unwrap().set_text("$160,204.25");
1367        tbl.cell(3, 1).unwrap().shading("B22222");
1368    }
1369
1370    doc.add_paragraph("");
1371    doc.add_paragraph("");
1372
1373    // ── Payment Details ──
1374    {
1375        let mut p = doc.add_paragraph("");
1376        p.add_run("Payment Details")
1377            .bold(true)
1378            .size(14.0)
1379            .color("333333");
1380    }
1381    doc.add_paragraph("Bank: Silicon Valley Bank")
1382        .indent_left(Length::inches(0.3));
1383    doc.add_paragraph("Account: Tensorbee Inc. — 0847-2953-1120")
1384        .indent_left(Length::inches(0.3));
1385    doc.add_paragraph("Routing: 121140399")
1386        .indent_left(Length::inches(0.3));
1387    doc.add_paragraph("Swift: SVBKUS6S")
1388        .indent_left(Length::inches(0.3));
1389
1390    doc.add_paragraph("");
1391
1392    doc.add_paragraph("Please include invoice number INV-2026-0392 in the payment reference.")
1393        .shading("FAF0F0")
1394        .border_all(BorderStyle::Single, 2, "B22222");
1395
1396    doc
1397}
1398
1399// =============================================================================
1400// 5. REPORT — Forest green + earth tones, with images & hierarchical sections
1401// =============================================================================
1402fn generate_report(_samples_dir: &Path) -> Document {
1403    let mut doc = Document::new();
1404
1405    // Colors: Forest #2D5016, Sage #6B8E23, Earth #8B7355, Cream #FFFAF0
1406    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1407    doc.set_margins(
1408        Length::inches(1.0),
1409        Length::inches(1.0),
1410        Length::inches(1.0),
1411        Length::inches(1.0),
1412    );
1413    doc.set_title("Q4 2025 Environmental Impact Report");
1414    doc.set_author("Walter White");
1415    doc.set_subject("Quarterly Environmental Report");
1416    doc.set_keywords("environment, sustainability, report, Tensorbee");
1417
1418    doc.set_different_first_page(true);
1419    doc.set_first_page_header("");
1420    doc.set_header("Tensorbee — Q4 2025 Environmental Impact Report");
1421    doc.set_footer("CONFIDENTIAL — Page");
1422
1423    // ── Cover ──
1424    let cover_bg = create_sample_png(612, 792, [20, 50, 15]);
1425    doc.add_background_image(&cover_bg, "report_cover.png");
1426
1427    for _ in 0..5 {
1428        doc.add_paragraph("");
1429    }
1430    {
1431        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1432        p.add_run("Q4 2025")
1433            .bold(true)
1434            .size(48.0)
1435            .color("FFFFFF")
1436            .font("Georgia");
1437    }
1438    {
1439        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1440        p.add_run("Environmental Impact Report")
1441            .size(24.0)
1442            .color("90EE90")
1443            .font("Georgia")
1444            .italic(true);
1445    }
1446    doc.add_paragraph("");
1447    {
1448        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1449        p.add_run("Tensorbee — Sustainability Division")
1450            .size(14.0)
1451            .color("C0C0C0");
1452    }
1453    {
1454        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1455        p.add_run("Prepared by Walter White, Chief Sustainability Officer")
1456            .size(11.0)
1457            .color("AAAAAA");
1458    }
1459
1460    // ── TOC ──
1461    doc.add_paragraph("").page_break_before(true);
1462    doc.insert_toc(doc.content_count(), 3);
1463
1464    // ── Section 1: Executive Overview ──
1465    doc.add_paragraph("").page_break_before(true);
1466    doc.add_paragraph("Executive Overview").style("Heading1");
1467    doc.add_paragraph(
1468        "This report presents Tensorbee's environmental performance for Q4 2025. Our \
1469         sustainability initiatives have yielded a 34% reduction in carbon emissions compared \
1470         to Q4 2024, exceeding our target of 25%. Key achievements include the transition to \
1471         100% renewable energy in our primary data centers and the launch of our carbon offset \
1472         marketplace.",
1473    )
1474    .first_line_indent(Length::inches(0.3));
1475
1476    // Image: performance chart
1477    let chart_img = create_chart_png(400, 200);
1478    doc.add_paragraph("");
1479    doc.add_picture(
1480        &chart_img,
1481        "performance_chart.png",
1482        Length::inches(5.0),
1483        Length::inches(2.5),
1484    )
1485    .alignment(Alignment::Center);
1486    {
1487        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1488        p.add_run("Figure 1: Quarterly Carbon Emissions (tonnes CO2e)")
1489            .italic(true)
1490            .size(9.0)
1491            .color("666666");
1492    }
1493
1494    // ── Section 2: Energy Consumption ──
1495    doc.add_paragraph("").page_break_before(true);
1496    doc.add_paragraph("Energy Consumption").style("Heading1");
1497
1498    doc.add_paragraph("Data Center Operations")
1499        .style("Heading2");
1500    doc.add_paragraph(
1501        "Our three primary data centers consumed a combined 4.2 GWh in Q4 2025, \
1502         a 12% reduction from Q3 2025 driven by improved cooling efficiency and \
1503         server consolidation.",
1504    );
1505
1506    // Data table
1507    {
1508        let mut tbl = doc
1509            .add_table(5, 4)
1510            .borders(BorderStyle::Single, 4, "2D5016")
1511            .width_pct(100.0);
1512        let hdrs = ["Data Center", "Capacity (MW)", "Usage (GWh)", "PUE"];
1513        for (c, h) in hdrs.iter().enumerate() {
1514            tbl.cell(0, c).unwrap().set_text(h);
1515            tbl.cell(0, c).unwrap().shading("2D5016");
1516        }
1517        let rows = [
1518            ("San Francisco (Primary)", "3.2", "1.8", "1.12"),
1519            ("Dublin (EU)", "2.1", "1.2", "1.18"),
1520            ("Singapore (APAC)", "1.8", "1.2", "1.24"),
1521            ("TOTAL", "7.1", "4.2", "1.17 avg"),
1522        ];
1523        for (i, (dc, cap, use_, pue)) in rows.iter().enumerate() {
1524            tbl.cell(i + 1, 0).unwrap().set_text(dc);
1525            tbl.cell(i + 1, 1).unwrap().set_text(cap);
1526            tbl.cell(i + 1, 2).unwrap().set_text(use_);
1527            tbl.cell(i + 1, 3).unwrap().set_text(pue);
1528            if i == 3 {
1529                for c in 0..4 {
1530                    tbl.cell(i + 1, c).unwrap().shading("E2EFDA");
1531                }
1532            }
1533        }
1534    }
1535
1536    doc.add_paragraph("");
1537
1538    doc.add_paragraph("Renewable Energy Mix").style("Heading2");
1539    doc.add_paragraph("Breakdown of energy sources across all facilities:");
1540
1541    doc.add_bullet_list_item("Solar PV: 42% (1.76 GWh)", 0);
1542    doc.add_bullet_list_item("Wind Power Purchase Agreements: 38% (1.60 GWh)", 0);
1543    doc.add_bullet_list_item("Hydroelectric: 12% (0.50 GWh)", 0);
1544    doc.add_bullet_list_item("Grid (non-renewable): 8% (0.34 GWh)", 0);
1545
1546    // ── Section 3: Water & Waste ──
1547    doc.add_paragraph("Water & Waste Management")
1548        .style("Heading1");
1549
1550    doc.add_paragraph("Water Usage").style("Heading2");
1551    doc.add_paragraph(
1552        "Total water consumption was 12.4 million gallons, a 15% reduction from Q3. \
1553         Our closed-loop cooling systems now recycle 78% of water used in cooling operations.",
1554    );
1555
1556    doc.add_paragraph("Waste Reduction").style("Heading2");
1557    doc.add_paragraph("E-waste management results for Q4:")
1558        .keep_with_next(true);
1559    {
1560        let mut tbl = doc
1561            .add_table(4, 3)
1562            .borders(BorderStyle::Single, 2, "6B8E23")
1563            .width_pct(80.0);
1564        tbl.cell(0, 0).unwrap().set_text("Category");
1565        tbl.cell(0, 0).unwrap().shading("6B8E23");
1566        tbl.cell(0, 1).unwrap().set_text("Weight (kg)");
1567        tbl.cell(0, 1).unwrap().shading("6B8E23");
1568        tbl.cell(0, 2).unwrap().set_text("Recycled %");
1569        tbl.cell(0, 2).unwrap().shading("6B8E23");
1570        let rows = [
1571            ("Server Hardware", "2,450", "94%"),
1572            ("Networking Equipment", "820", "91%"),
1573            ("Storage Media", "340", "99%"),
1574        ];
1575        for (i, (cat, wt, pct)) in rows.iter().enumerate() {
1576            tbl.cell(i + 1, 0).unwrap().set_text(cat);
1577            tbl.cell(i + 1, 1).unwrap().set_text(wt);
1578            tbl.cell(i + 1, 2).unwrap().set_text(pct);
1579        }
1580    }
1581
1582    // ── Section 4: Initiatives ──
1583    doc.add_paragraph("").page_break_before(true);
1584    doc.add_paragraph("Q1 2026 Initiatives").style("Heading1");
1585
1586    doc.add_paragraph("Planned Programs").style("Heading2");
1587    doc.add_numbered_list_item(
1588        "Deploy on-site battery storage at SF data center (2 MWh capacity)",
1589        0,
1590    );
1591    doc.add_numbered_list_item("Pilot immersion cooling in Dublin facility", 0);
1592    doc.add_numbered_list_item("Launch employee commute carbon offset program", 0);
1593    doc.add_numbered_list_item("Achieve ISO 14001 certification for Singapore facility", 0);
1594
1595    doc.add_paragraph("Investment Targets").style("Heading2");
1596    doc.add_paragraph("Sustainability CapEx allocation for FY2026:")
1597        .keep_with_next(true);
1598    {
1599        let mut tbl = doc
1600            .add_table(5, 2)
1601            .borders(BorderStyle::Single, 4, "2D5016");
1602        tbl.cell(0, 0).unwrap().set_text("Initiative");
1603        tbl.cell(0, 0).unwrap().shading("2D5016");
1604        tbl.cell(0, 1).unwrap().set_text("Budget");
1605        tbl.cell(0, 1).unwrap().shading("2D5016");
1606        let rows = [
1607            ("Battery Storage", "$1.2M"),
1608            ("Immersion Cooling Pilot", "$800K"),
1609            ("Solar Panel Expansion", "$2.1M"),
1610            ("Carbon Credits", "$500K"),
1611        ];
1612        for (i, (init, budget)) in rows.iter().enumerate() {
1613            tbl.cell(i + 1, 0).unwrap().set_text(init);
1614            tbl.cell(i + 1, 1).unwrap().set_text(budget);
1615            if i % 2 == 0 {
1616                tbl.cell(i + 1, 0).unwrap().shading("FFFAF0");
1617                tbl.cell(i + 1, 1).unwrap().shading("FFFAF0");
1618            }
1619        }
1620    }
1621
1622    // Image: sustainability roadmap
1623    let roadmap_img = create_sample_png(500, 100, [40, 80, 30]);
1624    doc.add_paragraph("");
1625    doc.add_picture(
1626        &roadmap_img,
1627        "roadmap.png",
1628        Length::inches(6.0),
1629        Length::inches(1.2),
1630    )
1631    .alignment(Alignment::Center);
1632    {
1633        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1634        p.add_run("Figure 2: 2026 Sustainability Roadmap")
1635            .italic(true)
1636            .size(9.0)
1637            .color("666666");
1638    }
1639
1640    doc.add_paragraph("");
1641    doc.add_paragraph(
1642        "For questions about this report, contact Walter White at sustainability@tensorbee.com.",
1643    )
1644    .shading("FFFAF0")
1645    .border_all(BorderStyle::Single, 2, "2D5016");
1646
1647    doc
1648}
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_samples.rs (line 50)
34fn generate_feature_showcase(path: &Path) {
35    let mut doc = Document::new();
36
37    // =========================================================================
38    // PAGE SETUP & METADATA
39    // =========================================================================
40    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
41    doc.set_margins(
42        Length::inches(1.0), // top
43        Length::inches(1.0), // right
44        Length::inches(1.0), // bottom
45        Length::inches(1.0), // left
46    );
47    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
48    doc.set_gutter(Length::twips(0));
49
50    doc.set_title("rdocx Feature Showcase");
51    doc.set_author("rdocx Sample Generator");
52    doc.set_subject("Comprehensive feature demonstration");
53    doc.set_keywords("rdocx, docx, rust, sample");
54
55    // Header & Footer
56    doc.set_header("rdocx Feature Showcase");
57    doc.set_footer("Generated by rdocx — Page");
58
59    // Different first page header
60    doc.set_different_first_page(true);
61    doc.set_first_page_header("rdocx");
62    doc.set_first_page_footer("Feature Showcase — Cover Page");
63
64    // =========================================================================
65    // PAGE 1: COVER PAGE — background image, run formatting
66    // =========================================================================
67    let bg_cover = create_sample_png(612, 792, [30, 60, 120]);
68    doc.add_background_image(&bg_cover, "cover_bg.png");
69
70    doc.add_paragraph(""); // spacer
71    doc.add_paragraph(""); // spacer
72    doc.add_paragraph(""); // spacer
73
74    {
75        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
76        p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
77    }
78    {
79        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
80        p.add_run("Feature Showcase")
81            .size(28.0)
82            .color("FFFFFF")
83            .italic(true);
84    }
85
86    doc.add_paragraph(""); // spacer
87
88    {
89        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
90        p.add_run("A comprehensive demonstration of every feature")
91            .size(14.0)
92            .color("CCDDFF");
93    }
94    {
95        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
96        p.add_run("provided by the rdocx Rust crate for DOCX generation.")
97            .size(14.0)
98            .color("CCDDFF");
99    }
100
101    // =========================================================================
102    // PAGE 2: TEXT FORMATTING
103    // =========================================================================
104    doc.add_paragraph("").page_break_before(true);
105
106    doc.add_paragraph("1. Text Formatting").style("Heading1");
107
108    doc.add_paragraph("This section demonstrates paragraph and run-level formatting options.");
109    doc.add_paragraph("");
110
111    // --- Paragraph alignment ---
112    doc.add_paragraph("Paragraph Alignment").style("Heading2");
113
114    doc.add_paragraph("This paragraph is left-aligned (the default).")
115        .alignment(Alignment::Left);
116    doc.add_paragraph("This paragraph is center-aligned.")
117        .alignment(Alignment::Center);
118    doc.add_paragraph("This paragraph is right-aligned.")
119        .alignment(Alignment::Right);
120    doc.add_paragraph(
121        "This paragraph is justified. To demonstrate justified text properly, it needs \
122         to be long enough to span multiple lines so the word spacing adjustment is visible \
123         across the full width of the text area on the page.",
124    )
125    .alignment(Alignment::Justify);
126
127    doc.add_paragraph("");
128
129    // --- Run formatting ---
130    doc.add_paragraph("Run Formatting").style("Heading2");
131
132    {
133        let mut p = doc.add_paragraph("");
134        p.add_run("Bold text").bold(true);
135        p.add_run(" | ");
136        p.add_run("Italic text").italic(true);
137        p.add_run(" | ");
138        p.add_run("Bold + Italic").bold(true).italic(true);
139    }
140    {
141        let mut p = doc.add_paragraph("");
142        p.add_run("Single underline").underline(true);
143        p.add_run(" | ");
144        p.add_run("Strikethrough").strike(true);
145        p.add_run(" | ");
146        p.add_run("Double strikethrough").double_strike(true);
147    }
148    {
149        let mut p = doc.add_paragraph("");
150        p.add_run("Red text").color("FF0000");
151        p.add_run(" | ");
152        p.add_run("Blue text").color("0000FF");
153        p.add_run(" | ");
154        p.add_run("Green text").color("00AA00");
155        p.add_run(" | ");
156        p.add_run("Highlighted").highlight("FFFF00");
157    }
158    {
159        let mut p = doc.add_paragraph("");
160        p.add_run("8pt small").size(8.0);
161        p.add_run(" | ");
162        p.add_run("11pt normal").size(11.0);
163        p.add_run(" | ");
164        p.add_run("16pt large").size(16.0);
165        p.add_run(" | ");
166        p.add_run("24pt extra-large").size(24.0);
167    }
168    {
169        let mut p = doc.add_paragraph("");
170        p.add_run("Arial font").font("Arial");
171        p.add_run(" | ");
172        p.add_run("Times New Roman font").font("Times New Roman");
173        p.add_run(" | ");
174        p.add_run("Courier New font").font("Courier New");
175    }
176    {
177        let mut p = doc.add_paragraph("");
178        p.add_run("Normal");
179        p.add_run(" H").size(11.0);
180        p.add_run("2").subscript();
181        p.add_run("O (subscript)").size(11.0);
182        p.add_run(" | E = mc").size(11.0);
183        p.add_run("2").superscript();
184        p.add_run(" (superscript)").size(11.0);
185    }
186    {
187        let mut p = doc.add_paragraph("");
188        p.add_run("ALL CAPS").all_caps(true);
189        p.add_run(" | ");
190        p.add_run("Small Caps").small_caps(true);
191        p.add_run(" | ");
192        p.add_run("Expanded spacing")
193            .character_spacing(Length::twips(40));
194    }
195
196    doc.add_paragraph("");
197
198    // --- Paragraph formatting ---
199    doc.add_paragraph("Paragraph Formatting").style("Heading2");
200
201    doc.add_paragraph("Paragraph with shading (light green background)")
202        .shading("E2EFDA");
203
204    doc.add_paragraph("Paragraph with bottom border")
205        .border_bottom(BorderStyle::Single, 6, "2E75B6");
206
207    doc.add_paragraph("Paragraph with all borders")
208        .border_all(BorderStyle::Single, 4, "FF0000");
209
210    doc.add_paragraph("Paragraph with 1-inch left indent and hanging indent")
211        .indent_left(Length::inches(1.0))
212        .hanging_indent(Length::inches(0.5));
213
214    doc.add_paragraph("Paragraph with first-line indent of 0.5 inches")
215        .first_line_indent(Length::inches(0.5));
216
217    doc.add_paragraph("Paragraph with extra space before (24pt) and after (12pt)")
218        .space_before(Length::pt(24.0))
219        .space_after(Length::pt(12.0));
220
221    doc.add_paragraph(
222        "Paragraph with double line spacing. This text should have extra vertical \
223         space between lines to demonstrate the line_spacing_multiple setting.",
224    )
225    .line_spacing_multiple(2.0);
226
227    doc.add_paragraph("Paragraph with keep-with-next (won't break from the next paragraph)")
228        .keep_with_next(true);
229    doc.add_paragraph("(This stays with the paragraph above.)");
230
231    // =========================================================================
232    // PAGE 3: LISTS & TAB STOPS
233    // =========================================================================
234    doc.add_paragraph("").page_break_before(true);
235
236    doc.add_paragraph("2. Lists").style("Heading1");
237
238    doc.add_paragraph("Bullet List").style("Heading2");
239
240    doc.add_bullet_list_item("First bullet item", 0);
241    doc.add_bullet_list_item("Second bullet item", 0);
242    doc.add_bullet_list_item("Nested level 1", 1);
243    doc.add_bullet_list_item("Nested level 2", 2);
244    doc.add_bullet_list_item("Back to level 1", 1);
245    doc.add_bullet_list_item("Third bullet item", 0);
246
247    doc.add_paragraph("");
248
249    doc.add_paragraph("Numbered List").style("Heading2");
250
251    doc.add_numbered_list_item("First numbered item", 0);
252    doc.add_numbered_list_item("Second numbered item", 0);
253    doc.add_numbered_list_item("Sub-item A", 1);
254    doc.add_numbered_list_item("Sub-item B", 1);
255    doc.add_numbered_list_item("Third numbered item", 0);
256
257    doc.add_paragraph("");
258
259    // --- Tab stops ---
260    doc.add_paragraph("Tab Stops").style("Heading2");
261
262    doc.add_paragraph("Left\tCenter\tRight\tDecimal")
263        .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
264        .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
265        .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
266        .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
267
268    doc.add_paragraph("Item\t........\tPrice")
269        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
270        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
271        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
272
273    doc.add_paragraph("Widget A\t........\t$19.99")
274        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
275        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
276        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
277
278    doc.add_paragraph("Gadget B\t________\t$249.50")
279        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
280        .add_tab_stop_with_leader(
281            TabAlignment::Right,
282            Length::inches(4.0),
283            TabLeader::Underscore,
284        )
285        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
286
287    // =========================================================================
288    // PAGE 4: TABLES
289    // =========================================================================
290    doc.add_paragraph("").page_break_before(true);
291
292    doc.add_paragraph("3. Tables").style("Heading1");
293
294    // --- Basic table with borders ---
295    doc.add_paragraph("Basic Table with Borders")
296        .style("Heading2");
297
298    {
299        let mut tbl = doc.add_table(4, 3);
300        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
301
302        // Header row
303        for col in 0..3 {
304            tbl.cell(0, col).unwrap().shading("2E75B6");
305        }
306        tbl.cell(0, 0).unwrap().set_text("Name");
307        tbl.cell(0, 1).unwrap().set_text("Role");
308        tbl.cell(0, 2).unwrap().set_text("Location");
309
310        tbl.cell(1, 0).unwrap().set_text("Alice Johnson");
311        tbl.cell(1, 1).unwrap().set_text("Engineering Lead");
312        tbl.cell(1, 2).unwrap().set_text("New York");
313
314        tbl.cell(2, 0).unwrap().set_text("Bob Smith");
315        tbl.cell(2, 1).unwrap().set_text("Product Manager");
316        tbl.cell(2, 2).unwrap().set_text("San Francisco");
317
318        tbl.cell(3, 0).unwrap().set_text("Carol Davis");
319        tbl.cell(3, 1).unwrap().set_text("Designer");
320        tbl.cell(3, 2).unwrap().set_text("London");
321    }
322
323    doc.add_paragraph("");
324
325    // --- Table with cell merging ---
326    doc.add_paragraph("Table with Cell Merging & Vertical Alignment")
327        .style("Heading2");
328
329    {
330        let mut tbl = doc.add_table(4, 4);
331        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
332        tbl = tbl.width_pct(100.0);
333
334        // Header spanning all columns
335        tbl.cell(0, 0).unwrap().set_text("Quarterly Revenue Report");
336        tbl.cell(0, 0).unwrap().shading("1F4E79");
337        tbl.cell(0, 0).unwrap().grid_span(4);
338
339        // Sub-header
340        tbl.cell(1, 0).unwrap().set_text("Region");
341        tbl.cell(1, 0).unwrap().shading("D6E4F0");
342        tbl.cell(1, 1).unwrap().set_text("Q1");
343        tbl.cell(1, 1).unwrap().shading("D6E4F0");
344        tbl.cell(1, 2).unwrap().set_text("Q2");
345        tbl.cell(1, 2).unwrap().shading("D6E4F0");
346        tbl.cell(1, 3).unwrap().set_text("Total");
347        tbl.cell(1, 3).unwrap().shading("D6E4F0");
348
349        // Data
350        tbl.cell(2, 0).unwrap().set_text("North America");
351        tbl.cell(2, 1).unwrap().set_text("$2.4M");
352        tbl.cell(2, 2).unwrap().set_text("$2.7M");
353        tbl.cell(2, 3).unwrap().set_text("$5.1M");
354
355        tbl.cell(3, 0).unwrap().set_text("Europe");
356        tbl.cell(3, 1).unwrap().set_text("$1.8M");
357        tbl.cell(3, 2).unwrap().set_text("$2.0M");
358        tbl.cell(3, 3).unwrap().set_text("$3.8M");
359
360        // Vertical alignment on data cells
361        tbl.cell(2, 3)
362            .unwrap()
363            .vertical_alignment(VerticalAlignment::Center);
364        tbl.cell(3, 3)
365            .unwrap()
366            .vertical_alignment(VerticalAlignment::Bottom);
367    }
368
369    doc.add_paragraph("");
370
371    // --- Table with vertical merge ---
372    doc.add_paragraph("Table with Vertical Merge")
373        .style("Heading2");
374
375    {
376        let mut tbl = doc.add_table(4, 3);
377        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
378
379        tbl.cell(0, 0).unwrap().set_text("Category");
380        tbl.cell(0, 0).unwrap().shading("E2EFDA");
381        tbl.cell(0, 1).unwrap().set_text("Item");
382        tbl.cell(0, 1).unwrap().shading("E2EFDA");
383        tbl.cell(0, 2).unwrap().set_text("Price");
384        tbl.cell(0, 2).unwrap().shading("E2EFDA");
385
386        // "Hardware" spans rows 1-2
387        tbl.cell(1, 0).unwrap().set_text("Hardware");
388        tbl.cell(1, 0).unwrap().v_merge_restart();
389        tbl.cell(1, 1).unwrap().set_text("Laptop");
390        tbl.cell(1, 2).unwrap().set_text("$1,200");
391
392        tbl.cell(2, 0).unwrap().v_merge_continue();
393        tbl.cell(2, 1).unwrap().set_text("Monitor");
394        tbl.cell(2, 2).unwrap().set_text("$450");
395
396        // "Software" on row 3
397        tbl.cell(3, 0).unwrap().set_text("Software");
398        tbl.cell(3, 1).unwrap().set_text("IDE License");
399        tbl.cell(3, 2).unwrap().set_text("$200/yr");
400    }
401
402    doc.add_paragraph("");
403
404    // --- Nested table ---
405    doc.add_paragraph("Nested Table").style("Heading2");
406
407    {
408        let mut tbl = doc.add_table(2, 2);
409        tbl = tbl.borders(BorderStyle::Single, 6, "2E75B6");
410
411        tbl.cell(0, 0).unwrap().set_text("Outer Cell (0,0)");
412        tbl.cell(0, 1).unwrap().set_text("Outer Cell (0,1)");
413        tbl.cell(1, 0).unwrap().set_text("Outer Cell (1,0)");
414
415        // Nested table inside cell (1,1)
416        {
417            let mut cell = tbl.cell(1, 1).unwrap();
418            cell.set_text("Contains nested table:");
419            let mut nested = cell.add_table(2, 2);
420            nested = nested.borders(BorderStyle::Single, 2, "FF6600");
421            nested.cell(0, 0).unwrap().set_text("Inner A");
422            nested.cell(0, 1).unwrap().set_text("Inner B");
423            nested.cell(1, 0).unwrap().set_text("Inner C");
424            nested.cell(1, 1).unwrap().set_text("Inner D");
425        }
426    }
427
428    // =========================================================================
429    // PAGE 5: IMAGES
430    // =========================================================================
431    doc.add_paragraph("").page_break_before(true);
432
433    doc.add_paragraph("4. Images").style("Heading1");
434
435    doc.add_paragraph("Inline Image").style("Heading2");
436
437    doc.add_paragraph("Below is an inline image (200x50 pixels, blue gradient):");
438    let inline_img = create_sample_png(200, 50, [0, 80, 200]);
439    doc.add_picture(
440        &inline_img,
441        "inline_chart.png",
442        Length::inches(3.0),
443        Length::inches(0.75),
444    );
445
446    doc.add_paragraph("");
447
448    doc.add_paragraph("Header Image").style("Heading2");
449
450    // Replace the text-only header with an image header
451    let header_img = create_sample_png(400, 40, [40, 40, 40]);
452    doc.set_header_image(
453        &header_img,
454        "header_logo.png",
455        Length::inches(2.0),
456        Length::inches(0.2),
457    );
458
459    doc.add_paragraph(
460        "The document header has been replaced with an inline image. \
461         Check the header area at the top of this page.",
462    );
463
464    doc.add_paragraph("");
465    doc.add_paragraph(
466        "Note: The cover page uses a full-page background image behind the text, \
467         demonstrated on page 1 via add_background_image().",
468    );
469
470    // =========================================================================
471    // PAGE 6: CONTENT MANIPULATION — placeholder replacement, insertion
472    // =========================================================================
473    doc.add_paragraph("").page_break_before(true);
474
475    doc.add_paragraph("5. Content Manipulation")
476        .style("Heading1");
477
478    // --- Placeholder replacement ---
479    doc.add_paragraph("Placeholder Replacement")
480        .style("Heading2");
481
482    doc.add_paragraph(
483        "Before replacement, this document contained {{customer}} and {{date}} placeholders.",
484    );
485
486    {
487        let mut p = doc.add_paragraph("");
488        p.add_run("Customer: ").bold(true);
489        p.add_run("{{customer}}");
490    }
491    {
492        let mut p = doc.add_paragraph("");
493        p.add_run("Date: ").bold(true);
494        p.add_run("{{date}}");
495    }
496    doc.add_paragraph("Reference: {{ref_number}}");
497
498    // Table with placeholders
499    {
500        let mut tbl = doc.add_table(3, 2);
501        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
502        tbl.cell(0, 0).unwrap().set_text("Field");
503        tbl.cell(0, 0).unwrap().shading("D6E4F0");
504        tbl.cell(0, 1).unwrap().set_text("Value");
505        tbl.cell(0, 1).unwrap().shading("D6E4F0");
506        tbl.cell(1, 0).unwrap().set_text("Project");
507        tbl.cell(1, 1).unwrap().set_text("{{project}}");
508        tbl.cell(2, 0).unwrap().set_text("Status");
509        tbl.cell(2, 1).unwrap().set_text("{{status}}");
510    }
511
512    // Perform replacements
513    let mut replacements = HashMap::new();
514    replacements.insert("{{customer}}", "Acme Corporation");
515    replacements.insert("{{date}}", "February 22, 2026");
516    replacements.insert("{{ref_number}}", "REF-2026-001");
517    replacements.insert("{{project}}", "Infrastructure Upgrade");
518    replacements.insert("{{status}}", "In Progress");
519    let replace_count = doc.replace_all(&replacements);
520
521    doc.add_paragraph("");
522    doc.add_paragraph(&format!(
523        "(Replaced {} placeholders above — in body text and table cells)",
524        replace_count
525    ));
526
527    doc.add_paragraph("");
528
529    // --- Content insertion ---
530    doc.add_paragraph("Content Insertion").style("Heading2");
531
532    doc.add_paragraph("Section A: First section of content.");
533    doc.add_paragraph("Section C: Third section of content.");
534
535    // Insert "Section B" between A and C
536    if let Some(idx) = doc.find_content_index("Section C") {
537        doc.insert_paragraph(
538            idx,
539            "Section B: Inserted between A and C using find_content_index().",
540        );
541    }
542
543    doc.add_paragraph("");
544    doc.add_paragraph(
545        "The paragraph above ('Section B') was inserted at a specific position \
546         using find_content_index() + insert_paragraph().",
547    );
548
549    // =========================================================================
550    // PAGE 7: LANDSCAPE — section break, wide table
551    // =========================================================================
552    doc.add_paragraph("").section_break(SectionBreak::NextPage);
553
554    doc.add_paragraph("6. Mixed Page Orientation")
555        .style("Heading1");
556
557    doc.add_paragraph(
558        "This page is in LANDSCAPE orientation. It was created using a section break \
559         followed by section_landscape(). This is useful for wide tables or charts.",
560    );
561
562    doc.add_paragraph("");
563
564    // Wide table for landscape
565    {
566        let mut tbl = doc.add_table(4, 7);
567        tbl = tbl.borders(BorderStyle::Single, 4, "2E75B6");
568
569        let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
570        for (col, h) in headers.iter().enumerate() {
571            tbl.cell(0, col).unwrap().set_text(h);
572            tbl.cell(0, col).unwrap().shading("2E75B6");
573        }
574
575        let data = [
576            [
577                "North America",
578                "$1.2M",
579                "$1.3M",
580                "$1.4M",
581                "$1.5M",
582                "$1.6M",
583                "$7.0M",
584            ],
585            [
586                "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
587            ],
588            [
589                "Asia Pacific",
590                "$0.5M",
591                "$0.6M",
592                "$0.7M",
593                "$0.7M",
594                "$0.8M",
595                "$3.3M",
596            ],
597        ];
598        for (row_idx, row_data) in data.iter().enumerate() {
599            for (col, val) in row_data.iter().enumerate() {
600                tbl.cell(row_idx + 1, col).unwrap().set_text(val);
601            }
602        }
603    }
604
605    // End landscape, return to portrait
606    doc.add_paragraph("")
607        .section_break(SectionBreak::NextPage)
608        .section_landscape();
609
610    // =========================================================================
611    // PAGE 8: BACK TO PORTRAIT — styles, final notes
612    // =========================================================================
613    doc.add_paragraph("7. Custom Styles & Summary")
614        .style("Heading1");
615
616    doc.add_paragraph(
617        "This final page is back in portrait orientation after a section break. \
618         The document has demonstrated:",
619    );
620
621    doc.add_bullet_list_item(
622        "Page setup: size, margins, header/footer distance, gutter",
623        0,
624    );
625    doc.add_bullet_list_item("Document metadata: title, author, subject, keywords", 0);
626    doc.add_bullet_list_item("Headers and footers: text, images, different first page", 0);
627    doc.add_bullet_list_item("Background images: full-page behind text", 0);
628    doc.add_bullet_list_item(
629        "Text formatting: bold, italic, underline, strike, color, size, font",
630        0,
631    );
632    doc.add_bullet_list_item(
633        "Advanced run formatting: superscript, subscript, caps, spacing",
634        0,
635    );
636    doc.add_bullet_list_item(
637        "Paragraph formatting: alignment, borders, shading, spacing, indentation",
638        0,
639    );
640    doc.add_bullet_list_item("Bullet and numbered lists with nesting levels", 0);
641    doc.add_bullet_list_item("Tab stops with dot/underscore leaders", 0);
642    doc.add_bullet_list_item(
643        "Tables: borders, shading, column spans, row spans, nesting",
644        0,
645    );
646    doc.add_bullet_list_item("Vertical alignment in table cells", 0);
647    doc.add_bullet_list_item("Inline images", 0);
648    doc.add_bullet_list_item("Placeholder replacement in body and table cells", 0);
649    doc.add_bullet_list_item("Content insertion at specific positions", 0);
650    doc.add_bullet_list_item(
651        "Section breaks with mixed portrait/landscape orientation",
652        0,
653    );
654
655    doc.add_paragraph("");
656    doc.add_paragraph("All features above were built entirely from scratch using the rdocx API.")
657        .alignment(Alignment::Center)
658        .shading("E2EFDA")
659        .border_all(BorderStyle::Single, 2, "00AA00");
660
661    doc.save(path).unwrap();
662}
examples/generate_all_samples.rs (line 99)
86fn generate_feature_showcase(_samples_dir: &Path) -> Document {
87    let mut doc = Document::new();
88
89    // ── Page Setup & Metadata ──
90    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
91    doc.set_margins(
92        Length::inches(1.0),
93        Length::inches(1.0),
94        Length::inches(1.0),
95        Length::inches(1.0),
96    );
97    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
98    doc.set_gutter(Length::twips(0));
99    doc.set_title("rdocx Feature Showcase");
100    doc.set_author("rdocx Sample Generator");
101    doc.set_subject("Comprehensive feature demonstration");
102    doc.set_keywords("rdocx, docx, rust, sample, showcase");
103
104    // Headers & Footers with different first page
105    doc.set_different_first_page(true);
106    doc.set_first_page_header("rdocx");
107    doc.set_first_page_footer("Feature Showcase — Cover Page");
108    doc.set_header("rdocx Feature Showcase");
109    doc.set_footer("Generated by rdocx");
110
111    // ── COVER PAGE ──
112    let bg = create_sample_png(612, 792, [20, 45, 90]);
113    doc.add_background_image(&bg, "cover_bg.png");
114
115    for _ in 0..3 {
116        doc.add_paragraph("");
117    }
118    {
119        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
120        p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
121    }
122    {
123        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
124        p.add_run("Complete Feature Showcase")
125            .size(28.0)
126            .color("FFFFFF")
127            .italic(true);
128    }
129    doc.add_paragraph("");
130    {
131        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
132        p.add_run("Every feature of the rdocx Rust library")
133            .size(13.0)
134            .color("B0C4DE");
135    }
136    {
137        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
138        p.add_run("demonstrated in a single document.")
139            .size(13.0)
140            .color("B0C4DE");
141    }
142
143    // ── TABLE OF CONTENTS ──
144    doc.add_paragraph("").page_break_before(true);
145    doc.insert_toc(doc.content_count(), 3);
146
147    // ── SECTION 1: TEXT FORMATTING ──
148    doc.add_paragraph("").page_break_before(true);
149    doc.add_paragraph("1. Text Formatting").style("Heading1");
150
151    // Paragraph Alignment
152    doc.add_paragraph("1.1 Paragraph Alignment")
153        .style("Heading2");
154    doc.add_paragraph("Left-aligned paragraph (default).")
155        .alignment(Alignment::Left);
156    doc.add_paragraph("Center-aligned paragraph.")
157        .alignment(Alignment::Center);
158    doc.add_paragraph("Right-aligned paragraph.")
159        .alignment(Alignment::Right);
160    doc.add_paragraph(
161        "Justified paragraph — this text is long enough to demonstrate how justified alignment \
162         distributes extra space across word gaps so lines fill the full width of the text area.",
163    )
164    .alignment(Alignment::Justify);
165
166    doc.add_paragraph("");
167
168    // Run Formatting
169    doc.add_paragraph("1.2 Run Formatting").style("Heading2");
170    {
171        let mut p = doc.add_paragraph("");
172        p.add_run("Bold").bold(true);
173        p.add_run(" | ");
174        p.add_run("Italic").italic(true);
175        p.add_run(" | ");
176        p.add_run("Bold Italic").bold(true).italic(true);
177        p.add_run(" | ");
178        p.add_run("Underline").underline(true);
179        p.add_run(" | ");
180        p.add_run("Strikethrough").strike(true);
181        p.add_run(" | ");
182        p.add_run("Double Strike").double_strike(true);
183    }
184    {
185        let mut p = doc.add_paragraph("");
186        p.add_run("Red").color("FF0000");
187        p.add_run(" | ");
188        p.add_run("Blue").color("0000FF");
189        p.add_run(" | ");
190        p.add_run("Green").color("00AA00");
191        p.add_run(" | ");
192        p.add_run("Highlighted").highlight("FFFF00");
193    }
194    {
195        let mut p = doc.add_paragraph("");
196        p.add_run("8pt").size(8.0);
197        p.add_run(" | ");
198        p.add_run("11pt").size(11.0);
199        p.add_run(" | ");
200        p.add_run("16pt").size(16.0);
201        p.add_run(" | ");
202        p.add_run("24pt").size(24.0);
203    }
204    {
205        let mut p = doc.add_paragraph("");
206        p.add_run("Arial").font("Arial");
207        p.add_run(" | ");
208        p.add_run("Times New Roman").font("Times New Roman");
209        p.add_run(" | ");
210        p.add_run("Courier New").font("Courier New");
211    }
212    {
213        let mut p = doc.add_paragraph("");
214        p.add_run("H");
215        p.add_run("2").subscript();
216        p.add_run("O (subscript) | E=mc");
217        p.add_run("2").superscript();
218        p.add_run(" (superscript)");
219    }
220    {
221        let mut p = doc.add_paragraph("");
222        p.add_run("ALL CAPS").all_caps(true);
223        p.add_run(" | ");
224        p.add_run("Small Caps").small_caps(true);
225        p.add_run(" | ");
226        p.add_run("Expanded").character_spacing(Length::twips(40));
227        p.add_run(" | ");
228        p.add_run("Hidden text (not visible)").hidden(true);
229    }
230
231    doc.add_paragraph("");
232
233    // Underline Styles
234    doc.add_paragraph("1.3 Underline Styles").style("Heading2");
235    {
236        let mut p = doc.add_paragraph("");
237        p.add_run("Single ")
238            .underline_style(rdocx::UnderlineStyle::Single);
239        p.add_run("Double ")
240            .underline_style(rdocx::UnderlineStyle::Double);
241        p.add_run("Thick ")
242            .underline_style(rdocx::UnderlineStyle::Thick);
243        p.add_run("Dotted ")
244            .underline_style(rdocx::UnderlineStyle::Dotted);
245        p.add_run("Dash ")
246            .underline_style(rdocx::UnderlineStyle::Dash);
247        p.add_run("Wave ")
248            .underline_style(rdocx::UnderlineStyle::Wave);
249    }
250
251    // ── SECTION 2: PARAGRAPH FORMATTING ──
252    doc.add_paragraph("").page_break_before(true);
253    doc.add_paragraph("2. Paragraph Formatting")
254        .style("Heading1");
255
256    doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
257    doc.add_paragraph("Paragraph with light green shading")
258        .shading("E2EFDA");
259    doc.add_paragraph("Paragraph with bottom border")
260        .border_bottom(BorderStyle::Single, 6, "2E75B6");
261    doc.add_paragraph("Paragraph with all borders (red)")
262        .border_all(BorderStyle::Single, 4, "FF0000");
263
264    doc.add_paragraph("2.2 Indentation").style("Heading2");
265    doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
266        .indent_left(Length::inches(1.0))
267        .hanging_indent(Length::inches(0.5));
268    doc.add_paragraph("0.5-inch first-line indent on this paragraph")
269        .first_line_indent(Length::inches(0.5));
270    doc.add_paragraph("Both left and right indent (0.75 inches each)")
271        .indent_left(Length::inches(0.75))
272        .indent_right(Length::inches(0.75));
273
274    doc.add_paragraph("2.3 Spacing & Line Height")
275        .style("Heading2");
276    doc.add_paragraph("Extra space before (24pt) and after (12pt)")
277        .space_before(Length::pt(24.0))
278        .space_after(Length::pt(12.0));
279    doc.add_paragraph(
280        "Double line spacing paragraph. This text should have extra vertical space between \
281         lines to demonstrate line_spacing_multiple(2.0).",
282    )
283    .line_spacing_multiple(2.0);
284    doc.add_paragraph("Exact 20pt line spacing (fixed height).")
285        .line_spacing(20.0);
286
287    doc.add_paragraph("2.4 Pagination Controls")
288        .style("Heading2");
289    doc.add_paragraph("keep_with_next — this paragraph stays with the next")
290        .keep_with_next(true);
291    doc.add_paragraph("(This paragraph was kept with the one above.)");
292    doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
293        .keep_together(true);
294    doc.add_paragraph("widow_control — prevents widow/orphan lines")
295        .widow_control(true);
296
297    // ── SECTION 3: LISTS ──
298    doc.add_paragraph("").page_break_before(true);
299    doc.add_paragraph("3. Lists").style("Heading1");
300
301    doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
302    doc.add_bullet_list_item("First item", 0);
303    doc.add_bullet_list_item("Second item", 0);
304    doc.add_bullet_list_item("Nested level 1", 1);
305    doc.add_bullet_list_item("Nested level 2", 2);
306    doc.add_bullet_list_item("Back to level 1", 1);
307    doc.add_bullet_list_item("Third item", 0);
308
309    doc.add_paragraph("");
310
311    doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
312    doc.add_numbered_list_item("First numbered item", 0);
313    doc.add_numbered_list_item("Second numbered item", 0);
314    doc.add_numbered_list_item("Sub-item A", 1);
315    doc.add_numbered_list_item("Sub-item B", 1);
316    doc.add_numbered_list_item("Third numbered item", 0);
317
318    // ── SECTION 4: TAB STOPS ──
319    doc.add_paragraph("");
320    doc.add_paragraph("4. Tab Stops").style("Heading1");
321
322    doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
323    doc.add_paragraph("Left\tCenter\tRight\tDecimal")
324        .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
325        .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
326        .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
327        .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
328
329    doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
330    doc.add_paragraph("Item\t\tPrice")
331        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
332        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
333        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
334    doc.add_paragraph("Widget\t\t$19.99")
335        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
336        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
337        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
338    doc.add_paragraph("Gadget\t\t$249.50")
339        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
340        .add_tab_stop_with_leader(
341            TabAlignment::Right,
342            Length::inches(4.0),
343            TabLeader::Underscore,
344        )
345        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
346
347    // ── SECTION 5: TABLES ──
348    doc.add_paragraph("").page_break_before(true);
349    doc.add_paragraph("5. Tables").style("Heading1");
350
351    doc.add_paragraph("5.1 Basic Table").style("Heading2");
352    {
353        let mut tbl = doc
354            .add_table(4, 3)
355            .borders(BorderStyle::Single, 4, "000000");
356        for col in 0..3 {
357            tbl.cell(0, col).unwrap().shading("2E75B6");
358        }
359        tbl.cell(0, 0).unwrap().set_text("Name");
360        tbl.cell(0, 1).unwrap().set_text("Role");
361        tbl.cell(0, 2).unwrap().set_text("Location");
362        tbl.cell(1, 0).unwrap().set_text("Walter White");
363        tbl.cell(1, 1).unwrap().set_text("CEO");
364        tbl.cell(1, 2).unwrap().set_text("Albuquerque");
365        tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
366        tbl.cell(2, 1).unwrap().set_text("CTO");
367        tbl.cell(2, 2).unwrap().set_text("Remote");
368        tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
369        tbl.cell(3, 1).unwrap().set_text("Security");
370        tbl.cell(3, 2).unwrap().set_text("Washington");
371    }
372
373    doc.add_paragraph("");
374
375    doc.add_paragraph("5.2 Column Span & Cell Shading")
376        .style("Heading2");
377    {
378        let mut tbl = doc
379            .add_table(3, 4)
380            .borders(BorderStyle::Single, 4, "000000")
381            .width_pct(100.0);
382        tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
383        tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
384        tbl.cell(1, 0).unwrap().set_text("Region");
385        tbl.cell(1, 0).unwrap().shading("D6E4F0");
386        tbl.cell(1, 1).unwrap().set_text("Q1");
387        tbl.cell(1, 1).unwrap().shading("D6E4F0");
388        tbl.cell(1, 2).unwrap().set_text("Q2");
389        tbl.cell(1, 2).unwrap().shading("D6E4F0");
390        tbl.cell(1, 3).unwrap().set_text("Total");
391        tbl.cell(1, 3).unwrap().shading("D6E4F0");
392        tbl.cell(2, 0).unwrap().set_text("Americas");
393        tbl.cell(2, 1).unwrap().set_text("$2.4M");
394        tbl.cell(2, 2).unwrap().set_text("$2.7M");
395        tbl.cell(2, 3).unwrap().set_text("$5.1M");
396    }
397
398    doc.add_paragraph("");
399
400    doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
401    {
402        let mut tbl = doc
403            .add_table(4, 3)
404            .borders(BorderStyle::Single, 4, "000000");
405        tbl.cell(0, 0).unwrap().set_text("Category");
406        tbl.cell(0, 0).unwrap().shading("E2EFDA");
407        tbl.cell(0, 1).unwrap().set_text("Item");
408        tbl.cell(0, 1).unwrap().shading("E2EFDA");
409        tbl.cell(0, 2).unwrap().set_text("Price");
410        tbl.cell(0, 2).unwrap().shading("E2EFDA");
411        tbl.cell(1, 0).unwrap().set_text("Hardware");
412        tbl.cell(1, 0).unwrap().v_merge_restart();
413        tbl.cell(1, 1).unwrap().set_text("Laptop");
414        tbl.cell(1, 2).unwrap().set_text("$1,200");
415        tbl.cell(2, 0).unwrap().v_merge_continue();
416        tbl.cell(2, 1).unwrap().set_text("Monitor");
417        tbl.cell(2, 2).unwrap().set_text("$450");
418        tbl.cell(3, 0).unwrap().set_text("Software");
419        tbl.cell(3, 1).unwrap().set_text("IDE License");
420        tbl.cell(3, 2).unwrap().set_text("$200/yr");
421    }
422
423    doc.add_paragraph("");
424
425    doc.add_paragraph("5.4 Nested Table").style("Heading2");
426    {
427        let mut tbl = doc
428            .add_table(2, 2)
429            .borders(BorderStyle::Single, 6, "2E75B6");
430        tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
431        tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
432        tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
433        {
434            let mut cell = tbl.cell(1, 1).unwrap();
435            cell.set_text("Nested table below:");
436            let mut nested = cell
437                .add_table(2, 2)
438                .borders(BorderStyle::Single, 2, "FF6600");
439            nested.cell(0, 0).unwrap().set_text("A");
440            nested.cell(0, 1).unwrap().set_text("B");
441            nested.cell(1, 0).unwrap().set_text("C");
442            nested.cell(1, 1).unwrap().set_text("D");
443        }
444    }
445
446    doc.add_paragraph("");
447
448    doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
449        .style("Heading2");
450    {
451        let mut tbl = doc
452            .add_table(2, 3)
453            .borders(BorderStyle::Single, 4, "666666");
454        tbl.row(0).unwrap().height(Length::pt(50.0)).header();
455        tbl.cell(0, 0).unwrap().set_text("Top");
456        tbl.cell(0, 0)
457            .unwrap()
458            .vertical_alignment(VerticalAlignment::Top)
459            .shading("FFF2CC");
460        tbl.cell(0, 1).unwrap().set_text("Center");
461        tbl.cell(0, 1)
462            .unwrap()
463            .vertical_alignment(VerticalAlignment::Center)
464            .shading("D9E2F3");
465        tbl.cell(0, 2).unwrap().set_text("Bottom");
466        tbl.cell(0, 2)
467            .unwrap()
468            .vertical_alignment(VerticalAlignment::Bottom)
469            .shading("E2EFDA");
470        tbl.row(1).unwrap().cant_split();
471        tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
472        tbl.cell(1, 0).unwrap().no_wrap();
473        tbl.cell(1, 1).unwrap().set_text("Fixed width");
474        tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
475        tbl.cell(1, 2).unwrap().set_text("Normal");
476    }
477
478    // ── SECTION 6: IMAGES ──
479    doc.add_paragraph("").page_break_before(true);
480    doc.add_paragraph("6. Images").style("Heading1");
481
482    doc.add_paragraph("6.1 Inline Image").style("Heading2");
483    doc.add_paragraph("A blue gradient image below:");
484    let img = create_sample_png(200, 50, [0, 80, 200]);
485    doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
486
487    doc.add_paragraph("");
488    doc.add_paragraph("6.2 Header Image").style("Heading2");
489    let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
490    doc.set_header_image(
491        &hdr_img,
492        "header_logo.png",
493        Length::inches(2.0),
494        Length::inches(0.2),
495    );
496    doc.add_paragraph("The header now contains an inline image (check the top of this page).");
497
498    doc.add_paragraph("");
499    doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
500
501    // ── SECTION 7: CONTENT MANIPULATION ──
502    doc.add_paragraph("").page_break_before(true);
503    doc.add_paragraph("7. Content Manipulation")
504        .style("Heading1");
505
506    doc.add_paragraph("7.1 Placeholder Replacement")
507        .style("Heading2");
508    doc.add_paragraph("Customer: {{customer}}");
509    doc.add_paragraph("Date: {{date}}");
510    doc.add_paragraph("Reference: {{ref_number}}");
511    {
512        let mut tbl = doc
513            .add_table(2, 2)
514            .borders(BorderStyle::Single, 4, "000000");
515        tbl.cell(0, 0).unwrap().set_text("Project");
516        tbl.cell(0, 0).unwrap().shading("D6E4F0");
517        tbl.cell(0, 1).unwrap().set_text("{{project}}");
518        tbl.cell(1, 0).unwrap().set_text("Status");
519        tbl.cell(1, 0).unwrap().shading("D6E4F0");
520        tbl.cell(1, 1).unwrap().set_text("{{status}}");
521    }
522
523    let mut replacements = HashMap::new();
524    replacements.insert("{{customer}}", "Tensorbee Inc.");
525    replacements.insert("{{date}}", "February 22, 2026");
526    replacements.insert("{{ref_number}}", "TB-2026-001");
527    replacements.insert("{{project}}", "Infrastructure Upgrade");
528    replacements.insert("{{status}}", "In Progress");
529    let n = doc.replace_all(&replacements);
530    doc.add_paragraph(&format!("({n} placeholders replaced above)"));
531
532    doc.add_paragraph("");
533
534    doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
535    doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
536    let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
537    doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
538
539    doc.add_paragraph("");
540
541    doc.add_paragraph("7.3 Content Insertion").style("Heading2");
542    doc.add_paragraph("Section A: First content.");
543    doc.add_paragraph("Section C: Third content.");
544    if let Some(idx) = doc.find_content_index("Section C") {
545        doc.insert_paragraph(
546            idx,
547            "Section B: Inserted between A and C via find_content_index().",
548        );
549    }
550
551    // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
552    doc.add_paragraph("").section_break(SectionBreak::NextPage);
553    doc.add_paragraph("8. Section Breaks & Orientation")
554        .style("Heading1");
555    doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
556
557    {
558        let mut tbl = doc
559            .add_table(3, 7)
560            .borders(BorderStyle::Single, 4, "2E75B6");
561        let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
562        for (c, h) in headers.iter().enumerate() {
563            tbl.cell(0, c).unwrap().set_text(h);
564            tbl.cell(0, c).unwrap().shading("2E75B6");
565        }
566        let data = [
567            [
568                "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
569            ],
570            [
571                "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
572            ],
573        ];
574        for (r, row) in data.iter().enumerate() {
575            for (c, v) in row.iter().enumerate() {
576                tbl.cell(r + 1, c).unwrap().set_text(v);
577            }
578        }
579    }
580
581    doc.add_paragraph("")
582        .section_break(SectionBreak::NextPage)
583        .section_landscape();
584
585    // ── SECTION 9: CUSTOM STYLES ──
586    doc.add_paragraph("9. Custom Styles").style("Heading1");
587    doc.add_style(
588        StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
589            .based_on("Normal")
590            .paragraph_properties({
591                let mut ppr = rdocx_oxml::properties::CT_PPr::default();
592                ppr.shading = Some(rdocx_oxml::properties::CT_Shd {
593                    val: "clear".to_string(),
594                    color: None,
595                    fill: Some("FFF2CC".to_string()),
596                });
597                ppr
598            })
599            .run_properties({
600                let mut rpr = rdocx_oxml::properties::CT_RPr::default();
601                rpr.bold = Some(true);
602                rpr.color = Some("C45911".to_string());
603                rpr
604            }),
605    );
606    doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
607        .style("CustomHighlight");
608
609    doc.add_paragraph("");
610
611    // ── SECTION 10: DOCUMENT INTELLIGENCE ──
612    doc.add_paragraph("10. Document Intelligence API")
613        .style("Heading1");
614
615    let wc = doc.word_count();
616    let hc = doc.headings().len();
617    let ic = doc.images().len();
618    let lc = doc.links().len();
619    doc.add_paragraph(&format!("Word count: {wc}"));
620    doc.add_paragraph(&format!("Heading count: {hc}"));
621    doc.add_paragraph(&format!("Image count: {ic}"));
622    doc.add_paragraph(&format!("Link count: {lc}"));
623
624    let outline = doc.document_outline();
625    doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
626
627    let issues = doc.audit_accessibility();
628    doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
629    for issue in &issues {
630        doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
631    }
632
633    // ── SECTION 11: DOCUMENT MERGING ──
634    doc.add_paragraph("");
635    doc.add_paragraph("11. Document Merging").style("Heading1");
636    {
637        let mut other = Document::new();
638        other.add_paragraph("This paragraph was merged from another document using append().");
639        doc.append(&other);
640    }
641
642    doc.add_paragraph("");
643
644    // ── SUMMARY ──
645    doc.add_paragraph("Summary of Demonstrated Features")
646        .style("Heading1");
647    let features = [
648        "Page setup: size, margins, header/footer distance, gutter",
649        "Document metadata: title, author, subject, keywords",
650        "Headers and footers: text, images, different first page",
651        "Background images (full-page behind text)",
652        "Table of Contents generation with bookmarks",
653        "Text formatting: bold, italic, underline styles, strike, color, size, font",
654        "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
655        "Paragraph formatting: alignment, borders, shading, spacing, indentation",
656        "Pagination controls: keep-with-next, keep-together, widow control",
657        "Bullet and numbered lists with nesting",
658        "Tab stops with dot/underscore/hyphen leaders",
659        "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
660        "Row properties: header rows, exact height, cant-split",
661        "Cell properties: width, no-wrap, vertical alignment",
662        "Inline images and header images",
663        "Placeholder replacement (single and batch)",
664        "Regex-based find and replace",
665        "Content insertion at specific positions",
666        "Section breaks with mixed portrait/landscape",
667        "Custom paragraph and character styles",
668        "Document intelligence: word count, headings, outline, images, links",
669        "Accessibility audit",
670        "Document merging (append)",
671        "Export: DOCX, PDF, HTML, Markdown",
672    ];
673    for f in &features {
674        doc.add_bullet_list_item(f, 0);
675    }
676    doc.add_paragraph("");
677    doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
678        .alignment(Alignment::Center)
679        .shading("E2EFDA")
680        .border_all(BorderStyle::Single, 2, "00AA00");
681
682    doc
683}
684
685// =============================================================================
686// 2. PROPOSAL DOCUMENT — Deep navy + gold scheme
687// =============================================================================
688fn generate_proposal(_samples_dir: &Path) -> Document {
689    let mut doc = Document::new();
690
691    // Colors: Navy #1B2A4A, Gold #C5922E, Light #F4F1EB
692    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
693    doc.set_margins(
694        Length::inches(1.0),
695        Length::inches(1.0),
696        Length::inches(1.0),
697        Length::inches(1.0),
698    );
699    doc.set_title("AI Platform Modernization Proposal");
700    doc.set_author("Walter White");
701    doc.set_subject("Technology Proposal");
702    doc.set_keywords("proposal, AI, modernization, Tensorbee");
703
704    // Banner header
705    let logo_img = create_logo_png(220, 48);
706    let banner = build_header_banner_xml(
707        "rId1",
708        &BannerOpts {
709            bg_color: "1B2A4A",
710            banner_width: 7772400,
711            banner_height: 914400,
712            logo_width: 2011680,
713            logo_height: 438912,
714            logo_x_offset: 295125,
715            logo_y_offset: 237744,
716        },
717    );
718    doc.set_raw_header_with_images(
719        banner,
720        &[("rId1", &logo_img, "logo.png")],
721        rdocx_oxml::header_footer::HdrFtrType::Default,
722    );
723    doc.set_different_first_page(true);
724    let cover_banner = build_header_banner_xml(
725        "rId1",
726        &BannerOpts {
727            bg_color: "C5922E",
728            banner_width: 7772400,
729            banner_height: 914400,
730            logo_width: 2011680,
731            logo_height: 438912,
732            logo_x_offset: 295125,
733            logo_y_offset: 237744,
734        },
735    );
736    doc.set_raw_header_with_images(
737        cover_banner,
738        &[("rId1", &logo_img, "logo.png")],
739        rdocx_oxml::header_footer::HdrFtrType::First,
740    );
741    doc.set_margins(
742        Length::twips(2292),
743        Length::twips(1440),
744        Length::twips(1440),
745        Length::twips(1440),
746    );
747    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
748    doc.set_footer("Tensorbee — Confidential");
749
750    // Custom styles
751    doc.add_style(
752        StyleBuilder::paragraph("ProposalTitle", "Proposal Title")
753            .based_on("Normal")
754            .run_properties({
755                let mut rpr = rdocx_oxml::properties::CT_RPr::default();
756                rpr.bold = Some(true);
757                rpr.font_ascii = Some("Georgia".to_string());
758                rpr.font_hansi = Some("Georgia".to_string());
759                rpr.sz = Some(rdocx_oxml::HalfPoint(56)); // half-points → 28pt
760                rpr.color = Some("1B2A4A".to_string());
761                rpr
762            }),
763    );
764
765    // ── Cover Page ──
766    for _ in 0..4 {
767        doc.add_paragraph("");
768    }
769    doc.add_paragraph("AI Platform Modernization")
770        .style("ProposalTitle")
771        .alignment(Alignment::Center);
772    doc.add_paragraph("")
773        .alignment(Alignment::Center)
774        .border_bottom(BorderStyle::Single, 8, "C5922E");
775    {
776        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
777        p.add_run("Prepared for Global Financial Corp.")
778            .size(14.0)
779            .color("666666")
780            .italic(true);
781    }
782    {
783        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
784        p.add_run("Prepared by: Walter White, CEO — Tensorbee")
785            .size(12.0)
786            .color("888888");
787    }
788    {
789        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
790        p.add_run("February 22, 2026").size(12.0).color("888888");
791    }
792
793    // ── TOC ──
794    doc.add_paragraph("").page_break_before(true);
795    doc.add_paragraph("Table of Contents").style("Heading1");
796    // We'll add a manual-style TOC since the insert_toc goes at a specific position
797    doc.insert_toc(doc.content_count(), 2);
798
799    // ── Executive Summary ──
800    doc.add_paragraph("").page_break_before(true);
801    doc.add_paragraph("Executive Summary").style("Heading1");
802    doc.add_paragraph(
803        "Tensorbee proposes a comprehensive modernization of Global Financial Corp's AI platform, \
804         transitioning from legacy batch-processing systems to a real-time inference architecture. \
805         This transformation will reduce model deployment time from weeks to hours, improve \
806         prediction accuracy by an estimated 23%, and deliver $4.2M in annual operational savings.",
807    )
808    .first_line_indent(Length::inches(0.3));
809    doc.add_paragraph(
810        "The project spans three phases over 18 months, with a total investment of $2.8M. \
811         Tensorbee brings deep expertise in ML infrastructure, having successfully completed \
812         similar transformations for three Fortune 500 financial institutions.",
813    )
814    .first_line_indent(Length::inches(0.3));
815
816    // Key metrics table
817    doc.add_paragraph("");
818    {
819        let mut tbl = doc
820            .add_table(5, 2)
821            .borders(BorderStyle::Single, 4, "1B2A4A")
822            .width_pct(80.0);
823        tbl.cell(0, 0).unwrap().set_text("Key Metric");
824        tbl.cell(0, 0).unwrap().shading("1B2A4A");
825        tbl.cell(0, 1).unwrap().set_text("Value");
826        tbl.cell(0, 1).unwrap().shading("1B2A4A");
827        let rows = [
828            ("Total Investment", "$2.8M"),
829            ("Annual Savings", "$4.2M"),
830            ("ROI (Year 1)", "150%"),
831            ("Timeline", "18 months"),
832        ];
833        for (i, (k, v)) in rows.iter().enumerate() {
834            tbl.cell(i + 1, 0).unwrap().set_text(k);
835            if i % 2 == 0 {
836                tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
837            }
838            tbl.cell(i + 1, 1).unwrap().set_text(v);
839            if i % 2 == 0 {
840                tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
841            }
842        }
843    }
844
845    // ── Problem Statement ──
846    doc.add_paragraph("").page_break_before(true);
847    doc.add_paragraph("Problem Statement").style("Heading1");
848    doc.add_paragraph(
849        "Global Financial Corp's current AI infrastructure faces three critical challenges:",
850    );
851    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);
852    doc.add_numbered_list_item("Scalability Constraints — The batch processing architecture cannot handle real-time inference demands during peak trading hours.", 0);
853    doc.add_numbered_list_item("Technical Debt — Legacy Python 2.7 codebases and manual deployment scripts create reliability risks and slow iteration speed.", 0);
854
855    // ── Proposed Solution ──
856    doc.add_paragraph("").page_break_before(true);
857    doc.add_paragraph("Proposed Solution").style("Heading1");
858
859    doc.add_paragraph("Phase 1: Foundation (Months 1-6)")
860        .style("Heading2");
861    doc.add_bullet_list_item("Deploy Kubernetes-based ML serving infrastructure", 0);
862    doc.add_bullet_list_item("Implement CI/CD pipeline for model deployment", 0);
863    doc.add_bullet_list_item("Migrate top 5 production models to new platform", 0);
864
865    doc.add_paragraph("Phase 2: Expansion (Months 7-12)")
866        .style("Heading2");
867    doc.add_bullet_list_item(
868        "Real-time feature engineering pipeline (Apache Kafka + Flink)",
869        0,
870    );
871    doc.add_bullet_list_item("A/B testing framework for model variants", 0);
872    doc.add_bullet_list_item("Automated model monitoring and drift detection", 0);
873
874    doc.add_paragraph("Phase 3: Optimization (Months 13-18)")
875        .style("Heading2");
876    doc.add_bullet_list_item("GPU inference optimization (TensorRT/ONNX Runtime)", 0);
877    doc.add_bullet_list_item("Multi-region deployment for latency reduction", 0);
878    doc.add_bullet_list_item(
879        "Self-service model deployment portal for data science teams",
880        0,
881    );
882
883    // ── Budget ──
884    doc.add_paragraph("Budget Breakdown").style("Heading1");
885    {
886        let mut tbl = doc
887            .add_table(6, 3)
888            .borders(BorderStyle::Single, 4, "1B2A4A")
889            .width_pct(100.0);
890        tbl.cell(0, 0).unwrap().set_text("Category");
891        tbl.cell(0, 0).unwrap().shading("1B2A4A");
892        tbl.cell(0, 1).unwrap().set_text("Cost");
893        tbl.cell(0, 1).unwrap().shading("1B2A4A");
894        tbl.cell(0, 2).unwrap().set_text("Notes");
895        tbl.cell(0, 2).unwrap().shading("1B2A4A");
896        let items = [
897            (
898                "Infrastructure",
899                "$450,000",
900                "Cloud compute + GPU instances",
901            ),
902            ("Engineering Services", "$1,600,000", "12 FTE x 18 months"),
903            (
904                "Software Licenses",
905                "$350,000",
906                "Kafka, monitoring, CI/CD tools",
907            ),
908            (
909                "Training & Enablement",
910                "$200,000",
911                "Team training, documentation",
912            ),
913            ("Contingency (10%)", "$200,000", "Risk buffer"),
914        ];
915        for (i, (cat, cost, note)) in items.iter().enumerate() {
916            tbl.cell(i + 1, 0).unwrap().set_text(cat);
917            tbl.cell(i + 1, 1).unwrap().set_text(cost);
918            tbl.cell(i + 1, 2).unwrap().set_text(note);
919            if i % 2 == 0 {
920                tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
921                tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
922                tbl.cell(i + 1, 2).unwrap().shading("F4F1EB");
923            }
924        }
925    }
926
927    // ── Closing ──
928    doc.add_paragraph("");
929    doc.add_paragraph("Next Steps").style("Heading1");
930    doc.add_numbered_list_item(
931        "Schedule technical deep-dive with GFC engineering team (Week 1)",
932        0,
933    );
934    doc.add_numbered_list_item("Finalize scope and sign SOW (Week 2-3)", 0);
935    doc.add_numbered_list_item("Kick off Phase 1 with joint planning session (Week 4)", 0);
936
937    doc.add_paragraph("");
938    {
939        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
940        p.add_run("Walter White")
941            .bold(true)
942            .size(14.0)
943            .color("1B2A4A");
944    }
945    {
946        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
947        p.add_run("CEO, Tensorbee | walter@tensorbee.com")
948            .size(10.0)
949            .color("888888");
950    }
951
952    doc
953}
954
955// =============================================================================
956// 3. QUOTE / BILL OF MATERIALS — Teal + orange scheme
957// =============================================================================
958fn generate_quote(_samples_dir: &Path) -> Document {
959    let mut doc = Document::new();
960
961    // Colors: Teal #008B8B, Dark #1A3C3C, Orange #E07020, Light #F0F8F8
962    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
963    doc.set_margins(
964        Length::inches(0.75),
965        Length::inches(0.75),
966        Length::inches(0.75),
967        Length::inches(0.75),
968    );
969    doc.set_title("Quotation QT-2026-0147");
970    doc.set_author("Walter White");
971    doc.set_keywords("quote, BOM, Tensorbee");
972
973    doc.set_header("Tensorbee — Quotation");
974    doc.set_footer("QT-2026-0147 | Page");
975
976    // ── Header Block ──
977    {
978        let mut p = doc.add_paragraph("").alignment(Alignment::Left);
979        p.add_run("TENSORBEE")
980            .bold(true)
981            .size(28.0)
982            .color("008B8B")
983            .font("Helvetica");
984    }
985    doc.add_paragraph("123 Innovation Drive, Suite 400")
986        .alignment(Alignment::Left);
987    doc.add_paragraph("San Francisco, CA 94105 | +1 (415) 555-0199")
988        .alignment(Alignment::Left);
989    doc.add_paragraph("")
990        .border_bottom(BorderStyle::Single, 8, "008B8B");
991
992    // Quote meta
993    doc.add_paragraph("");
994    {
995        let mut tbl = doc.add_table(4, 4).width_pct(100.0);
996        tbl.cell(0, 0).unwrap().set_text("QUOTATION");
997        tbl.cell(0, 0).unwrap().shading("008B8B").grid_span(2);
998        tbl.cell(0, 2).unwrap().set_text("DATE");
999        tbl.cell(0, 2).unwrap().shading("008B8B");
1000        tbl.cell(0, 3).unwrap().set_text("VALID UNTIL");
1001        tbl.cell(0, 3).unwrap().shading("008B8B");
1002        tbl.cell(1, 0).unwrap().set_text("Quote #:");
1003        tbl.cell(1, 1).unwrap().set_text("QT-2026-0147");
1004        tbl.cell(1, 2).unwrap().set_text("Feb 22, 2026");
1005        tbl.cell(1, 3).unwrap().set_text("Mar 22, 2026");
1006        tbl.cell(2, 0).unwrap().set_text("Prepared by:");
1007        tbl.cell(2, 1).unwrap().set_text("Walter White");
1008        tbl.cell(2, 2).unwrap().set_text("Payment:");
1009        tbl.cell(2, 3).unwrap().set_text("Net 30");
1010        tbl.cell(3, 0).unwrap().set_text("Customer:");
1011        tbl.cell(3, 1).unwrap().set_text("Meridian Dynamics LLC");
1012        tbl.cell(3, 2).unwrap().set_text("Currency:");
1013        tbl.cell(3, 3).unwrap().set_text("USD");
1014    }
1015
1016    doc.add_paragraph("");
1017
1018    // ── Bill of Materials ──
1019    {
1020        let mut p = doc.add_paragraph("");
1021        p.add_run("Bill of Materials")
1022            .bold(true)
1023            .size(16.0)
1024            .color("1A3C3C");
1025    }
1026    doc.add_paragraph("");
1027
1028    {
1029        let mut tbl = doc
1030            .add_table(10, 6)
1031            .borders(BorderStyle::Single, 2, "008B8B")
1032            .width_pct(100.0)
1033            .layout_fixed();
1034        // Headers
1035        let hdrs = [
1036            "#",
1037            "Part Number",
1038            "Description",
1039            "Qty",
1040            "Unit Price",
1041            "Total",
1042        ];
1043        for (c, h) in hdrs.iter().enumerate() {
1044            tbl.cell(0, c).unwrap().set_text(h);
1045            tbl.cell(0, c).unwrap().shading("008B8B");
1046        }
1047
1048        let items: Vec<(&str, &str, &str, &str, &str)> = vec![
1049            (
1050                "TB-GPU-A100",
1051                "NVIDIA A100 80GB GPU",
1052                "4",
1053                "$12,500.00",
1054                "$50,000.00",
1055            ),
1056            (
1057                "TB-SRV-R750",
1058                "Dell R750xa Server Chassis",
1059                "2",
1060                "$8,200.00",
1061                "$16,400.00",
1062            ),
1063            (
1064                "TB-RAM-256G",
1065                "256GB DDR5 ECC Memory Module",
1066                "8",
1067                "$890.00",
1068                "$7,120.00",
1069            ),
1070            (
1071                "TB-SSD-3840",
1072                "3.84TB NVMe U.2 SSD",
1073                "8",
1074                "$1,150.00",
1075                "$9,200.00",
1076            ),
1077            (
1078                "TB-NET-CX7",
1079                "ConnectX-7 200GbE NIC",
1080                "4",
1081                "$1,800.00",
1082                "$7,200.00",
1083            ),
1084            (
1085                "TB-SW-48P",
1086                "48-Port 100GbE Switch",
1087                "1",
1088                "$22,000.00",
1089                "$22,000.00",
1090            ),
1091            (
1092                "TB-CAB-RACK",
1093                "42U Server Rack + PDU",
1094                "1",
1095                "$4,500.00",
1096                "$4,500.00",
1097            ),
1098            (
1099                "TB-SVC-INST",
1100                "Installation & Configuration",
1101                "1",
1102                "$8,500.00",
1103                "$8,500.00",
1104            ),
1105            (
1106                "TB-SVC-SUPP",
1107                "3-Year Premium Support",
1108                "1",
1109                "$15,000.00",
1110                "$15,000.00",
1111            ),
1112        ];
1113
1114        for (i, (pn, desc, qty, unit, total)) in items.iter().enumerate() {
1115            let row = i + 1;
1116            tbl.cell(row, 0).unwrap().set_text(&format!("{}", i + 1));
1117            tbl.cell(row, 1).unwrap().set_text(pn);
1118            tbl.cell(row, 2).unwrap().set_text(desc);
1119            tbl.cell(row, 3).unwrap().set_text(qty);
1120            tbl.cell(row, 4).unwrap().set_text(unit);
1121            tbl.cell(row, 5).unwrap().set_text(total);
1122            if i % 2 == 0 {
1123                for c in 0..6 {
1124                    tbl.cell(row, c).unwrap().shading("F0F8F8");
1125                }
1126            }
1127        }
1128    }
1129
1130    doc.add_paragraph("");
1131
1132    // ── Totals ──
1133    {
1134        let mut tbl = doc
1135            .add_table(4, 2)
1136            .borders(BorderStyle::Single, 2, "008B8B")
1137            .width(Length::inches(3.5))
1138            .alignment(Alignment::Right);
1139        tbl.cell(0, 0).unwrap().set_text("Subtotal");
1140        tbl.cell(0, 1).unwrap().set_text("$139,920.00");
1141        tbl.cell(1, 0).unwrap().set_text("Shipping & Handling");
1142        tbl.cell(1, 1).unwrap().set_text("$2,500.00");
1143        tbl.cell(2, 0).unwrap().set_text("Tax (8.625%)");
1144        tbl.cell(2, 1).unwrap().set_text("$12,068.10");
1145        tbl.cell(3, 0).unwrap().set_text("TOTAL");
1146        tbl.cell(3, 0).unwrap().shading("E07020");
1147        tbl.cell(3, 1).unwrap().set_text("$154,488.10");
1148        tbl.cell(3, 1).unwrap().shading("E07020");
1149    }
1150
1151    doc.add_paragraph("");
1152
1153    // ── Terms & Conditions ──
1154    {
1155        let mut p = doc.add_paragraph("");
1156        p.add_run("Terms & Conditions")
1157            .bold(true)
1158            .size(14.0)
1159            .color("1A3C3C");
1160    }
1161    doc.add_numbered_list_item(
1162        "This quotation is valid for 30 calendar days from the date of issue.",
1163        0,
1164    );
1165    doc.add_numbered_list_item(
1166        "All prices are in USD and exclusive of applicable taxes unless stated.",
1167        0,
1168    );
1169    doc.add_numbered_list_item("Standard lead time is 4-6 weeks from PO receipt.", 0);
1170    doc.add_numbered_list_item(
1171        "Payment terms: Net 30 from invoice date. 2% discount for payment within 10 days.",
1172        0,
1173    );
1174    doc.add_numbered_list_item(
1175        "Warranty: 3-year manufacturer warranty on all hardware components.",
1176        0,
1177    );
1178    doc.add_numbered_list_item(
1179        "Returns subject to 15% restocking fee if initiated after 14 days.",
1180        0,
1181    );
1182
1183    doc.add_paragraph("");
1184    doc.add_paragraph("")
1185        .border_bottom(BorderStyle::Single, 4, "008B8B");
1186    doc.add_paragraph("");
1187    {
1188        let mut p = doc.add_paragraph("");
1189        p.add_run("Accepted by: ").bold(true);
1190        p.add_run("___________________________ Date: ___________");
1191    }
1192    {
1193        let mut p = doc.add_paragraph("");
1194        p.add_run("Print Name: ").bold(true);
1195        p.add_run("___________________________ Title: ___________");
1196    }
1197
1198    doc
1199}
1200
1201// =============================================================================
1202// 4. INVOICE — Crimson + charcoal scheme
1203// =============================================================================
1204fn generate_invoice(_samples_dir: &Path) -> Document {
1205    let mut doc = Document::new();
1206
1207    // Colors: Crimson #B22222, Charcoal #333333, Light #FAF0F0
1208    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1209    doc.set_margins(
1210        Length::inches(0.75),
1211        Length::inches(0.75),
1212        Length::inches(0.75),
1213        Length::inches(0.75),
1214    );
1215    doc.set_title("Invoice INV-2026-0392");
1216    doc.set_author("Walter White");
1217    doc.set_keywords("invoice, Tensorbee");
1218
1219    doc.set_footer("Tensorbee — Thank you for your business!");
1220
1221    // ── Company & Invoice Header ──
1222    {
1223        let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1224        // Company name left, INVOICE right
1225        {
1226            let mut cell = tbl.cell(0, 0).unwrap();
1227            let mut p = cell.add_paragraph("");
1228            p.add_run("TENSORBEE")
1229                .bold(true)
1230                .size(32.0)
1231                .color("B22222")
1232                .font("Helvetica");
1233        }
1234        {
1235            let mut cell = tbl.cell(0, 1).unwrap();
1236            let mut p = cell.add_paragraph("");
1237            p.add_run("INVOICE").bold(true).size(32.0).color("333333");
1238        }
1239        tbl.cell(1, 0)
1240            .unwrap()
1241            .set_text("123 Innovation Drive, Suite 400");
1242        tbl.cell(1, 1).unwrap().set_text("Invoice #: INV-2026-0392");
1243        tbl.cell(2, 0).unwrap().set_text("San Francisco, CA 94105");
1244        tbl.cell(2, 1).unwrap().set_text("Date: February 22, 2026");
1245        tbl.cell(3, 0).unwrap().set_text("walter@tensorbee.com");
1246        tbl.cell(3, 1).unwrap().set_text("Due Date: March 24, 2026");
1247    }
1248
1249    doc.add_paragraph("")
1250        .border_bottom(BorderStyle::Thick, 8, "B22222");
1251    doc.add_paragraph("");
1252
1253    // ── Bill To / Ship To ──
1254    {
1255        let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1256        tbl.cell(0, 0).unwrap().set_text("BILL TO");
1257        tbl.cell(0, 0).unwrap().shading("B22222");
1258        tbl.cell(0, 1).unwrap().set_text("SHIP TO");
1259        tbl.cell(0, 1).unwrap().shading("B22222");
1260        tbl.cell(1, 0).unwrap().set_text("Meridian Dynamics LLC");
1261        tbl.cell(1, 1).unwrap().set_text("Meridian Dynamics LLC");
1262        tbl.cell(2, 0).unwrap().set_text("456 Enterprise Blvd");
1263        tbl.cell(2, 1).unwrap().set_text("Attn: Server Room B");
1264        tbl.cell(3, 0).unwrap().set_text("Austin, TX 78701");
1265        tbl.cell(3, 1)
1266            .unwrap()
1267            .set_text("456 Enterprise Blvd, Austin, TX 78701");
1268    }
1269
1270    doc.add_paragraph("");
1271
1272    // ── Line Items ──
1273    {
1274        let mut tbl = doc
1275            .add_table(8, 5)
1276            .borders(BorderStyle::Single, 2, "B22222")
1277            .width_pct(100.0);
1278        let hdrs = ["Description", "Qty", "Unit Price", "Tax", "Amount"];
1279        for (c, h) in hdrs.iter().enumerate() {
1280            tbl.cell(0, c).unwrap().set_text(h);
1281            tbl.cell(0, c).unwrap().shading("B22222");
1282        }
1283        let items = [
1284            (
1285                "ML Infrastructure Setup — Phase 1",
1286                "1",
1287                "$45,000.00",
1288                "$3,881.25",
1289                "$48,881.25",
1290            ),
1291            (
1292                "Data Pipeline Development (160 hrs)",
1293                "160",
1294                "$225.00",
1295                "$3,105.00",
1296                "$39,105.00",
1297            ),
1298            (
1299                "GPU Cluster Configuration",
1300                "1",
1301                "$12,000.00",
1302                "$1,035.00",
1303                "$13,035.00",
1304            ),
1305            (
1306                "API Gateway Implementation",
1307                "1",
1308                "$18,500.00",
1309                "$1,595.63",
1310                "$20,095.63",
1311            ),
1312            (
1313                "Load Testing & QA (80 hrs)",
1314                "80",
1315                "$195.00",
1316                "$1,345.50",
1317                "$16,945.50",
1318            ),
1319            (
1320                "Documentation & Training",
1321                "1",
1322                "$8,000.00",
1323                "$690.00",
1324                "$8,690.00",
1325            ),
1326            (
1327                "Project Management (3 months)",
1328                "3",
1329                "$6,500.00",
1330                "$1,679.63",
1331                "$21,179.63",
1332            ),
1333        ];
1334        for (i, (desc, qty, unit, tax, amt)) in items.iter().enumerate() {
1335            let r = i + 1;
1336            tbl.cell(r, 0).unwrap().set_text(desc);
1337            tbl.cell(r, 1).unwrap().set_text(qty);
1338            tbl.cell(r, 2).unwrap().set_text(unit);
1339            tbl.cell(r, 3).unwrap().set_text(tax);
1340            tbl.cell(r, 4).unwrap().set_text(amt);
1341            if i % 2 == 0 {
1342                for c in 0..5 {
1343                    tbl.cell(r, c).unwrap().shading("FAF0F0");
1344                }
1345            }
1346        }
1347    }
1348
1349    doc.add_paragraph("");
1350
1351    // ── Totals ──
1352    {
1353        let mut tbl = doc
1354            .add_table(4, 2)
1355            .borders(BorderStyle::Single, 2, "B22222")
1356            .width(Length::inches(3.0))
1357            .alignment(Alignment::Right);
1358        tbl.cell(0, 0).unwrap().set_text("Subtotal");
1359        tbl.cell(0, 1).unwrap().set_text("$154,600.00");
1360        tbl.cell(1, 0).unwrap().set_text("Tax (8.625%)");
1361        tbl.cell(1, 1).unwrap().set_text("$13,334.25");
1362        tbl.cell(2, 0).unwrap().set_text("Discount (5%)");
1363        tbl.cell(2, 1).unwrap().set_text("-$7,730.00");
1364        tbl.cell(3, 0).unwrap().set_text("AMOUNT DUE");
1365        tbl.cell(3, 0).unwrap().shading("B22222");
1366        tbl.cell(3, 1).unwrap().set_text("$160,204.25");
1367        tbl.cell(3, 1).unwrap().shading("B22222");
1368    }
1369
1370    doc.add_paragraph("");
1371    doc.add_paragraph("");
1372
1373    // ── Payment Details ──
1374    {
1375        let mut p = doc.add_paragraph("");
1376        p.add_run("Payment Details")
1377            .bold(true)
1378            .size(14.0)
1379            .color("333333");
1380    }
1381    doc.add_paragraph("Bank: Silicon Valley Bank")
1382        .indent_left(Length::inches(0.3));
1383    doc.add_paragraph("Account: Tensorbee Inc. — 0847-2953-1120")
1384        .indent_left(Length::inches(0.3));
1385    doc.add_paragraph("Routing: 121140399")
1386        .indent_left(Length::inches(0.3));
1387    doc.add_paragraph("Swift: SVBKUS6S")
1388        .indent_left(Length::inches(0.3));
1389
1390    doc.add_paragraph("");
1391
1392    doc.add_paragraph("Please include invoice number INV-2026-0392 in the payment reference.")
1393        .shading("FAF0F0")
1394        .border_all(BorderStyle::Single, 2, "B22222");
1395
1396    doc
1397}
1398
1399// =============================================================================
1400// 5. REPORT — Forest green + earth tones, with images & hierarchical sections
1401// =============================================================================
1402fn generate_report(_samples_dir: &Path) -> Document {
1403    let mut doc = Document::new();
1404
1405    // Colors: Forest #2D5016, Sage #6B8E23, Earth #8B7355, Cream #FFFAF0
1406    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1407    doc.set_margins(
1408        Length::inches(1.0),
1409        Length::inches(1.0),
1410        Length::inches(1.0),
1411        Length::inches(1.0),
1412    );
1413    doc.set_title("Q4 2025 Environmental Impact Report");
1414    doc.set_author("Walter White");
1415    doc.set_subject("Quarterly Environmental Report");
1416    doc.set_keywords("environment, sustainability, report, Tensorbee");
1417
1418    doc.set_different_first_page(true);
1419    doc.set_first_page_header("");
1420    doc.set_header("Tensorbee — Q4 2025 Environmental Impact Report");
1421    doc.set_footer("CONFIDENTIAL — Page");
1422
1423    // ── Cover ──
1424    let cover_bg = create_sample_png(612, 792, [20, 50, 15]);
1425    doc.add_background_image(&cover_bg, "report_cover.png");
1426
1427    for _ in 0..5 {
1428        doc.add_paragraph("");
1429    }
1430    {
1431        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1432        p.add_run("Q4 2025")
1433            .bold(true)
1434            .size(48.0)
1435            .color("FFFFFF")
1436            .font("Georgia");
1437    }
1438    {
1439        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1440        p.add_run("Environmental Impact Report")
1441            .size(24.0)
1442            .color("90EE90")
1443            .font("Georgia")
1444            .italic(true);
1445    }
1446    doc.add_paragraph("");
1447    {
1448        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1449        p.add_run("Tensorbee — Sustainability Division")
1450            .size(14.0)
1451            .color("C0C0C0");
1452    }
1453    {
1454        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1455        p.add_run("Prepared by Walter White, Chief Sustainability Officer")
1456            .size(11.0)
1457            .color("AAAAAA");
1458    }
1459
1460    // ── TOC ──
1461    doc.add_paragraph("").page_break_before(true);
1462    doc.insert_toc(doc.content_count(), 3);
1463
1464    // ── Section 1: Executive Overview ──
1465    doc.add_paragraph("").page_break_before(true);
1466    doc.add_paragraph("Executive Overview").style("Heading1");
1467    doc.add_paragraph(
1468        "This report presents Tensorbee's environmental performance for Q4 2025. Our \
1469         sustainability initiatives have yielded a 34% reduction in carbon emissions compared \
1470         to Q4 2024, exceeding our target of 25%. Key achievements include the transition to \
1471         100% renewable energy in our primary data centers and the launch of our carbon offset \
1472         marketplace.",
1473    )
1474    .first_line_indent(Length::inches(0.3));
1475
1476    // Image: performance chart
1477    let chart_img = create_chart_png(400, 200);
1478    doc.add_paragraph("");
1479    doc.add_picture(
1480        &chart_img,
1481        "performance_chart.png",
1482        Length::inches(5.0),
1483        Length::inches(2.5),
1484    )
1485    .alignment(Alignment::Center);
1486    {
1487        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1488        p.add_run("Figure 1: Quarterly Carbon Emissions (tonnes CO2e)")
1489            .italic(true)
1490            .size(9.0)
1491            .color("666666");
1492    }
1493
1494    // ── Section 2: Energy Consumption ──
1495    doc.add_paragraph("").page_break_before(true);
1496    doc.add_paragraph("Energy Consumption").style("Heading1");
1497
1498    doc.add_paragraph("Data Center Operations")
1499        .style("Heading2");
1500    doc.add_paragraph(
1501        "Our three primary data centers consumed a combined 4.2 GWh in Q4 2025, \
1502         a 12% reduction from Q3 2025 driven by improved cooling efficiency and \
1503         server consolidation.",
1504    );
1505
1506    // Data table
1507    {
1508        let mut tbl = doc
1509            .add_table(5, 4)
1510            .borders(BorderStyle::Single, 4, "2D5016")
1511            .width_pct(100.0);
1512        let hdrs = ["Data Center", "Capacity (MW)", "Usage (GWh)", "PUE"];
1513        for (c, h) in hdrs.iter().enumerate() {
1514            tbl.cell(0, c).unwrap().set_text(h);
1515            tbl.cell(0, c).unwrap().shading("2D5016");
1516        }
1517        let rows = [
1518            ("San Francisco (Primary)", "3.2", "1.8", "1.12"),
1519            ("Dublin (EU)", "2.1", "1.2", "1.18"),
1520            ("Singapore (APAC)", "1.8", "1.2", "1.24"),
1521            ("TOTAL", "7.1", "4.2", "1.17 avg"),
1522        ];
1523        for (i, (dc, cap, use_, pue)) in rows.iter().enumerate() {
1524            tbl.cell(i + 1, 0).unwrap().set_text(dc);
1525            tbl.cell(i + 1, 1).unwrap().set_text(cap);
1526            tbl.cell(i + 1, 2).unwrap().set_text(use_);
1527            tbl.cell(i + 1, 3).unwrap().set_text(pue);
1528            if i == 3 {
1529                for c in 0..4 {
1530                    tbl.cell(i + 1, c).unwrap().shading("E2EFDA");
1531                }
1532            }
1533        }
1534    }
1535
1536    doc.add_paragraph("");
1537
1538    doc.add_paragraph("Renewable Energy Mix").style("Heading2");
1539    doc.add_paragraph("Breakdown of energy sources across all facilities:");
1540
1541    doc.add_bullet_list_item("Solar PV: 42% (1.76 GWh)", 0);
1542    doc.add_bullet_list_item("Wind Power Purchase Agreements: 38% (1.60 GWh)", 0);
1543    doc.add_bullet_list_item("Hydroelectric: 12% (0.50 GWh)", 0);
1544    doc.add_bullet_list_item("Grid (non-renewable): 8% (0.34 GWh)", 0);
1545
1546    // ── Section 3: Water & Waste ──
1547    doc.add_paragraph("Water & Waste Management")
1548        .style("Heading1");
1549
1550    doc.add_paragraph("Water Usage").style("Heading2");
1551    doc.add_paragraph(
1552        "Total water consumption was 12.4 million gallons, a 15% reduction from Q3. \
1553         Our closed-loop cooling systems now recycle 78% of water used in cooling operations.",
1554    );
1555
1556    doc.add_paragraph("Waste Reduction").style("Heading2");
1557    doc.add_paragraph("E-waste management results for Q4:")
1558        .keep_with_next(true);
1559    {
1560        let mut tbl = doc
1561            .add_table(4, 3)
1562            .borders(BorderStyle::Single, 2, "6B8E23")
1563            .width_pct(80.0);
1564        tbl.cell(0, 0).unwrap().set_text("Category");
1565        tbl.cell(0, 0).unwrap().shading("6B8E23");
1566        tbl.cell(0, 1).unwrap().set_text("Weight (kg)");
1567        tbl.cell(0, 1).unwrap().shading("6B8E23");
1568        tbl.cell(0, 2).unwrap().set_text("Recycled %");
1569        tbl.cell(0, 2).unwrap().shading("6B8E23");
1570        let rows = [
1571            ("Server Hardware", "2,450", "94%"),
1572            ("Networking Equipment", "820", "91%"),
1573            ("Storage Media", "340", "99%"),
1574        ];
1575        for (i, (cat, wt, pct)) in rows.iter().enumerate() {
1576            tbl.cell(i + 1, 0).unwrap().set_text(cat);
1577            tbl.cell(i + 1, 1).unwrap().set_text(wt);
1578            tbl.cell(i + 1, 2).unwrap().set_text(pct);
1579        }
1580    }
1581
1582    // ── Section 4: Initiatives ──
1583    doc.add_paragraph("").page_break_before(true);
1584    doc.add_paragraph("Q1 2026 Initiatives").style("Heading1");
1585
1586    doc.add_paragraph("Planned Programs").style("Heading2");
1587    doc.add_numbered_list_item(
1588        "Deploy on-site battery storage at SF data center (2 MWh capacity)",
1589        0,
1590    );
1591    doc.add_numbered_list_item("Pilot immersion cooling in Dublin facility", 0);
1592    doc.add_numbered_list_item("Launch employee commute carbon offset program", 0);
1593    doc.add_numbered_list_item("Achieve ISO 14001 certification for Singapore facility", 0);
1594
1595    doc.add_paragraph("Investment Targets").style("Heading2");
1596    doc.add_paragraph("Sustainability CapEx allocation for FY2026:")
1597        .keep_with_next(true);
1598    {
1599        let mut tbl = doc
1600            .add_table(5, 2)
1601            .borders(BorderStyle::Single, 4, "2D5016");
1602        tbl.cell(0, 0).unwrap().set_text("Initiative");
1603        tbl.cell(0, 0).unwrap().shading("2D5016");
1604        tbl.cell(0, 1).unwrap().set_text("Budget");
1605        tbl.cell(0, 1).unwrap().shading("2D5016");
1606        let rows = [
1607            ("Battery Storage", "$1.2M"),
1608            ("Immersion Cooling Pilot", "$800K"),
1609            ("Solar Panel Expansion", "$2.1M"),
1610            ("Carbon Credits", "$500K"),
1611        ];
1612        for (i, (init, budget)) in rows.iter().enumerate() {
1613            tbl.cell(i + 1, 0).unwrap().set_text(init);
1614            tbl.cell(i + 1, 1).unwrap().set_text(budget);
1615            if i % 2 == 0 {
1616                tbl.cell(i + 1, 0).unwrap().shading("FFFAF0");
1617                tbl.cell(i + 1, 1).unwrap().shading("FFFAF0");
1618            }
1619        }
1620    }
1621
1622    // Image: sustainability roadmap
1623    let roadmap_img = create_sample_png(500, 100, [40, 80, 30]);
1624    doc.add_paragraph("");
1625    doc.add_picture(
1626        &roadmap_img,
1627        "roadmap.png",
1628        Length::inches(6.0),
1629        Length::inches(1.2),
1630    )
1631    .alignment(Alignment::Center);
1632    {
1633        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1634        p.add_run("Figure 2: 2026 Sustainability Roadmap")
1635            .italic(true)
1636            .size(9.0)
1637            .color("666666");
1638    }
1639
1640    doc.add_paragraph("");
1641    doc.add_paragraph(
1642        "For questions about this report, contact Walter White at sustainability@tensorbee.com.",
1643    )
1644    .shading("FFFAF0")
1645    .border_all(BorderStyle::Single, 2, "2D5016");
1646
1647    doc
1648}
1649
1650// =============================================================================
1651// 6. LETTER — Slate blue + silver scheme
1652// =============================================================================
1653fn generate_letter(_samples_dir: &Path) -> Document {
1654    let mut doc = Document::new();
1655
1656    // Colors: Slate #4A5568, Blue accent #3182CE
1657    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1658    doc.set_margins(
1659        Length::inches(1.25),
1660        Length::inches(1.25),
1661        Length::inches(1.25),
1662        Length::inches(1.25),
1663    );
1664    doc.set_title("Business Letter");
1665    doc.set_author("Walter White");
1666
1667    // Banner header using raw XML
1668    let logo_img = create_logo_png(220, 48);
1669    let banner = build_header_banner_xml(
1670        "rId1",
1671        &BannerOpts {
1672            bg_color: "4A5568",
1673            banner_width: 7772400,
1674            banner_height: 731520, // ~0.8"
1675            logo_width: 2011680,
1676            logo_height: 438912,
1677            logo_x_offset: 295125,
1678            logo_y_offset: 146304,
1679        },
1680    );
1681    doc.set_raw_header_with_images(
1682        banner,
1683        &[("rId1", &logo_img, "logo.png")],
1684        rdocx_oxml::header_footer::HdrFtrType::Default,
1685    );
1686    doc.set_margins(
1687        Length::twips(2000),
1688        Length::twips(1800),
1689        Length::twips(1440),
1690        Length::twips(1800),
1691    );
1692    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
1693
1694    // Footer with contact
1695    doc.set_footer(
1696        "Tensorbee | 123 Innovation Drive, Suite 400 | San Francisco, CA 94105 | tensorbee.com",
1697    );
1698
1699    // ── Sender Address ──
1700    doc.add_paragraph("Walter White");
1701    doc.add_paragraph("Chief Executive Officer");
1702    doc.add_paragraph("Tensorbee");
1703    doc.add_paragraph("123 Innovation Drive, Suite 400");
1704    doc.add_paragraph("San Francisco, CA 94105");
1705    doc.add_paragraph("");
1706    doc.add_paragraph("February 22, 2026");
1707
1708    doc.add_paragraph("");
1709
1710    // ── Recipient ──
1711    doc.add_paragraph("Ms. Sarah Chen");
1712    doc.add_paragraph("VP of Engineering");
1713    doc.add_paragraph("Meridian Dynamics LLC");
1714    doc.add_paragraph("456 Enterprise Boulevard");
1715    doc.add_paragraph("Austin, TX 78701");
1716
1717    doc.add_paragraph("");
1718
1719    // ── Subject ──
1720    {
1721        let mut p = doc.add_paragraph("");
1722        p.add_run("Re: Strategic Technology Partnership — Phase 2 Expansion")
1723            .bold(true);
1724    }
1725
1726    doc.add_paragraph("");
1727
1728    // ── Body ──
1729    doc.add_paragraph("Dear Ms. Chen,");
1730    doc.add_paragraph("");
1731
1732    doc.add_paragraph(
1733        "I am writing to express Tensorbee's enthusiasm for expanding our strategic technology \
1734         partnership with Meridian Dynamics. Following the successful completion of Phase 1, \
1735         which delivered a 40% improvement in your ML inference pipeline throughput, we believe \
1736         the foundation is firmly established for an ambitious Phase 2 engagement.",
1737    )
1738    .first_line_indent(Length::inches(0.5));
1739
1740    doc.add_paragraph(
1741        "During our recent executive review, your team highlighted three priority areas for \
1742         the next phase of collaboration:",
1743    )
1744    .first_line_indent(Length::inches(0.5));
1745
1746    doc.add_numbered_list_item(
1747        "Real-time anomaly detection for your financial transaction monitoring system, \
1748         targeting sub-100ms latency at 50,000 transactions per second.",
1749        0,
1750    );
1751    doc.add_numbered_list_item(
1752        "Federated learning infrastructure to enable model training across your distributed \
1753         data centers without centralizing sensitive financial data.",
1754        0,
1755    );
1756    doc.add_numbered_list_item(
1757        "MLOps automation to reduce your model deployment cycle from the current 5 days \
1758         to under 4 hours.",
1759        0,
1760    );
1761
1762    doc.add_paragraph(
1763        "Our engineering team has prepared a detailed technical proposal addressing each of \
1764         these areas. We have allocated a dedicated team of eight senior engineers, led by \
1765         our CTO, to ensure continuity with the Phase 1 team your organization has already \
1766         built a productive working relationship with.",
1767    )
1768    .first_line_indent(Length::inches(0.5));
1769
1770    doc.add_paragraph(
1771        "I would welcome the opportunity to present our Phase 2 proposal in person. My \
1772         assistant will reach out to coordinate a meeting at your convenience during the \
1773         first week of March.",
1774    )
1775    .first_line_indent(Length::inches(0.5));
1776
1777    doc.add_paragraph("");
1778
1779    doc.add_paragraph("Warm regards,");
1780    doc.add_paragraph("");
1781    doc.add_paragraph("");
1782
1783    // Signature line
1784    doc.add_paragraph("")
1785        .border_bottom(BorderStyle::Single, 4, "4A5568");
1786    {
1787        let mut p = doc.add_paragraph("");
1788        p.add_run("Walter White").bold(true).size(12.0);
1789    }
1790    {
1791        let mut p = doc.add_paragraph("");
1792        p.add_run("Chief Executive Officer, Tensorbee")
1793            .italic(true)
1794            .size(10.0)
1795            .color("666666");
1796    }
1797    {
1798        let mut p = doc.add_paragraph("");
1799        p.add_run("walter@tensorbee.com | +1 (415) 555-0199")
1800            .size(10.0)
1801            .color("888888");
1802    }
1803
1804    doc
1805}
1806
1807// =============================================================================
1808// 7. EMPLOYMENT CONTRACT — Purple + charcoal scheme
1809// =============================================================================
1810fn generate_contract(_samples_dir: &Path) -> Document {
1811    let mut doc = Document::new();
1812
1813    // Colors: Purple #5B2C6F, Plum #8E44AD, Gray #2C3E50
1814    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1815    doc.set_margins(
1816        Length::inches(1.25),
1817        Length::inches(1.0),
1818        Length::inches(1.0),
1819        Length::inches(1.0),
1820    );
1821    doc.set_title("Employment Agreement");
1822    doc.set_author("Tensorbee HR Department");
1823    doc.set_subject("Employment Contract — Walter White");
1824    doc.set_keywords("employment, contract, agreement, Tensorbee");
1825
1826    doc.set_header("TENSORBEE — Employment Agreement");
1827    doc.set_footer("Employment Agreement — Walter White — Page");
1828
1829    // ── Title Block ──
1830    doc.add_paragraph("")
1831        .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1832    {
1833        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1834        p.add_run("EMPLOYMENT AGREEMENT")
1835            .bold(true)
1836            .size(24.0)
1837            .color("5B2C6F")
1838            .font("Georgia");
1839    }
1840    doc.add_paragraph("")
1841        .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1842    doc.add_paragraph("");
1843
1844    // ── Parties ──
1845    doc.add_paragraph(
1846        "This Employment Agreement (\"Agreement\") is entered into as of February 22, 2026 \
1847         (\"Effective Date\"), by and between:",
1848    );
1849
1850    doc.add_paragraph("");
1851
1852    {
1853        let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1854        p.add_run("EMPLOYER: ").bold(true);
1855        p.add_run(
1856            "Tensorbee, Inc., a Delaware corporation with its principal place of business at \
1857                   123 Innovation Drive, Suite 400, San Francisco, CA 94105 (\"Company\")",
1858        );
1859    }
1860    doc.add_paragraph("");
1861    {
1862        let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1863        p.add_run("EMPLOYEE: ").bold(true);
1864        p.add_run(
1865            "Walter White, residing at 308 Negra Arroyo Lane, Albuquerque, NM 87104 (\"Employee\")",
1866        );
1867    }
1868
1869    doc.add_paragraph("");
1870    doc.add_paragraph("The Company and Employee are collectively referred to as the \"Parties.\"");
1871    doc.add_paragraph("");
1872
1873    // ── Article 1: Position and Duties ──
1874    doc.add_paragraph("Article 1 — Position and Duties")
1875        .style("Heading1");
1876    {
1877        let mut p = doc.add_paragraph("");
1878        p.add_run("1.1 ").bold(true);
1879        p.add_run("The Company hereby employs the Employee as ");
1880        p.add_run("Chief Executive Officer (CEO)")
1881            .bold(true)
1882            .italic(true);
1883        p.add_run(", reporting directly to the Board of Directors.");
1884    }
1885    {
1886        let mut p = doc.add_paragraph("");
1887        p.add_run("1.2 ").bold(true);
1888        p.add_run("The Employee shall devote full working time, attention, and best efforts to \
1889                   the performance of duties as reasonably assigned by the Board, including but not limited to:");
1890    }
1891    doc.add_bullet_list_item(
1892        "Setting and executing the Company's strategic vision and business plan",
1893        0,
1894    );
1895    doc.add_bullet_list_item(
1896        "Overseeing all operations, engineering, and commercial activities",
1897        0,
1898    );
1899    doc.add_bullet_list_item(
1900        "Representing the Company to investors, customers, and the public",
1901        0,
1902    );
1903    doc.add_bullet_list_item("Recruiting, developing, and retaining key talent", 0);
1904
1905    {
1906        let mut p = doc.add_paragraph("");
1907        p.add_run("1.3 ").bold(true);
1908        p.add_run(
1909            "The Employee's primary work location shall be the Company's San Francisco \
1910                   headquarters, with reasonable travel as required by business needs.",
1911        );
1912    }
1913
1914    // ── Article 2: Compensation ──
1915    doc.add_paragraph("Article 2 — Compensation and Benefits")
1916        .style("Heading1");
1917    {
1918        let mut p = doc.add_paragraph("");
1919        p.add_run("2.1 Base Salary. ").bold(true);
1920        p.add_run("The Company shall pay the Employee an annual base salary of ");
1921        p.add_run("$375,000.00").bold(true);
1922        p.add_run(" (Three Hundred Seventy-Five Thousand Dollars), payable in accordance with the \
1923                   Company's standard payroll schedule, less applicable withholdings and deductions.");
1924    }
1925    {
1926        let mut p = doc.add_paragraph("");
1927        p.add_run("2.2 Annual Bonus. ").bold(true);
1928        p.add_run("The Employee shall be eligible for an annual performance bonus of up to ");
1929        p.add_run("40%").bold(true);
1930        p.add_run(
1931            " of base salary, based on achievement of mutually agreed performance objectives.",
1932        );
1933    }
1934    {
1935        let mut p = doc.add_paragraph("");
1936        p.add_run("2.3 Equity. ").bold(true);
1937        p.add_run("Subject to Board approval, the Employee shall receive a stock option grant of ");
1938        p.add_run("500,000 shares").bold(true);
1939        p.add_run(
1940            " of the Company's common stock, vesting over four (4) years with a one-year cliff, \
1941                   at an exercise price equal to the fair market value on the date of grant.",
1942        );
1943    }
1944
1945    // Compensation summary table
1946    doc.add_paragraph("");
1947    {
1948        let mut tbl = doc
1949            .add_table(5, 2)
1950            .borders(BorderStyle::Single, 4, "5B2C6F")
1951            .width_pct(70.0)
1952            .alignment(Alignment::Center);
1953        tbl.cell(0, 0).unwrap().set_text("Compensation Element");
1954        tbl.cell(0, 0).unwrap().shading("5B2C6F");
1955        tbl.cell(0, 1).unwrap().set_text("Value");
1956        tbl.cell(0, 1).unwrap().shading("5B2C6F");
1957        let items = [
1958            ("Base Salary", "$375,000/year"),
1959            ("Target Bonus", "Up to 40% ($150,000)"),
1960            ("Equity Grant", "500,000 shares (4yr vest)"),
1961            ("Total Target Comp", "$525,000 + equity"),
1962        ];
1963        for (i, (elem, val)) in items.iter().enumerate() {
1964            tbl.cell(i + 1, 0).unwrap().set_text(elem);
1965            tbl.cell(i + 1, 1).unwrap().set_text(val);
1966            if i == 3 {
1967                tbl.cell(i + 1, 0).unwrap().shading("E8DAEF");
1968                tbl.cell(i + 1, 1).unwrap().shading("E8DAEF");
1969            }
1970        }
1971    }
1972
1973    // ── Article 3: Benefits ──
1974    doc.add_paragraph("").page_break_before(true);
1975    doc.add_paragraph("Article 3 — Benefits").style("Heading1");
1976    {
1977        let mut p = doc.add_paragraph("");
1978        p.add_run("3.1 ").bold(true);
1979        p.add_run(
1980            "The Employee shall be entitled to participate in all benefit programs \
1981                   generally available to senior executives, including:",
1982        );
1983    }
1984    doc.add_bullet_list_item(
1985        "Medical, dental, and vision insurance (100% premium coverage for employee and dependents)",
1986        0,
1987    );
1988    doc.add_bullet_list_item("401(k) retirement plan with 6% company match", 0);
1989    doc.add_bullet_list_item("Life insurance and long-term disability coverage", 0);
1990    doc.add_bullet_list_item("Annual professional development allowance of $10,000", 0);
1991
1992    {
1993        let mut p = doc.add_paragraph("");
1994        p.add_run("3.2 Paid Time Off. ").bold(true);
1995        p.add_run(
1996            "The Employee shall receive 25 days of paid vacation per year, plus Company holidays, \
1997                   accruing on a monthly basis.",
1998        );
1999    }
2000
2001    // ── Article 4: Term and Termination ──
2002    doc.add_paragraph("Article 4 — Term and Termination")
2003        .style("Heading1");
2004    {
2005        let mut p = doc.add_paragraph("");
2006        p.add_run("4.1 At-Will Employment. ").bold(true);
2007        p.add_run(
2008            "This Agreement is for at-will employment and may be terminated by either Party \
2009                   at any time, with or without cause, subject to the notice provisions herein.",
2010        );
2011    }
2012    {
2013        let mut p = doc.add_paragraph("");
2014        p.add_run("4.2 Notice Period. ").bold(true);
2015        p.add_run("Either Party shall provide ");
2016        p.add_run("ninety (90) days").bold(true);
2017        p.add_run(" written notice of termination, or pay in lieu thereof.");
2018    }
2019    {
2020        let mut p = doc.add_paragraph("");
2021        p.add_run("4.3 Severance. ").bold(true);
2022        p.add_run("In the event of termination by the Company without Cause, the Employee shall \
2023                   receive (a) twelve (12) months of base salary continuation, (b) pro-rata bonus \
2024                   for the year of termination, and (c) twelve (12) months of COBRA premium coverage.");
2025    }
2026
2027    // ── Article 5: Confidentiality ──
2028    doc.add_paragraph("Article 5 — Confidentiality and IP")
2029        .style("Heading1");
2030    {
2031        let mut p = doc.add_paragraph("");
2032        p.add_run("5.1 ").bold(true);
2033        p.add_run(
2034            "The Employee agrees to maintain strict confidentiality of all proprietary \
2035                   information, trade secrets, and business plans of the Company during and after \
2036                   employment.",
2037        );
2038    }
2039    {
2040        let mut p = doc.add_paragraph("");
2041        p.add_run("5.2 ").bold(true);
2042        p.add_run(
2043            "All intellectual property, inventions, and works of authorship created by the \
2044                   Employee during the term of employment and within the scope of duties shall be \
2045                   the sole and exclusive property of the Company.",
2046        );
2047    }
2048
2049    // ── Article 6: Non-Compete ──
2050    doc.add_paragraph("Article 6 — Non-Competition")
2051        .style("Heading1");
2052    {
2053        let mut p = doc.add_paragraph("");
2054        p.add_run("6.1 ").bold(true);
2055        p.add_run("For a period of twelve (12) months following termination, the Employee agrees \
2056                   not to engage in any business that directly competes with the Company's core \
2057                   business of AI infrastructure and ML operations services, within the United States.");
2058    }
2059
2060    // ── Article 7: General Provisions ──
2061    doc.add_paragraph("Article 7 — General Provisions")
2062        .style("Heading1");
2063    {
2064        let mut p = doc.add_paragraph("");
2065        p.add_run("7.1 Governing Law. ").bold(true);
2066        p.add_run(
2067            "This Agreement shall be governed by and construed in accordance with the laws of \
2068                   the State of California.",
2069        );
2070    }
2071    {
2072        let mut p = doc.add_paragraph("");
2073        p.add_run("7.2 Entire Agreement. ").bold(true);
2074        p.add_run(
2075            "This Agreement constitutes the entire agreement between the Parties and supersedes \
2076                   all prior negotiations, representations, and agreements.",
2077        );
2078    }
2079    {
2080        let mut p = doc.add_paragraph("");
2081        p.add_run("7.3 Amendment. ").bold(true);
2082        p.add_run(
2083            "This Agreement may only be amended by a written instrument signed by both Parties.",
2084        );
2085    }
2086
2087    // ── Signature Block ──
2088    doc.add_paragraph("").page_break_before(true);
2089    doc.add_paragraph("IN WITNESS WHEREOF, the Parties have executed this Employment Agreement as of the Effective Date.")
2090        .space_after(Length::pt(24.0));
2091
2092    // Signature table
2093    {
2094        let mut tbl = doc.add_table(6, 2).width_pct(100.0);
2095        tbl.cell(0, 0).unwrap().set_text("FOR THE COMPANY:");
2096        tbl.cell(0, 0).unwrap().shading("5B2C6F");
2097        tbl.cell(0, 1).unwrap().set_text("EMPLOYEE:");
2098        tbl.cell(0, 1).unwrap().shading("5B2C6F");
2099
2100        tbl.cell(1, 0).unwrap().set_text("");
2101        tbl.cell(1, 1).unwrap().set_text("");
2102        tbl.row(1).unwrap().height(Length::pt(40.0));
2103
2104        tbl.cell(2, 0)
2105            .unwrap()
2106            .set_text("Signature: ___________________________");
2107        tbl.cell(2, 1)
2108            .unwrap()
2109            .set_text("Signature: ___________________________");
2110        tbl.cell(3, 0)
2111            .unwrap()
2112            .set_text("Name: Board Representative");
2113        tbl.cell(3, 1).unwrap().set_text("Name: Walter White");
2114        tbl.cell(4, 0)
2115            .unwrap()
2116            .set_text("Title: Chair, Board of Directors");
2117        tbl.cell(4, 1)
2118            .unwrap()
2119            .set_text("Title: Chief Executive Officer");
2120        tbl.cell(5, 0).unwrap().set_text("Date: _______________");
2121        tbl.cell(5, 1).unwrap().set_text("Date: _______________");
2122    }
2123
2124    doc
2125}
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_samples.rs (line 51)
34fn generate_feature_showcase(path: &Path) {
35    let mut doc = Document::new();
36
37    // =========================================================================
38    // PAGE SETUP & METADATA
39    // =========================================================================
40    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
41    doc.set_margins(
42        Length::inches(1.0), // top
43        Length::inches(1.0), // right
44        Length::inches(1.0), // bottom
45        Length::inches(1.0), // left
46    );
47    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
48    doc.set_gutter(Length::twips(0));
49
50    doc.set_title("rdocx Feature Showcase");
51    doc.set_author("rdocx Sample Generator");
52    doc.set_subject("Comprehensive feature demonstration");
53    doc.set_keywords("rdocx, docx, rust, sample");
54
55    // Header & Footer
56    doc.set_header("rdocx Feature Showcase");
57    doc.set_footer("Generated by rdocx — Page");
58
59    // Different first page header
60    doc.set_different_first_page(true);
61    doc.set_first_page_header("rdocx");
62    doc.set_first_page_footer("Feature Showcase — Cover Page");
63
64    // =========================================================================
65    // PAGE 1: COVER PAGE — background image, run formatting
66    // =========================================================================
67    let bg_cover = create_sample_png(612, 792, [30, 60, 120]);
68    doc.add_background_image(&bg_cover, "cover_bg.png");
69
70    doc.add_paragraph(""); // spacer
71    doc.add_paragraph(""); // spacer
72    doc.add_paragraph(""); // spacer
73
74    {
75        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
76        p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
77    }
78    {
79        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
80        p.add_run("Feature Showcase")
81            .size(28.0)
82            .color("FFFFFF")
83            .italic(true);
84    }
85
86    doc.add_paragraph(""); // spacer
87
88    {
89        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
90        p.add_run("A comprehensive demonstration of every feature")
91            .size(14.0)
92            .color("CCDDFF");
93    }
94    {
95        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
96        p.add_run("provided by the rdocx Rust crate for DOCX generation.")
97            .size(14.0)
98            .color("CCDDFF");
99    }
100
101    // =========================================================================
102    // PAGE 2: TEXT FORMATTING
103    // =========================================================================
104    doc.add_paragraph("").page_break_before(true);
105
106    doc.add_paragraph("1. Text Formatting").style("Heading1");
107
108    doc.add_paragraph("This section demonstrates paragraph and run-level formatting options.");
109    doc.add_paragraph("");
110
111    // --- Paragraph alignment ---
112    doc.add_paragraph("Paragraph Alignment").style("Heading2");
113
114    doc.add_paragraph("This paragraph is left-aligned (the default).")
115        .alignment(Alignment::Left);
116    doc.add_paragraph("This paragraph is center-aligned.")
117        .alignment(Alignment::Center);
118    doc.add_paragraph("This paragraph is right-aligned.")
119        .alignment(Alignment::Right);
120    doc.add_paragraph(
121        "This paragraph is justified. To demonstrate justified text properly, it needs \
122         to be long enough to span multiple lines so the word spacing adjustment is visible \
123         across the full width of the text area on the page.",
124    )
125    .alignment(Alignment::Justify);
126
127    doc.add_paragraph("");
128
129    // --- Run formatting ---
130    doc.add_paragraph("Run Formatting").style("Heading2");
131
132    {
133        let mut p = doc.add_paragraph("");
134        p.add_run("Bold text").bold(true);
135        p.add_run(" | ");
136        p.add_run("Italic text").italic(true);
137        p.add_run(" | ");
138        p.add_run("Bold + Italic").bold(true).italic(true);
139    }
140    {
141        let mut p = doc.add_paragraph("");
142        p.add_run("Single underline").underline(true);
143        p.add_run(" | ");
144        p.add_run("Strikethrough").strike(true);
145        p.add_run(" | ");
146        p.add_run("Double strikethrough").double_strike(true);
147    }
148    {
149        let mut p = doc.add_paragraph("");
150        p.add_run("Red text").color("FF0000");
151        p.add_run(" | ");
152        p.add_run("Blue text").color("0000FF");
153        p.add_run(" | ");
154        p.add_run("Green text").color("00AA00");
155        p.add_run(" | ");
156        p.add_run("Highlighted").highlight("FFFF00");
157    }
158    {
159        let mut p = doc.add_paragraph("");
160        p.add_run("8pt small").size(8.0);
161        p.add_run(" | ");
162        p.add_run("11pt normal").size(11.0);
163        p.add_run(" | ");
164        p.add_run("16pt large").size(16.0);
165        p.add_run(" | ");
166        p.add_run("24pt extra-large").size(24.0);
167    }
168    {
169        let mut p = doc.add_paragraph("");
170        p.add_run("Arial font").font("Arial");
171        p.add_run(" | ");
172        p.add_run("Times New Roman font").font("Times New Roman");
173        p.add_run(" | ");
174        p.add_run("Courier New font").font("Courier New");
175    }
176    {
177        let mut p = doc.add_paragraph("");
178        p.add_run("Normal");
179        p.add_run(" H").size(11.0);
180        p.add_run("2").subscript();
181        p.add_run("O (subscript)").size(11.0);
182        p.add_run(" | E = mc").size(11.0);
183        p.add_run("2").superscript();
184        p.add_run(" (superscript)").size(11.0);
185    }
186    {
187        let mut p = doc.add_paragraph("");
188        p.add_run("ALL CAPS").all_caps(true);
189        p.add_run(" | ");
190        p.add_run("Small Caps").small_caps(true);
191        p.add_run(" | ");
192        p.add_run("Expanded spacing")
193            .character_spacing(Length::twips(40));
194    }
195
196    doc.add_paragraph("");
197
198    // --- Paragraph formatting ---
199    doc.add_paragraph("Paragraph Formatting").style("Heading2");
200
201    doc.add_paragraph("Paragraph with shading (light green background)")
202        .shading("E2EFDA");
203
204    doc.add_paragraph("Paragraph with bottom border")
205        .border_bottom(BorderStyle::Single, 6, "2E75B6");
206
207    doc.add_paragraph("Paragraph with all borders")
208        .border_all(BorderStyle::Single, 4, "FF0000");
209
210    doc.add_paragraph("Paragraph with 1-inch left indent and hanging indent")
211        .indent_left(Length::inches(1.0))
212        .hanging_indent(Length::inches(0.5));
213
214    doc.add_paragraph("Paragraph with first-line indent of 0.5 inches")
215        .first_line_indent(Length::inches(0.5));
216
217    doc.add_paragraph("Paragraph with extra space before (24pt) and after (12pt)")
218        .space_before(Length::pt(24.0))
219        .space_after(Length::pt(12.0));
220
221    doc.add_paragraph(
222        "Paragraph with double line spacing. This text should have extra vertical \
223         space between lines to demonstrate the line_spacing_multiple setting.",
224    )
225    .line_spacing_multiple(2.0);
226
227    doc.add_paragraph("Paragraph with keep-with-next (won't break from the next paragraph)")
228        .keep_with_next(true);
229    doc.add_paragraph("(This stays with the paragraph above.)");
230
231    // =========================================================================
232    // PAGE 3: LISTS & TAB STOPS
233    // =========================================================================
234    doc.add_paragraph("").page_break_before(true);
235
236    doc.add_paragraph("2. Lists").style("Heading1");
237
238    doc.add_paragraph("Bullet List").style("Heading2");
239
240    doc.add_bullet_list_item("First bullet item", 0);
241    doc.add_bullet_list_item("Second bullet item", 0);
242    doc.add_bullet_list_item("Nested level 1", 1);
243    doc.add_bullet_list_item("Nested level 2", 2);
244    doc.add_bullet_list_item("Back to level 1", 1);
245    doc.add_bullet_list_item("Third bullet item", 0);
246
247    doc.add_paragraph("");
248
249    doc.add_paragraph("Numbered List").style("Heading2");
250
251    doc.add_numbered_list_item("First numbered item", 0);
252    doc.add_numbered_list_item("Second numbered item", 0);
253    doc.add_numbered_list_item("Sub-item A", 1);
254    doc.add_numbered_list_item("Sub-item B", 1);
255    doc.add_numbered_list_item("Third numbered item", 0);
256
257    doc.add_paragraph("");
258
259    // --- Tab stops ---
260    doc.add_paragraph("Tab Stops").style("Heading2");
261
262    doc.add_paragraph("Left\tCenter\tRight\tDecimal")
263        .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
264        .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
265        .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
266        .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
267
268    doc.add_paragraph("Item\t........\tPrice")
269        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
270        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
271        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
272
273    doc.add_paragraph("Widget A\t........\t$19.99")
274        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
275        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
276        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
277
278    doc.add_paragraph("Gadget B\t________\t$249.50")
279        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
280        .add_tab_stop_with_leader(
281            TabAlignment::Right,
282            Length::inches(4.0),
283            TabLeader::Underscore,
284        )
285        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
286
287    // =========================================================================
288    // PAGE 4: TABLES
289    // =========================================================================
290    doc.add_paragraph("").page_break_before(true);
291
292    doc.add_paragraph("3. Tables").style("Heading1");
293
294    // --- Basic table with borders ---
295    doc.add_paragraph("Basic Table with Borders")
296        .style("Heading2");
297
298    {
299        let mut tbl = doc.add_table(4, 3);
300        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
301
302        // Header row
303        for col in 0..3 {
304            tbl.cell(0, col).unwrap().shading("2E75B6");
305        }
306        tbl.cell(0, 0).unwrap().set_text("Name");
307        tbl.cell(0, 1).unwrap().set_text("Role");
308        tbl.cell(0, 2).unwrap().set_text("Location");
309
310        tbl.cell(1, 0).unwrap().set_text("Alice Johnson");
311        tbl.cell(1, 1).unwrap().set_text("Engineering Lead");
312        tbl.cell(1, 2).unwrap().set_text("New York");
313
314        tbl.cell(2, 0).unwrap().set_text("Bob Smith");
315        tbl.cell(2, 1).unwrap().set_text("Product Manager");
316        tbl.cell(2, 2).unwrap().set_text("San Francisco");
317
318        tbl.cell(3, 0).unwrap().set_text("Carol Davis");
319        tbl.cell(3, 1).unwrap().set_text("Designer");
320        tbl.cell(3, 2).unwrap().set_text("London");
321    }
322
323    doc.add_paragraph("");
324
325    // --- Table with cell merging ---
326    doc.add_paragraph("Table with Cell Merging & Vertical Alignment")
327        .style("Heading2");
328
329    {
330        let mut tbl = doc.add_table(4, 4);
331        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
332        tbl = tbl.width_pct(100.0);
333
334        // Header spanning all columns
335        tbl.cell(0, 0).unwrap().set_text("Quarterly Revenue Report");
336        tbl.cell(0, 0).unwrap().shading("1F4E79");
337        tbl.cell(0, 0).unwrap().grid_span(4);
338
339        // Sub-header
340        tbl.cell(1, 0).unwrap().set_text("Region");
341        tbl.cell(1, 0).unwrap().shading("D6E4F0");
342        tbl.cell(1, 1).unwrap().set_text("Q1");
343        tbl.cell(1, 1).unwrap().shading("D6E4F0");
344        tbl.cell(1, 2).unwrap().set_text("Q2");
345        tbl.cell(1, 2).unwrap().shading("D6E4F0");
346        tbl.cell(1, 3).unwrap().set_text("Total");
347        tbl.cell(1, 3).unwrap().shading("D6E4F0");
348
349        // Data
350        tbl.cell(2, 0).unwrap().set_text("North America");
351        tbl.cell(2, 1).unwrap().set_text("$2.4M");
352        tbl.cell(2, 2).unwrap().set_text("$2.7M");
353        tbl.cell(2, 3).unwrap().set_text("$5.1M");
354
355        tbl.cell(3, 0).unwrap().set_text("Europe");
356        tbl.cell(3, 1).unwrap().set_text("$1.8M");
357        tbl.cell(3, 2).unwrap().set_text("$2.0M");
358        tbl.cell(3, 3).unwrap().set_text("$3.8M");
359
360        // Vertical alignment on data cells
361        tbl.cell(2, 3)
362            .unwrap()
363            .vertical_alignment(VerticalAlignment::Center);
364        tbl.cell(3, 3)
365            .unwrap()
366            .vertical_alignment(VerticalAlignment::Bottom);
367    }
368
369    doc.add_paragraph("");
370
371    // --- Table with vertical merge ---
372    doc.add_paragraph("Table with Vertical Merge")
373        .style("Heading2");
374
375    {
376        let mut tbl = doc.add_table(4, 3);
377        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
378
379        tbl.cell(0, 0).unwrap().set_text("Category");
380        tbl.cell(0, 0).unwrap().shading("E2EFDA");
381        tbl.cell(0, 1).unwrap().set_text("Item");
382        tbl.cell(0, 1).unwrap().shading("E2EFDA");
383        tbl.cell(0, 2).unwrap().set_text("Price");
384        tbl.cell(0, 2).unwrap().shading("E2EFDA");
385
386        // "Hardware" spans rows 1-2
387        tbl.cell(1, 0).unwrap().set_text("Hardware");
388        tbl.cell(1, 0).unwrap().v_merge_restart();
389        tbl.cell(1, 1).unwrap().set_text("Laptop");
390        tbl.cell(1, 2).unwrap().set_text("$1,200");
391
392        tbl.cell(2, 0).unwrap().v_merge_continue();
393        tbl.cell(2, 1).unwrap().set_text("Monitor");
394        tbl.cell(2, 2).unwrap().set_text("$450");
395
396        // "Software" on row 3
397        tbl.cell(3, 0).unwrap().set_text("Software");
398        tbl.cell(3, 1).unwrap().set_text("IDE License");
399        tbl.cell(3, 2).unwrap().set_text("$200/yr");
400    }
401
402    doc.add_paragraph("");
403
404    // --- Nested table ---
405    doc.add_paragraph("Nested Table").style("Heading2");
406
407    {
408        let mut tbl = doc.add_table(2, 2);
409        tbl = tbl.borders(BorderStyle::Single, 6, "2E75B6");
410
411        tbl.cell(0, 0).unwrap().set_text("Outer Cell (0,0)");
412        tbl.cell(0, 1).unwrap().set_text("Outer Cell (0,1)");
413        tbl.cell(1, 0).unwrap().set_text("Outer Cell (1,0)");
414
415        // Nested table inside cell (1,1)
416        {
417            let mut cell = tbl.cell(1, 1).unwrap();
418            cell.set_text("Contains nested table:");
419            let mut nested = cell.add_table(2, 2);
420            nested = nested.borders(BorderStyle::Single, 2, "FF6600");
421            nested.cell(0, 0).unwrap().set_text("Inner A");
422            nested.cell(0, 1).unwrap().set_text("Inner B");
423            nested.cell(1, 0).unwrap().set_text("Inner C");
424            nested.cell(1, 1).unwrap().set_text("Inner D");
425        }
426    }
427
428    // =========================================================================
429    // PAGE 5: IMAGES
430    // =========================================================================
431    doc.add_paragraph("").page_break_before(true);
432
433    doc.add_paragraph("4. Images").style("Heading1");
434
435    doc.add_paragraph("Inline Image").style("Heading2");
436
437    doc.add_paragraph("Below is an inline image (200x50 pixels, blue gradient):");
438    let inline_img = create_sample_png(200, 50, [0, 80, 200]);
439    doc.add_picture(
440        &inline_img,
441        "inline_chart.png",
442        Length::inches(3.0),
443        Length::inches(0.75),
444    );
445
446    doc.add_paragraph("");
447
448    doc.add_paragraph("Header Image").style("Heading2");
449
450    // Replace the text-only header with an image header
451    let header_img = create_sample_png(400, 40, [40, 40, 40]);
452    doc.set_header_image(
453        &header_img,
454        "header_logo.png",
455        Length::inches(2.0),
456        Length::inches(0.2),
457    );
458
459    doc.add_paragraph(
460        "The document header has been replaced with an inline image. \
461         Check the header area at the top of this page.",
462    );
463
464    doc.add_paragraph("");
465    doc.add_paragraph(
466        "Note: The cover page uses a full-page background image behind the text, \
467         demonstrated on page 1 via add_background_image().",
468    );
469
470    // =========================================================================
471    // PAGE 6: CONTENT MANIPULATION — placeholder replacement, insertion
472    // =========================================================================
473    doc.add_paragraph("").page_break_before(true);
474
475    doc.add_paragraph("5. Content Manipulation")
476        .style("Heading1");
477
478    // --- Placeholder replacement ---
479    doc.add_paragraph("Placeholder Replacement")
480        .style("Heading2");
481
482    doc.add_paragraph(
483        "Before replacement, this document contained {{customer}} and {{date}} placeholders.",
484    );
485
486    {
487        let mut p = doc.add_paragraph("");
488        p.add_run("Customer: ").bold(true);
489        p.add_run("{{customer}}");
490    }
491    {
492        let mut p = doc.add_paragraph("");
493        p.add_run("Date: ").bold(true);
494        p.add_run("{{date}}");
495    }
496    doc.add_paragraph("Reference: {{ref_number}}");
497
498    // Table with placeholders
499    {
500        let mut tbl = doc.add_table(3, 2);
501        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
502        tbl.cell(0, 0).unwrap().set_text("Field");
503        tbl.cell(0, 0).unwrap().shading("D6E4F0");
504        tbl.cell(0, 1).unwrap().set_text("Value");
505        tbl.cell(0, 1).unwrap().shading("D6E4F0");
506        tbl.cell(1, 0).unwrap().set_text("Project");
507        tbl.cell(1, 1).unwrap().set_text("{{project}}");
508        tbl.cell(2, 0).unwrap().set_text("Status");
509        tbl.cell(2, 1).unwrap().set_text("{{status}}");
510    }
511
512    // Perform replacements
513    let mut replacements = HashMap::new();
514    replacements.insert("{{customer}}", "Acme Corporation");
515    replacements.insert("{{date}}", "February 22, 2026");
516    replacements.insert("{{ref_number}}", "REF-2026-001");
517    replacements.insert("{{project}}", "Infrastructure Upgrade");
518    replacements.insert("{{status}}", "In Progress");
519    let replace_count = doc.replace_all(&replacements);
520
521    doc.add_paragraph("");
522    doc.add_paragraph(&format!(
523        "(Replaced {} placeholders above — in body text and table cells)",
524        replace_count
525    ));
526
527    doc.add_paragraph("");
528
529    // --- Content insertion ---
530    doc.add_paragraph("Content Insertion").style("Heading2");
531
532    doc.add_paragraph("Section A: First section of content.");
533    doc.add_paragraph("Section C: Third section of content.");
534
535    // Insert "Section B" between A and C
536    if let Some(idx) = doc.find_content_index("Section C") {
537        doc.insert_paragraph(
538            idx,
539            "Section B: Inserted between A and C using find_content_index().",
540        );
541    }
542
543    doc.add_paragraph("");
544    doc.add_paragraph(
545        "The paragraph above ('Section B') was inserted at a specific position \
546         using find_content_index() + insert_paragraph().",
547    );
548
549    // =========================================================================
550    // PAGE 7: LANDSCAPE — section break, wide table
551    // =========================================================================
552    doc.add_paragraph("").section_break(SectionBreak::NextPage);
553
554    doc.add_paragraph("6. Mixed Page Orientation")
555        .style("Heading1");
556
557    doc.add_paragraph(
558        "This page is in LANDSCAPE orientation. It was created using a section break \
559         followed by section_landscape(). This is useful for wide tables or charts.",
560    );
561
562    doc.add_paragraph("");
563
564    // Wide table for landscape
565    {
566        let mut tbl = doc.add_table(4, 7);
567        tbl = tbl.borders(BorderStyle::Single, 4, "2E75B6");
568
569        let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
570        for (col, h) in headers.iter().enumerate() {
571            tbl.cell(0, col).unwrap().set_text(h);
572            tbl.cell(0, col).unwrap().shading("2E75B6");
573        }
574
575        let data = [
576            [
577                "North America",
578                "$1.2M",
579                "$1.3M",
580                "$1.4M",
581                "$1.5M",
582                "$1.6M",
583                "$7.0M",
584            ],
585            [
586                "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
587            ],
588            [
589                "Asia Pacific",
590                "$0.5M",
591                "$0.6M",
592                "$0.7M",
593                "$0.7M",
594                "$0.8M",
595                "$3.3M",
596            ],
597        ];
598        for (row_idx, row_data) in data.iter().enumerate() {
599            for (col, val) in row_data.iter().enumerate() {
600                tbl.cell(row_idx + 1, col).unwrap().set_text(val);
601            }
602        }
603    }
604
605    // End landscape, return to portrait
606    doc.add_paragraph("")
607        .section_break(SectionBreak::NextPage)
608        .section_landscape();
609
610    // =========================================================================
611    // PAGE 8: BACK TO PORTRAIT — styles, final notes
612    // =========================================================================
613    doc.add_paragraph("7. Custom Styles & Summary")
614        .style("Heading1");
615
616    doc.add_paragraph(
617        "This final page is back in portrait orientation after a section break. \
618         The document has demonstrated:",
619    );
620
621    doc.add_bullet_list_item(
622        "Page setup: size, margins, header/footer distance, gutter",
623        0,
624    );
625    doc.add_bullet_list_item("Document metadata: title, author, subject, keywords", 0);
626    doc.add_bullet_list_item("Headers and footers: text, images, different first page", 0);
627    doc.add_bullet_list_item("Background images: full-page behind text", 0);
628    doc.add_bullet_list_item(
629        "Text formatting: bold, italic, underline, strike, color, size, font",
630        0,
631    );
632    doc.add_bullet_list_item(
633        "Advanced run formatting: superscript, subscript, caps, spacing",
634        0,
635    );
636    doc.add_bullet_list_item(
637        "Paragraph formatting: alignment, borders, shading, spacing, indentation",
638        0,
639    );
640    doc.add_bullet_list_item("Bullet and numbered lists with nesting levels", 0);
641    doc.add_bullet_list_item("Tab stops with dot/underscore leaders", 0);
642    doc.add_bullet_list_item(
643        "Tables: borders, shading, column spans, row spans, nesting",
644        0,
645    );
646    doc.add_bullet_list_item("Vertical alignment in table cells", 0);
647    doc.add_bullet_list_item("Inline images", 0);
648    doc.add_bullet_list_item("Placeholder replacement in body and table cells", 0);
649    doc.add_bullet_list_item("Content insertion at specific positions", 0);
650    doc.add_bullet_list_item(
651        "Section breaks with mixed portrait/landscape orientation",
652        0,
653    );
654
655    doc.add_paragraph("");
656    doc.add_paragraph("All features above were built entirely from scratch using the rdocx API.")
657        .alignment(Alignment::Center)
658        .shading("E2EFDA")
659        .border_all(BorderStyle::Single, 2, "00AA00");
660
661    doc.save(path).unwrap();
662}
examples/generate_all_samples.rs (line 100)
86fn generate_feature_showcase(_samples_dir: &Path) -> Document {
87    let mut doc = Document::new();
88
89    // ── Page Setup & Metadata ──
90    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
91    doc.set_margins(
92        Length::inches(1.0),
93        Length::inches(1.0),
94        Length::inches(1.0),
95        Length::inches(1.0),
96    );
97    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
98    doc.set_gutter(Length::twips(0));
99    doc.set_title("rdocx Feature Showcase");
100    doc.set_author("rdocx Sample Generator");
101    doc.set_subject("Comprehensive feature demonstration");
102    doc.set_keywords("rdocx, docx, rust, sample, showcase");
103
104    // Headers & Footers with different first page
105    doc.set_different_first_page(true);
106    doc.set_first_page_header("rdocx");
107    doc.set_first_page_footer("Feature Showcase — Cover Page");
108    doc.set_header("rdocx Feature Showcase");
109    doc.set_footer("Generated by rdocx");
110
111    // ── COVER PAGE ──
112    let bg = create_sample_png(612, 792, [20, 45, 90]);
113    doc.add_background_image(&bg, "cover_bg.png");
114
115    for _ in 0..3 {
116        doc.add_paragraph("");
117    }
118    {
119        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
120        p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
121    }
122    {
123        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
124        p.add_run("Complete Feature Showcase")
125            .size(28.0)
126            .color("FFFFFF")
127            .italic(true);
128    }
129    doc.add_paragraph("");
130    {
131        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
132        p.add_run("Every feature of the rdocx Rust library")
133            .size(13.0)
134            .color("B0C4DE");
135    }
136    {
137        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
138        p.add_run("demonstrated in a single document.")
139            .size(13.0)
140            .color("B0C4DE");
141    }
142
143    // ── TABLE OF CONTENTS ──
144    doc.add_paragraph("").page_break_before(true);
145    doc.insert_toc(doc.content_count(), 3);
146
147    // ── SECTION 1: TEXT FORMATTING ──
148    doc.add_paragraph("").page_break_before(true);
149    doc.add_paragraph("1. Text Formatting").style("Heading1");
150
151    // Paragraph Alignment
152    doc.add_paragraph("1.1 Paragraph Alignment")
153        .style("Heading2");
154    doc.add_paragraph("Left-aligned paragraph (default).")
155        .alignment(Alignment::Left);
156    doc.add_paragraph("Center-aligned paragraph.")
157        .alignment(Alignment::Center);
158    doc.add_paragraph("Right-aligned paragraph.")
159        .alignment(Alignment::Right);
160    doc.add_paragraph(
161        "Justified paragraph — this text is long enough to demonstrate how justified alignment \
162         distributes extra space across word gaps so lines fill the full width of the text area.",
163    )
164    .alignment(Alignment::Justify);
165
166    doc.add_paragraph("");
167
168    // Run Formatting
169    doc.add_paragraph("1.2 Run Formatting").style("Heading2");
170    {
171        let mut p = doc.add_paragraph("");
172        p.add_run("Bold").bold(true);
173        p.add_run(" | ");
174        p.add_run("Italic").italic(true);
175        p.add_run(" | ");
176        p.add_run("Bold Italic").bold(true).italic(true);
177        p.add_run(" | ");
178        p.add_run("Underline").underline(true);
179        p.add_run(" | ");
180        p.add_run("Strikethrough").strike(true);
181        p.add_run(" | ");
182        p.add_run("Double Strike").double_strike(true);
183    }
184    {
185        let mut p = doc.add_paragraph("");
186        p.add_run("Red").color("FF0000");
187        p.add_run(" | ");
188        p.add_run("Blue").color("0000FF");
189        p.add_run(" | ");
190        p.add_run("Green").color("00AA00");
191        p.add_run(" | ");
192        p.add_run("Highlighted").highlight("FFFF00");
193    }
194    {
195        let mut p = doc.add_paragraph("");
196        p.add_run("8pt").size(8.0);
197        p.add_run(" | ");
198        p.add_run("11pt").size(11.0);
199        p.add_run(" | ");
200        p.add_run("16pt").size(16.0);
201        p.add_run(" | ");
202        p.add_run("24pt").size(24.0);
203    }
204    {
205        let mut p = doc.add_paragraph("");
206        p.add_run("Arial").font("Arial");
207        p.add_run(" | ");
208        p.add_run("Times New Roman").font("Times New Roman");
209        p.add_run(" | ");
210        p.add_run("Courier New").font("Courier New");
211    }
212    {
213        let mut p = doc.add_paragraph("");
214        p.add_run("H");
215        p.add_run("2").subscript();
216        p.add_run("O (subscript) | E=mc");
217        p.add_run("2").superscript();
218        p.add_run(" (superscript)");
219    }
220    {
221        let mut p = doc.add_paragraph("");
222        p.add_run("ALL CAPS").all_caps(true);
223        p.add_run(" | ");
224        p.add_run("Small Caps").small_caps(true);
225        p.add_run(" | ");
226        p.add_run("Expanded").character_spacing(Length::twips(40));
227        p.add_run(" | ");
228        p.add_run("Hidden text (not visible)").hidden(true);
229    }
230
231    doc.add_paragraph("");
232
233    // Underline Styles
234    doc.add_paragraph("1.3 Underline Styles").style("Heading2");
235    {
236        let mut p = doc.add_paragraph("");
237        p.add_run("Single ")
238            .underline_style(rdocx::UnderlineStyle::Single);
239        p.add_run("Double ")
240            .underline_style(rdocx::UnderlineStyle::Double);
241        p.add_run("Thick ")
242            .underline_style(rdocx::UnderlineStyle::Thick);
243        p.add_run("Dotted ")
244            .underline_style(rdocx::UnderlineStyle::Dotted);
245        p.add_run("Dash ")
246            .underline_style(rdocx::UnderlineStyle::Dash);
247        p.add_run("Wave ")
248            .underline_style(rdocx::UnderlineStyle::Wave);
249    }
250
251    // ── SECTION 2: PARAGRAPH FORMATTING ──
252    doc.add_paragraph("").page_break_before(true);
253    doc.add_paragraph("2. Paragraph Formatting")
254        .style("Heading1");
255
256    doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
257    doc.add_paragraph("Paragraph with light green shading")
258        .shading("E2EFDA");
259    doc.add_paragraph("Paragraph with bottom border")
260        .border_bottom(BorderStyle::Single, 6, "2E75B6");
261    doc.add_paragraph("Paragraph with all borders (red)")
262        .border_all(BorderStyle::Single, 4, "FF0000");
263
264    doc.add_paragraph("2.2 Indentation").style("Heading2");
265    doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
266        .indent_left(Length::inches(1.0))
267        .hanging_indent(Length::inches(0.5));
268    doc.add_paragraph("0.5-inch first-line indent on this paragraph")
269        .first_line_indent(Length::inches(0.5));
270    doc.add_paragraph("Both left and right indent (0.75 inches each)")
271        .indent_left(Length::inches(0.75))
272        .indent_right(Length::inches(0.75));
273
274    doc.add_paragraph("2.3 Spacing & Line Height")
275        .style("Heading2");
276    doc.add_paragraph("Extra space before (24pt) and after (12pt)")
277        .space_before(Length::pt(24.0))
278        .space_after(Length::pt(12.0));
279    doc.add_paragraph(
280        "Double line spacing paragraph. This text should have extra vertical space between \
281         lines to demonstrate line_spacing_multiple(2.0).",
282    )
283    .line_spacing_multiple(2.0);
284    doc.add_paragraph("Exact 20pt line spacing (fixed height).")
285        .line_spacing(20.0);
286
287    doc.add_paragraph("2.4 Pagination Controls")
288        .style("Heading2");
289    doc.add_paragraph("keep_with_next — this paragraph stays with the next")
290        .keep_with_next(true);
291    doc.add_paragraph("(This paragraph was kept with the one above.)");
292    doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
293        .keep_together(true);
294    doc.add_paragraph("widow_control — prevents widow/orphan lines")
295        .widow_control(true);
296
297    // ── SECTION 3: LISTS ──
298    doc.add_paragraph("").page_break_before(true);
299    doc.add_paragraph("3. Lists").style("Heading1");
300
301    doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
302    doc.add_bullet_list_item("First item", 0);
303    doc.add_bullet_list_item("Second item", 0);
304    doc.add_bullet_list_item("Nested level 1", 1);
305    doc.add_bullet_list_item("Nested level 2", 2);
306    doc.add_bullet_list_item("Back to level 1", 1);
307    doc.add_bullet_list_item("Third item", 0);
308
309    doc.add_paragraph("");
310
311    doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
312    doc.add_numbered_list_item("First numbered item", 0);
313    doc.add_numbered_list_item("Second numbered item", 0);
314    doc.add_numbered_list_item("Sub-item A", 1);
315    doc.add_numbered_list_item("Sub-item B", 1);
316    doc.add_numbered_list_item("Third numbered item", 0);
317
318    // ── SECTION 4: TAB STOPS ──
319    doc.add_paragraph("");
320    doc.add_paragraph("4. Tab Stops").style("Heading1");
321
322    doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
323    doc.add_paragraph("Left\tCenter\tRight\tDecimal")
324        .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
325        .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
326        .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
327        .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
328
329    doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
330    doc.add_paragraph("Item\t\tPrice")
331        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
332        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
333        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
334    doc.add_paragraph("Widget\t\t$19.99")
335        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
336        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
337        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
338    doc.add_paragraph("Gadget\t\t$249.50")
339        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
340        .add_tab_stop_with_leader(
341            TabAlignment::Right,
342            Length::inches(4.0),
343            TabLeader::Underscore,
344        )
345        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
346
347    // ── SECTION 5: TABLES ──
348    doc.add_paragraph("").page_break_before(true);
349    doc.add_paragraph("5. Tables").style("Heading1");
350
351    doc.add_paragraph("5.1 Basic Table").style("Heading2");
352    {
353        let mut tbl = doc
354            .add_table(4, 3)
355            .borders(BorderStyle::Single, 4, "000000");
356        for col in 0..3 {
357            tbl.cell(0, col).unwrap().shading("2E75B6");
358        }
359        tbl.cell(0, 0).unwrap().set_text("Name");
360        tbl.cell(0, 1).unwrap().set_text("Role");
361        tbl.cell(0, 2).unwrap().set_text("Location");
362        tbl.cell(1, 0).unwrap().set_text("Walter White");
363        tbl.cell(1, 1).unwrap().set_text("CEO");
364        tbl.cell(1, 2).unwrap().set_text("Albuquerque");
365        tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
366        tbl.cell(2, 1).unwrap().set_text("CTO");
367        tbl.cell(2, 2).unwrap().set_text("Remote");
368        tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
369        tbl.cell(3, 1).unwrap().set_text("Security");
370        tbl.cell(3, 2).unwrap().set_text("Washington");
371    }
372
373    doc.add_paragraph("");
374
375    doc.add_paragraph("5.2 Column Span & Cell Shading")
376        .style("Heading2");
377    {
378        let mut tbl = doc
379            .add_table(3, 4)
380            .borders(BorderStyle::Single, 4, "000000")
381            .width_pct(100.0);
382        tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
383        tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
384        tbl.cell(1, 0).unwrap().set_text("Region");
385        tbl.cell(1, 0).unwrap().shading("D6E4F0");
386        tbl.cell(1, 1).unwrap().set_text("Q1");
387        tbl.cell(1, 1).unwrap().shading("D6E4F0");
388        tbl.cell(1, 2).unwrap().set_text("Q2");
389        tbl.cell(1, 2).unwrap().shading("D6E4F0");
390        tbl.cell(1, 3).unwrap().set_text("Total");
391        tbl.cell(1, 3).unwrap().shading("D6E4F0");
392        tbl.cell(2, 0).unwrap().set_text("Americas");
393        tbl.cell(2, 1).unwrap().set_text("$2.4M");
394        tbl.cell(2, 2).unwrap().set_text("$2.7M");
395        tbl.cell(2, 3).unwrap().set_text("$5.1M");
396    }
397
398    doc.add_paragraph("");
399
400    doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
401    {
402        let mut tbl = doc
403            .add_table(4, 3)
404            .borders(BorderStyle::Single, 4, "000000");
405        tbl.cell(0, 0).unwrap().set_text("Category");
406        tbl.cell(0, 0).unwrap().shading("E2EFDA");
407        tbl.cell(0, 1).unwrap().set_text("Item");
408        tbl.cell(0, 1).unwrap().shading("E2EFDA");
409        tbl.cell(0, 2).unwrap().set_text("Price");
410        tbl.cell(0, 2).unwrap().shading("E2EFDA");
411        tbl.cell(1, 0).unwrap().set_text("Hardware");
412        tbl.cell(1, 0).unwrap().v_merge_restart();
413        tbl.cell(1, 1).unwrap().set_text("Laptop");
414        tbl.cell(1, 2).unwrap().set_text("$1,200");
415        tbl.cell(2, 0).unwrap().v_merge_continue();
416        tbl.cell(2, 1).unwrap().set_text("Monitor");
417        tbl.cell(2, 2).unwrap().set_text("$450");
418        tbl.cell(3, 0).unwrap().set_text("Software");
419        tbl.cell(3, 1).unwrap().set_text("IDE License");
420        tbl.cell(3, 2).unwrap().set_text("$200/yr");
421    }
422
423    doc.add_paragraph("");
424
425    doc.add_paragraph("5.4 Nested Table").style("Heading2");
426    {
427        let mut tbl = doc
428            .add_table(2, 2)
429            .borders(BorderStyle::Single, 6, "2E75B6");
430        tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
431        tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
432        tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
433        {
434            let mut cell = tbl.cell(1, 1).unwrap();
435            cell.set_text("Nested table below:");
436            let mut nested = cell
437                .add_table(2, 2)
438                .borders(BorderStyle::Single, 2, "FF6600");
439            nested.cell(0, 0).unwrap().set_text("A");
440            nested.cell(0, 1).unwrap().set_text("B");
441            nested.cell(1, 0).unwrap().set_text("C");
442            nested.cell(1, 1).unwrap().set_text("D");
443        }
444    }
445
446    doc.add_paragraph("");
447
448    doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
449        .style("Heading2");
450    {
451        let mut tbl = doc
452            .add_table(2, 3)
453            .borders(BorderStyle::Single, 4, "666666");
454        tbl.row(0).unwrap().height(Length::pt(50.0)).header();
455        tbl.cell(0, 0).unwrap().set_text("Top");
456        tbl.cell(0, 0)
457            .unwrap()
458            .vertical_alignment(VerticalAlignment::Top)
459            .shading("FFF2CC");
460        tbl.cell(0, 1).unwrap().set_text("Center");
461        tbl.cell(0, 1)
462            .unwrap()
463            .vertical_alignment(VerticalAlignment::Center)
464            .shading("D9E2F3");
465        tbl.cell(0, 2).unwrap().set_text("Bottom");
466        tbl.cell(0, 2)
467            .unwrap()
468            .vertical_alignment(VerticalAlignment::Bottom)
469            .shading("E2EFDA");
470        tbl.row(1).unwrap().cant_split();
471        tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
472        tbl.cell(1, 0).unwrap().no_wrap();
473        tbl.cell(1, 1).unwrap().set_text("Fixed width");
474        tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
475        tbl.cell(1, 2).unwrap().set_text("Normal");
476    }
477
478    // ── SECTION 6: IMAGES ──
479    doc.add_paragraph("").page_break_before(true);
480    doc.add_paragraph("6. Images").style("Heading1");
481
482    doc.add_paragraph("6.1 Inline Image").style("Heading2");
483    doc.add_paragraph("A blue gradient image below:");
484    let img = create_sample_png(200, 50, [0, 80, 200]);
485    doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
486
487    doc.add_paragraph("");
488    doc.add_paragraph("6.2 Header Image").style("Heading2");
489    let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
490    doc.set_header_image(
491        &hdr_img,
492        "header_logo.png",
493        Length::inches(2.0),
494        Length::inches(0.2),
495    );
496    doc.add_paragraph("The header now contains an inline image (check the top of this page).");
497
498    doc.add_paragraph("");
499    doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
500
501    // ── SECTION 7: CONTENT MANIPULATION ──
502    doc.add_paragraph("").page_break_before(true);
503    doc.add_paragraph("7. Content Manipulation")
504        .style("Heading1");
505
506    doc.add_paragraph("7.1 Placeholder Replacement")
507        .style("Heading2");
508    doc.add_paragraph("Customer: {{customer}}");
509    doc.add_paragraph("Date: {{date}}");
510    doc.add_paragraph("Reference: {{ref_number}}");
511    {
512        let mut tbl = doc
513            .add_table(2, 2)
514            .borders(BorderStyle::Single, 4, "000000");
515        tbl.cell(0, 0).unwrap().set_text("Project");
516        tbl.cell(0, 0).unwrap().shading("D6E4F0");
517        tbl.cell(0, 1).unwrap().set_text("{{project}}");
518        tbl.cell(1, 0).unwrap().set_text("Status");
519        tbl.cell(1, 0).unwrap().shading("D6E4F0");
520        tbl.cell(1, 1).unwrap().set_text("{{status}}");
521    }
522
523    let mut replacements = HashMap::new();
524    replacements.insert("{{customer}}", "Tensorbee Inc.");
525    replacements.insert("{{date}}", "February 22, 2026");
526    replacements.insert("{{ref_number}}", "TB-2026-001");
527    replacements.insert("{{project}}", "Infrastructure Upgrade");
528    replacements.insert("{{status}}", "In Progress");
529    let n = doc.replace_all(&replacements);
530    doc.add_paragraph(&format!("({n} placeholders replaced above)"));
531
532    doc.add_paragraph("");
533
534    doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
535    doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
536    let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
537    doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
538
539    doc.add_paragraph("");
540
541    doc.add_paragraph("7.3 Content Insertion").style("Heading2");
542    doc.add_paragraph("Section A: First content.");
543    doc.add_paragraph("Section C: Third content.");
544    if let Some(idx) = doc.find_content_index("Section C") {
545        doc.insert_paragraph(
546            idx,
547            "Section B: Inserted between A and C via find_content_index().",
548        );
549    }
550
551    // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
552    doc.add_paragraph("").section_break(SectionBreak::NextPage);
553    doc.add_paragraph("8. Section Breaks & Orientation")
554        .style("Heading1");
555    doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
556
557    {
558        let mut tbl = doc
559            .add_table(3, 7)
560            .borders(BorderStyle::Single, 4, "2E75B6");
561        let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
562        for (c, h) in headers.iter().enumerate() {
563            tbl.cell(0, c).unwrap().set_text(h);
564            tbl.cell(0, c).unwrap().shading("2E75B6");
565        }
566        let data = [
567            [
568                "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
569            ],
570            [
571                "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
572            ],
573        ];
574        for (r, row) in data.iter().enumerate() {
575            for (c, v) in row.iter().enumerate() {
576                tbl.cell(r + 1, c).unwrap().set_text(v);
577            }
578        }
579    }
580
581    doc.add_paragraph("")
582        .section_break(SectionBreak::NextPage)
583        .section_landscape();
584
585    // ── SECTION 9: CUSTOM STYLES ──
586    doc.add_paragraph("9. Custom Styles").style("Heading1");
587    doc.add_style(
588        StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
589            .based_on("Normal")
590            .paragraph_properties({
591                let mut ppr = rdocx_oxml::properties::CT_PPr::default();
592                ppr.shading = Some(rdocx_oxml::properties::CT_Shd {
593                    val: "clear".to_string(),
594                    color: None,
595                    fill: Some("FFF2CC".to_string()),
596                });
597                ppr
598            })
599            .run_properties({
600                let mut rpr = rdocx_oxml::properties::CT_RPr::default();
601                rpr.bold = Some(true);
602                rpr.color = Some("C45911".to_string());
603                rpr
604            }),
605    );
606    doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
607        .style("CustomHighlight");
608
609    doc.add_paragraph("");
610
611    // ── SECTION 10: DOCUMENT INTELLIGENCE ──
612    doc.add_paragraph("10. Document Intelligence API")
613        .style("Heading1");
614
615    let wc = doc.word_count();
616    let hc = doc.headings().len();
617    let ic = doc.images().len();
618    let lc = doc.links().len();
619    doc.add_paragraph(&format!("Word count: {wc}"));
620    doc.add_paragraph(&format!("Heading count: {hc}"));
621    doc.add_paragraph(&format!("Image count: {ic}"));
622    doc.add_paragraph(&format!("Link count: {lc}"));
623
624    let outline = doc.document_outline();
625    doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
626
627    let issues = doc.audit_accessibility();
628    doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
629    for issue in &issues {
630        doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
631    }
632
633    // ── SECTION 11: DOCUMENT MERGING ──
634    doc.add_paragraph("");
635    doc.add_paragraph("11. Document Merging").style("Heading1");
636    {
637        let mut other = Document::new();
638        other.add_paragraph("This paragraph was merged from another document using append().");
639        doc.append(&other);
640    }
641
642    doc.add_paragraph("");
643
644    // ── SUMMARY ──
645    doc.add_paragraph("Summary of Demonstrated Features")
646        .style("Heading1");
647    let features = [
648        "Page setup: size, margins, header/footer distance, gutter",
649        "Document metadata: title, author, subject, keywords",
650        "Headers and footers: text, images, different first page",
651        "Background images (full-page behind text)",
652        "Table of Contents generation with bookmarks",
653        "Text formatting: bold, italic, underline styles, strike, color, size, font",
654        "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
655        "Paragraph formatting: alignment, borders, shading, spacing, indentation",
656        "Pagination controls: keep-with-next, keep-together, widow control",
657        "Bullet and numbered lists with nesting",
658        "Tab stops with dot/underscore/hyphen leaders",
659        "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
660        "Row properties: header rows, exact height, cant-split",
661        "Cell properties: width, no-wrap, vertical alignment",
662        "Inline images and header images",
663        "Placeholder replacement (single and batch)",
664        "Regex-based find and replace",
665        "Content insertion at specific positions",
666        "Section breaks with mixed portrait/landscape",
667        "Custom paragraph and character styles",
668        "Document intelligence: word count, headings, outline, images, links",
669        "Accessibility audit",
670        "Document merging (append)",
671        "Export: DOCX, PDF, HTML, Markdown",
672    ];
673    for f in &features {
674        doc.add_bullet_list_item(f, 0);
675    }
676    doc.add_paragraph("");
677    doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
678        .alignment(Alignment::Center)
679        .shading("E2EFDA")
680        .border_all(BorderStyle::Single, 2, "00AA00");
681
682    doc
683}
684
685// =============================================================================
686// 2. PROPOSAL DOCUMENT — Deep navy + gold scheme
687// =============================================================================
688fn generate_proposal(_samples_dir: &Path) -> Document {
689    let mut doc = Document::new();
690
691    // Colors: Navy #1B2A4A, Gold #C5922E, Light #F4F1EB
692    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
693    doc.set_margins(
694        Length::inches(1.0),
695        Length::inches(1.0),
696        Length::inches(1.0),
697        Length::inches(1.0),
698    );
699    doc.set_title("AI Platform Modernization Proposal");
700    doc.set_author("Walter White");
701    doc.set_subject("Technology Proposal");
702    doc.set_keywords("proposal, AI, modernization, Tensorbee");
703
704    // Banner header
705    let logo_img = create_logo_png(220, 48);
706    let banner = build_header_banner_xml(
707        "rId1",
708        &BannerOpts {
709            bg_color: "1B2A4A",
710            banner_width: 7772400,
711            banner_height: 914400,
712            logo_width: 2011680,
713            logo_height: 438912,
714            logo_x_offset: 295125,
715            logo_y_offset: 237744,
716        },
717    );
718    doc.set_raw_header_with_images(
719        banner,
720        &[("rId1", &logo_img, "logo.png")],
721        rdocx_oxml::header_footer::HdrFtrType::Default,
722    );
723    doc.set_different_first_page(true);
724    let cover_banner = build_header_banner_xml(
725        "rId1",
726        &BannerOpts {
727            bg_color: "C5922E",
728            banner_width: 7772400,
729            banner_height: 914400,
730            logo_width: 2011680,
731            logo_height: 438912,
732            logo_x_offset: 295125,
733            logo_y_offset: 237744,
734        },
735    );
736    doc.set_raw_header_with_images(
737        cover_banner,
738        &[("rId1", &logo_img, "logo.png")],
739        rdocx_oxml::header_footer::HdrFtrType::First,
740    );
741    doc.set_margins(
742        Length::twips(2292),
743        Length::twips(1440),
744        Length::twips(1440),
745        Length::twips(1440),
746    );
747    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
748    doc.set_footer("Tensorbee — Confidential");
749
750    // Custom styles
751    doc.add_style(
752        StyleBuilder::paragraph("ProposalTitle", "Proposal Title")
753            .based_on("Normal")
754            .run_properties({
755                let mut rpr = rdocx_oxml::properties::CT_RPr::default();
756                rpr.bold = Some(true);
757                rpr.font_ascii = Some("Georgia".to_string());
758                rpr.font_hansi = Some("Georgia".to_string());
759                rpr.sz = Some(rdocx_oxml::HalfPoint(56)); // half-points → 28pt
760                rpr.color = Some("1B2A4A".to_string());
761                rpr
762            }),
763    );
764
765    // ── Cover Page ──
766    for _ in 0..4 {
767        doc.add_paragraph("");
768    }
769    doc.add_paragraph("AI Platform Modernization")
770        .style("ProposalTitle")
771        .alignment(Alignment::Center);
772    doc.add_paragraph("")
773        .alignment(Alignment::Center)
774        .border_bottom(BorderStyle::Single, 8, "C5922E");
775    {
776        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
777        p.add_run("Prepared for Global Financial Corp.")
778            .size(14.0)
779            .color("666666")
780            .italic(true);
781    }
782    {
783        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
784        p.add_run("Prepared by: Walter White, CEO — Tensorbee")
785            .size(12.0)
786            .color("888888");
787    }
788    {
789        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
790        p.add_run("February 22, 2026").size(12.0).color("888888");
791    }
792
793    // ── TOC ──
794    doc.add_paragraph("").page_break_before(true);
795    doc.add_paragraph("Table of Contents").style("Heading1");
796    // We'll add a manual-style TOC since the insert_toc goes at a specific position
797    doc.insert_toc(doc.content_count(), 2);
798
799    // ── Executive Summary ──
800    doc.add_paragraph("").page_break_before(true);
801    doc.add_paragraph("Executive Summary").style("Heading1");
802    doc.add_paragraph(
803        "Tensorbee proposes a comprehensive modernization of Global Financial Corp's AI platform, \
804         transitioning from legacy batch-processing systems to a real-time inference architecture. \
805         This transformation will reduce model deployment time from weeks to hours, improve \
806         prediction accuracy by an estimated 23%, and deliver $4.2M in annual operational savings.",
807    )
808    .first_line_indent(Length::inches(0.3));
809    doc.add_paragraph(
810        "The project spans three phases over 18 months, with a total investment of $2.8M. \
811         Tensorbee brings deep expertise in ML infrastructure, having successfully completed \
812         similar transformations for three Fortune 500 financial institutions.",
813    )
814    .first_line_indent(Length::inches(0.3));
815
816    // Key metrics table
817    doc.add_paragraph("");
818    {
819        let mut tbl = doc
820            .add_table(5, 2)
821            .borders(BorderStyle::Single, 4, "1B2A4A")
822            .width_pct(80.0);
823        tbl.cell(0, 0).unwrap().set_text("Key Metric");
824        tbl.cell(0, 0).unwrap().shading("1B2A4A");
825        tbl.cell(0, 1).unwrap().set_text("Value");
826        tbl.cell(0, 1).unwrap().shading("1B2A4A");
827        let rows = [
828            ("Total Investment", "$2.8M"),
829            ("Annual Savings", "$4.2M"),
830            ("ROI (Year 1)", "150%"),
831            ("Timeline", "18 months"),
832        ];
833        for (i, (k, v)) in rows.iter().enumerate() {
834            tbl.cell(i + 1, 0).unwrap().set_text(k);
835            if i % 2 == 0 {
836                tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
837            }
838            tbl.cell(i + 1, 1).unwrap().set_text(v);
839            if i % 2 == 0 {
840                tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
841            }
842        }
843    }
844
845    // ── Problem Statement ──
846    doc.add_paragraph("").page_break_before(true);
847    doc.add_paragraph("Problem Statement").style("Heading1");
848    doc.add_paragraph(
849        "Global Financial Corp's current AI infrastructure faces three critical challenges:",
850    );
851    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);
852    doc.add_numbered_list_item("Scalability Constraints — The batch processing architecture cannot handle real-time inference demands during peak trading hours.", 0);
853    doc.add_numbered_list_item("Technical Debt — Legacy Python 2.7 codebases and manual deployment scripts create reliability risks and slow iteration speed.", 0);
854
855    // ── Proposed Solution ──
856    doc.add_paragraph("").page_break_before(true);
857    doc.add_paragraph("Proposed Solution").style("Heading1");
858
859    doc.add_paragraph("Phase 1: Foundation (Months 1-6)")
860        .style("Heading2");
861    doc.add_bullet_list_item("Deploy Kubernetes-based ML serving infrastructure", 0);
862    doc.add_bullet_list_item("Implement CI/CD pipeline for model deployment", 0);
863    doc.add_bullet_list_item("Migrate top 5 production models to new platform", 0);
864
865    doc.add_paragraph("Phase 2: Expansion (Months 7-12)")
866        .style("Heading2");
867    doc.add_bullet_list_item(
868        "Real-time feature engineering pipeline (Apache Kafka + Flink)",
869        0,
870    );
871    doc.add_bullet_list_item("A/B testing framework for model variants", 0);
872    doc.add_bullet_list_item("Automated model monitoring and drift detection", 0);
873
874    doc.add_paragraph("Phase 3: Optimization (Months 13-18)")
875        .style("Heading2");
876    doc.add_bullet_list_item("GPU inference optimization (TensorRT/ONNX Runtime)", 0);
877    doc.add_bullet_list_item("Multi-region deployment for latency reduction", 0);
878    doc.add_bullet_list_item(
879        "Self-service model deployment portal for data science teams",
880        0,
881    );
882
883    // ── Budget ──
884    doc.add_paragraph("Budget Breakdown").style("Heading1");
885    {
886        let mut tbl = doc
887            .add_table(6, 3)
888            .borders(BorderStyle::Single, 4, "1B2A4A")
889            .width_pct(100.0);
890        tbl.cell(0, 0).unwrap().set_text("Category");
891        tbl.cell(0, 0).unwrap().shading("1B2A4A");
892        tbl.cell(0, 1).unwrap().set_text("Cost");
893        tbl.cell(0, 1).unwrap().shading("1B2A4A");
894        tbl.cell(0, 2).unwrap().set_text("Notes");
895        tbl.cell(0, 2).unwrap().shading("1B2A4A");
896        let items = [
897            (
898                "Infrastructure",
899                "$450,000",
900                "Cloud compute + GPU instances",
901            ),
902            ("Engineering Services", "$1,600,000", "12 FTE x 18 months"),
903            (
904                "Software Licenses",
905                "$350,000",
906                "Kafka, monitoring, CI/CD tools",
907            ),
908            (
909                "Training & Enablement",
910                "$200,000",
911                "Team training, documentation",
912            ),
913            ("Contingency (10%)", "$200,000", "Risk buffer"),
914        ];
915        for (i, (cat, cost, note)) in items.iter().enumerate() {
916            tbl.cell(i + 1, 0).unwrap().set_text(cat);
917            tbl.cell(i + 1, 1).unwrap().set_text(cost);
918            tbl.cell(i + 1, 2).unwrap().set_text(note);
919            if i % 2 == 0 {
920                tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
921                tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
922                tbl.cell(i + 1, 2).unwrap().shading("F4F1EB");
923            }
924        }
925    }
926
927    // ── Closing ──
928    doc.add_paragraph("");
929    doc.add_paragraph("Next Steps").style("Heading1");
930    doc.add_numbered_list_item(
931        "Schedule technical deep-dive with GFC engineering team (Week 1)",
932        0,
933    );
934    doc.add_numbered_list_item("Finalize scope and sign SOW (Week 2-3)", 0);
935    doc.add_numbered_list_item("Kick off Phase 1 with joint planning session (Week 4)", 0);
936
937    doc.add_paragraph("");
938    {
939        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
940        p.add_run("Walter White")
941            .bold(true)
942            .size(14.0)
943            .color("1B2A4A");
944    }
945    {
946        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
947        p.add_run("CEO, Tensorbee | walter@tensorbee.com")
948            .size(10.0)
949            .color("888888");
950    }
951
952    doc
953}
954
955// =============================================================================
956// 3. QUOTE / BILL OF MATERIALS — Teal + orange scheme
957// =============================================================================
958fn generate_quote(_samples_dir: &Path) -> Document {
959    let mut doc = Document::new();
960
961    // Colors: Teal #008B8B, Dark #1A3C3C, Orange #E07020, Light #F0F8F8
962    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
963    doc.set_margins(
964        Length::inches(0.75),
965        Length::inches(0.75),
966        Length::inches(0.75),
967        Length::inches(0.75),
968    );
969    doc.set_title("Quotation QT-2026-0147");
970    doc.set_author("Walter White");
971    doc.set_keywords("quote, BOM, Tensorbee");
972
973    doc.set_header("Tensorbee — Quotation");
974    doc.set_footer("QT-2026-0147 | Page");
975
976    // ── Header Block ──
977    {
978        let mut p = doc.add_paragraph("").alignment(Alignment::Left);
979        p.add_run("TENSORBEE")
980            .bold(true)
981            .size(28.0)
982            .color("008B8B")
983            .font("Helvetica");
984    }
985    doc.add_paragraph("123 Innovation Drive, Suite 400")
986        .alignment(Alignment::Left);
987    doc.add_paragraph("San Francisco, CA 94105 | +1 (415) 555-0199")
988        .alignment(Alignment::Left);
989    doc.add_paragraph("")
990        .border_bottom(BorderStyle::Single, 8, "008B8B");
991
992    // Quote meta
993    doc.add_paragraph("");
994    {
995        let mut tbl = doc.add_table(4, 4).width_pct(100.0);
996        tbl.cell(0, 0).unwrap().set_text("QUOTATION");
997        tbl.cell(0, 0).unwrap().shading("008B8B").grid_span(2);
998        tbl.cell(0, 2).unwrap().set_text("DATE");
999        tbl.cell(0, 2).unwrap().shading("008B8B");
1000        tbl.cell(0, 3).unwrap().set_text("VALID UNTIL");
1001        tbl.cell(0, 3).unwrap().shading("008B8B");
1002        tbl.cell(1, 0).unwrap().set_text("Quote #:");
1003        tbl.cell(1, 1).unwrap().set_text("QT-2026-0147");
1004        tbl.cell(1, 2).unwrap().set_text("Feb 22, 2026");
1005        tbl.cell(1, 3).unwrap().set_text("Mar 22, 2026");
1006        tbl.cell(2, 0).unwrap().set_text("Prepared by:");
1007        tbl.cell(2, 1).unwrap().set_text("Walter White");
1008        tbl.cell(2, 2).unwrap().set_text("Payment:");
1009        tbl.cell(2, 3).unwrap().set_text("Net 30");
1010        tbl.cell(3, 0).unwrap().set_text("Customer:");
1011        tbl.cell(3, 1).unwrap().set_text("Meridian Dynamics LLC");
1012        tbl.cell(3, 2).unwrap().set_text("Currency:");
1013        tbl.cell(3, 3).unwrap().set_text("USD");
1014    }
1015
1016    doc.add_paragraph("");
1017
1018    // ── Bill of Materials ──
1019    {
1020        let mut p = doc.add_paragraph("");
1021        p.add_run("Bill of Materials")
1022            .bold(true)
1023            .size(16.0)
1024            .color("1A3C3C");
1025    }
1026    doc.add_paragraph("");
1027
1028    {
1029        let mut tbl = doc
1030            .add_table(10, 6)
1031            .borders(BorderStyle::Single, 2, "008B8B")
1032            .width_pct(100.0)
1033            .layout_fixed();
1034        // Headers
1035        let hdrs = [
1036            "#",
1037            "Part Number",
1038            "Description",
1039            "Qty",
1040            "Unit Price",
1041            "Total",
1042        ];
1043        for (c, h) in hdrs.iter().enumerate() {
1044            tbl.cell(0, c).unwrap().set_text(h);
1045            tbl.cell(0, c).unwrap().shading("008B8B");
1046        }
1047
1048        let items: Vec<(&str, &str, &str, &str, &str)> = vec![
1049            (
1050                "TB-GPU-A100",
1051                "NVIDIA A100 80GB GPU",
1052                "4",
1053                "$12,500.00",
1054                "$50,000.00",
1055            ),
1056            (
1057                "TB-SRV-R750",
1058                "Dell R750xa Server Chassis",
1059                "2",
1060                "$8,200.00",
1061                "$16,400.00",
1062            ),
1063            (
1064                "TB-RAM-256G",
1065                "256GB DDR5 ECC Memory Module",
1066                "8",
1067                "$890.00",
1068                "$7,120.00",
1069            ),
1070            (
1071                "TB-SSD-3840",
1072                "3.84TB NVMe U.2 SSD",
1073                "8",
1074                "$1,150.00",
1075                "$9,200.00",
1076            ),
1077            (
1078                "TB-NET-CX7",
1079                "ConnectX-7 200GbE NIC",
1080                "4",
1081                "$1,800.00",
1082                "$7,200.00",
1083            ),
1084            (
1085                "TB-SW-48P",
1086                "48-Port 100GbE Switch",
1087                "1",
1088                "$22,000.00",
1089                "$22,000.00",
1090            ),
1091            (
1092                "TB-CAB-RACK",
1093                "42U Server Rack + PDU",
1094                "1",
1095                "$4,500.00",
1096                "$4,500.00",
1097            ),
1098            (
1099                "TB-SVC-INST",
1100                "Installation & Configuration",
1101                "1",
1102                "$8,500.00",
1103                "$8,500.00",
1104            ),
1105            (
1106                "TB-SVC-SUPP",
1107                "3-Year Premium Support",
1108                "1",
1109                "$15,000.00",
1110                "$15,000.00",
1111            ),
1112        ];
1113
1114        for (i, (pn, desc, qty, unit, total)) in items.iter().enumerate() {
1115            let row = i + 1;
1116            tbl.cell(row, 0).unwrap().set_text(&format!("{}", i + 1));
1117            tbl.cell(row, 1).unwrap().set_text(pn);
1118            tbl.cell(row, 2).unwrap().set_text(desc);
1119            tbl.cell(row, 3).unwrap().set_text(qty);
1120            tbl.cell(row, 4).unwrap().set_text(unit);
1121            tbl.cell(row, 5).unwrap().set_text(total);
1122            if i % 2 == 0 {
1123                for c in 0..6 {
1124                    tbl.cell(row, c).unwrap().shading("F0F8F8");
1125                }
1126            }
1127        }
1128    }
1129
1130    doc.add_paragraph("");
1131
1132    // ── Totals ──
1133    {
1134        let mut tbl = doc
1135            .add_table(4, 2)
1136            .borders(BorderStyle::Single, 2, "008B8B")
1137            .width(Length::inches(3.5))
1138            .alignment(Alignment::Right);
1139        tbl.cell(0, 0).unwrap().set_text("Subtotal");
1140        tbl.cell(0, 1).unwrap().set_text("$139,920.00");
1141        tbl.cell(1, 0).unwrap().set_text("Shipping & Handling");
1142        tbl.cell(1, 1).unwrap().set_text("$2,500.00");
1143        tbl.cell(2, 0).unwrap().set_text("Tax (8.625%)");
1144        tbl.cell(2, 1).unwrap().set_text("$12,068.10");
1145        tbl.cell(3, 0).unwrap().set_text("TOTAL");
1146        tbl.cell(3, 0).unwrap().shading("E07020");
1147        tbl.cell(3, 1).unwrap().set_text("$154,488.10");
1148        tbl.cell(3, 1).unwrap().shading("E07020");
1149    }
1150
1151    doc.add_paragraph("");
1152
1153    // ── Terms & Conditions ──
1154    {
1155        let mut p = doc.add_paragraph("");
1156        p.add_run("Terms & Conditions")
1157            .bold(true)
1158            .size(14.0)
1159            .color("1A3C3C");
1160    }
1161    doc.add_numbered_list_item(
1162        "This quotation is valid for 30 calendar days from the date of issue.",
1163        0,
1164    );
1165    doc.add_numbered_list_item(
1166        "All prices are in USD and exclusive of applicable taxes unless stated.",
1167        0,
1168    );
1169    doc.add_numbered_list_item("Standard lead time is 4-6 weeks from PO receipt.", 0);
1170    doc.add_numbered_list_item(
1171        "Payment terms: Net 30 from invoice date. 2% discount for payment within 10 days.",
1172        0,
1173    );
1174    doc.add_numbered_list_item(
1175        "Warranty: 3-year manufacturer warranty on all hardware components.",
1176        0,
1177    );
1178    doc.add_numbered_list_item(
1179        "Returns subject to 15% restocking fee if initiated after 14 days.",
1180        0,
1181    );
1182
1183    doc.add_paragraph("");
1184    doc.add_paragraph("")
1185        .border_bottom(BorderStyle::Single, 4, "008B8B");
1186    doc.add_paragraph("");
1187    {
1188        let mut p = doc.add_paragraph("");
1189        p.add_run("Accepted by: ").bold(true);
1190        p.add_run("___________________________ Date: ___________");
1191    }
1192    {
1193        let mut p = doc.add_paragraph("");
1194        p.add_run("Print Name: ").bold(true);
1195        p.add_run("___________________________ Title: ___________");
1196    }
1197
1198    doc
1199}
1200
1201// =============================================================================
1202// 4. INVOICE — Crimson + charcoal scheme
1203// =============================================================================
1204fn generate_invoice(_samples_dir: &Path) -> Document {
1205    let mut doc = Document::new();
1206
1207    // Colors: Crimson #B22222, Charcoal #333333, Light #FAF0F0
1208    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1209    doc.set_margins(
1210        Length::inches(0.75),
1211        Length::inches(0.75),
1212        Length::inches(0.75),
1213        Length::inches(0.75),
1214    );
1215    doc.set_title("Invoice INV-2026-0392");
1216    doc.set_author("Walter White");
1217    doc.set_keywords("invoice, Tensorbee");
1218
1219    doc.set_footer("Tensorbee — Thank you for your business!");
1220
1221    // ── Company & Invoice Header ──
1222    {
1223        let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1224        // Company name left, INVOICE right
1225        {
1226            let mut cell = tbl.cell(0, 0).unwrap();
1227            let mut p = cell.add_paragraph("");
1228            p.add_run("TENSORBEE")
1229                .bold(true)
1230                .size(32.0)
1231                .color("B22222")
1232                .font("Helvetica");
1233        }
1234        {
1235            let mut cell = tbl.cell(0, 1).unwrap();
1236            let mut p = cell.add_paragraph("");
1237            p.add_run("INVOICE").bold(true).size(32.0).color("333333");
1238        }
1239        tbl.cell(1, 0)
1240            .unwrap()
1241            .set_text("123 Innovation Drive, Suite 400");
1242        tbl.cell(1, 1).unwrap().set_text("Invoice #: INV-2026-0392");
1243        tbl.cell(2, 0).unwrap().set_text("San Francisco, CA 94105");
1244        tbl.cell(2, 1).unwrap().set_text("Date: February 22, 2026");
1245        tbl.cell(3, 0).unwrap().set_text("walter@tensorbee.com");
1246        tbl.cell(3, 1).unwrap().set_text("Due Date: March 24, 2026");
1247    }
1248
1249    doc.add_paragraph("")
1250        .border_bottom(BorderStyle::Thick, 8, "B22222");
1251    doc.add_paragraph("");
1252
1253    // ── Bill To / Ship To ──
1254    {
1255        let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1256        tbl.cell(0, 0).unwrap().set_text("BILL TO");
1257        tbl.cell(0, 0).unwrap().shading("B22222");
1258        tbl.cell(0, 1).unwrap().set_text("SHIP TO");
1259        tbl.cell(0, 1).unwrap().shading("B22222");
1260        tbl.cell(1, 0).unwrap().set_text("Meridian Dynamics LLC");
1261        tbl.cell(1, 1).unwrap().set_text("Meridian Dynamics LLC");
1262        tbl.cell(2, 0).unwrap().set_text("456 Enterprise Blvd");
1263        tbl.cell(2, 1).unwrap().set_text("Attn: Server Room B");
1264        tbl.cell(3, 0).unwrap().set_text("Austin, TX 78701");
1265        tbl.cell(3, 1)
1266            .unwrap()
1267            .set_text("456 Enterprise Blvd, Austin, TX 78701");
1268    }
1269
1270    doc.add_paragraph("");
1271
1272    // ── Line Items ──
1273    {
1274        let mut tbl = doc
1275            .add_table(8, 5)
1276            .borders(BorderStyle::Single, 2, "B22222")
1277            .width_pct(100.0);
1278        let hdrs = ["Description", "Qty", "Unit Price", "Tax", "Amount"];
1279        for (c, h) in hdrs.iter().enumerate() {
1280            tbl.cell(0, c).unwrap().set_text(h);
1281            tbl.cell(0, c).unwrap().shading("B22222");
1282        }
1283        let items = [
1284            (
1285                "ML Infrastructure Setup — Phase 1",
1286                "1",
1287                "$45,000.00",
1288                "$3,881.25",
1289                "$48,881.25",
1290            ),
1291            (
1292                "Data Pipeline Development (160 hrs)",
1293                "160",
1294                "$225.00",
1295                "$3,105.00",
1296                "$39,105.00",
1297            ),
1298            (
1299                "GPU Cluster Configuration",
1300                "1",
1301                "$12,000.00",
1302                "$1,035.00",
1303                "$13,035.00",
1304            ),
1305            (
1306                "API Gateway Implementation",
1307                "1",
1308                "$18,500.00",
1309                "$1,595.63",
1310                "$20,095.63",
1311            ),
1312            (
1313                "Load Testing & QA (80 hrs)",
1314                "80",
1315                "$195.00",
1316                "$1,345.50",
1317                "$16,945.50",
1318            ),
1319            (
1320                "Documentation & Training",
1321                "1",
1322                "$8,000.00",
1323                "$690.00",
1324                "$8,690.00",
1325            ),
1326            (
1327                "Project Management (3 months)",
1328                "3",
1329                "$6,500.00",
1330                "$1,679.63",
1331                "$21,179.63",
1332            ),
1333        ];
1334        for (i, (desc, qty, unit, tax, amt)) in items.iter().enumerate() {
1335            let r = i + 1;
1336            tbl.cell(r, 0).unwrap().set_text(desc);
1337            tbl.cell(r, 1).unwrap().set_text(qty);
1338            tbl.cell(r, 2).unwrap().set_text(unit);
1339            tbl.cell(r, 3).unwrap().set_text(tax);
1340            tbl.cell(r, 4).unwrap().set_text(amt);
1341            if i % 2 == 0 {
1342                for c in 0..5 {
1343                    tbl.cell(r, c).unwrap().shading("FAF0F0");
1344                }
1345            }
1346        }
1347    }
1348
1349    doc.add_paragraph("");
1350
1351    // ── Totals ──
1352    {
1353        let mut tbl = doc
1354            .add_table(4, 2)
1355            .borders(BorderStyle::Single, 2, "B22222")
1356            .width(Length::inches(3.0))
1357            .alignment(Alignment::Right);
1358        tbl.cell(0, 0).unwrap().set_text("Subtotal");
1359        tbl.cell(0, 1).unwrap().set_text("$154,600.00");
1360        tbl.cell(1, 0).unwrap().set_text("Tax (8.625%)");
1361        tbl.cell(1, 1).unwrap().set_text("$13,334.25");
1362        tbl.cell(2, 0).unwrap().set_text("Discount (5%)");
1363        tbl.cell(2, 1).unwrap().set_text("-$7,730.00");
1364        tbl.cell(3, 0).unwrap().set_text("AMOUNT DUE");
1365        tbl.cell(3, 0).unwrap().shading("B22222");
1366        tbl.cell(3, 1).unwrap().set_text("$160,204.25");
1367        tbl.cell(3, 1).unwrap().shading("B22222");
1368    }
1369
1370    doc.add_paragraph("");
1371    doc.add_paragraph("");
1372
1373    // ── Payment Details ──
1374    {
1375        let mut p = doc.add_paragraph("");
1376        p.add_run("Payment Details")
1377            .bold(true)
1378            .size(14.0)
1379            .color("333333");
1380    }
1381    doc.add_paragraph("Bank: Silicon Valley Bank")
1382        .indent_left(Length::inches(0.3));
1383    doc.add_paragraph("Account: Tensorbee Inc. — 0847-2953-1120")
1384        .indent_left(Length::inches(0.3));
1385    doc.add_paragraph("Routing: 121140399")
1386        .indent_left(Length::inches(0.3));
1387    doc.add_paragraph("Swift: SVBKUS6S")
1388        .indent_left(Length::inches(0.3));
1389
1390    doc.add_paragraph("");
1391
1392    doc.add_paragraph("Please include invoice number INV-2026-0392 in the payment reference.")
1393        .shading("FAF0F0")
1394        .border_all(BorderStyle::Single, 2, "B22222");
1395
1396    doc
1397}
1398
1399// =============================================================================
1400// 5. REPORT — Forest green + earth tones, with images & hierarchical sections
1401// =============================================================================
1402fn generate_report(_samples_dir: &Path) -> Document {
1403    let mut doc = Document::new();
1404
1405    // Colors: Forest #2D5016, Sage #6B8E23, Earth #8B7355, Cream #FFFAF0
1406    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1407    doc.set_margins(
1408        Length::inches(1.0),
1409        Length::inches(1.0),
1410        Length::inches(1.0),
1411        Length::inches(1.0),
1412    );
1413    doc.set_title("Q4 2025 Environmental Impact Report");
1414    doc.set_author("Walter White");
1415    doc.set_subject("Quarterly Environmental Report");
1416    doc.set_keywords("environment, sustainability, report, Tensorbee");
1417
1418    doc.set_different_first_page(true);
1419    doc.set_first_page_header("");
1420    doc.set_header("Tensorbee — Q4 2025 Environmental Impact Report");
1421    doc.set_footer("CONFIDENTIAL — Page");
1422
1423    // ── Cover ──
1424    let cover_bg = create_sample_png(612, 792, [20, 50, 15]);
1425    doc.add_background_image(&cover_bg, "report_cover.png");
1426
1427    for _ in 0..5 {
1428        doc.add_paragraph("");
1429    }
1430    {
1431        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1432        p.add_run("Q4 2025")
1433            .bold(true)
1434            .size(48.0)
1435            .color("FFFFFF")
1436            .font("Georgia");
1437    }
1438    {
1439        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1440        p.add_run("Environmental Impact Report")
1441            .size(24.0)
1442            .color("90EE90")
1443            .font("Georgia")
1444            .italic(true);
1445    }
1446    doc.add_paragraph("");
1447    {
1448        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1449        p.add_run("Tensorbee — Sustainability Division")
1450            .size(14.0)
1451            .color("C0C0C0");
1452    }
1453    {
1454        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1455        p.add_run("Prepared by Walter White, Chief Sustainability Officer")
1456            .size(11.0)
1457            .color("AAAAAA");
1458    }
1459
1460    // ── TOC ──
1461    doc.add_paragraph("").page_break_before(true);
1462    doc.insert_toc(doc.content_count(), 3);
1463
1464    // ── Section 1: Executive Overview ──
1465    doc.add_paragraph("").page_break_before(true);
1466    doc.add_paragraph("Executive Overview").style("Heading1");
1467    doc.add_paragraph(
1468        "This report presents Tensorbee's environmental performance for Q4 2025. Our \
1469         sustainability initiatives have yielded a 34% reduction in carbon emissions compared \
1470         to Q4 2024, exceeding our target of 25%. Key achievements include the transition to \
1471         100% renewable energy in our primary data centers and the launch of our carbon offset \
1472         marketplace.",
1473    )
1474    .first_line_indent(Length::inches(0.3));
1475
1476    // Image: performance chart
1477    let chart_img = create_chart_png(400, 200);
1478    doc.add_paragraph("");
1479    doc.add_picture(
1480        &chart_img,
1481        "performance_chart.png",
1482        Length::inches(5.0),
1483        Length::inches(2.5),
1484    )
1485    .alignment(Alignment::Center);
1486    {
1487        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1488        p.add_run("Figure 1: Quarterly Carbon Emissions (tonnes CO2e)")
1489            .italic(true)
1490            .size(9.0)
1491            .color("666666");
1492    }
1493
1494    // ── Section 2: Energy Consumption ──
1495    doc.add_paragraph("").page_break_before(true);
1496    doc.add_paragraph("Energy Consumption").style("Heading1");
1497
1498    doc.add_paragraph("Data Center Operations")
1499        .style("Heading2");
1500    doc.add_paragraph(
1501        "Our three primary data centers consumed a combined 4.2 GWh in Q4 2025, \
1502         a 12% reduction from Q3 2025 driven by improved cooling efficiency and \
1503         server consolidation.",
1504    );
1505
1506    // Data table
1507    {
1508        let mut tbl = doc
1509            .add_table(5, 4)
1510            .borders(BorderStyle::Single, 4, "2D5016")
1511            .width_pct(100.0);
1512        let hdrs = ["Data Center", "Capacity (MW)", "Usage (GWh)", "PUE"];
1513        for (c, h) in hdrs.iter().enumerate() {
1514            tbl.cell(0, c).unwrap().set_text(h);
1515            tbl.cell(0, c).unwrap().shading("2D5016");
1516        }
1517        let rows = [
1518            ("San Francisco (Primary)", "3.2", "1.8", "1.12"),
1519            ("Dublin (EU)", "2.1", "1.2", "1.18"),
1520            ("Singapore (APAC)", "1.8", "1.2", "1.24"),
1521            ("TOTAL", "7.1", "4.2", "1.17 avg"),
1522        ];
1523        for (i, (dc, cap, use_, pue)) in rows.iter().enumerate() {
1524            tbl.cell(i + 1, 0).unwrap().set_text(dc);
1525            tbl.cell(i + 1, 1).unwrap().set_text(cap);
1526            tbl.cell(i + 1, 2).unwrap().set_text(use_);
1527            tbl.cell(i + 1, 3).unwrap().set_text(pue);
1528            if i == 3 {
1529                for c in 0..4 {
1530                    tbl.cell(i + 1, c).unwrap().shading("E2EFDA");
1531                }
1532            }
1533        }
1534    }
1535
1536    doc.add_paragraph("");
1537
1538    doc.add_paragraph("Renewable Energy Mix").style("Heading2");
1539    doc.add_paragraph("Breakdown of energy sources across all facilities:");
1540
1541    doc.add_bullet_list_item("Solar PV: 42% (1.76 GWh)", 0);
1542    doc.add_bullet_list_item("Wind Power Purchase Agreements: 38% (1.60 GWh)", 0);
1543    doc.add_bullet_list_item("Hydroelectric: 12% (0.50 GWh)", 0);
1544    doc.add_bullet_list_item("Grid (non-renewable): 8% (0.34 GWh)", 0);
1545
1546    // ── Section 3: Water & Waste ──
1547    doc.add_paragraph("Water & Waste Management")
1548        .style("Heading1");
1549
1550    doc.add_paragraph("Water Usage").style("Heading2");
1551    doc.add_paragraph(
1552        "Total water consumption was 12.4 million gallons, a 15% reduction from Q3. \
1553         Our closed-loop cooling systems now recycle 78% of water used in cooling operations.",
1554    );
1555
1556    doc.add_paragraph("Waste Reduction").style("Heading2");
1557    doc.add_paragraph("E-waste management results for Q4:")
1558        .keep_with_next(true);
1559    {
1560        let mut tbl = doc
1561            .add_table(4, 3)
1562            .borders(BorderStyle::Single, 2, "6B8E23")
1563            .width_pct(80.0);
1564        tbl.cell(0, 0).unwrap().set_text("Category");
1565        tbl.cell(0, 0).unwrap().shading("6B8E23");
1566        tbl.cell(0, 1).unwrap().set_text("Weight (kg)");
1567        tbl.cell(0, 1).unwrap().shading("6B8E23");
1568        tbl.cell(0, 2).unwrap().set_text("Recycled %");
1569        tbl.cell(0, 2).unwrap().shading("6B8E23");
1570        let rows = [
1571            ("Server Hardware", "2,450", "94%"),
1572            ("Networking Equipment", "820", "91%"),
1573            ("Storage Media", "340", "99%"),
1574        ];
1575        for (i, (cat, wt, pct)) in rows.iter().enumerate() {
1576            tbl.cell(i + 1, 0).unwrap().set_text(cat);
1577            tbl.cell(i + 1, 1).unwrap().set_text(wt);
1578            tbl.cell(i + 1, 2).unwrap().set_text(pct);
1579        }
1580    }
1581
1582    // ── Section 4: Initiatives ──
1583    doc.add_paragraph("").page_break_before(true);
1584    doc.add_paragraph("Q1 2026 Initiatives").style("Heading1");
1585
1586    doc.add_paragraph("Planned Programs").style("Heading2");
1587    doc.add_numbered_list_item(
1588        "Deploy on-site battery storage at SF data center (2 MWh capacity)",
1589        0,
1590    );
1591    doc.add_numbered_list_item("Pilot immersion cooling in Dublin facility", 0);
1592    doc.add_numbered_list_item("Launch employee commute carbon offset program", 0);
1593    doc.add_numbered_list_item("Achieve ISO 14001 certification for Singapore facility", 0);
1594
1595    doc.add_paragraph("Investment Targets").style("Heading2");
1596    doc.add_paragraph("Sustainability CapEx allocation for FY2026:")
1597        .keep_with_next(true);
1598    {
1599        let mut tbl = doc
1600            .add_table(5, 2)
1601            .borders(BorderStyle::Single, 4, "2D5016");
1602        tbl.cell(0, 0).unwrap().set_text("Initiative");
1603        tbl.cell(0, 0).unwrap().shading("2D5016");
1604        tbl.cell(0, 1).unwrap().set_text("Budget");
1605        tbl.cell(0, 1).unwrap().shading("2D5016");
1606        let rows = [
1607            ("Battery Storage", "$1.2M"),
1608            ("Immersion Cooling Pilot", "$800K"),
1609            ("Solar Panel Expansion", "$2.1M"),
1610            ("Carbon Credits", "$500K"),
1611        ];
1612        for (i, (init, budget)) in rows.iter().enumerate() {
1613            tbl.cell(i + 1, 0).unwrap().set_text(init);
1614            tbl.cell(i + 1, 1).unwrap().set_text(budget);
1615            if i % 2 == 0 {
1616                tbl.cell(i + 1, 0).unwrap().shading("FFFAF0");
1617                tbl.cell(i + 1, 1).unwrap().shading("FFFAF0");
1618            }
1619        }
1620    }
1621
1622    // Image: sustainability roadmap
1623    let roadmap_img = create_sample_png(500, 100, [40, 80, 30]);
1624    doc.add_paragraph("");
1625    doc.add_picture(
1626        &roadmap_img,
1627        "roadmap.png",
1628        Length::inches(6.0),
1629        Length::inches(1.2),
1630    )
1631    .alignment(Alignment::Center);
1632    {
1633        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1634        p.add_run("Figure 2: 2026 Sustainability Roadmap")
1635            .italic(true)
1636            .size(9.0)
1637            .color("666666");
1638    }
1639
1640    doc.add_paragraph("");
1641    doc.add_paragraph(
1642        "For questions about this report, contact Walter White at sustainability@tensorbee.com.",
1643    )
1644    .shading("FFFAF0")
1645    .border_all(BorderStyle::Single, 2, "2D5016");
1646
1647    doc
1648}
1649
1650// =============================================================================
1651// 6. LETTER — Slate blue + silver scheme
1652// =============================================================================
1653fn generate_letter(_samples_dir: &Path) -> Document {
1654    let mut doc = Document::new();
1655
1656    // Colors: Slate #4A5568, Blue accent #3182CE
1657    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1658    doc.set_margins(
1659        Length::inches(1.25),
1660        Length::inches(1.25),
1661        Length::inches(1.25),
1662        Length::inches(1.25),
1663    );
1664    doc.set_title("Business Letter");
1665    doc.set_author("Walter White");
1666
1667    // Banner header using raw XML
1668    let logo_img = create_logo_png(220, 48);
1669    let banner = build_header_banner_xml(
1670        "rId1",
1671        &BannerOpts {
1672            bg_color: "4A5568",
1673            banner_width: 7772400,
1674            banner_height: 731520, // ~0.8"
1675            logo_width: 2011680,
1676            logo_height: 438912,
1677            logo_x_offset: 295125,
1678            logo_y_offset: 146304,
1679        },
1680    );
1681    doc.set_raw_header_with_images(
1682        banner,
1683        &[("rId1", &logo_img, "logo.png")],
1684        rdocx_oxml::header_footer::HdrFtrType::Default,
1685    );
1686    doc.set_margins(
1687        Length::twips(2000),
1688        Length::twips(1800),
1689        Length::twips(1440),
1690        Length::twips(1800),
1691    );
1692    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
1693
1694    // Footer with contact
1695    doc.set_footer(
1696        "Tensorbee | 123 Innovation Drive, Suite 400 | San Francisco, CA 94105 | tensorbee.com",
1697    );
1698
1699    // ── Sender Address ──
1700    doc.add_paragraph("Walter White");
1701    doc.add_paragraph("Chief Executive Officer");
1702    doc.add_paragraph("Tensorbee");
1703    doc.add_paragraph("123 Innovation Drive, Suite 400");
1704    doc.add_paragraph("San Francisco, CA 94105");
1705    doc.add_paragraph("");
1706    doc.add_paragraph("February 22, 2026");
1707
1708    doc.add_paragraph("");
1709
1710    // ── Recipient ──
1711    doc.add_paragraph("Ms. Sarah Chen");
1712    doc.add_paragraph("VP of Engineering");
1713    doc.add_paragraph("Meridian Dynamics LLC");
1714    doc.add_paragraph("456 Enterprise Boulevard");
1715    doc.add_paragraph("Austin, TX 78701");
1716
1717    doc.add_paragraph("");
1718
1719    // ── Subject ──
1720    {
1721        let mut p = doc.add_paragraph("");
1722        p.add_run("Re: Strategic Technology Partnership — Phase 2 Expansion")
1723            .bold(true);
1724    }
1725
1726    doc.add_paragraph("");
1727
1728    // ── Body ──
1729    doc.add_paragraph("Dear Ms. Chen,");
1730    doc.add_paragraph("");
1731
1732    doc.add_paragraph(
1733        "I am writing to express Tensorbee's enthusiasm for expanding our strategic technology \
1734         partnership with Meridian Dynamics. Following the successful completion of Phase 1, \
1735         which delivered a 40% improvement in your ML inference pipeline throughput, we believe \
1736         the foundation is firmly established for an ambitious Phase 2 engagement.",
1737    )
1738    .first_line_indent(Length::inches(0.5));
1739
1740    doc.add_paragraph(
1741        "During our recent executive review, your team highlighted three priority areas for \
1742         the next phase of collaboration:",
1743    )
1744    .first_line_indent(Length::inches(0.5));
1745
1746    doc.add_numbered_list_item(
1747        "Real-time anomaly detection for your financial transaction monitoring system, \
1748         targeting sub-100ms latency at 50,000 transactions per second.",
1749        0,
1750    );
1751    doc.add_numbered_list_item(
1752        "Federated learning infrastructure to enable model training across your distributed \
1753         data centers without centralizing sensitive financial data.",
1754        0,
1755    );
1756    doc.add_numbered_list_item(
1757        "MLOps automation to reduce your model deployment cycle from the current 5 days \
1758         to under 4 hours.",
1759        0,
1760    );
1761
1762    doc.add_paragraph(
1763        "Our engineering team has prepared a detailed technical proposal addressing each of \
1764         these areas. We have allocated a dedicated team of eight senior engineers, led by \
1765         our CTO, to ensure continuity with the Phase 1 team your organization has already \
1766         built a productive working relationship with.",
1767    )
1768    .first_line_indent(Length::inches(0.5));
1769
1770    doc.add_paragraph(
1771        "I would welcome the opportunity to present our Phase 2 proposal in person. My \
1772         assistant will reach out to coordinate a meeting at your convenience during the \
1773         first week of March.",
1774    )
1775    .first_line_indent(Length::inches(0.5));
1776
1777    doc.add_paragraph("");
1778
1779    doc.add_paragraph("Warm regards,");
1780    doc.add_paragraph("");
1781    doc.add_paragraph("");
1782
1783    // Signature line
1784    doc.add_paragraph("")
1785        .border_bottom(BorderStyle::Single, 4, "4A5568");
1786    {
1787        let mut p = doc.add_paragraph("");
1788        p.add_run("Walter White").bold(true).size(12.0);
1789    }
1790    {
1791        let mut p = doc.add_paragraph("");
1792        p.add_run("Chief Executive Officer, Tensorbee")
1793            .italic(true)
1794            .size(10.0)
1795            .color("666666");
1796    }
1797    {
1798        let mut p = doc.add_paragraph("");
1799        p.add_run("walter@tensorbee.com | +1 (415) 555-0199")
1800            .size(10.0)
1801            .color("888888");
1802    }
1803
1804    doc
1805}
1806
1807// =============================================================================
1808// 7. EMPLOYMENT CONTRACT — Purple + charcoal scheme
1809// =============================================================================
1810fn generate_contract(_samples_dir: &Path) -> Document {
1811    let mut doc = Document::new();
1812
1813    // Colors: Purple #5B2C6F, Plum #8E44AD, Gray #2C3E50
1814    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1815    doc.set_margins(
1816        Length::inches(1.25),
1817        Length::inches(1.0),
1818        Length::inches(1.0),
1819        Length::inches(1.0),
1820    );
1821    doc.set_title("Employment Agreement");
1822    doc.set_author("Tensorbee HR Department");
1823    doc.set_subject("Employment Contract — Walter White");
1824    doc.set_keywords("employment, contract, agreement, Tensorbee");
1825
1826    doc.set_header("TENSORBEE — Employment Agreement");
1827    doc.set_footer("Employment Agreement — Walter White — Page");
1828
1829    // ── Title Block ──
1830    doc.add_paragraph("")
1831        .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1832    {
1833        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1834        p.add_run("EMPLOYMENT AGREEMENT")
1835            .bold(true)
1836            .size(24.0)
1837            .color("5B2C6F")
1838            .font("Georgia");
1839    }
1840    doc.add_paragraph("")
1841        .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1842    doc.add_paragraph("");
1843
1844    // ── Parties ──
1845    doc.add_paragraph(
1846        "This Employment Agreement (\"Agreement\") is entered into as of February 22, 2026 \
1847         (\"Effective Date\"), by and between:",
1848    );
1849
1850    doc.add_paragraph("");
1851
1852    {
1853        let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1854        p.add_run("EMPLOYER: ").bold(true);
1855        p.add_run(
1856            "Tensorbee, Inc., a Delaware corporation with its principal place of business at \
1857                   123 Innovation Drive, Suite 400, San Francisco, CA 94105 (\"Company\")",
1858        );
1859    }
1860    doc.add_paragraph("");
1861    {
1862        let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1863        p.add_run("EMPLOYEE: ").bold(true);
1864        p.add_run(
1865            "Walter White, residing at 308 Negra Arroyo Lane, Albuquerque, NM 87104 (\"Employee\")",
1866        );
1867    }
1868
1869    doc.add_paragraph("");
1870    doc.add_paragraph("The Company and Employee are collectively referred to as the \"Parties.\"");
1871    doc.add_paragraph("");
1872
1873    // ── Article 1: Position and Duties ──
1874    doc.add_paragraph("Article 1 — Position and Duties")
1875        .style("Heading1");
1876    {
1877        let mut p = doc.add_paragraph("");
1878        p.add_run("1.1 ").bold(true);
1879        p.add_run("The Company hereby employs the Employee as ");
1880        p.add_run("Chief Executive Officer (CEO)")
1881            .bold(true)
1882            .italic(true);
1883        p.add_run(", reporting directly to the Board of Directors.");
1884    }
1885    {
1886        let mut p = doc.add_paragraph("");
1887        p.add_run("1.2 ").bold(true);
1888        p.add_run("The Employee shall devote full working time, attention, and best efforts to \
1889                   the performance of duties as reasonably assigned by the Board, including but not limited to:");
1890    }
1891    doc.add_bullet_list_item(
1892        "Setting and executing the Company's strategic vision and business plan",
1893        0,
1894    );
1895    doc.add_bullet_list_item(
1896        "Overseeing all operations, engineering, and commercial activities",
1897        0,
1898    );
1899    doc.add_bullet_list_item(
1900        "Representing the Company to investors, customers, and the public",
1901        0,
1902    );
1903    doc.add_bullet_list_item("Recruiting, developing, and retaining key talent", 0);
1904
1905    {
1906        let mut p = doc.add_paragraph("");
1907        p.add_run("1.3 ").bold(true);
1908        p.add_run(
1909            "The Employee's primary work location shall be the Company's San Francisco \
1910                   headquarters, with reasonable travel as required by business needs.",
1911        );
1912    }
1913
1914    // ── Article 2: Compensation ──
1915    doc.add_paragraph("Article 2 — Compensation and Benefits")
1916        .style("Heading1");
1917    {
1918        let mut p = doc.add_paragraph("");
1919        p.add_run("2.1 Base Salary. ").bold(true);
1920        p.add_run("The Company shall pay the Employee an annual base salary of ");
1921        p.add_run("$375,000.00").bold(true);
1922        p.add_run(" (Three Hundred Seventy-Five Thousand Dollars), payable in accordance with the \
1923                   Company's standard payroll schedule, less applicable withholdings and deductions.");
1924    }
1925    {
1926        let mut p = doc.add_paragraph("");
1927        p.add_run("2.2 Annual Bonus. ").bold(true);
1928        p.add_run("The Employee shall be eligible for an annual performance bonus of up to ");
1929        p.add_run("40%").bold(true);
1930        p.add_run(
1931            " of base salary, based on achievement of mutually agreed performance objectives.",
1932        );
1933    }
1934    {
1935        let mut p = doc.add_paragraph("");
1936        p.add_run("2.3 Equity. ").bold(true);
1937        p.add_run("Subject to Board approval, the Employee shall receive a stock option grant of ");
1938        p.add_run("500,000 shares").bold(true);
1939        p.add_run(
1940            " of the Company's common stock, vesting over four (4) years with a one-year cliff, \
1941                   at an exercise price equal to the fair market value on the date of grant.",
1942        );
1943    }
1944
1945    // Compensation summary table
1946    doc.add_paragraph("");
1947    {
1948        let mut tbl = doc
1949            .add_table(5, 2)
1950            .borders(BorderStyle::Single, 4, "5B2C6F")
1951            .width_pct(70.0)
1952            .alignment(Alignment::Center);
1953        tbl.cell(0, 0).unwrap().set_text("Compensation Element");
1954        tbl.cell(0, 0).unwrap().shading("5B2C6F");
1955        tbl.cell(0, 1).unwrap().set_text("Value");
1956        tbl.cell(0, 1).unwrap().shading("5B2C6F");
1957        let items = [
1958            ("Base Salary", "$375,000/year"),
1959            ("Target Bonus", "Up to 40% ($150,000)"),
1960            ("Equity Grant", "500,000 shares (4yr vest)"),
1961            ("Total Target Comp", "$525,000 + equity"),
1962        ];
1963        for (i, (elem, val)) in items.iter().enumerate() {
1964            tbl.cell(i + 1, 0).unwrap().set_text(elem);
1965            tbl.cell(i + 1, 1).unwrap().set_text(val);
1966            if i == 3 {
1967                tbl.cell(i + 1, 0).unwrap().shading("E8DAEF");
1968                tbl.cell(i + 1, 1).unwrap().shading("E8DAEF");
1969            }
1970        }
1971    }
1972
1973    // ── Article 3: Benefits ──
1974    doc.add_paragraph("").page_break_before(true);
1975    doc.add_paragraph("Article 3 — Benefits").style("Heading1");
1976    {
1977        let mut p = doc.add_paragraph("");
1978        p.add_run("3.1 ").bold(true);
1979        p.add_run(
1980            "The Employee shall be entitled to participate in all benefit programs \
1981                   generally available to senior executives, including:",
1982        );
1983    }
1984    doc.add_bullet_list_item(
1985        "Medical, dental, and vision insurance (100% premium coverage for employee and dependents)",
1986        0,
1987    );
1988    doc.add_bullet_list_item("401(k) retirement plan with 6% company match", 0);
1989    doc.add_bullet_list_item("Life insurance and long-term disability coverage", 0);
1990    doc.add_bullet_list_item("Annual professional development allowance of $10,000", 0);
1991
1992    {
1993        let mut p = doc.add_paragraph("");
1994        p.add_run("3.2 Paid Time Off. ").bold(true);
1995        p.add_run(
1996            "The Employee shall receive 25 days of paid vacation per year, plus Company holidays, \
1997                   accruing on a monthly basis.",
1998        );
1999    }
2000
2001    // ── Article 4: Term and Termination ──
2002    doc.add_paragraph("Article 4 — Term and Termination")
2003        .style("Heading1");
2004    {
2005        let mut p = doc.add_paragraph("");
2006        p.add_run("4.1 At-Will Employment. ").bold(true);
2007        p.add_run(
2008            "This Agreement is for at-will employment and may be terminated by either Party \
2009                   at any time, with or without cause, subject to the notice provisions herein.",
2010        );
2011    }
2012    {
2013        let mut p = doc.add_paragraph("");
2014        p.add_run("4.2 Notice Period. ").bold(true);
2015        p.add_run("Either Party shall provide ");
2016        p.add_run("ninety (90) days").bold(true);
2017        p.add_run(" written notice of termination, or pay in lieu thereof.");
2018    }
2019    {
2020        let mut p = doc.add_paragraph("");
2021        p.add_run("4.3 Severance. ").bold(true);
2022        p.add_run("In the event of termination by the Company without Cause, the Employee shall \
2023                   receive (a) twelve (12) months of base salary continuation, (b) pro-rata bonus \
2024                   for the year of termination, and (c) twelve (12) months of COBRA premium coverage.");
2025    }
2026
2027    // ── Article 5: Confidentiality ──
2028    doc.add_paragraph("Article 5 — Confidentiality and IP")
2029        .style("Heading1");
2030    {
2031        let mut p = doc.add_paragraph("");
2032        p.add_run("5.1 ").bold(true);
2033        p.add_run(
2034            "The Employee agrees to maintain strict confidentiality of all proprietary \
2035                   information, trade secrets, and business plans of the Company during and after \
2036                   employment.",
2037        );
2038    }
2039    {
2040        let mut p = doc.add_paragraph("");
2041        p.add_run("5.2 ").bold(true);
2042        p.add_run(
2043            "All intellectual property, inventions, and works of authorship created by the \
2044                   Employee during the term of employment and within the scope of duties shall be \
2045                   the sole and exclusive property of the Company.",
2046        );
2047    }
2048
2049    // ── Article 6: Non-Compete ──
2050    doc.add_paragraph("Article 6 — Non-Competition")
2051        .style("Heading1");
2052    {
2053        let mut p = doc.add_paragraph("");
2054        p.add_run("6.1 ").bold(true);
2055        p.add_run("For a period of twelve (12) months following termination, the Employee agrees \
2056                   not to engage in any business that directly competes with the Company's core \
2057                   business of AI infrastructure and ML operations services, within the United States.");
2058    }
2059
2060    // ── Article 7: General Provisions ──
2061    doc.add_paragraph("Article 7 — General Provisions")
2062        .style("Heading1");
2063    {
2064        let mut p = doc.add_paragraph("");
2065        p.add_run("7.1 Governing Law. ").bold(true);
2066        p.add_run(
2067            "This Agreement shall be governed by and construed in accordance with the laws of \
2068                   the State of California.",
2069        );
2070    }
2071    {
2072        let mut p = doc.add_paragraph("");
2073        p.add_run("7.2 Entire Agreement. ").bold(true);
2074        p.add_run(
2075            "This Agreement constitutes the entire agreement between the Parties and supersedes \
2076                   all prior negotiations, representations, and agreements.",
2077        );
2078    }
2079    {
2080        let mut p = doc.add_paragraph("");
2081        p.add_run("7.3 Amendment. ").bold(true);
2082        p.add_run(
2083            "This Agreement may only be amended by a written instrument signed by both Parties.",
2084        );
2085    }
2086
2087    // ── Signature Block ──
2088    doc.add_paragraph("").page_break_before(true);
2089    doc.add_paragraph("IN WITNESS WHEREOF, the Parties have executed this Employment Agreement as of the Effective Date.")
2090        .space_after(Length::pt(24.0));
2091
2092    // Signature table
2093    {
2094        let mut tbl = doc.add_table(6, 2).width_pct(100.0);
2095        tbl.cell(0, 0).unwrap().set_text("FOR THE COMPANY:");
2096        tbl.cell(0, 0).unwrap().shading("5B2C6F");
2097        tbl.cell(0, 1).unwrap().set_text("EMPLOYEE:");
2098        tbl.cell(0, 1).unwrap().shading("5B2C6F");
2099
2100        tbl.cell(1, 0).unwrap().set_text("");
2101        tbl.cell(1, 1).unwrap().set_text("");
2102        tbl.row(1).unwrap().height(Length::pt(40.0));
2103
2104        tbl.cell(2, 0)
2105            .unwrap()
2106            .set_text("Signature: ___________________________");
2107        tbl.cell(2, 1)
2108            .unwrap()
2109            .set_text("Signature: ___________________________");
2110        tbl.cell(3, 0)
2111            .unwrap()
2112            .set_text("Name: Board Representative");
2113        tbl.cell(3, 1).unwrap().set_text("Name: Walter White");
2114        tbl.cell(4, 0)
2115            .unwrap()
2116            .set_text("Title: Chair, Board of Directors");
2117        tbl.cell(4, 1)
2118            .unwrap()
2119            .set_text("Title: Chief Executive Officer");
2120        tbl.cell(5, 0).unwrap().set_text("Date: _______________");
2121        tbl.cell(5, 1).unwrap().set_text("Date: _______________");
2122    }
2123
2124    doc
2125}
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_samples.rs (line 52)
34fn generate_feature_showcase(path: &Path) {
35    let mut doc = Document::new();
36
37    // =========================================================================
38    // PAGE SETUP & METADATA
39    // =========================================================================
40    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
41    doc.set_margins(
42        Length::inches(1.0), // top
43        Length::inches(1.0), // right
44        Length::inches(1.0), // bottom
45        Length::inches(1.0), // left
46    );
47    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
48    doc.set_gutter(Length::twips(0));
49
50    doc.set_title("rdocx Feature Showcase");
51    doc.set_author("rdocx Sample Generator");
52    doc.set_subject("Comprehensive feature demonstration");
53    doc.set_keywords("rdocx, docx, rust, sample");
54
55    // Header & Footer
56    doc.set_header("rdocx Feature Showcase");
57    doc.set_footer("Generated by rdocx — Page");
58
59    // Different first page header
60    doc.set_different_first_page(true);
61    doc.set_first_page_header("rdocx");
62    doc.set_first_page_footer("Feature Showcase — Cover Page");
63
64    // =========================================================================
65    // PAGE 1: COVER PAGE — background image, run formatting
66    // =========================================================================
67    let bg_cover = create_sample_png(612, 792, [30, 60, 120]);
68    doc.add_background_image(&bg_cover, "cover_bg.png");
69
70    doc.add_paragraph(""); // spacer
71    doc.add_paragraph(""); // spacer
72    doc.add_paragraph(""); // spacer
73
74    {
75        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
76        p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
77    }
78    {
79        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
80        p.add_run("Feature Showcase")
81            .size(28.0)
82            .color("FFFFFF")
83            .italic(true);
84    }
85
86    doc.add_paragraph(""); // spacer
87
88    {
89        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
90        p.add_run("A comprehensive demonstration of every feature")
91            .size(14.0)
92            .color("CCDDFF");
93    }
94    {
95        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
96        p.add_run("provided by the rdocx Rust crate for DOCX generation.")
97            .size(14.0)
98            .color("CCDDFF");
99    }
100
101    // =========================================================================
102    // PAGE 2: TEXT FORMATTING
103    // =========================================================================
104    doc.add_paragraph("").page_break_before(true);
105
106    doc.add_paragraph("1. Text Formatting").style("Heading1");
107
108    doc.add_paragraph("This section demonstrates paragraph and run-level formatting options.");
109    doc.add_paragraph("");
110
111    // --- Paragraph alignment ---
112    doc.add_paragraph("Paragraph Alignment").style("Heading2");
113
114    doc.add_paragraph("This paragraph is left-aligned (the default).")
115        .alignment(Alignment::Left);
116    doc.add_paragraph("This paragraph is center-aligned.")
117        .alignment(Alignment::Center);
118    doc.add_paragraph("This paragraph is right-aligned.")
119        .alignment(Alignment::Right);
120    doc.add_paragraph(
121        "This paragraph is justified. To demonstrate justified text properly, it needs \
122         to be long enough to span multiple lines so the word spacing adjustment is visible \
123         across the full width of the text area on the page.",
124    )
125    .alignment(Alignment::Justify);
126
127    doc.add_paragraph("");
128
129    // --- Run formatting ---
130    doc.add_paragraph("Run Formatting").style("Heading2");
131
132    {
133        let mut p = doc.add_paragraph("");
134        p.add_run("Bold text").bold(true);
135        p.add_run(" | ");
136        p.add_run("Italic text").italic(true);
137        p.add_run(" | ");
138        p.add_run("Bold + Italic").bold(true).italic(true);
139    }
140    {
141        let mut p = doc.add_paragraph("");
142        p.add_run("Single underline").underline(true);
143        p.add_run(" | ");
144        p.add_run("Strikethrough").strike(true);
145        p.add_run(" | ");
146        p.add_run("Double strikethrough").double_strike(true);
147    }
148    {
149        let mut p = doc.add_paragraph("");
150        p.add_run("Red text").color("FF0000");
151        p.add_run(" | ");
152        p.add_run("Blue text").color("0000FF");
153        p.add_run(" | ");
154        p.add_run("Green text").color("00AA00");
155        p.add_run(" | ");
156        p.add_run("Highlighted").highlight("FFFF00");
157    }
158    {
159        let mut p = doc.add_paragraph("");
160        p.add_run("8pt small").size(8.0);
161        p.add_run(" | ");
162        p.add_run("11pt normal").size(11.0);
163        p.add_run(" | ");
164        p.add_run("16pt large").size(16.0);
165        p.add_run(" | ");
166        p.add_run("24pt extra-large").size(24.0);
167    }
168    {
169        let mut p = doc.add_paragraph("");
170        p.add_run("Arial font").font("Arial");
171        p.add_run(" | ");
172        p.add_run("Times New Roman font").font("Times New Roman");
173        p.add_run(" | ");
174        p.add_run("Courier New font").font("Courier New");
175    }
176    {
177        let mut p = doc.add_paragraph("");
178        p.add_run("Normal");
179        p.add_run(" H").size(11.0);
180        p.add_run("2").subscript();
181        p.add_run("O (subscript)").size(11.0);
182        p.add_run(" | E = mc").size(11.0);
183        p.add_run("2").superscript();
184        p.add_run(" (superscript)").size(11.0);
185    }
186    {
187        let mut p = doc.add_paragraph("");
188        p.add_run("ALL CAPS").all_caps(true);
189        p.add_run(" | ");
190        p.add_run("Small Caps").small_caps(true);
191        p.add_run(" | ");
192        p.add_run("Expanded spacing")
193            .character_spacing(Length::twips(40));
194    }
195
196    doc.add_paragraph("");
197
198    // --- Paragraph formatting ---
199    doc.add_paragraph("Paragraph Formatting").style("Heading2");
200
201    doc.add_paragraph("Paragraph with shading (light green background)")
202        .shading("E2EFDA");
203
204    doc.add_paragraph("Paragraph with bottom border")
205        .border_bottom(BorderStyle::Single, 6, "2E75B6");
206
207    doc.add_paragraph("Paragraph with all borders")
208        .border_all(BorderStyle::Single, 4, "FF0000");
209
210    doc.add_paragraph("Paragraph with 1-inch left indent and hanging indent")
211        .indent_left(Length::inches(1.0))
212        .hanging_indent(Length::inches(0.5));
213
214    doc.add_paragraph("Paragraph with first-line indent of 0.5 inches")
215        .first_line_indent(Length::inches(0.5));
216
217    doc.add_paragraph("Paragraph with extra space before (24pt) and after (12pt)")
218        .space_before(Length::pt(24.0))
219        .space_after(Length::pt(12.0));
220
221    doc.add_paragraph(
222        "Paragraph with double line spacing. This text should have extra vertical \
223         space between lines to demonstrate the line_spacing_multiple setting.",
224    )
225    .line_spacing_multiple(2.0);
226
227    doc.add_paragraph("Paragraph with keep-with-next (won't break from the next paragraph)")
228        .keep_with_next(true);
229    doc.add_paragraph("(This stays with the paragraph above.)");
230
231    // =========================================================================
232    // PAGE 3: LISTS & TAB STOPS
233    // =========================================================================
234    doc.add_paragraph("").page_break_before(true);
235
236    doc.add_paragraph("2. Lists").style("Heading1");
237
238    doc.add_paragraph("Bullet List").style("Heading2");
239
240    doc.add_bullet_list_item("First bullet item", 0);
241    doc.add_bullet_list_item("Second bullet item", 0);
242    doc.add_bullet_list_item("Nested level 1", 1);
243    doc.add_bullet_list_item("Nested level 2", 2);
244    doc.add_bullet_list_item("Back to level 1", 1);
245    doc.add_bullet_list_item("Third bullet item", 0);
246
247    doc.add_paragraph("");
248
249    doc.add_paragraph("Numbered List").style("Heading2");
250
251    doc.add_numbered_list_item("First numbered item", 0);
252    doc.add_numbered_list_item("Second numbered item", 0);
253    doc.add_numbered_list_item("Sub-item A", 1);
254    doc.add_numbered_list_item("Sub-item B", 1);
255    doc.add_numbered_list_item("Third numbered item", 0);
256
257    doc.add_paragraph("");
258
259    // --- Tab stops ---
260    doc.add_paragraph("Tab Stops").style("Heading2");
261
262    doc.add_paragraph("Left\tCenter\tRight\tDecimal")
263        .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
264        .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
265        .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
266        .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
267
268    doc.add_paragraph("Item\t........\tPrice")
269        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
270        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
271        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
272
273    doc.add_paragraph("Widget A\t........\t$19.99")
274        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
275        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
276        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
277
278    doc.add_paragraph("Gadget B\t________\t$249.50")
279        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
280        .add_tab_stop_with_leader(
281            TabAlignment::Right,
282            Length::inches(4.0),
283            TabLeader::Underscore,
284        )
285        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
286
287    // =========================================================================
288    // PAGE 4: TABLES
289    // =========================================================================
290    doc.add_paragraph("").page_break_before(true);
291
292    doc.add_paragraph("3. Tables").style("Heading1");
293
294    // --- Basic table with borders ---
295    doc.add_paragraph("Basic Table with Borders")
296        .style("Heading2");
297
298    {
299        let mut tbl = doc.add_table(4, 3);
300        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
301
302        // Header row
303        for col in 0..3 {
304            tbl.cell(0, col).unwrap().shading("2E75B6");
305        }
306        tbl.cell(0, 0).unwrap().set_text("Name");
307        tbl.cell(0, 1).unwrap().set_text("Role");
308        tbl.cell(0, 2).unwrap().set_text("Location");
309
310        tbl.cell(1, 0).unwrap().set_text("Alice Johnson");
311        tbl.cell(1, 1).unwrap().set_text("Engineering Lead");
312        tbl.cell(1, 2).unwrap().set_text("New York");
313
314        tbl.cell(2, 0).unwrap().set_text("Bob Smith");
315        tbl.cell(2, 1).unwrap().set_text("Product Manager");
316        tbl.cell(2, 2).unwrap().set_text("San Francisco");
317
318        tbl.cell(3, 0).unwrap().set_text("Carol Davis");
319        tbl.cell(3, 1).unwrap().set_text("Designer");
320        tbl.cell(3, 2).unwrap().set_text("London");
321    }
322
323    doc.add_paragraph("");
324
325    // --- Table with cell merging ---
326    doc.add_paragraph("Table with Cell Merging & Vertical Alignment")
327        .style("Heading2");
328
329    {
330        let mut tbl = doc.add_table(4, 4);
331        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
332        tbl = tbl.width_pct(100.0);
333
334        // Header spanning all columns
335        tbl.cell(0, 0).unwrap().set_text("Quarterly Revenue Report");
336        tbl.cell(0, 0).unwrap().shading("1F4E79");
337        tbl.cell(0, 0).unwrap().grid_span(4);
338
339        // Sub-header
340        tbl.cell(1, 0).unwrap().set_text("Region");
341        tbl.cell(1, 0).unwrap().shading("D6E4F0");
342        tbl.cell(1, 1).unwrap().set_text("Q1");
343        tbl.cell(1, 1).unwrap().shading("D6E4F0");
344        tbl.cell(1, 2).unwrap().set_text("Q2");
345        tbl.cell(1, 2).unwrap().shading("D6E4F0");
346        tbl.cell(1, 3).unwrap().set_text("Total");
347        tbl.cell(1, 3).unwrap().shading("D6E4F0");
348
349        // Data
350        tbl.cell(2, 0).unwrap().set_text("North America");
351        tbl.cell(2, 1).unwrap().set_text("$2.4M");
352        tbl.cell(2, 2).unwrap().set_text("$2.7M");
353        tbl.cell(2, 3).unwrap().set_text("$5.1M");
354
355        tbl.cell(3, 0).unwrap().set_text("Europe");
356        tbl.cell(3, 1).unwrap().set_text("$1.8M");
357        tbl.cell(3, 2).unwrap().set_text("$2.0M");
358        tbl.cell(3, 3).unwrap().set_text("$3.8M");
359
360        // Vertical alignment on data cells
361        tbl.cell(2, 3)
362            .unwrap()
363            .vertical_alignment(VerticalAlignment::Center);
364        tbl.cell(3, 3)
365            .unwrap()
366            .vertical_alignment(VerticalAlignment::Bottom);
367    }
368
369    doc.add_paragraph("");
370
371    // --- Table with vertical merge ---
372    doc.add_paragraph("Table with Vertical Merge")
373        .style("Heading2");
374
375    {
376        let mut tbl = doc.add_table(4, 3);
377        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
378
379        tbl.cell(0, 0).unwrap().set_text("Category");
380        tbl.cell(0, 0).unwrap().shading("E2EFDA");
381        tbl.cell(0, 1).unwrap().set_text("Item");
382        tbl.cell(0, 1).unwrap().shading("E2EFDA");
383        tbl.cell(0, 2).unwrap().set_text("Price");
384        tbl.cell(0, 2).unwrap().shading("E2EFDA");
385
386        // "Hardware" spans rows 1-2
387        tbl.cell(1, 0).unwrap().set_text("Hardware");
388        tbl.cell(1, 0).unwrap().v_merge_restart();
389        tbl.cell(1, 1).unwrap().set_text("Laptop");
390        tbl.cell(1, 2).unwrap().set_text("$1,200");
391
392        tbl.cell(2, 0).unwrap().v_merge_continue();
393        tbl.cell(2, 1).unwrap().set_text("Monitor");
394        tbl.cell(2, 2).unwrap().set_text("$450");
395
396        // "Software" on row 3
397        tbl.cell(3, 0).unwrap().set_text("Software");
398        tbl.cell(3, 1).unwrap().set_text("IDE License");
399        tbl.cell(3, 2).unwrap().set_text("$200/yr");
400    }
401
402    doc.add_paragraph("");
403
404    // --- Nested table ---
405    doc.add_paragraph("Nested Table").style("Heading2");
406
407    {
408        let mut tbl = doc.add_table(2, 2);
409        tbl = tbl.borders(BorderStyle::Single, 6, "2E75B6");
410
411        tbl.cell(0, 0).unwrap().set_text("Outer Cell (0,0)");
412        tbl.cell(0, 1).unwrap().set_text("Outer Cell (0,1)");
413        tbl.cell(1, 0).unwrap().set_text("Outer Cell (1,0)");
414
415        // Nested table inside cell (1,1)
416        {
417            let mut cell = tbl.cell(1, 1).unwrap();
418            cell.set_text("Contains nested table:");
419            let mut nested = cell.add_table(2, 2);
420            nested = nested.borders(BorderStyle::Single, 2, "FF6600");
421            nested.cell(0, 0).unwrap().set_text("Inner A");
422            nested.cell(0, 1).unwrap().set_text("Inner B");
423            nested.cell(1, 0).unwrap().set_text("Inner C");
424            nested.cell(1, 1).unwrap().set_text("Inner D");
425        }
426    }
427
428    // =========================================================================
429    // PAGE 5: IMAGES
430    // =========================================================================
431    doc.add_paragraph("").page_break_before(true);
432
433    doc.add_paragraph("4. Images").style("Heading1");
434
435    doc.add_paragraph("Inline Image").style("Heading2");
436
437    doc.add_paragraph("Below is an inline image (200x50 pixels, blue gradient):");
438    let inline_img = create_sample_png(200, 50, [0, 80, 200]);
439    doc.add_picture(
440        &inline_img,
441        "inline_chart.png",
442        Length::inches(3.0),
443        Length::inches(0.75),
444    );
445
446    doc.add_paragraph("");
447
448    doc.add_paragraph("Header Image").style("Heading2");
449
450    // Replace the text-only header with an image header
451    let header_img = create_sample_png(400, 40, [40, 40, 40]);
452    doc.set_header_image(
453        &header_img,
454        "header_logo.png",
455        Length::inches(2.0),
456        Length::inches(0.2),
457    );
458
459    doc.add_paragraph(
460        "The document header has been replaced with an inline image. \
461         Check the header area at the top of this page.",
462    );
463
464    doc.add_paragraph("");
465    doc.add_paragraph(
466        "Note: The cover page uses a full-page background image behind the text, \
467         demonstrated on page 1 via add_background_image().",
468    );
469
470    // =========================================================================
471    // PAGE 6: CONTENT MANIPULATION — placeholder replacement, insertion
472    // =========================================================================
473    doc.add_paragraph("").page_break_before(true);
474
475    doc.add_paragraph("5. Content Manipulation")
476        .style("Heading1");
477
478    // --- Placeholder replacement ---
479    doc.add_paragraph("Placeholder Replacement")
480        .style("Heading2");
481
482    doc.add_paragraph(
483        "Before replacement, this document contained {{customer}} and {{date}} placeholders.",
484    );
485
486    {
487        let mut p = doc.add_paragraph("");
488        p.add_run("Customer: ").bold(true);
489        p.add_run("{{customer}}");
490    }
491    {
492        let mut p = doc.add_paragraph("");
493        p.add_run("Date: ").bold(true);
494        p.add_run("{{date}}");
495    }
496    doc.add_paragraph("Reference: {{ref_number}}");
497
498    // Table with placeholders
499    {
500        let mut tbl = doc.add_table(3, 2);
501        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
502        tbl.cell(0, 0).unwrap().set_text("Field");
503        tbl.cell(0, 0).unwrap().shading("D6E4F0");
504        tbl.cell(0, 1).unwrap().set_text("Value");
505        tbl.cell(0, 1).unwrap().shading("D6E4F0");
506        tbl.cell(1, 0).unwrap().set_text("Project");
507        tbl.cell(1, 1).unwrap().set_text("{{project}}");
508        tbl.cell(2, 0).unwrap().set_text("Status");
509        tbl.cell(2, 1).unwrap().set_text("{{status}}");
510    }
511
512    // Perform replacements
513    let mut replacements = HashMap::new();
514    replacements.insert("{{customer}}", "Acme Corporation");
515    replacements.insert("{{date}}", "February 22, 2026");
516    replacements.insert("{{ref_number}}", "REF-2026-001");
517    replacements.insert("{{project}}", "Infrastructure Upgrade");
518    replacements.insert("{{status}}", "In Progress");
519    let replace_count = doc.replace_all(&replacements);
520
521    doc.add_paragraph("");
522    doc.add_paragraph(&format!(
523        "(Replaced {} placeholders above — in body text and table cells)",
524        replace_count
525    ));
526
527    doc.add_paragraph("");
528
529    // --- Content insertion ---
530    doc.add_paragraph("Content Insertion").style("Heading2");
531
532    doc.add_paragraph("Section A: First section of content.");
533    doc.add_paragraph("Section C: Third section of content.");
534
535    // Insert "Section B" between A and C
536    if let Some(idx) = doc.find_content_index("Section C") {
537        doc.insert_paragraph(
538            idx,
539            "Section B: Inserted between A and C using find_content_index().",
540        );
541    }
542
543    doc.add_paragraph("");
544    doc.add_paragraph(
545        "The paragraph above ('Section B') was inserted at a specific position \
546         using find_content_index() + insert_paragraph().",
547    );
548
549    // =========================================================================
550    // PAGE 7: LANDSCAPE — section break, wide table
551    // =========================================================================
552    doc.add_paragraph("").section_break(SectionBreak::NextPage);
553
554    doc.add_paragraph("6. Mixed Page Orientation")
555        .style("Heading1");
556
557    doc.add_paragraph(
558        "This page is in LANDSCAPE orientation. It was created using a section break \
559         followed by section_landscape(). This is useful for wide tables or charts.",
560    );
561
562    doc.add_paragraph("");
563
564    // Wide table for landscape
565    {
566        let mut tbl = doc.add_table(4, 7);
567        tbl = tbl.borders(BorderStyle::Single, 4, "2E75B6");
568
569        let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
570        for (col, h) in headers.iter().enumerate() {
571            tbl.cell(0, col).unwrap().set_text(h);
572            tbl.cell(0, col).unwrap().shading("2E75B6");
573        }
574
575        let data = [
576            [
577                "North America",
578                "$1.2M",
579                "$1.3M",
580                "$1.4M",
581                "$1.5M",
582                "$1.6M",
583                "$7.0M",
584            ],
585            [
586                "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
587            ],
588            [
589                "Asia Pacific",
590                "$0.5M",
591                "$0.6M",
592                "$0.7M",
593                "$0.7M",
594                "$0.8M",
595                "$3.3M",
596            ],
597        ];
598        for (row_idx, row_data) in data.iter().enumerate() {
599            for (col, val) in row_data.iter().enumerate() {
600                tbl.cell(row_idx + 1, col).unwrap().set_text(val);
601            }
602        }
603    }
604
605    // End landscape, return to portrait
606    doc.add_paragraph("")
607        .section_break(SectionBreak::NextPage)
608        .section_landscape();
609
610    // =========================================================================
611    // PAGE 8: BACK TO PORTRAIT — styles, final notes
612    // =========================================================================
613    doc.add_paragraph("7. Custom Styles & Summary")
614        .style("Heading1");
615
616    doc.add_paragraph(
617        "This final page is back in portrait orientation after a section break. \
618         The document has demonstrated:",
619    );
620
621    doc.add_bullet_list_item(
622        "Page setup: size, margins, header/footer distance, gutter",
623        0,
624    );
625    doc.add_bullet_list_item("Document metadata: title, author, subject, keywords", 0);
626    doc.add_bullet_list_item("Headers and footers: text, images, different first page", 0);
627    doc.add_bullet_list_item("Background images: full-page behind text", 0);
628    doc.add_bullet_list_item(
629        "Text formatting: bold, italic, underline, strike, color, size, font",
630        0,
631    );
632    doc.add_bullet_list_item(
633        "Advanced run formatting: superscript, subscript, caps, spacing",
634        0,
635    );
636    doc.add_bullet_list_item(
637        "Paragraph formatting: alignment, borders, shading, spacing, indentation",
638        0,
639    );
640    doc.add_bullet_list_item("Bullet and numbered lists with nesting levels", 0);
641    doc.add_bullet_list_item("Tab stops with dot/underscore leaders", 0);
642    doc.add_bullet_list_item(
643        "Tables: borders, shading, column spans, row spans, nesting",
644        0,
645    );
646    doc.add_bullet_list_item("Vertical alignment in table cells", 0);
647    doc.add_bullet_list_item("Inline images", 0);
648    doc.add_bullet_list_item("Placeholder replacement in body and table cells", 0);
649    doc.add_bullet_list_item("Content insertion at specific positions", 0);
650    doc.add_bullet_list_item(
651        "Section breaks with mixed portrait/landscape orientation",
652        0,
653    );
654
655    doc.add_paragraph("");
656    doc.add_paragraph("All features above were built entirely from scratch using the rdocx API.")
657        .alignment(Alignment::Center)
658        .shading("E2EFDA")
659        .border_all(BorderStyle::Single, 2, "00AA00");
660
661    doc.save(path).unwrap();
662}
examples/generate_all_samples.rs (line 101)
86fn generate_feature_showcase(_samples_dir: &Path) -> Document {
87    let mut doc = Document::new();
88
89    // ── Page Setup & Metadata ──
90    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
91    doc.set_margins(
92        Length::inches(1.0),
93        Length::inches(1.0),
94        Length::inches(1.0),
95        Length::inches(1.0),
96    );
97    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
98    doc.set_gutter(Length::twips(0));
99    doc.set_title("rdocx Feature Showcase");
100    doc.set_author("rdocx Sample Generator");
101    doc.set_subject("Comprehensive feature demonstration");
102    doc.set_keywords("rdocx, docx, rust, sample, showcase");
103
104    // Headers & Footers with different first page
105    doc.set_different_first_page(true);
106    doc.set_first_page_header("rdocx");
107    doc.set_first_page_footer("Feature Showcase — Cover Page");
108    doc.set_header("rdocx Feature Showcase");
109    doc.set_footer("Generated by rdocx");
110
111    // ── COVER PAGE ──
112    let bg = create_sample_png(612, 792, [20, 45, 90]);
113    doc.add_background_image(&bg, "cover_bg.png");
114
115    for _ in 0..3 {
116        doc.add_paragraph("");
117    }
118    {
119        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
120        p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
121    }
122    {
123        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
124        p.add_run("Complete Feature Showcase")
125            .size(28.0)
126            .color("FFFFFF")
127            .italic(true);
128    }
129    doc.add_paragraph("");
130    {
131        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
132        p.add_run("Every feature of the rdocx Rust library")
133            .size(13.0)
134            .color("B0C4DE");
135    }
136    {
137        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
138        p.add_run("demonstrated in a single document.")
139            .size(13.0)
140            .color("B0C4DE");
141    }
142
143    // ── TABLE OF CONTENTS ──
144    doc.add_paragraph("").page_break_before(true);
145    doc.insert_toc(doc.content_count(), 3);
146
147    // ── SECTION 1: TEXT FORMATTING ──
148    doc.add_paragraph("").page_break_before(true);
149    doc.add_paragraph("1. Text Formatting").style("Heading1");
150
151    // Paragraph Alignment
152    doc.add_paragraph("1.1 Paragraph Alignment")
153        .style("Heading2");
154    doc.add_paragraph("Left-aligned paragraph (default).")
155        .alignment(Alignment::Left);
156    doc.add_paragraph("Center-aligned paragraph.")
157        .alignment(Alignment::Center);
158    doc.add_paragraph("Right-aligned paragraph.")
159        .alignment(Alignment::Right);
160    doc.add_paragraph(
161        "Justified paragraph — this text is long enough to demonstrate how justified alignment \
162         distributes extra space across word gaps so lines fill the full width of the text area.",
163    )
164    .alignment(Alignment::Justify);
165
166    doc.add_paragraph("");
167
168    // Run Formatting
169    doc.add_paragraph("1.2 Run Formatting").style("Heading2");
170    {
171        let mut p = doc.add_paragraph("");
172        p.add_run("Bold").bold(true);
173        p.add_run(" | ");
174        p.add_run("Italic").italic(true);
175        p.add_run(" | ");
176        p.add_run("Bold Italic").bold(true).italic(true);
177        p.add_run(" | ");
178        p.add_run("Underline").underline(true);
179        p.add_run(" | ");
180        p.add_run("Strikethrough").strike(true);
181        p.add_run(" | ");
182        p.add_run("Double Strike").double_strike(true);
183    }
184    {
185        let mut p = doc.add_paragraph("");
186        p.add_run("Red").color("FF0000");
187        p.add_run(" | ");
188        p.add_run("Blue").color("0000FF");
189        p.add_run(" | ");
190        p.add_run("Green").color("00AA00");
191        p.add_run(" | ");
192        p.add_run("Highlighted").highlight("FFFF00");
193    }
194    {
195        let mut p = doc.add_paragraph("");
196        p.add_run("8pt").size(8.0);
197        p.add_run(" | ");
198        p.add_run("11pt").size(11.0);
199        p.add_run(" | ");
200        p.add_run("16pt").size(16.0);
201        p.add_run(" | ");
202        p.add_run("24pt").size(24.0);
203    }
204    {
205        let mut p = doc.add_paragraph("");
206        p.add_run("Arial").font("Arial");
207        p.add_run(" | ");
208        p.add_run("Times New Roman").font("Times New Roman");
209        p.add_run(" | ");
210        p.add_run("Courier New").font("Courier New");
211    }
212    {
213        let mut p = doc.add_paragraph("");
214        p.add_run("H");
215        p.add_run("2").subscript();
216        p.add_run("O (subscript) | E=mc");
217        p.add_run("2").superscript();
218        p.add_run(" (superscript)");
219    }
220    {
221        let mut p = doc.add_paragraph("");
222        p.add_run("ALL CAPS").all_caps(true);
223        p.add_run(" | ");
224        p.add_run("Small Caps").small_caps(true);
225        p.add_run(" | ");
226        p.add_run("Expanded").character_spacing(Length::twips(40));
227        p.add_run(" | ");
228        p.add_run("Hidden text (not visible)").hidden(true);
229    }
230
231    doc.add_paragraph("");
232
233    // Underline Styles
234    doc.add_paragraph("1.3 Underline Styles").style("Heading2");
235    {
236        let mut p = doc.add_paragraph("");
237        p.add_run("Single ")
238            .underline_style(rdocx::UnderlineStyle::Single);
239        p.add_run("Double ")
240            .underline_style(rdocx::UnderlineStyle::Double);
241        p.add_run("Thick ")
242            .underline_style(rdocx::UnderlineStyle::Thick);
243        p.add_run("Dotted ")
244            .underline_style(rdocx::UnderlineStyle::Dotted);
245        p.add_run("Dash ")
246            .underline_style(rdocx::UnderlineStyle::Dash);
247        p.add_run("Wave ")
248            .underline_style(rdocx::UnderlineStyle::Wave);
249    }
250
251    // ── SECTION 2: PARAGRAPH FORMATTING ──
252    doc.add_paragraph("").page_break_before(true);
253    doc.add_paragraph("2. Paragraph Formatting")
254        .style("Heading1");
255
256    doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
257    doc.add_paragraph("Paragraph with light green shading")
258        .shading("E2EFDA");
259    doc.add_paragraph("Paragraph with bottom border")
260        .border_bottom(BorderStyle::Single, 6, "2E75B6");
261    doc.add_paragraph("Paragraph with all borders (red)")
262        .border_all(BorderStyle::Single, 4, "FF0000");
263
264    doc.add_paragraph("2.2 Indentation").style("Heading2");
265    doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
266        .indent_left(Length::inches(1.0))
267        .hanging_indent(Length::inches(0.5));
268    doc.add_paragraph("0.5-inch first-line indent on this paragraph")
269        .first_line_indent(Length::inches(0.5));
270    doc.add_paragraph("Both left and right indent (0.75 inches each)")
271        .indent_left(Length::inches(0.75))
272        .indent_right(Length::inches(0.75));
273
274    doc.add_paragraph("2.3 Spacing & Line Height")
275        .style("Heading2");
276    doc.add_paragraph("Extra space before (24pt) and after (12pt)")
277        .space_before(Length::pt(24.0))
278        .space_after(Length::pt(12.0));
279    doc.add_paragraph(
280        "Double line spacing paragraph. This text should have extra vertical space between \
281         lines to demonstrate line_spacing_multiple(2.0).",
282    )
283    .line_spacing_multiple(2.0);
284    doc.add_paragraph("Exact 20pt line spacing (fixed height).")
285        .line_spacing(20.0);
286
287    doc.add_paragraph("2.4 Pagination Controls")
288        .style("Heading2");
289    doc.add_paragraph("keep_with_next — this paragraph stays with the next")
290        .keep_with_next(true);
291    doc.add_paragraph("(This paragraph was kept with the one above.)");
292    doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
293        .keep_together(true);
294    doc.add_paragraph("widow_control — prevents widow/orphan lines")
295        .widow_control(true);
296
297    // ── SECTION 3: LISTS ──
298    doc.add_paragraph("").page_break_before(true);
299    doc.add_paragraph("3. Lists").style("Heading1");
300
301    doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
302    doc.add_bullet_list_item("First item", 0);
303    doc.add_bullet_list_item("Second item", 0);
304    doc.add_bullet_list_item("Nested level 1", 1);
305    doc.add_bullet_list_item("Nested level 2", 2);
306    doc.add_bullet_list_item("Back to level 1", 1);
307    doc.add_bullet_list_item("Third item", 0);
308
309    doc.add_paragraph("");
310
311    doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
312    doc.add_numbered_list_item("First numbered item", 0);
313    doc.add_numbered_list_item("Second numbered item", 0);
314    doc.add_numbered_list_item("Sub-item A", 1);
315    doc.add_numbered_list_item("Sub-item B", 1);
316    doc.add_numbered_list_item("Third numbered item", 0);
317
318    // ── SECTION 4: TAB STOPS ──
319    doc.add_paragraph("");
320    doc.add_paragraph("4. Tab Stops").style("Heading1");
321
322    doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
323    doc.add_paragraph("Left\tCenter\tRight\tDecimal")
324        .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
325        .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
326        .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
327        .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
328
329    doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
330    doc.add_paragraph("Item\t\tPrice")
331        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
332        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
333        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
334    doc.add_paragraph("Widget\t\t$19.99")
335        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
336        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
337        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
338    doc.add_paragraph("Gadget\t\t$249.50")
339        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
340        .add_tab_stop_with_leader(
341            TabAlignment::Right,
342            Length::inches(4.0),
343            TabLeader::Underscore,
344        )
345        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
346
347    // ── SECTION 5: TABLES ──
348    doc.add_paragraph("").page_break_before(true);
349    doc.add_paragraph("5. Tables").style("Heading1");
350
351    doc.add_paragraph("5.1 Basic Table").style("Heading2");
352    {
353        let mut tbl = doc
354            .add_table(4, 3)
355            .borders(BorderStyle::Single, 4, "000000");
356        for col in 0..3 {
357            tbl.cell(0, col).unwrap().shading("2E75B6");
358        }
359        tbl.cell(0, 0).unwrap().set_text("Name");
360        tbl.cell(0, 1).unwrap().set_text("Role");
361        tbl.cell(0, 2).unwrap().set_text("Location");
362        tbl.cell(1, 0).unwrap().set_text("Walter White");
363        tbl.cell(1, 1).unwrap().set_text("CEO");
364        tbl.cell(1, 2).unwrap().set_text("Albuquerque");
365        tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
366        tbl.cell(2, 1).unwrap().set_text("CTO");
367        tbl.cell(2, 2).unwrap().set_text("Remote");
368        tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
369        tbl.cell(3, 1).unwrap().set_text("Security");
370        tbl.cell(3, 2).unwrap().set_text("Washington");
371    }
372
373    doc.add_paragraph("");
374
375    doc.add_paragraph("5.2 Column Span & Cell Shading")
376        .style("Heading2");
377    {
378        let mut tbl = doc
379            .add_table(3, 4)
380            .borders(BorderStyle::Single, 4, "000000")
381            .width_pct(100.0);
382        tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
383        tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
384        tbl.cell(1, 0).unwrap().set_text("Region");
385        tbl.cell(1, 0).unwrap().shading("D6E4F0");
386        tbl.cell(1, 1).unwrap().set_text("Q1");
387        tbl.cell(1, 1).unwrap().shading("D6E4F0");
388        tbl.cell(1, 2).unwrap().set_text("Q2");
389        tbl.cell(1, 2).unwrap().shading("D6E4F0");
390        tbl.cell(1, 3).unwrap().set_text("Total");
391        tbl.cell(1, 3).unwrap().shading("D6E4F0");
392        tbl.cell(2, 0).unwrap().set_text("Americas");
393        tbl.cell(2, 1).unwrap().set_text("$2.4M");
394        tbl.cell(2, 2).unwrap().set_text("$2.7M");
395        tbl.cell(2, 3).unwrap().set_text("$5.1M");
396    }
397
398    doc.add_paragraph("");
399
400    doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
401    {
402        let mut tbl = doc
403            .add_table(4, 3)
404            .borders(BorderStyle::Single, 4, "000000");
405        tbl.cell(0, 0).unwrap().set_text("Category");
406        tbl.cell(0, 0).unwrap().shading("E2EFDA");
407        tbl.cell(0, 1).unwrap().set_text("Item");
408        tbl.cell(0, 1).unwrap().shading("E2EFDA");
409        tbl.cell(0, 2).unwrap().set_text("Price");
410        tbl.cell(0, 2).unwrap().shading("E2EFDA");
411        tbl.cell(1, 0).unwrap().set_text("Hardware");
412        tbl.cell(1, 0).unwrap().v_merge_restart();
413        tbl.cell(1, 1).unwrap().set_text("Laptop");
414        tbl.cell(1, 2).unwrap().set_text("$1,200");
415        tbl.cell(2, 0).unwrap().v_merge_continue();
416        tbl.cell(2, 1).unwrap().set_text("Monitor");
417        tbl.cell(2, 2).unwrap().set_text("$450");
418        tbl.cell(3, 0).unwrap().set_text("Software");
419        tbl.cell(3, 1).unwrap().set_text("IDE License");
420        tbl.cell(3, 2).unwrap().set_text("$200/yr");
421    }
422
423    doc.add_paragraph("");
424
425    doc.add_paragraph("5.4 Nested Table").style("Heading2");
426    {
427        let mut tbl = doc
428            .add_table(2, 2)
429            .borders(BorderStyle::Single, 6, "2E75B6");
430        tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
431        tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
432        tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
433        {
434            let mut cell = tbl.cell(1, 1).unwrap();
435            cell.set_text("Nested table below:");
436            let mut nested = cell
437                .add_table(2, 2)
438                .borders(BorderStyle::Single, 2, "FF6600");
439            nested.cell(0, 0).unwrap().set_text("A");
440            nested.cell(0, 1).unwrap().set_text("B");
441            nested.cell(1, 0).unwrap().set_text("C");
442            nested.cell(1, 1).unwrap().set_text("D");
443        }
444    }
445
446    doc.add_paragraph("");
447
448    doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
449        .style("Heading2");
450    {
451        let mut tbl = doc
452            .add_table(2, 3)
453            .borders(BorderStyle::Single, 4, "666666");
454        tbl.row(0).unwrap().height(Length::pt(50.0)).header();
455        tbl.cell(0, 0).unwrap().set_text("Top");
456        tbl.cell(0, 0)
457            .unwrap()
458            .vertical_alignment(VerticalAlignment::Top)
459            .shading("FFF2CC");
460        tbl.cell(0, 1).unwrap().set_text("Center");
461        tbl.cell(0, 1)
462            .unwrap()
463            .vertical_alignment(VerticalAlignment::Center)
464            .shading("D9E2F3");
465        tbl.cell(0, 2).unwrap().set_text("Bottom");
466        tbl.cell(0, 2)
467            .unwrap()
468            .vertical_alignment(VerticalAlignment::Bottom)
469            .shading("E2EFDA");
470        tbl.row(1).unwrap().cant_split();
471        tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
472        tbl.cell(1, 0).unwrap().no_wrap();
473        tbl.cell(1, 1).unwrap().set_text("Fixed width");
474        tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
475        tbl.cell(1, 2).unwrap().set_text("Normal");
476    }
477
478    // ── SECTION 6: IMAGES ──
479    doc.add_paragraph("").page_break_before(true);
480    doc.add_paragraph("6. Images").style("Heading1");
481
482    doc.add_paragraph("6.1 Inline Image").style("Heading2");
483    doc.add_paragraph("A blue gradient image below:");
484    let img = create_sample_png(200, 50, [0, 80, 200]);
485    doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
486
487    doc.add_paragraph("");
488    doc.add_paragraph("6.2 Header Image").style("Heading2");
489    let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
490    doc.set_header_image(
491        &hdr_img,
492        "header_logo.png",
493        Length::inches(2.0),
494        Length::inches(0.2),
495    );
496    doc.add_paragraph("The header now contains an inline image (check the top of this page).");
497
498    doc.add_paragraph("");
499    doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
500
501    // ── SECTION 7: CONTENT MANIPULATION ──
502    doc.add_paragraph("").page_break_before(true);
503    doc.add_paragraph("7. Content Manipulation")
504        .style("Heading1");
505
506    doc.add_paragraph("7.1 Placeholder Replacement")
507        .style("Heading2");
508    doc.add_paragraph("Customer: {{customer}}");
509    doc.add_paragraph("Date: {{date}}");
510    doc.add_paragraph("Reference: {{ref_number}}");
511    {
512        let mut tbl = doc
513            .add_table(2, 2)
514            .borders(BorderStyle::Single, 4, "000000");
515        tbl.cell(0, 0).unwrap().set_text("Project");
516        tbl.cell(0, 0).unwrap().shading("D6E4F0");
517        tbl.cell(0, 1).unwrap().set_text("{{project}}");
518        tbl.cell(1, 0).unwrap().set_text("Status");
519        tbl.cell(1, 0).unwrap().shading("D6E4F0");
520        tbl.cell(1, 1).unwrap().set_text("{{status}}");
521    }
522
523    let mut replacements = HashMap::new();
524    replacements.insert("{{customer}}", "Tensorbee Inc.");
525    replacements.insert("{{date}}", "February 22, 2026");
526    replacements.insert("{{ref_number}}", "TB-2026-001");
527    replacements.insert("{{project}}", "Infrastructure Upgrade");
528    replacements.insert("{{status}}", "In Progress");
529    let n = doc.replace_all(&replacements);
530    doc.add_paragraph(&format!("({n} placeholders replaced above)"));
531
532    doc.add_paragraph("");
533
534    doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
535    doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
536    let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
537    doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
538
539    doc.add_paragraph("");
540
541    doc.add_paragraph("7.3 Content Insertion").style("Heading2");
542    doc.add_paragraph("Section A: First content.");
543    doc.add_paragraph("Section C: Third content.");
544    if let Some(idx) = doc.find_content_index("Section C") {
545        doc.insert_paragraph(
546            idx,
547            "Section B: Inserted between A and C via find_content_index().",
548        );
549    }
550
551    // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
552    doc.add_paragraph("").section_break(SectionBreak::NextPage);
553    doc.add_paragraph("8. Section Breaks & Orientation")
554        .style("Heading1");
555    doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
556
557    {
558        let mut tbl = doc
559            .add_table(3, 7)
560            .borders(BorderStyle::Single, 4, "2E75B6");
561        let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
562        for (c, h) in headers.iter().enumerate() {
563            tbl.cell(0, c).unwrap().set_text(h);
564            tbl.cell(0, c).unwrap().shading("2E75B6");
565        }
566        let data = [
567            [
568                "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
569            ],
570            [
571                "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
572            ],
573        ];
574        for (r, row) in data.iter().enumerate() {
575            for (c, v) in row.iter().enumerate() {
576                tbl.cell(r + 1, c).unwrap().set_text(v);
577            }
578        }
579    }
580
581    doc.add_paragraph("")
582        .section_break(SectionBreak::NextPage)
583        .section_landscape();
584
585    // ── SECTION 9: CUSTOM STYLES ──
586    doc.add_paragraph("9. Custom Styles").style("Heading1");
587    doc.add_style(
588        StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
589            .based_on("Normal")
590            .paragraph_properties({
591                let mut ppr = rdocx_oxml::properties::CT_PPr::default();
592                ppr.shading = Some(rdocx_oxml::properties::CT_Shd {
593                    val: "clear".to_string(),
594                    color: None,
595                    fill: Some("FFF2CC".to_string()),
596                });
597                ppr
598            })
599            .run_properties({
600                let mut rpr = rdocx_oxml::properties::CT_RPr::default();
601                rpr.bold = Some(true);
602                rpr.color = Some("C45911".to_string());
603                rpr
604            }),
605    );
606    doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
607        .style("CustomHighlight");
608
609    doc.add_paragraph("");
610
611    // ── SECTION 10: DOCUMENT INTELLIGENCE ──
612    doc.add_paragraph("10. Document Intelligence API")
613        .style("Heading1");
614
615    let wc = doc.word_count();
616    let hc = doc.headings().len();
617    let ic = doc.images().len();
618    let lc = doc.links().len();
619    doc.add_paragraph(&format!("Word count: {wc}"));
620    doc.add_paragraph(&format!("Heading count: {hc}"));
621    doc.add_paragraph(&format!("Image count: {ic}"));
622    doc.add_paragraph(&format!("Link count: {lc}"));
623
624    let outline = doc.document_outline();
625    doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
626
627    let issues = doc.audit_accessibility();
628    doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
629    for issue in &issues {
630        doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
631    }
632
633    // ── SECTION 11: DOCUMENT MERGING ──
634    doc.add_paragraph("");
635    doc.add_paragraph("11. Document Merging").style("Heading1");
636    {
637        let mut other = Document::new();
638        other.add_paragraph("This paragraph was merged from another document using append().");
639        doc.append(&other);
640    }
641
642    doc.add_paragraph("");
643
644    // ── SUMMARY ──
645    doc.add_paragraph("Summary of Demonstrated Features")
646        .style("Heading1");
647    let features = [
648        "Page setup: size, margins, header/footer distance, gutter",
649        "Document metadata: title, author, subject, keywords",
650        "Headers and footers: text, images, different first page",
651        "Background images (full-page behind text)",
652        "Table of Contents generation with bookmarks",
653        "Text formatting: bold, italic, underline styles, strike, color, size, font",
654        "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
655        "Paragraph formatting: alignment, borders, shading, spacing, indentation",
656        "Pagination controls: keep-with-next, keep-together, widow control",
657        "Bullet and numbered lists with nesting",
658        "Tab stops with dot/underscore/hyphen leaders",
659        "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
660        "Row properties: header rows, exact height, cant-split",
661        "Cell properties: width, no-wrap, vertical alignment",
662        "Inline images and header images",
663        "Placeholder replacement (single and batch)",
664        "Regex-based find and replace",
665        "Content insertion at specific positions",
666        "Section breaks with mixed portrait/landscape",
667        "Custom paragraph and character styles",
668        "Document intelligence: word count, headings, outline, images, links",
669        "Accessibility audit",
670        "Document merging (append)",
671        "Export: DOCX, PDF, HTML, Markdown",
672    ];
673    for f in &features {
674        doc.add_bullet_list_item(f, 0);
675    }
676    doc.add_paragraph("");
677    doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
678        .alignment(Alignment::Center)
679        .shading("E2EFDA")
680        .border_all(BorderStyle::Single, 2, "00AA00");
681
682    doc
683}
684
685// =============================================================================
686// 2. PROPOSAL DOCUMENT — Deep navy + gold scheme
687// =============================================================================
688fn generate_proposal(_samples_dir: &Path) -> Document {
689    let mut doc = Document::new();
690
691    // Colors: Navy #1B2A4A, Gold #C5922E, Light #F4F1EB
692    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
693    doc.set_margins(
694        Length::inches(1.0),
695        Length::inches(1.0),
696        Length::inches(1.0),
697        Length::inches(1.0),
698    );
699    doc.set_title("AI Platform Modernization Proposal");
700    doc.set_author("Walter White");
701    doc.set_subject("Technology Proposal");
702    doc.set_keywords("proposal, AI, modernization, Tensorbee");
703
704    // Banner header
705    let logo_img = create_logo_png(220, 48);
706    let banner = build_header_banner_xml(
707        "rId1",
708        &BannerOpts {
709            bg_color: "1B2A4A",
710            banner_width: 7772400,
711            banner_height: 914400,
712            logo_width: 2011680,
713            logo_height: 438912,
714            logo_x_offset: 295125,
715            logo_y_offset: 237744,
716        },
717    );
718    doc.set_raw_header_with_images(
719        banner,
720        &[("rId1", &logo_img, "logo.png")],
721        rdocx_oxml::header_footer::HdrFtrType::Default,
722    );
723    doc.set_different_first_page(true);
724    let cover_banner = build_header_banner_xml(
725        "rId1",
726        &BannerOpts {
727            bg_color: "C5922E",
728            banner_width: 7772400,
729            banner_height: 914400,
730            logo_width: 2011680,
731            logo_height: 438912,
732            logo_x_offset: 295125,
733            logo_y_offset: 237744,
734        },
735    );
736    doc.set_raw_header_with_images(
737        cover_banner,
738        &[("rId1", &logo_img, "logo.png")],
739        rdocx_oxml::header_footer::HdrFtrType::First,
740    );
741    doc.set_margins(
742        Length::twips(2292),
743        Length::twips(1440),
744        Length::twips(1440),
745        Length::twips(1440),
746    );
747    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
748    doc.set_footer("Tensorbee — Confidential");
749
750    // Custom styles
751    doc.add_style(
752        StyleBuilder::paragraph("ProposalTitle", "Proposal Title")
753            .based_on("Normal")
754            .run_properties({
755                let mut rpr = rdocx_oxml::properties::CT_RPr::default();
756                rpr.bold = Some(true);
757                rpr.font_ascii = Some("Georgia".to_string());
758                rpr.font_hansi = Some("Georgia".to_string());
759                rpr.sz = Some(rdocx_oxml::HalfPoint(56)); // half-points → 28pt
760                rpr.color = Some("1B2A4A".to_string());
761                rpr
762            }),
763    );
764
765    // ── Cover Page ──
766    for _ in 0..4 {
767        doc.add_paragraph("");
768    }
769    doc.add_paragraph("AI Platform Modernization")
770        .style("ProposalTitle")
771        .alignment(Alignment::Center);
772    doc.add_paragraph("")
773        .alignment(Alignment::Center)
774        .border_bottom(BorderStyle::Single, 8, "C5922E");
775    {
776        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
777        p.add_run("Prepared for Global Financial Corp.")
778            .size(14.0)
779            .color("666666")
780            .italic(true);
781    }
782    {
783        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
784        p.add_run("Prepared by: Walter White, CEO — Tensorbee")
785            .size(12.0)
786            .color("888888");
787    }
788    {
789        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
790        p.add_run("February 22, 2026").size(12.0).color("888888");
791    }
792
793    // ── TOC ──
794    doc.add_paragraph("").page_break_before(true);
795    doc.add_paragraph("Table of Contents").style("Heading1");
796    // We'll add a manual-style TOC since the insert_toc goes at a specific position
797    doc.insert_toc(doc.content_count(), 2);
798
799    // ── Executive Summary ──
800    doc.add_paragraph("").page_break_before(true);
801    doc.add_paragraph("Executive Summary").style("Heading1");
802    doc.add_paragraph(
803        "Tensorbee proposes a comprehensive modernization of Global Financial Corp's AI platform, \
804         transitioning from legacy batch-processing systems to a real-time inference architecture. \
805         This transformation will reduce model deployment time from weeks to hours, improve \
806         prediction accuracy by an estimated 23%, and deliver $4.2M in annual operational savings.",
807    )
808    .first_line_indent(Length::inches(0.3));
809    doc.add_paragraph(
810        "The project spans three phases over 18 months, with a total investment of $2.8M. \
811         Tensorbee brings deep expertise in ML infrastructure, having successfully completed \
812         similar transformations for three Fortune 500 financial institutions.",
813    )
814    .first_line_indent(Length::inches(0.3));
815
816    // Key metrics table
817    doc.add_paragraph("");
818    {
819        let mut tbl = doc
820            .add_table(5, 2)
821            .borders(BorderStyle::Single, 4, "1B2A4A")
822            .width_pct(80.0);
823        tbl.cell(0, 0).unwrap().set_text("Key Metric");
824        tbl.cell(0, 0).unwrap().shading("1B2A4A");
825        tbl.cell(0, 1).unwrap().set_text("Value");
826        tbl.cell(0, 1).unwrap().shading("1B2A4A");
827        let rows = [
828            ("Total Investment", "$2.8M"),
829            ("Annual Savings", "$4.2M"),
830            ("ROI (Year 1)", "150%"),
831            ("Timeline", "18 months"),
832        ];
833        for (i, (k, v)) in rows.iter().enumerate() {
834            tbl.cell(i + 1, 0).unwrap().set_text(k);
835            if i % 2 == 0 {
836                tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
837            }
838            tbl.cell(i + 1, 1).unwrap().set_text(v);
839            if i % 2 == 0 {
840                tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
841            }
842        }
843    }
844
845    // ── Problem Statement ──
846    doc.add_paragraph("").page_break_before(true);
847    doc.add_paragraph("Problem Statement").style("Heading1");
848    doc.add_paragraph(
849        "Global Financial Corp's current AI infrastructure faces three critical challenges:",
850    );
851    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);
852    doc.add_numbered_list_item("Scalability Constraints — The batch processing architecture cannot handle real-time inference demands during peak trading hours.", 0);
853    doc.add_numbered_list_item("Technical Debt — Legacy Python 2.7 codebases and manual deployment scripts create reliability risks and slow iteration speed.", 0);
854
855    // ── Proposed Solution ──
856    doc.add_paragraph("").page_break_before(true);
857    doc.add_paragraph("Proposed Solution").style("Heading1");
858
859    doc.add_paragraph("Phase 1: Foundation (Months 1-6)")
860        .style("Heading2");
861    doc.add_bullet_list_item("Deploy Kubernetes-based ML serving infrastructure", 0);
862    doc.add_bullet_list_item("Implement CI/CD pipeline for model deployment", 0);
863    doc.add_bullet_list_item("Migrate top 5 production models to new platform", 0);
864
865    doc.add_paragraph("Phase 2: Expansion (Months 7-12)")
866        .style("Heading2");
867    doc.add_bullet_list_item(
868        "Real-time feature engineering pipeline (Apache Kafka + Flink)",
869        0,
870    );
871    doc.add_bullet_list_item("A/B testing framework for model variants", 0);
872    doc.add_bullet_list_item("Automated model monitoring and drift detection", 0);
873
874    doc.add_paragraph("Phase 3: Optimization (Months 13-18)")
875        .style("Heading2");
876    doc.add_bullet_list_item("GPU inference optimization (TensorRT/ONNX Runtime)", 0);
877    doc.add_bullet_list_item("Multi-region deployment for latency reduction", 0);
878    doc.add_bullet_list_item(
879        "Self-service model deployment portal for data science teams",
880        0,
881    );
882
883    // ── Budget ──
884    doc.add_paragraph("Budget Breakdown").style("Heading1");
885    {
886        let mut tbl = doc
887            .add_table(6, 3)
888            .borders(BorderStyle::Single, 4, "1B2A4A")
889            .width_pct(100.0);
890        tbl.cell(0, 0).unwrap().set_text("Category");
891        tbl.cell(0, 0).unwrap().shading("1B2A4A");
892        tbl.cell(0, 1).unwrap().set_text("Cost");
893        tbl.cell(0, 1).unwrap().shading("1B2A4A");
894        tbl.cell(0, 2).unwrap().set_text("Notes");
895        tbl.cell(0, 2).unwrap().shading("1B2A4A");
896        let items = [
897            (
898                "Infrastructure",
899                "$450,000",
900                "Cloud compute + GPU instances",
901            ),
902            ("Engineering Services", "$1,600,000", "12 FTE x 18 months"),
903            (
904                "Software Licenses",
905                "$350,000",
906                "Kafka, monitoring, CI/CD tools",
907            ),
908            (
909                "Training & Enablement",
910                "$200,000",
911                "Team training, documentation",
912            ),
913            ("Contingency (10%)", "$200,000", "Risk buffer"),
914        ];
915        for (i, (cat, cost, note)) in items.iter().enumerate() {
916            tbl.cell(i + 1, 0).unwrap().set_text(cat);
917            tbl.cell(i + 1, 1).unwrap().set_text(cost);
918            tbl.cell(i + 1, 2).unwrap().set_text(note);
919            if i % 2 == 0 {
920                tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
921                tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
922                tbl.cell(i + 1, 2).unwrap().shading("F4F1EB");
923            }
924        }
925    }
926
927    // ── Closing ──
928    doc.add_paragraph("");
929    doc.add_paragraph("Next Steps").style("Heading1");
930    doc.add_numbered_list_item(
931        "Schedule technical deep-dive with GFC engineering team (Week 1)",
932        0,
933    );
934    doc.add_numbered_list_item("Finalize scope and sign SOW (Week 2-3)", 0);
935    doc.add_numbered_list_item("Kick off Phase 1 with joint planning session (Week 4)", 0);
936
937    doc.add_paragraph("");
938    {
939        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
940        p.add_run("Walter White")
941            .bold(true)
942            .size(14.0)
943            .color("1B2A4A");
944    }
945    {
946        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
947        p.add_run("CEO, Tensorbee | walter@tensorbee.com")
948            .size(10.0)
949            .color("888888");
950    }
951
952    doc
953}
954
955// =============================================================================
956// 3. QUOTE / BILL OF MATERIALS — Teal + orange scheme
957// =============================================================================
958fn generate_quote(_samples_dir: &Path) -> Document {
959    let mut doc = Document::new();
960
961    // Colors: Teal #008B8B, Dark #1A3C3C, Orange #E07020, Light #F0F8F8
962    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
963    doc.set_margins(
964        Length::inches(0.75),
965        Length::inches(0.75),
966        Length::inches(0.75),
967        Length::inches(0.75),
968    );
969    doc.set_title("Quotation QT-2026-0147");
970    doc.set_author("Walter White");
971    doc.set_keywords("quote, BOM, Tensorbee");
972
973    doc.set_header("Tensorbee — Quotation");
974    doc.set_footer("QT-2026-0147 | Page");
975
976    // ── Header Block ──
977    {
978        let mut p = doc.add_paragraph("").alignment(Alignment::Left);
979        p.add_run("TENSORBEE")
980            .bold(true)
981            .size(28.0)
982            .color("008B8B")
983            .font("Helvetica");
984    }
985    doc.add_paragraph("123 Innovation Drive, Suite 400")
986        .alignment(Alignment::Left);
987    doc.add_paragraph("San Francisco, CA 94105 | +1 (415) 555-0199")
988        .alignment(Alignment::Left);
989    doc.add_paragraph("")
990        .border_bottom(BorderStyle::Single, 8, "008B8B");
991
992    // Quote meta
993    doc.add_paragraph("");
994    {
995        let mut tbl = doc.add_table(4, 4).width_pct(100.0);
996        tbl.cell(0, 0).unwrap().set_text("QUOTATION");
997        tbl.cell(0, 0).unwrap().shading("008B8B").grid_span(2);
998        tbl.cell(0, 2).unwrap().set_text("DATE");
999        tbl.cell(0, 2).unwrap().shading("008B8B");
1000        tbl.cell(0, 3).unwrap().set_text("VALID UNTIL");
1001        tbl.cell(0, 3).unwrap().shading("008B8B");
1002        tbl.cell(1, 0).unwrap().set_text("Quote #:");
1003        tbl.cell(1, 1).unwrap().set_text("QT-2026-0147");
1004        tbl.cell(1, 2).unwrap().set_text("Feb 22, 2026");
1005        tbl.cell(1, 3).unwrap().set_text("Mar 22, 2026");
1006        tbl.cell(2, 0).unwrap().set_text("Prepared by:");
1007        tbl.cell(2, 1).unwrap().set_text("Walter White");
1008        tbl.cell(2, 2).unwrap().set_text("Payment:");
1009        tbl.cell(2, 3).unwrap().set_text("Net 30");
1010        tbl.cell(3, 0).unwrap().set_text("Customer:");
1011        tbl.cell(3, 1).unwrap().set_text("Meridian Dynamics LLC");
1012        tbl.cell(3, 2).unwrap().set_text("Currency:");
1013        tbl.cell(3, 3).unwrap().set_text("USD");
1014    }
1015
1016    doc.add_paragraph("");
1017
1018    // ── Bill of Materials ──
1019    {
1020        let mut p = doc.add_paragraph("");
1021        p.add_run("Bill of Materials")
1022            .bold(true)
1023            .size(16.0)
1024            .color("1A3C3C");
1025    }
1026    doc.add_paragraph("");
1027
1028    {
1029        let mut tbl = doc
1030            .add_table(10, 6)
1031            .borders(BorderStyle::Single, 2, "008B8B")
1032            .width_pct(100.0)
1033            .layout_fixed();
1034        // Headers
1035        let hdrs = [
1036            "#",
1037            "Part Number",
1038            "Description",
1039            "Qty",
1040            "Unit Price",
1041            "Total",
1042        ];
1043        for (c, h) in hdrs.iter().enumerate() {
1044            tbl.cell(0, c).unwrap().set_text(h);
1045            tbl.cell(0, c).unwrap().shading("008B8B");
1046        }
1047
1048        let items: Vec<(&str, &str, &str, &str, &str)> = vec![
1049            (
1050                "TB-GPU-A100",
1051                "NVIDIA A100 80GB GPU",
1052                "4",
1053                "$12,500.00",
1054                "$50,000.00",
1055            ),
1056            (
1057                "TB-SRV-R750",
1058                "Dell R750xa Server Chassis",
1059                "2",
1060                "$8,200.00",
1061                "$16,400.00",
1062            ),
1063            (
1064                "TB-RAM-256G",
1065                "256GB DDR5 ECC Memory Module",
1066                "8",
1067                "$890.00",
1068                "$7,120.00",
1069            ),
1070            (
1071                "TB-SSD-3840",
1072                "3.84TB NVMe U.2 SSD",
1073                "8",
1074                "$1,150.00",
1075                "$9,200.00",
1076            ),
1077            (
1078                "TB-NET-CX7",
1079                "ConnectX-7 200GbE NIC",
1080                "4",
1081                "$1,800.00",
1082                "$7,200.00",
1083            ),
1084            (
1085                "TB-SW-48P",
1086                "48-Port 100GbE Switch",
1087                "1",
1088                "$22,000.00",
1089                "$22,000.00",
1090            ),
1091            (
1092                "TB-CAB-RACK",
1093                "42U Server Rack + PDU",
1094                "1",
1095                "$4,500.00",
1096                "$4,500.00",
1097            ),
1098            (
1099                "TB-SVC-INST",
1100                "Installation & Configuration",
1101                "1",
1102                "$8,500.00",
1103                "$8,500.00",
1104            ),
1105            (
1106                "TB-SVC-SUPP",
1107                "3-Year Premium Support",
1108                "1",
1109                "$15,000.00",
1110                "$15,000.00",
1111            ),
1112        ];
1113
1114        for (i, (pn, desc, qty, unit, total)) in items.iter().enumerate() {
1115            let row = i + 1;
1116            tbl.cell(row, 0).unwrap().set_text(&format!("{}", i + 1));
1117            tbl.cell(row, 1).unwrap().set_text(pn);
1118            tbl.cell(row, 2).unwrap().set_text(desc);
1119            tbl.cell(row, 3).unwrap().set_text(qty);
1120            tbl.cell(row, 4).unwrap().set_text(unit);
1121            tbl.cell(row, 5).unwrap().set_text(total);
1122            if i % 2 == 0 {
1123                for c in 0..6 {
1124                    tbl.cell(row, c).unwrap().shading("F0F8F8");
1125                }
1126            }
1127        }
1128    }
1129
1130    doc.add_paragraph("");
1131
1132    // ── Totals ──
1133    {
1134        let mut tbl = doc
1135            .add_table(4, 2)
1136            .borders(BorderStyle::Single, 2, "008B8B")
1137            .width(Length::inches(3.5))
1138            .alignment(Alignment::Right);
1139        tbl.cell(0, 0).unwrap().set_text("Subtotal");
1140        tbl.cell(0, 1).unwrap().set_text("$139,920.00");
1141        tbl.cell(1, 0).unwrap().set_text("Shipping & Handling");
1142        tbl.cell(1, 1).unwrap().set_text("$2,500.00");
1143        tbl.cell(2, 0).unwrap().set_text("Tax (8.625%)");
1144        tbl.cell(2, 1).unwrap().set_text("$12,068.10");
1145        tbl.cell(3, 0).unwrap().set_text("TOTAL");
1146        tbl.cell(3, 0).unwrap().shading("E07020");
1147        tbl.cell(3, 1).unwrap().set_text("$154,488.10");
1148        tbl.cell(3, 1).unwrap().shading("E07020");
1149    }
1150
1151    doc.add_paragraph("");
1152
1153    // ── Terms & Conditions ──
1154    {
1155        let mut p = doc.add_paragraph("");
1156        p.add_run("Terms & Conditions")
1157            .bold(true)
1158            .size(14.0)
1159            .color("1A3C3C");
1160    }
1161    doc.add_numbered_list_item(
1162        "This quotation is valid for 30 calendar days from the date of issue.",
1163        0,
1164    );
1165    doc.add_numbered_list_item(
1166        "All prices are in USD and exclusive of applicable taxes unless stated.",
1167        0,
1168    );
1169    doc.add_numbered_list_item("Standard lead time is 4-6 weeks from PO receipt.", 0);
1170    doc.add_numbered_list_item(
1171        "Payment terms: Net 30 from invoice date. 2% discount for payment within 10 days.",
1172        0,
1173    );
1174    doc.add_numbered_list_item(
1175        "Warranty: 3-year manufacturer warranty on all hardware components.",
1176        0,
1177    );
1178    doc.add_numbered_list_item(
1179        "Returns subject to 15% restocking fee if initiated after 14 days.",
1180        0,
1181    );
1182
1183    doc.add_paragraph("");
1184    doc.add_paragraph("")
1185        .border_bottom(BorderStyle::Single, 4, "008B8B");
1186    doc.add_paragraph("");
1187    {
1188        let mut p = doc.add_paragraph("");
1189        p.add_run("Accepted by: ").bold(true);
1190        p.add_run("___________________________ Date: ___________");
1191    }
1192    {
1193        let mut p = doc.add_paragraph("");
1194        p.add_run("Print Name: ").bold(true);
1195        p.add_run("___________________________ Title: ___________");
1196    }
1197
1198    doc
1199}
1200
1201// =============================================================================
1202// 4. INVOICE — Crimson + charcoal scheme
1203// =============================================================================
1204fn generate_invoice(_samples_dir: &Path) -> Document {
1205    let mut doc = Document::new();
1206
1207    // Colors: Crimson #B22222, Charcoal #333333, Light #FAF0F0
1208    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1209    doc.set_margins(
1210        Length::inches(0.75),
1211        Length::inches(0.75),
1212        Length::inches(0.75),
1213        Length::inches(0.75),
1214    );
1215    doc.set_title("Invoice INV-2026-0392");
1216    doc.set_author("Walter White");
1217    doc.set_keywords("invoice, Tensorbee");
1218
1219    doc.set_footer("Tensorbee — Thank you for your business!");
1220
1221    // ── Company & Invoice Header ──
1222    {
1223        let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1224        // Company name left, INVOICE right
1225        {
1226            let mut cell = tbl.cell(0, 0).unwrap();
1227            let mut p = cell.add_paragraph("");
1228            p.add_run("TENSORBEE")
1229                .bold(true)
1230                .size(32.0)
1231                .color("B22222")
1232                .font("Helvetica");
1233        }
1234        {
1235            let mut cell = tbl.cell(0, 1).unwrap();
1236            let mut p = cell.add_paragraph("");
1237            p.add_run("INVOICE").bold(true).size(32.0).color("333333");
1238        }
1239        tbl.cell(1, 0)
1240            .unwrap()
1241            .set_text("123 Innovation Drive, Suite 400");
1242        tbl.cell(1, 1).unwrap().set_text("Invoice #: INV-2026-0392");
1243        tbl.cell(2, 0).unwrap().set_text("San Francisco, CA 94105");
1244        tbl.cell(2, 1).unwrap().set_text("Date: February 22, 2026");
1245        tbl.cell(3, 0).unwrap().set_text("walter@tensorbee.com");
1246        tbl.cell(3, 1).unwrap().set_text("Due Date: March 24, 2026");
1247    }
1248
1249    doc.add_paragraph("")
1250        .border_bottom(BorderStyle::Thick, 8, "B22222");
1251    doc.add_paragraph("");
1252
1253    // ── Bill To / Ship To ──
1254    {
1255        let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1256        tbl.cell(0, 0).unwrap().set_text("BILL TO");
1257        tbl.cell(0, 0).unwrap().shading("B22222");
1258        tbl.cell(0, 1).unwrap().set_text("SHIP TO");
1259        tbl.cell(0, 1).unwrap().shading("B22222");
1260        tbl.cell(1, 0).unwrap().set_text("Meridian Dynamics LLC");
1261        tbl.cell(1, 1).unwrap().set_text("Meridian Dynamics LLC");
1262        tbl.cell(2, 0).unwrap().set_text("456 Enterprise Blvd");
1263        tbl.cell(2, 1).unwrap().set_text("Attn: Server Room B");
1264        tbl.cell(3, 0).unwrap().set_text("Austin, TX 78701");
1265        tbl.cell(3, 1)
1266            .unwrap()
1267            .set_text("456 Enterprise Blvd, Austin, TX 78701");
1268    }
1269
1270    doc.add_paragraph("");
1271
1272    // ── Line Items ──
1273    {
1274        let mut tbl = doc
1275            .add_table(8, 5)
1276            .borders(BorderStyle::Single, 2, "B22222")
1277            .width_pct(100.0);
1278        let hdrs = ["Description", "Qty", "Unit Price", "Tax", "Amount"];
1279        for (c, h) in hdrs.iter().enumerate() {
1280            tbl.cell(0, c).unwrap().set_text(h);
1281            tbl.cell(0, c).unwrap().shading("B22222");
1282        }
1283        let items = [
1284            (
1285                "ML Infrastructure Setup — Phase 1",
1286                "1",
1287                "$45,000.00",
1288                "$3,881.25",
1289                "$48,881.25",
1290            ),
1291            (
1292                "Data Pipeline Development (160 hrs)",
1293                "160",
1294                "$225.00",
1295                "$3,105.00",
1296                "$39,105.00",
1297            ),
1298            (
1299                "GPU Cluster Configuration",
1300                "1",
1301                "$12,000.00",
1302                "$1,035.00",
1303                "$13,035.00",
1304            ),
1305            (
1306                "API Gateway Implementation",
1307                "1",
1308                "$18,500.00",
1309                "$1,595.63",
1310                "$20,095.63",
1311            ),
1312            (
1313                "Load Testing & QA (80 hrs)",
1314                "80",
1315                "$195.00",
1316                "$1,345.50",
1317                "$16,945.50",
1318            ),
1319            (
1320                "Documentation & Training",
1321                "1",
1322                "$8,000.00",
1323                "$690.00",
1324                "$8,690.00",
1325            ),
1326            (
1327                "Project Management (3 months)",
1328                "3",
1329                "$6,500.00",
1330                "$1,679.63",
1331                "$21,179.63",
1332            ),
1333        ];
1334        for (i, (desc, qty, unit, tax, amt)) in items.iter().enumerate() {
1335            let r = i + 1;
1336            tbl.cell(r, 0).unwrap().set_text(desc);
1337            tbl.cell(r, 1).unwrap().set_text(qty);
1338            tbl.cell(r, 2).unwrap().set_text(unit);
1339            tbl.cell(r, 3).unwrap().set_text(tax);
1340            tbl.cell(r, 4).unwrap().set_text(amt);
1341            if i % 2 == 0 {
1342                for c in 0..5 {
1343                    tbl.cell(r, c).unwrap().shading("FAF0F0");
1344                }
1345            }
1346        }
1347    }
1348
1349    doc.add_paragraph("");
1350
1351    // ── Totals ──
1352    {
1353        let mut tbl = doc
1354            .add_table(4, 2)
1355            .borders(BorderStyle::Single, 2, "B22222")
1356            .width(Length::inches(3.0))
1357            .alignment(Alignment::Right);
1358        tbl.cell(0, 0).unwrap().set_text("Subtotal");
1359        tbl.cell(0, 1).unwrap().set_text("$154,600.00");
1360        tbl.cell(1, 0).unwrap().set_text("Tax (8.625%)");
1361        tbl.cell(1, 1).unwrap().set_text("$13,334.25");
1362        tbl.cell(2, 0).unwrap().set_text("Discount (5%)");
1363        tbl.cell(2, 1).unwrap().set_text("-$7,730.00");
1364        tbl.cell(3, 0).unwrap().set_text("AMOUNT DUE");
1365        tbl.cell(3, 0).unwrap().shading("B22222");
1366        tbl.cell(3, 1).unwrap().set_text("$160,204.25");
1367        tbl.cell(3, 1).unwrap().shading("B22222");
1368    }
1369
1370    doc.add_paragraph("");
1371    doc.add_paragraph("");
1372
1373    // ── Payment Details ──
1374    {
1375        let mut p = doc.add_paragraph("");
1376        p.add_run("Payment Details")
1377            .bold(true)
1378            .size(14.0)
1379            .color("333333");
1380    }
1381    doc.add_paragraph("Bank: Silicon Valley Bank")
1382        .indent_left(Length::inches(0.3));
1383    doc.add_paragraph("Account: Tensorbee Inc. — 0847-2953-1120")
1384        .indent_left(Length::inches(0.3));
1385    doc.add_paragraph("Routing: 121140399")
1386        .indent_left(Length::inches(0.3));
1387    doc.add_paragraph("Swift: SVBKUS6S")
1388        .indent_left(Length::inches(0.3));
1389
1390    doc.add_paragraph("");
1391
1392    doc.add_paragraph("Please include invoice number INV-2026-0392 in the payment reference.")
1393        .shading("FAF0F0")
1394        .border_all(BorderStyle::Single, 2, "B22222");
1395
1396    doc
1397}
1398
1399// =============================================================================
1400// 5. REPORT — Forest green + earth tones, with images & hierarchical sections
1401// =============================================================================
1402fn generate_report(_samples_dir: &Path) -> Document {
1403    let mut doc = Document::new();
1404
1405    // Colors: Forest #2D5016, Sage #6B8E23, Earth #8B7355, Cream #FFFAF0
1406    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1407    doc.set_margins(
1408        Length::inches(1.0),
1409        Length::inches(1.0),
1410        Length::inches(1.0),
1411        Length::inches(1.0),
1412    );
1413    doc.set_title("Q4 2025 Environmental Impact Report");
1414    doc.set_author("Walter White");
1415    doc.set_subject("Quarterly Environmental Report");
1416    doc.set_keywords("environment, sustainability, report, Tensorbee");
1417
1418    doc.set_different_first_page(true);
1419    doc.set_first_page_header("");
1420    doc.set_header("Tensorbee — Q4 2025 Environmental Impact Report");
1421    doc.set_footer("CONFIDENTIAL — Page");
1422
1423    // ── Cover ──
1424    let cover_bg = create_sample_png(612, 792, [20, 50, 15]);
1425    doc.add_background_image(&cover_bg, "report_cover.png");
1426
1427    for _ in 0..5 {
1428        doc.add_paragraph("");
1429    }
1430    {
1431        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1432        p.add_run("Q4 2025")
1433            .bold(true)
1434            .size(48.0)
1435            .color("FFFFFF")
1436            .font("Georgia");
1437    }
1438    {
1439        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1440        p.add_run("Environmental Impact Report")
1441            .size(24.0)
1442            .color("90EE90")
1443            .font("Georgia")
1444            .italic(true);
1445    }
1446    doc.add_paragraph("");
1447    {
1448        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1449        p.add_run("Tensorbee — Sustainability Division")
1450            .size(14.0)
1451            .color("C0C0C0");
1452    }
1453    {
1454        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1455        p.add_run("Prepared by Walter White, Chief Sustainability Officer")
1456            .size(11.0)
1457            .color("AAAAAA");
1458    }
1459
1460    // ── TOC ──
1461    doc.add_paragraph("").page_break_before(true);
1462    doc.insert_toc(doc.content_count(), 3);
1463
1464    // ── Section 1: Executive Overview ──
1465    doc.add_paragraph("").page_break_before(true);
1466    doc.add_paragraph("Executive Overview").style("Heading1");
1467    doc.add_paragraph(
1468        "This report presents Tensorbee's environmental performance for Q4 2025. Our \
1469         sustainability initiatives have yielded a 34% reduction in carbon emissions compared \
1470         to Q4 2024, exceeding our target of 25%. Key achievements include the transition to \
1471         100% renewable energy in our primary data centers and the launch of our carbon offset \
1472         marketplace.",
1473    )
1474    .first_line_indent(Length::inches(0.3));
1475
1476    // Image: performance chart
1477    let chart_img = create_chart_png(400, 200);
1478    doc.add_paragraph("");
1479    doc.add_picture(
1480        &chart_img,
1481        "performance_chart.png",
1482        Length::inches(5.0),
1483        Length::inches(2.5),
1484    )
1485    .alignment(Alignment::Center);
1486    {
1487        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1488        p.add_run("Figure 1: Quarterly Carbon Emissions (tonnes CO2e)")
1489            .italic(true)
1490            .size(9.0)
1491            .color("666666");
1492    }
1493
1494    // ── Section 2: Energy Consumption ──
1495    doc.add_paragraph("").page_break_before(true);
1496    doc.add_paragraph("Energy Consumption").style("Heading1");
1497
1498    doc.add_paragraph("Data Center Operations")
1499        .style("Heading2");
1500    doc.add_paragraph(
1501        "Our three primary data centers consumed a combined 4.2 GWh in Q4 2025, \
1502         a 12% reduction from Q3 2025 driven by improved cooling efficiency and \
1503         server consolidation.",
1504    );
1505
1506    // Data table
1507    {
1508        let mut tbl = doc
1509            .add_table(5, 4)
1510            .borders(BorderStyle::Single, 4, "2D5016")
1511            .width_pct(100.0);
1512        let hdrs = ["Data Center", "Capacity (MW)", "Usage (GWh)", "PUE"];
1513        for (c, h) in hdrs.iter().enumerate() {
1514            tbl.cell(0, c).unwrap().set_text(h);
1515            tbl.cell(0, c).unwrap().shading("2D5016");
1516        }
1517        let rows = [
1518            ("San Francisco (Primary)", "3.2", "1.8", "1.12"),
1519            ("Dublin (EU)", "2.1", "1.2", "1.18"),
1520            ("Singapore (APAC)", "1.8", "1.2", "1.24"),
1521            ("TOTAL", "7.1", "4.2", "1.17 avg"),
1522        ];
1523        for (i, (dc, cap, use_, pue)) in rows.iter().enumerate() {
1524            tbl.cell(i + 1, 0).unwrap().set_text(dc);
1525            tbl.cell(i + 1, 1).unwrap().set_text(cap);
1526            tbl.cell(i + 1, 2).unwrap().set_text(use_);
1527            tbl.cell(i + 1, 3).unwrap().set_text(pue);
1528            if i == 3 {
1529                for c in 0..4 {
1530                    tbl.cell(i + 1, c).unwrap().shading("E2EFDA");
1531                }
1532            }
1533        }
1534    }
1535
1536    doc.add_paragraph("");
1537
1538    doc.add_paragraph("Renewable Energy Mix").style("Heading2");
1539    doc.add_paragraph("Breakdown of energy sources across all facilities:");
1540
1541    doc.add_bullet_list_item("Solar PV: 42% (1.76 GWh)", 0);
1542    doc.add_bullet_list_item("Wind Power Purchase Agreements: 38% (1.60 GWh)", 0);
1543    doc.add_bullet_list_item("Hydroelectric: 12% (0.50 GWh)", 0);
1544    doc.add_bullet_list_item("Grid (non-renewable): 8% (0.34 GWh)", 0);
1545
1546    // ── Section 3: Water & Waste ──
1547    doc.add_paragraph("Water & Waste Management")
1548        .style("Heading1");
1549
1550    doc.add_paragraph("Water Usage").style("Heading2");
1551    doc.add_paragraph(
1552        "Total water consumption was 12.4 million gallons, a 15% reduction from Q3. \
1553         Our closed-loop cooling systems now recycle 78% of water used in cooling operations.",
1554    );
1555
1556    doc.add_paragraph("Waste Reduction").style("Heading2");
1557    doc.add_paragraph("E-waste management results for Q4:")
1558        .keep_with_next(true);
1559    {
1560        let mut tbl = doc
1561            .add_table(4, 3)
1562            .borders(BorderStyle::Single, 2, "6B8E23")
1563            .width_pct(80.0);
1564        tbl.cell(0, 0).unwrap().set_text("Category");
1565        tbl.cell(0, 0).unwrap().shading("6B8E23");
1566        tbl.cell(0, 1).unwrap().set_text("Weight (kg)");
1567        tbl.cell(0, 1).unwrap().shading("6B8E23");
1568        tbl.cell(0, 2).unwrap().set_text("Recycled %");
1569        tbl.cell(0, 2).unwrap().shading("6B8E23");
1570        let rows = [
1571            ("Server Hardware", "2,450", "94%"),
1572            ("Networking Equipment", "820", "91%"),
1573            ("Storage Media", "340", "99%"),
1574        ];
1575        for (i, (cat, wt, pct)) in rows.iter().enumerate() {
1576            tbl.cell(i + 1, 0).unwrap().set_text(cat);
1577            tbl.cell(i + 1, 1).unwrap().set_text(wt);
1578            tbl.cell(i + 1, 2).unwrap().set_text(pct);
1579        }
1580    }
1581
1582    // ── Section 4: Initiatives ──
1583    doc.add_paragraph("").page_break_before(true);
1584    doc.add_paragraph("Q1 2026 Initiatives").style("Heading1");
1585
1586    doc.add_paragraph("Planned Programs").style("Heading2");
1587    doc.add_numbered_list_item(
1588        "Deploy on-site battery storage at SF data center (2 MWh capacity)",
1589        0,
1590    );
1591    doc.add_numbered_list_item("Pilot immersion cooling in Dublin facility", 0);
1592    doc.add_numbered_list_item("Launch employee commute carbon offset program", 0);
1593    doc.add_numbered_list_item("Achieve ISO 14001 certification for Singapore facility", 0);
1594
1595    doc.add_paragraph("Investment Targets").style("Heading2");
1596    doc.add_paragraph("Sustainability CapEx allocation for FY2026:")
1597        .keep_with_next(true);
1598    {
1599        let mut tbl = doc
1600            .add_table(5, 2)
1601            .borders(BorderStyle::Single, 4, "2D5016");
1602        tbl.cell(0, 0).unwrap().set_text("Initiative");
1603        tbl.cell(0, 0).unwrap().shading("2D5016");
1604        tbl.cell(0, 1).unwrap().set_text("Budget");
1605        tbl.cell(0, 1).unwrap().shading("2D5016");
1606        let rows = [
1607            ("Battery Storage", "$1.2M"),
1608            ("Immersion Cooling Pilot", "$800K"),
1609            ("Solar Panel Expansion", "$2.1M"),
1610            ("Carbon Credits", "$500K"),
1611        ];
1612        for (i, (init, budget)) in rows.iter().enumerate() {
1613            tbl.cell(i + 1, 0).unwrap().set_text(init);
1614            tbl.cell(i + 1, 1).unwrap().set_text(budget);
1615            if i % 2 == 0 {
1616                tbl.cell(i + 1, 0).unwrap().shading("FFFAF0");
1617                tbl.cell(i + 1, 1).unwrap().shading("FFFAF0");
1618            }
1619        }
1620    }
1621
1622    // Image: sustainability roadmap
1623    let roadmap_img = create_sample_png(500, 100, [40, 80, 30]);
1624    doc.add_paragraph("");
1625    doc.add_picture(
1626        &roadmap_img,
1627        "roadmap.png",
1628        Length::inches(6.0),
1629        Length::inches(1.2),
1630    )
1631    .alignment(Alignment::Center);
1632    {
1633        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1634        p.add_run("Figure 2: 2026 Sustainability Roadmap")
1635            .italic(true)
1636            .size(9.0)
1637            .color("666666");
1638    }
1639
1640    doc.add_paragraph("");
1641    doc.add_paragraph(
1642        "For questions about this report, contact Walter White at sustainability@tensorbee.com.",
1643    )
1644    .shading("FFFAF0")
1645    .border_all(BorderStyle::Single, 2, "2D5016");
1646
1647    doc
1648}
1649
1650// =============================================================================
1651// 6. LETTER — Slate blue + silver scheme
1652// =============================================================================
1653fn generate_letter(_samples_dir: &Path) -> Document {
1654    let mut doc = Document::new();
1655
1656    // Colors: Slate #4A5568, Blue accent #3182CE
1657    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1658    doc.set_margins(
1659        Length::inches(1.25),
1660        Length::inches(1.25),
1661        Length::inches(1.25),
1662        Length::inches(1.25),
1663    );
1664    doc.set_title("Business Letter");
1665    doc.set_author("Walter White");
1666
1667    // Banner header using raw XML
1668    let logo_img = create_logo_png(220, 48);
1669    let banner = build_header_banner_xml(
1670        "rId1",
1671        &BannerOpts {
1672            bg_color: "4A5568",
1673            banner_width: 7772400,
1674            banner_height: 731520, // ~0.8"
1675            logo_width: 2011680,
1676            logo_height: 438912,
1677            logo_x_offset: 295125,
1678            logo_y_offset: 146304,
1679        },
1680    );
1681    doc.set_raw_header_with_images(
1682        banner,
1683        &[("rId1", &logo_img, "logo.png")],
1684        rdocx_oxml::header_footer::HdrFtrType::Default,
1685    );
1686    doc.set_margins(
1687        Length::twips(2000),
1688        Length::twips(1800),
1689        Length::twips(1440),
1690        Length::twips(1800),
1691    );
1692    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
1693
1694    // Footer with contact
1695    doc.set_footer(
1696        "Tensorbee | 123 Innovation Drive, Suite 400 | San Francisco, CA 94105 | tensorbee.com",
1697    );
1698
1699    // ── Sender Address ──
1700    doc.add_paragraph("Walter White");
1701    doc.add_paragraph("Chief Executive Officer");
1702    doc.add_paragraph("Tensorbee");
1703    doc.add_paragraph("123 Innovation Drive, Suite 400");
1704    doc.add_paragraph("San Francisco, CA 94105");
1705    doc.add_paragraph("");
1706    doc.add_paragraph("February 22, 2026");
1707
1708    doc.add_paragraph("");
1709
1710    // ── Recipient ──
1711    doc.add_paragraph("Ms. Sarah Chen");
1712    doc.add_paragraph("VP of Engineering");
1713    doc.add_paragraph("Meridian Dynamics LLC");
1714    doc.add_paragraph("456 Enterprise Boulevard");
1715    doc.add_paragraph("Austin, TX 78701");
1716
1717    doc.add_paragraph("");
1718
1719    // ── Subject ──
1720    {
1721        let mut p = doc.add_paragraph("");
1722        p.add_run("Re: Strategic Technology Partnership — Phase 2 Expansion")
1723            .bold(true);
1724    }
1725
1726    doc.add_paragraph("");
1727
1728    // ── Body ──
1729    doc.add_paragraph("Dear Ms. Chen,");
1730    doc.add_paragraph("");
1731
1732    doc.add_paragraph(
1733        "I am writing to express Tensorbee's enthusiasm for expanding our strategic technology \
1734         partnership with Meridian Dynamics. Following the successful completion of Phase 1, \
1735         which delivered a 40% improvement in your ML inference pipeline throughput, we believe \
1736         the foundation is firmly established for an ambitious Phase 2 engagement.",
1737    )
1738    .first_line_indent(Length::inches(0.5));
1739
1740    doc.add_paragraph(
1741        "During our recent executive review, your team highlighted three priority areas for \
1742         the next phase of collaboration:",
1743    )
1744    .first_line_indent(Length::inches(0.5));
1745
1746    doc.add_numbered_list_item(
1747        "Real-time anomaly detection for your financial transaction monitoring system, \
1748         targeting sub-100ms latency at 50,000 transactions per second.",
1749        0,
1750    );
1751    doc.add_numbered_list_item(
1752        "Federated learning infrastructure to enable model training across your distributed \
1753         data centers without centralizing sensitive financial data.",
1754        0,
1755    );
1756    doc.add_numbered_list_item(
1757        "MLOps automation to reduce your model deployment cycle from the current 5 days \
1758         to under 4 hours.",
1759        0,
1760    );
1761
1762    doc.add_paragraph(
1763        "Our engineering team has prepared a detailed technical proposal addressing each of \
1764         these areas. We have allocated a dedicated team of eight senior engineers, led by \
1765         our CTO, to ensure continuity with the Phase 1 team your organization has already \
1766         built a productive working relationship with.",
1767    )
1768    .first_line_indent(Length::inches(0.5));
1769
1770    doc.add_paragraph(
1771        "I would welcome the opportunity to present our Phase 2 proposal in person. My \
1772         assistant will reach out to coordinate a meeting at your convenience during the \
1773         first week of March.",
1774    )
1775    .first_line_indent(Length::inches(0.5));
1776
1777    doc.add_paragraph("");
1778
1779    doc.add_paragraph("Warm regards,");
1780    doc.add_paragraph("");
1781    doc.add_paragraph("");
1782
1783    // Signature line
1784    doc.add_paragraph("")
1785        .border_bottom(BorderStyle::Single, 4, "4A5568");
1786    {
1787        let mut p = doc.add_paragraph("");
1788        p.add_run("Walter White").bold(true).size(12.0);
1789    }
1790    {
1791        let mut p = doc.add_paragraph("");
1792        p.add_run("Chief Executive Officer, Tensorbee")
1793            .italic(true)
1794            .size(10.0)
1795            .color("666666");
1796    }
1797    {
1798        let mut p = doc.add_paragraph("");
1799        p.add_run("walter@tensorbee.com | +1 (415) 555-0199")
1800            .size(10.0)
1801            .color("888888");
1802    }
1803
1804    doc
1805}
1806
1807// =============================================================================
1808// 7. EMPLOYMENT CONTRACT — Purple + charcoal scheme
1809// =============================================================================
1810fn generate_contract(_samples_dir: &Path) -> Document {
1811    let mut doc = Document::new();
1812
1813    // Colors: Purple #5B2C6F, Plum #8E44AD, Gray #2C3E50
1814    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1815    doc.set_margins(
1816        Length::inches(1.25),
1817        Length::inches(1.0),
1818        Length::inches(1.0),
1819        Length::inches(1.0),
1820    );
1821    doc.set_title("Employment Agreement");
1822    doc.set_author("Tensorbee HR Department");
1823    doc.set_subject("Employment Contract — Walter White");
1824    doc.set_keywords("employment, contract, agreement, Tensorbee");
1825
1826    doc.set_header("TENSORBEE — Employment Agreement");
1827    doc.set_footer("Employment Agreement — Walter White — Page");
1828
1829    // ── Title Block ──
1830    doc.add_paragraph("")
1831        .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1832    {
1833        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1834        p.add_run("EMPLOYMENT AGREEMENT")
1835            .bold(true)
1836            .size(24.0)
1837            .color("5B2C6F")
1838            .font("Georgia");
1839    }
1840    doc.add_paragraph("")
1841        .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1842    doc.add_paragraph("");
1843
1844    // ── Parties ──
1845    doc.add_paragraph(
1846        "This Employment Agreement (\"Agreement\") is entered into as of February 22, 2026 \
1847         (\"Effective Date\"), by and between:",
1848    );
1849
1850    doc.add_paragraph("");
1851
1852    {
1853        let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1854        p.add_run("EMPLOYER: ").bold(true);
1855        p.add_run(
1856            "Tensorbee, Inc., a Delaware corporation with its principal place of business at \
1857                   123 Innovation Drive, Suite 400, San Francisco, CA 94105 (\"Company\")",
1858        );
1859    }
1860    doc.add_paragraph("");
1861    {
1862        let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1863        p.add_run("EMPLOYEE: ").bold(true);
1864        p.add_run(
1865            "Walter White, residing at 308 Negra Arroyo Lane, Albuquerque, NM 87104 (\"Employee\")",
1866        );
1867    }
1868
1869    doc.add_paragraph("");
1870    doc.add_paragraph("The Company and Employee are collectively referred to as the \"Parties.\"");
1871    doc.add_paragraph("");
1872
1873    // ── Article 1: Position and Duties ──
1874    doc.add_paragraph("Article 1 — Position and Duties")
1875        .style("Heading1");
1876    {
1877        let mut p = doc.add_paragraph("");
1878        p.add_run("1.1 ").bold(true);
1879        p.add_run("The Company hereby employs the Employee as ");
1880        p.add_run("Chief Executive Officer (CEO)")
1881            .bold(true)
1882            .italic(true);
1883        p.add_run(", reporting directly to the Board of Directors.");
1884    }
1885    {
1886        let mut p = doc.add_paragraph("");
1887        p.add_run("1.2 ").bold(true);
1888        p.add_run("The Employee shall devote full working time, attention, and best efforts to \
1889                   the performance of duties as reasonably assigned by the Board, including but not limited to:");
1890    }
1891    doc.add_bullet_list_item(
1892        "Setting and executing the Company's strategic vision and business plan",
1893        0,
1894    );
1895    doc.add_bullet_list_item(
1896        "Overseeing all operations, engineering, and commercial activities",
1897        0,
1898    );
1899    doc.add_bullet_list_item(
1900        "Representing the Company to investors, customers, and the public",
1901        0,
1902    );
1903    doc.add_bullet_list_item("Recruiting, developing, and retaining key talent", 0);
1904
1905    {
1906        let mut p = doc.add_paragraph("");
1907        p.add_run("1.3 ").bold(true);
1908        p.add_run(
1909            "The Employee's primary work location shall be the Company's San Francisco \
1910                   headquarters, with reasonable travel as required by business needs.",
1911        );
1912    }
1913
1914    // ── Article 2: Compensation ──
1915    doc.add_paragraph("Article 2 — Compensation and Benefits")
1916        .style("Heading1");
1917    {
1918        let mut p = doc.add_paragraph("");
1919        p.add_run("2.1 Base Salary. ").bold(true);
1920        p.add_run("The Company shall pay the Employee an annual base salary of ");
1921        p.add_run("$375,000.00").bold(true);
1922        p.add_run(" (Three Hundred Seventy-Five Thousand Dollars), payable in accordance with the \
1923                   Company's standard payroll schedule, less applicable withholdings and deductions.");
1924    }
1925    {
1926        let mut p = doc.add_paragraph("");
1927        p.add_run("2.2 Annual Bonus. ").bold(true);
1928        p.add_run("The Employee shall be eligible for an annual performance bonus of up to ");
1929        p.add_run("40%").bold(true);
1930        p.add_run(
1931            " of base salary, based on achievement of mutually agreed performance objectives.",
1932        );
1933    }
1934    {
1935        let mut p = doc.add_paragraph("");
1936        p.add_run("2.3 Equity. ").bold(true);
1937        p.add_run("Subject to Board approval, the Employee shall receive a stock option grant of ");
1938        p.add_run("500,000 shares").bold(true);
1939        p.add_run(
1940            " of the Company's common stock, vesting over four (4) years with a one-year cliff, \
1941                   at an exercise price equal to the fair market value on the date of grant.",
1942        );
1943    }
1944
1945    // Compensation summary table
1946    doc.add_paragraph("");
1947    {
1948        let mut tbl = doc
1949            .add_table(5, 2)
1950            .borders(BorderStyle::Single, 4, "5B2C6F")
1951            .width_pct(70.0)
1952            .alignment(Alignment::Center);
1953        tbl.cell(0, 0).unwrap().set_text("Compensation Element");
1954        tbl.cell(0, 0).unwrap().shading("5B2C6F");
1955        tbl.cell(0, 1).unwrap().set_text("Value");
1956        tbl.cell(0, 1).unwrap().shading("5B2C6F");
1957        let items = [
1958            ("Base Salary", "$375,000/year"),
1959            ("Target Bonus", "Up to 40% ($150,000)"),
1960            ("Equity Grant", "500,000 shares (4yr vest)"),
1961            ("Total Target Comp", "$525,000 + equity"),
1962        ];
1963        for (i, (elem, val)) in items.iter().enumerate() {
1964            tbl.cell(i + 1, 0).unwrap().set_text(elem);
1965            tbl.cell(i + 1, 1).unwrap().set_text(val);
1966            if i == 3 {
1967                tbl.cell(i + 1, 0).unwrap().shading("E8DAEF");
1968                tbl.cell(i + 1, 1).unwrap().shading("E8DAEF");
1969            }
1970        }
1971    }
1972
1973    // ── Article 3: Benefits ──
1974    doc.add_paragraph("").page_break_before(true);
1975    doc.add_paragraph("Article 3 — Benefits").style("Heading1");
1976    {
1977        let mut p = doc.add_paragraph("");
1978        p.add_run("3.1 ").bold(true);
1979        p.add_run(
1980            "The Employee shall be entitled to participate in all benefit programs \
1981                   generally available to senior executives, including:",
1982        );
1983    }
1984    doc.add_bullet_list_item(
1985        "Medical, dental, and vision insurance (100% premium coverage for employee and dependents)",
1986        0,
1987    );
1988    doc.add_bullet_list_item("401(k) retirement plan with 6% company match", 0);
1989    doc.add_bullet_list_item("Life insurance and long-term disability coverage", 0);
1990    doc.add_bullet_list_item("Annual professional development allowance of $10,000", 0);
1991
1992    {
1993        let mut p = doc.add_paragraph("");
1994        p.add_run("3.2 Paid Time Off. ").bold(true);
1995        p.add_run(
1996            "The Employee shall receive 25 days of paid vacation per year, plus Company holidays, \
1997                   accruing on a monthly basis.",
1998        );
1999    }
2000
2001    // ── Article 4: Term and Termination ──
2002    doc.add_paragraph("Article 4 — Term and Termination")
2003        .style("Heading1");
2004    {
2005        let mut p = doc.add_paragraph("");
2006        p.add_run("4.1 At-Will Employment. ").bold(true);
2007        p.add_run(
2008            "This Agreement is for at-will employment and may be terminated by either Party \
2009                   at any time, with or without cause, subject to the notice provisions herein.",
2010        );
2011    }
2012    {
2013        let mut p = doc.add_paragraph("");
2014        p.add_run("4.2 Notice Period. ").bold(true);
2015        p.add_run("Either Party shall provide ");
2016        p.add_run("ninety (90) days").bold(true);
2017        p.add_run(" written notice of termination, or pay in lieu thereof.");
2018    }
2019    {
2020        let mut p = doc.add_paragraph("");
2021        p.add_run("4.3 Severance. ").bold(true);
2022        p.add_run("In the event of termination by the Company without Cause, the Employee shall \
2023                   receive (a) twelve (12) months of base salary continuation, (b) pro-rata bonus \
2024                   for the year of termination, and (c) twelve (12) months of COBRA premium coverage.");
2025    }
2026
2027    // ── Article 5: Confidentiality ──
2028    doc.add_paragraph("Article 5 — Confidentiality and IP")
2029        .style("Heading1");
2030    {
2031        let mut p = doc.add_paragraph("");
2032        p.add_run("5.1 ").bold(true);
2033        p.add_run(
2034            "The Employee agrees to maintain strict confidentiality of all proprietary \
2035                   information, trade secrets, and business plans of the Company during and after \
2036                   employment.",
2037        );
2038    }
2039    {
2040        let mut p = doc.add_paragraph("");
2041        p.add_run("5.2 ").bold(true);
2042        p.add_run(
2043            "All intellectual property, inventions, and works of authorship created by the \
2044                   Employee during the term of employment and within the scope of duties shall be \
2045                   the sole and exclusive property of the Company.",
2046        );
2047    }
2048
2049    // ── Article 6: Non-Compete ──
2050    doc.add_paragraph("Article 6 — Non-Competition")
2051        .style("Heading1");
2052    {
2053        let mut p = doc.add_paragraph("");
2054        p.add_run("6.1 ").bold(true);
2055        p.add_run("For a period of twelve (12) months following termination, the Employee agrees \
2056                   not to engage in any business that directly competes with the Company's core \
2057                   business of AI infrastructure and ML operations services, within the United States.");
2058    }
2059
2060    // ── Article 7: General Provisions ──
2061    doc.add_paragraph("Article 7 — General Provisions")
2062        .style("Heading1");
2063    {
2064        let mut p = doc.add_paragraph("");
2065        p.add_run("7.1 Governing Law. ").bold(true);
2066        p.add_run(
2067            "This Agreement shall be governed by and construed in accordance with the laws of \
2068                   the State of California.",
2069        );
2070    }
2071    {
2072        let mut p = doc.add_paragraph("");
2073        p.add_run("7.2 Entire Agreement. ").bold(true);
2074        p.add_run(
2075            "This Agreement constitutes the entire agreement between the Parties and supersedes \
2076                   all prior negotiations, representations, and agreements.",
2077        );
2078    }
2079    {
2080        let mut p = doc.add_paragraph("");
2081        p.add_run("7.3 Amendment. ").bold(true);
2082        p.add_run(
2083            "This Agreement may only be amended by a written instrument signed by both Parties.",
2084        );
2085    }
2086
2087    // ── Signature Block ──
2088    doc.add_paragraph("").page_break_before(true);
2089    doc.add_paragraph("IN WITNESS WHEREOF, the Parties have executed this Employment Agreement as of the Effective Date.")
2090        .space_after(Length::pt(24.0));
2091
2092    // Signature table
2093    {
2094        let mut tbl = doc.add_table(6, 2).width_pct(100.0);
2095        tbl.cell(0, 0).unwrap().set_text("FOR THE COMPANY:");
2096        tbl.cell(0, 0).unwrap().shading("5B2C6F");
2097        tbl.cell(0, 1).unwrap().set_text("EMPLOYEE:");
2098        tbl.cell(0, 1).unwrap().shading("5B2C6F");
2099
2100        tbl.cell(1, 0).unwrap().set_text("");
2101        tbl.cell(1, 1).unwrap().set_text("");
2102        tbl.row(1).unwrap().height(Length::pt(40.0));
2103
2104        tbl.cell(2, 0)
2105            .unwrap()
2106            .set_text("Signature: ___________________________");
2107        tbl.cell(2, 1)
2108            .unwrap()
2109            .set_text("Signature: ___________________________");
2110        tbl.cell(3, 0)
2111            .unwrap()
2112            .set_text("Name: Board Representative");
2113        tbl.cell(3, 1).unwrap().set_text("Name: Walter White");
2114        tbl.cell(4, 0)
2115            .unwrap()
2116            .set_text("Title: Chair, Board of Directors");
2117        tbl.cell(4, 1)
2118            .unwrap()
2119            .set_text("Title: Chief Executive Officer");
2120        tbl.cell(5, 0).unwrap().set_text("Date: _______________");
2121        tbl.cell(5, 1).unwrap().set_text("Date: _______________");
2122    }
2123
2124    doc
2125}
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_samples.rs (line 53)
34fn generate_feature_showcase(path: &Path) {
35    let mut doc = Document::new();
36
37    // =========================================================================
38    // PAGE SETUP & METADATA
39    // =========================================================================
40    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
41    doc.set_margins(
42        Length::inches(1.0), // top
43        Length::inches(1.0), // right
44        Length::inches(1.0), // bottom
45        Length::inches(1.0), // left
46    );
47    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
48    doc.set_gutter(Length::twips(0));
49
50    doc.set_title("rdocx Feature Showcase");
51    doc.set_author("rdocx Sample Generator");
52    doc.set_subject("Comprehensive feature demonstration");
53    doc.set_keywords("rdocx, docx, rust, sample");
54
55    // Header & Footer
56    doc.set_header("rdocx Feature Showcase");
57    doc.set_footer("Generated by rdocx — Page");
58
59    // Different first page header
60    doc.set_different_first_page(true);
61    doc.set_first_page_header("rdocx");
62    doc.set_first_page_footer("Feature Showcase — Cover Page");
63
64    // =========================================================================
65    // PAGE 1: COVER PAGE — background image, run formatting
66    // =========================================================================
67    let bg_cover = create_sample_png(612, 792, [30, 60, 120]);
68    doc.add_background_image(&bg_cover, "cover_bg.png");
69
70    doc.add_paragraph(""); // spacer
71    doc.add_paragraph(""); // spacer
72    doc.add_paragraph(""); // spacer
73
74    {
75        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
76        p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
77    }
78    {
79        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
80        p.add_run("Feature Showcase")
81            .size(28.0)
82            .color("FFFFFF")
83            .italic(true);
84    }
85
86    doc.add_paragraph(""); // spacer
87
88    {
89        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
90        p.add_run("A comprehensive demonstration of every feature")
91            .size(14.0)
92            .color("CCDDFF");
93    }
94    {
95        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
96        p.add_run("provided by the rdocx Rust crate for DOCX generation.")
97            .size(14.0)
98            .color("CCDDFF");
99    }
100
101    // =========================================================================
102    // PAGE 2: TEXT FORMATTING
103    // =========================================================================
104    doc.add_paragraph("").page_break_before(true);
105
106    doc.add_paragraph("1. Text Formatting").style("Heading1");
107
108    doc.add_paragraph("This section demonstrates paragraph and run-level formatting options.");
109    doc.add_paragraph("");
110
111    // --- Paragraph alignment ---
112    doc.add_paragraph("Paragraph Alignment").style("Heading2");
113
114    doc.add_paragraph("This paragraph is left-aligned (the default).")
115        .alignment(Alignment::Left);
116    doc.add_paragraph("This paragraph is center-aligned.")
117        .alignment(Alignment::Center);
118    doc.add_paragraph("This paragraph is right-aligned.")
119        .alignment(Alignment::Right);
120    doc.add_paragraph(
121        "This paragraph is justified. To demonstrate justified text properly, it needs \
122         to be long enough to span multiple lines so the word spacing adjustment is visible \
123         across the full width of the text area on the page.",
124    )
125    .alignment(Alignment::Justify);
126
127    doc.add_paragraph("");
128
129    // --- Run formatting ---
130    doc.add_paragraph("Run Formatting").style("Heading2");
131
132    {
133        let mut p = doc.add_paragraph("");
134        p.add_run("Bold text").bold(true);
135        p.add_run(" | ");
136        p.add_run("Italic text").italic(true);
137        p.add_run(" | ");
138        p.add_run("Bold + Italic").bold(true).italic(true);
139    }
140    {
141        let mut p = doc.add_paragraph("");
142        p.add_run("Single underline").underline(true);
143        p.add_run(" | ");
144        p.add_run("Strikethrough").strike(true);
145        p.add_run(" | ");
146        p.add_run("Double strikethrough").double_strike(true);
147    }
148    {
149        let mut p = doc.add_paragraph("");
150        p.add_run("Red text").color("FF0000");
151        p.add_run(" | ");
152        p.add_run("Blue text").color("0000FF");
153        p.add_run(" | ");
154        p.add_run("Green text").color("00AA00");
155        p.add_run(" | ");
156        p.add_run("Highlighted").highlight("FFFF00");
157    }
158    {
159        let mut p = doc.add_paragraph("");
160        p.add_run("8pt small").size(8.0);
161        p.add_run(" | ");
162        p.add_run("11pt normal").size(11.0);
163        p.add_run(" | ");
164        p.add_run("16pt large").size(16.0);
165        p.add_run(" | ");
166        p.add_run("24pt extra-large").size(24.0);
167    }
168    {
169        let mut p = doc.add_paragraph("");
170        p.add_run("Arial font").font("Arial");
171        p.add_run(" | ");
172        p.add_run("Times New Roman font").font("Times New Roman");
173        p.add_run(" | ");
174        p.add_run("Courier New font").font("Courier New");
175    }
176    {
177        let mut p = doc.add_paragraph("");
178        p.add_run("Normal");
179        p.add_run(" H").size(11.0);
180        p.add_run("2").subscript();
181        p.add_run("O (subscript)").size(11.0);
182        p.add_run(" | E = mc").size(11.0);
183        p.add_run("2").superscript();
184        p.add_run(" (superscript)").size(11.0);
185    }
186    {
187        let mut p = doc.add_paragraph("");
188        p.add_run("ALL CAPS").all_caps(true);
189        p.add_run(" | ");
190        p.add_run("Small Caps").small_caps(true);
191        p.add_run(" | ");
192        p.add_run("Expanded spacing")
193            .character_spacing(Length::twips(40));
194    }
195
196    doc.add_paragraph("");
197
198    // --- Paragraph formatting ---
199    doc.add_paragraph("Paragraph Formatting").style("Heading2");
200
201    doc.add_paragraph("Paragraph with shading (light green background)")
202        .shading("E2EFDA");
203
204    doc.add_paragraph("Paragraph with bottom border")
205        .border_bottom(BorderStyle::Single, 6, "2E75B6");
206
207    doc.add_paragraph("Paragraph with all borders")
208        .border_all(BorderStyle::Single, 4, "FF0000");
209
210    doc.add_paragraph("Paragraph with 1-inch left indent and hanging indent")
211        .indent_left(Length::inches(1.0))
212        .hanging_indent(Length::inches(0.5));
213
214    doc.add_paragraph("Paragraph with first-line indent of 0.5 inches")
215        .first_line_indent(Length::inches(0.5));
216
217    doc.add_paragraph("Paragraph with extra space before (24pt) and after (12pt)")
218        .space_before(Length::pt(24.0))
219        .space_after(Length::pt(12.0));
220
221    doc.add_paragraph(
222        "Paragraph with double line spacing. This text should have extra vertical \
223         space between lines to demonstrate the line_spacing_multiple setting.",
224    )
225    .line_spacing_multiple(2.0);
226
227    doc.add_paragraph("Paragraph with keep-with-next (won't break from the next paragraph)")
228        .keep_with_next(true);
229    doc.add_paragraph("(This stays with the paragraph above.)");
230
231    // =========================================================================
232    // PAGE 3: LISTS & TAB STOPS
233    // =========================================================================
234    doc.add_paragraph("").page_break_before(true);
235
236    doc.add_paragraph("2. Lists").style("Heading1");
237
238    doc.add_paragraph("Bullet List").style("Heading2");
239
240    doc.add_bullet_list_item("First bullet item", 0);
241    doc.add_bullet_list_item("Second bullet item", 0);
242    doc.add_bullet_list_item("Nested level 1", 1);
243    doc.add_bullet_list_item("Nested level 2", 2);
244    doc.add_bullet_list_item("Back to level 1", 1);
245    doc.add_bullet_list_item("Third bullet item", 0);
246
247    doc.add_paragraph("");
248
249    doc.add_paragraph("Numbered List").style("Heading2");
250
251    doc.add_numbered_list_item("First numbered item", 0);
252    doc.add_numbered_list_item("Second numbered item", 0);
253    doc.add_numbered_list_item("Sub-item A", 1);
254    doc.add_numbered_list_item("Sub-item B", 1);
255    doc.add_numbered_list_item("Third numbered item", 0);
256
257    doc.add_paragraph("");
258
259    // --- Tab stops ---
260    doc.add_paragraph("Tab Stops").style("Heading2");
261
262    doc.add_paragraph("Left\tCenter\tRight\tDecimal")
263        .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
264        .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
265        .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
266        .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
267
268    doc.add_paragraph("Item\t........\tPrice")
269        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
270        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
271        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
272
273    doc.add_paragraph("Widget A\t........\t$19.99")
274        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
275        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
276        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
277
278    doc.add_paragraph("Gadget B\t________\t$249.50")
279        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
280        .add_tab_stop_with_leader(
281            TabAlignment::Right,
282            Length::inches(4.0),
283            TabLeader::Underscore,
284        )
285        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
286
287    // =========================================================================
288    // PAGE 4: TABLES
289    // =========================================================================
290    doc.add_paragraph("").page_break_before(true);
291
292    doc.add_paragraph("3. Tables").style("Heading1");
293
294    // --- Basic table with borders ---
295    doc.add_paragraph("Basic Table with Borders")
296        .style("Heading2");
297
298    {
299        let mut tbl = doc.add_table(4, 3);
300        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
301
302        // Header row
303        for col in 0..3 {
304            tbl.cell(0, col).unwrap().shading("2E75B6");
305        }
306        tbl.cell(0, 0).unwrap().set_text("Name");
307        tbl.cell(0, 1).unwrap().set_text("Role");
308        tbl.cell(0, 2).unwrap().set_text("Location");
309
310        tbl.cell(1, 0).unwrap().set_text("Alice Johnson");
311        tbl.cell(1, 1).unwrap().set_text("Engineering Lead");
312        tbl.cell(1, 2).unwrap().set_text("New York");
313
314        tbl.cell(2, 0).unwrap().set_text("Bob Smith");
315        tbl.cell(2, 1).unwrap().set_text("Product Manager");
316        tbl.cell(2, 2).unwrap().set_text("San Francisco");
317
318        tbl.cell(3, 0).unwrap().set_text("Carol Davis");
319        tbl.cell(3, 1).unwrap().set_text("Designer");
320        tbl.cell(3, 2).unwrap().set_text("London");
321    }
322
323    doc.add_paragraph("");
324
325    // --- Table with cell merging ---
326    doc.add_paragraph("Table with Cell Merging & Vertical Alignment")
327        .style("Heading2");
328
329    {
330        let mut tbl = doc.add_table(4, 4);
331        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
332        tbl = tbl.width_pct(100.0);
333
334        // Header spanning all columns
335        tbl.cell(0, 0).unwrap().set_text("Quarterly Revenue Report");
336        tbl.cell(0, 0).unwrap().shading("1F4E79");
337        tbl.cell(0, 0).unwrap().grid_span(4);
338
339        // Sub-header
340        tbl.cell(1, 0).unwrap().set_text("Region");
341        tbl.cell(1, 0).unwrap().shading("D6E4F0");
342        tbl.cell(1, 1).unwrap().set_text("Q1");
343        tbl.cell(1, 1).unwrap().shading("D6E4F0");
344        tbl.cell(1, 2).unwrap().set_text("Q2");
345        tbl.cell(1, 2).unwrap().shading("D6E4F0");
346        tbl.cell(1, 3).unwrap().set_text("Total");
347        tbl.cell(1, 3).unwrap().shading("D6E4F0");
348
349        // Data
350        tbl.cell(2, 0).unwrap().set_text("North America");
351        tbl.cell(2, 1).unwrap().set_text("$2.4M");
352        tbl.cell(2, 2).unwrap().set_text("$2.7M");
353        tbl.cell(2, 3).unwrap().set_text("$5.1M");
354
355        tbl.cell(3, 0).unwrap().set_text("Europe");
356        tbl.cell(3, 1).unwrap().set_text("$1.8M");
357        tbl.cell(3, 2).unwrap().set_text("$2.0M");
358        tbl.cell(3, 3).unwrap().set_text("$3.8M");
359
360        // Vertical alignment on data cells
361        tbl.cell(2, 3)
362            .unwrap()
363            .vertical_alignment(VerticalAlignment::Center);
364        tbl.cell(3, 3)
365            .unwrap()
366            .vertical_alignment(VerticalAlignment::Bottom);
367    }
368
369    doc.add_paragraph("");
370
371    // --- Table with vertical merge ---
372    doc.add_paragraph("Table with Vertical Merge")
373        .style("Heading2");
374
375    {
376        let mut tbl = doc.add_table(4, 3);
377        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
378
379        tbl.cell(0, 0).unwrap().set_text("Category");
380        tbl.cell(0, 0).unwrap().shading("E2EFDA");
381        tbl.cell(0, 1).unwrap().set_text("Item");
382        tbl.cell(0, 1).unwrap().shading("E2EFDA");
383        tbl.cell(0, 2).unwrap().set_text("Price");
384        tbl.cell(0, 2).unwrap().shading("E2EFDA");
385
386        // "Hardware" spans rows 1-2
387        tbl.cell(1, 0).unwrap().set_text("Hardware");
388        tbl.cell(1, 0).unwrap().v_merge_restart();
389        tbl.cell(1, 1).unwrap().set_text("Laptop");
390        tbl.cell(1, 2).unwrap().set_text("$1,200");
391
392        tbl.cell(2, 0).unwrap().v_merge_continue();
393        tbl.cell(2, 1).unwrap().set_text("Monitor");
394        tbl.cell(2, 2).unwrap().set_text("$450");
395
396        // "Software" on row 3
397        tbl.cell(3, 0).unwrap().set_text("Software");
398        tbl.cell(3, 1).unwrap().set_text("IDE License");
399        tbl.cell(3, 2).unwrap().set_text("$200/yr");
400    }
401
402    doc.add_paragraph("");
403
404    // --- Nested table ---
405    doc.add_paragraph("Nested Table").style("Heading2");
406
407    {
408        let mut tbl = doc.add_table(2, 2);
409        tbl = tbl.borders(BorderStyle::Single, 6, "2E75B6");
410
411        tbl.cell(0, 0).unwrap().set_text("Outer Cell (0,0)");
412        tbl.cell(0, 1).unwrap().set_text("Outer Cell (0,1)");
413        tbl.cell(1, 0).unwrap().set_text("Outer Cell (1,0)");
414
415        // Nested table inside cell (1,1)
416        {
417            let mut cell = tbl.cell(1, 1).unwrap();
418            cell.set_text("Contains nested table:");
419            let mut nested = cell.add_table(2, 2);
420            nested = nested.borders(BorderStyle::Single, 2, "FF6600");
421            nested.cell(0, 0).unwrap().set_text("Inner A");
422            nested.cell(0, 1).unwrap().set_text("Inner B");
423            nested.cell(1, 0).unwrap().set_text("Inner C");
424            nested.cell(1, 1).unwrap().set_text("Inner D");
425        }
426    }
427
428    // =========================================================================
429    // PAGE 5: IMAGES
430    // =========================================================================
431    doc.add_paragraph("").page_break_before(true);
432
433    doc.add_paragraph("4. Images").style("Heading1");
434
435    doc.add_paragraph("Inline Image").style("Heading2");
436
437    doc.add_paragraph("Below is an inline image (200x50 pixels, blue gradient):");
438    let inline_img = create_sample_png(200, 50, [0, 80, 200]);
439    doc.add_picture(
440        &inline_img,
441        "inline_chart.png",
442        Length::inches(3.0),
443        Length::inches(0.75),
444    );
445
446    doc.add_paragraph("");
447
448    doc.add_paragraph("Header Image").style("Heading2");
449
450    // Replace the text-only header with an image header
451    let header_img = create_sample_png(400, 40, [40, 40, 40]);
452    doc.set_header_image(
453        &header_img,
454        "header_logo.png",
455        Length::inches(2.0),
456        Length::inches(0.2),
457    );
458
459    doc.add_paragraph(
460        "The document header has been replaced with an inline image. \
461         Check the header area at the top of this page.",
462    );
463
464    doc.add_paragraph("");
465    doc.add_paragraph(
466        "Note: The cover page uses a full-page background image behind the text, \
467         demonstrated on page 1 via add_background_image().",
468    );
469
470    // =========================================================================
471    // PAGE 6: CONTENT MANIPULATION — placeholder replacement, insertion
472    // =========================================================================
473    doc.add_paragraph("").page_break_before(true);
474
475    doc.add_paragraph("5. Content Manipulation")
476        .style("Heading1");
477
478    // --- Placeholder replacement ---
479    doc.add_paragraph("Placeholder Replacement")
480        .style("Heading2");
481
482    doc.add_paragraph(
483        "Before replacement, this document contained {{customer}} and {{date}} placeholders.",
484    );
485
486    {
487        let mut p = doc.add_paragraph("");
488        p.add_run("Customer: ").bold(true);
489        p.add_run("{{customer}}");
490    }
491    {
492        let mut p = doc.add_paragraph("");
493        p.add_run("Date: ").bold(true);
494        p.add_run("{{date}}");
495    }
496    doc.add_paragraph("Reference: {{ref_number}}");
497
498    // Table with placeholders
499    {
500        let mut tbl = doc.add_table(3, 2);
501        tbl = tbl.borders(BorderStyle::Single, 4, "000000");
502        tbl.cell(0, 0).unwrap().set_text("Field");
503        tbl.cell(0, 0).unwrap().shading("D6E4F0");
504        tbl.cell(0, 1).unwrap().set_text("Value");
505        tbl.cell(0, 1).unwrap().shading("D6E4F0");
506        tbl.cell(1, 0).unwrap().set_text("Project");
507        tbl.cell(1, 1).unwrap().set_text("{{project}}");
508        tbl.cell(2, 0).unwrap().set_text("Status");
509        tbl.cell(2, 1).unwrap().set_text("{{status}}");
510    }
511
512    // Perform replacements
513    let mut replacements = HashMap::new();
514    replacements.insert("{{customer}}", "Acme Corporation");
515    replacements.insert("{{date}}", "February 22, 2026");
516    replacements.insert("{{ref_number}}", "REF-2026-001");
517    replacements.insert("{{project}}", "Infrastructure Upgrade");
518    replacements.insert("{{status}}", "In Progress");
519    let replace_count = doc.replace_all(&replacements);
520
521    doc.add_paragraph("");
522    doc.add_paragraph(&format!(
523        "(Replaced {} placeholders above — in body text and table cells)",
524        replace_count
525    ));
526
527    doc.add_paragraph("");
528
529    // --- Content insertion ---
530    doc.add_paragraph("Content Insertion").style("Heading2");
531
532    doc.add_paragraph("Section A: First section of content.");
533    doc.add_paragraph("Section C: Third section of content.");
534
535    // Insert "Section B" between A and C
536    if let Some(idx) = doc.find_content_index("Section C") {
537        doc.insert_paragraph(
538            idx,
539            "Section B: Inserted between A and C using find_content_index().",
540        );
541    }
542
543    doc.add_paragraph("");
544    doc.add_paragraph(
545        "The paragraph above ('Section B') was inserted at a specific position \
546         using find_content_index() + insert_paragraph().",
547    );
548
549    // =========================================================================
550    // PAGE 7: LANDSCAPE — section break, wide table
551    // =========================================================================
552    doc.add_paragraph("").section_break(SectionBreak::NextPage);
553
554    doc.add_paragraph("6. Mixed Page Orientation")
555        .style("Heading1");
556
557    doc.add_paragraph(
558        "This page is in LANDSCAPE orientation. It was created using a section break \
559         followed by section_landscape(). This is useful for wide tables or charts.",
560    );
561
562    doc.add_paragraph("");
563
564    // Wide table for landscape
565    {
566        let mut tbl = doc.add_table(4, 7);
567        tbl = tbl.borders(BorderStyle::Single, 4, "2E75B6");
568
569        let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
570        for (col, h) in headers.iter().enumerate() {
571            tbl.cell(0, col).unwrap().set_text(h);
572            tbl.cell(0, col).unwrap().shading("2E75B6");
573        }
574
575        let data = [
576            [
577                "North America",
578                "$1.2M",
579                "$1.3M",
580                "$1.4M",
581                "$1.5M",
582                "$1.6M",
583                "$7.0M",
584            ],
585            [
586                "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
587            ],
588            [
589                "Asia Pacific",
590                "$0.5M",
591                "$0.6M",
592                "$0.7M",
593                "$0.7M",
594                "$0.8M",
595                "$3.3M",
596            ],
597        ];
598        for (row_idx, row_data) in data.iter().enumerate() {
599            for (col, val) in row_data.iter().enumerate() {
600                tbl.cell(row_idx + 1, col).unwrap().set_text(val);
601            }
602        }
603    }
604
605    // End landscape, return to portrait
606    doc.add_paragraph("")
607        .section_break(SectionBreak::NextPage)
608        .section_landscape();
609
610    // =========================================================================
611    // PAGE 8: BACK TO PORTRAIT — styles, final notes
612    // =========================================================================
613    doc.add_paragraph("7. Custom Styles & Summary")
614        .style("Heading1");
615
616    doc.add_paragraph(
617        "This final page is back in portrait orientation after a section break. \
618         The document has demonstrated:",
619    );
620
621    doc.add_bullet_list_item(
622        "Page setup: size, margins, header/footer distance, gutter",
623        0,
624    );
625    doc.add_bullet_list_item("Document metadata: title, author, subject, keywords", 0);
626    doc.add_bullet_list_item("Headers and footers: text, images, different first page", 0);
627    doc.add_bullet_list_item("Background images: full-page behind text", 0);
628    doc.add_bullet_list_item(
629        "Text formatting: bold, italic, underline, strike, color, size, font",
630        0,
631    );
632    doc.add_bullet_list_item(
633        "Advanced run formatting: superscript, subscript, caps, spacing",
634        0,
635    );
636    doc.add_bullet_list_item(
637        "Paragraph formatting: alignment, borders, shading, spacing, indentation",
638        0,
639    );
640    doc.add_bullet_list_item("Bullet and numbered lists with nesting levels", 0);
641    doc.add_bullet_list_item("Tab stops with dot/underscore leaders", 0);
642    doc.add_bullet_list_item(
643        "Tables: borders, shading, column spans, row spans, nesting",
644        0,
645    );
646    doc.add_bullet_list_item("Vertical alignment in table cells", 0);
647    doc.add_bullet_list_item("Inline images", 0);
648    doc.add_bullet_list_item("Placeholder replacement in body and table cells", 0);
649    doc.add_bullet_list_item("Content insertion at specific positions", 0);
650    doc.add_bullet_list_item(
651        "Section breaks with mixed portrait/landscape orientation",
652        0,
653    );
654
655    doc.add_paragraph("");
656    doc.add_paragraph("All features above were built entirely from scratch using the rdocx API.")
657        .alignment(Alignment::Center)
658        .shading("E2EFDA")
659        .border_all(BorderStyle::Single, 2, "00AA00");
660
661    doc.save(path).unwrap();
662}
examples/generate_all_samples.rs (line 102)
86fn generate_feature_showcase(_samples_dir: &Path) -> Document {
87    let mut doc = Document::new();
88
89    // ── Page Setup & Metadata ──
90    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
91    doc.set_margins(
92        Length::inches(1.0),
93        Length::inches(1.0),
94        Length::inches(1.0),
95        Length::inches(1.0),
96    );
97    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
98    doc.set_gutter(Length::twips(0));
99    doc.set_title("rdocx Feature Showcase");
100    doc.set_author("rdocx Sample Generator");
101    doc.set_subject("Comprehensive feature demonstration");
102    doc.set_keywords("rdocx, docx, rust, sample, showcase");
103
104    // Headers & Footers with different first page
105    doc.set_different_first_page(true);
106    doc.set_first_page_header("rdocx");
107    doc.set_first_page_footer("Feature Showcase — Cover Page");
108    doc.set_header("rdocx Feature Showcase");
109    doc.set_footer("Generated by rdocx");
110
111    // ── COVER PAGE ──
112    let bg = create_sample_png(612, 792, [20, 45, 90]);
113    doc.add_background_image(&bg, "cover_bg.png");
114
115    for _ in 0..3 {
116        doc.add_paragraph("");
117    }
118    {
119        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
120        p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
121    }
122    {
123        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
124        p.add_run("Complete Feature Showcase")
125            .size(28.0)
126            .color("FFFFFF")
127            .italic(true);
128    }
129    doc.add_paragraph("");
130    {
131        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
132        p.add_run("Every feature of the rdocx Rust library")
133            .size(13.0)
134            .color("B0C4DE");
135    }
136    {
137        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
138        p.add_run("demonstrated in a single document.")
139            .size(13.0)
140            .color("B0C4DE");
141    }
142
143    // ── TABLE OF CONTENTS ──
144    doc.add_paragraph("").page_break_before(true);
145    doc.insert_toc(doc.content_count(), 3);
146
147    // ── SECTION 1: TEXT FORMATTING ──
148    doc.add_paragraph("").page_break_before(true);
149    doc.add_paragraph("1. Text Formatting").style("Heading1");
150
151    // Paragraph Alignment
152    doc.add_paragraph("1.1 Paragraph Alignment")
153        .style("Heading2");
154    doc.add_paragraph("Left-aligned paragraph (default).")
155        .alignment(Alignment::Left);
156    doc.add_paragraph("Center-aligned paragraph.")
157        .alignment(Alignment::Center);
158    doc.add_paragraph("Right-aligned paragraph.")
159        .alignment(Alignment::Right);
160    doc.add_paragraph(
161        "Justified paragraph — this text is long enough to demonstrate how justified alignment \
162         distributes extra space across word gaps so lines fill the full width of the text area.",
163    )
164    .alignment(Alignment::Justify);
165
166    doc.add_paragraph("");
167
168    // Run Formatting
169    doc.add_paragraph("1.2 Run Formatting").style("Heading2");
170    {
171        let mut p = doc.add_paragraph("");
172        p.add_run("Bold").bold(true);
173        p.add_run(" | ");
174        p.add_run("Italic").italic(true);
175        p.add_run(" | ");
176        p.add_run("Bold Italic").bold(true).italic(true);
177        p.add_run(" | ");
178        p.add_run("Underline").underline(true);
179        p.add_run(" | ");
180        p.add_run("Strikethrough").strike(true);
181        p.add_run(" | ");
182        p.add_run("Double Strike").double_strike(true);
183    }
184    {
185        let mut p = doc.add_paragraph("");
186        p.add_run("Red").color("FF0000");
187        p.add_run(" | ");
188        p.add_run("Blue").color("0000FF");
189        p.add_run(" | ");
190        p.add_run("Green").color("00AA00");
191        p.add_run(" | ");
192        p.add_run("Highlighted").highlight("FFFF00");
193    }
194    {
195        let mut p = doc.add_paragraph("");
196        p.add_run("8pt").size(8.0);
197        p.add_run(" | ");
198        p.add_run("11pt").size(11.0);
199        p.add_run(" | ");
200        p.add_run("16pt").size(16.0);
201        p.add_run(" | ");
202        p.add_run("24pt").size(24.0);
203    }
204    {
205        let mut p = doc.add_paragraph("");
206        p.add_run("Arial").font("Arial");
207        p.add_run(" | ");
208        p.add_run("Times New Roman").font("Times New Roman");
209        p.add_run(" | ");
210        p.add_run("Courier New").font("Courier New");
211    }
212    {
213        let mut p = doc.add_paragraph("");
214        p.add_run("H");
215        p.add_run("2").subscript();
216        p.add_run("O (subscript) | E=mc");
217        p.add_run("2").superscript();
218        p.add_run(" (superscript)");
219    }
220    {
221        let mut p = doc.add_paragraph("");
222        p.add_run("ALL CAPS").all_caps(true);
223        p.add_run(" | ");
224        p.add_run("Small Caps").small_caps(true);
225        p.add_run(" | ");
226        p.add_run("Expanded").character_spacing(Length::twips(40));
227        p.add_run(" | ");
228        p.add_run("Hidden text (not visible)").hidden(true);
229    }
230
231    doc.add_paragraph("");
232
233    // Underline Styles
234    doc.add_paragraph("1.3 Underline Styles").style("Heading2");
235    {
236        let mut p = doc.add_paragraph("");
237        p.add_run("Single ")
238            .underline_style(rdocx::UnderlineStyle::Single);
239        p.add_run("Double ")
240            .underline_style(rdocx::UnderlineStyle::Double);
241        p.add_run("Thick ")
242            .underline_style(rdocx::UnderlineStyle::Thick);
243        p.add_run("Dotted ")
244            .underline_style(rdocx::UnderlineStyle::Dotted);
245        p.add_run("Dash ")
246            .underline_style(rdocx::UnderlineStyle::Dash);
247        p.add_run("Wave ")
248            .underline_style(rdocx::UnderlineStyle::Wave);
249    }
250
251    // ── SECTION 2: PARAGRAPH FORMATTING ──
252    doc.add_paragraph("").page_break_before(true);
253    doc.add_paragraph("2. Paragraph Formatting")
254        .style("Heading1");
255
256    doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
257    doc.add_paragraph("Paragraph with light green shading")
258        .shading("E2EFDA");
259    doc.add_paragraph("Paragraph with bottom border")
260        .border_bottom(BorderStyle::Single, 6, "2E75B6");
261    doc.add_paragraph("Paragraph with all borders (red)")
262        .border_all(BorderStyle::Single, 4, "FF0000");
263
264    doc.add_paragraph("2.2 Indentation").style("Heading2");
265    doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
266        .indent_left(Length::inches(1.0))
267        .hanging_indent(Length::inches(0.5));
268    doc.add_paragraph("0.5-inch first-line indent on this paragraph")
269        .first_line_indent(Length::inches(0.5));
270    doc.add_paragraph("Both left and right indent (0.75 inches each)")
271        .indent_left(Length::inches(0.75))
272        .indent_right(Length::inches(0.75));
273
274    doc.add_paragraph("2.3 Spacing & Line Height")
275        .style("Heading2");
276    doc.add_paragraph("Extra space before (24pt) and after (12pt)")
277        .space_before(Length::pt(24.0))
278        .space_after(Length::pt(12.0));
279    doc.add_paragraph(
280        "Double line spacing paragraph. This text should have extra vertical space between \
281         lines to demonstrate line_spacing_multiple(2.0).",
282    )
283    .line_spacing_multiple(2.0);
284    doc.add_paragraph("Exact 20pt line spacing (fixed height).")
285        .line_spacing(20.0);
286
287    doc.add_paragraph("2.4 Pagination Controls")
288        .style("Heading2");
289    doc.add_paragraph("keep_with_next — this paragraph stays with the next")
290        .keep_with_next(true);
291    doc.add_paragraph("(This paragraph was kept with the one above.)");
292    doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
293        .keep_together(true);
294    doc.add_paragraph("widow_control — prevents widow/orphan lines")
295        .widow_control(true);
296
297    // ── SECTION 3: LISTS ──
298    doc.add_paragraph("").page_break_before(true);
299    doc.add_paragraph("3. Lists").style("Heading1");
300
301    doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
302    doc.add_bullet_list_item("First item", 0);
303    doc.add_bullet_list_item("Second item", 0);
304    doc.add_bullet_list_item("Nested level 1", 1);
305    doc.add_bullet_list_item("Nested level 2", 2);
306    doc.add_bullet_list_item("Back to level 1", 1);
307    doc.add_bullet_list_item("Third item", 0);
308
309    doc.add_paragraph("");
310
311    doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
312    doc.add_numbered_list_item("First numbered item", 0);
313    doc.add_numbered_list_item("Second numbered item", 0);
314    doc.add_numbered_list_item("Sub-item A", 1);
315    doc.add_numbered_list_item("Sub-item B", 1);
316    doc.add_numbered_list_item("Third numbered item", 0);
317
318    // ── SECTION 4: TAB STOPS ──
319    doc.add_paragraph("");
320    doc.add_paragraph("4. Tab Stops").style("Heading1");
321
322    doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
323    doc.add_paragraph("Left\tCenter\tRight\tDecimal")
324        .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
325        .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
326        .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
327        .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
328
329    doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
330    doc.add_paragraph("Item\t\tPrice")
331        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
332        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
333        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
334    doc.add_paragraph("Widget\t\t$19.99")
335        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
336        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
337        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
338    doc.add_paragraph("Gadget\t\t$249.50")
339        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
340        .add_tab_stop_with_leader(
341            TabAlignment::Right,
342            Length::inches(4.0),
343            TabLeader::Underscore,
344        )
345        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
346
347    // ── SECTION 5: TABLES ──
348    doc.add_paragraph("").page_break_before(true);
349    doc.add_paragraph("5. Tables").style("Heading1");
350
351    doc.add_paragraph("5.1 Basic Table").style("Heading2");
352    {
353        let mut tbl = doc
354            .add_table(4, 3)
355            .borders(BorderStyle::Single, 4, "000000");
356        for col in 0..3 {
357            tbl.cell(0, col).unwrap().shading("2E75B6");
358        }
359        tbl.cell(0, 0).unwrap().set_text("Name");
360        tbl.cell(0, 1).unwrap().set_text("Role");
361        tbl.cell(0, 2).unwrap().set_text("Location");
362        tbl.cell(1, 0).unwrap().set_text("Walter White");
363        tbl.cell(1, 1).unwrap().set_text("CEO");
364        tbl.cell(1, 2).unwrap().set_text("Albuquerque");
365        tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
366        tbl.cell(2, 1).unwrap().set_text("CTO");
367        tbl.cell(2, 2).unwrap().set_text("Remote");
368        tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
369        tbl.cell(3, 1).unwrap().set_text("Security");
370        tbl.cell(3, 2).unwrap().set_text("Washington");
371    }
372
373    doc.add_paragraph("");
374
375    doc.add_paragraph("5.2 Column Span & Cell Shading")
376        .style("Heading2");
377    {
378        let mut tbl = doc
379            .add_table(3, 4)
380            .borders(BorderStyle::Single, 4, "000000")
381            .width_pct(100.0);
382        tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
383        tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
384        tbl.cell(1, 0).unwrap().set_text("Region");
385        tbl.cell(1, 0).unwrap().shading("D6E4F0");
386        tbl.cell(1, 1).unwrap().set_text("Q1");
387        tbl.cell(1, 1).unwrap().shading("D6E4F0");
388        tbl.cell(1, 2).unwrap().set_text("Q2");
389        tbl.cell(1, 2).unwrap().shading("D6E4F0");
390        tbl.cell(1, 3).unwrap().set_text("Total");
391        tbl.cell(1, 3).unwrap().shading("D6E4F0");
392        tbl.cell(2, 0).unwrap().set_text("Americas");
393        tbl.cell(2, 1).unwrap().set_text("$2.4M");
394        tbl.cell(2, 2).unwrap().set_text("$2.7M");
395        tbl.cell(2, 3).unwrap().set_text("$5.1M");
396    }
397
398    doc.add_paragraph("");
399
400    doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
401    {
402        let mut tbl = doc
403            .add_table(4, 3)
404            .borders(BorderStyle::Single, 4, "000000");
405        tbl.cell(0, 0).unwrap().set_text("Category");
406        tbl.cell(0, 0).unwrap().shading("E2EFDA");
407        tbl.cell(0, 1).unwrap().set_text("Item");
408        tbl.cell(0, 1).unwrap().shading("E2EFDA");
409        tbl.cell(0, 2).unwrap().set_text("Price");
410        tbl.cell(0, 2).unwrap().shading("E2EFDA");
411        tbl.cell(1, 0).unwrap().set_text("Hardware");
412        tbl.cell(1, 0).unwrap().v_merge_restart();
413        tbl.cell(1, 1).unwrap().set_text("Laptop");
414        tbl.cell(1, 2).unwrap().set_text("$1,200");
415        tbl.cell(2, 0).unwrap().v_merge_continue();
416        tbl.cell(2, 1).unwrap().set_text("Monitor");
417        tbl.cell(2, 2).unwrap().set_text("$450");
418        tbl.cell(3, 0).unwrap().set_text("Software");
419        tbl.cell(3, 1).unwrap().set_text("IDE License");
420        tbl.cell(3, 2).unwrap().set_text("$200/yr");
421    }
422
423    doc.add_paragraph("");
424
425    doc.add_paragraph("5.4 Nested Table").style("Heading2");
426    {
427        let mut tbl = doc
428            .add_table(2, 2)
429            .borders(BorderStyle::Single, 6, "2E75B6");
430        tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
431        tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
432        tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
433        {
434            let mut cell = tbl.cell(1, 1).unwrap();
435            cell.set_text("Nested table below:");
436            let mut nested = cell
437                .add_table(2, 2)
438                .borders(BorderStyle::Single, 2, "FF6600");
439            nested.cell(0, 0).unwrap().set_text("A");
440            nested.cell(0, 1).unwrap().set_text("B");
441            nested.cell(1, 0).unwrap().set_text("C");
442            nested.cell(1, 1).unwrap().set_text("D");
443        }
444    }
445
446    doc.add_paragraph("");
447
448    doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
449        .style("Heading2");
450    {
451        let mut tbl = doc
452            .add_table(2, 3)
453            .borders(BorderStyle::Single, 4, "666666");
454        tbl.row(0).unwrap().height(Length::pt(50.0)).header();
455        tbl.cell(0, 0).unwrap().set_text("Top");
456        tbl.cell(0, 0)
457            .unwrap()
458            .vertical_alignment(VerticalAlignment::Top)
459            .shading("FFF2CC");
460        tbl.cell(0, 1).unwrap().set_text("Center");
461        tbl.cell(0, 1)
462            .unwrap()
463            .vertical_alignment(VerticalAlignment::Center)
464            .shading("D9E2F3");
465        tbl.cell(0, 2).unwrap().set_text("Bottom");
466        tbl.cell(0, 2)
467            .unwrap()
468            .vertical_alignment(VerticalAlignment::Bottom)
469            .shading("E2EFDA");
470        tbl.row(1).unwrap().cant_split();
471        tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
472        tbl.cell(1, 0).unwrap().no_wrap();
473        tbl.cell(1, 1).unwrap().set_text("Fixed width");
474        tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
475        tbl.cell(1, 2).unwrap().set_text("Normal");
476    }
477
478    // ── SECTION 6: IMAGES ──
479    doc.add_paragraph("").page_break_before(true);
480    doc.add_paragraph("6. Images").style("Heading1");
481
482    doc.add_paragraph("6.1 Inline Image").style("Heading2");
483    doc.add_paragraph("A blue gradient image below:");
484    let img = create_sample_png(200, 50, [0, 80, 200]);
485    doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
486
487    doc.add_paragraph("");
488    doc.add_paragraph("6.2 Header Image").style("Heading2");
489    let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
490    doc.set_header_image(
491        &hdr_img,
492        "header_logo.png",
493        Length::inches(2.0),
494        Length::inches(0.2),
495    );
496    doc.add_paragraph("The header now contains an inline image (check the top of this page).");
497
498    doc.add_paragraph("");
499    doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
500
501    // ── SECTION 7: CONTENT MANIPULATION ──
502    doc.add_paragraph("").page_break_before(true);
503    doc.add_paragraph("7. Content Manipulation")
504        .style("Heading1");
505
506    doc.add_paragraph("7.1 Placeholder Replacement")
507        .style("Heading2");
508    doc.add_paragraph("Customer: {{customer}}");
509    doc.add_paragraph("Date: {{date}}");
510    doc.add_paragraph("Reference: {{ref_number}}");
511    {
512        let mut tbl = doc
513            .add_table(2, 2)
514            .borders(BorderStyle::Single, 4, "000000");
515        tbl.cell(0, 0).unwrap().set_text("Project");
516        tbl.cell(0, 0).unwrap().shading("D6E4F0");
517        tbl.cell(0, 1).unwrap().set_text("{{project}}");
518        tbl.cell(1, 0).unwrap().set_text("Status");
519        tbl.cell(1, 0).unwrap().shading("D6E4F0");
520        tbl.cell(1, 1).unwrap().set_text("{{status}}");
521    }
522
523    let mut replacements = HashMap::new();
524    replacements.insert("{{customer}}", "Tensorbee Inc.");
525    replacements.insert("{{date}}", "February 22, 2026");
526    replacements.insert("{{ref_number}}", "TB-2026-001");
527    replacements.insert("{{project}}", "Infrastructure Upgrade");
528    replacements.insert("{{status}}", "In Progress");
529    let n = doc.replace_all(&replacements);
530    doc.add_paragraph(&format!("({n} placeholders replaced above)"));
531
532    doc.add_paragraph("");
533
534    doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
535    doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
536    let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
537    doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
538
539    doc.add_paragraph("");
540
541    doc.add_paragraph("7.3 Content Insertion").style("Heading2");
542    doc.add_paragraph("Section A: First content.");
543    doc.add_paragraph("Section C: Third content.");
544    if let Some(idx) = doc.find_content_index("Section C") {
545        doc.insert_paragraph(
546            idx,
547            "Section B: Inserted between A and C via find_content_index().",
548        );
549    }
550
551    // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
552    doc.add_paragraph("").section_break(SectionBreak::NextPage);
553    doc.add_paragraph("8. Section Breaks & Orientation")
554        .style("Heading1");
555    doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
556
557    {
558        let mut tbl = doc
559            .add_table(3, 7)
560            .borders(BorderStyle::Single, 4, "2E75B6");
561        let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
562        for (c, h) in headers.iter().enumerate() {
563            tbl.cell(0, c).unwrap().set_text(h);
564            tbl.cell(0, c).unwrap().shading("2E75B6");
565        }
566        let data = [
567            [
568                "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
569            ],
570            [
571                "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
572            ],
573        ];
574        for (r, row) in data.iter().enumerate() {
575            for (c, v) in row.iter().enumerate() {
576                tbl.cell(r + 1, c).unwrap().set_text(v);
577            }
578        }
579    }
580
581    doc.add_paragraph("")
582        .section_break(SectionBreak::NextPage)
583        .section_landscape();
584
585    // ── SECTION 9: CUSTOM STYLES ──
586    doc.add_paragraph("9. Custom Styles").style("Heading1");
587    doc.add_style(
588        StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
589            .based_on("Normal")
590            .paragraph_properties({
591                let mut ppr = rdocx_oxml::properties::CT_PPr::default();
592                ppr.shading = Some(rdocx_oxml::properties::CT_Shd {
593                    val: "clear".to_string(),
594                    color: None,
595                    fill: Some("FFF2CC".to_string()),
596                });
597                ppr
598            })
599            .run_properties({
600                let mut rpr = rdocx_oxml::properties::CT_RPr::default();
601                rpr.bold = Some(true);
602                rpr.color = Some("C45911".to_string());
603                rpr
604            }),
605    );
606    doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
607        .style("CustomHighlight");
608
609    doc.add_paragraph("");
610
611    // ── SECTION 10: DOCUMENT INTELLIGENCE ──
612    doc.add_paragraph("10. Document Intelligence API")
613        .style("Heading1");
614
615    let wc = doc.word_count();
616    let hc = doc.headings().len();
617    let ic = doc.images().len();
618    let lc = doc.links().len();
619    doc.add_paragraph(&format!("Word count: {wc}"));
620    doc.add_paragraph(&format!("Heading count: {hc}"));
621    doc.add_paragraph(&format!("Image count: {ic}"));
622    doc.add_paragraph(&format!("Link count: {lc}"));
623
624    let outline = doc.document_outline();
625    doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
626
627    let issues = doc.audit_accessibility();
628    doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
629    for issue in &issues {
630        doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
631    }
632
633    // ── SECTION 11: DOCUMENT MERGING ──
634    doc.add_paragraph("");
635    doc.add_paragraph("11. Document Merging").style("Heading1");
636    {
637        let mut other = Document::new();
638        other.add_paragraph("This paragraph was merged from another document using append().");
639        doc.append(&other);
640    }
641
642    doc.add_paragraph("");
643
644    // ── SUMMARY ──
645    doc.add_paragraph("Summary of Demonstrated Features")
646        .style("Heading1");
647    let features = [
648        "Page setup: size, margins, header/footer distance, gutter",
649        "Document metadata: title, author, subject, keywords",
650        "Headers and footers: text, images, different first page",
651        "Background images (full-page behind text)",
652        "Table of Contents generation with bookmarks",
653        "Text formatting: bold, italic, underline styles, strike, color, size, font",
654        "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
655        "Paragraph formatting: alignment, borders, shading, spacing, indentation",
656        "Pagination controls: keep-with-next, keep-together, widow control",
657        "Bullet and numbered lists with nesting",
658        "Tab stops with dot/underscore/hyphen leaders",
659        "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
660        "Row properties: header rows, exact height, cant-split",
661        "Cell properties: width, no-wrap, vertical alignment",
662        "Inline images and header images",
663        "Placeholder replacement (single and batch)",
664        "Regex-based find and replace",
665        "Content insertion at specific positions",
666        "Section breaks with mixed portrait/landscape",
667        "Custom paragraph and character styles",
668        "Document intelligence: word count, headings, outline, images, links",
669        "Accessibility audit",
670        "Document merging (append)",
671        "Export: DOCX, PDF, HTML, Markdown",
672    ];
673    for f in &features {
674        doc.add_bullet_list_item(f, 0);
675    }
676    doc.add_paragraph("");
677    doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
678        .alignment(Alignment::Center)
679        .shading("E2EFDA")
680        .border_all(BorderStyle::Single, 2, "00AA00");
681
682    doc
683}
684
685// =============================================================================
686// 2. PROPOSAL DOCUMENT — Deep navy + gold scheme
687// =============================================================================
688fn generate_proposal(_samples_dir: &Path) -> Document {
689    let mut doc = Document::new();
690
691    // Colors: Navy #1B2A4A, Gold #C5922E, Light #F4F1EB
692    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
693    doc.set_margins(
694        Length::inches(1.0),
695        Length::inches(1.0),
696        Length::inches(1.0),
697        Length::inches(1.0),
698    );
699    doc.set_title("AI Platform Modernization Proposal");
700    doc.set_author("Walter White");
701    doc.set_subject("Technology Proposal");
702    doc.set_keywords("proposal, AI, modernization, Tensorbee");
703
704    // Banner header
705    let logo_img = create_logo_png(220, 48);
706    let banner = build_header_banner_xml(
707        "rId1",
708        &BannerOpts {
709            bg_color: "1B2A4A",
710            banner_width: 7772400,
711            banner_height: 914400,
712            logo_width: 2011680,
713            logo_height: 438912,
714            logo_x_offset: 295125,
715            logo_y_offset: 237744,
716        },
717    );
718    doc.set_raw_header_with_images(
719        banner,
720        &[("rId1", &logo_img, "logo.png")],
721        rdocx_oxml::header_footer::HdrFtrType::Default,
722    );
723    doc.set_different_first_page(true);
724    let cover_banner = build_header_banner_xml(
725        "rId1",
726        &BannerOpts {
727            bg_color: "C5922E",
728            banner_width: 7772400,
729            banner_height: 914400,
730            logo_width: 2011680,
731            logo_height: 438912,
732            logo_x_offset: 295125,
733            logo_y_offset: 237744,
734        },
735    );
736    doc.set_raw_header_with_images(
737        cover_banner,
738        &[("rId1", &logo_img, "logo.png")],
739        rdocx_oxml::header_footer::HdrFtrType::First,
740    );
741    doc.set_margins(
742        Length::twips(2292),
743        Length::twips(1440),
744        Length::twips(1440),
745        Length::twips(1440),
746    );
747    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
748    doc.set_footer("Tensorbee — Confidential");
749
750    // Custom styles
751    doc.add_style(
752        StyleBuilder::paragraph("ProposalTitle", "Proposal Title")
753            .based_on("Normal")
754            .run_properties({
755                let mut rpr = rdocx_oxml::properties::CT_RPr::default();
756                rpr.bold = Some(true);
757                rpr.font_ascii = Some("Georgia".to_string());
758                rpr.font_hansi = Some("Georgia".to_string());
759                rpr.sz = Some(rdocx_oxml::HalfPoint(56)); // half-points → 28pt
760                rpr.color = Some("1B2A4A".to_string());
761                rpr
762            }),
763    );
764
765    // ── Cover Page ──
766    for _ in 0..4 {
767        doc.add_paragraph("");
768    }
769    doc.add_paragraph("AI Platform Modernization")
770        .style("ProposalTitle")
771        .alignment(Alignment::Center);
772    doc.add_paragraph("")
773        .alignment(Alignment::Center)
774        .border_bottom(BorderStyle::Single, 8, "C5922E");
775    {
776        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
777        p.add_run("Prepared for Global Financial Corp.")
778            .size(14.0)
779            .color("666666")
780            .italic(true);
781    }
782    {
783        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
784        p.add_run("Prepared by: Walter White, CEO — Tensorbee")
785            .size(12.0)
786            .color("888888");
787    }
788    {
789        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
790        p.add_run("February 22, 2026").size(12.0).color("888888");
791    }
792
793    // ── TOC ──
794    doc.add_paragraph("").page_break_before(true);
795    doc.add_paragraph("Table of Contents").style("Heading1");
796    // We'll add a manual-style TOC since the insert_toc goes at a specific position
797    doc.insert_toc(doc.content_count(), 2);
798
799    // ── Executive Summary ──
800    doc.add_paragraph("").page_break_before(true);
801    doc.add_paragraph("Executive Summary").style("Heading1");
802    doc.add_paragraph(
803        "Tensorbee proposes a comprehensive modernization of Global Financial Corp's AI platform, \
804         transitioning from legacy batch-processing systems to a real-time inference architecture. \
805         This transformation will reduce model deployment time from weeks to hours, improve \
806         prediction accuracy by an estimated 23%, and deliver $4.2M in annual operational savings.",
807    )
808    .first_line_indent(Length::inches(0.3));
809    doc.add_paragraph(
810        "The project spans three phases over 18 months, with a total investment of $2.8M. \
811         Tensorbee brings deep expertise in ML infrastructure, having successfully completed \
812         similar transformations for three Fortune 500 financial institutions.",
813    )
814    .first_line_indent(Length::inches(0.3));
815
816    // Key metrics table
817    doc.add_paragraph("");
818    {
819        let mut tbl = doc
820            .add_table(5, 2)
821            .borders(BorderStyle::Single, 4, "1B2A4A")
822            .width_pct(80.0);
823        tbl.cell(0, 0).unwrap().set_text("Key Metric");
824        tbl.cell(0, 0).unwrap().shading("1B2A4A");
825        tbl.cell(0, 1).unwrap().set_text("Value");
826        tbl.cell(0, 1).unwrap().shading("1B2A4A");
827        let rows = [
828            ("Total Investment", "$2.8M"),
829            ("Annual Savings", "$4.2M"),
830            ("ROI (Year 1)", "150%"),
831            ("Timeline", "18 months"),
832        ];
833        for (i, (k, v)) in rows.iter().enumerate() {
834            tbl.cell(i + 1, 0).unwrap().set_text(k);
835            if i % 2 == 0 {
836                tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
837            }
838            tbl.cell(i + 1, 1).unwrap().set_text(v);
839            if i % 2 == 0 {
840                tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
841            }
842        }
843    }
844
845    // ── Problem Statement ──
846    doc.add_paragraph("").page_break_before(true);
847    doc.add_paragraph("Problem Statement").style("Heading1");
848    doc.add_paragraph(
849        "Global Financial Corp's current AI infrastructure faces three critical challenges:",
850    );
851    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);
852    doc.add_numbered_list_item("Scalability Constraints — The batch processing architecture cannot handle real-time inference demands during peak trading hours.", 0);
853    doc.add_numbered_list_item("Technical Debt — Legacy Python 2.7 codebases and manual deployment scripts create reliability risks and slow iteration speed.", 0);
854
855    // ── Proposed Solution ──
856    doc.add_paragraph("").page_break_before(true);
857    doc.add_paragraph("Proposed Solution").style("Heading1");
858
859    doc.add_paragraph("Phase 1: Foundation (Months 1-6)")
860        .style("Heading2");
861    doc.add_bullet_list_item("Deploy Kubernetes-based ML serving infrastructure", 0);
862    doc.add_bullet_list_item("Implement CI/CD pipeline for model deployment", 0);
863    doc.add_bullet_list_item("Migrate top 5 production models to new platform", 0);
864
865    doc.add_paragraph("Phase 2: Expansion (Months 7-12)")
866        .style("Heading2");
867    doc.add_bullet_list_item(
868        "Real-time feature engineering pipeline (Apache Kafka + Flink)",
869        0,
870    );
871    doc.add_bullet_list_item("A/B testing framework for model variants", 0);
872    doc.add_bullet_list_item("Automated model monitoring and drift detection", 0);
873
874    doc.add_paragraph("Phase 3: Optimization (Months 13-18)")
875        .style("Heading2");
876    doc.add_bullet_list_item("GPU inference optimization (TensorRT/ONNX Runtime)", 0);
877    doc.add_bullet_list_item("Multi-region deployment for latency reduction", 0);
878    doc.add_bullet_list_item(
879        "Self-service model deployment portal for data science teams",
880        0,
881    );
882
883    // ── Budget ──
884    doc.add_paragraph("Budget Breakdown").style("Heading1");
885    {
886        let mut tbl = doc
887            .add_table(6, 3)
888            .borders(BorderStyle::Single, 4, "1B2A4A")
889            .width_pct(100.0);
890        tbl.cell(0, 0).unwrap().set_text("Category");
891        tbl.cell(0, 0).unwrap().shading("1B2A4A");
892        tbl.cell(0, 1).unwrap().set_text("Cost");
893        tbl.cell(0, 1).unwrap().shading("1B2A4A");
894        tbl.cell(0, 2).unwrap().set_text("Notes");
895        tbl.cell(0, 2).unwrap().shading("1B2A4A");
896        let items = [
897            (
898                "Infrastructure",
899                "$450,000",
900                "Cloud compute + GPU instances",
901            ),
902            ("Engineering Services", "$1,600,000", "12 FTE x 18 months"),
903            (
904                "Software Licenses",
905                "$350,000",
906                "Kafka, monitoring, CI/CD tools",
907            ),
908            (
909                "Training & Enablement",
910                "$200,000",
911                "Team training, documentation",
912            ),
913            ("Contingency (10%)", "$200,000", "Risk buffer"),
914        ];
915        for (i, (cat, cost, note)) in items.iter().enumerate() {
916            tbl.cell(i + 1, 0).unwrap().set_text(cat);
917            tbl.cell(i + 1, 1).unwrap().set_text(cost);
918            tbl.cell(i + 1, 2).unwrap().set_text(note);
919            if i % 2 == 0 {
920                tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
921                tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
922                tbl.cell(i + 1, 2).unwrap().shading("F4F1EB");
923            }
924        }
925    }
926
927    // ── Closing ──
928    doc.add_paragraph("");
929    doc.add_paragraph("Next Steps").style("Heading1");
930    doc.add_numbered_list_item(
931        "Schedule technical deep-dive with GFC engineering team (Week 1)",
932        0,
933    );
934    doc.add_numbered_list_item("Finalize scope and sign SOW (Week 2-3)", 0);
935    doc.add_numbered_list_item("Kick off Phase 1 with joint planning session (Week 4)", 0);
936
937    doc.add_paragraph("");
938    {
939        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
940        p.add_run("Walter White")
941            .bold(true)
942            .size(14.0)
943            .color("1B2A4A");
944    }
945    {
946        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
947        p.add_run("CEO, Tensorbee | walter@tensorbee.com")
948            .size(10.0)
949            .color("888888");
950    }
951
952    doc
953}
954
955// =============================================================================
956// 3. QUOTE / BILL OF MATERIALS — Teal + orange scheme
957// =============================================================================
958fn generate_quote(_samples_dir: &Path) -> Document {
959    let mut doc = Document::new();
960
961    // Colors: Teal #008B8B, Dark #1A3C3C, Orange #E07020, Light #F0F8F8
962    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
963    doc.set_margins(
964        Length::inches(0.75),
965        Length::inches(0.75),
966        Length::inches(0.75),
967        Length::inches(0.75),
968    );
969    doc.set_title("Quotation QT-2026-0147");
970    doc.set_author("Walter White");
971    doc.set_keywords("quote, BOM, Tensorbee");
972
973    doc.set_header("Tensorbee — Quotation");
974    doc.set_footer("QT-2026-0147 | Page");
975
976    // ── Header Block ──
977    {
978        let mut p = doc.add_paragraph("").alignment(Alignment::Left);
979        p.add_run("TENSORBEE")
980            .bold(true)
981            .size(28.0)
982            .color("008B8B")
983            .font("Helvetica");
984    }
985    doc.add_paragraph("123 Innovation Drive, Suite 400")
986        .alignment(Alignment::Left);
987    doc.add_paragraph("San Francisco, CA 94105 | +1 (415) 555-0199")
988        .alignment(Alignment::Left);
989    doc.add_paragraph("")
990        .border_bottom(BorderStyle::Single, 8, "008B8B");
991
992    // Quote meta
993    doc.add_paragraph("");
994    {
995        let mut tbl = doc.add_table(4, 4).width_pct(100.0);
996        tbl.cell(0, 0).unwrap().set_text("QUOTATION");
997        tbl.cell(0, 0).unwrap().shading("008B8B").grid_span(2);
998        tbl.cell(0, 2).unwrap().set_text("DATE");
999        tbl.cell(0, 2).unwrap().shading("008B8B");
1000        tbl.cell(0, 3).unwrap().set_text("VALID UNTIL");
1001        tbl.cell(0, 3).unwrap().shading("008B8B");
1002        tbl.cell(1, 0).unwrap().set_text("Quote #:");
1003        tbl.cell(1, 1).unwrap().set_text("QT-2026-0147");
1004        tbl.cell(1, 2).unwrap().set_text("Feb 22, 2026");
1005        tbl.cell(1, 3).unwrap().set_text("Mar 22, 2026");
1006        tbl.cell(2, 0).unwrap().set_text("Prepared by:");
1007        tbl.cell(2, 1).unwrap().set_text("Walter White");
1008        tbl.cell(2, 2).unwrap().set_text("Payment:");
1009        tbl.cell(2, 3).unwrap().set_text("Net 30");
1010        tbl.cell(3, 0).unwrap().set_text("Customer:");
1011        tbl.cell(3, 1).unwrap().set_text("Meridian Dynamics LLC");
1012        tbl.cell(3, 2).unwrap().set_text("Currency:");
1013        tbl.cell(3, 3).unwrap().set_text("USD");
1014    }
1015
1016    doc.add_paragraph("");
1017
1018    // ── Bill of Materials ──
1019    {
1020        let mut p = doc.add_paragraph("");
1021        p.add_run("Bill of Materials")
1022            .bold(true)
1023            .size(16.0)
1024            .color("1A3C3C");
1025    }
1026    doc.add_paragraph("");
1027
1028    {
1029        let mut tbl = doc
1030            .add_table(10, 6)
1031            .borders(BorderStyle::Single, 2, "008B8B")
1032            .width_pct(100.0)
1033            .layout_fixed();
1034        // Headers
1035        let hdrs = [
1036            "#",
1037            "Part Number",
1038            "Description",
1039            "Qty",
1040            "Unit Price",
1041            "Total",
1042        ];
1043        for (c, h) in hdrs.iter().enumerate() {
1044            tbl.cell(0, c).unwrap().set_text(h);
1045            tbl.cell(0, c).unwrap().shading("008B8B");
1046        }
1047
1048        let items: Vec<(&str, &str, &str, &str, &str)> = vec![
1049            (
1050                "TB-GPU-A100",
1051                "NVIDIA A100 80GB GPU",
1052                "4",
1053                "$12,500.00",
1054                "$50,000.00",
1055            ),
1056            (
1057                "TB-SRV-R750",
1058                "Dell R750xa Server Chassis",
1059                "2",
1060                "$8,200.00",
1061                "$16,400.00",
1062            ),
1063            (
1064                "TB-RAM-256G",
1065                "256GB DDR5 ECC Memory Module",
1066                "8",
1067                "$890.00",
1068                "$7,120.00",
1069            ),
1070            (
1071                "TB-SSD-3840",
1072                "3.84TB NVMe U.2 SSD",
1073                "8",
1074                "$1,150.00",
1075                "$9,200.00",
1076            ),
1077            (
1078                "TB-NET-CX7",
1079                "ConnectX-7 200GbE NIC",
1080                "4",
1081                "$1,800.00",
1082                "$7,200.00",
1083            ),
1084            (
1085                "TB-SW-48P",
1086                "48-Port 100GbE Switch",
1087                "1",
1088                "$22,000.00",
1089                "$22,000.00",
1090            ),
1091            (
1092                "TB-CAB-RACK",
1093                "42U Server Rack + PDU",
1094                "1",
1095                "$4,500.00",
1096                "$4,500.00",
1097            ),
1098            (
1099                "TB-SVC-INST",
1100                "Installation & Configuration",
1101                "1",
1102                "$8,500.00",
1103                "$8,500.00",
1104            ),
1105            (
1106                "TB-SVC-SUPP",
1107                "3-Year Premium Support",
1108                "1",
1109                "$15,000.00",
1110                "$15,000.00",
1111            ),
1112        ];
1113
1114        for (i, (pn, desc, qty, unit, total)) in items.iter().enumerate() {
1115            let row = i + 1;
1116            tbl.cell(row, 0).unwrap().set_text(&format!("{}", i + 1));
1117            tbl.cell(row, 1).unwrap().set_text(pn);
1118            tbl.cell(row, 2).unwrap().set_text(desc);
1119            tbl.cell(row, 3).unwrap().set_text(qty);
1120            tbl.cell(row, 4).unwrap().set_text(unit);
1121            tbl.cell(row, 5).unwrap().set_text(total);
1122            if i % 2 == 0 {
1123                for c in 0..6 {
1124                    tbl.cell(row, c).unwrap().shading("F0F8F8");
1125                }
1126            }
1127        }
1128    }
1129
1130    doc.add_paragraph("");
1131
1132    // ── Totals ──
1133    {
1134        let mut tbl = doc
1135            .add_table(4, 2)
1136            .borders(BorderStyle::Single, 2, "008B8B")
1137            .width(Length::inches(3.5))
1138            .alignment(Alignment::Right);
1139        tbl.cell(0, 0).unwrap().set_text("Subtotal");
1140        tbl.cell(0, 1).unwrap().set_text("$139,920.00");
1141        tbl.cell(1, 0).unwrap().set_text("Shipping & Handling");
1142        tbl.cell(1, 1).unwrap().set_text("$2,500.00");
1143        tbl.cell(2, 0).unwrap().set_text("Tax (8.625%)");
1144        tbl.cell(2, 1).unwrap().set_text("$12,068.10");
1145        tbl.cell(3, 0).unwrap().set_text("TOTAL");
1146        tbl.cell(3, 0).unwrap().shading("E07020");
1147        tbl.cell(3, 1).unwrap().set_text("$154,488.10");
1148        tbl.cell(3, 1).unwrap().shading("E07020");
1149    }
1150
1151    doc.add_paragraph("");
1152
1153    // ── Terms & Conditions ──
1154    {
1155        let mut p = doc.add_paragraph("");
1156        p.add_run("Terms & Conditions")
1157            .bold(true)
1158            .size(14.0)
1159            .color("1A3C3C");
1160    }
1161    doc.add_numbered_list_item(
1162        "This quotation is valid for 30 calendar days from the date of issue.",
1163        0,
1164    );
1165    doc.add_numbered_list_item(
1166        "All prices are in USD and exclusive of applicable taxes unless stated.",
1167        0,
1168    );
1169    doc.add_numbered_list_item("Standard lead time is 4-6 weeks from PO receipt.", 0);
1170    doc.add_numbered_list_item(
1171        "Payment terms: Net 30 from invoice date. 2% discount for payment within 10 days.",
1172        0,
1173    );
1174    doc.add_numbered_list_item(
1175        "Warranty: 3-year manufacturer warranty on all hardware components.",
1176        0,
1177    );
1178    doc.add_numbered_list_item(
1179        "Returns subject to 15% restocking fee if initiated after 14 days.",
1180        0,
1181    );
1182
1183    doc.add_paragraph("");
1184    doc.add_paragraph("")
1185        .border_bottom(BorderStyle::Single, 4, "008B8B");
1186    doc.add_paragraph("");
1187    {
1188        let mut p = doc.add_paragraph("");
1189        p.add_run("Accepted by: ").bold(true);
1190        p.add_run("___________________________ Date: ___________");
1191    }
1192    {
1193        let mut p = doc.add_paragraph("");
1194        p.add_run("Print Name: ").bold(true);
1195        p.add_run("___________________________ Title: ___________");
1196    }
1197
1198    doc
1199}
1200
1201// =============================================================================
1202// 4. INVOICE — Crimson + charcoal scheme
1203// =============================================================================
1204fn generate_invoice(_samples_dir: &Path) -> Document {
1205    let mut doc = Document::new();
1206
1207    // Colors: Crimson #B22222, Charcoal #333333, Light #FAF0F0
1208    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1209    doc.set_margins(
1210        Length::inches(0.75),
1211        Length::inches(0.75),
1212        Length::inches(0.75),
1213        Length::inches(0.75),
1214    );
1215    doc.set_title("Invoice INV-2026-0392");
1216    doc.set_author("Walter White");
1217    doc.set_keywords("invoice, Tensorbee");
1218
1219    doc.set_footer("Tensorbee — Thank you for your business!");
1220
1221    // ── Company & Invoice Header ──
1222    {
1223        let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1224        // Company name left, INVOICE right
1225        {
1226            let mut cell = tbl.cell(0, 0).unwrap();
1227            let mut p = cell.add_paragraph("");
1228            p.add_run("TENSORBEE")
1229                .bold(true)
1230                .size(32.0)
1231                .color("B22222")
1232                .font("Helvetica");
1233        }
1234        {
1235            let mut cell = tbl.cell(0, 1).unwrap();
1236            let mut p = cell.add_paragraph("");
1237            p.add_run("INVOICE").bold(true).size(32.0).color("333333");
1238        }
1239        tbl.cell(1, 0)
1240            .unwrap()
1241            .set_text("123 Innovation Drive, Suite 400");
1242        tbl.cell(1, 1).unwrap().set_text("Invoice #: INV-2026-0392");
1243        tbl.cell(2, 0).unwrap().set_text("San Francisco, CA 94105");
1244        tbl.cell(2, 1).unwrap().set_text("Date: February 22, 2026");
1245        tbl.cell(3, 0).unwrap().set_text("walter@tensorbee.com");
1246        tbl.cell(3, 1).unwrap().set_text("Due Date: March 24, 2026");
1247    }
1248
1249    doc.add_paragraph("")
1250        .border_bottom(BorderStyle::Thick, 8, "B22222");
1251    doc.add_paragraph("");
1252
1253    // ── Bill To / Ship To ──
1254    {
1255        let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1256        tbl.cell(0, 0).unwrap().set_text("BILL TO");
1257        tbl.cell(0, 0).unwrap().shading("B22222");
1258        tbl.cell(0, 1).unwrap().set_text("SHIP TO");
1259        tbl.cell(0, 1).unwrap().shading("B22222");
1260        tbl.cell(1, 0).unwrap().set_text("Meridian Dynamics LLC");
1261        tbl.cell(1, 1).unwrap().set_text("Meridian Dynamics LLC");
1262        tbl.cell(2, 0).unwrap().set_text("456 Enterprise Blvd");
1263        tbl.cell(2, 1).unwrap().set_text("Attn: Server Room B");
1264        tbl.cell(3, 0).unwrap().set_text("Austin, TX 78701");
1265        tbl.cell(3, 1)
1266            .unwrap()
1267            .set_text("456 Enterprise Blvd, Austin, TX 78701");
1268    }
1269
1270    doc.add_paragraph("");
1271
1272    // ── Line Items ──
1273    {
1274        let mut tbl = doc
1275            .add_table(8, 5)
1276            .borders(BorderStyle::Single, 2, "B22222")
1277            .width_pct(100.0);
1278        let hdrs = ["Description", "Qty", "Unit Price", "Tax", "Amount"];
1279        for (c, h) in hdrs.iter().enumerate() {
1280            tbl.cell(0, c).unwrap().set_text(h);
1281            tbl.cell(0, c).unwrap().shading("B22222");
1282        }
1283        let items = [
1284            (
1285                "ML Infrastructure Setup — Phase 1",
1286                "1",
1287                "$45,000.00",
1288                "$3,881.25",
1289                "$48,881.25",
1290            ),
1291            (
1292                "Data Pipeline Development (160 hrs)",
1293                "160",
1294                "$225.00",
1295                "$3,105.00",
1296                "$39,105.00",
1297            ),
1298            (
1299                "GPU Cluster Configuration",
1300                "1",
1301                "$12,000.00",
1302                "$1,035.00",
1303                "$13,035.00",
1304            ),
1305            (
1306                "API Gateway Implementation",
1307                "1",
1308                "$18,500.00",
1309                "$1,595.63",
1310                "$20,095.63",
1311            ),
1312            (
1313                "Load Testing & QA (80 hrs)",
1314                "80",
1315                "$195.00",
1316                "$1,345.50",
1317                "$16,945.50",
1318            ),
1319            (
1320                "Documentation & Training",
1321                "1",
1322                "$8,000.00",
1323                "$690.00",
1324                "$8,690.00",
1325            ),
1326            (
1327                "Project Management (3 months)",
1328                "3",
1329                "$6,500.00",
1330                "$1,679.63",
1331                "$21,179.63",
1332            ),
1333        ];
1334        for (i, (desc, qty, unit, tax, amt)) in items.iter().enumerate() {
1335            let r = i + 1;
1336            tbl.cell(r, 0).unwrap().set_text(desc);
1337            tbl.cell(r, 1).unwrap().set_text(qty);
1338            tbl.cell(r, 2).unwrap().set_text(unit);
1339            tbl.cell(r, 3).unwrap().set_text(tax);
1340            tbl.cell(r, 4).unwrap().set_text(amt);
1341            if i % 2 == 0 {
1342                for c in 0..5 {
1343                    tbl.cell(r, c).unwrap().shading("FAF0F0");
1344                }
1345            }
1346        }
1347    }
1348
1349    doc.add_paragraph("");
1350
1351    // ── Totals ──
1352    {
1353        let mut tbl = doc
1354            .add_table(4, 2)
1355            .borders(BorderStyle::Single, 2, "B22222")
1356            .width(Length::inches(3.0))
1357            .alignment(Alignment::Right);
1358        tbl.cell(0, 0).unwrap().set_text("Subtotal");
1359        tbl.cell(0, 1).unwrap().set_text("$154,600.00");
1360        tbl.cell(1, 0).unwrap().set_text("Tax (8.625%)");
1361        tbl.cell(1, 1).unwrap().set_text("$13,334.25");
1362        tbl.cell(2, 0).unwrap().set_text("Discount (5%)");
1363        tbl.cell(2, 1).unwrap().set_text("-$7,730.00");
1364        tbl.cell(3, 0).unwrap().set_text("AMOUNT DUE");
1365        tbl.cell(3, 0).unwrap().shading("B22222");
1366        tbl.cell(3, 1).unwrap().set_text("$160,204.25");
1367        tbl.cell(3, 1).unwrap().shading("B22222");
1368    }
1369
1370    doc.add_paragraph("");
1371    doc.add_paragraph("");
1372
1373    // ── Payment Details ──
1374    {
1375        let mut p = doc.add_paragraph("");
1376        p.add_run("Payment Details")
1377            .bold(true)
1378            .size(14.0)
1379            .color("333333");
1380    }
1381    doc.add_paragraph("Bank: Silicon Valley Bank")
1382        .indent_left(Length::inches(0.3));
1383    doc.add_paragraph("Account: Tensorbee Inc. — 0847-2953-1120")
1384        .indent_left(Length::inches(0.3));
1385    doc.add_paragraph("Routing: 121140399")
1386        .indent_left(Length::inches(0.3));
1387    doc.add_paragraph("Swift: SVBKUS6S")
1388        .indent_left(Length::inches(0.3));
1389
1390    doc.add_paragraph("");
1391
1392    doc.add_paragraph("Please include invoice number INV-2026-0392 in the payment reference.")
1393        .shading("FAF0F0")
1394        .border_all(BorderStyle::Single, 2, "B22222");
1395
1396    doc
1397}
1398
1399// =============================================================================
1400// 5. REPORT — Forest green + earth tones, with images & hierarchical sections
1401// =============================================================================
1402fn generate_report(_samples_dir: &Path) -> Document {
1403    let mut doc = Document::new();
1404
1405    // Colors: Forest #2D5016, Sage #6B8E23, Earth #8B7355, Cream #FFFAF0
1406    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1407    doc.set_margins(
1408        Length::inches(1.0),
1409        Length::inches(1.0),
1410        Length::inches(1.0),
1411        Length::inches(1.0),
1412    );
1413    doc.set_title("Q4 2025 Environmental Impact Report");
1414    doc.set_author("Walter White");
1415    doc.set_subject("Quarterly Environmental Report");
1416    doc.set_keywords("environment, sustainability, report, Tensorbee");
1417
1418    doc.set_different_first_page(true);
1419    doc.set_first_page_header("");
1420    doc.set_header("Tensorbee — Q4 2025 Environmental Impact Report");
1421    doc.set_footer("CONFIDENTIAL — Page");
1422
1423    // ── Cover ──
1424    let cover_bg = create_sample_png(612, 792, [20, 50, 15]);
1425    doc.add_background_image(&cover_bg, "report_cover.png");
1426
1427    for _ in 0..5 {
1428        doc.add_paragraph("");
1429    }
1430    {
1431        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1432        p.add_run("Q4 2025")
1433            .bold(true)
1434            .size(48.0)
1435            .color("FFFFFF")
1436            .font("Georgia");
1437    }
1438    {
1439        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1440        p.add_run("Environmental Impact Report")
1441            .size(24.0)
1442            .color("90EE90")
1443            .font("Georgia")
1444            .italic(true);
1445    }
1446    doc.add_paragraph("");
1447    {
1448        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1449        p.add_run("Tensorbee — Sustainability Division")
1450            .size(14.0)
1451            .color("C0C0C0");
1452    }
1453    {
1454        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1455        p.add_run("Prepared by Walter White, Chief Sustainability Officer")
1456            .size(11.0)
1457            .color("AAAAAA");
1458    }
1459
1460    // ── TOC ──
1461    doc.add_paragraph("").page_break_before(true);
1462    doc.insert_toc(doc.content_count(), 3);
1463
1464    // ── Section 1: Executive Overview ──
1465    doc.add_paragraph("").page_break_before(true);
1466    doc.add_paragraph("Executive Overview").style("Heading1");
1467    doc.add_paragraph(
1468        "This report presents Tensorbee's environmental performance for Q4 2025. Our \
1469         sustainability initiatives have yielded a 34% reduction in carbon emissions compared \
1470         to Q4 2024, exceeding our target of 25%. Key achievements include the transition to \
1471         100% renewable energy in our primary data centers and the launch of our carbon offset \
1472         marketplace.",
1473    )
1474    .first_line_indent(Length::inches(0.3));
1475
1476    // Image: performance chart
1477    let chart_img = create_chart_png(400, 200);
1478    doc.add_paragraph("");
1479    doc.add_picture(
1480        &chart_img,
1481        "performance_chart.png",
1482        Length::inches(5.0),
1483        Length::inches(2.5),
1484    )
1485    .alignment(Alignment::Center);
1486    {
1487        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1488        p.add_run("Figure 1: Quarterly Carbon Emissions (tonnes CO2e)")
1489            .italic(true)
1490            .size(9.0)
1491            .color("666666");
1492    }
1493
1494    // ── Section 2: Energy Consumption ──
1495    doc.add_paragraph("").page_break_before(true);
1496    doc.add_paragraph("Energy Consumption").style("Heading1");
1497
1498    doc.add_paragraph("Data Center Operations")
1499        .style("Heading2");
1500    doc.add_paragraph(
1501        "Our three primary data centers consumed a combined 4.2 GWh in Q4 2025, \
1502         a 12% reduction from Q3 2025 driven by improved cooling efficiency and \
1503         server consolidation.",
1504    );
1505
1506    // Data table
1507    {
1508        let mut tbl = doc
1509            .add_table(5, 4)
1510            .borders(BorderStyle::Single, 4, "2D5016")
1511            .width_pct(100.0);
1512        let hdrs = ["Data Center", "Capacity (MW)", "Usage (GWh)", "PUE"];
1513        for (c, h) in hdrs.iter().enumerate() {
1514            tbl.cell(0, c).unwrap().set_text(h);
1515            tbl.cell(0, c).unwrap().shading("2D5016");
1516        }
1517        let rows = [
1518            ("San Francisco (Primary)", "3.2", "1.8", "1.12"),
1519            ("Dublin (EU)", "2.1", "1.2", "1.18"),
1520            ("Singapore (APAC)", "1.8", "1.2", "1.24"),
1521            ("TOTAL", "7.1", "4.2", "1.17 avg"),
1522        ];
1523        for (i, (dc, cap, use_, pue)) in rows.iter().enumerate() {
1524            tbl.cell(i + 1, 0).unwrap().set_text(dc);
1525            tbl.cell(i + 1, 1).unwrap().set_text(cap);
1526            tbl.cell(i + 1, 2).unwrap().set_text(use_);
1527            tbl.cell(i + 1, 3).unwrap().set_text(pue);
1528            if i == 3 {
1529                for c in 0..4 {
1530                    tbl.cell(i + 1, c).unwrap().shading("E2EFDA");
1531                }
1532            }
1533        }
1534    }
1535
1536    doc.add_paragraph("");
1537
1538    doc.add_paragraph("Renewable Energy Mix").style("Heading2");
1539    doc.add_paragraph("Breakdown of energy sources across all facilities:");
1540
1541    doc.add_bullet_list_item("Solar PV: 42% (1.76 GWh)", 0);
1542    doc.add_bullet_list_item("Wind Power Purchase Agreements: 38% (1.60 GWh)", 0);
1543    doc.add_bullet_list_item("Hydroelectric: 12% (0.50 GWh)", 0);
1544    doc.add_bullet_list_item("Grid (non-renewable): 8% (0.34 GWh)", 0);
1545
1546    // ── Section 3: Water & Waste ──
1547    doc.add_paragraph("Water & Waste Management")
1548        .style("Heading1");
1549
1550    doc.add_paragraph("Water Usage").style("Heading2");
1551    doc.add_paragraph(
1552        "Total water consumption was 12.4 million gallons, a 15% reduction from Q3. \
1553         Our closed-loop cooling systems now recycle 78% of water used in cooling operations.",
1554    );
1555
1556    doc.add_paragraph("Waste Reduction").style("Heading2");
1557    doc.add_paragraph("E-waste management results for Q4:")
1558        .keep_with_next(true);
1559    {
1560        let mut tbl = doc
1561            .add_table(4, 3)
1562            .borders(BorderStyle::Single, 2, "6B8E23")
1563            .width_pct(80.0);
1564        tbl.cell(0, 0).unwrap().set_text("Category");
1565        tbl.cell(0, 0).unwrap().shading("6B8E23");
1566        tbl.cell(0, 1).unwrap().set_text("Weight (kg)");
1567        tbl.cell(0, 1).unwrap().shading("6B8E23");
1568        tbl.cell(0, 2).unwrap().set_text("Recycled %");
1569        tbl.cell(0, 2).unwrap().shading("6B8E23");
1570        let rows = [
1571            ("Server Hardware", "2,450", "94%"),
1572            ("Networking Equipment", "820", "91%"),
1573            ("Storage Media", "340", "99%"),
1574        ];
1575        for (i, (cat, wt, pct)) in rows.iter().enumerate() {
1576            tbl.cell(i + 1, 0).unwrap().set_text(cat);
1577            tbl.cell(i + 1, 1).unwrap().set_text(wt);
1578            tbl.cell(i + 1, 2).unwrap().set_text(pct);
1579        }
1580    }
1581
1582    // ── Section 4: Initiatives ──
1583    doc.add_paragraph("").page_break_before(true);
1584    doc.add_paragraph("Q1 2026 Initiatives").style("Heading1");
1585
1586    doc.add_paragraph("Planned Programs").style("Heading2");
1587    doc.add_numbered_list_item(
1588        "Deploy on-site battery storage at SF data center (2 MWh capacity)",
1589        0,
1590    );
1591    doc.add_numbered_list_item("Pilot immersion cooling in Dublin facility", 0);
1592    doc.add_numbered_list_item("Launch employee commute carbon offset program", 0);
1593    doc.add_numbered_list_item("Achieve ISO 14001 certification for Singapore facility", 0);
1594
1595    doc.add_paragraph("Investment Targets").style("Heading2");
1596    doc.add_paragraph("Sustainability CapEx allocation for FY2026:")
1597        .keep_with_next(true);
1598    {
1599        let mut tbl = doc
1600            .add_table(5, 2)
1601            .borders(BorderStyle::Single, 4, "2D5016");
1602        tbl.cell(0, 0).unwrap().set_text("Initiative");
1603        tbl.cell(0, 0).unwrap().shading("2D5016");
1604        tbl.cell(0, 1).unwrap().set_text("Budget");
1605        tbl.cell(0, 1).unwrap().shading("2D5016");
1606        let rows = [
1607            ("Battery Storage", "$1.2M"),
1608            ("Immersion Cooling Pilot", "$800K"),
1609            ("Solar Panel Expansion", "$2.1M"),
1610            ("Carbon Credits", "$500K"),
1611        ];
1612        for (i, (init, budget)) in rows.iter().enumerate() {
1613            tbl.cell(i + 1, 0).unwrap().set_text(init);
1614            tbl.cell(i + 1, 1).unwrap().set_text(budget);
1615            if i % 2 == 0 {
1616                tbl.cell(i + 1, 0).unwrap().shading("FFFAF0");
1617                tbl.cell(i + 1, 1).unwrap().shading("FFFAF0");
1618            }
1619        }
1620    }
1621
1622    // Image: sustainability roadmap
1623    let roadmap_img = create_sample_png(500, 100, [40, 80, 30]);
1624    doc.add_paragraph("");
1625    doc.add_picture(
1626        &roadmap_img,
1627        "roadmap.png",
1628        Length::inches(6.0),
1629        Length::inches(1.2),
1630    )
1631    .alignment(Alignment::Center);
1632    {
1633        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1634        p.add_run("Figure 2: 2026 Sustainability Roadmap")
1635            .italic(true)
1636            .size(9.0)
1637            .color("666666");
1638    }
1639
1640    doc.add_paragraph("");
1641    doc.add_paragraph(
1642        "For questions about this report, contact Walter White at sustainability@tensorbee.com.",
1643    )
1644    .shading("FFFAF0")
1645    .border_all(BorderStyle::Single, 2, "2D5016");
1646
1647    doc
1648}
1649
1650// =============================================================================
1651// 6. LETTER — Slate blue + silver scheme
1652// =============================================================================
1653fn generate_letter(_samples_dir: &Path) -> Document {
1654    let mut doc = Document::new();
1655
1656    // Colors: Slate #4A5568, Blue accent #3182CE
1657    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1658    doc.set_margins(
1659        Length::inches(1.25),
1660        Length::inches(1.25),
1661        Length::inches(1.25),
1662        Length::inches(1.25),
1663    );
1664    doc.set_title("Business Letter");
1665    doc.set_author("Walter White");
1666
1667    // Banner header using raw XML
1668    let logo_img = create_logo_png(220, 48);
1669    let banner = build_header_banner_xml(
1670        "rId1",
1671        &BannerOpts {
1672            bg_color: "4A5568",
1673            banner_width: 7772400,
1674            banner_height: 731520, // ~0.8"
1675            logo_width: 2011680,
1676            logo_height: 438912,
1677            logo_x_offset: 295125,
1678            logo_y_offset: 146304,
1679        },
1680    );
1681    doc.set_raw_header_with_images(
1682        banner,
1683        &[("rId1", &logo_img, "logo.png")],
1684        rdocx_oxml::header_footer::HdrFtrType::Default,
1685    );
1686    doc.set_margins(
1687        Length::twips(2000),
1688        Length::twips(1800),
1689        Length::twips(1440),
1690        Length::twips(1800),
1691    );
1692    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
1693
1694    // Footer with contact
1695    doc.set_footer(
1696        "Tensorbee | 123 Innovation Drive, Suite 400 | San Francisco, CA 94105 | tensorbee.com",
1697    );
1698
1699    // ── Sender Address ──
1700    doc.add_paragraph("Walter White");
1701    doc.add_paragraph("Chief Executive Officer");
1702    doc.add_paragraph("Tensorbee");
1703    doc.add_paragraph("123 Innovation Drive, Suite 400");
1704    doc.add_paragraph("San Francisco, CA 94105");
1705    doc.add_paragraph("");
1706    doc.add_paragraph("February 22, 2026");
1707
1708    doc.add_paragraph("");
1709
1710    // ── Recipient ──
1711    doc.add_paragraph("Ms. Sarah Chen");
1712    doc.add_paragraph("VP of Engineering");
1713    doc.add_paragraph("Meridian Dynamics LLC");
1714    doc.add_paragraph("456 Enterprise Boulevard");
1715    doc.add_paragraph("Austin, TX 78701");
1716
1717    doc.add_paragraph("");
1718
1719    // ── Subject ──
1720    {
1721        let mut p = doc.add_paragraph("");
1722        p.add_run("Re: Strategic Technology Partnership — Phase 2 Expansion")
1723            .bold(true);
1724    }
1725
1726    doc.add_paragraph("");
1727
1728    // ── Body ──
1729    doc.add_paragraph("Dear Ms. Chen,");
1730    doc.add_paragraph("");
1731
1732    doc.add_paragraph(
1733        "I am writing to express Tensorbee's enthusiasm for expanding our strategic technology \
1734         partnership with Meridian Dynamics. Following the successful completion of Phase 1, \
1735         which delivered a 40% improvement in your ML inference pipeline throughput, we believe \
1736         the foundation is firmly established for an ambitious Phase 2 engagement.",
1737    )
1738    .first_line_indent(Length::inches(0.5));
1739
1740    doc.add_paragraph(
1741        "During our recent executive review, your team highlighted three priority areas for \
1742         the next phase of collaboration:",
1743    )
1744    .first_line_indent(Length::inches(0.5));
1745
1746    doc.add_numbered_list_item(
1747        "Real-time anomaly detection for your financial transaction monitoring system, \
1748         targeting sub-100ms latency at 50,000 transactions per second.",
1749        0,
1750    );
1751    doc.add_numbered_list_item(
1752        "Federated learning infrastructure to enable model training across your distributed \
1753         data centers without centralizing sensitive financial data.",
1754        0,
1755    );
1756    doc.add_numbered_list_item(
1757        "MLOps automation to reduce your model deployment cycle from the current 5 days \
1758         to under 4 hours.",
1759        0,
1760    );
1761
1762    doc.add_paragraph(
1763        "Our engineering team has prepared a detailed technical proposal addressing each of \
1764         these areas. We have allocated a dedicated team of eight senior engineers, led by \
1765         our CTO, to ensure continuity with the Phase 1 team your organization has already \
1766         built a productive working relationship with.",
1767    )
1768    .first_line_indent(Length::inches(0.5));
1769
1770    doc.add_paragraph(
1771        "I would welcome the opportunity to present our Phase 2 proposal in person. My \
1772         assistant will reach out to coordinate a meeting at your convenience during the \
1773         first week of March.",
1774    )
1775    .first_line_indent(Length::inches(0.5));
1776
1777    doc.add_paragraph("");
1778
1779    doc.add_paragraph("Warm regards,");
1780    doc.add_paragraph("");
1781    doc.add_paragraph("");
1782
1783    // Signature line
1784    doc.add_paragraph("")
1785        .border_bottom(BorderStyle::Single, 4, "4A5568");
1786    {
1787        let mut p = doc.add_paragraph("");
1788        p.add_run("Walter White").bold(true).size(12.0);
1789    }
1790    {
1791        let mut p = doc.add_paragraph("");
1792        p.add_run("Chief Executive Officer, Tensorbee")
1793            .italic(true)
1794            .size(10.0)
1795            .color("666666");
1796    }
1797    {
1798        let mut p = doc.add_paragraph("");
1799        p.add_run("walter@tensorbee.com | +1 (415) 555-0199")
1800            .size(10.0)
1801            .color("888888");
1802    }
1803
1804    doc
1805}
1806
1807// =============================================================================
1808// 7. EMPLOYMENT CONTRACT — Purple + charcoal scheme
1809// =============================================================================
1810fn generate_contract(_samples_dir: &Path) -> Document {
1811    let mut doc = Document::new();
1812
1813    // Colors: Purple #5B2C6F, Plum #8E44AD, Gray #2C3E50
1814    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1815    doc.set_margins(
1816        Length::inches(1.25),
1817        Length::inches(1.0),
1818        Length::inches(1.0),
1819        Length::inches(1.0),
1820    );
1821    doc.set_title("Employment Agreement");
1822    doc.set_author("Tensorbee HR Department");
1823    doc.set_subject("Employment Contract — Walter White");
1824    doc.set_keywords("employment, contract, agreement, Tensorbee");
1825
1826    doc.set_header("TENSORBEE — Employment Agreement");
1827    doc.set_footer("Employment Agreement — Walter White — Page");
1828
1829    // ── Title Block ──
1830    doc.add_paragraph("")
1831        .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1832    {
1833        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1834        p.add_run("EMPLOYMENT AGREEMENT")
1835            .bold(true)
1836            .size(24.0)
1837            .color("5B2C6F")
1838            .font("Georgia");
1839    }
1840    doc.add_paragraph("")
1841        .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1842    doc.add_paragraph("");
1843
1844    // ── Parties ──
1845    doc.add_paragraph(
1846        "This Employment Agreement (\"Agreement\") is entered into as of February 22, 2026 \
1847         (\"Effective Date\"), by and between:",
1848    );
1849
1850    doc.add_paragraph("");
1851
1852    {
1853        let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1854        p.add_run("EMPLOYER: ").bold(true);
1855        p.add_run(
1856            "Tensorbee, Inc., a Delaware corporation with its principal place of business at \
1857                   123 Innovation Drive, Suite 400, San Francisco, CA 94105 (\"Company\")",
1858        );
1859    }
1860    doc.add_paragraph("");
1861    {
1862        let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1863        p.add_run("EMPLOYEE: ").bold(true);
1864        p.add_run(
1865            "Walter White, residing at 308 Negra Arroyo Lane, Albuquerque, NM 87104 (\"Employee\")",
1866        );
1867    }
1868
1869    doc.add_paragraph("");
1870    doc.add_paragraph("The Company and Employee are collectively referred to as the \"Parties.\"");
1871    doc.add_paragraph("");
1872
1873    // ── Article 1: Position and Duties ──
1874    doc.add_paragraph("Article 1 — Position and Duties")
1875        .style("Heading1");
1876    {
1877        let mut p = doc.add_paragraph("");
1878        p.add_run("1.1 ").bold(true);
1879        p.add_run("The Company hereby employs the Employee as ");
1880        p.add_run("Chief Executive Officer (CEO)")
1881            .bold(true)
1882            .italic(true);
1883        p.add_run(", reporting directly to the Board of Directors.");
1884    }
1885    {
1886        let mut p = doc.add_paragraph("");
1887        p.add_run("1.2 ").bold(true);
1888        p.add_run("The Employee shall devote full working time, attention, and best efforts to \
1889                   the performance of duties as reasonably assigned by the Board, including but not limited to:");
1890    }
1891    doc.add_bullet_list_item(
1892        "Setting and executing the Company's strategic vision and business plan",
1893        0,
1894    );
1895    doc.add_bullet_list_item(
1896        "Overseeing all operations, engineering, and commercial activities",
1897        0,
1898    );
1899    doc.add_bullet_list_item(
1900        "Representing the Company to investors, customers, and the public",
1901        0,
1902    );
1903    doc.add_bullet_list_item("Recruiting, developing, and retaining key talent", 0);
1904
1905    {
1906        let mut p = doc.add_paragraph("");
1907        p.add_run("1.3 ").bold(true);
1908        p.add_run(
1909            "The Employee's primary work location shall be the Company's San Francisco \
1910                   headquarters, with reasonable travel as required by business needs.",
1911        );
1912    }
1913
1914    // ── Article 2: Compensation ──
1915    doc.add_paragraph("Article 2 — Compensation and Benefits")
1916        .style("Heading1");
1917    {
1918        let mut p = doc.add_paragraph("");
1919        p.add_run("2.1 Base Salary. ").bold(true);
1920        p.add_run("The Company shall pay the Employee an annual base salary of ");
1921        p.add_run("$375,000.00").bold(true);
1922        p.add_run(" (Three Hundred Seventy-Five Thousand Dollars), payable in accordance with the \
1923                   Company's standard payroll schedule, less applicable withholdings and deductions.");
1924    }
1925    {
1926        let mut p = doc.add_paragraph("");
1927        p.add_run("2.2 Annual Bonus. ").bold(true);
1928        p.add_run("The Employee shall be eligible for an annual performance bonus of up to ");
1929        p.add_run("40%").bold(true);
1930        p.add_run(
1931            " of base salary, based on achievement of mutually agreed performance objectives.",
1932        );
1933    }
1934    {
1935        let mut p = doc.add_paragraph("");
1936        p.add_run("2.3 Equity. ").bold(true);
1937        p.add_run("Subject to Board approval, the Employee shall receive a stock option grant of ");
1938        p.add_run("500,000 shares").bold(true);
1939        p.add_run(
1940            " of the Company's common stock, vesting over four (4) years with a one-year cliff, \
1941                   at an exercise price equal to the fair market value on the date of grant.",
1942        );
1943    }
1944
1945    // Compensation summary table
1946    doc.add_paragraph("");
1947    {
1948        let mut tbl = doc
1949            .add_table(5, 2)
1950            .borders(BorderStyle::Single, 4, "5B2C6F")
1951            .width_pct(70.0)
1952            .alignment(Alignment::Center);
1953        tbl.cell(0, 0).unwrap().set_text("Compensation Element");
1954        tbl.cell(0, 0).unwrap().shading("5B2C6F");
1955        tbl.cell(0, 1).unwrap().set_text("Value");
1956        tbl.cell(0, 1).unwrap().shading("5B2C6F");
1957        let items = [
1958            ("Base Salary", "$375,000/year"),
1959            ("Target Bonus", "Up to 40% ($150,000)"),
1960            ("Equity Grant", "500,000 shares (4yr vest)"),
1961            ("Total Target Comp", "$525,000 + equity"),
1962        ];
1963        for (i, (elem, val)) in items.iter().enumerate() {
1964            tbl.cell(i + 1, 0).unwrap().set_text(elem);
1965            tbl.cell(i + 1, 1).unwrap().set_text(val);
1966            if i == 3 {
1967                tbl.cell(i + 1, 0).unwrap().shading("E8DAEF");
1968                tbl.cell(i + 1, 1).unwrap().shading("E8DAEF");
1969            }
1970        }
1971    }
1972
1973    // ── Article 3: Benefits ──
1974    doc.add_paragraph("").page_break_before(true);
1975    doc.add_paragraph("Article 3 — Benefits").style("Heading1");
1976    {
1977        let mut p = doc.add_paragraph("");
1978        p.add_run("3.1 ").bold(true);
1979        p.add_run(
1980            "The Employee shall be entitled to participate in all benefit programs \
1981                   generally available to senior executives, including:",
1982        );
1983    }
1984    doc.add_bullet_list_item(
1985        "Medical, dental, and vision insurance (100% premium coverage for employee and dependents)",
1986        0,
1987    );
1988    doc.add_bullet_list_item("401(k) retirement plan with 6% company match", 0);
1989    doc.add_bullet_list_item("Life insurance and long-term disability coverage", 0);
1990    doc.add_bullet_list_item("Annual professional development allowance of $10,000", 0);
1991
1992    {
1993        let mut p = doc.add_paragraph("");
1994        p.add_run("3.2 Paid Time Off. ").bold(true);
1995        p.add_run(
1996            "The Employee shall receive 25 days of paid vacation per year, plus Company holidays, \
1997                   accruing on a monthly basis.",
1998        );
1999    }
2000
2001    // ── Article 4: Term and Termination ──
2002    doc.add_paragraph("Article 4 — Term and Termination")
2003        .style("Heading1");
2004    {
2005        let mut p = doc.add_paragraph("");
2006        p.add_run("4.1 At-Will Employment. ").bold(true);
2007        p.add_run(
2008            "This Agreement is for at-will employment and may be terminated by either Party \
2009                   at any time, with or without cause, subject to the notice provisions herein.",
2010        );
2011    }
2012    {
2013        let mut p = doc.add_paragraph("");
2014        p.add_run("4.2 Notice Period. ").bold(true);
2015        p.add_run("Either Party shall provide ");
2016        p.add_run("ninety (90) days").bold(true);
2017        p.add_run(" written notice of termination, or pay in lieu thereof.");
2018    }
2019    {
2020        let mut p = doc.add_paragraph("");
2021        p.add_run("4.3 Severance. ").bold(true);
2022        p.add_run("In the event of termination by the Company without Cause, the Employee shall \
2023                   receive (a) twelve (12) months of base salary continuation, (b) pro-rata bonus \
2024                   for the year of termination, and (c) twelve (12) months of COBRA premium coverage.");
2025    }
2026
2027    // ── Article 5: Confidentiality ──
2028    doc.add_paragraph("Article 5 — Confidentiality and IP")
2029        .style("Heading1");
2030    {
2031        let mut p = doc.add_paragraph("");
2032        p.add_run("5.1 ").bold(true);
2033        p.add_run(
2034            "The Employee agrees to maintain strict confidentiality of all proprietary \
2035                   information, trade secrets, and business plans of the Company during and after \
2036                   employment.",
2037        );
2038    }
2039    {
2040        let mut p = doc.add_paragraph("");
2041        p.add_run("5.2 ").bold(true);
2042        p.add_run(
2043            "All intellectual property, inventions, and works of authorship created by the \
2044                   Employee during the term of employment and within the scope of duties shall be \
2045                   the sole and exclusive property of the Company.",
2046        );
2047    }
2048
2049    // ── Article 6: Non-Compete ──
2050    doc.add_paragraph("Article 6 — Non-Competition")
2051        .style("Heading1");
2052    {
2053        let mut p = doc.add_paragraph("");
2054        p.add_run("6.1 ").bold(true);
2055        p.add_run("For a period of twelve (12) months following termination, the Employee agrees \
2056                   not to engage in any business that directly competes with the Company's core \
2057                   business of AI infrastructure and ML operations services, within the United States.");
2058    }
2059
2060    // ── Article 7: General Provisions ──
2061    doc.add_paragraph("Article 7 — General Provisions")
2062        .style("Heading1");
2063    {
2064        let mut p = doc.add_paragraph("");
2065        p.add_run("7.1 Governing Law. ").bold(true);
2066        p.add_run(
2067            "This Agreement shall be governed by and construed in accordance with the laws of \
2068                   the State of California.",
2069        );
2070    }
2071    {
2072        let mut p = doc.add_paragraph("");
2073        p.add_run("7.2 Entire Agreement. ").bold(true);
2074        p.add_run(
2075            "This Agreement constitutes the entire agreement between the Parties and supersedes \
2076                   all prior negotiations, representations, and agreements.",
2077        );
2078    }
2079    {
2080        let mut p = doc.add_paragraph("");
2081        p.add_run("7.3 Amendment. ").bold(true);
2082        p.add_run(
2083            "This Agreement may only be amended by a written instrument signed by both Parties.",
2084        );
2085    }
2086
2087    // ── Signature Block ──
2088    doc.add_paragraph("").page_break_before(true);
2089    doc.add_paragraph("IN WITNESS WHEREOF, the Parties have executed this Employment Agreement as of the Effective Date.")
2090        .space_after(Length::pt(24.0));
2091
2092    // Signature table
2093    {
2094        let mut tbl = doc.add_table(6, 2).width_pct(100.0);
2095        tbl.cell(0, 0).unwrap().set_text("FOR THE COMPANY:");
2096        tbl.cell(0, 0).unwrap().shading("5B2C6F");
2097        tbl.cell(0, 1).unwrap().set_text("EMPLOYEE:");
2098        tbl.cell(0, 1).unwrap().shading("5B2C6F");
2099
2100        tbl.cell(1, 0).unwrap().set_text("");
2101        tbl.cell(1, 1).unwrap().set_text("");
2102        tbl.row(1).unwrap().height(Length::pt(40.0));
2103
2104        tbl.cell(2, 0)
2105            .unwrap()
2106            .set_text("Signature: ___________________________");
2107        tbl.cell(2, 1)
2108            .unwrap()
2109            .set_text("Signature: ___________________________");
2110        tbl.cell(3, 0)
2111            .unwrap()
2112            .set_text("Name: Board Representative");
2113        tbl.cell(3, 1).unwrap().set_text("Name: Walter White");
2114        tbl.cell(4, 0)
2115            .unwrap()
2116            .set_text("Title: Chair, Board of Directors");
2117        tbl.cell(4, 1)
2118            .unwrap()
2119            .set_text("Title: Chief Executive Officer");
2120        tbl.cell(5, 0).unwrap().set_text("Date: _______________");
2121        tbl.cell(5, 1).unwrap().set_text("Date: _______________");
2122    }
2123
2124    doc
2125}
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 639)
86fn generate_feature_showcase(_samples_dir: &Path) -> Document {
87    let mut doc = Document::new();
88
89    // ── Page Setup & Metadata ──
90    doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
91    doc.set_margins(
92        Length::inches(1.0),
93        Length::inches(1.0),
94        Length::inches(1.0),
95        Length::inches(1.0),
96    );
97    doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
98    doc.set_gutter(Length::twips(0));
99    doc.set_title("rdocx Feature Showcase");
100    doc.set_author("rdocx Sample Generator");
101    doc.set_subject("Comprehensive feature demonstration");
102    doc.set_keywords("rdocx, docx, rust, sample, showcase");
103
104    // Headers & Footers with different first page
105    doc.set_different_first_page(true);
106    doc.set_first_page_header("rdocx");
107    doc.set_first_page_footer("Feature Showcase — Cover Page");
108    doc.set_header("rdocx Feature Showcase");
109    doc.set_footer("Generated by rdocx");
110
111    // ── COVER PAGE ──
112    let bg = create_sample_png(612, 792, [20, 45, 90]);
113    doc.add_background_image(&bg, "cover_bg.png");
114
115    for _ in 0..3 {
116        doc.add_paragraph("");
117    }
118    {
119        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
120        p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
121    }
122    {
123        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
124        p.add_run("Complete Feature Showcase")
125            .size(28.0)
126            .color("FFFFFF")
127            .italic(true);
128    }
129    doc.add_paragraph("");
130    {
131        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
132        p.add_run("Every feature of the rdocx Rust library")
133            .size(13.0)
134            .color("B0C4DE");
135    }
136    {
137        let mut p = doc.add_paragraph("").alignment(Alignment::Center);
138        p.add_run("demonstrated in a single document.")
139            .size(13.0)
140            .color("B0C4DE");
141    }
142
143    // ── TABLE OF CONTENTS ──
144    doc.add_paragraph("").page_break_before(true);
145    doc.insert_toc(doc.content_count(), 3);
146
147    // ── SECTION 1: TEXT FORMATTING ──
148    doc.add_paragraph("").page_break_before(true);
149    doc.add_paragraph("1. Text Formatting").style("Heading1");
150
151    // Paragraph Alignment
152    doc.add_paragraph("1.1 Paragraph Alignment")
153        .style("Heading2");
154    doc.add_paragraph("Left-aligned paragraph (default).")
155        .alignment(Alignment::Left);
156    doc.add_paragraph("Center-aligned paragraph.")
157        .alignment(Alignment::Center);
158    doc.add_paragraph("Right-aligned paragraph.")
159        .alignment(Alignment::Right);
160    doc.add_paragraph(
161        "Justified paragraph — this text is long enough to demonstrate how justified alignment \
162         distributes extra space across word gaps so lines fill the full width of the text area.",
163    )
164    .alignment(Alignment::Justify);
165
166    doc.add_paragraph("");
167
168    // Run Formatting
169    doc.add_paragraph("1.2 Run Formatting").style("Heading2");
170    {
171        let mut p = doc.add_paragraph("");
172        p.add_run("Bold").bold(true);
173        p.add_run(" | ");
174        p.add_run("Italic").italic(true);
175        p.add_run(" | ");
176        p.add_run("Bold Italic").bold(true).italic(true);
177        p.add_run(" | ");
178        p.add_run("Underline").underline(true);
179        p.add_run(" | ");
180        p.add_run("Strikethrough").strike(true);
181        p.add_run(" | ");
182        p.add_run("Double Strike").double_strike(true);
183    }
184    {
185        let mut p = doc.add_paragraph("");
186        p.add_run("Red").color("FF0000");
187        p.add_run(" | ");
188        p.add_run("Blue").color("0000FF");
189        p.add_run(" | ");
190        p.add_run("Green").color("00AA00");
191        p.add_run(" | ");
192        p.add_run("Highlighted").highlight("FFFF00");
193    }
194    {
195        let mut p = doc.add_paragraph("");
196        p.add_run("8pt").size(8.0);
197        p.add_run(" | ");
198        p.add_run("11pt").size(11.0);
199        p.add_run(" | ");
200        p.add_run("16pt").size(16.0);
201        p.add_run(" | ");
202        p.add_run("24pt").size(24.0);
203    }
204    {
205        let mut p = doc.add_paragraph("");
206        p.add_run("Arial").font("Arial");
207        p.add_run(" | ");
208        p.add_run("Times New Roman").font("Times New Roman");
209        p.add_run(" | ");
210        p.add_run("Courier New").font("Courier New");
211    }
212    {
213        let mut p = doc.add_paragraph("");
214        p.add_run("H");
215        p.add_run("2").subscript();
216        p.add_run("O (subscript) | E=mc");
217        p.add_run("2").superscript();
218        p.add_run(" (superscript)");
219    }
220    {
221        let mut p = doc.add_paragraph("");
222        p.add_run("ALL CAPS").all_caps(true);
223        p.add_run(" | ");
224        p.add_run("Small Caps").small_caps(true);
225        p.add_run(" | ");
226        p.add_run("Expanded").character_spacing(Length::twips(40));
227        p.add_run(" | ");
228        p.add_run("Hidden text (not visible)").hidden(true);
229    }
230
231    doc.add_paragraph("");
232
233    // Underline Styles
234    doc.add_paragraph("1.3 Underline Styles").style("Heading2");
235    {
236        let mut p = doc.add_paragraph("");
237        p.add_run("Single ")
238            .underline_style(rdocx::UnderlineStyle::Single);
239        p.add_run("Double ")
240            .underline_style(rdocx::UnderlineStyle::Double);
241        p.add_run("Thick ")
242            .underline_style(rdocx::UnderlineStyle::Thick);
243        p.add_run("Dotted ")
244            .underline_style(rdocx::UnderlineStyle::Dotted);
245        p.add_run("Dash ")
246            .underline_style(rdocx::UnderlineStyle::Dash);
247        p.add_run("Wave ")
248            .underline_style(rdocx::UnderlineStyle::Wave);
249    }
250
251    // ── SECTION 2: PARAGRAPH FORMATTING ──
252    doc.add_paragraph("").page_break_before(true);
253    doc.add_paragraph("2. Paragraph Formatting")
254        .style("Heading1");
255
256    doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
257    doc.add_paragraph("Paragraph with light green shading")
258        .shading("E2EFDA");
259    doc.add_paragraph("Paragraph with bottom border")
260        .border_bottom(BorderStyle::Single, 6, "2E75B6");
261    doc.add_paragraph("Paragraph with all borders (red)")
262        .border_all(BorderStyle::Single, 4, "FF0000");
263
264    doc.add_paragraph("2.2 Indentation").style("Heading2");
265    doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
266        .indent_left(Length::inches(1.0))
267        .hanging_indent(Length::inches(0.5));
268    doc.add_paragraph("0.5-inch first-line indent on this paragraph")
269        .first_line_indent(Length::inches(0.5));
270    doc.add_paragraph("Both left and right indent (0.75 inches each)")
271        .indent_left(Length::inches(0.75))
272        .indent_right(Length::inches(0.75));
273
274    doc.add_paragraph("2.3 Spacing & Line Height")
275        .style("Heading2");
276    doc.add_paragraph("Extra space before (24pt) and after (12pt)")
277        .space_before(Length::pt(24.0))
278        .space_after(Length::pt(12.0));
279    doc.add_paragraph(
280        "Double line spacing paragraph. This text should have extra vertical space between \
281         lines to demonstrate line_spacing_multiple(2.0).",
282    )
283    .line_spacing_multiple(2.0);
284    doc.add_paragraph("Exact 20pt line spacing (fixed height).")
285        .line_spacing(20.0);
286
287    doc.add_paragraph("2.4 Pagination Controls")
288        .style("Heading2");
289    doc.add_paragraph("keep_with_next — this paragraph stays with the next")
290        .keep_with_next(true);
291    doc.add_paragraph("(This paragraph was kept with the one above.)");
292    doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
293        .keep_together(true);
294    doc.add_paragraph("widow_control — prevents widow/orphan lines")
295        .widow_control(true);
296
297    // ── SECTION 3: LISTS ──
298    doc.add_paragraph("").page_break_before(true);
299    doc.add_paragraph("3. Lists").style("Heading1");
300
301    doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
302    doc.add_bullet_list_item("First item", 0);
303    doc.add_bullet_list_item("Second item", 0);
304    doc.add_bullet_list_item("Nested level 1", 1);
305    doc.add_bullet_list_item("Nested level 2", 2);
306    doc.add_bullet_list_item("Back to level 1", 1);
307    doc.add_bullet_list_item("Third item", 0);
308
309    doc.add_paragraph("");
310
311    doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
312    doc.add_numbered_list_item("First numbered item", 0);
313    doc.add_numbered_list_item("Second numbered item", 0);
314    doc.add_numbered_list_item("Sub-item A", 1);
315    doc.add_numbered_list_item("Sub-item B", 1);
316    doc.add_numbered_list_item("Third numbered item", 0);
317
318    // ── SECTION 4: TAB STOPS ──
319    doc.add_paragraph("");
320    doc.add_paragraph("4. Tab Stops").style("Heading1");
321
322    doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
323    doc.add_paragraph("Left\tCenter\tRight\tDecimal")
324        .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
325        .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
326        .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
327        .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
328
329    doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
330    doc.add_paragraph("Item\t\tPrice")
331        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
332        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
333        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
334    doc.add_paragraph("Widget\t\t$19.99")
335        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
336        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
337        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
338    doc.add_paragraph("Gadget\t\t$249.50")
339        .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
340        .add_tab_stop_with_leader(
341            TabAlignment::Right,
342            Length::inches(4.0),
343            TabLeader::Underscore,
344        )
345        .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
346
347    // ── SECTION 5: TABLES ──
348    doc.add_paragraph("").page_break_before(true);
349    doc.add_paragraph("5. Tables").style("Heading1");
350
351    doc.add_paragraph("5.1 Basic Table").style("Heading2");
352    {
353        let mut tbl = doc
354            .add_table(4, 3)
355            .borders(BorderStyle::Single, 4, "000000");
356        for col in 0..3 {
357            tbl.cell(0, col).unwrap().shading("2E75B6");
358        }
359        tbl.cell(0, 0).unwrap().set_text("Name");
360        tbl.cell(0, 1).unwrap().set_text("Role");
361        tbl.cell(0, 2).unwrap().set_text("Location");
362        tbl.cell(1, 0).unwrap().set_text("Walter White");
363        tbl.cell(1, 1).unwrap().set_text("CEO");
364        tbl.cell(1, 2).unwrap().set_text("Albuquerque");
365        tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
366        tbl.cell(2, 1).unwrap().set_text("CTO");
367        tbl.cell(2, 2).unwrap().set_text("Remote");
368        tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
369        tbl.cell(3, 1).unwrap().set_text("Security");
370        tbl.cell(3, 2).unwrap().set_text("Washington");
371    }
372
373    doc.add_paragraph("");
374
375    doc.add_paragraph("5.2 Column Span & Cell Shading")
376        .style("Heading2");
377    {
378        let mut tbl = doc
379            .add_table(3, 4)
380            .borders(BorderStyle::Single, 4, "000000")
381            .width_pct(100.0);
382        tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
383        tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
384        tbl.cell(1, 0).unwrap().set_text("Region");
385        tbl.cell(1, 0).unwrap().shading("D6E4F0");
386        tbl.cell(1, 1).unwrap().set_text("Q1");
387        tbl.cell(1, 1).unwrap().shading("D6E4F0");
388        tbl.cell(1, 2).unwrap().set_text("Q2");
389        tbl.cell(1, 2).unwrap().shading("D6E4F0");
390        tbl.cell(1, 3).unwrap().set_text("Total");
391        tbl.cell(1, 3).unwrap().shading("D6E4F0");
392        tbl.cell(2, 0).unwrap().set_text("Americas");
393        tbl.cell(2, 1).unwrap().set_text("$2.4M");
394        tbl.cell(2, 2).unwrap().set_text("$2.7M");
395        tbl.cell(2, 3).unwrap().set_text("$5.1M");
396    }
397
398    doc.add_paragraph("");
399
400    doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
401    {
402        let mut tbl = doc
403            .add_table(4, 3)
404            .borders(BorderStyle::Single, 4, "000000");
405        tbl.cell(0, 0).unwrap().set_text("Category");
406        tbl.cell(0, 0).unwrap().shading("E2EFDA");
407        tbl.cell(0, 1).unwrap().set_text("Item");
408        tbl.cell(0, 1).unwrap().shading("E2EFDA");
409        tbl.cell(0, 2).unwrap().set_text("Price");
410        tbl.cell(0, 2).unwrap().shading("E2EFDA");
411        tbl.cell(1, 0).unwrap().set_text("Hardware");
412        tbl.cell(1, 0).unwrap().v_merge_restart();
413        tbl.cell(1, 1).unwrap().set_text("Laptop");
414        tbl.cell(1, 2).unwrap().set_text("$1,200");
415        tbl.cell(2, 0).unwrap().v_merge_continue();
416        tbl.cell(2, 1).unwrap().set_text("Monitor");
417        tbl.cell(2, 2).unwrap().set_text("$450");
418        tbl.cell(3, 0).unwrap().set_text("Software");
419        tbl.cell(3, 1).unwrap().set_text("IDE License");
420        tbl.cell(3, 2).unwrap().set_text("$200/yr");
421    }
422
423    doc.add_paragraph("");
424
425    doc.add_paragraph("5.4 Nested Table").style("Heading2");
426    {
427        let mut tbl = doc
428            .add_table(2, 2)
429            .borders(BorderStyle::Single, 6, "2E75B6");
430        tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
431        tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
432        tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
433        {
434            let mut cell = tbl.cell(1, 1).unwrap();
435            cell.set_text("Nested table below:");
436            let mut nested = cell
437                .add_table(2, 2)
438                .borders(BorderStyle::Single, 2, "FF6600");
439            nested.cell(0, 0).unwrap().set_text("A");
440            nested.cell(0, 1).unwrap().set_text("B");
441            nested.cell(1, 0).unwrap().set_text("C");
442            nested.cell(1, 1).unwrap().set_text("D");
443        }
444    }
445
446    doc.add_paragraph("");
447
448    doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
449        .style("Heading2");
450    {
451        let mut tbl = doc
452            .add_table(2, 3)
453            .borders(BorderStyle::Single, 4, "666666");
454        tbl.row(0).unwrap().height(Length::pt(50.0)).header();
455        tbl.cell(0, 0).unwrap().set_text("Top");
456        tbl.cell(0, 0)
457            .unwrap()
458            .vertical_alignment(VerticalAlignment::Top)
459            .shading("FFF2CC");
460        tbl.cell(0, 1).unwrap().set_text("Center");
461        tbl.cell(0, 1)
462            .unwrap()
463            .vertical_alignment(VerticalAlignment::Center)
464            .shading("D9E2F3");
465        tbl.cell(0, 2).unwrap().set_text("Bottom");
466        tbl.cell(0, 2)
467            .unwrap()
468            .vertical_alignment(VerticalAlignment::Bottom)
469            .shading("E2EFDA");
470        tbl.row(1).unwrap().cant_split();
471        tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
472        tbl.cell(1, 0).unwrap().no_wrap();
473        tbl.cell(1, 1).unwrap().set_text("Fixed width");
474        tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
475        tbl.cell(1, 2).unwrap().set_text("Normal");
476    }
477
478    // ── SECTION 6: IMAGES ──
479    doc.add_paragraph("").page_break_before(true);
480    doc.add_paragraph("6. Images").style("Heading1");
481
482    doc.add_paragraph("6.1 Inline Image").style("Heading2");
483    doc.add_paragraph("A blue gradient image below:");
484    let img = create_sample_png(200, 50, [0, 80, 200]);
485    doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
486
487    doc.add_paragraph("");
488    doc.add_paragraph("6.2 Header Image").style("Heading2");
489    let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
490    doc.set_header_image(
491        &hdr_img,
492        "header_logo.png",
493        Length::inches(2.0),
494        Length::inches(0.2),
495    );
496    doc.add_paragraph("The header now contains an inline image (check the top of this page).");
497
498    doc.add_paragraph("");
499    doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
500
501    // ── SECTION 7: CONTENT MANIPULATION ──
502    doc.add_paragraph("").page_break_before(true);
503    doc.add_paragraph("7. Content Manipulation")
504        .style("Heading1");
505
506    doc.add_paragraph("7.1 Placeholder Replacement")
507        .style("Heading2");
508    doc.add_paragraph("Customer: {{customer}}");
509    doc.add_paragraph("Date: {{date}}");
510    doc.add_paragraph("Reference: {{ref_number}}");
511    {
512        let mut tbl = doc
513            .add_table(2, 2)
514            .borders(BorderStyle::Single, 4, "000000");
515        tbl.cell(0, 0).unwrap().set_text("Project");
516        tbl.cell(0, 0).unwrap().shading("D6E4F0");
517        tbl.cell(0, 1).unwrap().set_text("{{project}}");
518        tbl.cell(1, 0).unwrap().set_text("Status");
519        tbl.cell(1, 0).unwrap().shading("D6E4F0");
520        tbl.cell(1, 1).unwrap().set_text("{{status}}");
521    }
522
523    let mut replacements = HashMap::new();
524    replacements.insert("{{customer}}", "Tensorbee Inc.");
525    replacements.insert("{{date}}", "February 22, 2026");
526    replacements.insert("{{ref_number}}", "TB-2026-001");
527    replacements.insert("{{project}}", "Infrastructure Upgrade");
528    replacements.insert("{{status}}", "In Progress");
529    let n = doc.replace_all(&replacements);
530    doc.add_paragraph(&format!("({n} placeholders replaced above)"));
531
532    doc.add_paragraph("");
533
534    doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
535    doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
536    let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
537    doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
538
539    doc.add_paragraph("");
540
541    doc.add_paragraph("7.3 Content Insertion").style("Heading2");
542    doc.add_paragraph("Section A: First content.");
543    doc.add_paragraph("Section C: Third content.");
544    if let Some(idx) = doc.find_content_index("Section C") {
545        doc.insert_paragraph(
546            idx,
547            "Section B: Inserted between A and C via find_content_index().",
548        );
549    }
550
551    // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
552    doc.add_paragraph("").section_break(SectionBreak::NextPage);
553    doc.add_paragraph("8. Section Breaks & Orientation")
554        .style("Heading1");
555    doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
556
557    {
558        let mut tbl = doc
559            .add_table(3, 7)
560            .borders(BorderStyle::Single, 4, "2E75B6");
561        let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
562        for (c, h) in headers.iter().enumerate() {
563            tbl.cell(0, c).unwrap().set_text(h);
564            tbl.cell(0, c).unwrap().shading("2E75B6");
565        }
566        let data = [
567            [
568                "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
569            ],
570            [
571                "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
572            ],
573        ];
574        for (r, row) in data.iter().enumerate() {
575            for (c, v) in row.iter().enumerate() {
576                tbl.cell(r + 1, c).unwrap().set_text(v);
577            }
578        }
579    }
580
581    doc.add_paragraph("")
582        .section_break(SectionBreak::NextPage)
583        .section_landscape();
584
585    // ── SECTION 9: CUSTOM STYLES ──
586    doc.add_paragraph("9. Custom Styles").style("Heading1");
587    doc.add_style(
588        StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
589            .based_on("Normal")
590            .paragraph_properties({
591                let mut ppr = rdocx_oxml::properties::CT_PPr::default();
592                ppr.shading = Some(rdocx_oxml::properties::CT_Shd {
593                    val: "clear".to_string(),
594                    color: None,
595                    fill: Some("FFF2CC".to_string()),
596                });
597                ppr
598            })
599            .run_properties({
600                let mut rpr = rdocx_oxml::properties::CT_RPr::default();
601                rpr.bold = Some(true);
602                rpr.color = Some("C45911".to_string());
603                rpr
604            }),
605    );
606    doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
607        .style("CustomHighlight");
608
609    doc.add_paragraph("");
610
611    // ── SECTION 10: DOCUMENT INTELLIGENCE ──
612    doc.add_paragraph("10. Document Intelligence API")
613        .style("Heading1");
614
615    let wc = doc.word_count();
616    let hc = doc.headings().len();
617    let ic = doc.images().len();
618    let lc = doc.links().len();
619    doc.add_paragraph(&format!("Word count: {wc}"));
620    doc.add_paragraph(&format!("Heading count: {hc}"));
621    doc.add_paragraph(&format!("Image count: {ic}"));
622    doc.add_paragraph(&format!("Link count: {lc}"));
623
624    let outline = doc.document_outline();
625    doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
626
627    let issues = doc.audit_accessibility();
628    doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
629    for issue in &issues {
630        doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
631    }
632
633    // ── SECTION 11: DOCUMENT MERGING ──
634    doc.add_paragraph("");
635    doc.add_paragraph("11. Document Merging").style("Heading1");
636    {
637        let mut other = Document::new();
638        other.add_paragraph("This paragraph was merged from another document using append().");
639        doc.append(&other);
640    }
641
642    doc.add_paragraph("");
643
644    // ── SUMMARY ──
645    doc.add_paragraph("Summary of Demonstrated Features")
646        .style("Heading1");
647    let features = [
648        "Page setup: size, margins, header/footer distance, gutter",
649        "Document metadata: title, author, subject, keywords",
650        "Headers and footers: text, images, different first page",
651        "Background images (full-page behind text)",
652        "Table of Contents generation with bookmarks",
653        "Text formatting: bold, italic, underline styles, strike, color, size, font",
654        "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
655        "Paragraph formatting: alignment, borders, shading, spacing, indentation",
656        "Pagination controls: keep-with-next, keep-together, widow control",
657        "Bullet and numbered lists with nesting",
658        "Tab stops with dot/underscore/hyphen leaders",
659        "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
660        "Row properties: header rows, exact height, cant-split",
661        "Cell properties: width, no-wrap, vertical alignment",
662        "Inline images and header images",
663        "Placeholder replacement (single and batch)",
664        "Regex-based find and replace",
665        "Content insertion at specific positions",
666        "Section breaks with mixed portrait/landscape",
667        "Custom paragraph and character styles",
668        "Document intelligence: word count, headings, outline, images, links",
669        "Accessibility audit",
670        "Document merging (append)",
671        "Export: DOCX, PDF, HTML, Markdown",
672    ];
673    for f in &features {
674        doc.add_bullet_list_item(f, 0);
675    }
676    doc.add_paragraph("");
677    doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
678        .alignment(Alignment::Center)
679        .shading("E2EFDA")
680        .border_all(BorderStyle::Single, 2, "00AA00");
681
682    doc
683}
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.

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

Source

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

Replace multiple placeholders at once. Returns total replacements.

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

Render all pages of the document to PNG bytes.

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

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

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<T> Same for T

Source§

type Output = T

Should always be Self
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.