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
impl Document
Sourcepub fn new() -> Self
pub fn new() -> Self
Create a new, empty document with default page setup and styles.
Examples found in repository?
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
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}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}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}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}97fn generate_feature_showcase(_samples_dir: &Path) -> Document {
98 let mut doc = Document::new();
99
100 // ── Page Setup & Metadata ──
101 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
102 doc.set_margins(
103 Length::inches(1.0),
104 Length::inches(1.0),
105 Length::inches(1.0),
106 Length::inches(1.0),
107 );
108 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
109 doc.set_gutter(Length::twips(0));
110 doc.set_title("rdocx Feature Showcase");
111 doc.set_author("rdocx Sample Generator");
112 doc.set_subject("Comprehensive feature demonstration");
113 doc.set_keywords("rdocx, docx, rust, sample, showcase");
114
115 // Headers & Footers with different first page
116 doc.set_different_first_page(true);
117 doc.set_first_page_header("rdocx");
118 doc.set_first_page_footer("Feature Showcase — Cover Page");
119 doc.set_header("rdocx Feature Showcase");
120 doc.set_footer("Generated by rdocx");
121
122 // ── COVER PAGE ──
123 let bg = create_sample_png(612, 792, [20, 45, 90]);
124 doc.add_background_image(&bg, "cover_bg.png");
125
126 for _ in 0..3 {
127 doc.add_paragraph("");
128 }
129 {
130 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
131 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
132 }
133 {
134 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
135 p.add_run("Complete Feature Showcase")
136 .size(28.0)
137 .color("FFFFFF")
138 .italic(true);
139 }
140 doc.add_paragraph("");
141 {
142 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
143 p.add_run("Every feature of the rdocx Rust library")
144 .size(13.0)
145 .color("B0C4DE");
146 }
147 {
148 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
149 p.add_run("demonstrated in a single document.")
150 .size(13.0)
151 .color("B0C4DE");
152 }
153
154 // ── TABLE OF CONTENTS ──
155 doc.add_paragraph("").page_break_before(true);
156 doc.insert_toc(doc.content_count(), 3);
157
158 // ── SECTION 1: TEXT FORMATTING ──
159 doc.add_paragraph("").page_break_before(true);
160 doc.add_paragraph("1. Text Formatting").style("Heading1");
161
162 // Paragraph Alignment
163 doc.add_paragraph("1.1 Paragraph Alignment")
164 .style("Heading2");
165 doc.add_paragraph("Left-aligned paragraph (default).")
166 .alignment(Alignment::Left);
167 doc.add_paragraph("Center-aligned paragraph.")
168 .alignment(Alignment::Center);
169 doc.add_paragraph("Right-aligned paragraph.")
170 .alignment(Alignment::Right);
171 doc.add_paragraph(
172 "Justified paragraph — this text is long enough to demonstrate how justified alignment \
173 distributes extra space across word gaps so lines fill the full width of the text area.",
174 )
175 .alignment(Alignment::Justify);
176
177 doc.add_paragraph("");
178
179 // Run Formatting
180 doc.add_paragraph("1.2 Run Formatting").style("Heading2");
181 {
182 let mut p = doc.add_paragraph("");
183 p.add_run("Bold").bold(true);
184 p.add_run(" | ");
185 p.add_run("Italic").italic(true);
186 p.add_run(" | ");
187 p.add_run("Bold Italic").bold(true).italic(true);
188 p.add_run(" | ");
189 p.add_run("Underline").underline(true);
190 p.add_run(" | ");
191 p.add_run("Strikethrough").strike(true);
192 p.add_run(" | ");
193 p.add_run("Double Strike").double_strike(true);
194 }
195 {
196 let mut p = doc.add_paragraph("");
197 p.add_run("Red").color("FF0000");
198 p.add_run(" | ");
199 p.add_run("Blue").color("0000FF");
200 p.add_run(" | ");
201 p.add_run("Green").color("00AA00");
202 p.add_run(" | ");
203 p.add_run("Highlighted").highlight("FFFF00");
204 }
205 {
206 let mut p = doc.add_paragraph("");
207 p.add_run("8pt").size(8.0);
208 p.add_run(" | ");
209 p.add_run("11pt").size(11.0);
210 p.add_run(" | ");
211 p.add_run("16pt").size(16.0);
212 p.add_run(" | ");
213 p.add_run("24pt").size(24.0);
214 }
215 {
216 let mut p = doc.add_paragraph("");
217 p.add_run("Arial").font("Arial");
218 p.add_run(" | ");
219 p.add_run("Times New Roman").font("Times New Roman");
220 p.add_run(" | ");
221 p.add_run("Courier New").font("Courier New");
222 }
223 {
224 let mut p = doc.add_paragraph("");
225 p.add_run("H");
226 p.add_run("2").subscript();
227 p.add_run("O (subscript) | E=mc");
228 p.add_run("2").superscript();
229 p.add_run(" (superscript)");
230 }
231 {
232 let mut p = doc.add_paragraph("");
233 p.add_run("ALL CAPS").all_caps(true);
234 p.add_run(" | ");
235 p.add_run("Small Caps").small_caps(true);
236 p.add_run(" | ");
237 p.add_run("Expanded").character_spacing(Length::twips(40));
238 p.add_run(" | ");
239 p.add_run("Hidden text (not visible)").hidden(true);
240 }
241
242 doc.add_paragraph("");
243
244 // Underline Styles
245 doc.add_paragraph("1.3 Underline Styles").style("Heading2");
246 {
247 let mut p = doc.add_paragraph("");
248 p.add_run("Single ")
249 .underline_style(rdocx::UnderlineStyle::Single);
250 p.add_run("Double ")
251 .underline_style(rdocx::UnderlineStyle::Double);
252 p.add_run("Thick ")
253 .underline_style(rdocx::UnderlineStyle::Thick);
254 p.add_run("Dotted ")
255 .underline_style(rdocx::UnderlineStyle::Dotted);
256 p.add_run("Dash ")
257 .underline_style(rdocx::UnderlineStyle::Dash);
258 p.add_run("Wave ")
259 .underline_style(rdocx::UnderlineStyle::Wave);
260 }
261
262 // ── SECTION 2: PARAGRAPH FORMATTING ──
263 doc.add_paragraph("").page_break_before(true);
264 doc.add_paragraph("2. Paragraph Formatting")
265 .style("Heading1");
266
267 doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
268 doc.add_paragraph("Paragraph with light green shading")
269 .shading("E2EFDA");
270 doc.add_paragraph("Paragraph with bottom border")
271 .border_bottom(BorderStyle::Single, 6, "2E75B6");
272 doc.add_paragraph("Paragraph with all borders (red)")
273 .border_all(BorderStyle::Single, 4, "FF0000");
274
275 doc.add_paragraph("2.2 Indentation").style("Heading2");
276 doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
277 .indent_left(Length::inches(1.0))
278 .hanging_indent(Length::inches(0.5));
279 doc.add_paragraph("0.5-inch first-line indent on this paragraph")
280 .first_line_indent(Length::inches(0.5));
281 doc.add_paragraph("Both left and right indent (0.75 inches each)")
282 .indent_left(Length::inches(0.75))
283 .indent_right(Length::inches(0.75));
284
285 doc.add_paragraph("2.3 Spacing & Line Height")
286 .style("Heading2");
287 doc.add_paragraph("Extra space before (24pt) and after (12pt)")
288 .space_before(Length::pt(24.0))
289 .space_after(Length::pt(12.0));
290 doc.add_paragraph(
291 "Double line spacing paragraph. This text should have extra vertical space between \
292 lines to demonstrate line_spacing_multiple(2.0).",
293 )
294 .line_spacing_multiple(2.0);
295 doc.add_paragraph("Exact 20pt line spacing (fixed height).")
296 .line_spacing(20.0);
297
298 doc.add_paragraph("2.4 Pagination Controls")
299 .style("Heading2");
300 doc.add_paragraph("keep_with_next — this paragraph stays with the next")
301 .keep_with_next(true);
302 doc.add_paragraph("(This paragraph was kept with the one above.)");
303 doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
304 .keep_together(true);
305 doc.add_paragraph("widow_control — prevents widow/orphan lines")
306 .widow_control(true);
307
308 // ── SECTION 3: LISTS ──
309 doc.add_paragraph("").page_break_before(true);
310 doc.add_paragraph("3. Lists").style("Heading1");
311
312 doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
313 doc.add_bullet_list_item("First item", 0);
314 doc.add_bullet_list_item("Second item", 0);
315 doc.add_bullet_list_item("Nested level 1", 1);
316 doc.add_bullet_list_item("Nested level 2", 2);
317 doc.add_bullet_list_item("Back to level 1", 1);
318 doc.add_bullet_list_item("Third item", 0);
319
320 doc.add_paragraph("");
321
322 doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
323 doc.add_numbered_list_item("First numbered item", 0);
324 doc.add_numbered_list_item("Second numbered item", 0);
325 doc.add_numbered_list_item("Sub-item A", 1);
326 doc.add_numbered_list_item("Sub-item B", 1);
327 doc.add_numbered_list_item("Third numbered item", 0);
328
329 // ── SECTION 4: TAB STOPS ──
330 doc.add_paragraph("");
331 doc.add_paragraph("4. Tab Stops").style("Heading1");
332
333 doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
334 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
335 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
336 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
337 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
338 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
339
340 doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
341 doc.add_paragraph("Item\t\tPrice")
342 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
343 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
344 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
345 doc.add_paragraph("Widget\t\t$19.99")
346 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
347 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
348 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
349 doc.add_paragraph("Gadget\t\t$249.50")
350 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
351 .add_tab_stop_with_leader(
352 TabAlignment::Right,
353 Length::inches(4.0),
354 TabLeader::Underscore,
355 )
356 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
357
358 // ── SECTION 5: TABLES ──
359 doc.add_paragraph("").page_break_before(true);
360 doc.add_paragraph("5. Tables").style("Heading1");
361
362 doc.add_paragraph("5.1 Basic Table").style("Heading2");
363 {
364 let mut tbl = doc
365 .add_table(4, 3)
366 .borders(BorderStyle::Single, 4, "000000");
367 for col in 0..3 {
368 tbl.cell(0, col).unwrap().shading("2E75B6");
369 }
370 tbl.cell(0, 0).unwrap().set_text("Name");
371 tbl.cell(0, 1).unwrap().set_text("Role");
372 tbl.cell(0, 2).unwrap().set_text("Location");
373 tbl.cell(1, 0).unwrap().set_text("Walter White");
374 tbl.cell(1, 1).unwrap().set_text("CEO");
375 tbl.cell(1, 2).unwrap().set_text("Albuquerque");
376 tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
377 tbl.cell(2, 1).unwrap().set_text("CTO");
378 tbl.cell(2, 2).unwrap().set_text("Remote");
379 tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
380 tbl.cell(3, 1).unwrap().set_text("Security");
381 tbl.cell(3, 2).unwrap().set_text("Washington");
382 }
383
384 doc.add_paragraph("");
385
386 doc.add_paragraph("5.2 Column Span & Cell Shading")
387 .style("Heading2");
388 {
389 let mut tbl = doc
390 .add_table(3, 4)
391 .borders(BorderStyle::Single, 4, "000000")
392 .width_pct(100.0);
393 tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
394 tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
395 tbl.cell(1, 0).unwrap().set_text("Region");
396 tbl.cell(1, 0).unwrap().shading("D6E4F0");
397 tbl.cell(1, 1).unwrap().set_text("Q1");
398 tbl.cell(1, 1).unwrap().shading("D6E4F0");
399 tbl.cell(1, 2).unwrap().set_text("Q2");
400 tbl.cell(1, 2).unwrap().shading("D6E4F0");
401 tbl.cell(1, 3).unwrap().set_text("Total");
402 tbl.cell(1, 3).unwrap().shading("D6E4F0");
403 tbl.cell(2, 0).unwrap().set_text("Americas");
404 tbl.cell(2, 1).unwrap().set_text("$2.4M");
405 tbl.cell(2, 2).unwrap().set_text("$2.7M");
406 tbl.cell(2, 3).unwrap().set_text("$5.1M");
407 }
408
409 doc.add_paragraph("");
410
411 doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
412 {
413 let mut tbl = doc
414 .add_table(4, 3)
415 .borders(BorderStyle::Single, 4, "000000");
416 tbl.cell(0, 0).unwrap().set_text("Category");
417 tbl.cell(0, 0).unwrap().shading("E2EFDA");
418 tbl.cell(0, 1).unwrap().set_text("Item");
419 tbl.cell(0, 1).unwrap().shading("E2EFDA");
420 tbl.cell(0, 2).unwrap().set_text("Price");
421 tbl.cell(0, 2).unwrap().shading("E2EFDA");
422 tbl.cell(1, 0).unwrap().set_text("Hardware");
423 tbl.cell(1, 0).unwrap().v_merge_restart();
424 tbl.cell(1, 1).unwrap().set_text("Laptop");
425 tbl.cell(1, 2).unwrap().set_text("$1,200");
426 tbl.cell(2, 0).unwrap().v_merge_continue();
427 tbl.cell(2, 1).unwrap().set_text("Monitor");
428 tbl.cell(2, 2).unwrap().set_text("$450");
429 tbl.cell(3, 0).unwrap().set_text("Software");
430 tbl.cell(3, 1).unwrap().set_text("IDE License");
431 tbl.cell(3, 2).unwrap().set_text("$200/yr");
432 }
433
434 doc.add_paragraph("");
435
436 doc.add_paragraph("5.4 Nested Table").style("Heading2");
437 {
438 let mut tbl = doc
439 .add_table(2, 2)
440 .borders(BorderStyle::Single, 6, "2E75B6");
441 tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
442 tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
443 tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
444 {
445 let mut cell = tbl.cell(1, 1).unwrap();
446 cell.set_text("Nested table below:");
447 let mut nested = cell
448 .add_table(2, 2)
449 .borders(BorderStyle::Single, 2, "FF6600");
450 nested.cell(0, 0).unwrap().set_text("A");
451 nested.cell(0, 1).unwrap().set_text("B");
452 nested.cell(1, 0).unwrap().set_text("C");
453 nested.cell(1, 1).unwrap().set_text("D");
454 }
455 }
456
457 doc.add_paragraph("");
458
459 doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
460 .style("Heading2");
461 {
462 let mut tbl = doc
463 .add_table(2, 3)
464 .borders(BorderStyle::Single, 4, "666666");
465 tbl.row(0).unwrap().height(Length::pt(50.0)).header();
466 tbl.cell(0, 0).unwrap().set_text("Top");
467 tbl.cell(0, 0)
468 .unwrap()
469 .vertical_alignment(VerticalAlignment::Top)
470 .shading("FFF2CC");
471 tbl.cell(0, 1).unwrap().set_text("Center");
472 tbl.cell(0, 1)
473 .unwrap()
474 .vertical_alignment(VerticalAlignment::Center)
475 .shading("D9E2F3");
476 tbl.cell(0, 2).unwrap().set_text("Bottom");
477 tbl.cell(0, 2)
478 .unwrap()
479 .vertical_alignment(VerticalAlignment::Bottom)
480 .shading("E2EFDA");
481 tbl.row(1).unwrap().cant_split();
482 tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
483 tbl.cell(1, 0).unwrap().no_wrap();
484 tbl.cell(1, 1).unwrap().set_text("Fixed width");
485 tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
486 tbl.cell(1, 2).unwrap().set_text("Normal");
487 }
488
489 // ── SECTION 6: IMAGES ──
490 doc.add_paragraph("").page_break_before(true);
491 doc.add_paragraph("6. Images").style("Heading1");
492
493 doc.add_paragraph("6.1 Inline Image").style("Heading2");
494 doc.add_paragraph("A blue gradient image below:");
495 let img = create_sample_png(200, 50, [0, 80, 200]);
496 doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
497
498 doc.add_paragraph("");
499 doc.add_paragraph("6.2 Header Image").style("Heading2");
500 let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
501 doc.set_header_image(
502 &hdr_img,
503 "header_logo.png",
504 Length::inches(2.0),
505 Length::inches(0.2),
506 );
507 doc.add_paragraph("The header now contains an inline image (check the top of this page).");
508
509 doc.add_paragraph("");
510 doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
511
512 // ── SECTION 7: CONTENT MANIPULATION ──
513 doc.add_paragraph("").page_break_before(true);
514 doc.add_paragraph("7. Content Manipulation")
515 .style("Heading1");
516
517 doc.add_paragraph("7.1 Placeholder Replacement")
518 .style("Heading2");
519 doc.add_paragraph("Customer: {{customer}}");
520 doc.add_paragraph("Date: {{date}}");
521 doc.add_paragraph("Reference: {{ref_number}}");
522 {
523 let mut tbl = doc
524 .add_table(2, 2)
525 .borders(BorderStyle::Single, 4, "000000");
526 tbl.cell(0, 0).unwrap().set_text("Project");
527 tbl.cell(0, 0).unwrap().shading("D6E4F0");
528 tbl.cell(0, 1).unwrap().set_text("{{project}}");
529 tbl.cell(1, 0).unwrap().set_text("Status");
530 tbl.cell(1, 0).unwrap().shading("D6E4F0");
531 tbl.cell(1, 1).unwrap().set_text("{{status}}");
532 }
533
534 let mut replacements = HashMap::new();
535 replacements.insert("{{customer}}", "Tensorbee Inc.");
536 replacements.insert("{{date}}", "February 22, 2026");
537 replacements.insert("{{ref_number}}", "TB-2026-001");
538 replacements.insert("{{project}}", "Infrastructure Upgrade");
539 replacements.insert("{{status}}", "In Progress");
540 let n = doc.replace_all(&replacements);
541 doc.add_paragraph(&format!("({n} placeholders replaced above)"));
542
543 doc.add_paragraph("");
544
545 doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
546 doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
547 let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
548 doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
549
550 doc.add_paragraph("");
551
552 doc.add_paragraph("7.3 Content Insertion").style("Heading2");
553 doc.add_paragraph("Section A: First content.");
554 doc.add_paragraph("Section C: Third content.");
555 if let Some(idx) = doc.find_content_index("Section C") {
556 doc.insert_paragraph(
557 idx,
558 "Section B: Inserted between A and C via find_content_index().",
559 );
560 }
561
562 // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
563 doc.add_paragraph("").section_break(SectionBreak::NextPage);
564 doc.add_paragraph("8. Section Breaks & Orientation")
565 .style("Heading1");
566 doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
567
568 {
569 let mut tbl = doc
570 .add_table(3, 7)
571 .borders(BorderStyle::Single, 4, "2E75B6");
572 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
573 for (c, h) in headers.iter().enumerate() {
574 tbl.cell(0, c).unwrap().set_text(h);
575 tbl.cell(0, c).unwrap().shading("2E75B6");
576 }
577 let data = [
578 [
579 "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
580 ],
581 [
582 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
583 ],
584 ];
585 for (r, row) in data.iter().enumerate() {
586 for (c, v) in row.iter().enumerate() {
587 tbl.cell(r + 1, c).unwrap().set_text(v);
588 }
589 }
590 }
591
592 doc.add_paragraph("")
593 .section_break(SectionBreak::NextPage)
594 .section_landscape();
595
596 // ── SECTION 9: CUSTOM STYLES ──
597 doc.add_paragraph("9. Custom Styles").style("Heading1");
598 doc.add_style(
599 StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
600 .based_on("Normal")
601 .paragraph_properties(rdocx_oxml::properties::CT_PPr {
602 shading: Some(rdocx_oxml::properties::CT_Shd {
603 val: "clear".to_string(),
604 color: None,
605 fill: Some("FFF2CC".to_string()),
606 }),
607 ..Default::default()
608 })
609 .run_properties(rdocx_oxml::properties::CT_RPr {
610 bold: Some(true),
611 color: Some("C45911".to_string()),
612 ..Default::default()
613 }),
614 );
615 doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
616 .style("CustomHighlight");
617
618 doc.add_paragraph("");
619
620 // ── SECTION 10: DOCUMENT INTELLIGENCE ──
621 doc.add_paragraph("10. Document Intelligence API")
622 .style("Heading1");
623
624 let wc = doc.word_count();
625 let hc = doc.headings().len();
626 let ic = doc.images().len();
627 let lc = doc.links().len();
628 doc.add_paragraph(&format!("Word count: {wc}"));
629 doc.add_paragraph(&format!("Heading count: {hc}"));
630 doc.add_paragraph(&format!("Image count: {ic}"));
631 doc.add_paragraph(&format!("Link count: {lc}"));
632
633 let outline = doc.document_outline();
634 doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
635
636 let issues = doc.audit_accessibility();
637 doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
638 for issue in &issues {
639 doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
640 }
641
642 // ── SECTION 11: DOCUMENT MERGING ──
643 doc.add_paragraph("");
644 doc.add_paragraph("11. Document Merging").style("Heading1");
645 {
646 let mut other = Document::new();
647 other.add_paragraph("This paragraph was merged from another document using append().");
648 doc.append(&other);
649 }
650
651 doc.add_paragraph("");
652
653 // ── SUMMARY ──
654 doc.add_paragraph("Summary of Demonstrated Features")
655 .style("Heading1");
656 let features = [
657 "Page setup: size, margins, header/footer distance, gutter",
658 "Document metadata: title, author, subject, keywords",
659 "Headers and footers: text, images, different first page",
660 "Background images (full-page behind text)",
661 "Table of Contents generation with bookmarks",
662 "Text formatting: bold, italic, underline styles, strike, color, size, font",
663 "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
664 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
665 "Pagination controls: keep-with-next, keep-together, widow control",
666 "Bullet and numbered lists with nesting",
667 "Tab stops with dot/underscore/hyphen leaders",
668 "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
669 "Row properties: header rows, exact height, cant-split",
670 "Cell properties: width, no-wrap, vertical alignment",
671 "Inline images and header images",
672 "Placeholder replacement (single and batch)",
673 "Regex-based find and replace",
674 "Content insertion at specific positions",
675 "Section breaks with mixed portrait/landscape",
676 "Custom paragraph and character styles",
677 "Document intelligence: word count, headings, outline, images, links",
678 "Accessibility audit",
679 "Document merging (append)",
680 "Export: DOCX, PDF, HTML, Markdown",
681 ];
682 for f in &features {
683 doc.add_bullet_list_item(f, 0);
684 }
685 doc.add_paragraph("");
686 doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
687 .alignment(Alignment::Center)
688 .shading("E2EFDA")
689 .border_all(BorderStyle::Single, 2, "00AA00");
690
691 doc
692}
693
694// =============================================================================
695// 2. PROPOSAL DOCUMENT — Deep navy + gold scheme
696// =============================================================================
697fn generate_proposal(_samples_dir: &Path) -> Document {
698 let mut doc = Document::new();
699
700 // Colors: Navy #1B2A4A, Gold #C5922E, Light #F4F1EB
701 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
702 doc.set_margins(
703 Length::inches(1.0),
704 Length::inches(1.0),
705 Length::inches(1.0),
706 Length::inches(1.0),
707 );
708 doc.set_title("AI Platform Modernization Proposal");
709 doc.set_author("Walter White");
710 doc.set_subject("Technology Proposal");
711 doc.set_keywords("proposal, AI, modernization, Tensorbee");
712
713 // Banner header
714 let logo_img = create_logo_png(220, 48);
715 let banner = build_header_banner_xml(
716 "rId1",
717 &BannerOpts {
718 bg_color: "1B2A4A",
719 banner_width: 7772400,
720 banner_height: 914400,
721 logo_width: 2011680,
722 logo_height: 438912,
723 logo_x_offset: 295125,
724 logo_y_offset: 237744,
725 },
726 );
727 doc.set_raw_header_with_images(
728 banner,
729 &[("rId1", &logo_img, "logo.png")],
730 rdocx_oxml::header_footer::HdrFtrType::Default,
731 );
732 doc.set_different_first_page(true);
733 let cover_banner = build_header_banner_xml(
734 "rId1",
735 &BannerOpts {
736 bg_color: "C5922E",
737 banner_width: 7772400,
738 banner_height: 914400,
739 logo_width: 2011680,
740 logo_height: 438912,
741 logo_x_offset: 295125,
742 logo_y_offset: 237744,
743 },
744 );
745 doc.set_raw_header_with_images(
746 cover_banner,
747 &[("rId1", &logo_img, "logo.png")],
748 rdocx_oxml::header_footer::HdrFtrType::First,
749 );
750 doc.set_margins(
751 Length::twips(2292),
752 Length::twips(1440),
753 Length::twips(1440),
754 Length::twips(1440),
755 );
756 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
757 doc.set_footer("Tensorbee — Confidential");
758
759 // Custom styles
760 doc.add_style(
761 StyleBuilder::paragraph("ProposalTitle", "Proposal Title")
762 .based_on("Normal")
763 .run_properties(rdocx_oxml::properties::CT_RPr {
764 bold: Some(true),
765 font_ascii: Some("Georgia".to_string()),
766 font_hansi: Some("Georgia".to_string()),
767 sz: Some(rdocx_oxml::HalfPoint(56)), // half-points → 28pt
768 color: Some("1B2A4A".to_string()),
769 ..Default::default()
770 }),
771 );
772
773 // ── Cover Page ──
774 for _ in 0..4 {
775 doc.add_paragraph("");
776 }
777 doc.add_paragraph("AI Platform Modernization")
778 .style("ProposalTitle")
779 .alignment(Alignment::Center);
780 doc.add_paragraph("")
781 .alignment(Alignment::Center)
782 .border_bottom(BorderStyle::Single, 8, "C5922E");
783 {
784 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
785 p.add_run("Prepared for Global Financial Corp.")
786 .size(14.0)
787 .color("666666")
788 .italic(true);
789 }
790 {
791 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
792 p.add_run("Prepared by: Walter White, CEO — Tensorbee")
793 .size(12.0)
794 .color("888888");
795 }
796 {
797 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
798 p.add_run("February 22, 2026").size(12.0).color("888888");
799 }
800
801 // ── TOC ──
802 doc.add_paragraph("").page_break_before(true);
803 doc.add_paragraph("Table of Contents").style("Heading1");
804 // We'll add a manual-style TOC since the insert_toc goes at a specific position
805 doc.insert_toc(doc.content_count(), 2);
806
807 // ── Executive Summary ──
808 doc.add_paragraph("").page_break_before(true);
809 doc.add_paragraph("Executive Summary").style("Heading1");
810 doc.add_paragraph(
811 "Tensorbee proposes a comprehensive modernization of Global Financial Corp's AI platform, \
812 transitioning from legacy batch-processing systems to a real-time inference architecture. \
813 This transformation will reduce model deployment time from weeks to hours, improve \
814 prediction accuracy by an estimated 23%, and deliver $4.2M in annual operational savings.",
815 )
816 .first_line_indent(Length::inches(0.3));
817 doc.add_paragraph(
818 "The project spans three phases over 18 months, with a total investment of $2.8M. \
819 Tensorbee brings deep expertise in ML infrastructure, having successfully completed \
820 similar transformations for three Fortune 500 financial institutions.",
821 )
822 .first_line_indent(Length::inches(0.3));
823
824 // Key metrics table
825 doc.add_paragraph("");
826 {
827 let mut tbl = doc
828 .add_table(5, 2)
829 .borders(BorderStyle::Single, 4, "1B2A4A")
830 .width_pct(80.0);
831 tbl.cell(0, 0).unwrap().set_text("Key Metric");
832 tbl.cell(0, 0).unwrap().shading("1B2A4A");
833 tbl.cell(0, 1).unwrap().set_text("Value");
834 tbl.cell(0, 1).unwrap().shading("1B2A4A");
835 let rows = [
836 ("Total Investment", "$2.8M"),
837 ("Annual Savings", "$4.2M"),
838 ("ROI (Year 1)", "150%"),
839 ("Timeline", "18 months"),
840 ];
841 for (i, (k, v)) in rows.iter().enumerate() {
842 tbl.cell(i + 1, 0).unwrap().set_text(k);
843 if i % 2 == 0 {
844 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
845 }
846 tbl.cell(i + 1, 1).unwrap().set_text(v);
847 if i % 2 == 0 {
848 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
849 }
850 }
851 }
852
853 // ── Problem Statement ──
854 doc.add_paragraph("").page_break_before(true);
855 doc.add_paragraph("Problem Statement").style("Heading1");
856 doc.add_paragraph(
857 "Global Financial Corp's current AI infrastructure faces three critical challenges:",
858 );
859 doc.add_numbered_list_item("Deployment Latency — New models take 3-4 weeks to deploy to production, compared to the industry benchmark of 2-3 days.", 0);
860 doc.add_numbered_list_item("Scalability Constraints — The batch processing architecture cannot handle real-time inference demands during peak trading hours.", 0);
861 doc.add_numbered_list_item("Technical Debt — Legacy Python 2.7 codebases and manual deployment scripts create reliability risks and slow iteration speed.", 0);
862
863 // ── Proposed Solution ──
864 doc.add_paragraph("").page_break_before(true);
865 doc.add_paragraph("Proposed Solution").style("Heading1");
866
867 doc.add_paragraph("Phase 1: Foundation (Months 1-6)")
868 .style("Heading2");
869 doc.add_bullet_list_item("Deploy Kubernetes-based ML serving infrastructure", 0);
870 doc.add_bullet_list_item("Implement CI/CD pipeline for model deployment", 0);
871 doc.add_bullet_list_item("Migrate top 5 production models to new platform", 0);
872
873 doc.add_paragraph("Phase 2: Expansion (Months 7-12)")
874 .style("Heading2");
875 doc.add_bullet_list_item(
876 "Real-time feature engineering pipeline (Apache Kafka + Flink)",
877 0,
878 );
879 doc.add_bullet_list_item("A/B testing framework for model variants", 0);
880 doc.add_bullet_list_item("Automated model monitoring and drift detection", 0);
881
882 doc.add_paragraph("Phase 3: Optimization (Months 13-18)")
883 .style("Heading2");
884 doc.add_bullet_list_item("GPU inference optimization (TensorRT/ONNX Runtime)", 0);
885 doc.add_bullet_list_item("Multi-region deployment for latency reduction", 0);
886 doc.add_bullet_list_item(
887 "Self-service model deployment portal for data science teams",
888 0,
889 );
890
891 // ── Budget ──
892 doc.add_paragraph("Budget Breakdown").style("Heading1");
893 {
894 let mut tbl = doc
895 .add_table(6, 3)
896 .borders(BorderStyle::Single, 4, "1B2A4A")
897 .width_pct(100.0);
898 tbl.cell(0, 0).unwrap().set_text("Category");
899 tbl.cell(0, 0).unwrap().shading("1B2A4A");
900 tbl.cell(0, 1).unwrap().set_text("Cost");
901 tbl.cell(0, 1).unwrap().shading("1B2A4A");
902 tbl.cell(0, 2).unwrap().set_text("Notes");
903 tbl.cell(0, 2).unwrap().shading("1B2A4A");
904 let items = [
905 (
906 "Infrastructure",
907 "$450,000",
908 "Cloud compute + GPU instances",
909 ),
910 ("Engineering Services", "$1,600,000", "12 FTE x 18 months"),
911 (
912 "Software Licenses",
913 "$350,000",
914 "Kafka, monitoring, CI/CD tools",
915 ),
916 (
917 "Training & Enablement",
918 "$200,000",
919 "Team training, documentation",
920 ),
921 ("Contingency (10%)", "$200,000", "Risk buffer"),
922 ];
923 for (i, (cat, cost, note)) in items.iter().enumerate() {
924 tbl.cell(i + 1, 0).unwrap().set_text(cat);
925 tbl.cell(i + 1, 1).unwrap().set_text(cost);
926 tbl.cell(i + 1, 2).unwrap().set_text(note);
927 if i % 2 == 0 {
928 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
929 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
930 tbl.cell(i + 1, 2).unwrap().shading("F4F1EB");
931 }
932 }
933 }
934
935 // ── Closing ──
936 doc.add_paragraph("");
937 doc.add_paragraph("Next Steps").style("Heading1");
938 doc.add_numbered_list_item(
939 "Schedule technical deep-dive with GFC engineering team (Week 1)",
940 0,
941 );
942 doc.add_numbered_list_item("Finalize scope and sign SOW (Week 2-3)", 0);
943 doc.add_numbered_list_item("Kick off Phase 1 with joint planning session (Week 4)", 0);
944
945 doc.add_paragraph("");
946 {
947 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
948 p.add_run("Walter White")
949 .bold(true)
950 .size(14.0)
951 .color("1B2A4A");
952 }
953 {
954 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
955 p.add_run("CEO, Tensorbee | walter@tensorbee.com")
956 .size(10.0)
957 .color("888888");
958 }
959
960 doc
961}
962
963// =============================================================================
964// 3. QUOTE / BILL OF MATERIALS — Teal + orange scheme
965// =============================================================================
966fn generate_quote(_samples_dir: &Path) -> Document {
967 let mut doc = Document::new();
968
969 // Colors: Teal #008B8B, Dark #1A3C3C, Orange #E07020, Light #F0F8F8
970 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
971 doc.set_margins(
972 Length::inches(0.75),
973 Length::inches(0.75),
974 Length::inches(0.75),
975 Length::inches(0.75),
976 );
977 doc.set_title("Quotation QT-2026-0147");
978 doc.set_author("Walter White");
979 doc.set_keywords("quote, BOM, Tensorbee");
980
981 doc.set_header("Tensorbee — Quotation");
982 doc.set_footer("QT-2026-0147 | Page");
983
984 // ── Header Block ──
985 {
986 let mut p = doc.add_paragraph("").alignment(Alignment::Left);
987 p.add_run("TENSORBEE")
988 .bold(true)
989 .size(28.0)
990 .color("008B8B")
991 .font("Helvetica");
992 }
993 doc.add_paragraph("123 Innovation Drive, Suite 400")
994 .alignment(Alignment::Left);
995 doc.add_paragraph("San Francisco, CA 94105 | +1 (415) 555-0199")
996 .alignment(Alignment::Left);
997 doc.add_paragraph("")
998 .border_bottom(BorderStyle::Single, 8, "008B8B");
999
1000 // Quote meta
1001 doc.add_paragraph("");
1002 {
1003 let mut tbl = doc.add_table(4, 4).width_pct(100.0);
1004 tbl.cell(0, 0).unwrap().set_text("QUOTATION");
1005 tbl.cell(0, 0).unwrap().shading("008B8B").grid_span(2);
1006 tbl.cell(0, 2).unwrap().set_text("DATE");
1007 tbl.cell(0, 2).unwrap().shading("008B8B");
1008 tbl.cell(0, 3).unwrap().set_text("VALID UNTIL");
1009 tbl.cell(0, 3).unwrap().shading("008B8B");
1010 tbl.cell(1, 0).unwrap().set_text("Quote #:");
1011 tbl.cell(1, 1).unwrap().set_text("QT-2026-0147");
1012 tbl.cell(1, 2).unwrap().set_text("Feb 22, 2026");
1013 tbl.cell(1, 3).unwrap().set_text("Mar 22, 2026");
1014 tbl.cell(2, 0).unwrap().set_text("Prepared by:");
1015 tbl.cell(2, 1).unwrap().set_text("Walter White");
1016 tbl.cell(2, 2).unwrap().set_text("Payment:");
1017 tbl.cell(2, 3).unwrap().set_text("Net 30");
1018 tbl.cell(3, 0).unwrap().set_text("Customer:");
1019 tbl.cell(3, 1).unwrap().set_text("Meridian Dynamics LLC");
1020 tbl.cell(3, 2).unwrap().set_text("Currency:");
1021 tbl.cell(3, 3).unwrap().set_text("USD");
1022 }
1023
1024 doc.add_paragraph("");
1025
1026 // ── Bill of Materials ──
1027 {
1028 let mut p = doc.add_paragraph("");
1029 p.add_run("Bill of Materials")
1030 .bold(true)
1031 .size(16.0)
1032 .color("1A3C3C");
1033 }
1034 doc.add_paragraph("");
1035
1036 {
1037 let mut tbl = doc
1038 .add_table(10, 6)
1039 .borders(BorderStyle::Single, 2, "008B8B")
1040 .width_pct(100.0)
1041 .layout_fixed();
1042 // Headers
1043 let hdrs = [
1044 "#",
1045 "Part Number",
1046 "Description",
1047 "Qty",
1048 "Unit Price",
1049 "Total",
1050 ];
1051 for (c, h) in hdrs.iter().enumerate() {
1052 tbl.cell(0, c).unwrap().set_text(h);
1053 tbl.cell(0, c).unwrap().shading("008B8B");
1054 }
1055
1056 let items: Vec<(&str, &str, &str, &str, &str)> = vec![
1057 (
1058 "TB-GPU-A100",
1059 "NVIDIA A100 80GB GPU",
1060 "4",
1061 "$12,500.00",
1062 "$50,000.00",
1063 ),
1064 (
1065 "TB-SRV-R750",
1066 "Dell R750xa Server Chassis",
1067 "2",
1068 "$8,200.00",
1069 "$16,400.00",
1070 ),
1071 (
1072 "TB-RAM-256G",
1073 "256GB DDR5 ECC Memory Module",
1074 "8",
1075 "$890.00",
1076 "$7,120.00",
1077 ),
1078 (
1079 "TB-SSD-3840",
1080 "3.84TB NVMe U.2 SSD",
1081 "8",
1082 "$1,150.00",
1083 "$9,200.00",
1084 ),
1085 (
1086 "TB-NET-CX7",
1087 "ConnectX-7 200GbE NIC",
1088 "4",
1089 "$1,800.00",
1090 "$7,200.00",
1091 ),
1092 (
1093 "TB-SW-48P",
1094 "48-Port 100GbE Switch",
1095 "1",
1096 "$22,000.00",
1097 "$22,000.00",
1098 ),
1099 (
1100 "TB-CAB-RACK",
1101 "42U Server Rack + PDU",
1102 "1",
1103 "$4,500.00",
1104 "$4,500.00",
1105 ),
1106 (
1107 "TB-SVC-INST",
1108 "Installation & Configuration",
1109 "1",
1110 "$8,500.00",
1111 "$8,500.00",
1112 ),
1113 (
1114 "TB-SVC-SUPP",
1115 "3-Year Premium Support",
1116 "1",
1117 "$15,000.00",
1118 "$15,000.00",
1119 ),
1120 ];
1121
1122 for (i, (pn, desc, qty, unit, total)) in items.iter().enumerate() {
1123 let row = i + 1;
1124 tbl.cell(row, 0).unwrap().set_text(&format!("{}", i + 1));
1125 tbl.cell(row, 1).unwrap().set_text(pn);
1126 tbl.cell(row, 2).unwrap().set_text(desc);
1127 tbl.cell(row, 3).unwrap().set_text(qty);
1128 tbl.cell(row, 4).unwrap().set_text(unit);
1129 tbl.cell(row, 5).unwrap().set_text(total);
1130 if i % 2 == 0 {
1131 for c in 0..6 {
1132 tbl.cell(row, c).unwrap().shading("F0F8F8");
1133 }
1134 }
1135 }
1136 }
1137
1138 doc.add_paragraph("");
1139
1140 // ── Totals ──
1141 {
1142 let mut tbl = doc
1143 .add_table(4, 2)
1144 .borders(BorderStyle::Single, 2, "008B8B")
1145 .width(Length::inches(3.5))
1146 .alignment(Alignment::Right);
1147 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1148 tbl.cell(0, 1).unwrap().set_text("$139,920.00");
1149 tbl.cell(1, 0).unwrap().set_text("Shipping & Handling");
1150 tbl.cell(1, 1).unwrap().set_text("$2,500.00");
1151 tbl.cell(2, 0).unwrap().set_text("Tax (8.625%)");
1152 tbl.cell(2, 1).unwrap().set_text("$12,068.10");
1153 tbl.cell(3, 0).unwrap().set_text("TOTAL");
1154 tbl.cell(3, 0).unwrap().shading("E07020");
1155 tbl.cell(3, 1).unwrap().set_text("$154,488.10");
1156 tbl.cell(3, 1).unwrap().shading("E07020");
1157 }
1158
1159 doc.add_paragraph("");
1160
1161 // ── Terms & Conditions ──
1162 {
1163 let mut p = doc.add_paragraph("");
1164 p.add_run("Terms & Conditions")
1165 .bold(true)
1166 .size(14.0)
1167 .color("1A3C3C");
1168 }
1169 doc.add_numbered_list_item(
1170 "This quotation is valid for 30 calendar days from the date of issue.",
1171 0,
1172 );
1173 doc.add_numbered_list_item(
1174 "All prices are in USD and exclusive of applicable taxes unless stated.",
1175 0,
1176 );
1177 doc.add_numbered_list_item("Standard lead time is 4-6 weeks from PO receipt.", 0);
1178 doc.add_numbered_list_item(
1179 "Payment terms: Net 30 from invoice date. 2% discount for payment within 10 days.",
1180 0,
1181 );
1182 doc.add_numbered_list_item(
1183 "Warranty: 3-year manufacturer warranty on all hardware components.",
1184 0,
1185 );
1186 doc.add_numbered_list_item(
1187 "Returns subject to 15% restocking fee if initiated after 14 days.",
1188 0,
1189 );
1190
1191 doc.add_paragraph("");
1192 doc.add_paragraph("")
1193 .border_bottom(BorderStyle::Single, 4, "008B8B");
1194 doc.add_paragraph("");
1195 {
1196 let mut p = doc.add_paragraph("");
1197 p.add_run("Accepted by: ").bold(true);
1198 p.add_run("___________________________ Date: ___________");
1199 }
1200 {
1201 let mut p = doc.add_paragraph("");
1202 p.add_run("Print Name: ").bold(true);
1203 p.add_run("___________________________ Title: ___________");
1204 }
1205
1206 doc
1207}
1208
1209// =============================================================================
1210// 4. INVOICE — Crimson + charcoal scheme
1211// =============================================================================
1212fn generate_invoice(_samples_dir: &Path) -> Document {
1213 let mut doc = Document::new();
1214
1215 // Colors: Crimson #B22222, Charcoal #333333, Light #FAF0F0
1216 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1217 doc.set_margins(
1218 Length::inches(0.75),
1219 Length::inches(0.75),
1220 Length::inches(0.75),
1221 Length::inches(0.75),
1222 );
1223 doc.set_title("Invoice INV-2026-0392");
1224 doc.set_author("Walter White");
1225 doc.set_keywords("invoice, Tensorbee");
1226
1227 doc.set_footer("Tensorbee — Thank you for your business!");
1228
1229 // ── Company & Invoice Header ──
1230 {
1231 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1232 // Company name left, INVOICE right
1233 {
1234 let mut cell = tbl.cell(0, 0).unwrap();
1235 let mut p = cell.add_paragraph("");
1236 p.add_run("TENSORBEE")
1237 .bold(true)
1238 .size(32.0)
1239 .color("B22222")
1240 .font("Helvetica");
1241 }
1242 {
1243 let mut cell = tbl.cell(0, 1).unwrap();
1244 let mut p = cell.add_paragraph("");
1245 p.add_run("INVOICE").bold(true).size(32.0).color("333333");
1246 }
1247 tbl.cell(1, 0)
1248 .unwrap()
1249 .set_text("123 Innovation Drive, Suite 400");
1250 tbl.cell(1, 1).unwrap().set_text("Invoice #: INV-2026-0392");
1251 tbl.cell(2, 0).unwrap().set_text("San Francisco, CA 94105");
1252 tbl.cell(2, 1).unwrap().set_text("Date: February 22, 2026");
1253 tbl.cell(3, 0).unwrap().set_text("walter@tensorbee.com");
1254 tbl.cell(3, 1).unwrap().set_text("Due Date: March 24, 2026");
1255 }
1256
1257 doc.add_paragraph("")
1258 .border_bottom(BorderStyle::Thick, 8, "B22222");
1259 doc.add_paragraph("");
1260
1261 // ── Bill To / Ship To ──
1262 {
1263 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1264 tbl.cell(0, 0).unwrap().set_text("BILL TO");
1265 tbl.cell(0, 0).unwrap().shading("B22222");
1266 tbl.cell(0, 1).unwrap().set_text("SHIP TO");
1267 tbl.cell(0, 1).unwrap().shading("B22222");
1268 tbl.cell(1, 0).unwrap().set_text("Meridian Dynamics LLC");
1269 tbl.cell(1, 1).unwrap().set_text("Meridian Dynamics LLC");
1270 tbl.cell(2, 0).unwrap().set_text("456 Enterprise Blvd");
1271 tbl.cell(2, 1).unwrap().set_text("Attn: Server Room B");
1272 tbl.cell(3, 0).unwrap().set_text("Austin, TX 78701");
1273 tbl.cell(3, 1)
1274 .unwrap()
1275 .set_text("456 Enterprise Blvd, Austin, TX 78701");
1276 }
1277
1278 doc.add_paragraph("");
1279
1280 // ── Line Items ──
1281 {
1282 let mut tbl = doc
1283 .add_table(8, 5)
1284 .borders(BorderStyle::Single, 2, "B22222")
1285 .width_pct(100.0);
1286 let hdrs = ["Description", "Qty", "Unit Price", "Tax", "Amount"];
1287 for (c, h) in hdrs.iter().enumerate() {
1288 tbl.cell(0, c).unwrap().set_text(h);
1289 tbl.cell(0, c).unwrap().shading("B22222");
1290 }
1291 let items = [
1292 (
1293 "ML Infrastructure Setup — Phase 1",
1294 "1",
1295 "$45,000.00",
1296 "$3,881.25",
1297 "$48,881.25",
1298 ),
1299 (
1300 "Data Pipeline Development (160 hrs)",
1301 "160",
1302 "$225.00",
1303 "$3,105.00",
1304 "$39,105.00",
1305 ),
1306 (
1307 "GPU Cluster Configuration",
1308 "1",
1309 "$12,000.00",
1310 "$1,035.00",
1311 "$13,035.00",
1312 ),
1313 (
1314 "API Gateway Implementation",
1315 "1",
1316 "$18,500.00",
1317 "$1,595.63",
1318 "$20,095.63",
1319 ),
1320 (
1321 "Load Testing & QA (80 hrs)",
1322 "80",
1323 "$195.00",
1324 "$1,345.50",
1325 "$16,945.50",
1326 ),
1327 (
1328 "Documentation & Training",
1329 "1",
1330 "$8,000.00",
1331 "$690.00",
1332 "$8,690.00",
1333 ),
1334 (
1335 "Project Management (3 months)",
1336 "3",
1337 "$6,500.00",
1338 "$1,679.63",
1339 "$21,179.63",
1340 ),
1341 ];
1342 for (i, (desc, qty, unit, tax, amt)) in items.iter().enumerate() {
1343 let r = i + 1;
1344 tbl.cell(r, 0).unwrap().set_text(desc);
1345 tbl.cell(r, 1).unwrap().set_text(qty);
1346 tbl.cell(r, 2).unwrap().set_text(unit);
1347 tbl.cell(r, 3).unwrap().set_text(tax);
1348 tbl.cell(r, 4).unwrap().set_text(amt);
1349 if i % 2 == 0 {
1350 for c in 0..5 {
1351 tbl.cell(r, c).unwrap().shading("FAF0F0");
1352 }
1353 }
1354 }
1355 }
1356
1357 doc.add_paragraph("");
1358
1359 // ── Totals ──
1360 {
1361 let mut tbl = doc
1362 .add_table(4, 2)
1363 .borders(BorderStyle::Single, 2, "B22222")
1364 .width(Length::inches(3.0))
1365 .alignment(Alignment::Right);
1366 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1367 tbl.cell(0, 1).unwrap().set_text("$154,600.00");
1368 tbl.cell(1, 0).unwrap().set_text("Tax (8.625%)");
1369 tbl.cell(1, 1).unwrap().set_text("$13,334.25");
1370 tbl.cell(2, 0).unwrap().set_text("Discount (5%)");
1371 tbl.cell(2, 1).unwrap().set_text("-$7,730.00");
1372 tbl.cell(3, 0).unwrap().set_text("AMOUNT DUE");
1373 tbl.cell(3, 0).unwrap().shading("B22222");
1374 tbl.cell(3, 1).unwrap().set_text("$160,204.25");
1375 tbl.cell(3, 1).unwrap().shading("B22222");
1376 }
1377
1378 doc.add_paragraph("");
1379 doc.add_paragraph("");
1380
1381 // ── Payment Details ──
1382 {
1383 let mut p = doc.add_paragraph("");
1384 p.add_run("Payment Details")
1385 .bold(true)
1386 .size(14.0)
1387 .color("333333");
1388 }
1389 doc.add_paragraph("Bank: Silicon Valley Bank")
1390 .indent_left(Length::inches(0.3));
1391 doc.add_paragraph("Account: Tensorbee Inc. — 0847-2953-1120")
1392 .indent_left(Length::inches(0.3));
1393 doc.add_paragraph("Routing: 121140399")
1394 .indent_left(Length::inches(0.3));
1395 doc.add_paragraph("Swift: SVBKUS6S")
1396 .indent_left(Length::inches(0.3));
1397
1398 doc.add_paragraph("");
1399
1400 doc.add_paragraph("Please include invoice number INV-2026-0392 in the payment reference.")
1401 .shading("FAF0F0")
1402 .border_all(BorderStyle::Single, 2, "B22222");
1403
1404 doc
1405}
1406
1407// =============================================================================
1408// 5. REPORT — Forest green + earth tones, with images & hierarchical sections
1409// =============================================================================
1410fn generate_report(_samples_dir: &Path) -> Document {
1411 let mut doc = Document::new();
1412
1413 // Colors: Forest #2D5016, Sage #6B8E23, Earth #8B7355, Cream #FFFAF0
1414 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1415 doc.set_margins(
1416 Length::inches(1.0),
1417 Length::inches(1.0),
1418 Length::inches(1.0),
1419 Length::inches(1.0),
1420 );
1421 doc.set_title("Q4 2025 Environmental Impact Report");
1422 doc.set_author("Walter White");
1423 doc.set_subject("Quarterly Environmental Report");
1424 doc.set_keywords("environment, sustainability, report, Tensorbee");
1425
1426 doc.set_different_first_page(true);
1427 doc.set_first_page_header("");
1428 doc.set_header("Tensorbee — Q4 2025 Environmental Impact Report");
1429 doc.set_footer("CONFIDENTIAL — Page");
1430
1431 // ── Cover ──
1432 let cover_bg = create_sample_png(612, 792, [20, 50, 15]);
1433 doc.add_background_image(&cover_bg, "report_cover.png");
1434
1435 for _ in 0..5 {
1436 doc.add_paragraph("");
1437 }
1438 {
1439 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1440 p.add_run("Q4 2025")
1441 .bold(true)
1442 .size(48.0)
1443 .color("FFFFFF")
1444 .font("Georgia");
1445 }
1446 {
1447 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1448 p.add_run("Environmental Impact Report")
1449 .size(24.0)
1450 .color("90EE90")
1451 .font("Georgia")
1452 .italic(true);
1453 }
1454 doc.add_paragraph("");
1455 {
1456 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1457 p.add_run("Tensorbee — Sustainability Division")
1458 .size(14.0)
1459 .color("C0C0C0");
1460 }
1461 {
1462 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1463 p.add_run("Prepared by Walter White, Chief Sustainability Officer")
1464 .size(11.0)
1465 .color("AAAAAA");
1466 }
1467
1468 // ── TOC ──
1469 doc.add_paragraph("").page_break_before(true);
1470 doc.insert_toc(doc.content_count(), 3);
1471
1472 // ── Section 1: Executive Overview ──
1473 doc.add_paragraph("").page_break_before(true);
1474 doc.add_paragraph("Executive Overview").style("Heading1");
1475 doc.add_paragraph(
1476 "This report presents Tensorbee's environmental performance for Q4 2025. Our \
1477 sustainability initiatives have yielded a 34% reduction in carbon emissions compared \
1478 to Q4 2024, exceeding our target of 25%. Key achievements include the transition to \
1479 100% renewable energy in our primary data centers and the launch of our carbon offset \
1480 marketplace.",
1481 )
1482 .first_line_indent(Length::inches(0.3));
1483
1484 // Image: performance chart
1485 let chart_img = create_chart_png(400, 200);
1486 doc.add_paragraph("");
1487 doc.add_picture(
1488 &chart_img,
1489 "performance_chart.png",
1490 Length::inches(5.0),
1491 Length::inches(2.5),
1492 )
1493 .alignment(Alignment::Center);
1494 {
1495 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1496 p.add_run("Figure 1: Quarterly Carbon Emissions (tonnes CO2e)")
1497 .italic(true)
1498 .size(9.0)
1499 .color("666666");
1500 }
1501
1502 // ── Section 2: Energy Consumption ──
1503 doc.add_paragraph("").page_break_before(true);
1504 doc.add_paragraph("Energy Consumption").style("Heading1");
1505
1506 doc.add_paragraph("Data Center Operations")
1507 .style("Heading2");
1508 doc.add_paragraph(
1509 "Our three primary data centers consumed a combined 4.2 GWh in Q4 2025, \
1510 a 12% reduction from Q3 2025 driven by improved cooling efficiency and \
1511 server consolidation.",
1512 );
1513
1514 // Data table
1515 {
1516 let mut tbl = doc
1517 .add_table(5, 4)
1518 .borders(BorderStyle::Single, 4, "2D5016")
1519 .width_pct(100.0);
1520 let hdrs = ["Data Center", "Capacity (MW)", "Usage (GWh)", "PUE"];
1521 for (c, h) in hdrs.iter().enumerate() {
1522 tbl.cell(0, c).unwrap().set_text(h);
1523 tbl.cell(0, c).unwrap().shading("2D5016");
1524 }
1525 let rows = [
1526 ("San Francisco (Primary)", "3.2", "1.8", "1.12"),
1527 ("Dublin (EU)", "2.1", "1.2", "1.18"),
1528 ("Singapore (APAC)", "1.8", "1.2", "1.24"),
1529 ("TOTAL", "7.1", "4.2", "1.17 avg"),
1530 ];
1531 for (i, (dc, cap, use_, pue)) in rows.iter().enumerate() {
1532 tbl.cell(i + 1, 0).unwrap().set_text(dc);
1533 tbl.cell(i + 1, 1).unwrap().set_text(cap);
1534 tbl.cell(i + 1, 2).unwrap().set_text(use_);
1535 tbl.cell(i + 1, 3).unwrap().set_text(pue);
1536 if i == 3 {
1537 for c in 0..4 {
1538 tbl.cell(i + 1, c).unwrap().shading("E2EFDA");
1539 }
1540 }
1541 }
1542 }
1543
1544 doc.add_paragraph("");
1545
1546 doc.add_paragraph("Renewable Energy Mix").style("Heading2");
1547 doc.add_paragraph("Breakdown of energy sources across all facilities:");
1548
1549 doc.add_bullet_list_item("Solar PV: 42% (1.76 GWh)", 0);
1550 doc.add_bullet_list_item("Wind Power Purchase Agreements: 38% (1.60 GWh)", 0);
1551 doc.add_bullet_list_item("Hydroelectric: 12% (0.50 GWh)", 0);
1552 doc.add_bullet_list_item("Grid (non-renewable): 8% (0.34 GWh)", 0);
1553
1554 // ── Section 3: Water & Waste ──
1555 doc.add_paragraph("Water & Waste Management")
1556 .style("Heading1");
1557
1558 doc.add_paragraph("Water Usage").style("Heading2");
1559 doc.add_paragraph(
1560 "Total water consumption was 12.4 million gallons, a 15% reduction from Q3. \
1561 Our closed-loop cooling systems now recycle 78% of water used in cooling operations.",
1562 );
1563
1564 doc.add_paragraph("Waste Reduction").style("Heading2");
1565 doc.add_paragraph("E-waste management results for Q4:")
1566 .keep_with_next(true);
1567 {
1568 let mut tbl = doc
1569 .add_table(4, 3)
1570 .borders(BorderStyle::Single, 2, "6B8E23")
1571 .width_pct(80.0);
1572 tbl.cell(0, 0).unwrap().set_text("Category");
1573 tbl.cell(0, 0).unwrap().shading("6B8E23");
1574 tbl.cell(0, 1).unwrap().set_text("Weight (kg)");
1575 tbl.cell(0, 1).unwrap().shading("6B8E23");
1576 tbl.cell(0, 2).unwrap().set_text("Recycled %");
1577 tbl.cell(0, 2).unwrap().shading("6B8E23");
1578 let rows = [
1579 ("Server Hardware", "2,450", "94%"),
1580 ("Networking Equipment", "820", "91%"),
1581 ("Storage Media", "340", "99%"),
1582 ];
1583 for (i, (cat, wt, pct)) in rows.iter().enumerate() {
1584 tbl.cell(i + 1, 0).unwrap().set_text(cat);
1585 tbl.cell(i + 1, 1).unwrap().set_text(wt);
1586 tbl.cell(i + 1, 2).unwrap().set_text(pct);
1587 }
1588 }
1589
1590 // ── Section 4: Initiatives ──
1591 doc.add_paragraph("").page_break_before(true);
1592 doc.add_paragraph("Q1 2026 Initiatives").style("Heading1");
1593
1594 doc.add_paragraph("Planned Programs").style("Heading2");
1595 doc.add_numbered_list_item(
1596 "Deploy on-site battery storage at SF data center (2 MWh capacity)",
1597 0,
1598 );
1599 doc.add_numbered_list_item("Pilot immersion cooling in Dublin facility", 0);
1600 doc.add_numbered_list_item("Launch employee commute carbon offset program", 0);
1601 doc.add_numbered_list_item("Achieve ISO 14001 certification for Singapore facility", 0);
1602
1603 doc.add_paragraph("Investment Targets").style("Heading2");
1604 doc.add_paragraph("Sustainability CapEx allocation for FY2026:")
1605 .keep_with_next(true);
1606 {
1607 let mut tbl = doc
1608 .add_table(5, 2)
1609 .borders(BorderStyle::Single, 4, "2D5016");
1610 tbl.cell(0, 0).unwrap().set_text("Initiative");
1611 tbl.cell(0, 0).unwrap().shading("2D5016");
1612 tbl.cell(0, 1).unwrap().set_text("Budget");
1613 tbl.cell(0, 1).unwrap().shading("2D5016");
1614 let rows = [
1615 ("Battery Storage", "$1.2M"),
1616 ("Immersion Cooling Pilot", "$800K"),
1617 ("Solar Panel Expansion", "$2.1M"),
1618 ("Carbon Credits", "$500K"),
1619 ];
1620 for (i, (init, budget)) in rows.iter().enumerate() {
1621 tbl.cell(i + 1, 0).unwrap().set_text(init);
1622 tbl.cell(i + 1, 1).unwrap().set_text(budget);
1623 if i % 2 == 0 {
1624 tbl.cell(i + 1, 0).unwrap().shading("FFFAF0");
1625 tbl.cell(i + 1, 1).unwrap().shading("FFFAF0");
1626 }
1627 }
1628 }
1629
1630 // Image: sustainability roadmap
1631 let roadmap_img = create_sample_png(500, 100, [40, 80, 30]);
1632 doc.add_paragraph("");
1633 doc.add_picture(
1634 &roadmap_img,
1635 "roadmap.png",
1636 Length::inches(6.0),
1637 Length::inches(1.2),
1638 )
1639 .alignment(Alignment::Center);
1640 {
1641 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1642 p.add_run("Figure 2: 2026 Sustainability Roadmap")
1643 .italic(true)
1644 .size(9.0)
1645 .color("666666");
1646 }
1647
1648 doc.add_paragraph("");
1649 doc.add_paragraph(
1650 "For questions about this report, contact Walter White at sustainability@tensorbee.com.",
1651 )
1652 .shading("FFFAF0")
1653 .border_all(BorderStyle::Single, 2, "2D5016");
1654
1655 doc
1656}
1657
1658// =============================================================================
1659// 6. LETTER — Slate blue + silver scheme
1660// =============================================================================
1661fn generate_letter(_samples_dir: &Path) -> Document {
1662 let mut doc = Document::new();
1663
1664 // Colors: Slate #4A5568, Blue accent #3182CE
1665 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1666 doc.set_margins(
1667 Length::inches(1.25),
1668 Length::inches(1.25),
1669 Length::inches(1.25),
1670 Length::inches(1.25),
1671 );
1672 doc.set_title("Business Letter");
1673 doc.set_author("Walter White");
1674
1675 // Banner header using raw XML
1676 let logo_img = create_logo_png(220, 48);
1677 let banner = build_header_banner_xml(
1678 "rId1",
1679 &BannerOpts {
1680 bg_color: "4A5568",
1681 banner_width: 7772400,
1682 banner_height: 731520, // ~0.8"
1683 logo_width: 2011680,
1684 logo_height: 438912,
1685 logo_x_offset: 295125,
1686 logo_y_offset: 146304,
1687 },
1688 );
1689 doc.set_raw_header_with_images(
1690 banner,
1691 &[("rId1", &logo_img, "logo.png")],
1692 rdocx_oxml::header_footer::HdrFtrType::Default,
1693 );
1694 doc.set_margins(
1695 Length::twips(2000),
1696 Length::twips(1800),
1697 Length::twips(1440),
1698 Length::twips(1800),
1699 );
1700 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
1701
1702 // Footer with contact
1703 doc.set_footer(
1704 "Tensorbee | 123 Innovation Drive, Suite 400 | San Francisco, CA 94105 | tensorbee.com",
1705 );
1706
1707 // ── Sender Address ──
1708 doc.add_paragraph("Walter White");
1709 doc.add_paragraph("Chief Executive Officer");
1710 doc.add_paragraph("Tensorbee");
1711 doc.add_paragraph("123 Innovation Drive, Suite 400");
1712 doc.add_paragraph("San Francisco, CA 94105");
1713 doc.add_paragraph("");
1714 doc.add_paragraph("February 22, 2026");
1715
1716 doc.add_paragraph("");
1717
1718 // ── Recipient ──
1719 doc.add_paragraph("Ms. Sarah Chen");
1720 doc.add_paragraph("VP of Engineering");
1721 doc.add_paragraph("Meridian Dynamics LLC");
1722 doc.add_paragraph("456 Enterprise Boulevard");
1723 doc.add_paragraph("Austin, TX 78701");
1724
1725 doc.add_paragraph("");
1726
1727 // ── Subject ──
1728 {
1729 let mut p = doc.add_paragraph("");
1730 p.add_run("Re: Strategic Technology Partnership — Phase 2 Expansion")
1731 .bold(true);
1732 }
1733
1734 doc.add_paragraph("");
1735
1736 // ── Body ──
1737 doc.add_paragraph("Dear Ms. Chen,");
1738 doc.add_paragraph("");
1739
1740 doc.add_paragraph(
1741 "I am writing to express Tensorbee's enthusiasm for expanding our strategic technology \
1742 partnership with Meridian Dynamics. Following the successful completion of Phase 1, \
1743 which delivered a 40% improvement in your ML inference pipeline throughput, we believe \
1744 the foundation is firmly established for an ambitious Phase 2 engagement.",
1745 )
1746 .first_line_indent(Length::inches(0.5));
1747
1748 doc.add_paragraph(
1749 "During our recent executive review, your team highlighted three priority areas for \
1750 the next phase of collaboration:",
1751 )
1752 .first_line_indent(Length::inches(0.5));
1753
1754 doc.add_numbered_list_item(
1755 "Real-time anomaly detection for your financial transaction monitoring system, \
1756 targeting sub-100ms latency at 50,000 transactions per second.",
1757 0,
1758 );
1759 doc.add_numbered_list_item(
1760 "Federated learning infrastructure to enable model training across your distributed \
1761 data centers without centralizing sensitive financial data.",
1762 0,
1763 );
1764 doc.add_numbered_list_item(
1765 "MLOps automation to reduce your model deployment cycle from the current 5 days \
1766 to under 4 hours.",
1767 0,
1768 );
1769
1770 doc.add_paragraph(
1771 "Our engineering team has prepared a detailed technical proposal addressing each of \
1772 these areas. We have allocated a dedicated team of eight senior engineers, led by \
1773 our CTO, to ensure continuity with the Phase 1 team your organization has already \
1774 built a productive working relationship with.",
1775 )
1776 .first_line_indent(Length::inches(0.5));
1777
1778 doc.add_paragraph(
1779 "I would welcome the opportunity to present our Phase 2 proposal in person. My \
1780 assistant will reach out to coordinate a meeting at your convenience during the \
1781 first week of March.",
1782 )
1783 .first_line_indent(Length::inches(0.5));
1784
1785 doc.add_paragraph("");
1786
1787 doc.add_paragraph("Warm regards,");
1788 doc.add_paragraph("");
1789 doc.add_paragraph("");
1790
1791 // Signature line
1792 doc.add_paragraph("")
1793 .border_bottom(BorderStyle::Single, 4, "4A5568");
1794 {
1795 let mut p = doc.add_paragraph("");
1796 p.add_run("Walter White").bold(true).size(12.0);
1797 }
1798 {
1799 let mut p = doc.add_paragraph("");
1800 p.add_run("Chief Executive Officer, Tensorbee")
1801 .italic(true)
1802 .size(10.0)
1803 .color("666666");
1804 }
1805 {
1806 let mut p = doc.add_paragraph("");
1807 p.add_run("walter@tensorbee.com | +1 (415) 555-0199")
1808 .size(10.0)
1809 .color("888888");
1810 }
1811
1812 doc
1813}
1814
1815// =============================================================================
1816// 7. EMPLOYMENT CONTRACT — Purple + charcoal scheme
1817// =============================================================================
1818fn generate_contract(_samples_dir: &Path) -> Document {
1819 let mut doc = Document::new();
1820
1821 // Colors: Purple #5B2C6F, Plum #8E44AD, Gray #2C3E50
1822 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1823 doc.set_margins(
1824 Length::inches(1.25),
1825 Length::inches(1.0),
1826 Length::inches(1.0),
1827 Length::inches(1.0),
1828 );
1829 doc.set_title("Employment Agreement");
1830 doc.set_author("Tensorbee HR Department");
1831 doc.set_subject("Employment Contract — Walter White");
1832 doc.set_keywords("employment, contract, agreement, Tensorbee");
1833
1834 doc.set_header("TENSORBEE — Employment Agreement");
1835 doc.set_footer("Employment Agreement — Walter White — Page");
1836
1837 // ── Title Block ──
1838 doc.add_paragraph("")
1839 .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1840 {
1841 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1842 p.add_run("EMPLOYMENT AGREEMENT")
1843 .bold(true)
1844 .size(24.0)
1845 .color("5B2C6F")
1846 .font("Georgia");
1847 }
1848 doc.add_paragraph("")
1849 .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1850 doc.add_paragraph("");
1851
1852 // ── Parties ──
1853 doc.add_paragraph(
1854 "This Employment Agreement (\"Agreement\") is entered into as of February 22, 2026 \
1855 (\"Effective Date\"), by and between:",
1856 );
1857
1858 doc.add_paragraph("");
1859
1860 {
1861 let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1862 p.add_run("EMPLOYER: ").bold(true);
1863 p.add_run(
1864 "Tensorbee, Inc., a Delaware corporation with its principal place of business at \
1865 123 Innovation Drive, Suite 400, San Francisco, CA 94105 (\"Company\")",
1866 );
1867 }
1868 doc.add_paragraph("");
1869 {
1870 let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1871 p.add_run("EMPLOYEE: ").bold(true);
1872 p.add_run(
1873 "Walter White, residing at 308 Negra Arroyo Lane, Albuquerque, NM 87104 (\"Employee\")",
1874 );
1875 }
1876
1877 doc.add_paragraph("");
1878 doc.add_paragraph("The Company and Employee are collectively referred to as the \"Parties.\"");
1879 doc.add_paragraph("");
1880
1881 // ── Article 1: Position and Duties ──
1882 doc.add_paragraph("Article 1 — Position and Duties")
1883 .style("Heading1");
1884 {
1885 let mut p = doc.add_paragraph("");
1886 p.add_run("1.1 ").bold(true);
1887 p.add_run("The Company hereby employs the Employee as ");
1888 p.add_run("Chief Executive Officer (CEO)")
1889 .bold(true)
1890 .italic(true);
1891 p.add_run(", reporting directly to the Board of Directors.");
1892 }
1893 {
1894 let mut p = doc.add_paragraph("");
1895 p.add_run("1.2 ").bold(true);
1896 p.add_run("The Employee shall devote full working time, attention, and best efforts to \
1897 the performance of duties as reasonably assigned by the Board, including but not limited to:");
1898 }
1899 doc.add_bullet_list_item(
1900 "Setting and executing the Company's strategic vision and business plan",
1901 0,
1902 );
1903 doc.add_bullet_list_item(
1904 "Overseeing all operations, engineering, and commercial activities",
1905 0,
1906 );
1907 doc.add_bullet_list_item(
1908 "Representing the Company to investors, customers, and the public",
1909 0,
1910 );
1911 doc.add_bullet_list_item("Recruiting, developing, and retaining key talent", 0);
1912
1913 {
1914 let mut p = doc.add_paragraph("");
1915 p.add_run("1.3 ").bold(true);
1916 p.add_run(
1917 "The Employee's primary work location shall be the Company's San Francisco \
1918 headquarters, with reasonable travel as required by business needs.",
1919 );
1920 }
1921
1922 // ── Article 2: Compensation ──
1923 doc.add_paragraph("Article 2 — Compensation and Benefits")
1924 .style("Heading1");
1925 {
1926 let mut p = doc.add_paragraph("");
1927 p.add_run("2.1 Base Salary. ").bold(true);
1928 p.add_run("The Company shall pay the Employee an annual base salary of ");
1929 p.add_run("$375,000.00").bold(true);
1930 p.add_run(" (Three Hundred Seventy-Five Thousand Dollars), payable in accordance with the \
1931 Company's standard payroll schedule, less applicable withholdings and deductions.");
1932 }
1933 {
1934 let mut p = doc.add_paragraph("");
1935 p.add_run("2.2 Annual Bonus. ").bold(true);
1936 p.add_run("The Employee shall be eligible for an annual performance bonus of up to ");
1937 p.add_run("40%").bold(true);
1938 p.add_run(
1939 " of base salary, based on achievement of mutually agreed performance objectives.",
1940 );
1941 }
1942 {
1943 let mut p = doc.add_paragraph("");
1944 p.add_run("2.3 Equity. ").bold(true);
1945 p.add_run("Subject to Board approval, the Employee shall receive a stock option grant of ");
1946 p.add_run("500,000 shares").bold(true);
1947 p.add_run(
1948 " of the Company's common stock, vesting over four (4) years with a one-year cliff, \
1949 at an exercise price equal to the fair market value on the date of grant.",
1950 );
1951 }
1952
1953 // Compensation summary table
1954 doc.add_paragraph("");
1955 {
1956 let mut tbl = doc
1957 .add_table(5, 2)
1958 .borders(BorderStyle::Single, 4, "5B2C6F")
1959 .width_pct(70.0)
1960 .alignment(Alignment::Center);
1961 tbl.cell(0, 0).unwrap().set_text("Compensation Element");
1962 tbl.cell(0, 0).unwrap().shading("5B2C6F");
1963 tbl.cell(0, 1).unwrap().set_text("Value");
1964 tbl.cell(0, 1).unwrap().shading("5B2C6F");
1965 let items = [
1966 ("Base Salary", "$375,000/year"),
1967 ("Target Bonus", "Up to 40% ($150,000)"),
1968 ("Equity Grant", "500,000 shares (4yr vest)"),
1969 ("Total Target Comp", "$525,000 + equity"),
1970 ];
1971 for (i, (elem, val)) in items.iter().enumerate() {
1972 tbl.cell(i + 1, 0).unwrap().set_text(elem);
1973 tbl.cell(i + 1, 1).unwrap().set_text(val);
1974 if i == 3 {
1975 tbl.cell(i + 1, 0).unwrap().shading("E8DAEF");
1976 tbl.cell(i + 1, 1).unwrap().shading("E8DAEF");
1977 }
1978 }
1979 }
1980
1981 // ── Article 3: Benefits ──
1982 doc.add_paragraph("").page_break_before(true);
1983 doc.add_paragraph("Article 3 — Benefits").style("Heading1");
1984 {
1985 let mut p = doc.add_paragraph("");
1986 p.add_run("3.1 ").bold(true);
1987 p.add_run(
1988 "The Employee shall be entitled to participate in all benefit programs \
1989 generally available to senior executives, including:",
1990 );
1991 }
1992 doc.add_bullet_list_item(
1993 "Medical, dental, and vision insurance (100% premium coverage for employee and dependents)",
1994 0,
1995 );
1996 doc.add_bullet_list_item("401(k) retirement plan with 6% company match", 0);
1997 doc.add_bullet_list_item("Life insurance and long-term disability coverage", 0);
1998 doc.add_bullet_list_item("Annual professional development allowance of $10,000", 0);
1999
2000 {
2001 let mut p = doc.add_paragraph("");
2002 p.add_run("3.2 Paid Time Off. ").bold(true);
2003 p.add_run(
2004 "The Employee shall receive 25 days of paid vacation per year, plus Company holidays, \
2005 accruing on a monthly basis.",
2006 );
2007 }
2008
2009 // ── Article 4: Term and Termination ──
2010 doc.add_paragraph("Article 4 — Term and Termination")
2011 .style("Heading1");
2012 {
2013 let mut p = doc.add_paragraph("");
2014 p.add_run("4.1 At-Will Employment. ").bold(true);
2015 p.add_run(
2016 "This Agreement is for at-will employment and may be terminated by either Party \
2017 at any time, with or without cause, subject to the notice provisions herein.",
2018 );
2019 }
2020 {
2021 let mut p = doc.add_paragraph("");
2022 p.add_run("4.2 Notice Period. ").bold(true);
2023 p.add_run("Either Party shall provide ");
2024 p.add_run("ninety (90) days").bold(true);
2025 p.add_run(" written notice of termination, or pay in lieu thereof.");
2026 }
2027 {
2028 let mut p = doc.add_paragraph("");
2029 p.add_run("4.3 Severance. ").bold(true);
2030 p.add_run("In the event of termination by the Company without Cause, the Employee shall \
2031 receive (a) twelve (12) months of base salary continuation, (b) pro-rata bonus \
2032 for the year of termination, and (c) twelve (12) months of COBRA premium coverage.");
2033 }
2034
2035 // ── Article 5: Confidentiality ──
2036 doc.add_paragraph("Article 5 — Confidentiality and IP")
2037 .style("Heading1");
2038 {
2039 let mut p = doc.add_paragraph("");
2040 p.add_run("5.1 ").bold(true);
2041 p.add_run(
2042 "The Employee agrees to maintain strict confidentiality of all proprietary \
2043 information, trade secrets, and business plans of the Company during and after \
2044 employment.",
2045 );
2046 }
2047 {
2048 let mut p = doc.add_paragraph("");
2049 p.add_run("5.2 ").bold(true);
2050 p.add_run(
2051 "All intellectual property, inventions, and works of authorship created by the \
2052 Employee during the term of employment and within the scope of duties shall be \
2053 the sole and exclusive property of the Company.",
2054 );
2055 }
2056
2057 // ── Article 6: Non-Compete ──
2058 doc.add_paragraph("Article 6 — Non-Competition")
2059 .style("Heading1");
2060 {
2061 let mut p = doc.add_paragraph("");
2062 p.add_run("6.1 ").bold(true);
2063 p.add_run("For a period of twelve (12) months following termination, the Employee agrees \
2064 not to engage in any business that directly competes with the Company's core \
2065 business of AI infrastructure and ML operations services, within the United States.");
2066 }
2067
2068 // ── Article 7: General Provisions ──
2069 doc.add_paragraph("Article 7 — General Provisions")
2070 .style("Heading1");
2071 {
2072 let mut p = doc.add_paragraph("");
2073 p.add_run("7.1 Governing Law. ").bold(true);
2074 p.add_run(
2075 "This Agreement shall be governed by and construed in accordance with the laws of \
2076 the State of California.",
2077 );
2078 }
2079 {
2080 let mut p = doc.add_paragraph("");
2081 p.add_run("7.2 Entire Agreement. ").bold(true);
2082 p.add_run(
2083 "This Agreement constitutes the entire agreement between the Parties and supersedes \
2084 all prior negotiations, representations, and agreements.",
2085 );
2086 }
2087 {
2088 let mut p = doc.add_paragraph("");
2089 p.add_run("7.3 Amendment. ").bold(true);
2090 p.add_run(
2091 "This Agreement may only be amended by a written instrument signed by both Parties.",
2092 );
2093 }
2094
2095 // ── Signature Block ──
2096 doc.add_paragraph("").page_break_before(true);
2097 doc.add_paragraph("IN WITNESS WHEREOF, the Parties have executed this Employment Agreement as of the Effective Date.")
2098 .space_after(Length::pt(24.0));
2099
2100 // Signature table
2101 {
2102 let mut tbl = doc.add_table(6, 2).width_pct(100.0);
2103 tbl.cell(0, 0).unwrap().set_text("FOR THE COMPANY:");
2104 tbl.cell(0, 0).unwrap().shading("5B2C6F");
2105 tbl.cell(0, 1).unwrap().set_text("EMPLOYEE:");
2106 tbl.cell(0, 1).unwrap().shading("5B2C6F");
2107
2108 tbl.cell(1, 0).unwrap().set_text("");
2109 tbl.cell(1, 1).unwrap().set_text("");
2110 tbl.row(1).unwrap().height(Length::pt(40.0));
2111
2112 tbl.cell(2, 0)
2113 .unwrap()
2114 .set_text("Signature: ___________________________");
2115 tbl.cell(2, 1)
2116 .unwrap()
2117 .set_text("Signature: ___________________________");
2118 tbl.cell(3, 0)
2119 .unwrap()
2120 .set_text("Name: Board Representative");
2121 tbl.cell(3, 1).unwrap().set_text("Name: Walter White");
2122 tbl.cell(4, 0)
2123 .unwrap()
2124 .set_text("Title: Chair, Board of Directors");
2125 tbl.cell(4, 1)
2126 .unwrap()
2127 .set_text("Title: Chief Executive Officer");
2128 tbl.cell(5, 0).unwrap().set_text("Date: _______________");
2129 tbl.cell(5, 1).unwrap().set_text("Date: _______________");
2130 }
2131
2132 doc
2133}Sourcepub fn open<P: AsRef<Path>>(path: P) -> Result<Self>
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self>
Open a document from a file path.
Examples found in repository?
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
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}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}Sourcepub fn from_bytes(bytes: &[u8]) -> Result<Self>
pub fn from_bytes(bytes: &[u8]) -> Result<Self>
Open a document from bytes.
Sourcepub fn save<P: AsRef<Path>>(&mut self, path: P) -> Result<()>
pub fn save<P: AsRef<Path>>(&mut self, path: P) -> Result<()>
Save the document to a file path.
Examples found in repository?
61fn export_all(dir: &Path, name: &str, mut doc: Document) {
62 let docx_path = dir.join(format!("{name}.docx"));
63 doc.save(&docx_path).unwrap();
64 println!(" {name}.docx");
65
66 let png = doc
67 .render_page_to_png_deterministic(0, 150.0)
68 .unwrap()
69 .expect("every generated sample should have a first page");
70 let png_path = dir.join(format!("{name}.png"));
71 std::fs::write(&png_path, &png).unwrap();
72 println!(" {name}.png ({} bytes)", png.len());
73
74 match doc.to_pdf_deterministic() {
75 Ok(pdf) => {
76 let pdf_path = dir.join(format!("{name}.pdf"));
77 std::fs::write(&pdf_path, &pdf).unwrap();
78 println!(" {name}.pdf ({} bytes)", pdf.len());
79 }
80 Err(e) => println!(" {name}.pdf — skipped: {e}"),
81 }
82
83 let html = doc.to_html();
84 let html_path = dir.join(format!("{name}.html"));
85 std::fs::write(&html_path, &html).unwrap();
86 println!(" {name}.html ({} bytes)", html.len());
87
88 let md = doc.to_markdown();
89 let md_path = dir.join(format!("{name}.md"));
90 std::fs::write(&md_path, &md).unwrap();
91 println!(" {name}.md ({} bytes)", md.len());
92}More examples
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}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}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}Sourcepub fn paragraphs(&self) -> Vec<ParagraphRef<'_>>
pub fn paragraphs(&self) -> Vec<ParagraphRef<'_>>
Get immutable references to all paragraphs.
Sourcepub fn paragraph(&self, index: usize) -> Option<ParagraphRef<'_>>
pub fn paragraph(&self, index: usize) -> Option<ParagraphRef<'_>>
Get an immutable reference to a paragraph by index (among paragraphs only).
Sourcepub fn footnotes(&self) -> Vec<(i32, String)>
pub fn footnotes(&self) -> Vec<(i32, String)>
All footnotes as (id, plain text), in file order.
Separator entries are excluded. They live in the same stream and are retained by the model so a round trip preserves them, but they are not notes and never were part of this listing.
Sourcepub fn add_footnote(&mut self, text: &str) -> i32
pub fn add_footnote(&mut self, text: &str) -> i32
Add a footnote with the given text; returns its id. Pair with
Paragraph::add_footnote_ref to reference it from the body.
Sourcepub fn add_paragraph(&mut self, text: &str) -> Paragraph<'_>
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?
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
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}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}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}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}97fn generate_feature_showcase(_samples_dir: &Path) -> Document {
98 let mut doc = Document::new();
99
100 // ── Page Setup & Metadata ──
101 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
102 doc.set_margins(
103 Length::inches(1.0),
104 Length::inches(1.0),
105 Length::inches(1.0),
106 Length::inches(1.0),
107 );
108 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
109 doc.set_gutter(Length::twips(0));
110 doc.set_title("rdocx Feature Showcase");
111 doc.set_author("rdocx Sample Generator");
112 doc.set_subject("Comprehensive feature demonstration");
113 doc.set_keywords("rdocx, docx, rust, sample, showcase");
114
115 // Headers & Footers with different first page
116 doc.set_different_first_page(true);
117 doc.set_first_page_header("rdocx");
118 doc.set_first_page_footer("Feature Showcase — Cover Page");
119 doc.set_header("rdocx Feature Showcase");
120 doc.set_footer("Generated by rdocx");
121
122 // ── COVER PAGE ──
123 let bg = create_sample_png(612, 792, [20, 45, 90]);
124 doc.add_background_image(&bg, "cover_bg.png");
125
126 for _ in 0..3 {
127 doc.add_paragraph("");
128 }
129 {
130 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
131 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
132 }
133 {
134 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
135 p.add_run("Complete Feature Showcase")
136 .size(28.0)
137 .color("FFFFFF")
138 .italic(true);
139 }
140 doc.add_paragraph("");
141 {
142 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
143 p.add_run("Every feature of the rdocx Rust library")
144 .size(13.0)
145 .color("B0C4DE");
146 }
147 {
148 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
149 p.add_run("demonstrated in a single document.")
150 .size(13.0)
151 .color("B0C4DE");
152 }
153
154 // ── TABLE OF CONTENTS ──
155 doc.add_paragraph("").page_break_before(true);
156 doc.insert_toc(doc.content_count(), 3);
157
158 // ── SECTION 1: TEXT FORMATTING ──
159 doc.add_paragraph("").page_break_before(true);
160 doc.add_paragraph("1. Text Formatting").style("Heading1");
161
162 // Paragraph Alignment
163 doc.add_paragraph("1.1 Paragraph Alignment")
164 .style("Heading2");
165 doc.add_paragraph("Left-aligned paragraph (default).")
166 .alignment(Alignment::Left);
167 doc.add_paragraph("Center-aligned paragraph.")
168 .alignment(Alignment::Center);
169 doc.add_paragraph("Right-aligned paragraph.")
170 .alignment(Alignment::Right);
171 doc.add_paragraph(
172 "Justified paragraph — this text is long enough to demonstrate how justified alignment \
173 distributes extra space across word gaps so lines fill the full width of the text area.",
174 )
175 .alignment(Alignment::Justify);
176
177 doc.add_paragraph("");
178
179 // Run Formatting
180 doc.add_paragraph("1.2 Run Formatting").style("Heading2");
181 {
182 let mut p = doc.add_paragraph("");
183 p.add_run("Bold").bold(true);
184 p.add_run(" | ");
185 p.add_run("Italic").italic(true);
186 p.add_run(" | ");
187 p.add_run("Bold Italic").bold(true).italic(true);
188 p.add_run(" | ");
189 p.add_run("Underline").underline(true);
190 p.add_run(" | ");
191 p.add_run("Strikethrough").strike(true);
192 p.add_run(" | ");
193 p.add_run("Double Strike").double_strike(true);
194 }
195 {
196 let mut p = doc.add_paragraph("");
197 p.add_run("Red").color("FF0000");
198 p.add_run(" | ");
199 p.add_run("Blue").color("0000FF");
200 p.add_run(" | ");
201 p.add_run("Green").color("00AA00");
202 p.add_run(" | ");
203 p.add_run("Highlighted").highlight("FFFF00");
204 }
205 {
206 let mut p = doc.add_paragraph("");
207 p.add_run("8pt").size(8.0);
208 p.add_run(" | ");
209 p.add_run("11pt").size(11.0);
210 p.add_run(" | ");
211 p.add_run("16pt").size(16.0);
212 p.add_run(" | ");
213 p.add_run("24pt").size(24.0);
214 }
215 {
216 let mut p = doc.add_paragraph("");
217 p.add_run("Arial").font("Arial");
218 p.add_run(" | ");
219 p.add_run("Times New Roman").font("Times New Roman");
220 p.add_run(" | ");
221 p.add_run("Courier New").font("Courier New");
222 }
223 {
224 let mut p = doc.add_paragraph("");
225 p.add_run("H");
226 p.add_run("2").subscript();
227 p.add_run("O (subscript) | E=mc");
228 p.add_run("2").superscript();
229 p.add_run(" (superscript)");
230 }
231 {
232 let mut p = doc.add_paragraph("");
233 p.add_run("ALL CAPS").all_caps(true);
234 p.add_run(" | ");
235 p.add_run("Small Caps").small_caps(true);
236 p.add_run(" | ");
237 p.add_run("Expanded").character_spacing(Length::twips(40));
238 p.add_run(" | ");
239 p.add_run("Hidden text (not visible)").hidden(true);
240 }
241
242 doc.add_paragraph("");
243
244 // Underline Styles
245 doc.add_paragraph("1.3 Underline Styles").style("Heading2");
246 {
247 let mut p = doc.add_paragraph("");
248 p.add_run("Single ")
249 .underline_style(rdocx::UnderlineStyle::Single);
250 p.add_run("Double ")
251 .underline_style(rdocx::UnderlineStyle::Double);
252 p.add_run("Thick ")
253 .underline_style(rdocx::UnderlineStyle::Thick);
254 p.add_run("Dotted ")
255 .underline_style(rdocx::UnderlineStyle::Dotted);
256 p.add_run("Dash ")
257 .underline_style(rdocx::UnderlineStyle::Dash);
258 p.add_run("Wave ")
259 .underline_style(rdocx::UnderlineStyle::Wave);
260 }
261
262 // ── SECTION 2: PARAGRAPH FORMATTING ──
263 doc.add_paragraph("").page_break_before(true);
264 doc.add_paragraph("2. Paragraph Formatting")
265 .style("Heading1");
266
267 doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
268 doc.add_paragraph("Paragraph with light green shading")
269 .shading("E2EFDA");
270 doc.add_paragraph("Paragraph with bottom border")
271 .border_bottom(BorderStyle::Single, 6, "2E75B6");
272 doc.add_paragraph("Paragraph with all borders (red)")
273 .border_all(BorderStyle::Single, 4, "FF0000");
274
275 doc.add_paragraph("2.2 Indentation").style("Heading2");
276 doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
277 .indent_left(Length::inches(1.0))
278 .hanging_indent(Length::inches(0.5));
279 doc.add_paragraph("0.5-inch first-line indent on this paragraph")
280 .first_line_indent(Length::inches(0.5));
281 doc.add_paragraph("Both left and right indent (0.75 inches each)")
282 .indent_left(Length::inches(0.75))
283 .indent_right(Length::inches(0.75));
284
285 doc.add_paragraph("2.3 Spacing & Line Height")
286 .style("Heading2");
287 doc.add_paragraph("Extra space before (24pt) and after (12pt)")
288 .space_before(Length::pt(24.0))
289 .space_after(Length::pt(12.0));
290 doc.add_paragraph(
291 "Double line spacing paragraph. This text should have extra vertical space between \
292 lines to demonstrate line_spacing_multiple(2.0).",
293 )
294 .line_spacing_multiple(2.0);
295 doc.add_paragraph("Exact 20pt line spacing (fixed height).")
296 .line_spacing(20.0);
297
298 doc.add_paragraph("2.4 Pagination Controls")
299 .style("Heading2");
300 doc.add_paragraph("keep_with_next — this paragraph stays with the next")
301 .keep_with_next(true);
302 doc.add_paragraph("(This paragraph was kept with the one above.)");
303 doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
304 .keep_together(true);
305 doc.add_paragraph("widow_control — prevents widow/orphan lines")
306 .widow_control(true);
307
308 // ── SECTION 3: LISTS ──
309 doc.add_paragraph("").page_break_before(true);
310 doc.add_paragraph("3. Lists").style("Heading1");
311
312 doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
313 doc.add_bullet_list_item("First item", 0);
314 doc.add_bullet_list_item("Second item", 0);
315 doc.add_bullet_list_item("Nested level 1", 1);
316 doc.add_bullet_list_item("Nested level 2", 2);
317 doc.add_bullet_list_item("Back to level 1", 1);
318 doc.add_bullet_list_item("Third item", 0);
319
320 doc.add_paragraph("");
321
322 doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
323 doc.add_numbered_list_item("First numbered item", 0);
324 doc.add_numbered_list_item("Second numbered item", 0);
325 doc.add_numbered_list_item("Sub-item A", 1);
326 doc.add_numbered_list_item("Sub-item B", 1);
327 doc.add_numbered_list_item("Third numbered item", 0);
328
329 // ── SECTION 4: TAB STOPS ──
330 doc.add_paragraph("");
331 doc.add_paragraph("4. Tab Stops").style("Heading1");
332
333 doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
334 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
335 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
336 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
337 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
338 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
339
340 doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
341 doc.add_paragraph("Item\t\tPrice")
342 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
343 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
344 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
345 doc.add_paragraph("Widget\t\t$19.99")
346 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
347 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
348 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
349 doc.add_paragraph("Gadget\t\t$249.50")
350 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
351 .add_tab_stop_with_leader(
352 TabAlignment::Right,
353 Length::inches(4.0),
354 TabLeader::Underscore,
355 )
356 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
357
358 // ── SECTION 5: TABLES ──
359 doc.add_paragraph("").page_break_before(true);
360 doc.add_paragraph("5. Tables").style("Heading1");
361
362 doc.add_paragraph("5.1 Basic Table").style("Heading2");
363 {
364 let mut tbl = doc
365 .add_table(4, 3)
366 .borders(BorderStyle::Single, 4, "000000");
367 for col in 0..3 {
368 tbl.cell(0, col).unwrap().shading("2E75B6");
369 }
370 tbl.cell(0, 0).unwrap().set_text("Name");
371 tbl.cell(0, 1).unwrap().set_text("Role");
372 tbl.cell(0, 2).unwrap().set_text("Location");
373 tbl.cell(1, 0).unwrap().set_text("Walter White");
374 tbl.cell(1, 1).unwrap().set_text("CEO");
375 tbl.cell(1, 2).unwrap().set_text("Albuquerque");
376 tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
377 tbl.cell(2, 1).unwrap().set_text("CTO");
378 tbl.cell(2, 2).unwrap().set_text("Remote");
379 tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
380 tbl.cell(3, 1).unwrap().set_text("Security");
381 tbl.cell(3, 2).unwrap().set_text("Washington");
382 }
383
384 doc.add_paragraph("");
385
386 doc.add_paragraph("5.2 Column Span & Cell Shading")
387 .style("Heading2");
388 {
389 let mut tbl = doc
390 .add_table(3, 4)
391 .borders(BorderStyle::Single, 4, "000000")
392 .width_pct(100.0);
393 tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
394 tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
395 tbl.cell(1, 0).unwrap().set_text("Region");
396 tbl.cell(1, 0).unwrap().shading("D6E4F0");
397 tbl.cell(1, 1).unwrap().set_text("Q1");
398 tbl.cell(1, 1).unwrap().shading("D6E4F0");
399 tbl.cell(1, 2).unwrap().set_text("Q2");
400 tbl.cell(1, 2).unwrap().shading("D6E4F0");
401 tbl.cell(1, 3).unwrap().set_text("Total");
402 tbl.cell(1, 3).unwrap().shading("D6E4F0");
403 tbl.cell(2, 0).unwrap().set_text("Americas");
404 tbl.cell(2, 1).unwrap().set_text("$2.4M");
405 tbl.cell(2, 2).unwrap().set_text("$2.7M");
406 tbl.cell(2, 3).unwrap().set_text("$5.1M");
407 }
408
409 doc.add_paragraph("");
410
411 doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
412 {
413 let mut tbl = doc
414 .add_table(4, 3)
415 .borders(BorderStyle::Single, 4, "000000");
416 tbl.cell(0, 0).unwrap().set_text("Category");
417 tbl.cell(0, 0).unwrap().shading("E2EFDA");
418 tbl.cell(0, 1).unwrap().set_text("Item");
419 tbl.cell(0, 1).unwrap().shading("E2EFDA");
420 tbl.cell(0, 2).unwrap().set_text("Price");
421 tbl.cell(0, 2).unwrap().shading("E2EFDA");
422 tbl.cell(1, 0).unwrap().set_text("Hardware");
423 tbl.cell(1, 0).unwrap().v_merge_restart();
424 tbl.cell(1, 1).unwrap().set_text("Laptop");
425 tbl.cell(1, 2).unwrap().set_text("$1,200");
426 tbl.cell(2, 0).unwrap().v_merge_continue();
427 tbl.cell(2, 1).unwrap().set_text("Monitor");
428 tbl.cell(2, 2).unwrap().set_text("$450");
429 tbl.cell(3, 0).unwrap().set_text("Software");
430 tbl.cell(3, 1).unwrap().set_text("IDE License");
431 tbl.cell(3, 2).unwrap().set_text("$200/yr");
432 }
433
434 doc.add_paragraph("");
435
436 doc.add_paragraph("5.4 Nested Table").style("Heading2");
437 {
438 let mut tbl = doc
439 .add_table(2, 2)
440 .borders(BorderStyle::Single, 6, "2E75B6");
441 tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
442 tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
443 tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
444 {
445 let mut cell = tbl.cell(1, 1).unwrap();
446 cell.set_text("Nested table below:");
447 let mut nested = cell
448 .add_table(2, 2)
449 .borders(BorderStyle::Single, 2, "FF6600");
450 nested.cell(0, 0).unwrap().set_text("A");
451 nested.cell(0, 1).unwrap().set_text("B");
452 nested.cell(1, 0).unwrap().set_text("C");
453 nested.cell(1, 1).unwrap().set_text("D");
454 }
455 }
456
457 doc.add_paragraph("");
458
459 doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
460 .style("Heading2");
461 {
462 let mut tbl = doc
463 .add_table(2, 3)
464 .borders(BorderStyle::Single, 4, "666666");
465 tbl.row(0).unwrap().height(Length::pt(50.0)).header();
466 tbl.cell(0, 0).unwrap().set_text("Top");
467 tbl.cell(0, 0)
468 .unwrap()
469 .vertical_alignment(VerticalAlignment::Top)
470 .shading("FFF2CC");
471 tbl.cell(0, 1).unwrap().set_text("Center");
472 tbl.cell(0, 1)
473 .unwrap()
474 .vertical_alignment(VerticalAlignment::Center)
475 .shading("D9E2F3");
476 tbl.cell(0, 2).unwrap().set_text("Bottom");
477 tbl.cell(0, 2)
478 .unwrap()
479 .vertical_alignment(VerticalAlignment::Bottom)
480 .shading("E2EFDA");
481 tbl.row(1).unwrap().cant_split();
482 tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
483 tbl.cell(1, 0).unwrap().no_wrap();
484 tbl.cell(1, 1).unwrap().set_text("Fixed width");
485 tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
486 tbl.cell(1, 2).unwrap().set_text("Normal");
487 }
488
489 // ── SECTION 6: IMAGES ──
490 doc.add_paragraph("").page_break_before(true);
491 doc.add_paragraph("6. Images").style("Heading1");
492
493 doc.add_paragraph("6.1 Inline Image").style("Heading2");
494 doc.add_paragraph("A blue gradient image below:");
495 let img = create_sample_png(200, 50, [0, 80, 200]);
496 doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
497
498 doc.add_paragraph("");
499 doc.add_paragraph("6.2 Header Image").style("Heading2");
500 let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
501 doc.set_header_image(
502 &hdr_img,
503 "header_logo.png",
504 Length::inches(2.0),
505 Length::inches(0.2),
506 );
507 doc.add_paragraph("The header now contains an inline image (check the top of this page).");
508
509 doc.add_paragraph("");
510 doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
511
512 // ── SECTION 7: CONTENT MANIPULATION ──
513 doc.add_paragraph("").page_break_before(true);
514 doc.add_paragraph("7. Content Manipulation")
515 .style("Heading1");
516
517 doc.add_paragraph("7.1 Placeholder Replacement")
518 .style("Heading2");
519 doc.add_paragraph("Customer: {{customer}}");
520 doc.add_paragraph("Date: {{date}}");
521 doc.add_paragraph("Reference: {{ref_number}}");
522 {
523 let mut tbl = doc
524 .add_table(2, 2)
525 .borders(BorderStyle::Single, 4, "000000");
526 tbl.cell(0, 0).unwrap().set_text("Project");
527 tbl.cell(0, 0).unwrap().shading("D6E4F0");
528 tbl.cell(0, 1).unwrap().set_text("{{project}}");
529 tbl.cell(1, 0).unwrap().set_text("Status");
530 tbl.cell(1, 0).unwrap().shading("D6E4F0");
531 tbl.cell(1, 1).unwrap().set_text("{{status}}");
532 }
533
534 let mut replacements = HashMap::new();
535 replacements.insert("{{customer}}", "Tensorbee Inc.");
536 replacements.insert("{{date}}", "February 22, 2026");
537 replacements.insert("{{ref_number}}", "TB-2026-001");
538 replacements.insert("{{project}}", "Infrastructure Upgrade");
539 replacements.insert("{{status}}", "In Progress");
540 let n = doc.replace_all(&replacements);
541 doc.add_paragraph(&format!("({n} placeholders replaced above)"));
542
543 doc.add_paragraph("");
544
545 doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
546 doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
547 let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
548 doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
549
550 doc.add_paragraph("");
551
552 doc.add_paragraph("7.3 Content Insertion").style("Heading2");
553 doc.add_paragraph("Section A: First content.");
554 doc.add_paragraph("Section C: Third content.");
555 if let Some(idx) = doc.find_content_index("Section C") {
556 doc.insert_paragraph(
557 idx,
558 "Section B: Inserted between A and C via find_content_index().",
559 );
560 }
561
562 // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
563 doc.add_paragraph("").section_break(SectionBreak::NextPage);
564 doc.add_paragraph("8. Section Breaks & Orientation")
565 .style("Heading1");
566 doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
567
568 {
569 let mut tbl = doc
570 .add_table(3, 7)
571 .borders(BorderStyle::Single, 4, "2E75B6");
572 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
573 for (c, h) in headers.iter().enumerate() {
574 tbl.cell(0, c).unwrap().set_text(h);
575 tbl.cell(0, c).unwrap().shading("2E75B6");
576 }
577 let data = [
578 [
579 "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
580 ],
581 [
582 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
583 ],
584 ];
585 for (r, row) in data.iter().enumerate() {
586 for (c, v) in row.iter().enumerate() {
587 tbl.cell(r + 1, c).unwrap().set_text(v);
588 }
589 }
590 }
591
592 doc.add_paragraph("")
593 .section_break(SectionBreak::NextPage)
594 .section_landscape();
595
596 // ── SECTION 9: CUSTOM STYLES ──
597 doc.add_paragraph("9. Custom Styles").style("Heading1");
598 doc.add_style(
599 StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
600 .based_on("Normal")
601 .paragraph_properties(rdocx_oxml::properties::CT_PPr {
602 shading: Some(rdocx_oxml::properties::CT_Shd {
603 val: "clear".to_string(),
604 color: None,
605 fill: Some("FFF2CC".to_string()),
606 }),
607 ..Default::default()
608 })
609 .run_properties(rdocx_oxml::properties::CT_RPr {
610 bold: Some(true),
611 color: Some("C45911".to_string()),
612 ..Default::default()
613 }),
614 );
615 doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
616 .style("CustomHighlight");
617
618 doc.add_paragraph("");
619
620 // ── SECTION 10: DOCUMENT INTELLIGENCE ──
621 doc.add_paragraph("10. Document Intelligence API")
622 .style("Heading1");
623
624 let wc = doc.word_count();
625 let hc = doc.headings().len();
626 let ic = doc.images().len();
627 let lc = doc.links().len();
628 doc.add_paragraph(&format!("Word count: {wc}"));
629 doc.add_paragraph(&format!("Heading count: {hc}"));
630 doc.add_paragraph(&format!("Image count: {ic}"));
631 doc.add_paragraph(&format!("Link count: {lc}"));
632
633 let outline = doc.document_outline();
634 doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
635
636 let issues = doc.audit_accessibility();
637 doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
638 for issue in &issues {
639 doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
640 }
641
642 // ── SECTION 11: DOCUMENT MERGING ──
643 doc.add_paragraph("");
644 doc.add_paragraph("11. Document Merging").style("Heading1");
645 {
646 let mut other = Document::new();
647 other.add_paragraph("This paragraph was merged from another document using append().");
648 doc.append(&other);
649 }
650
651 doc.add_paragraph("");
652
653 // ── SUMMARY ──
654 doc.add_paragraph("Summary of Demonstrated Features")
655 .style("Heading1");
656 let features = [
657 "Page setup: size, margins, header/footer distance, gutter",
658 "Document metadata: title, author, subject, keywords",
659 "Headers and footers: text, images, different first page",
660 "Background images (full-page behind text)",
661 "Table of Contents generation with bookmarks",
662 "Text formatting: bold, italic, underline styles, strike, color, size, font",
663 "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
664 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
665 "Pagination controls: keep-with-next, keep-together, widow control",
666 "Bullet and numbered lists with nesting",
667 "Tab stops with dot/underscore/hyphen leaders",
668 "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
669 "Row properties: header rows, exact height, cant-split",
670 "Cell properties: width, no-wrap, vertical alignment",
671 "Inline images and header images",
672 "Placeholder replacement (single and batch)",
673 "Regex-based find and replace",
674 "Content insertion at specific positions",
675 "Section breaks with mixed portrait/landscape",
676 "Custom paragraph and character styles",
677 "Document intelligence: word count, headings, outline, images, links",
678 "Accessibility audit",
679 "Document merging (append)",
680 "Export: DOCX, PDF, HTML, Markdown",
681 ];
682 for f in &features {
683 doc.add_bullet_list_item(f, 0);
684 }
685 doc.add_paragraph("");
686 doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
687 .alignment(Alignment::Center)
688 .shading("E2EFDA")
689 .border_all(BorderStyle::Single, 2, "00AA00");
690
691 doc
692}
693
694// =============================================================================
695// 2. PROPOSAL DOCUMENT — Deep navy + gold scheme
696// =============================================================================
697fn generate_proposal(_samples_dir: &Path) -> Document {
698 let mut doc = Document::new();
699
700 // Colors: Navy #1B2A4A, Gold #C5922E, Light #F4F1EB
701 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
702 doc.set_margins(
703 Length::inches(1.0),
704 Length::inches(1.0),
705 Length::inches(1.0),
706 Length::inches(1.0),
707 );
708 doc.set_title("AI Platform Modernization Proposal");
709 doc.set_author("Walter White");
710 doc.set_subject("Technology Proposal");
711 doc.set_keywords("proposal, AI, modernization, Tensorbee");
712
713 // Banner header
714 let logo_img = create_logo_png(220, 48);
715 let banner = build_header_banner_xml(
716 "rId1",
717 &BannerOpts {
718 bg_color: "1B2A4A",
719 banner_width: 7772400,
720 banner_height: 914400,
721 logo_width: 2011680,
722 logo_height: 438912,
723 logo_x_offset: 295125,
724 logo_y_offset: 237744,
725 },
726 );
727 doc.set_raw_header_with_images(
728 banner,
729 &[("rId1", &logo_img, "logo.png")],
730 rdocx_oxml::header_footer::HdrFtrType::Default,
731 );
732 doc.set_different_first_page(true);
733 let cover_banner = build_header_banner_xml(
734 "rId1",
735 &BannerOpts {
736 bg_color: "C5922E",
737 banner_width: 7772400,
738 banner_height: 914400,
739 logo_width: 2011680,
740 logo_height: 438912,
741 logo_x_offset: 295125,
742 logo_y_offset: 237744,
743 },
744 );
745 doc.set_raw_header_with_images(
746 cover_banner,
747 &[("rId1", &logo_img, "logo.png")],
748 rdocx_oxml::header_footer::HdrFtrType::First,
749 );
750 doc.set_margins(
751 Length::twips(2292),
752 Length::twips(1440),
753 Length::twips(1440),
754 Length::twips(1440),
755 );
756 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
757 doc.set_footer("Tensorbee — Confidential");
758
759 // Custom styles
760 doc.add_style(
761 StyleBuilder::paragraph("ProposalTitle", "Proposal Title")
762 .based_on("Normal")
763 .run_properties(rdocx_oxml::properties::CT_RPr {
764 bold: Some(true),
765 font_ascii: Some("Georgia".to_string()),
766 font_hansi: Some("Georgia".to_string()),
767 sz: Some(rdocx_oxml::HalfPoint(56)), // half-points → 28pt
768 color: Some("1B2A4A".to_string()),
769 ..Default::default()
770 }),
771 );
772
773 // ── Cover Page ──
774 for _ in 0..4 {
775 doc.add_paragraph("");
776 }
777 doc.add_paragraph("AI Platform Modernization")
778 .style("ProposalTitle")
779 .alignment(Alignment::Center);
780 doc.add_paragraph("")
781 .alignment(Alignment::Center)
782 .border_bottom(BorderStyle::Single, 8, "C5922E");
783 {
784 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
785 p.add_run("Prepared for Global Financial Corp.")
786 .size(14.0)
787 .color("666666")
788 .italic(true);
789 }
790 {
791 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
792 p.add_run("Prepared by: Walter White, CEO — Tensorbee")
793 .size(12.0)
794 .color("888888");
795 }
796 {
797 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
798 p.add_run("February 22, 2026").size(12.0).color("888888");
799 }
800
801 // ── TOC ──
802 doc.add_paragraph("").page_break_before(true);
803 doc.add_paragraph("Table of Contents").style("Heading1");
804 // We'll add a manual-style TOC since the insert_toc goes at a specific position
805 doc.insert_toc(doc.content_count(), 2);
806
807 // ── Executive Summary ──
808 doc.add_paragraph("").page_break_before(true);
809 doc.add_paragraph("Executive Summary").style("Heading1");
810 doc.add_paragraph(
811 "Tensorbee proposes a comprehensive modernization of Global Financial Corp's AI platform, \
812 transitioning from legacy batch-processing systems to a real-time inference architecture. \
813 This transformation will reduce model deployment time from weeks to hours, improve \
814 prediction accuracy by an estimated 23%, and deliver $4.2M in annual operational savings.",
815 )
816 .first_line_indent(Length::inches(0.3));
817 doc.add_paragraph(
818 "The project spans three phases over 18 months, with a total investment of $2.8M. \
819 Tensorbee brings deep expertise in ML infrastructure, having successfully completed \
820 similar transformations for three Fortune 500 financial institutions.",
821 )
822 .first_line_indent(Length::inches(0.3));
823
824 // Key metrics table
825 doc.add_paragraph("");
826 {
827 let mut tbl = doc
828 .add_table(5, 2)
829 .borders(BorderStyle::Single, 4, "1B2A4A")
830 .width_pct(80.0);
831 tbl.cell(0, 0).unwrap().set_text("Key Metric");
832 tbl.cell(0, 0).unwrap().shading("1B2A4A");
833 tbl.cell(0, 1).unwrap().set_text("Value");
834 tbl.cell(0, 1).unwrap().shading("1B2A4A");
835 let rows = [
836 ("Total Investment", "$2.8M"),
837 ("Annual Savings", "$4.2M"),
838 ("ROI (Year 1)", "150%"),
839 ("Timeline", "18 months"),
840 ];
841 for (i, (k, v)) in rows.iter().enumerate() {
842 tbl.cell(i + 1, 0).unwrap().set_text(k);
843 if i % 2 == 0 {
844 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
845 }
846 tbl.cell(i + 1, 1).unwrap().set_text(v);
847 if i % 2 == 0 {
848 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
849 }
850 }
851 }
852
853 // ── Problem Statement ──
854 doc.add_paragraph("").page_break_before(true);
855 doc.add_paragraph("Problem Statement").style("Heading1");
856 doc.add_paragraph(
857 "Global Financial Corp's current AI infrastructure faces three critical challenges:",
858 );
859 doc.add_numbered_list_item("Deployment Latency — New models take 3-4 weeks to deploy to production, compared to the industry benchmark of 2-3 days.", 0);
860 doc.add_numbered_list_item("Scalability Constraints — The batch processing architecture cannot handle real-time inference demands during peak trading hours.", 0);
861 doc.add_numbered_list_item("Technical Debt — Legacy Python 2.7 codebases and manual deployment scripts create reliability risks and slow iteration speed.", 0);
862
863 // ── Proposed Solution ──
864 doc.add_paragraph("").page_break_before(true);
865 doc.add_paragraph("Proposed Solution").style("Heading1");
866
867 doc.add_paragraph("Phase 1: Foundation (Months 1-6)")
868 .style("Heading2");
869 doc.add_bullet_list_item("Deploy Kubernetes-based ML serving infrastructure", 0);
870 doc.add_bullet_list_item("Implement CI/CD pipeline for model deployment", 0);
871 doc.add_bullet_list_item("Migrate top 5 production models to new platform", 0);
872
873 doc.add_paragraph("Phase 2: Expansion (Months 7-12)")
874 .style("Heading2");
875 doc.add_bullet_list_item(
876 "Real-time feature engineering pipeline (Apache Kafka + Flink)",
877 0,
878 );
879 doc.add_bullet_list_item("A/B testing framework for model variants", 0);
880 doc.add_bullet_list_item("Automated model monitoring and drift detection", 0);
881
882 doc.add_paragraph("Phase 3: Optimization (Months 13-18)")
883 .style("Heading2");
884 doc.add_bullet_list_item("GPU inference optimization (TensorRT/ONNX Runtime)", 0);
885 doc.add_bullet_list_item("Multi-region deployment for latency reduction", 0);
886 doc.add_bullet_list_item(
887 "Self-service model deployment portal for data science teams",
888 0,
889 );
890
891 // ── Budget ──
892 doc.add_paragraph("Budget Breakdown").style("Heading1");
893 {
894 let mut tbl = doc
895 .add_table(6, 3)
896 .borders(BorderStyle::Single, 4, "1B2A4A")
897 .width_pct(100.0);
898 tbl.cell(0, 0).unwrap().set_text("Category");
899 tbl.cell(0, 0).unwrap().shading("1B2A4A");
900 tbl.cell(0, 1).unwrap().set_text("Cost");
901 tbl.cell(0, 1).unwrap().shading("1B2A4A");
902 tbl.cell(0, 2).unwrap().set_text("Notes");
903 tbl.cell(0, 2).unwrap().shading("1B2A4A");
904 let items = [
905 (
906 "Infrastructure",
907 "$450,000",
908 "Cloud compute + GPU instances",
909 ),
910 ("Engineering Services", "$1,600,000", "12 FTE x 18 months"),
911 (
912 "Software Licenses",
913 "$350,000",
914 "Kafka, monitoring, CI/CD tools",
915 ),
916 (
917 "Training & Enablement",
918 "$200,000",
919 "Team training, documentation",
920 ),
921 ("Contingency (10%)", "$200,000", "Risk buffer"),
922 ];
923 for (i, (cat, cost, note)) in items.iter().enumerate() {
924 tbl.cell(i + 1, 0).unwrap().set_text(cat);
925 tbl.cell(i + 1, 1).unwrap().set_text(cost);
926 tbl.cell(i + 1, 2).unwrap().set_text(note);
927 if i % 2 == 0 {
928 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
929 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
930 tbl.cell(i + 1, 2).unwrap().shading("F4F1EB");
931 }
932 }
933 }
934
935 // ── Closing ──
936 doc.add_paragraph("");
937 doc.add_paragraph("Next Steps").style("Heading1");
938 doc.add_numbered_list_item(
939 "Schedule technical deep-dive with GFC engineering team (Week 1)",
940 0,
941 );
942 doc.add_numbered_list_item("Finalize scope and sign SOW (Week 2-3)", 0);
943 doc.add_numbered_list_item("Kick off Phase 1 with joint planning session (Week 4)", 0);
944
945 doc.add_paragraph("");
946 {
947 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
948 p.add_run("Walter White")
949 .bold(true)
950 .size(14.0)
951 .color("1B2A4A");
952 }
953 {
954 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
955 p.add_run("CEO, Tensorbee | walter@tensorbee.com")
956 .size(10.0)
957 .color("888888");
958 }
959
960 doc
961}
962
963// =============================================================================
964// 3. QUOTE / BILL OF MATERIALS — Teal + orange scheme
965// =============================================================================
966fn generate_quote(_samples_dir: &Path) -> Document {
967 let mut doc = Document::new();
968
969 // Colors: Teal #008B8B, Dark #1A3C3C, Orange #E07020, Light #F0F8F8
970 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
971 doc.set_margins(
972 Length::inches(0.75),
973 Length::inches(0.75),
974 Length::inches(0.75),
975 Length::inches(0.75),
976 );
977 doc.set_title("Quotation QT-2026-0147");
978 doc.set_author("Walter White");
979 doc.set_keywords("quote, BOM, Tensorbee");
980
981 doc.set_header("Tensorbee — Quotation");
982 doc.set_footer("QT-2026-0147 | Page");
983
984 // ── Header Block ──
985 {
986 let mut p = doc.add_paragraph("").alignment(Alignment::Left);
987 p.add_run("TENSORBEE")
988 .bold(true)
989 .size(28.0)
990 .color("008B8B")
991 .font("Helvetica");
992 }
993 doc.add_paragraph("123 Innovation Drive, Suite 400")
994 .alignment(Alignment::Left);
995 doc.add_paragraph("San Francisco, CA 94105 | +1 (415) 555-0199")
996 .alignment(Alignment::Left);
997 doc.add_paragraph("")
998 .border_bottom(BorderStyle::Single, 8, "008B8B");
999
1000 // Quote meta
1001 doc.add_paragraph("");
1002 {
1003 let mut tbl = doc.add_table(4, 4).width_pct(100.0);
1004 tbl.cell(0, 0).unwrap().set_text("QUOTATION");
1005 tbl.cell(0, 0).unwrap().shading("008B8B").grid_span(2);
1006 tbl.cell(0, 2).unwrap().set_text("DATE");
1007 tbl.cell(0, 2).unwrap().shading("008B8B");
1008 tbl.cell(0, 3).unwrap().set_text("VALID UNTIL");
1009 tbl.cell(0, 3).unwrap().shading("008B8B");
1010 tbl.cell(1, 0).unwrap().set_text("Quote #:");
1011 tbl.cell(1, 1).unwrap().set_text("QT-2026-0147");
1012 tbl.cell(1, 2).unwrap().set_text("Feb 22, 2026");
1013 tbl.cell(1, 3).unwrap().set_text("Mar 22, 2026");
1014 tbl.cell(2, 0).unwrap().set_text("Prepared by:");
1015 tbl.cell(2, 1).unwrap().set_text("Walter White");
1016 tbl.cell(2, 2).unwrap().set_text("Payment:");
1017 tbl.cell(2, 3).unwrap().set_text("Net 30");
1018 tbl.cell(3, 0).unwrap().set_text("Customer:");
1019 tbl.cell(3, 1).unwrap().set_text("Meridian Dynamics LLC");
1020 tbl.cell(3, 2).unwrap().set_text("Currency:");
1021 tbl.cell(3, 3).unwrap().set_text("USD");
1022 }
1023
1024 doc.add_paragraph("");
1025
1026 // ── Bill of Materials ──
1027 {
1028 let mut p = doc.add_paragraph("");
1029 p.add_run("Bill of Materials")
1030 .bold(true)
1031 .size(16.0)
1032 .color("1A3C3C");
1033 }
1034 doc.add_paragraph("");
1035
1036 {
1037 let mut tbl = doc
1038 .add_table(10, 6)
1039 .borders(BorderStyle::Single, 2, "008B8B")
1040 .width_pct(100.0)
1041 .layout_fixed();
1042 // Headers
1043 let hdrs = [
1044 "#",
1045 "Part Number",
1046 "Description",
1047 "Qty",
1048 "Unit Price",
1049 "Total",
1050 ];
1051 for (c, h) in hdrs.iter().enumerate() {
1052 tbl.cell(0, c).unwrap().set_text(h);
1053 tbl.cell(0, c).unwrap().shading("008B8B");
1054 }
1055
1056 let items: Vec<(&str, &str, &str, &str, &str)> = vec![
1057 (
1058 "TB-GPU-A100",
1059 "NVIDIA A100 80GB GPU",
1060 "4",
1061 "$12,500.00",
1062 "$50,000.00",
1063 ),
1064 (
1065 "TB-SRV-R750",
1066 "Dell R750xa Server Chassis",
1067 "2",
1068 "$8,200.00",
1069 "$16,400.00",
1070 ),
1071 (
1072 "TB-RAM-256G",
1073 "256GB DDR5 ECC Memory Module",
1074 "8",
1075 "$890.00",
1076 "$7,120.00",
1077 ),
1078 (
1079 "TB-SSD-3840",
1080 "3.84TB NVMe U.2 SSD",
1081 "8",
1082 "$1,150.00",
1083 "$9,200.00",
1084 ),
1085 (
1086 "TB-NET-CX7",
1087 "ConnectX-7 200GbE NIC",
1088 "4",
1089 "$1,800.00",
1090 "$7,200.00",
1091 ),
1092 (
1093 "TB-SW-48P",
1094 "48-Port 100GbE Switch",
1095 "1",
1096 "$22,000.00",
1097 "$22,000.00",
1098 ),
1099 (
1100 "TB-CAB-RACK",
1101 "42U Server Rack + PDU",
1102 "1",
1103 "$4,500.00",
1104 "$4,500.00",
1105 ),
1106 (
1107 "TB-SVC-INST",
1108 "Installation & Configuration",
1109 "1",
1110 "$8,500.00",
1111 "$8,500.00",
1112 ),
1113 (
1114 "TB-SVC-SUPP",
1115 "3-Year Premium Support",
1116 "1",
1117 "$15,000.00",
1118 "$15,000.00",
1119 ),
1120 ];
1121
1122 for (i, (pn, desc, qty, unit, total)) in items.iter().enumerate() {
1123 let row = i + 1;
1124 tbl.cell(row, 0).unwrap().set_text(&format!("{}", i + 1));
1125 tbl.cell(row, 1).unwrap().set_text(pn);
1126 tbl.cell(row, 2).unwrap().set_text(desc);
1127 tbl.cell(row, 3).unwrap().set_text(qty);
1128 tbl.cell(row, 4).unwrap().set_text(unit);
1129 tbl.cell(row, 5).unwrap().set_text(total);
1130 if i % 2 == 0 {
1131 for c in 0..6 {
1132 tbl.cell(row, c).unwrap().shading("F0F8F8");
1133 }
1134 }
1135 }
1136 }
1137
1138 doc.add_paragraph("");
1139
1140 // ── Totals ──
1141 {
1142 let mut tbl = doc
1143 .add_table(4, 2)
1144 .borders(BorderStyle::Single, 2, "008B8B")
1145 .width(Length::inches(3.5))
1146 .alignment(Alignment::Right);
1147 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1148 tbl.cell(0, 1).unwrap().set_text("$139,920.00");
1149 tbl.cell(1, 0).unwrap().set_text("Shipping & Handling");
1150 tbl.cell(1, 1).unwrap().set_text("$2,500.00");
1151 tbl.cell(2, 0).unwrap().set_text("Tax (8.625%)");
1152 tbl.cell(2, 1).unwrap().set_text("$12,068.10");
1153 tbl.cell(3, 0).unwrap().set_text("TOTAL");
1154 tbl.cell(3, 0).unwrap().shading("E07020");
1155 tbl.cell(3, 1).unwrap().set_text("$154,488.10");
1156 tbl.cell(3, 1).unwrap().shading("E07020");
1157 }
1158
1159 doc.add_paragraph("");
1160
1161 // ── Terms & Conditions ──
1162 {
1163 let mut p = doc.add_paragraph("");
1164 p.add_run("Terms & Conditions")
1165 .bold(true)
1166 .size(14.0)
1167 .color("1A3C3C");
1168 }
1169 doc.add_numbered_list_item(
1170 "This quotation is valid for 30 calendar days from the date of issue.",
1171 0,
1172 );
1173 doc.add_numbered_list_item(
1174 "All prices are in USD and exclusive of applicable taxes unless stated.",
1175 0,
1176 );
1177 doc.add_numbered_list_item("Standard lead time is 4-6 weeks from PO receipt.", 0);
1178 doc.add_numbered_list_item(
1179 "Payment terms: Net 30 from invoice date. 2% discount for payment within 10 days.",
1180 0,
1181 );
1182 doc.add_numbered_list_item(
1183 "Warranty: 3-year manufacturer warranty on all hardware components.",
1184 0,
1185 );
1186 doc.add_numbered_list_item(
1187 "Returns subject to 15% restocking fee if initiated after 14 days.",
1188 0,
1189 );
1190
1191 doc.add_paragraph("");
1192 doc.add_paragraph("")
1193 .border_bottom(BorderStyle::Single, 4, "008B8B");
1194 doc.add_paragraph("");
1195 {
1196 let mut p = doc.add_paragraph("");
1197 p.add_run("Accepted by: ").bold(true);
1198 p.add_run("___________________________ Date: ___________");
1199 }
1200 {
1201 let mut p = doc.add_paragraph("");
1202 p.add_run("Print Name: ").bold(true);
1203 p.add_run("___________________________ Title: ___________");
1204 }
1205
1206 doc
1207}
1208
1209// =============================================================================
1210// 4. INVOICE — Crimson + charcoal scheme
1211// =============================================================================
1212fn generate_invoice(_samples_dir: &Path) -> Document {
1213 let mut doc = Document::new();
1214
1215 // Colors: Crimson #B22222, Charcoal #333333, Light #FAF0F0
1216 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1217 doc.set_margins(
1218 Length::inches(0.75),
1219 Length::inches(0.75),
1220 Length::inches(0.75),
1221 Length::inches(0.75),
1222 );
1223 doc.set_title("Invoice INV-2026-0392");
1224 doc.set_author("Walter White");
1225 doc.set_keywords("invoice, Tensorbee");
1226
1227 doc.set_footer("Tensorbee — Thank you for your business!");
1228
1229 // ── Company & Invoice Header ──
1230 {
1231 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1232 // Company name left, INVOICE right
1233 {
1234 let mut cell = tbl.cell(0, 0).unwrap();
1235 let mut p = cell.add_paragraph("");
1236 p.add_run("TENSORBEE")
1237 .bold(true)
1238 .size(32.0)
1239 .color("B22222")
1240 .font("Helvetica");
1241 }
1242 {
1243 let mut cell = tbl.cell(0, 1).unwrap();
1244 let mut p = cell.add_paragraph("");
1245 p.add_run("INVOICE").bold(true).size(32.0).color("333333");
1246 }
1247 tbl.cell(1, 0)
1248 .unwrap()
1249 .set_text("123 Innovation Drive, Suite 400");
1250 tbl.cell(1, 1).unwrap().set_text("Invoice #: INV-2026-0392");
1251 tbl.cell(2, 0).unwrap().set_text("San Francisco, CA 94105");
1252 tbl.cell(2, 1).unwrap().set_text("Date: February 22, 2026");
1253 tbl.cell(3, 0).unwrap().set_text("walter@tensorbee.com");
1254 tbl.cell(3, 1).unwrap().set_text("Due Date: March 24, 2026");
1255 }
1256
1257 doc.add_paragraph("")
1258 .border_bottom(BorderStyle::Thick, 8, "B22222");
1259 doc.add_paragraph("");
1260
1261 // ── Bill To / Ship To ──
1262 {
1263 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1264 tbl.cell(0, 0).unwrap().set_text("BILL TO");
1265 tbl.cell(0, 0).unwrap().shading("B22222");
1266 tbl.cell(0, 1).unwrap().set_text("SHIP TO");
1267 tbl.cell(0, 1).unwrap().shading("B22222");
1268 tbl.cell(1, 0).unwrap().set_text("Meridian Dynamics LLC");
1269 tbl.cell(1, 1).unwrap().set_text("Meridian Dynamics LLC");
1270 tbl.cell(2, 0).unwrap().set_text("456 Enterprise Blvd");
1271 tbl.cell(2, 1).unwrap().set_text("Attn: Server Room B");
1272 tbl.cell(3, 0).unwrap().set_text("Austin, TX 78701");
1273 tbl.cell(3, 1)
1274 .unwrap()
1275 .set_text("456 Enterprise Blvd, Austin, TX 78701");
1276 }
1277
1278 doc.add_paragraph("");
1279
1280 // ── Line Items ──
1281 {
1282 let mut tbl = doc
1283 .add_table(8, 5)
1284 .borders(BorderStyle::Single, 2, "B22222")
1285 .width_pct(100.0);
1286 let hdrs = ["Description", "Qty", "Unit Price", "Tax", "Amount"];
1287 for (c, h) in hdrs.iter().enumerate() {
1288 tbl.cell(0, c).unwrap().set_text(h);
1289 tbl.cell(0, c).unwrap().shading("B22222");
1290 }
1291 let items = [
1292 (
1293 "ML Infrastructure Setup — Phase 1",
1294 "1",
1295 "$45,000.00",
1296 "$3,881.25",
1297 "$48,881.25",
1298 ),
1299 (
1300 "Data Pipeline Development (160 hrs)",
1301 "160",
1302 "$225.00",
1303 "$3,105.00",
1304 "$39,105.00",
1305 ),
1306 (
1307 "GPU Cluster Configuration",
1308 "1",
1309 "$12,000.00",
1310 "$1,035.00",
1311 "$13,035.00",
1312 ),
1313 (
1314 "API Gateway Implementation",
1315 "1",
1316 "$18,500.00",
1317 "$1,595.63",
1318 "$20,095.63",
1319 ),
1320 (
1321 "Load Testing & QA (80 hrs)",
1322 "80",
1323 "$195.00",
1324 "$1,345.50",
1325 "$16,945.50",
1326 ),
1327 (
1328 "Documentation & Training",
1329 "1",
1330 "$8,000.00",
1331 "$690.00",
1332 "$8,690.00",
1333 ),
1334 (
1335 "Project Management (3 months)",
1336 "3",
1337 "$6,500.00",
1338 "$1,679.63",
1339 "$21,179.63",
1340 ),
1341 ];
1342 for (i, (desc, qty, unit, tax, amt)) in items.iter().enumerate() {
1343 let r = i + 1;
1344 tbl.cell(r, 0).unwrap().set_text(desc);
1345 tbl.cell(r, 1).unwrap().set_text(qty);
1346 tbl.cell(r, 2).unwrap().set_text(unit);
1347 tbl.cell(r, 3).unwrap().set_text(tax);
1348 tbl.cell(r, 4).unwrap().set_text(amt);
1349 if i % 2 == 0 {
1350 for c in 0..5 {
1351 tbl.cell(r, c).unwrap().shading("FAF0F0");
1352 }
1353 }
1354 }
1355 }
1356
1357 doc.add_paragraph("");
1358
1359 // ── Totals ──
1360 {
1361 let mut tbl = doc
1362 .add_table(4, 2)
1363 .borders(BorderStyle::Single, 2, "B22222")
1364 .width(Length::inches(3.0))
1365 .alignment(Alignment::Right);
1366 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1367 tbl.cell(0, 1).unwrap().set_text("$154,600.00");
1368 tbl.cell(1, 0).unwrap().set_text("Tax (8.625%)");
1369 tbl.cell(1, 1).unwrap().set_text("$13,334.25");
1370 tbl.cell(2, 0).unwrap().set_text("Discount (5%)");
1371 tbl.cell(2, 1).unwrap().set_text("-$7,730.00");
1372 tbl.cell(3, 0).unwrap().set_text("AMOUNT DUE");
1373 tbl.cell(3, 0).unwrap().shading("B22222");
1374 tbl.cell(3, 1).unwrap().set_text("$160,204.25");
1375 tbl.cell(3, 1).unwrap().shading("B22222");
1376 }
1377
1378 doc.add_paragraph("");
1379 doc.add_paragraph("");
1380
1381 // ── Payment Details ──
1382 {
1383 let mut p = doc.add_paragraph("");
1384 p.add_run("Payment Details")
1385 .bold(true)
1386 .size(14.0)
1387 .color("333333");
1388 }
1389 doc.add_paragraph("Bank: Silicon Valley Bank")
1390 .indent_left(Length::inches(0.3));
1391 doc.add_paragraph("Account: Tensorbee Inc. — 0847-2953-1120")
1392 .indent_left(Length::inches(0.3));
1393 doc.add_paragraph("Routing: 121140399")
1394 .indent_left(Length::inches(0.3));
1395 doc.add_paragraph("Swift: SVBKUS6S")
1396 .indent_left(Length::inches(0.3));
1397
1398 doc.add_paragraph("");
1399
1400 doc.add_paragraph("Please include invoice number INV-2026-0392 in the payment reference.")
1401 .shading("FAF0F0")
1402 .border_all(BorderStyle::Single, 2, "B22222");
1403
1404 doc
1405}
1406
1407// =============================================================================
1408// 5. REPORT — Forest green + earth tones, with images & hierarchical sections
1409// =============================================================================
1410fn generate_report(_samples_dir: &Path) -> Document {
1411 let mut doc = Document::new();
1412
1413 // Colors: Forest #2D5016, Sage #6B8E23, Earth #8B7355, Cream #FFFAF0
1414 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1415 doc.set_margins(
1416 Length::inches(1.0),
1417 Length::inches(1.0),
1418 Length::inches(1.0),
1419 Length::inches(1.0),
1420 );
1421 doc.set_title("Q4 2025 Environmental Impact Report");
1422 doc.set_author("Walter White");
1423 doc.set_subject("Quarterly Environmental Report");
1424 doc.set_keywords("environment, sustainability, report, Tensorbee");
1425
1426 doc.set_different_first_page(true);
1427 doc.set_first_page_header("");
1428 doc.set_header("Tensorbee — Q4 2025 Environmental Impact Report");
1429 doc.set_footer("CONFIDENTIAL — Page");
1430
1431 // ── Cover ──
1432 let cover_bg = create_sample_png(612, 792, [20, 50, 15]);
1433 doc.add_background_image(&cover_bg, "report_cover.png");
1434
1435 for _ in 0..5 {
1436 doc.add_paragraph("");
1437 }
1438 {
1439 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1440 p.add_run("Q4 2025")
1441 .bold(true)
1442 .size(48.0)
1443 .color("FFFFFF")
1444 .font("Georgia");
1445 }
1446 {
1447 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1448 p.add_run("Environmental Impact Report")
1449 .size(24.0)
1450 .color("90EE90")
1451 .font("Georgia")
1452 .italic(true);
1453 }
1454 doc.add_paragraph("");
1455 {
1456 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1457 p.add_run("Tensorbee — Sustainability Division")
1458 .size(14.0)
1459 .color("C0C0C0");
1460 }
1461 {
1462 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1463 p.add_run("Prepared by Walter White, Chief Sustainability Officer")
1464 .size(11.0)
1465 .color("AAAAAA");
1466 }
1467
1468 // ── TOC ──
1469 doc.add_paragraph("").page_break_before(true);
1470 doc.insert_toc(doc.content_count(), 3);
1471
1472 // ── Section 1: Executive Overview ──
1473 doc.add_paragraph("").page_break_before(true);
1474 doc.add_paragraph("Executive Overview").style("Heading1");
1475 doc.add_paragraph(
1476 "This report presents Tensorbee's environmental performance for Q4 2025. Our \
1477 sustainability initiatives have yielded a 34% reduction in carbon emissions compared \
1478 to Q4 2024, exceeding our target of 25%. Key achievements include the transition to \
1479 100% renewable energy in our primary data centers and the launch of our carbon offset \
1480 marketplace.",
1481 )
1482 .first_line_indent(Length::inches(0.3));
1483
1484 // Image: performance chart
1485 let chart_img = create_chart_png(400, 200);
1486 doc.add_paragraph("");
1487 doc.add_picture(
1488 &chart_img,
1489 "performance_chart.png",
1490 Length::inches(5.0),
1491 Length::inches(2.5),
1492 )
1493 .alignment(Alignment::Center);
1494 {
1495 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1496 p.add_run("Figure 1: Quarterly Carbon Emissions (tonnes CO2e)")
1497 .italic(true)
1498 .size(9.0)
1499 .color("666666");
1500 }
1501
1502 // ── Section 2: Energy Consumption ──
1503 doc.add_paragraph("").page_break_before(true);
1504 doc.add_paragraph("Energy Consumption").style("Heading1");
1505
1506 doc.add_paragraph("Data Center Operations")
1507 .style("Heading2");
1508 doc.add_paragraph(
1509 "Our three primary data centers consumed a combined 4.2 GWh in Q4 2025, \
1510 a 12% reduction from Q3 2025 driven by improved cooling efficiency and \
1511 server consolidation.",
1512 );
1513
1514 // Data table
1515 {
1516 let mut tbl = doc
1517 .add_table(5, 4)
1518 .borders(BorderStyle::Single, 4, "2D5016")
1519 .width_pct(100.0);
1520 let hdrs = ["Data Center", "Capacity (MW)", "Usage (GWh)", "PUE"];
1521 for (c, h) in hdrs.iter().enumerate() {
1522 tbl.cell(0, c).unwrap().set_text(h);
1523 tbl.cell(0, c).unwrap().shading("2D5016");
1524 }
1525 let rows = [
1526 ("San Francisco (Primary)", "3.2", "1.8", "1.12"),
1527 ("Dublin (EU)", "2.1", "1.2", "1.18"),
1528 ("Singapore (APAC)", "1.8", "1.2", "1.24"),
1529 ("TOTAL", "7.1", "4.2", "1.17 avg"),
1530 ];
1531 for (i, (dc, cap, use_, pue)) in rows.iter().enumerate() {
1532 tbl.cell(i + 1, 0).unwrap().set_text(dc);
1533 tbl.cell(i + 1, 1).unwrap().set_text(cap);
1534 tbl.cell(i + 1, 2).unwrap().set_text(use_);
1535 tbl.cell(i + 1, 3).unwrap().set_text(pue);
1536 if i == 3 {
1537 for c in 0..4 {
1538 tbl.cell(i + 1, c).unwrap().shading("E2EFDA");
1539 }
1540 }
1541 }
1542 }
1543
1544 doc.add_paragraph("");
1545
1546 doc.add_paragraph("Renewable Energy Mix").style("Heading2");
1547 doc.add_paragraph("Breakdown of energy sources across all facilities:");
1548
1549 doc.add_bullet_list_item("Solar PV: 42% (1.76 GWh)", 0);
1550 doc.add_bullet_list_item("Wind Power Purchase Agreements: 38% (1.60 GWh)", 0);
1551 doc.add_bullet_list_item("Hydroelectric: 12% (0.50 GWh)", 0);
1552 doc.add_bullet_list_item("Grid (non-renewable): 8% (0.34 GWh)", 0);
1553
1554 // ── Section 3: Water & Waste ──
1555 doc.add_paragraph("Water & Waste Management")
1556 .style("Heading1");
1557
1558 doc.add_paragraph("Water Usage").style("Heading2");
1559 doc.add_paragraph(
1560 "Total water consumption was 12.4 million gallons, a 15% reduction from Q3. \
1561 Our closed-loop cooling systems now recycle 78% of water used in cooling operations.",
1562 );
1563
1564 doc.add_paragraph("Waste Reduction").style("Heading2");
1565 doc.add_paragraph("E-waste management results for Q4:")
1566 .keep_with_next(true);
1567 {
1568 let mut tbl = doc
1569 .add_table(4, 3)
1570 .borders(BorderStyle::Single, 2, "6B8E23")
1571 .width_pct(80.0);
1572 tbl.cell(0, 0).unwrap().set_text("Category");
1573 tbl.cell(0, 0).unwrap().shading("6B8E23");
1574 tbl.cell(0, 1).unwrap().set_text("Weight (kg)");
1575 tbl.cell(0, 1).unwrap().shading("6B8E23");
1576 tbl.cell(0, 2).unwrap().set_text("Recycled %");
1577 tbl.cell(0, 2).unwrap().shading("6B8E23");
1578 let rows = [
1579 ("Server Hardware", "2,450", "94%"),
1580 ("Networking Equipment", "820", "91%"),
1581 ("Storage Media", "340", "99%"),
1582 ];
1583 for (i, (cat, wt, pct)) in rows.iter().enumerate() {
1584 tbl.cell(i + 1, 0).unwrap().set_text(cat);
1585 tbl.cell(i + 1, 1).unwrap().set_text(wt);
1586 tbl.cell(i + 1, 2).unwrap().set_text(pct);
1587 }
1588 }
1589
1590 // ── Section 4: Initiatives ──
1591 doc.add_paragraph("").page_break_before(true);
1592 doc.add_paragraph("Q1 2026 Initiatives").style("Heading1");
1593
1594 doc.add_paragraph("Planned Programs").style("Heading2");
1595 doc.add_numbered_list_item(
1596 "Deploy on-site battery storage at SF data center (2 MWh capacity)",
1597 0,
1598 );
1599 doc.add_numbered_list_item("Pilot immersion cooling in Dublin facility", 0);
1600 doc.add_numbered_list_item("Launch employee commute carbon offset program", 0);
1601 doc.add_numbered_list_item("Achieve ISO 14001 certification for Singapore facility", 0);
1602
1603 doc.add_paragraph("Investment Targets").style("Heading2");
1604 doc.add_paragraph("Sustainability CapEx allocation for FY2026:")
1605 .keep_with_next(true);
1606 {
1607 let mut tbl = doc
1608 .add_table(5, 2)
1609 .borders(BorderStyle::Single, 4, "2D5016");
1610 tbl.cell(0, 0).unwrap().set_text("Initiative");
1611 tbl.cell(0, 0).unwrap().shading("2D5016");
1612 tbl.cell(0, 1).unwrap().set_text("Budget");
1613 tbl.cell(0, 1).unwrap().shading("2D5016");
1614 let rows = [
1615 ("Battery Storage", "$1.2M"),
1616 ("Immersion Cooling Pilot", "$800K"),
1617 ("Solar Panel Expansion", "$2.1M"),
1618 ("Carbon Credits", "$500K"),
1619 ];
1620 for (i, (init, budget)) in rows.iter().enumerate() {
1621 tbl.cell(i + 1, 0).unwrap().set_text(init);
1622 tbl.cell(i + 1, 1).unwrap().set_text(budget);
1623 if i % 2 == 0 {
1624 tbl.cell(i + 1, 0).unwrap().shading("FFFAF0");
1625 tbl.cell(i + 1, 1).unwrap().shading("FFFAF0");
1626 }
1627 }
1628 }
1629
1630 // Image: sustainability roadmap
1631 let roadmap_img = create_sample_png(500, 100, [40, 80, 30]);
1632 doc.add_paragraph("");
1633 doc.add_picture(
1634 &roadmap_img,
1635 "roadmap.png",
1636 Length::inches(6.0),
1637 Length::inches(1.2),
1638 )
1639 .alignment(Alignment::Center);
1640 {
1641 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1642 p.add_run("Figure 2: 2026 Sustainability Roadmap")
1643 .italic(true)
1644 .size(9.0)
1645 .color("666666");
1646 }
1647
1648 doc.add_paragraph("");
1649 doc.add_paragraph(
1650 "For questions about this report, contact Walter White at sustainability@tensorbee.com.",
1651 )
1652 .shading("FFFAF0")
1653 .border_all(BorderStyle::Single, 2, "2D5016");
1654
1655 doc
1656}
1657
1658// =============================================================================
1659// 6. LETTER — Slate blue + silver scheme
1660// =============================================================================
1661fn generate_letter(_samples_dir: &Path) -> Document {
1662 let mut doc = Document::new();
1663
1664 // Colors: Slate #4A5568, Blue accent #3182CE
1665 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1666 doc.set_margins(
1667 Length::inches(1.25),
1668 Length::inches(1.25),
1669 Length::inches(1.25),
1670 Length::inches(1.25),
1671 );
1672 doc.set_title("Business Letter");
1673 doc.set_author("Walter White");
1674
1675 // Banner header using raw XML
1676 let logo_img = create_logo_png(220, 48);
1677 let banner = build_header_banner_xml(
1678 "rId1",
1679 &BannerOpts {
1680 bg_color: "4A5568",
1681 banner_width: 7772400,
1682 banner_height: 731520, // ~0.8"
1683 logo_width: 2011680,
1684 logo_height: 438912,
1685 logo_x_offset: 295125,
1686 logo_y_offset: 146304,
1687 },
1688 );
1689 doc.set_raw_header_with_images(
1690 banner,
1691 &[("rId1", &logo_img, "logo.png")],
1692 rdocx_oxml::header_footer::HdrFtrType::Default,
1693 );
1694 doc.set_margins(
1695 Length::twips(2000),
1696 Length::twips(1800),
1697 Length::twips(1440),
1698 Length::twips(1800),
1699 );
1700 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
1701
1702 // Footer with contact
1703 doc.set_footer(
1704 "Tensorbee | 123 Innovation Drive, Suite 400 | San Francisco, CA 94105 | tensorbee.com",
1705 );
1706
1707 // ── Sender Address ──
1708 doc.add_paragraph("Walter White");
1709 doc.add_paragraph("Chief Executive Officer");
1710 doc.add_paragraph("Tensorbee");
1711 doc.add_paragraph("123 Innovation Drive, Suite 400");
1712 doc.add_paragraph("San Francisco, CA 94105");
1713 doc.add_paragraph("");
1714 doc.add_paragraph("February 22, 2026");
1715
1716 doc.add_paragraph("");
1717
1718 // ── Recipient ──
1719 doc.add_paragraph("Ms. Sarah Chen");
1720 doc.add_paragraph("VP of Engineering");
1721 doc.add_paragraph("Meridian Dynamics LLC");
1722 doc.add_paragraph("456 Enterprise Boulevard");
1723 doc.add_paragraph("Austin, TX 78701");
1724
1725 doc.add_paragraph("");
1726
1727 // ── Subject ──
1728 {
1729 let mut p = doc.add_paragraph("");
1730 p.add_run("Re: Strategic Technology Partnership — Phase 2 Expansion")
1731 .bold(true);
1732 }
1733
1734 doc.add_paragraph("");
1735
1736 // ── Body ──
1737 doc.add_paragraph("Dear Ms. Chen,");
1738 doc.add_paragraph("");
1739
1740 doc.add_paragraph(
1741 "I am writing to express Tensorbee's enthusiasm for expanding our strategic technology \
1742 partnership with Meridian Dynamics. Following the successful completion of Phase 1, \
1743 which delivered a 40% improvement in your ML inference pipeline throughput, we believe \
1744 the foundation is firmly established for an ambitious Phase 2 engagement.",
1745 )
1746 .first_line_indent(Length::inches(0.5));
1747
1748 doc.add_paragraph(
1749 "During our recent executive review, your team highlighted three priority areas for \
1750 the next phase of collaboration:",
1751 )
1752 .first_line_indent(Length::inches(0.5));
1753
1754 doc.add_numbered_list_item(
1755 "Real-time anomaly detection for your financial transaction monitoring system, \
1756 targeting sub-100ms latency at 50,000 transactions per second.",
1757 0,
1758 );
1759 doc.add_numbered_list_item(
1760 "Federated learning infrastructure to enable model training across your distributed \
1761 data centers without centralizing sensitive financial data.",
1762 0,
1763 );
1764 doc.add_numbered_list_item(
1765 "MLOps automation to reduce your model deployment cycle from the current 5 days \
1766 to under 4 hours.",
1767 0,
1768 );
1769
1770 doc.add_paragraph(
1771 "Our engineering team has prepared a detailed technical proposal addressing each of \
1772 these areas. We have allocated a dedicated team of eight senior engineers, led by \
1773 our CTO, to ensure continuity with the Phase 1 team your organization has already \
1774 built a productive working relationship with.",
1775 )
1776 .first_line_indent(Length::inches(0.5));
1777
1778 doc.add_paragraph(
1779 "I would welcome the opportunity to present our Phase 2 proposal in person. My \
1780 assistant will reach out to coordinate a meeting at your convenience during the \
1781 first week of March.",
1782 )
1783 .first_line_indent(Length::inches(0.5));
1784
1785 doc.add_paragraph("");
1786
1787 doc.add_paragraph("Warm regards,");
1788 doc.add_paragraph("");
1789 doc.add_paragraph("");
1790
1791 // Signature line
1792 doc.add_paragraph("")
1793 .border_bottom(BorderStyle::Single, 4, "4A5568");
1794 {
1795 let mut p = doc.add_paragraph("");
1796 p.add_run("Walter White").bold(true).size(12.0);
1797 }
1798 {
1799 let mut p = doc.add_paragraph("");
1800 p.add_run("Chief Executive Officer, Tensorbee")
1801 .italic(true)
1802 .size(10.0)
1803 .color("666666");
1804 }
1805 {
1806 let mut p = doc.add_paragraph("");
1807 p.add_run("walter@tensorbee.com | +1 (415) 555-0199")
1808 .size(10.0)
1809 .color("888888");
1810 }
1811
1812 doc
1813}
1814
1815// =============================================================================
1816// 7. EMPLOYMENT CONTRACT — Purple + charcoal scheme
1817// =============================================================================
1818fn generate_contract(_samples_dir: &Path) -> Document {
1819 let mut doc = Document::new();
1820
1821 // Colors: Purple #5B2C6F, Plum #8E44AD, Gray #2C3E50
1822 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1823 doc.set_margins(
1824 Length::inches(1.25),
1825 Length::inches(1.0),
1826 Length::inches(1.0),
1827 Length::inches(1.0),
1828 );
1829 doc.set_title("Employment Agreement");
1830 doc.set_author("Tensorbee HR Department");
1831 doc.set_subject("Employment Contract — Walter White");
1832 doc.set_keywords("employment, contract, agreement, Tensorbee");
1833
1834 doc.set_header("TENSORBEE — Employment Agreement");
1835 doc.set_footer("Employment Agreement — Walter White — Page");
1836
1837 // ── Title Block ──
1838 doc.add_paragraph("")
1839 .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1840 {
1841 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1842 p.add_run("EMPLOYMENT AGREEMENT")
1843 .bold(true)
1844 .size(24.0)
1845 .color("5B2C6F")
1846 .font("Georgia");
1847 }
1848 doc.add_paragraph("")
1849 .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1850 doc.add_paragraph("");
1851
1852 // ── Parties ──
1853 doc.add_paragraph(
1854 "This Employment Agreement (\"Agreement\") is entered into as of February 22, 2026 \
1855 (\"Effective Date\"), by and between:",
1856 );
1857
1858 doc.add_paragraph("");
1859
1860 {
1861 let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1862 p.add_run("EMPLOYER: ").bold(true);
1863 p.add_run(
1864 "Tensorbee, Inc., a Delaware corporation with its principal place of business at \
1865 123 Innovation Drive, Suite 400, San Francisco, CA 94105 (\"Company\")",
1866 );
1867 }
1868 doc.add_paragraph("");
1869 {
1870 let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1871 p.add_run("EMPLOYEE: ").bold(true);
1872 p.add_run(
1873 "Walter White, residing at 308 Negra Arroyo Lane, Albuquerque, NM 87104 (\"Employee\")",
1874 );
1875 }
1876
1877 doc.add_paragraph("");
1878 doc.add_paragraph("The Company and Employee are collectively referred to as the \"Parties.\"");
1879 doc.add_paragraph("");
1880
1881 // ── Article 1: Position and Duties ──
1882 doc.add_paragraph("Article 1 — Position and Duties")
1883 .style("Heading1");
1884 {
1885 let mut p = doc.add_paragraph("");
1886 p.add_run("1.1 ").bold(true);
1887 p.add_run("The Company hereby employs the Employee as ");
1888 p.add_run("Chief Executive Officer (CEO)")
1889 .bold(true)
1890 .italic(true);
1891 p.add_run(", reporting directly to the Board of Directors.");
1892 }
1893 {
1894 let mut p = doc.add_paragraph("");
1895 p.add_run("1.2 ").bold(true);
1896 p.add_run("The Employee shall devote full working time, attention, and best efforts to \
1897 the performance of duties as reasonably assigned by the Board, including but not limited to:");
1898 }
1899 doc.add_bullet_list_item(
1900 "Setting and executing the Company's strategic vision and business plan",
1901 0,
1902 );
1903 doc.add_bullet_list_item(
1904 "Overseeing all operations, engineering, and commercial activities",
1905 0,
1906 );
1907 doc.add_bullet_list_item(
1908 "Representing the Company to investors, customers, and the public",
1909 0,
1910 );
1911 doc.add_bullet_list_item("Recruiting, developing, and retaining key talent", 0);
1912
1913 {
1914 let mut p = doc.add_paragraph("");
1915 p.add_run("1.3 ").bold(true);
1916 p.add_run(
1917 "The Employee's primary work location shall be the Company's San Francisco \
1918 headquarters, with reasonable travel as required by business needs.",
1919 );
1920 }
1921
1922 // ── Article 2: Compensation ──
1923 doc.add_paragraph("Article 2 — Compensation and Benefits")
1924 .style("Heading1");
1925 {
1926 let mut p = doc.add_paragraph("");
1927 p.add_run("2.1 Base Salary. ").bold(true);
1928 p.add_run("The Company shall pay the Employee an annual base salary of ");
1929 p.add_run("$375,000.00").bold(true);
1930 p.add_run(" (Three Hundred Seventy-Five Thousand Dollars), payable in accordance with the \
1931 Company's standard payroll schedule, less applicable withholdings and deductions.");
1932 }
1933 {
1934 let mut p = doc.add_paragraph("");
1935 p.add_run("2.2 Annual Bonus. ").bold(true);
1936 p.add_run("The Employee shall be eligible for an annual performance bonus of up to ");
1937 p.add_run("40%").bold(true);
1938 p.add_run(
1939 " of base salary, based on achievement of mutually agreed performance objectives.",
1940 );
1941 }
1942 {
1943 let mut p = doc.add_paragraph("");
1944 p.add_run("2.3 Equity. ").bold(true);
1945 p.add_run("Subject to Board approval, the Employee shall receive a stock option grant of ");
1946 p.add_run("500,000 shares").bold(true);
1947 p.add_run(
1948 " of the Company's common stock, vesting over four (4) years with a one-year cliff, \
1949 at an exercise price equal to the fair market value on the date of grant.",
1950 );
1951 }
1952
1953 // Compensation summary table
1954 doc.add_paragraph("");
1955 {
1956 let mut tbl = doc
1957 .add_table(5, 2)
1958 .borders(BorderStyle::Single, 4, "5B2C6F")
1959 .width_pct(70.0)
1960 .alignment(Alignment::Center);
1961 tbl.cell(0, 0).unwrap().set_text("Compensation Element");
1962 tbl.cell(0, 0).unwrap().shading("5B2C6F");
1963 tbl.cell(0, 1).unwrap().set_text("Value");
1964 tbl.cell(0, 1).unwrap().shading("5B2C6F");
1965 let items = [
1966 ("Base Salary", "$375,000/year"),
1967 ("Target Bonus", "Up to 40% ($150,000)"),
1968 ("Equity Grant", "500,000 shares (4yr vest)"),
1969 ("Total Target Comp", "$525,000 + equity"),
1970 ];
1971 for (i, (elem, val)) in items.iter().enumerate() {
1972 tbl.cell(i + 1, 0).unwrap().set_text(elem);
1973 tbl.cell(i + 1, 1).unwrap().set_text(val);
1974 if i == 3 {
1975 tbl.cell(i + 1, 0).unwrap().shading("E8DAEF");
1976 tbl.cell(i + 1, 1).unwrap().shading("E8DAEF");
1977 }
1978 }
1979 }
1980
1981 // ── Article 3: Benefits ──
1982 doc.add_paragraph("").page_break_before(true);
1983 doc.add_paragraph("Article 3 — Benefits").style("Heading1");
1984 {
1985 let mut p = doc.add_paragraph("");
1986 p.add_run("3.1 ").bold(true);
1987 p.add_run(
1988 "The Employee shall be entitled to participate in all benefit programs \
1989 generally available to senior executives, including:",
1990 );
1991 }
1992 doc.add_bullet_list_item(
1993 "Medical, dental, and vision insurance (100% premium coverage for employee and dependents)",
1994 0,
1995 );
1996 doc.add_bullet_list_item("401(k) retirement plan with 6% company match", 0);
1997 doc.add_bullet_list_item("Life insurance and long-term disability coverage", 0);
1998 doc.add_bullet_list_item("Annual professional development allowance of $10,000", 0);
1999
2000 {
2001 let mut p = doc.add_paragraph("");
2002 p.add_run("3.2 Paid Time Off. ").bold(true);
2003 p.add_run(
2004 "The Employee shall receive 25 days of paid vacation per year, plus Company holidays, \
2005 accruing on a monthly basis.",
2006 );
2007 }
2008
2009 // ── Article 4: Term and Termination ──
2010 doc.add_paragraph("Article 4 — Term and Termination")
2011 .style("Heading1");
2012 {
2013 let mut p = doc.add_paragraph("");
2014 p.add_run("4.1 At-Will Employment. ").bold(true);
2015 p.add_run(
2016 "This Agreement is for at-will employment and may be terminated by either Party \
2017 at any time, with or without cause, subject to the notice provisions herein.",
2018 );
2019 }
2020 {
2021 let mut p = doc.add_paragraph("");
2022 p.add_run("4.2 Notice Period. ").bold(true);
2023 p.add_run("Either Party shall provide ");
2024 p.add_run("ninety (90) days").bold(true);
2025 p.add_run(" written notice of termination, or pay in lieu thereof.");
2026 }
2027 {
2028 let mut p = doc.add_paragraph("");
2029 p.add_run("4.3 Severance. ").bold(true);
2030 p.add_run("In the event of termination by the Company without Cause, the Employee shall \
2031 receive (a) twelve (12) months of base salary continuation, (b) pro-rata bonus \
2032 for the year of termination, and (c) twelve (12) months of COBRA premium coverage.");
2033 }
2034
2035 // ── Article 5: Confidentiality ──
2036 doc.add_paragraph("Article 5 — Confidentiality and IP")
2037 .style("Heading1");
2038 {
2039 let mut p = doc.add_paragraph("");
2040 p.add_run("5.1 ").bold(true);
2041 p.add_run(
2042 "The Employee agrees to maintain strict confidentiality of all proprietary \
2043 information, trade secrets, and business plans of the Company during and after \
2044 employment.",
2045 );
2046 }
2047 {
2048 let mut p = doc.add_paragraph("");
2049 p.add_run("5.2 ").bold(true);
2050 p.add_run(
2051 "All intellectual property, inventions, and works of authorship created by the \
2052 Employee during the term of employment and within the scope of duties shall be \
2053 the sole and exclusive property of the Company.",
2054 );
2055 }
2056
2057 // ── Article 6: Non-Compete ──
2058 doc.add_paragraph("Article 6 — Non-Competition")
2059 .style("Heading1");
2060 {
2061 let mut p = doc.add_paragraph("");
2062 p.add_run("6.1 ").bold(true);
2063 p.add_run("For a period of twelve (12) months following termination, the Employee agrees \
2064 not to engage in any business that directly competes with the Company's core \
2065 business of AI infrastructure and ML operations services, within the United States.");
2066 }
2067
2068 // ── Article 7: General Provisions ──
2069 doc.add_paragraph("Article 7 — General Provisions")
2070 .style("Heading1");
2071 {
2072 let mut p = doc.add_paragraph("");
2073 p.add_run("7.1 Governing Law. ").bold(true);
2074 p.add_run(
2075 "This Agreement shall be governed by and construed in accordance with the laws of \
2076 the State of California.",
2077 );
2078 }
2079 {
2080 let mut p = doc.add_paragraph("");
2081 p.add_run("7.2 Entire Agreement. ").bold(true);
2082 p.add_run(
2083 "This Agreement constitutes the entire agreement between the Parties and supersedes \
2084 all prior negotiations, representations, and agreements.",
2085 );
2086 }
2087 {
2088 let mut p = doc.add_paragraph("");
2089 p.add_run("7.3 Amendment. ").bold(true);
2090 p.add_run(
2091 "This Agreement may only be amended by a written instrument signed by both Parties.",
2092 );
2093 }
2094
2095 // ── Signature Block ──
2096 doc.add_paragraph("").page_break_before(true);
2097 doc.add_paragraph("IN WITNESS WHEREOF, the Parties have executed this Employment Agreement as of the Effective Date.")
2098 .space_after(Length::pt(24.0));
2099
2100 // Signature table
2101 {
2102 let mut tbl = doc.add_table(6, 2).width_pct(100.0);
2103 tbl.cell(0, 0).unwrap().set_text("FOR THE COMPANY:");
2104 tbl.cell(0, 0).unwrap().shading("5B2C6F");
2105 tbl.cell(0, 1).unwrap().set_text("EMPLOYEE:");
2106 tbl.cell(0, 1).unwrap().shading("5B2C6F");
2107
2108 tbl.cell(1, 0).unwrap().set_text("");
2109 tbl.cell(1, 1).unwrap().set_text("");
2110 tbl.row(1).unwrap().height(Length::pt(40.0));
2111
2112 tbl.cell(2, 0)
2113 .unwrap()
2114 .set_text("Signature: ___________________________");
2115 tbl.cell(2, 1)
2116 .unwrap()
2117 .set_text("Signature: ___________________________");
2118 tbl.cell(3, 0)
2119 .unwrap()
2120 .set_text("Name: Board Representative");
2121 tbl.cell(3, 1).unwrap().set_text("Name: Walter White");
2122 tbl.cell(4, 0)
2123 .unwrap()
2124 .set_text("Title: Chair, Board of Directors");
2125 tbl.cell(4, 1)
2126 .unwrap()
2127 .set_text("Title: Chief Executive Officer");
2128 tbl.cell(5, 0).unwrap().set_text("Date: _______________");
2129 tbl.cell(5, 1).unwrap().set_text("Date: _______________");
2130 }
2131
2132 doc
2133}Sourcepub fn paragraph_count(&self) -> usize
pub fn paragraph_count(&self) -> usize
Get the number of paragraphs.
Sourcepub fn text(&self) -> String
pub fn text(&self) -> String
Get the plain text of body paragraphs and table cells in document order.
Sourcepub fn paragraph_mut(&mut self, index: usize) -> Option<Paragraph<'_>>
pub fn paragraph_mut(&mut self, index: usize) -> Option<Paragraph<'_>>
Get a mutable reference to a paragraph by index (among paragraphs only).
Sourcepub fn table(&self, index: usize) -> Option<TableRef<'_>>
pub fn table(&self, index: usize) -> Option<TableRef<'_>>
Get an immutable table by index among tables only.
Sourcepub fn table_mut(&mut self, index: usize) -> Option<Table<'_>>
pub fn table_mut(&mut self, index: usize) -> Option<Table<'_>>
Get a mutable table by index among tables only.
Sourcepub fn add_table(&mut self, rows: usize, cols: usize) -> Table<'_>
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?
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
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}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}97fn generate_feature_showcase(_samples_dir: &Path) -> Document {
98 let mut doc = Document::new();
99
100 // ── Page Setup & Metadata ──
101 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
102 doc.set_margins(
103 Length::inches(1.0),
104 Length::inches(1.0),
105 Length::inches(1.0),
106 Length::inches(1.0),
107 );
108 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
109 doc.set_gutter(Length::twips(0));
110 doc.set_title("rdocx Feature Showcase");
111 doc.set_author("rdocx Sample Generator");
112 doc.set_subject("Comprehensive feature demonstration");
113 doc.set_keywords("rdocx, docx, rust, sample, showcase");
114
115 // Headers & Footers with different first page
116 doc.set_different_first_page(true);
117 doc.set_first_page_header("rdocx");
118 doc.set_first_page_footer("Feature Showcase — Cover Page");
119 doc.set_header("rdocx Feature Showcase");
120 doc.set_footer("Generated by rdocx");
121
122 // ── COVER PAGE ──
123 let bg = create_sample_png(612, 792, [20, 45, 90]);
124 doc.add_background_image(&bg, "cover_bg.png");
125
126 for _ in 0..3 {
127 doc.add_paragraph("");
128 }
129 {
130 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
131 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
132 }
133 {
134 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
135 p.add_run("Complete Feature Showcase")
136 .size(28.0)
137 .color("FFFFFF")
138 .italic(true);
139 }
140 doc.add_paragraph("");
141 {
142 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
143 p.add_run("Every feature of the rdocx Rust library")
144 .size(13.0)
145 .color("B0C4DE");
146 }
147 {
148 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
149 p.add_run("demonstrated in a single document.")
150 .size(13.0)
151 .color("B0C4DE");
152 }
153
154 // ── TABLE OF CONTENTS ──
155 doc.add_paragraph("").page_break_before(true);
156 doc.insert_toc(doc.content_count(), 3);
157
158 // ── SECTION 1: TEXT FORMATTING ──
159 doc.add_paragraph("").page_break_before(true);
160 doc.add_paragraph("1. Text Formatting").style("Heading1");
161
162 // Paragraph Alignment
163 doc.add_paragraph("1.1 Paragraph Alignment")
164 .style("Heading2");
165 doc.add_paragraph("Left-aligned paragraph (default).")
166 .alignment(Alignment::Left);
167 doc.add_paragraph("Center-aligned paragraph.")
168 .alignment(Alignment::Center);
169 doc.add_paragraph("Right-aligned paragraph.")
170 .alignment(Alignment::Right);
171 doc.add_paragraph(
172 "Justified paragraph — this text is long enough to demonstrate how justified alignment \
173 distributes extra space across word gaps so lines fill the full width of the text area.",
174 )
175 .alignment(Alignment::Justify);
176
177 doc.add_paragraph("");
178
179 // Run Formatting
180 doc.add_paragraph("1.2 Run Formatting").style("Heading2");
181 {
182 let mut p = doc.add_paragraph("");
183 p.add_run("Bold").bold(true);
184 p.add_run(" | ");
185 p.add_run("Italic").italic(true);
186 p.add_run(" | ");
187 p.add_run("Bold Italic").bold(true).italic(true);
188 p.add_run(" | ");
189 p.add_run("Underline").underline(true);
190 p.add_run(" | ");
191 p.add_run("Strikethrough").strike(true);
192 p.add_run(" | ");
193 p.add_run("Double Strike").double_strike(true);
194 }
195 {
196 let mut p = doc.add_paragraph("");
197 p.add_run("Red").color("FF0000");
198 p.add_run(" | ");
199 p.add_run("Blue").color("0000FF");
200 p.add_run(" | ");
201 p.add_run("Green").color("00AA00");
202 p.add_run(" | ");
203 p.add_run("Highlighted").highlight("FFFF00");
204 }
205 {
206 let mut p = doc.add_paragraph("");
207 p.add_run("8pt").size(8.0);
208 p.add_run(" | ");
209 p.add_run("11pt").size(11.0);
210 p.add_run(" | ");
211 p.add_run("16pt").size(16.0);
212 p.add_run(" | ");
213 p.add_run("24pt").size(24.0);
214 }
215 {
216 let mut p = doc.add_paragraph("");
217 p.add_run("Arial").font("Arial");
218 p.add_run(" | ");
219 p.add_run("Times New Roman").font("Times New Roman");
220 p.add_run(" | ");
221 p.add_run("Courier New").font("Courier New");
222 }
223 {
224 let mut p = doc.add_paragraph("");
225 p.add_run("H");
226 p.add_run("2").subscript();
227 p.add_run("O (subscript) | E=mc");
228 p.add_run("2").superscript();
229 p.add_run(" (superscript)");
230 }
231 {
232 let mut p = doc.add_paragraph("");
233 p.add_run("ALL CAPS").all_caps(true);
234 p.add_run(" | ");
235 p.add_run("Small Caps").small_caps(true);
236 p.add_run(" | ");
237 p.add_run("Expanded").character_spacing(Length::twips(40));
238 p.add_run(" | ");
239 p.add_run("Hidden text (not visible)").hidden(true);
240 }
241
242 doc.add_paragraph("");
243
244 // Underline Styles
245 doc.add_paragraph("1.3 Underline Styles").style("Heading2");
246 {
247 let mut p = doc.add_paragraph("");
248 p.add_run("Single ")
249 .underline_style(rdocx::UnderlineStyle::Single);
250 p.add_run("Double ")
251 .underline_style(rdocx::UnderlineStyle::Double);
252 p.add_run("Thick ")
253 .underline_style(rdocx::UnderlineStyle::Thick);
254 p.add_run("Dotted ")
255 .underline_style(rdocx::UnderlineStyle::Dotted);
256 p.add_run("Dash ")
257 .underline_style(rdocx::UnderlineStyle::Dash);
258 p.add_run("Wave ")
259 .underline_style(rdocx::UnderlineStyle::Wave);
260 }
261
262 // ── SECTION 2: PARAGRAPH FORMATTING ──
263 doc.add_paragraph("").page_break_before(true);
264 doc.add_paragraph("2. Paragraph Formatting")
265 .style("Heading1");
266
267 doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
268 doc.add_paragraph("Paragraph with light green shading")
269 .shading("E2EFDA");
270 doc.add_paragraph("Paragraph with bottom border")
271 .border_bottom(BorderStyle::Single, 6, "2E75B6");
272 doc.add_paragraph("Paragraph with all borders (red)")
273 .border_all(BorderStyle::Single, 4, "FF0000");
274
275 doc.add_paragraph("2.2 Indentation").style("Heading2");
276 doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
277 .indent_left(Length::inches(1.0))
278 .hanging_indent(Length::inches(0.5));
279 doc.add_paragraph("0.5-inch first-line indent on this paragraph")
280 .first_line_indent(Length::inches(0.5));
281 doc.add_paragraph("Both left and right indent (0.75 inches each)")
282 .indent_left(Length::inches(0.75))
283 .indent_right(Length::inches(0.75));
284
285 doc.add_paragraph("2.3 Spacing & Line Height")
286 .style("Heading2");
287 doc.add_paragraph("Extra space before (24pt) and after (12pt)")
288 .space_before(Length::pt(24.0))
289 .space_after(Length::pt(12.0));
290 doc.add_paragraph(
291 "Double line spacing paragraph. This text should have extra vertical space between \
292 lines to demonstrate line_spacing_multiple(2.0).",
293 )
294 .line_spacing_multiple(2.0);
295 doc.add_paragraph("Exact 20pt line spacing (fixed height).")
296 .line_spacing(20.0);
297
298 doc.add_paragraph("2.4 Pagination Controls")
299 .style("Heading2");
300 doc.add_paragraph("keep_with_next — this paragraph stays with the next")
301 .keep_with_next(true);
302 doc.add_paragraph("(This paragraph was kept with the one above.)");
303 doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
304 .keep_together(true);
305 doc.add_paragraph("widow_control — prevents widow/orphan lines")
306 .widow_control(true);
307
308 // ── SECTION 3: LISTS ──
309 doc.add_paragraph("").page_break_before(true);
310 doc.add_paragraph("3. Lists").style("Heading1");
311
312 doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
313 doc.add_bullet_list_item("First item", 0);
314 doc.add_bullet_list_item("Second item", 0);
315 doc.add_bullet_list_item("Nested level 1", 1);
316 doc.add_bullet_list_item("Nested level 2", 2);
317 doc.add_bullet_list_item("Back to level 1", 1);
318 doc.add_bullet_list_item("Third item", 0);
319
320 doc.add_paragraph("");
321
322 doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
323 doc.add_numbered_list_item("First numbered item", 0);
324 doc.add_numbered_list_item("Second numbered item", 0);
325 doc.add_numbered_list_item("Sub-item A", 1);
326 doc.add_numbered_list_item("Sub-item B", 1);
327 doc.add_numbered_list_item("Third numbered item", 0);
328
329 // ── SECTION 4: TAB STOPS ──
330 doc.add_paragraph("");
331 doc.add_paragraph("4. Tab Stops").style("Heading1");
332
333 doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
334 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
335 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
336 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
337 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
338 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
339
340 doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
341 doc.add_paragraph("Item\t\tPrice")
342 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
343 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
344 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
345 doc.add_paragraph("Widget\t\t$19.99")
346 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
347 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
348 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
349 doc.add_paragraph("Gadget\t\t$249.50")
350 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
351 .add_tab_stop_with_leader(
352 TabAlignment::Right,
353 Length::inches(4.0),
354 TabLeader::Underscore,
355 )
356 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
357
358 // ── SECTION 5: TABLES ──
359 doc.add_paragraph("").page_break_before(true);
360 doc.add_paragraph("5. Tables").style("Heading1");
361
362 doc.add_paragraph("5.1 Basic Table").style("Heading2");
363 {
364 let mut tbl = doc
365 .add_table(4, 3)
366 .borders(BorderStyle::Single, 4, "000000");
367 for col in 0..3 {
368 tbl.cell(0, col).unwrap().shading("2E75B6");
369 }
370 tbl.cell(0, 0).unwrap().set_text("Name");
371 tbl.cell(0, 1).unwrap().set_text("Role");
372 tbl.cell(0, 2).unwrap().set_text("Location");
373 tbl.cell(1, 0).unwrap().set_text("Walter White");
374 tbl.cell(1, 1).unwrap().set_text("CEO");
375 tbl.cell(1, 2).unwrap().set_text("Albuquerque");
376 tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
377 tbl.cell(2, 1).unwrap().set_text("CTO");
378 tbl.cell(2, 2).unwrap().set_text("Remote");
379 tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
380 tbl.cell(3, 1).unwrap().set_text("Security");
381 tbl.cell(3, 2).unwrap().set_text("Washington");
382 }
383
384 doc.add_paragraph("");
385
386 doc.add_paragraph("5.2 Column Span & Cell Shading")
387 .style("Heading2");
388 {
389 let mut tbl = doc
390 .add_table(3, 4)
391 .borders(BorderStyle::Single, 4, "000000")
392 .width_pct(100.0);
393 tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
394 tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
395 tbl.cell(1, 0).unwrap().set_text("Region");
396 tbl.cell(1, 0).unwrap().shading("D6E4F0");
397 tbl.cell(1, 1).unwrap().set_text("Q1");
398 tbl.cell(1, 1).unwrap().shading("D6E4F0");
399 tbl.cell(1, 2).unwrap().set_text("Q2");
400 tbl.cell(1, 2).unwrap().shading("D6E4F0");
401 tbl.cell(1, 3).unwrap().set_text("Total");
402 tbl.cell(1, 3).unwrap().shading("D6E4F0");
403 tbl.cell(2, 0).unwrap().set_text("Americas");
404 tbl.cell(2, 1).unwrap().set_text("$2.4M");
405 tbl.cell(2, 2).unwrap().set_text("$2.7M");
406 tbl.cell(2, 3).unwrap().set_text("$5.1M");
407 }
408
409 doc.add_paragraph("");
410
411 doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
412 {
413 let mut tbl = doc
414 .add_table(4, 3)
415 .borders(BorderStyle::Single, 4, "000000");
416 tbl.cell(0, 0).unwrap().set_text("Category");
417 tbl.cell(0, 0).unwrap().shading("E2EFDA");
418 tbl.cell(0, 1).unwrap().set_text("Item");
419 tbl.cell(0, 1).unwrap().shading("E2EFDA");
420 tbl.cell(0, 2).unwrap().set_text("Price");
421 tbl.cell(0, 2).unwrap().shading("E2EFDA");
422 tbl.cell(1, 0).unwrap().set_text("Hardware");
423 tbl.cell(1, 0).unwrap().v_merge_restart();
424 tbl.cell(1, 1).unwrap().set_text("Laptop");
425 tbl.cell(1, 2).unwrap().set_text("$1,200");
426 tbl.cell(2, 0).unwrap().v_merge_continue();
427 tbl.cell(2, 1).unwrap().set_text("Monitor");
428 tbl.cell(2, 2).unwrap().set_text("$450");
429 tbl.cell(3, 0).unwrap().set_text("Software");
430 tbl.cell(3, 1).unwrap().set_text("IDE License");
431 tbl.cell(3, 2).unwrap().set_text("$200/yr");
432 }
433
434 doc.add_paragraph("");
435
436 doc.add_paragraph("5.4 Nested Table").style("Heading2");
437 {
438 let mut tbl = doc
439 .add_table(2, 2)
440 .borders(BorderStyle::Single, 6, "2E75B6");
441 tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
442 tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
443 tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
444 {
445 let mut cell = tbl.cell(1, 1).unwrap();
446 cell.set_text("Nested table below:");
447 let mut nested = cell
448 .add_table(2, 2)
449 .borders(BorderStyle::Single, 2, "FF6600");
450 nested.cell(0, 0).unwrap().set_text("A");
451 nested.cell(0, 1).unwrap().set_text("B");
452 nested.cell(1, 0).unwrap().set_text("C");
453 nested.cell(1, 1).unwrap().set_text("D");
454 }
455 }
456
457 doc.add_paragraph("");
458
459 doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
460 .style("Heading2");
461 {
462 let mut tbl = doc
463 .add_table(2, 3)
464 .borders(BorderStyle::Single, 4, "666666");
465 tbl.row(0).unwrap().height(Length::pt(50.0)).header();
466 tbl.cell(0, 0).unwrap().set_text("Top");
467 tbl.cell(0, 0)
468 .unwrap()
469 .vertical_alignment(VerticalAlignment::Top)
470 .shading("FFF2CC");
471 tbl.cell(0, 1).unwrap().set_text("Center");
472 tbl.cell(0, 1)
473 .unwrap()
474 .vertical_alignment(VerticalAlignment::Center)
475 .shading("D9E2F3");
476 tbl.cell(0, 2).unwrap().set_text("Bottom");
477 tbl.cell(0, 2)
478 .unwrap()
479 .vertical_alignment(VerticalAlignment::Bottom)
480 .shading("E2EFDA");
481 tbl.row(1).unwrap().cant_split();
482 tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
483 tbl.cell(1, 0).unwrap().no_wrap();
484 tbl.cell(1, 1).unwrap().set_text("Fixed width");
485 tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
486 tbl.cell(1, 2).unwrap().set_text("Normal");
487 }
488
489 // ── SECTION 6: IMAGES ──
490 doc.add_paragraph("").page_break_before(true);
491 doc.add_paragraph("6. Images").style("Heading1");
492
493 doc.add_paragraph("6.1 Inline Image").style("Heading2");
494 doc.add_paragraph("A blue gradient image below:");
495 let img = create_sample_png(200, 50, [0, 80, 200]);
496 doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
497
498 doc.add_paragraph("");
499 doc.add_paragraph("6.2 Header Image").style("Heading2");
500 let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
501 doc.set_header_image(
502 &hdr_img,
503 "header_logo.png",
504 Length::inches(2.0),
505 Length::inches(0.2),
506 );
507 doc.add_paragraph("The header now contains an inline image (check the top of this page).");
508
509 doc.add_paragraph("");
510 doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
511
512 // ── SECTION 7: CONTENT MANIPULATION ──
513 doc.add_paragraph("").page_break_before(true);
514 doc.add_paragraph("7. Content Manipulation")
515 .style("Heading1");
516
517 doc.add_paragraph("7.1 Placeholder Replacement")
518 .style("Heading2");
519 doc.add_paragraph("Customer: {{customer}}");
520 doc.add_paragraph("Date: {{date}}");
521 doc.add_paragraph("Reference: {{ref_number}}");
522 {
523 let mut tbl = doc
524 .add_table(2, 2)
525 .borders(BorderStyle::Single, 4, "000000");
526 tbl.cell(0, 0).unwrap().set_text("Project");
527 tbl.cell(0, 0).unwrap().shading("D6E4F0");
528 tbl.cell(0, 1).unwrap().set_text("{{project}}");
529 tbl.cell(1, 0).unwrap().set_text("Status");
530 tbl.cell(1, 0).unwrap().shading("D6E4F0");
531 tbl.cell(1, 1).unwrap().set_text("{{status}}");
532 }
533
534 let mut replacements = HashMap::new();
535 replacements.insert("{{customer}}", "Tensorbee Inc.");
536 replacements.insert("{{date}}", "February 22, 2026");
537 replacements.insert("{{ref_number}}", "TB-2026-001");
538 replacements.insert("{{project}}", "Infrastructure Upgrade");
539 replacements.insert("{{status}}", "In Progress");
540 let n = doc.replace_all(&replacements);
541 doc.add_paragraph(&format!("({n} placeholders replaced above)"));
542
543 doc.add_paragraph("");
544
545 doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
546 doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
547 let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
548 doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
549
550 doc.add_paragraph("");
551
552 doc.add_paragraph("7.3 Content Insertion").style("Heading2");
553 doc.add_paragraph("Section A: First content.");
554 doc.add_paragraph("Section C: Third content.");
555 if let Some(idx) = doc.find_content_index("Section C") {
556 doc.insert_paragraph(
557 idx,
558 "Section B: Inserted between A and C via find_content_index().",
559 );
560 }
561
562 // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
563 doc.add_paragraph("").section_break(SectionBreak::NextPage);
564 doc.add_paragraph("8. Section Breaks & Orientation")
565 .style("Heading1");
566 doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
567
568 {
569 let mut tbl = doc
570 .add_table(3, 7)
571 .borders(BorderStyle::Single, 4, "2E75B6");
572 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
573 for (c, h) in headers.iter().enumerate() {
574 tbl.cell(0, c).unwrap().set_text(h);
575 tbl.cell(0, c).unwrap().shading("2E75B6");
576 }
577 let data = [
578 [
579 "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
580 ],
581 [
582 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
583 ],
584 ];
585 for (r, row) in data.iter().enumerate() {
586 for (c, v) in row.iter().enumerate() {
587 tbl.cell(r + 1, c).unwrap().set_text(v);
588 }
589 }
590 }
591
592 doc.add_paragraph("")
593 .section_break(SectionBreak::NextPage)
594 .section_landscape();
595
596 // ── SECTION 9: CUSTOM STYLES ──
597 doc.add_paragraph("9. Custom Styles").style("Heading1");
598 doc.add_style(
599 StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
600 .based_on("Normal")
601 .paragraph_properties(rdocx_oxml::properties::CT_PPr {
602 shading: Some(rdocx_oxml::properties::CT_Shd {
603 val: "clear".to_string(),
604 color: None,
605 fill: Some("FFF2CC".to_string()),
606 }),
607 ..Default::default()
608 })
609 .run_properties(rdocx_oxml::properties::CT_RPr {
610 bold: Some(true),
611 color: Some("C45911".to_string()),
612 ..Default::default()
613 }),
614 );
615 doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
616 .style("CustomHighlight");
617
618 doc.add_paragraph("");
619
620 // ── SECTION 10: DOCUMENT INTELLIGENCE ──
621 doc.add_paragraph("10. Document Intelligence API")
622 .style("Heading1");
623
624 let wc = doc.word_count();
625 let hc = doc.headings().len();
626 let ic = doc.images().len();
627 let lc = doc.links().len();
628 doc.add_paragraph(&format!("Word count: {wc}"));
629 doc.add_paragraph(&format!("Heading count: {hc}"));
630 doc.add_paragraph(&format!("Image count: {ic}"));
631 doc.add_paragraph(&format!("Link count: {lc}"));
632
633 let outline = doc.document_outline();
634 doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
635
636 let issues = doc.audit_accessibility();
637 doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
638 for issue in &issues {
639 doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
640 }
641
642 // ── SECTION 11: DOCUMENT MERGING ──
643 doc.add_paragraph("");
644 doc.add_paragraph("11. Document Merging").style("Heading1");
645 {
646 let mut other = Document::new();
647 other.add_paragraph("This paragraph was merged from another document using append().");
648 doc.append(&other);
649 }
650
651 doc.add_paragraph("");
652
653 // ── SUMMARY ──
654 doc.add_paragraph("Summary of Demonstrated Features")
655 .style("Heading1");
656 let features = [
657 "Page setup: size, margins, header/footer distance, gutter",
658 "Document metadata: title, author, subject, keywords",
659 "Headers and footers: text, images, different first page",
660 "Background images (full-page behind text)",
661 "Table of Contents generation with bookmarks",
662 "Text formatting: bold, italic, underline styles, strike, color, size, font",
663 "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
664 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
665 "Pagination controls: keep-with-next, keep-together, widow control",
666 "Bullet and numbered lists with nesting",
667 "Tab stops with dot/underscore/hyphen leaders",
668 "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
669 "Row properties: header rows, exact height, cant-split",
670 "Cell properties: width, no-wrap, vertical alignment",
671 "Inline images and header images",
672 "Placeholder replacement (single and batch)",
673 "Regex-based find and replace",
674 "Content insertion at specific positions",
675 "Section breaks with mixed portrait/landscape",
676 "Custom paragraph and character styles",
677 "Document intelligence: word count, headings, outline, images, links",
678 "Accessibility audit",
679 "Document merging (append)",
680 "Export: DOCX, PDF, HTML, Markdown",
681 ];
682 for f in &features {
683 doc.add_bullet_list_item(f, 0);
684 }
685 doc.add_paragraph("");
686 doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
687 .alignment(Alignment::Center)
688 .shading("E2EFDA")
689 .border_all(BorderStyle::Single, 2, "00AA00");
690
691 doc
692}
693
694// =============================================================================
695// 2. PROPOSAL DOCUMENT — Deep navy + gold scheme
696// =============================================================================
697fn generate_proposal(_samples_dir: &Path) -> Document {
698 let mut doc = Document::new();
699
700 // Colors: Navy #1B2A4A, Gold #C5922E, Light #F4F1EB
701 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
702 doc.set_margins(
703 Length::inches(1.0),
704 Length::inches(1.0),
705 Length::inches(1.0),
706 Length::inches(1.0),
707 );
708 doc.set_title("AI Platform Modernization Proposal");
709 doc.set_author("Walter White");
710 doc.set_subject("Technology Proposal");
711 doc.set_keywords("proposal, AI, modernization, Tensorbee");
712
713 // Banner header
714 let logo_img = create_logo_png(220, 48);
715 let banner = build_header_banner_xml(
716 "rId1",
717 &BannerOpts {
718 bg_color: "1B2A4A",
719 banner_width: 7772400,
720 banner_height: 914400,
721 logo_width: 2011680,
722 logo_height: 438912,
723 logo_x_offset: 295125,
724 logo_y_offset: 237744,
725 },
726 );
727 doc.set_raw_header_with_images(
728 banner,
729 &[("rId1", &logo_img, "logo.png")],
730 rdocx_oxml::header_footer::HdrFtrType::Default,
731 );
732 doc.set_different_first_page(true);
733 let cover_banner = build_header_banner_xml(
734 "rId1",
735 &BannerOpts {
736 bg_color: "C5922E",
737 banner_width: 7772400,
738 banner_height: 914400,
739 logo_width: 2011680,
740 logo_height: 438912,
741 logo_x_offset: 295125,
742 logo_y_offset: 237744,
743 },
744 );
745 doc.set_raw_header_with_images(
746 cover_banner,
747 &[("rId1", &logo_img, "logo.png")],
748 rdocx_oxml::header_footer::HdrFtrType::First,
749 );
750 doc.set_margins(
751 Length::twips(2292),
752 Length::twips(1440),
753 Length::twips(1440),
754 Length::twips(1440),
755 );
756 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
757 doc.set_footer("Tensorbee — Confidential");
758
759 // Custom styles
760 doc.add_style(
761 StyleBuilder::paragraph("ProposalTitle", "Proposal Title")
762 .based_on("Normal")
763 .run_properties(rdocx_oxml::properties::CT_RPr {
764 bold: Some(true),
765 font_ascii: Some("Georgia".to_string()),
766 font_hansi: Some("Georgia".to_string()),
767 sz: Some(rdocx_oxml::HalfPoint(56)), // half-points → 28pt
768 color: Some("1B2A4A".to_string()),
769 ..Default::default()
770 }),
771 );
772
773 // ── Cover Page ──
774 for _ in 0..4 {
775 doc.add_paragraph("");
776 }
777 doc.add_paragraph("AI Platform Modernization")
778 .style("ProposalTitle")
779 .alignment(Alignment::Center);
780 doc.add_paragraph("")
781 .alignment(Alignment::Center)
782 .border_bottom(BorderStyle::Single, 8, "C5922E");
783 {
784 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
785 p.add_run("Prepared for Global Financial Corp.")
786 .size(14.0)
787 .color("666666")
788 .italic(true);
789 }
790 {
791 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
792 p.add_run("Prepared by: Walter White, CEO — Tensorbee")
793 .size(12.0)
794 .color("888888");
795 }
796 {
797 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
798 p.add_run("February 22, 2026").size(12.0).color("888888");
799 }
800
801 // ── TOC ──
802 doc.add_paragraph("").page_break_before(true);
803 doc.add_paragraph("Table of Contents").style("Heading1");
804 // We'll add a manual-style TOC since the insert_toc goes at a specific position
805 doc.insert_toc(doc.content_count(), 2);
806
807 // ── Executive Summary ──
808 doc.add_paragraph("").page_break_before(true);
809 doc.add_paragraph("Executive Summary").style("Heading1");
810 doc.add_paragraph(
811 "Tensorbee proposes a comprehensive modernization of Global Financial Corp's AI platform, \
812 transitioning from legacy batch-processing systems to a real-time inference architecture. \
813 This transformation will reduce model deployment time from weeks to hours, improve \
814 prediction accuracy by an estimated 23%, and deliver $4.2M in annual operational savings.",
815 )
816 .first_line_indent(Length::inches(0.3));
817 doc.add_paragraph(
818 "The project spans three phases over 18 months, with a total investment of $2.8M. \
819 Tensorbee brings deep expertise in ML infrastructure, having successfully completed \
820 similar transformations for three Fortune 500 financial institutions.",
821 )
822 .first_line_indent(Length::inches(0.3));
823
824 // Key metrics table
825 doc.add_paragraph("");
826 {
827 let mut tbl = doc
828 .add_table(5, 2)
829 .borders(BorderStyle::Single, 4, "1B2A4A")
830 .width_pct(80.0);
831 tbl.cell(0, 0).unwrap().set_text("Key Metric");
832 tbl.cell(0, 0).unwrap().shading("1B2A4A");
833 tbl.cell(0, 1).unwrap().set_text("Value");
834 tbl.cell(0, 1).unwrap().shading("1B2A4A");
835 let rows = [
836 ("Total Investment", "$2.8M"),
837 ("Annual Savings", "$4.2M"),
838 ("ROI (Year 1)", "150%"),
839 ("Timeline", "18 months"),
840 ];
841 for (i, (k, v)) in rows.iter().enumerate() {
842 tbl.cell(i + 1, 0).unwrap().set_text(k);
843 if i % 2 == 0 {
844 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
845 }
846 tbl.cell(i + 1, 1).unwrap().set_text(v);
847 if i % 2 == 0 {
848 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
849 }
850 }
851 }
852
853 // ── Problem Statement ──
854 doc.add_paragraph("").page_break_before(true);
855 doc.add_paragraph("Problem Statement").style("Heading1");
856 doc.add_paragraph(
857 "Global Financial Corp's current AI infrastructure faces three critical challenges:",
858 );
859 doc.add_numbered_list_item("Deployment Latency — New models take 3-4 weeks to deploy to production, compared to the industry benchmark of 2-3 days.", 0);
860 doc.add_numbered_list_item("Scalability Constraints — The batch processing architecture cannot handle real-time inference demands during peak trading hours.", 0);
861 doc.add_numbered_list_item("Technical Debt — Legacy Python 2.7 codebases and manual deployment scripts create reliability risks and slow iteration speed.", 0);
862
863 // ── Proposed Solution ──
864 doc.add_paragraph("").page_break_before(true);
865 doc.add_paragraph("Proposed Solution").style("Heading1");
866
867 doc.add_paragraph("Phase 1: Foundation (Months 1-6)")
868 .style("Heading2");
869 doc.add_bullet_list_item("Deploy Kubernetes-based ML serving infrastructure", 0);
870 doc.add_bullet_list_item("Implement CI/CD pipeline for model deployment", 0);
871 doc.add_bullet_list_item("Migrate top 5 production models to new platform", 0);
872
873 doc.add_paragraph("Phase 2: Expansion (Months 7-12)")
874 .style("Heading2");
875 doc.add_bullet_list_item(
876 "Real-time feature engineering pipeline (Apache Kafka + Flink)",
877 0,
878 );
879 doc.add_bullet_list_item("A/B testing framework for model variants", 0);
880 doc.add_bullet_list_item("Automated model monitoring and drift detection", 0);
881
882 doc.add_paragraph("Phase 3: Optimization (Months 13-18)")
883 .style("Heading2");
884 doc.add_bullet_list_item("GPU inference optimization (TensorRT/ONNX Runtime)", 0);
885 doc.add_bullet_list_item("Multi-region deployment for latency reduction", 0);
886 doc.add_bullet_list_item(
887 "Self-service model deployment portal for data science teams",
888 0,
889 );
890
891 // ── Budget ──
892 doc.add_paragraph("Budget Breakdown").style("Heading1");
893 {
894 let mut tbl = doc
895 .add_table(6, 3)
896 .borders(BorderStyle::Single, 4, "1B2A4A")
897 .width_pct(100.0);
898 tbl.cell(0, 0).unwrap().set_text("Category");
899 tbl.cell(0, 0).unwrap().shading("1B2A4A");
900 tbl.cell(0, 1).unwrap().set_text("Cost");
901 tbl.cell(0, 1).unwrap().shading("1B2A4A");
902 tbl.cell(0, 2).unwrap().set_text("Notes");
903 tbl.cell(0, 2).unwrap().shading("1B2A4A");
904 let items = [
905 (
906 "Infrastructure",
907 "$450,000",
908 "Cloud compute + GPU instances",
909 ),
910 ("Engineering Services", "$1,600,000", "12 FTE x 18 months"),
911 (
912 "Software Licenses",
913 "$350,000",
914 "Kafka, monitoring, CI/CD tools",
915 ),
916 (
917 "Training & Enablement",
918 "$200,000",
919 "Team training, documentation",
920 ),
921 ("Contingency (10%)", "$200,000", "Risk buffer"),
922 ];
923 for (i, (cat, cost, note)) in items.iter().enumerate() {
924 tbl.cell(i + 1, 0).unwrap().set_text(cat);
925 tbl.cell(i + 1, 1).unwrap().set_text(cost);
926 tbl.cell(i + 1, 2).unwrap().set_text(note);
927 if i % 2 == 0 {
928 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
929 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
930 tbl.cell(i + 1, 2).unwrap().shading("F4F1EB");
931 }
932 }
933 }
934
935 // ── Closing ──
936 doc.add_paragraph("");
937 doc.add_paragraph("Next Steps").style("Heading1");
938 doc.add_numbered_list_item(
939 "Schedule technical deep-dive with GFC engineering team (Week 1)",
940 0,
941 );
942 doc.add_numbered_list_item("Finalize scope and sign SOW (Week 2-3)", 0);
943 doc.add_numbered_list_item("Kick off Phase 1 with joint planning session (Week 4)", 0);
944
945 doc.add_paragraph("");
946 {
947 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
948 p.add_run("Walter White")
949 .bold(true)
950 .size(14.0)
951 .color("1B2A4A");
952 }
953 {
954 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
955 p.add_run("CEO, Tensorbee | walter@tensorbee.com")
956 .size(10.0)
957 .color("888888");
958 }
959
960 doc
961}
962
963// =============================================================================
964// 3. QUOTE / BILL OF MATERIALS — Teal + orange scheme
965// =============================================================================
966fn generate_quote(_samples_dir: &Path) -> Document {
967 let mut doc = Document::new();
968
969 // Colors: Teal #008B8B, Dark #1A3C3C, Orange #E07020, Light #F0F8F8
970 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
971 doc.set_margins(
972 Length::inches(0.75),
973 Length::inches(0.75),
974 Length::inches(0.75),
975 Length::inches(0.75),
976 );
977 doc.set_title("Quotation QT-2026-0147");
978 doc.set_author("Walter White");
979 doc.set_keywords("quote, BOM, Tensorbee");
980
981 doc.set_header("Tensorbee — Quotation");
982 doc.set_footer("QT-2026-0147 | Page");
983
984 // ── Header Block ──
985 {
986 let mut p = doc.add_paragraph("").alignment(Alignment::Left);
987 p.add_run("TENSORBEE")
988 .bold(true)
989 .size(28.0)
990 .color("008B8B")
991 .font("Helvetica");
992 }
993 doc.add_paragraph("123 Innovation Drive, Suite 400")
994 .alignment(Alignment::Left);
995 doc.add_paragraph("San Francisco, CA 94105 | +1 (415) 555-0199")
996 .alignment(Alignment::Left);
997 doc.add_paragraph("")
998 .border_bottom(BorderStyle::Single, 8, "008B8B");
999
1000 // Quote meta
1001 doc.add_paragraph("");
1002 {
1003 let mut tbl = doc.add_table(4, 4).width_pct(100.0);
1004 tbl.cell(0, 0).unwrap().set_text("QUOTATION");
1005 tbl.cell(0, 0).unwrap().shading("008B8B").grid_span(2);
1006 tbl.cell(0, 2).unwrap().set_text("DATE");
1007 tbl.cell(0, 2).unwrap().shading("008B8B");
1008 tbl.cell(0, 3).unwrap().set_text("VALID UNTIL");
1009 tbl.cell(0, 3).unwrap().shading("008B8B");
1010 tbl.cell(1, 0).unwrap().set_text("Quote #:");
1011 tbl.cell(1, 1).unwrap().set_text("QT-2026-0147");
1012 tbl.cell(1, 2).unwrap().set_text("Feb 22, 2026");
1013 tbl.cell(1, 3).unwrap().set_text("Mar 22, 2026");
1014 tbl.cell(2, 0).unwrap().set_text("Prepared by:");
1015 tbl.cell(2, 1).unwrap().set_text("Walter White");
1016 tbl.cell(2, 2).unwrap().set_text("Payment:");
1017 tbl.cell(2, 3).unwrap().set_text("Net 30");
1018 tbl.cell(3, 0).unwrap().set_text("Customer:");
1019 tbl.cell(3, 1).unwrap().set_text("Meridian Dynamics LLC");
1020 tbl.cell(3, 2).unwrap().set_text("Currency:");
1021 tbl.cell(3, 3).unwrap().set_text("USD");
1022 }
1023
1024 doc.add_paragraph("");
1025
1026 // ── Bill of Materials ──
1027 {
1028 let mut p = doc.add_paragraph("");
1029 p.add_run("Bill of Materials")
1030 .bold(true)
1031 .size(16.0)
1032 .color("1A3C3C");
1033 }
1034 doc.add_paragraph("");
1035
1036 {
1037 let mut tbl = doc
1038 .add_table(10, 6)
1039 .borders(BorderStyle::Single, 2, "008B8B")
1040 .width_pct(100.0)
1041 .layout_fixed();
1042 // Headers
1043 let hdrs = [
1044 "#",
1045 "Part Number",
1046 "Description",
1047 "Qty",
1048 "Unit Price",
1049 "Total",
1050 ];
1051 for (c, h) in hdrs.iter().enumerate() {
1052 tbl.cell(0, c).unwrap().set_text(h);
1053 tbl.cell(0, c).unwrap().shading("008B8B");
1054 }
1055
1056 let items: Vec<(&str, &str, &str, &str, &str)> = vec![
1057 (
1058 "TB-GPU-A100",
1059 "NVIDIA A100 80GB GPU",
1060 "4",
1061 "$12,500.00",
1062 "$50,000.00",
1063 ),
1064 (
1065 "TB-SRV-R750",
1066 "Dell R750xa Server Chassis",
1067 "2",
1068 "$8,200.00",
1069 "$16,400.00",
1070 ),
1071 (
1072 "TB-RAM-256G",
1073 "256GB DDR5 ECC Memory Module",
1074 "8",
1075 "$890.00",
1076 "$7,120.00",
1077 ),
1078 (
1079 "TB-SSD-3840",
1080 "3.84TB NVMe U.2 SSD",
1081 "8",
1082 "$1,150.00",
1083 "$9,200.00",
1084 ),
1085 (
1086 "TB-NET-CX7",
1087 "ConnectX-7 200GbE NIC",
1088 "4",
1089 "$1,800.00",
1090 "$7,200.00",
1091 ),
1092 (
1093 "TB-SW-48P",
1094 "48-Port 100GbE Switch",
1095 "1",
1096 "$22,000.00",
1097 "$22,000.00",
1098 ),
1099 (
1100 "TB-CAB-RACK",
1101 "42U Server Rack + PDU",
1102 "1",
1103 "$4,500.00",
1104 "$4,500.00",
1105 ),
1106 (
1107 "TB-SVC-INST",
1108 "Installation & Configuration",
1109 "1",
1110 "$8,500.00",
1111 "$8,500.00",
1112 ),
1113 (
1114 "TB-SVC-SUPP",
1115 "3-Year Premium Support",
1116 "1",
1117 "$15,000.00",
1118 "$15,000.00",
1119 ),
1120 ];
1121
1122 for (i, (pn, desc, qty, unit, total)) in items.iter().enumerate() {
1123 let row = i + 1;
1124 tbl.cell(row, 0).unwrap().set_text(&format!("{}", i + 1));
1125 tbl.cell(row, 1).unwrap().set_text(pn);
1126 tbl.cell(row, 2).unwrap().set_text(desc);
1127 tbl.cell(row, 3).unwrap().set_text(qty);
1128 tbl.cell(row, 4).unwrap().set_text(unit);
1129 tbl.cell(row, 5).unwrap().set_text(total);
1130 if i % 2 == 0 {
1131 for c in 0..6 {
1132 tbl.cell(row, c).unwrap().shading("F0F8F8");
1133 }
1134 }
1135 }
1136 }
1137
1138 doc.add_paragraph("");
1139
1140 // ── Totals ──
1141 {
1142 let mut tbl = doc
1143 .add_table(4, 2)
1144 .borders(BorderStyle::Single, 2, "008B8B")
1145 .width(Length::inches(3.5))
1146 .alignment(Alignment::Right);
1147 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1148 tbl.cell(0, 1).unwrap().set_text("$139,920.00");
1149 tbl.cell(1, 0).unwrap().set_text("Shipping & Handling");
1150 tbl.cell(1, 1).unwrap().set_text("$2,500.00");
1151 tbl.cell(2, 0).unwrap().set_text("Tax (8.625%)");
1152 tbl.cell(2, 1).unwrap().set_text("$12,068.10");
1153 tbl.cell(3, 0).unwrap().set_text("TOTAL");
1154 tbl.cell(3, 0).unwrap().shading("E07020");
1155 tbl.cell(3, 1).unwrap().set_text("$154,488.10");
1156 tbl.cell(3, 1).unwrap().shading("E07020");
1157 }
1158
1159 doc.add_paragraph("");
1160
1161 // ── Terms & Conditions ──
1162 {
1163 let mut p = doc.add_paragraph("");
1164 p.add_run("Terms & Conditions")
1165 .bold(true)
1166 .size(14.0)
1167 .color("1A3C3C");
1168 }
1169 doc.add_numbered_list_item(
1170 "This quotation is valid for 30 calendar days from the date of issue.",
1171 0,
1172 );
1173 doc.add_numbered_list_item(
1174 "All prices are in USD and exclusive of applicable taxes unless stated.",
1175 0,
1176 );
1177 doc.add_numbered_list_item("Standard lead time is 4-6 weeks from PO receipt.", 0);
1178 doc.add_numbered_list_item(
1179 "Payment terms: Net 30 from invoice date. 2% discount for payment within 10 days.",
1180 0,
1181 );
1182 doc.add_numbered_list_item(
1183 "Warranty: 3-year manufacturer warranty on all hardware components.",
1184 0,
1185 );
1186 doc.add_numbered_list_item(
1187 "Returns subject to 15% restocking fee if initiated after 14 days.",
1188 0,
1189 );
1190
1191 doc.add_paragraph("");
1192 doc.add_paragraph("")
1193 .border_bottom(BorderStyle::Single, 4, "008B8B");
1194 doc.add_paragraph("");
1195 {
1196 let mut p = doc.add_paragraph("");
1197 p.add_run("Accepted by: ").bold(true);
1198 p.add_run("___________________________ Date: ___________");
1199 }
1200 {
1201 let mut p = doc.add_paragraph("");
1202 p.add_run("Print Name: ").bold(true);
1203 p.add_run("___________________________ Title: ___________");
1204 }
1205
1206 doc
1207}
1208
1209// =============================================================================
1210// 4. INVOICE — Crimson + charcoal scheme
1211// =============================================================================
1212fn generate_invoice(_samples_dir: &Path) -> Document {
1213 let mut doc = Document::new();
1214
1215 // Colors: Crimson #B22222, Charcoal #333333, Light #FAF0F0
1216 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1217 doc.set_margins(
1218 Length::inches(0.75),
1219 Length::inches(0.75),
1220 Length::inches(0.75),
1221 Length::inches(0.75),
1222 );
1223 doc.set_title("Invoice INV-2026-0392");
1224 doc.set_author("Walter White");
1225 doc.set_keywords("invoice, Tensorbee");
1226
1227 doc.set_footer("Tensorbee — Thank you for your business!");
1228
1229 // ── Company & Invoice Header ──
1230 {
1231 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1232 // Company name left, INVOICE right
1233 {
1234 let mut cell = tbl.cell(0, 0).unwrap();
1235 let mut p = cell.add_paragraph("");
1236 p.add_run("TENSORBEE")
1237 .bold(true)
1238 .size(32.0)
1239 .color("B22222")
1240 .font("Helvetica");
1241 }
1242 {
1243 let mut cell = tbl.cell(0, 1).unwrap();
1244 let mut p = cell.add_paragraph("");
1245 p.add_run("INVOICE").bold(true).size(32.0).color("333333");
1246 }
1247 tbl.cell(1, 0)
1248 .unwrap()
1249 .set_text("123 Innovation Drive, Suite 400");
1250 tbl.cell(1, 1).unwrap().set_text("Invoice #: INV-2026-0392");
1251 tbl.cell(2, 0).unwrap().set_text("San Francisco, CA 94105");
1252 tbl.cell(2, 1).unwrap().set_text("Date: February 22, 2026");
1253 tbl.cell(3, 0).unwrap().set_text("walter@tensorbee.com");
1254 tbl.cell(3, 1).unwrap().set_text("Due Date: March 24, 2026");
1255 }
1256
1257 doc.add_paragraph("")
1258 .border_bottom(BorderStyle::Thick, 8, "B22222");
1259 doc.add_paragraph("");
1260
1261 // ── Bill To / Ship To ──
1262 {
1263 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1264 tbl.cell(0, 0).unwrap().set_text("BILL TO");
1265 tbl.cell(0, 0).unwrap().shading("B22222");
1266 tbl.cell(0, 1).unwrap().set_text("SHIP TO");
1267 tbl.cell(0, 1).unwrap().shading("B22222");
1268 tbl.cell(1, 0).unwrap().set_text("Meridian Dynamics LLC");
1269 tbl.cell(1, 1).unwrap().set_text("Meridian Dynamics LLC");
1270 tbl.cell(2, 0).unwrap().set_text("456 Enterprise Blvd");
1271 tbl.cell(2, 1).unwrap().set_text("Attn: Server Room B");
1272 tbl.cell(3, 0).unwrap().set_text("Austin, TX 78701");
1273 tbl.cell(3, 1)
1274 .unwrap()
1275 .set_text("456 Enterprise Blvd, Austin, TX 78701");
1276 }
1277
1278 doc.add_paragraph("");
1279
1280 // ── Line Items ──
1281 {
1282 let mut tbl = doc
1283 .add_table(8, 5)
1284 .borders(BorderStyle::Single, 2, "B22222")
1285 .width_pct(100.0);
1286 let hdrs = ["Description", "Qty", "Unit Price", "Tax", "Amount"];
1287 for (c, h) in hdrs.iter().enumerate() {
1288 tbl.cell(0, c).unwrap().set_text(h);
1289 tbl.cell(0, c).unwrap().shading("B22222");
1290 }
1291 let items = [
1292 (
1293 "ML Infrastructure Setup — Phase 1",
1294 "1",
1295 "$45,000.00",
1296 "$3,881.25",
1297 "$48,881.25",
1298 ),
1299 (
1300 "Data Pipeline Development (160 hrs)",
1301 "160",
1302 "$225.00",
1303 "$3,105.00",
1304 "$39,105.00",
1305 ),
1306 (
1307 "GPU Cluster Configuration",
1308 "1",
1309 "$12,000.00",
1310 "$1,035.00",
1311 "$13,035.00",
1312 ),
1313 (
1314 "API Gateway Implementation",
1315 "1",
1316 "$18,500.00",
1317 "$1,595.63",
1318 "$20,095.63",
1319 ),
1320 (
1321 "Load Testing & QA (80 hrs)",
1322 "80",
1323 "$195.00",
1324 "$1,345.50",
1325 "$16,945.50",
1326 ),
1327 (
1328 "Documentation & Training",
1329 "1",
1330 "$8,000.00",
1331 "$690.00",
1332 "$8,690.00",
1333 ),
1334 (
1335 "Project Management (3 months)",
1336 "3",
1337 "$6,500.00",
1338 "$1,679.63",
1339 "$21,179.63",
1340 ),
1341 ];
1342 for (i, (desc, qty, unit, tax, amt)) in items.iter().enumerate() {
1343 let r = i + 1;
1344 tbl.cell(r, 0).unwrap().set_text(desc);
1345 tbl.cell(r, 1).unwrap().set_text(qty);
1346 tbl.cell(r, 2).unwrap().set_text(unit);
1347 tbl.cell(r, 3).unwrap().set_text(tax);
1348 tbl.cell(r, 4).unwrap().set_text(amt);
1349 if i % 2 == 0 {
1350 for c in 0..5 {
1351 tbl.cell(r, c).unwrap().shading("FAF0F0");
1352 }
1353 }
1354 }
1355 }
1356
1357 doc.add_paragraph("");
1358
1359 // ── Totals ──
1360 {
1361 let mut tbl = doc
1362 .add_table(4, 2)
1363 .borders(BorderStyle::Single, 2, "B22222")
1364 .width(Length::inches(3.0))
1365 .alignment(Alignment::Right);
1366 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1367 tbl.cell(0, 1).unwrap().set_text("$154,600.00");
1368 tbl.cell(1, 0).unwrap().set_text("Tax (8.625%)");
1369 tbl.cell(1, 1).unwrap().set_text("$13,334.25");
1370 tbl.cell(2, 0).unwrap().set_text("Discount (5%)");
1371 tbl.cell(2, 1).unwrap().set_text("-$7,730.00");
1372 tbl.cell(3, 0).unwrap().set_text("AMOUNT DUE");
1373 tbl.cell(3, 0).unwrap().shading("B22222");
1374 tbl.cell(3, 1).unwrap().set_text("$160,204.25");
1375 tbl.cell(3, 1).unwrap().shading("B22222");
1376 }
1377
1378 doc.add_paragraph("");
1379 doc.add_paragraph("");
1380
1381 // ── Payment Details ──
1382 {
1383 let mut p = doc.add_paragraph("");
1384 p.add_run("Payment Details")
1385 .bold(true)
1386 .size(14.0)
1387 .color("333333");
1388 }
1389 doc.add_paragraph("Bank: Silicon Valley Bank")
1390 .indent_left(Length::inches(0.3));
1391 doc.add_paragraph("Account: Tensorbee Inc. — 0847-2953-1120")
1392 .indent_left(Length::inches(0.3));
1393 doc.add_paragraph("Routing: 121140399")
1394 .indent_left(Length::inches(0.3));
1395 doc.add_paragraph("Swift: SVBKUS6S")
1396 .indent_left(Length::inches(0.3));
1397
1398 doc.add_paragraph("");
1399
1400 doc.add_paragraph("Please include invoice number INV-2026-0392 in the payment reference.")
1401 .shading("FAF0F0")
1402 .border_all(BorderStyle::Single, 2, "B22222");
1403
1404 doc
1405}
1406
1407// =============================================================================
1408// 5. REPORT — Forest green + earth tones, with images & hierarchical sections
1409// =============================================================================
1410fn generate_report(_samples_dir: &Path) -> Document {
1411 let mut doc = Document::new();
1412
1413 // Colors: Forest #2D5016, Sage #6B8E23, Earth #8B7355, Cream #FFFAF0
1414 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1415 doc.set_margins(
1416 Length::inches(1.0),
1417 Length::inches(1.0),
1418 Length::inches(1.0),
1419 Length::inches(1.0),
1420 );
1421 doc.set_title("Q4 2025 Environmental Impact Report");
1422 doc.set_author("Walter White");
1423 doc.set_subject("Quarterly Environmental Report");
1424 doc.set_keywords("environment, sustainability, report, Tensorbee");
1425
1426 doc.set_different_first_page(true);
1427 doc.set_first_page_header("");
1428 doc.set_header("Tensorbee — Q4 2025 Environmental Impact Report");
1429 doc.set_footer("CONFIDENTIAL — Page");
1430
1431 // ── Cover ──
1432 let cover_bg = create_sample_png(612, 792, [20, 50, 15]);
1433 doc.add_background_image(&cover_bg, "report_cover.png");
1434
1435 for _ in 0..5 {
1436 doc.add_paragraph("");
1437 }
1438 {
1439 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1440 p.add_run("Q4 2025")
1441 .bold(true)
1442 .size(48.0)
1443 .color("FFFFFF")
1444 .font("Georgia");
1445 }
1446 {
1447 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1448 p.add_run("Environmental Impact Report")
1449 .size(24.0)
1450 .color("90EE90")
1451 .font("Georgia")
1452 .italic(true);
1453 }
1454 doc.add_paragraph("");
1455 {
1456 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1457 p.add_run("Tensorbee — Sustainability Division")
1458 .size(14.0)
1459 .color("C0C0C0");
1460 }
1461 {
1462 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1463 p.add_run("Prepared by Walter White, Chief Sustainability Officer")
1464 .size(11.0)
1465 .color("AAAAAA");
1466 }
1467
1468 // ── TOC ──
1469 doc.add_paragraph("").page_break_before(true);
1470 doc.insert_toc(doc.content_count(), 3);
1471
1472 // ── Section 1: Executive Overview ──
1473 doc.add_paragraph("").page_break_before(true);
1474 doc.add_paragraph("Executive Overview").style("Heading1");
1475 doc.add_paragraph(
1476 "This report presents Tensorbee's environmental performance for Q4 2025. Our \
1477 sustainability initiatives have yielded a 34% reduction in carbon emissions compared \
1478 to Q4 2024, exceeding our target of 25%. Key achievements include the transition to \
1479 100% renewable energy in our primary data centers and the launch of our carbon offset \
1480 marketplace.",
1481 )
1482 .first_line_indent(Length::inches(0.3));
1483
1484 // Image: performance chart
1485 let chart_img = create_chart_png(400, 200);
1486 doc.add_paragraph("");
1487 doc.add_picture(
1488 &chart_img,
1489 "performance_chart.png",
1490 Length::inches(5.0),
1491 Length::inches(2.5),
1492 )
1493 .alignment(Alignment::Center);
1494 {
1495 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1496 p.add_run("Figure 1: Quarterly Carbon Emissions (tonnes CO2e)")
1497 .italic(true)
1498 .size(9.0)
1499 .color("666666");
1500 }
1501
1502 // ── Section 2: Energy Consumption ──
1503 doc.add_paragraph("").page_break_before(true);
1504 doc.add_paragraph("Energy Consumption").style("Heading1");
1505
1506 doc.add_paragraph("Data Center Operations")
1507 .style("Heading2");
1508 doc.add_paragraph(
1509 "Our three primary data centers consumed a combined 4.2 GWh in Q4 2025, \
1510 a 12% reduction from Q3 2025 driven by improved cooling efficiency and \
1511 server consolidation.",
1512 );
1513
1514 // Data table
1515 {
1516 let mut tbl = doc
1517 .add_table(5, 4)
1518 .borders(BorderStyle::Single, 4, "2D5016")
1519 .width_pct(100.0);
1520 let hdrs = ["Data Center", "Capacity (MW)", "Usage (GWh)", "PUE"];
1521 for (c, h) in hdrs.iter().enumerate() {
1522 tbl.cell(0, c).unwrap().set_text(h);
1523 tbl.cell(0, c).unwrap().shading("2D5016");
1524 }
1525 let rows = [
1526 ("San Francisco (Primary)", "3.2", "1.8", "1.12"),
1527 ("Dublin (EU)", "2.1", "1.2", "1.18"),
1528 ("Singapore (APAC)", "1.8", "1.2", "1.24"),
1529 ("TOTAL", "7.1", "4.2", "1.17 avg"),
1530 ];
1531 for (i, (dc, cap, use_, pue)) in rows.iter().enumerate() {
1532 tbl.cell(i + 1, 0).unwrap().set_text(dc);
1533 tbl.cell(i + 1, 1).unwrap().set_text(cap);
1534 tbl.cell(i + 1, 2).unwrap().set_text(use_);
1535 tbl.cell(i + 1, 3).unwrap().set_text(pue);
1536 if i == 3 {
1537 for c in 0..4 {
1538 tbl.cell(i + 1, c).unwrap().shading("E2EFDA");
1539 }
1540 }
1541 }
1542 }
1543
1544 doc.add_paragraph("");
1545
1546 doc.add_paragraph("Renewable Energy Mix").style("Heading2");
1547 doc.add_paragraph("Breakdown of energy sources across all facilities:");
1548
1549 doc.add_bullet_list_item("Solar PV: 42% (1.76 GWh)", 0);
1550 doc.add_bullet_list_item("Wind Power Purchase Agreements: 38% (1.60 GWh)", 0);
1551 doc.add_bullet_list_item("Hydroelectric: 12% (0.50 GWh)", 0);
1552 doc.add_bullet_list_item("Grid (non-renewable): 8% (0.34 GWh)", 0);
1553
1554 // ── Section 3: Water & Waste ──
1555 doc.add_paragraph("Water & Waste Management")
1556 .style("Heading1");
1557
1558 doc.add_paragraph("Water Usage").style("Heading2");
1559 doc.add_paragraph(
1560 "Total water consumption was 12.4 million gallons, a 15% reduction from Q3. \
1561 Our closed-loop cooling systems now recycle 78% of water used in cooling operations.",
1562 );
1563
1564 doc.add_paragraph("Waste Reduction").style("Heading2");
1565 doc.add_paragraph("E-waste management results for Q4:")
1566 .keep_with_next(true);
1567 {
1568 let mut tbl = doc
1569 .add_table(4, 3)
1570 .borders(BorderStyle::Single, 2, "6B8E23")
1571 .width_pct(80.0);
1572 tbl.cell(0, 0).unwrap().set_text("Category");
1573 tbl.cell(0, 0).unwrap().shading("6B8E23");
1574 tbl.cell(0, 1).unwrap().set_text("Weight (kg)");
1575 tbl.cell(0, 1).unwrap().shading("6B8E23");
1576 tbl.cell(0, 2).unwrap().set_text("Recycled %");
1577 tbl.cell(0, 2).unwrap().shading("6B8E23");
1578 let rows = [
1579 ("Server Hardware", "2,450", "94%"),
1580 ("Networking Equipment", "820", "91%"),
1581 ("Storage Media", "340", "99%"),
1582 ];
1583 for (i, (cat, wt, pct)) in rows.iter().enumerate() {
1584 tbl.cell(i + 1, 0).unwrap().set_text(cat);
1585 tbl.cell(i + 1, 1).unwrap().set_text(wt);
1586 tbl.cell(i + 1, 2).unwrap().set_text(pct);
1587 }
1588 }
1589
1590 // ── Section 4: Initiatives ──
1591 doc.add_paragraph("").page_break_before(true);
1592 doc.add_paragraph("Q1 2026 Initiatives").style("Heading1");
1593
1594 doc.add_paragraph("Planned Programs").style("Heading2");
1595 doc.add_numbered_list_item(
1596 "Deploy on-site battery storage at SF data center (2 MWh capacity)",
1597 0,
1598 );
1599 doc.add_numbered_list_item("Pilot immersion cooling in Dublin facility", 0);
1600 doc.add_numbered_list_item("Launch employee commute carbon offset program", 0);
1601 doc.add_numbered_list_item("Achieve ISO 14001 certification for Singapore facility", 0);
1602
1603 doc.add_paragraph("Investment Targets").style("Heading2");
1604 doc.add_paragraph("Sustainability CapEx allocation for FY2026:")
1605 .keep_with_next(true);
1606 {
1607 let mut tbl = doc
1608 .add_table(5, 2)
1609 .borders(BorderStyle::Single, 4, "2D5016");
1610 tbl.cell(0, 0).unwrap().set_text("Initiative");
1611 tbl.cell(0, 0).unwrap().shading("2D5016");
1612 tbl.cell(0, 1).unwrap().set_text("Budget");
1613 tbl.cell(0, 1).unwrap().shading("2D5016");
1614 let rows = [
1615 ("Battery Storage", "$1.2M"),
1616 ("Immersion Cooling Pilot", "$800K"),
1617 ("Solar Panel Expansion", "$2.1M"),
1618 ("Carbon Credits", "$500K"),
1619 ];
1620 for (i, (init, budget)) in rows.iter().enumerate() {
1621 tbl.cell(i + 1, 0).unwrap().set_text(init);
1622 tbl.cell(i + 1, 1).unwrap().set_text(budget);
1623 if i % 2 == 0 {
1624 tbl.cell(i + 1, 0).unwrap().shading("FFFAF0");
1625 tbl.cell(i + 1, 1).unwrap().shading("FFFAF0");
1626 }
1627 }
1628 }
1629
1630 // Image: sustainability roadmap
1631 let roadmap_img = create_sample_png(500, 100, [40, 80, 30]);
1632 doc.add_paragraph("");
1633 doc.add_picture(
1634 &roadmap_img,
1635 "roadmap.png",
1636 Length::inches(6.0),
1637 Length::inches(1.2),
1638 )
1639 .alignment(Alignment::Center);
1640 {
1641 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1642 p.add_run("Figure 2: 2026 Sustainability Roadmap")
1643 .italic(true)
1644 .size(9.0)
1645 .color("666666");
1646 }
1647
1648 doc.add_paragraph("");
1649 doc.add_paragraph(
1650 "For questions about this report, contact Walter White at sustainability@tensorbee.com.",
1651 )
1652 .shading("FFFAF0")
1653 .border_all(BorderStyle::Single, 2, "2D5016");
1654
1655 doc
1656}
1657
1658// =============================================================================
1659// 6. LETTER — Slate blue + silver scheme
1660// =============================================================================
1661fn generate_letter(_samples_dir: &Path) -> Document {
1662 let mut doc = Document::new();
1663
1664 // Colors: Slate #4A5568, Blue accent #3182CE
1665 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1666 doc.set_margins(
1667 Length::inches(1.25),
1668 Length::inches(1.25),
1669 Length::inches(1.25),
1670 Length::inches(1.25),
1671 );
1672 doc.set_title("Business Letter");
1673 doc.set_author("Walter White");
1674
1675 // Banner header using raw XML
1676 let logo_img = create_logo_png(220, 48);
1677 let banner = build_header_banner_xml(
1678 "rId1",
1679 &BannerOpts {
1680 bg_color: "4A5568",
1681 banner_width: 7772400,
1682 banner_height: 731520, // ~0.8"
1683 logo_width: 2011680,
1684 logo_height: 438912,
1685 logo_x_offset: 295125,
1686 logo_y_offset: 146304,
1687 },
1688 );
1689 doc.set_raw_header_with_images(
1690 banner,
1691 &[("rId1", &logo_img, "logo.png")],
1692 rdocx_oxml::header_footer::HdrFtrType::Default,
1693 );
1694 doc.set_margins(
1695 Length::twips(2000),
1696 Length::twips(1800),
1697 Length::twips(1440),
1698 Length::twips(1800),
1699 );
1700 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
1701
1702 // Footer with contact
1703 doc.set_footer(
1704 "Tensorbee | 123 Innovation Drive, Suite 400 | San Francisco, CA 94105 | tensorbee.com",
1705 );
1706
1707 // ── Sender Address ──
1708 doc.add_paragraph("Walter White");
1709 doc.add_paragraph("Chief Executive Officer");
1710 doc.add_paragraph("Tensorbee");
1711 doc.add_paragraph("123 Innovation Drive, Suite 400");
1712 doc.add_paragraph("San Francisco, CA 94105");
1713 doc.add_paragraph("");
1714 doc.add_paragraph("February 22, 2026");
1715
1716 doc.add_paragraph("");
1717
1718 // ── Recipient ──
1719 doc.add_paragraph("Ms. Sarah Chen");
1720 doc.add_paragraph("VP of Engineering");
1721 doc.add_paragraph("Meridian Dynamics LLC");
1722 doc.add_paragraph("456 Enterprise Boulevard");
1723 doc.add_paragraph("Austin, TX 78701");
1724
1725 doc.add_paragraph("");
1726
1727 // ── Subject ──
1728 {
1729 let mut p = doc.add_paragraph("");
1730 p.add_run("Re: Strategic Technology Partnership — Phase 2 Expansion")
1731 .bold(true);
1732 }
1733
1734 doc.add_paragraph("");
1735
1736 // ── Body ──
1737 doc.add_paragraph("Dear Ms. Chen,");
1738 doc.add_paragraph("");
1739
1740 doc.add_paragraph(
1741 "I am writing to express Tensorbee's enthusiasm for expanding our strategic technology \
1742 partnership with Meridian Dynamics. Following the successful completion of Phase 1, \
1743 which delivered a 40% improvement in your ML inference pipeline throughput, we believe \
1744 the foundation is firmly established for an ambitious Phase 2 engagement.",
1745 )
1746 .first_line_indent(Length::inches(0.5));
1747
1748 doc.add_paragraph(
1749 "During our recent executive review, your team highlighted three priority areas for \
1750 the next phase of collaboration:",
1751 )
1752 .first_line_indent(Length::inches(0.5));
1753
1754 doc.add_numbered_list_item(
1755 "Real-time anomaly detection for your financial transaction monitoring system, \
1756 targeting sub-100ms latency at 50,000 transactions per second.",
1757 0,
1758 );
1759 doc.add_numbered_list_item(
1760 "Federated learning infrastructure to enable model training across your distributed \
1761 data centers without centralizing sensitive financial data.",
1762 0,
1763 );
1764 doc.add_numbered_list_item(
1765 "MLOps automation to reduce your model deployment cycle from the current 5 days \
1766 to under 4 hours.",
1767 0,
1768 );
1769
1770 doc.add_paragraph(
1771 "Our engineering team has prepared a detailed technical proposal addressing each of \
1772 these areas. We have allocated a dedicated team of eight senior engineers, led by \
1773 our CTO, to ensure continuity with the Phase 1 team your organization has already \
1774 built a productive working relationship with.",
1775 )
1776 .first_line_indent(Length::inches(0.5));
1777
1778 doc.add_paragraph(
1779 "I would welcome the opportunity to present our Phase 2 proposal in person. My \
1780 assistant will reach out to coordinate a meeting at your convenience during the \
1781 first week of March.",
1782 )
1783 .first_line_indent(Length::inches(0.5));
1784
1785 doc.add_paragraph("");
1786
1787 doc.add_paragraph("Warm regards,");
1788 doc.add_paragraph("");
1789 doc.add_paragraph("");
1790
1791 // Signature line
1792 doc.add_paragraph("")
1793 .border_bottom(BorderStyle::Single, 4, "4A5568");
1794 {
1795 let mut p = doc.add_paragraph("");
1796 p.add_run("Walter White").bold(true).size(12.0);
1797 }
1798 {
1799 let mut p = doc.add_paragraph("");
1800 p.add_run("Chief Executive Officer, Tensorbee")
1801 .italic(true)
1802 .size(10.0)
1803 .color("666666");
1804 }
1805 {
1806 let mut p = doc.add_paragraph("");
1807 p.add_run("walter@tensorbee.com | +1 (415) 555-0199")
1808 .size(10.0)
1809 .color("888888");
1810 }
1811
1812 doc
1813}
1814
1815// =============================================================================
1816// 7. EMPLOYMENT CONTRACT — Purple + charcoal scheme
1817// =============================================================================
1818fn generate_contract(_samples_dir: &Path) -> Document {
1819 let mut doc = Document::new();
1820
1821 // Colors: Purple #5B2C6F, Plum #8E44AD, Gray #2C3E50
1822 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1823 doc.set_margins(
1824 Length::inches(1.25),
1825 Length::inches(1.0),
1826 Length::inches(1.0),
1827 Length::inches(1.0),
1828 );
1829 doc.set_title("Employment Agreement");
1830 doc.set_author("Tensorbee HR Department");
1831 doc.set_subject("Employment Contract — Walter White");
1832 doc.set_keywords("employment, contract, agreement, Tensorbee");
1833
1834 doc.set_header("TENSORBEE — Employment Agreement");
1835 doc.set_footer("Employment Agreement — Walter White — Page");
1836
1837 // ── Title Block ──
1838 doc.add_paragraph("")
1839 .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1840 {
1841 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1842 p.add_run("EMPLOYMENT AGREEMENT")
1843 .bold(true)
1844 .size(24.0)
1845 .color("5B2C6F")
1846 .font("Georgia");
1847 }
1848 doc.add_paragraph("")
1849 .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1850 doc.add_paragraph("");
1851
1852 // ── Parties ──
1853 doc.add_paragraph(
1854 "This Employment Agreement (\"Agreement\") is entered into as of February 22, 2026 \
1855 (\"Effective Date\"), by and between:",
1856 );
1857
1858 doc.add_paragraph("");
1859
1860 {
1861 let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1862 p.add_run("EMPLOYER: ").bold(true);
1863 p.add_run(
1864 "Tensorbee, Inc., a Delaware corporation with its principal place of business at \
1865 123 Innovation Drive, Suite 400, San Francisco, CA 94105 (\"Company\")",
1866 );
1867 }
1868 doc.add_paragraph("");
1869 {
1870 let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1871 p.add_run("EMPLOYEE: ").bold(true);
1872 p.add_run(
1873 "Walter White, residing at 308 Negra Arroyo Lane, Albuquerque, NM 87104 (\"Employee\")",
1874 );
1875 }
1876
1877 doc.add_paragraph("");
1878 doc.add_paragraph("The Company and Employee are collectively referred to as the \"Parties.\"");
1879 doc.add_paragraph("");
1880
1881 // ── Article 1: Position and Duties ──
1882 doc.add_paragraph("Article 1 — Position and Duties")
1883 .style("Heading1");
1884 {
1885 let mut p = doc.add_paragraph("");
1886 p.add_run("1.1 ").bold(true);
1887 p.add_run("The Company hereby employs the Employee as ");
1888 p.add_run("Chief Executive Officer (CEO)")
1889 .bold(true)
1890 .italic(true);
1891 p.add_run(", reporting directly to the Board of Directors.");
1892 }
1893 {
1894 let mut p = doc.add_paragraph("");
1895 p.add_run("1.2 ").bold(true);
1896 p.add_run("The Employee shall devote full working time, attention, and best efforts to \
1897 the performance of duties as reasonably assigned by the Board, including but not limited to:");
1898 }
1899 doc.add_bullet_list_item(
1900 "Setting and executing the Company's strategic vision and business plan",
1901 0,
1902 );
1903 doc.add_bullet_list_item(
1904 "Overseeing all operations, engineering, and commercial activities",
1905 0,
1906 );
1907 doc.add_bullet_list_item(
1908 "Representing the Company to investors, customers, and the public",
1909 0,
1910 );
1911 doc.add_bullet_list_item("Recruiting, developing, and retaining key talent", 0);
1912
1913 {
1914 let mut p = doc.add_paragraph("");
1915 p.add_run("1.3 ").bold(true);
1916 p.add_run(
1917 "The Employee's primary work location shall be the Company's San Francisco \
1918 headquarters, with reasonable travel as required by business needs.",
1919 );
1920 }
1921
1922 // ── Article 2: Compensation ──
1923 doc.add_paragraph("Article 2 — Compensation and Benefits")
1924 .style("Heading1");
1925 {
1926 let mut p = doc.add_paragraph("");
1927 p.add_run("2.1 Base Salary. ").bold(true);
1928 p.add_run("The Company shall pay the Employee an annual base salary of ");
1929 p.add_run("$375,000.00").bold(true);
1930 p.add_run(" (Three Hundred Seventy-Five Thousand Dollars), payable in accordance with the \
1931 Company's standard payroll schedule, less applicable withholdings and deductions.");
1932 }
1933 {
1934 let mut p = doc.add_paragraph("");
1935 p.add_run("2.2 Annual Bonus. ").bold(true);
1936 p.add_run("The Employee shall be eligible for an annual performance bonus of up to ");
1937 p.add_run("40%").bold(true);
1938 p.add_run(
1939 " of base salary, based on achievement of mutually agreed performance objectives.",
1940 );
1941 }
1942 {
1943 let mut p = doc.add_paragraph("");
1944 p.add_run("2.3 Equity. ").bold(true);
1945 p.add_run("Subject to Board approval, the Employee shall receive a stock option grant of ");
1946 p.add_run("500,000 shares").bold(true);
1947 p.add_run(
1948 " of the Company's common stock, vesting over four (4) years with a one-year cliff, \
1949 at an exercise price equal to the fair market value on the date of grant.",
1950 );
1951 }
1952
1953 // Compensation summary table
1954 doc.add_paragraph("");
1955 {
1956 let mut tbl = doc
1957 .add_table(5, 2)
1958 .borders(BorderStyle::Single, 4, "5B2C6F")
1959 .width_pct(70.0)
1960 .alignment(Alignment::Center);
1961 tbl.cell(0, 0).unwrap().set_text("Compensation Element");
1962 tbl.cell(0, 0).unwrap().shading("5B2C6F");
1963 tbl.cell(0, 1).unwrap().set_text("Value");
1964 tbl.cell(0, 1).unwrap().shading("5B2C6F");
1965 let items = [
1966 ("Base Salary", "$375,000/year"),
1967 ("Target Bonus", "Up to 40% ($150,000)"),
1968 ("Equity Grant", "500,000 shares (4yr vest)"),
1969 ("Total Target Comp", "$525,000 + equity"),
1970 ];
1971 for (i, (elem, val)) in items.iter().enumerate() {
1972 tbl.cell(i + 1, 0).unwrap().set_text(elem);
1973 tbl.cell(i + 1, 1).unwrap().set_text(val);
1974 if i == 3 {
1975 tbl.cell(i + 1, 0).unwrap().shading("E8DAEF");
1976 tbl.cell(i + 1, 1).unwrap().shading("E8DAEF");
1977 }
1978 }
1979 }
1980
1981 // ── Article 3: Benefits ──
1982 doc.add_paragraph("").page_break_before(true);
1983 doc.add_paragraph("Article 3 — Benefits").style("Heading1");
1984 {
1985 let mut p = doc.add_paragraph("");
1986 p.add_run("3.1 ").bold(true);
1987 p.add_run(
1988 "The Employee shall be entitled to participate in all benefit programs \
1989 generally available to senior executives, including:",
1990 );
1991 }
1992 doc.add_bullet_list_item(
1993 "Medical, dental, and vision insurance (100% premium coverage for employee and dependents)",
1994 0,
1995 );
1996 doc.add_bullet_list_item("401(k) retirement plan with 6% company match", 0);
1997 doc.add_bullet_list_item("Life insurance and long-term disability coverage", 0);
1998 doc.add_bullet_list_item("Annual professional development allowance of $10,000", 0);
1999
2000 {
2001 let mut p = doc.add_paragraph("");
2002 p.add_run("3.2 Paid Time Off. ").bold(true);
2003 p.add_run(
2004 "The Employee shall receive 25 days of paid vacation per year, plus Company holidays, \
2005 accruing on a monthly basis.",
2006 );
2007 }
2008
2009 // ── Article 4: Term and Termination ──
2010 doc.add_paragraph("Article 4 — Term and Termination")
2011 .style("Heading1");
2012 {
2013 let mut p = doc.add_paragraph("");
2014 p.add_run("4.1 At-Will Employment. ").bold(true);
2015 p.add_run(
2016 "This Agreement is for at-will employment and may be terminated by either Party \
2017 at any time, with or without cause, subject to the notice provisions herein.",
2018 );
2019 }
2020 {
2021 let mut p = doc.add_paragraph("");
2022 p.add_run("4.2 Notice Period. ").bold(true);
2023 p.add_run("Either Party shall provide ");
2024 p.add_run("ninety (90) days").bold(true);
2025 p.add_run(" written notice of termination, or pay in lieu thereof.");
2026 }
2027 {
2028 let mut p = doc.add_paragraph("");
2029 p.add_run("4.3 Severance. ").bold(true);
2030 p.add_run("In the event of termination by the Company without Cause, the Employee shall \
2031 receive (a) twelve (12) months of base salary continuation, (b) pro-rata bonus \
2032 for the year of termination, and (c) twelve (12) months of COBRA premium coverage.");
2033 }
2034
2035 // ── Article 5: Confidentiality ──
2036 doc.add_paragraph("Article 5 — Confidentiality and IP")
2037 .style("Heading1");
2038 {
2039 let mut p = doc.add_paragraph("");
2040 p.add_run("5.1 ").bold(true);
2041 p.add_run(
2042 "The Employee agrees to maintain strict confidentiality of all proprietary \
2043 information, trade secrets, and business plans of the Company during and after \
2044 employment.",
2045 );
2046 }
2047 {
2048 let mut p = doc.add_paragraph("");
2049 p.add_run("5.2 ").bold(true);
2050 p.add_run(
2051 "All intellectual property, inventions, and works of authorship created by the \
2052 Employee during the term of employment and within the scope of duties shall be \
2053 the sole and exclusive property of the Company.",
2054 );
2055 }
2056
2057 // ── Article 6: Non-Compete ──
2058 doc.add_paragraph("Article 6 — Non-Competition")
2059 .style("Heading1");
2060 {
2061 let mut p = doc.add_paragraph("");
2062 p.add_run("6.1 ").bold(true);
2063 p.add_run("For a period of twelve (12) months following termination, the Employee agrees \
2064 not to engage in any business that directly competes with the Company's core \
2065 business of AI infrastructure and ML operations services, within the United States.");
2066 }
2067
2068 // ── Article 7: General Provisions ──
2069 doc.add_paragraph("Article 7 — General Provisions")
2070 .style("Heading1");
2071 {
2072 let mut p = doc.add_paragraph("");
2073 p.add_run("7.1 Governing Law. ").bold(true);
2074 p.add_run(
2075 "This Agreement shall be governed by and construed in accordance with the laws of \
2076 the State of California.",
2077 );
2078 }
2079 {
2080 let mut p = doc.add_paragraph("");
2081 p.add_run("7.2 Entire Agreement. ").bold(true);
2082 p.add_run(
2083 "This Agreement constitutes the entire agreement between the Parties and supersedes \
2084 all prior negotiations, representations, and agreements.",
2085 );
2086 }
2087 {
2088 let mut p = doc.add_paragraph("");
2089 p.add_run("7.3 Amendment. ").bold(true);
2090 p.add_run(
2091 "This Agreement may only be amended by a written instrument signed by both Parties.",
2092 );
2093 }
2094
2095 // ── Signature Block ──
2096 doc.add_paragraph("").page_break_before(true);
2097 doc.add_paragraph("IN WITNESS WHEREOF, the Parties have executed this Employment Agreement as of the Effective Date.")
2098 .space_after(Length::pt(24.0));
2099
2100 // Signature table
2101 {
2102 let mut tbl = doc.add_table(6, 2).width_pct(100.0);
2103 tbl.cell(0, 0).unwrap().set_text("FOR THE COMPANY:");
2104 tbl.cell(0, 0).unwrap().shading("5B2C6F");
2105 tbl.cell(0, 1).unwrap().set_text("EMPLOYEE:");
2106 tbl.cell(0, 1).unwrap().shading("5B2C6F");
2107
2108 tbl.cell(1, 0).unwrap().set_text("");
2109 tbl.cell(1, 1).unwrap().set_text("");
2110 tbl.row(1).unwrap().height(Length::pt(40.0));
2111
2112 tbl.cell(2, 0)
2113 .unwrap()
2114 .set_text("Signature: ___________________________");
2115 tbl.cell(2, 1)
2116 .unwrap()
2117 .set_text("Signature: ___________________________");
2118 tbl.cell(3, 0)
2119 .unwrap()
2120 .set_text("Name: Board Representative");
2121 tbl.cell(3, 1).unwrap().set_text("Name: Walter White");
2122 tbl.cell(4, 0)
2123 .unwrap()
2124 .set_text("Title: Chair, Board of Directors");
2125 tbl.cell(4, 1)
2126 .unwrap()
2127 .set_text("Title: Chief Executive Officer");
2128 tbl.cell(5, 0).unwrap().set_text("Date: _______________");
2129 tbl.cell(5, 1).unwrap().set_text("Date: _______________");
2130 }
2131
2132 doc
2133}Sourcepub fn table_count(&self) -> usize
pub fn table_count(&self) -> usize
Get the number of tables.
Sourcepub fn content_count(&self) -> usize
pub fn content_count(&self) -> usize
Get the number of body content elements (paragraphs + tables).
Examples found in repository?
97fn generate_feature_showcase(_samples_dir: &Path) -> Document {
98 let mut doc = Document::new();
99
100 // ── Page Setup & Metadata ──
101 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
102 doc.set_margins(
103 Length::inches(1.0),
104 Length::inches(1.0),
105 Length::inches(1.0),
106 Length::inches(1.0),
107 );
108 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
109 doc.set_gutter(Length::twips(0));
110 doc.set_title("rdocx Feature Showcase");
111 doc.set_author("rdocx Sample Generator");
112 doc.set_subject("Comprehensive feature demonstration");
113 doc.set_keywords("rdocx, docx, rust, sample, showcase");
114
115 // Headers & Footers with different first page
116 doc.set_different_first_page(true);
117 doc.set_first_page_header("rdocx");
118 doc.set_first_page_footer("Feature Showcase — Cover Page");
119 doc.set_header("rdocx Feature Showcase");
120 doc.set_footer("Generated by rdocx");
121
122 // ── COVER PAGE ──
123 let bg = create_sample_png(612, 792, [20, 45, 90]);
124 doc.add_background_image(&bg, "cover_bg.png");
125
126 for _ in 0..3 {
127 doc.add_paragraph("");
128 }
129 {
130 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
131 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
132 }
133 {
134 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
135 p.add_run("Complete Feature Showcase")
136 .size(28.0)
137 .color("FFFFFF")
138 .italic(true);
139 }
140 doc.add_paragraph("");
141 {
142 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
143 p.add_run("Every feature of the rdocx Rust library")
144 .size(13.0)
145 .color("B0C4DE");
146 }
147 {
148 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
149 p.add_run("demonstrated in a single document.")
150 .size(13.0)
151 .color("B0C4DE");
152 }
153
154 // ── TABLE OF CONTENTS ──
155 doc.add_paragraph("").page_break_before(true);
156 doc.insert_toc(doc.content_count(), 3);
157
158 // ── SECTION 1: TEXT FORMATTING ──
159 doc.add_paragraph("").page_break_before(true);
160 doc.add_paragraph("1. Text Formatting").style("Heading1");
161
162 // Paragraph Alignment
163 doc.add_paragraph("1.1 Paragraph Alignment")
164 .style("Heading2");
165 doc.add_paragraph("Left-aligned paragraph (default).")
166 .alignment(Alignment::Left);
167 doc.add_paragraph("Center-aligned paragraph.")
168 .alignment(Alignment::Center);
169 doc.add_paragraph("Right-aligned paragraph.")
170 .alignment(Alignment::Right);
171 doc.add_paragraph(
172 "Justified paragraph — this text is long enough to demonstrate how justified alignment \
173 distributes extra space across word gaps so lines fill the full width of the text area.",
174 )
175 .alignment(Alignment::Justify);
176
177 doc.add_paragraph("");
178
179 // Run Formatting
180 doc.add_paragraph("1.2 Run Formatting").style("Heading2");
181 {
182 let mut p = doc.add_paragraph("");
183 p.add_run("Bold").bold(true);
184 p.add_run(" | ");
185 p.add_run("Italic").italic(true);
186 p.add_run(" | ");
187 p.add_run("Bold Italic").bold(true).italic(true);
188 p.add_run(" | ");
189 p.add_run("Underline").underline(true);
190 p.add_run(" | ");
191 p.add_run("Strikethrough").strike(true);
192 p.add_run(" | ");
193 p.add_run("Double Strike").double_strike(true);
194 }
195 {
196 let mut p = doc.add_paragraph("");
197 p.add_run("Red").color("FF0000");
198 p.add_run(" | ");
199 p.add_run("Blue").color("0000FF");
200 p.add_run(" | ");
201 p.add_run("Green").color("00AA00");
202 p.add_run(" | ");
203 p.add_run("Highlighted").highlight("FFFF00");
204 }
205 {
206 let mut p = doc.add_paragraph("");
207 p.add_run("8pt").size(8.0);
208 p.add_run(" | ");
209 p.add_run("11pt").size(11.0);
210 p.add_run(" | ");
211 p.add_run("16pt").size(16.0);
212 p.add_run(" | ");
213 p.add_run("24pt").size(24.0);
214 }
215 {
216 let mut p = doc.add_paragraph("");
217 p.add_run("Arial").font("Arial");
218 p.add_run(" | ");
219 p.add_run("Times New Roman").font("Times New Roman");
220 p.add_run(" | ");
221 p.add_run("Courier New").font("Courier New");
222 }
223 {
224 let mut p = doc.add_paragraph("");
225 p.add_run("H");
226 p.add_run("2").subscript();
227 p.add_run("O (subscript) | E=mc");
228 p.add_run("2").superscript();
229 p.add_run(" (superscript)");
230 }
231 {
232 let mut p = doc.add_paragraph("");
233 p.add_run("ALL CAPS").all_caps(true);
234 p.add_run(" | ");
235 p.add_run("Small Caps").small_caps(true);
236 p.add_run(" | ");
237 p.add_run("Expanded").character_spacing(Length::twips(40));
238 p.add_run(" | ");
239 p.add_run("Hidden text (not visible)").hidden(true);
240 }
241
242 doc.add_paragraph("");
243
244 // Underline Styles
245 doc.add_paragraph("1.3 Underline Styles").style("Heading2");
246 {
247 let mut p = doc.add_paragraph("");
248 p.add_run("Single ")
249 .underline_style(rdocx::UnderlineStyle::Single);
250 p.add_run("Double ")
251 .underline_style(rdocx::UnderlineStyle::Double);
252 p.add_run("Thick ")
253 .underline_style(rdocx::UnderlineStyle::Thick);
254 p.add_run("Dotted ")
255 .underline_style(rdocx::UnderlineStyle::Dotted);
256 p.add_run("Dash ")
257 .underline_style(rdocx::UnderlineStyle::Dash);
258 p.add_run("Wave ")
259 .underline_style(rdocx::UnderlineStyle::Wave);
260 }
261
262 // ── SECTION 2: PARAGRAPH FORMATTING ──
263 doc.add_paragraph("").page_break_before(true);
264 doc.add_paragraph("2. Paragraph Formatting")
265 .style("Heading1");
266
267 doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
268 doc.add_paragraph("Paragraph with light green shading")
269 .shading("E2EFDA");
270 doc.add_paragraph("Paragraph with bottom border")
271 .border_bottom(BorderStyle::Single, 6, "2E75B6");
272 doc.add_paragraph("Paragraph with all borders (red)")
273 .border_all(BorderStyle::Single, 4, "FF0000");
274
275 doc.add_paragraph("2.2 Indentation").style("Heading2");
276 doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
277 .indent_left(Length::inches(1.0))
278 .hanging_indent(Length::inches(0.5));
279 doc.add_paragraph("0.5-inch first-line indent on this paragraph")
280 .first_line_indent(Length::inches(0.5));
281 doc.add_paragraph("Both left and right indent (0.75 inches each)")
282 .indent_left(Length::inches(0.75))
283 .indent_right(Length::inches(0.75));
284
285 doc.add_paragraph("2.3 Spacing & Line Height")
286 .style("Heading2");
287 doc.add_paragraph("Extra space before (24pt) and after (12pt)")
288 .space_before(Length::pt(24.0))
289 .space_after(Length::pt(12.0));
290 doc.add_paragraph(
291 "Double line spacing paragraph. This text should have extra vertical space between \
292 lines to demonstrate line_spacing_multiple(2.0).",
293 )
294 .line_spacing_multiple(2.0);
295 doc.add_paragraph("Exact 20pt line spacing (fixed height).")
296 .line_spacing(20.0);
297
298 doc.add_paragraph("2.4 Pagination Controls")
299 .style("Heading2");
300 doc.add_paragraph("keep_with_next — this paragraph stays with the next")
301 .keep_with_next(true);
302 doc.add_paragraph("(This paragraph was kept with the one above.)");
303 doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
304 .keep_together(true);
305 doc.add_paragraph("widow_control — prevents widow/orphan lines")
306 .widow_control(true);
307
308 // ── SECTION 3: LISTS ──
309 doc.add_paragraph("").page_break_before(true);
310 doc.add_paragraph("3. Lists").style("Heading1");
311
312 doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
313 doc.add_bullet_list_item("First item", 0);
314 doc.add_bullet_list_item("Second item", 0);
315 doc.add_bullet_list_item("Nested level 1", 1);
316 doc.add_bullet_list_item("Nested level 2", 2);
317 doc.add_bullet_list_item("Back to level 1", 1);
318 doc.add_bullet_list_item("Third item", 0);
319
320 doc.add_paragraph("");
321
322 doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
323 doc.add_numbered_list_item("First numbered item", 0);
324 doc.add_numbered_list_item("Second numbered item", 0);
325 doc.add_numbered_list_item("Sub-item A", 1);
326 doc.add_numbered_list_item("Sub-item B", 1);
327 doc.add_numbered_list_item("Third numbered item", 0);
328
329 // ── SECTION 4: TAB STOPS ──
330 doc.add_paragraph("");
331 doc.add_paragraph("4. Tab Stops").style("Heading1");
332
333 doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
334 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
335 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
336 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
337 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
338 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
339
340 doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
341 doc.add_paragraph("Item\t\tPrice")
342 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
343 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
344 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
345 doc.add_paragraph("Widget\t\t$19.99")
346 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
347 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
348 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
349 doc.add_paragraph("Gadget\t\t$249.50")
350 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
351 .add_tab_stop_with_leader(
352 TabAlignment::Right,
353 Length::inches(4.0),
354 TabLeader::Underscore,
355 )
356 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
357
358 // ── SECTION 5: TABLES ──
359 doc.add_paragraph("").page_break_before(true);
360 doc.add_paragraph("5. Tables").style("Heading1");
361
362 doc.add_paragraph("5.1 Basic Table").style("Heading2");
363 {
364 let mut tbl = doc
365 .add_table(4, 3)
366 .borders(BorderStyle::Single, 4, "000000");
367 for col in 0..3 {
368 tbl.cell(0, col).unwrap().shading("2E75B6");
369 }
370 tbl.cell(0, 0).unwrap().set_text("Name");
371 tbl.cell(0, 1).unwrap().set_text("Role");
372 tbl.cell(0, 2).unwrap().set_text("Location");
373 tbl.cell(1, 0).unwrap().set_text("Walter White");
374 tbl.cell(1, 1).unwrap().set_text("CEO");
375 tbl.cell(1, 2).unwrap().set_text("Albuquerque");
376 tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
377 tbl.cell(2, 1).unwrap().set_text("CTO");
378 tbl.cell(2, 2).unwrap().set_text("Remote");
379 tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
380 tbl.cell(3, 1).unwrap().set_text("Security");
381 tbl.cell(3, 2).unwrap().set_text("Washington");
382 }
383
384 doc.add_paragraph("");
385
386 doc.add_paragraph("5.2 Column Span & Cell Shading")
387 .style("Heading2");
388 {
389 let mut tbl = doc
390 .add_table(3, 4)
391 .borders(BorderStyle::Single, 4, "000000")
392 .width_pct(100.0);
393 tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
394 tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
395 tbl.cell(1, 0).unwrap().set_text("Region");
396 tbl.cell(1, 0).unwrap().shading("D6E4F0");
397 tbl.cell(1, 1).unwrap().set_text("Q1");
398 tbl.cell(1, 1).unwrap().shading("D6E4F0");
399 tbl.cell(1, 2).unwrap().set_text("Q2");
400 tbl.cell(1, 2).unwrap().shading("D6E4F0");
401 tbl.cell(1, 3).unwrap().set_text("Total");
402 tbl.cell(1, 3).unwrap().shading("D6E4F0");
403 tbl.cell(2, 0).unwrap().set_text("Americas");
404 tbl.cell(2, 1).unwrap().set_text("$2.4M");
405 tbl.cell(2, 2).unwrap().set_text("$2.7M");
406 tbl.cell(2, 3).unwrap().set_text("$5.1M");
407 }
408
409 doc.add_paragraph("");
410
411 doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
412 {
413 let mut tbl = doc
414 .add_table(4, 3)
415 .borders(BorderStyle::Single, 4, "000000");
416 tbl.cell(0, 0).unwrap().set_text("Category");
417 tbl.cell(0, 0).unwrap().shading("E2EFDA");
418 tbl.cell(0, 1).unwrap().set_text("Item");
419 tbl.cell(0, 1).unwrap().shading("E2EFDA");
420 tbl.cell(0, 2).unwrap().set_text("Price");
421 tbl.cell(0, 2).unwrap().shading("E2EFDA");
422 tbl.cell(1, 0).unwrap().set_text("Hardware");
423 tbl.cell(1, 0).unwrap().v_merge_restart();
424 tbl.cell(1, 1).unwrap().set_text("Laptop");
425 tbl.cell(1, 2).unwrap().set_text("$1,200");
426 tbl.cell(2, 0).unwrap().v_merge_continue();
427 tbl.cell(2, 1).unwrap().set_text("Monitor");
428 tbl.cell(2, 2).unwrap().set_text("$450");
429 tbl.cell(3, 0).unwrap().set_text("Software");
430 tbl.cell(3, 1).unwrap().set_text("IDE License");
431 tbl.cell(3, 2).unwrap().set_text("$200/yr");
432 }
433
434 doc.add_paragraph("");
435
436 doc.add_paragraph("5.4 Nested Table").style("Heading2");
437 {
438 let mut tbl = doc
439 .add_table(2, 2)
440 .borders(BorderStyle::Single, 6, "2E75B6");
441 tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
442 tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
443 tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
444 {
445 let mut cell = tbl.cell(1, 1).unwrap();
446 cell.set_text("Nested table below:");
447 let mut nested = cell
448 .add_table(2, 2)
449 .borders(BorderStyle::Single, 2, "FF6600");
450 nested.cell(0, 0).unwrap().set_text("A");
451 nested.cell(0, 1).unwrap().set_text("B");
452 nested.cell(1, 0).unwrap().set_text("C");
453 nested.cell(1, 1).unwrap().set_text("D");
454 }
455 }
456
457 doc.add_paragraph("");
458
459 doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
460 .style("Heading2");
461 {
462 let mut tbl = doc
463 .add_table(2, 3)
464 .borders(BorderStyle::Single, 4, "666666");
465 tbl.row(0).unwrap().height(Length::pt(50.0)).header();
466 tbl.cell(0, 0).unwrap().set_text("Top");
467 tbl.cell(0, 0)
468 .unwrap()
469 .vertical_alignment(VerticalAlignment::Top)
470 .shading("FFF2CC");
471 tbl.cell(0, 1).unwrap().set_text("Center");
472 tbl.cell(0, 1)
473 .unwrap()
474 .vertical_alignment(VerticalAlignment::Center)
475 .shading("D9E2F3");
476 tbl.cell(0, 2).unwrap().set_text("Bottom");
477 tbl.cell(0, 2)
478 .unwrap()
479 .vertical_alignment(VerticalAlignment::Bottom)
480 .shading("E2EFDA");
481 tbl.row(1).unwrap().cant_split();
482 tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
483 tbl.cell(1, 0).unwrap().no_wrap();
484 tbl.cell(1, 1).unwrap().set_text("Fixed width");
485 tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
486 tbl.cell(1, 2).unwrap().set_text("Normal");
487 }
488
489 // ── SECTION 6: IMAGES ──
490 doc.add_paragraph("").page_break_before(true);
491 doc.add_paragraph("6. Images").style("Heading1");
492
493 doc.add_paragraph("6.1 Inline Image").style("Heading2");
494 doc.add_paragraph("A blue gradient image below:");
495 let img = create_sample_png(200, 50, [0, 80, 200]);
496 doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
497
498 doc.add_paragraph("");
499 doc.add_paragraph("6.2 Header Image").style("Heading2");
500 let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
501 doc.set_header_image(
502 &hdr_img,
503 "header_logo.png",
504 Length::inches(2.0),
505 Length::inches(0.2),
506 );
507 doc.add_paragraph("The header now contains an inline image (check the top of this page).");
508
509 doc.add_paragraph("");
510 doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
511
512 // ── SECTION 7: CONTENT MANIPULATION ──
513 doc.add_paragraph("").page_break_before(true);
514 doc.add_paragraph("7. Content Manipulation")
515 .style("Heading1");
516
517 doc.add_paragraph("7.1 Placeholder Replacement")
518 .style("Heading2");
519 doc.add_paragraph("Customer: {{customer}}");
520 doc.add_paragraph("Date: {{date}}");
521 doc.add_paragraph("Reference: {{ref_number}}");
522 {
523 let mut tbl = doc
524 .add_table(2, 2)
525 .borders(BorderStyle::Single, 4, "000000");
526 tbl.cell(0, 0).unwrap().set_text("Project");
527 tbl.cell(0, 0).unwrap().shading("D6E4F0");
528 tbl.cell(0, 1).unwrap().set_text("{{project}}");
529 tbl.cell(1, 0).unwrap().set_text("Status");
530 tbl.cell(1, 0).unwrap().shading("D6E4F0");
531 tbl.cell(1, 1).unwrap().set_text("{{status}}");
532 }
533
534 let mut replacements = HashMap::new();
535 replacements.insert("{{customer}}", "Tensorbee Inc.");
536 replacements.insert("{{date}}", "February 22, 2026");
537 replacements.insert("{{ref_number}}", "TB-2026-001");
538 replacements.insert("{{project}}", "Infrastructure Upgrade");
539 replacements.insert("{{status}}", "In Progress");
540 let n = doc.replace_all(&replacements);
541 doc.add_paragraph(&format!("({n} placeholders replaced above)"));
542
543 doc.add_paragraph("");
544
545 doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
546 doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
547 let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
548 doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
549
550 doc.add_paragraph("");
551
552 doc.add_paragraph("7.3 Content Insertion").style("Heading2");
553 doc.add_paragraph("Section A: First content.");
554 doc.add_paragraph("Section C: Third content.");
555 if let Some(idx) = doc.find_content_index("Section C") {
556 doc.insert_paragraph(
557 idx,
558 "Section B: Inserted between A and C via find_content_index().",
559 );
560 }
561
562 // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
563 doc.add_paragraph("").section_break(SectionBreak::NextPage);
564 doc.add_paragraph("8. Section Breaks & Orientation")
565 .style("Heading1");
566 doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
567
568 {
569 let mut tbl = doc
570 .add_table(3, 7)
571 .borders(BorderStyle::Single, 4, "2E75B6");
572 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
573 for (c, h) in headers.iter().enumerate() {
574 tbl.cell(0, c).unwrap().set_text(h);
575 tbl.cell(0, c).unwrap().shading("2E75B6");
576 }
577 let data = [
578 [
579 "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
580 ],
581 [
582 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
583 ],
584 ];
585 for (r, row) in data.iter().enumerate() {
586 for (c, v) in row.iter().enumerate() {
587 tbl.cell(r + 1, c).unwrap().set_text(v);
588 }
589 }
590 }
591
592 doc.add_paragraph("")
593 .section_break(SectionBreak::NextPage)
594 .section_landscape();
595
596 // ── SECTION 9: CUSTOM STYLES ──
597 doc.add_paragraph("9. Custom Styles").style("Heading1");
598 doc.add_style(
599 StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
600 .based_on("Normal")
601 .paragraph_properties(rdocx_oxml::properties::CT_PPr {
602 shading: Some(rdocx_oxml::properties::CT_Shd {
603 val: "clear".to_string(),
604 color: None,
605 fill: Some("FFF2CC".to_string()),
606 }),
607 ..Default::default()
608 })
609 .run_properties(rdocx_oxml::properties::CT_RPr {
610 bold: Some(true),
611 color: Some("C45911".to_string()),
612 ..Default::default()
613 }),
614 );
615 doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
616 .style("CustomHighlight");
617
618 doc.add_paragraph("");
619
620 // ── SECTION 10: DOCUMENT INTELLIGENCE ──
621 doc.add_paragraph("10. Document Intelligence API")
622 .style("Heading1");
623
624 let wc = doc.word_count();
625 let hc = doc.headings().len();
626 let ic = doc.images().len();
627 let lc = doc.links().len();
628 doc.add_paragraph(&format!("Word count: {wc}"));
629 doc.add_paragraph(&format!("Heading count: {hc}"));
630 doc.add_paragraph(&format!("Image count: {ic}"));
631 doc.add_paragraph(&format!("Link count: {lc}"));
632
633 let outline = doc.document_outline();
634 doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
635
636 let issues = doc.audit_accessibility();
637 doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
638 for issue in &issues {
639 doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
640 }
641
642 // ── SECTION 11: DOCUMENT MERGING ──
643 doc.add_paragraph("");
644 doc.add_paragraph("11. Document Merging").style("Heading1");
645 {
646 let mut other = Document::new();
647 other.add_paragraph("This paragraph was merged from another document using append().");
648 doc.append(&other);
649 }
650
651 doc.add_paragraph("");
652
653 // ── SUMMARY ──
654 doc.add_paragraph("Summary of Demonstrated Features")
655 .style("Heading1");
656 let features = [
657 "Page setup: size, margins, header/footer distance, gutter",
658 "Document metadata: title, author, subject, keywords",
659 "Headers and footers: text, images, different first page",
660 "Background images (full-page behind text)",
661 "Table of Contents generation with bookmarks",
662 "Text formatting: bold, italic, underline styles, strike, color, size, font",
663 "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
664 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
665 "Pagination controls: keep-with-next, keep-together, widow control",
666 "Bullet and numbered lists with nesting",
667 "Tab stops with dot/underscore/hyphen leaders",
668 "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
669 "Row properties: header rows, exact height, cant-split",
670 "Cell properties: width, no-wrap, vertical alignment",
671 "Inline images and header images",
672 "Placeholder replacement (single and batch)",
673 "Regex-based find and replace",
674 "Content insertion at specific positions",
675 "Section breaks with mixed portrait/landscape",
676 "Custom paragraph and character styles",
677 "Document intelligence: word count, headings, outline, images, links",
678 "Accessibility audit",
679 "Document merging (append)",
680 "Export: DOCX, PDF, HTML, Markdown",
681 ];
682 for f in &features {
683 doc.add_bullet_list_item(f, 0);
684 }
685 doc.add_paragraph("");
686 doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
687 .alignment(Alignment::Center)
688 .shading("E2EFDA")
689 .border_all(BorderStyle::Single, 2, "00AA00");
690
691 doc
692}
693
694// =============================================================================
695// 2. PROPOSAL DOCUMENT — Deep navy + gold scheme
696// =============================================================================
697fn generate_proposal(_samples_dir: &Path) -> Document {
698 let mut doc = Document::new();
699
700 // Colors: Navy #1B2A4A, Gold #C5922E, Light #F4F1EB
701 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
702 doc.set_margins(
703 Length::inches(1.0),
704 Length::inches(1.0),
705 Length::inches(1.0),
706 Length::inches(1.0),
707 );
708 doc.set_title("AI Platform Modernization Proposal");
709 doc.set_author("Walter White");
710 doc.set_subject("Technology Proposal");
711 doc.set_keywords("proposal, AI, modernization, Tensorbee");
712
713 // Banner header
714 let logo_img = create_logo_png(220, 48);
715 let banner = build_header_banner_xml(
716 "rId1",
717 &BannerOpts {
718 bg_color: "1B2A4A",
719 banner_width: 7772400,
720 banner_height: 914400,
721 logo_width: 2011680,
722 logo_height: 438912,
723 logo_x_offset: 295125,
724 logo_y_offset: 237744,
725 },
726 );
727 doc.set_raw_header_with_images(
728 banner,
729 &[("rId1", &logo_img, "logo.png")],
730 rdocx_oxml::header_footer::HdrFtrType::Default,
731 );
732 doc.set_different_first_page(true);
733 let cover_banner = build_header_banner_xml(
734 "rId1",
735 &BannerOpts {
736 bg_color: "C5922E",
737 banner_width: 7772400,
738 banner_height: 914400,
739 logo_width: 2011680,
740 logo_height: 438912,
741 logo_x_offset: 295125,
742 logo_y_offset: 237744,
743 },
744 );
745 doc.set_raw_header_with_images(
746 cover_banner,
747 &[("rId1", &logo_img, "logo.png")],
748 rdocx_oxml::header_footer::HdrFtrType::First,
749 );
750 doc.set_margins(
751 Length::twips(2292),
752 Length::twips(1440),
753 Length::twips(1440),
754 Length::twips(1440),
755 );
756 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
757 doc.set_footer("Tensorbee — Confidential");
758
759 // Custom styles
760 doc.add_style(
761 StyleBuilder::paragraph("ProposalTitle", "Proposal Title")
762 .based_on("Normal")
763 .run_properties(rdocx_oxml::properties::CT_RPr {
764 bold: Some(true),
765 font_ascii: Some("Georgia".to_string()),
766 font_hansi: Some("Georgia".to_string()),
767 sz: Some(rdocx_oxml::HalfPoint(56)), // half-points → 28pt
768 color: Some("1B2A4A".to_string()),
769 ..Default::default()
770 }),
771 );
772
773 // ── Cover Page ──
774 for _ in 0..4 {
775 doc.add_paragraph("");
776 }
777 doc.add_paragraph("AI Platform Modernization")
778 .style("ProposalTitle")
779 .alignment(Alignment::Center);
780 doc.add_paragraph("")
781 .alignment(Alignment::Center)
782 .border_bottom(BorderStyle::Single, 8, "C5922E");
783 {
784 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
785 p.add_run("Prepared for Global Financial Corp.")
786 .size(14.0)
787 .color("666666")
788 .italic(true);
789 }
790 {
791 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
792 p.add_run("Prepared by: Walter White, CEO — Tensorbee")
793 .size(12.0)
794 .color("888888");
795 }
796 {
797 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
798 p.add_run("February 22, 2026").size(12.0).color("888888");
799 }
800
801 // ── TOC ──
802 doc.add_paragraph("").page_break_before(true);
803 doc.add_paragraph("Table of Contents").style("Heading1");
804 // We'll add a manual-style TOC since the insert_toc goes at a specific position
805 doc.insert_toc(doc.content_count(), 2);
806
807 // ── Executive Summary ──
808 doc.add_paragraph("").page_break_before(true);
809 doc.add_paragraph("Executive Summary").style("Heading1");
810 doc.add_paragraph(
811 "Tensorbee proposes a comprehensive modernization of Global Financial Corp's AI platform, \
812 transitioning from legacy batch-processing systems to a real-time inference architecture. \
813 This transformation will reduce model deployment time from weeks to hours, improve \
814 prediction accuracy by an estimated 23%, and deliver $4.2M in annual operational savings.",
815 )
816 .first_line_indent(Length::inches(0.3));
817 doc.add_paragraph(
818 "The project spans three phases over 18 months, with a total investment of $2.8M. \
819 Tensorbee brings deep expertise in ML infrastructure, having successfully completed \
820 similar transformations for three Fortune 500 financial institutions.",
821 )
822 .first_line_indent(Length::inches(0.3));
823
824 // Key metrics table
825 doc.add_paragraph("");
826 {
827 let mut tbl = doc
828 .add_table(5, 2)
829 .borders(BorderStyle::Single, 4, "1B2A4A")
830 .width_pct(80.0);
831 tbl.cell(0, 0).unwrap().set_text("Key Metric");
832 tbl.cell(0, 0).unwrap().shading("1B2A4A");
833 tbl.cell(0, 1).unwrap().set_text("Value");
834 tbl.cell(0, 1).unwrap().shading("1B2A4A");
835 let rows = [
836 ("Total Investment", "$2.8M"),
837 ("Annual Savings", "$4.2M"),
838 ("ROI (Year 1)", "150%"),
839 ("Timeline", "18 months"),
840 ];
841 for (i, (k, v)) in rows.iter().enumerate() {
842 tbl.cell(i + 1, 0).unwrap().set_text(k);
843 if i % 2 == 0 {
844 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
845 }
846 tbl.cell(i + 1, 1).unwrap().set_text(v);
847 if i % 2 == 0 {
848 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
849 }
850 }
851 }
852
853 // ── Problem Statement ──
854 doc.add_paragraph("").page_break_before(true);
855 doc.add_paragraph("Problem Statement").style("Heading1");
856 doc.add_paragraph(
857 "Global Financial Corp's current AI infrastructure faces three critical challenges:",
858 );
859 doc.add_numbered_list_item("Deployment Latency — New models take 3-4 weeks to deploy to production, compared to the industry benchmark of 2-3 days.", 0);
860 doc.add_numbered_list_item("Scalability Constraints — The batch processing architecture cannot handle real-time inference demands during peak trading hours.", 0);
861 doc.add_numbered_list_item("Technical Debt — Legacy Python 2.7 codebases and manual deployment scripts create reliability risks and slow iteration speed.", 0);
862
863 // ── Proposed Solution ──
864 doc.add_paragraph("").page_break_before(true);
865 doc.add_paragraph("Proposed Solution").style("Heading1");
866
867 doc.add_paragraph("Phase 1: Foundation (Months 1-6)")
868 .style("Heading2");
869 doc.add_bullet_list_item("Deploy Kubernetes-based ML serving infrastructure", 0);
870 doc.add_bullet_list_item("Implement CI/CD pipeline for model deployment", 0);
871 doc.add_bullet_list_item("Migrate top 5 production models to new platform", 0);
872
873 doc.add_paragraph("Phase 2: Expansion (Months 7-12)")
874 .style("Heading2");
875 doc.add_bullet_list_item(
876 "Real-time feature engineering pipeline (Apache Kafka + Flink)",
877 0,
878 );
879 doc.add_bullet_list_item("A/B testing framework for model variants", 0);
880 doc.add_bullet_list_item("Automated model monitoring and drift detection", 0);
881
882 doc.add_paragraph("Phase 3: Optimization (Months 13-18)")
883 .style("Heading2");
884 doc.add_bullet_list_item("GPU inference optimization (TensorRT/ONNX Runtime)", 0);
885 doc.add_bullet_list_item("Multi-region deployment for latency reduction", 0);
886 doc.add_bullet_list_item(
887 "Self-service model deployment portal for data science teams",
888 0,
889 );
890
891 // ── Budget ──
892 doc.add_paragraph("Budget Breakdown").style("Heading1");
893 {
894 let mut tbl = doc
895 .add_table(6, 3)
896 .borders(BorderStyle::Single, 4, "1B2A4A")
897 .width_pct(100.0);
898 tbl.cell(0, 0).unwrap().set_text("Category");
899 tbl.cell(0, 0).unwrap().shading("1B2A4A");
900 tbl.cell(0, 1).unwrap().set_text("Cost");
901 tbl.cell(0, 1).unwrap().shading("1B2A4A");
902 tbl.cell(0, 2).unwrap().set_text("Notes");
903 tbl.cell(0, 2).unwrap().shading("1B2A4A");
904 let items = [
905 (
906 "Infrastructure",
907 "$450,000",
908 "Cloud compute + GPU instances",
909 ),
910 ("Engineering Services", "$1,600,000", "12 FTE x 18 months"),
911 (
912 "Software Licenses",
913 "$350,000",
914 "Kafka, monitoring, CI/CD tools",
915 ),
916 (
917 "Training & Enablement",
918 "$200,000",
919 "Team training, documentation",
920 ),
921 ("Contingency (10%)", "$200,000", "Risk buffer"),
922 ];
923 for (i, (cat, cost, note)) in items.iter().enumerate() {
924 tbl.cell(i + 1, 0).unwrap().set_text(cat);
925 tbl.cell(i + 1, 1).unwrap().set_text(cost);
926 tbl.cell(i + 1, 2).unwrap().set_text(note);
927 if i % 2 == 0 {
928 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
929 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
930 tbl.cell(i + 1, 2).unwrap().shading("F4F1EB");
931 }
932 }
933 }
934
935 // ── Closing ──
936 doc.add_paragraph("");
937 doc.add_paragraph("Next Steps").style("Heading1");
938 doc.add_numbered_list_item(
939 "Schedule technical deep-dive with GFC engineering team (Week 1)",
940 0,
941 );
942 doc.add_numbered_list_item("Finalize scope and sign SOW (Week 2-3)", 0);
943 doc.add_numbered_list_item("Kick off Phase 1 with joint planning session (Week 4)", 0);
944
945 doc.add_paragraph("");
946 {
947 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
948 p.add_run("Walter White")
949 .bold(true)
950 .size(14.0)
951 .color("1B2A4A");
952 }
953 {
954 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
955 p.add_run("CEO, Tensorbee | walter@tensorbee.com")
956 .size(10.0)
957 .color("888888");
958 }
959
960 doc
961}
962
963// =============================================================================
964// 3. QUOTE / BILL OF MATERIALS — Teal + orange scheme
965// =============================================================================
966fn generate_quote(_samples_dir: &Path) -> Document {
967 let mut doc = Document::new();
968
969 // Colors: Teal #008B8B, Dark #1A3C3C, Orange #E07020, Light #F0F8F8
970 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
971 doc.set_margins(
972 Length::inches(0.75),
973 Length::inches(0.75),
974 Length::inches(0.75),
975 Length::inches(0.75),
976 );
977 doc.set_title("Quotation QT-2026-0147");
978 doc.set_author("Walter White");
979 doc.set_keywords("quote, BOM, Tensorbee");
980
981 doc.set_header("Tensorbee — Quotation");
982 doc.set_footer("QT-2026-0147 | Page");
983
984 // ── Header Block ──
985 {
986 let mut p = doc.add_paragraph("").alignment(Alignment::Left);
987 p.add_run("TENSORBEE")
988 .bold(true)
989 .size(28.0)
990 .color("008B8B")
991 .font("Helvetica");
992 }
993 doc.add_paragraph("123 Innovation Drive, Suite 400")
994 .alignment(Alignment::Left);
995 doc.add_paragraph("San Francisco, CA 94105 | +1 (415) 555-0199")
996 .alignment(Alignment::Left);
997 doc.add_paragraph("")
998 .border_bottom(BorderStyle::Single, 8, "008B8B");
999
1000 // Quote meta
1001 doc.add_paragraph("");
1002 {
1003 let mut tbl = doc.add_table(4, 4).width_pct(100.0);
1004 tbl.cell(0, 0).unwrap().set_text("QUOTATION");
1005 tbl.cell(0, 0).unwrap().shading("008B8B").grid_span(2);
1006 tbl.cell(0, 2).unwrap().set_text("DATE");
1007 tbl.cell(0, 2).unwrap().shading("008B8B");
1008 tbl.cell(0, 3).unwrap().set_text("VALID UNTIL");
1009 tbl.cell(0, 3).unwrap().shading("008B8B");
1010 tbl.cell(1, 0).unwrap().set_text("Quote #:");
1011 tbl.cell(1, 1).unwrap().set_text("QT-2026-0147");
1012 tbl.cell(1, 2).unwrap().set_text("Feb 22, 2026");
1013 tbl.cell(1, 3).unwrap().set_text("Mar 22, 2026");
1014 tbl.cell(2, 0).unwrap().set_text("Prepared by:");
1015 tbl.cell(2, 1).unwrap().set_text("Walter White");
1016 tbl.cell(2, 2).unwrap().set_text("Payment:");
1017 tbl.cell(2, 3).unwrap().set_text("Net 30");
1018 tbl.cell(3, 0).unwrap().set_text("Customer:");
1019 tbl.cell(3, 1).unwrap().set_text("Meridian Dynamics LLC");
1020 tbl.cell(3, 2).unwrap().set_text("Currency:");
1021 tbl.cell(3, 3).unwrap().set_text("USD");
1022 }
1023
1024 doc.add_paragraph("");
1025
1026 // ── Bill of Materials ──
1027 {
1028 let mut p = doc.add_paragraph("");
1029 p.add_run("Bill of Materials")
1030 .bold(true)
1031 .size(16.0)
1032 .color("1A3C3C");
1033 }
1034 doc.add_paragraph("");
1035
1036 {
1037 let mut tbl = doc
1038 .add_table(10, 6)
1039 .borders(BorderStyle::Single, 2, "008B8B")
1040 .width_pct(100.0)
1041 .layout_fixed();
1042 // Headers
1043 let hdrs = [
1044 "#",
1045 "Part Number",
1046 "Description",
1047 "Qty",
1048 "Unit Price",
1049 "Total",
1050 ];
1051 for (c, h) in hdrs.iter().enumerate() {
1052 tbl.cell(0, c).unwrap().set_text(h);
1053 tbl.cell(0, c).unwrap().shading("008B8B");
1054 }
1055
1056 let items: Vec<(&str, &str, &str, &str, &str)> = vec![
1057 (
1058 "TB-GPU-A100",
1059 "NVIDIA A100 80GB GPU",
1060 "4",
1061 "$12,500.00",
1062 "$50,000.00",
1063 ),
1064 (
1065 "TB-SRV-R750",
1066 "Dell R750xa Server Chassis",
1067 "2",
1068 "$8,200.00",
1069 "$16,400.00",
1070 ),
1071 (
1072 "TB-RAM-256G",
1073 "256GB DDR5 ECC Memory Module",
1074 "8",
1075 "$890.00",
1076 "$7,120.00",
1077 ),
1078 (
1079 "TB-SSD-3840",
1080 "3.84TB NVMe U.2 SSD",
1081 "8",
1082 "$1,150.00",
1083 "$9,200.00",
1084 ),
1085 (
1086 "TB-NET-CX7",
1087 "ConnectX-7 200GbE NIC",
1088 "4",
1089 "$1,800.00",
1090 "$7,200.00",
1091 ),
1092 (
1093 "TB-SW-48P",
1094 "48-Port 100GbE Switch",
1095 "1",
1096 "$22,000.00",
1097 "$22,000.00",
1098 ),
1099 (
1100 "TB-CAB-RACK",
1101 "42U Server Rack + PDU",
1102 "1",
1103 "$4,500.00",
1104 "$4,500.00",
1105 ),
1106 (
1107 "TB-SVC-INST",
1108 "Installation & Configuration",
1109 "1",
1110 "$8,500.00",
1111 "$8,500.00",
1112 ),
1113 (
1114 "TB-SVC-SUPP",
1115 "3-Year Premium Support",
1116 "1",
1117 "$15,000.00",
1118 "$15,000.00",
1119 ),
1120 ];
1121
1122 for (i, (pn, desc, qty, unit, total)) in items.iter().enumerate() {
1123 let row = i + 1;
1124 tbl.cell(row, 0).unwrap().set_text(&format!("{}", i + 1));
1125 tbl.cell(row, 1).unwrap().set_text(pn);
1126 tbl.cell(row, 2).unwrap().set_text(desc);
1127 tbl.cell(row, 3).unwrap().set_text(qty);
1128 tbl.cell(row, 4).unwrap().set_text(unit);
1129 tbl.cell(row, 5).unwrap().set_text(total);
1130 if i % 2 == 0 {
1131 for c in 0..6 {
1132 tbl.cell(row, c).unwrap().shading("F0F8F8");
1133 }
1134 }
1135 }
1136 }
1137
1138 doc.add_paragraph("");
1139
1140 // ── Totals ──
1141 {
1142 let mut tbl = doc
1143 .add_table(4, 2)
1144 .borders(BorderStyle::Single, 2, "008B8B")
1145 .width(Length::inches(3.5))
1146 .alignment(Alignment::Right);
1147 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1148 tbl.cell(0, 1).unwrap().set_text("$139,920.00");
1149 tbl.cell(1, 0).unwrap().set_text("Shipping & Handling");
1150 tbl.cell(1, 1).unwrap().set_text("$2,500.00");
1151 tbl.cell(2, 0).unwrap().set_text("Tax (8.625%)");
1152 tbl.cell(2, 1).unwrap().set_text("$12,068.10");
1153 tbl.cell(3, 0).unwrap().set_text("TOTAL");
1154 tbl.cell(3, 0).unwrap().shading("E07020");
1155 tbl.cell(3, 1).unwrap().set_text("$154,488.10");
1156 tbl.cell(3, 1).unwrap().shading("E07020");
1157 }
1158
1159 doc.add_paragraph("");
1160
1161 // ── Terms & Conditions ──
1162 {
1163 let mut p = doc.add_paragraph("");
1164 p.add_run("Terms & Conditions")
1165 .bold(true)
1166 .size(14.0)
1167 .color("1A3C3C");
1168 }
1169 doc.add_numbered_list_item(
1170 "This quotation is valid for 30 calendar days from the date of issue.",
1171 0,
1172 );
1173 doc.add_numbered_list_item(
1174 "All prices are in USD and exclusive of applicable taxes unless stated.",
1175 0,
1176 );
1177 doc.add_numbered_list_item("Standard lead time is 4-6 weeks from PO receipt.", 0);
1178 doc.add_numbered_list_item(
1179 "Payment terms: Net 30 from invoice date. 2% discount for payment within 10 days.",
1180 0,
1181 );
1182 doc.add_numbered_list_item(
1183 "Warranty: 3-year manufacturer warranty on all hardware components.",
1184 0,
1185 );
1186 doc.add_numbered_list_item(
1187 "Returns subject to 15% restocking fee if initiated after 14 days.",
1188 0,
1189 );
1190
1191 doc.add_paragraph("");
1192 doc.add_paragraph("")
1193 .border_bottom(BorderStyle::Single, 4, "008B8B");
1194 doc.add_paragraph("");
1195 {
1196 let mut p = doc.add_paragraph("");
1197 p.add_run("Accepted by: ").bold(true);
1198 p.add_run("___________________________ Date: ___________");
1199 }
1200 {
1201 let mut p = doc.add_paragraph("");
1202 p.add_run("Print Name: ").bold(true);
1203 p.add_run("___________________________ Title: ___________");
1204 }
1205
1206 doc
1207}
1208
1209// =============================================================================
1210// 4. INVOICE — Crimson + charcoal scheme
1211// =============================================================================
1212fn generate_invoice(_samples_dir: &Path) -> Document {
1213 let mut doc = Document::new();
1214
1215 // Colors: Crimson #B22222, Charcoal #333333, Light #FAF0F0
1216 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1217 doc.set_margins(
1218 Length::inches(0.75),
1219 Length::inches(0.75),
1220 Length::inches(0.75),
1221 Length::inches(0.75),
1222 );
1223 doc.set_title("Invoice INV-2026-0392");
1224 doc.set_author("Walter White");
1225 doc.set_keywords("invoice, Tensorbee");
1226
1227 doc.set_footer("Tensorbee — Thank you for your business!");
1228
1229 // ── Company & Invoice Header ──
1230 {
1231 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1232 // Company name left, INVOICE right
1233 {
1234 let mut cell = tbl.cell(0, 0).unwrap();
1235 let mut p = cell.add_paragraph("");
1236 p.add_run("TENSORBEE")
1237 .bold(true)
1238 .size(32.0)
1239 .color("B22222")
1240 .font("Helvetica");
1241 }
1242 {
1243 let mut cell = tbl.cell(0, 1).unwrap();
1244 let mut p = cell.add_paragraph("");
1245 p.add_run("INVOICE").bold(true).size(32.0).color("333333");
1246 }
1247 tbl.cell(1, 0)
1248 .unwrap()
1249 .set_text("123 Innovation Drive, Suite 400");
1250 tbl.cell(1, 1).unwrap().set_text("Invoice #: INV-2026-0392");
1251 tbl.cell(2, 0).unwrap().set_text("San Francisco, CA 94105");
1252 tbl.cell(2, 1).unwrap().set_text("Date: February 22, 2026");
1253 tbl.cell(3, 0).unwrap().set_text("walter@tensorbee.com");
1254 tbl.cell(3, 1).unwrap().set_text("Due Date: March 24, 2026");
1255 }
1256
1257 doc.add_paragraph("")
1258 .border_bottom(BorderStyle::Thick, 8, "B22222");
1259 doc.add_paragraph("");
1260
1261 // ── Bill To / Ship To ──
1262 {
1263 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1264 tbl.cell(0, 0).unwrap().set_text("BILL TO");
1265 tbl.cell(0, 0).unwrap().shading("B22222");
1266 tbl.cell(0, 1).unwrap().set_text("SHIP TO");
1267 tbl.cell(0, 1).unwrap().shading("B22222");
1268 tbl.cell(1, 0).unwrap().set_text("Meridian Dynamics LLC");
1269 tbl.cell(1, 1).unwrap().set_text("Meridian Dynamics LLC");
1270 tbl.cell(2, 0).unwrap().set_text("456 Enterprise Blvd");
1271 tbl.cell(2, 1).unwrap().set_text("Attn: Server Room B");
1272 tbl.cell(3, 0).unwrap().set_text("Austin, TX 78701");
1273 tbl.cell(3, 1)
1274 .unwrap()
1275 .set_text("456 Enterprise Blvd, Austin, TX 78701");
1276 }
1277
1278 doc.add_paragraph("");
1279
1280 // ── Line Items ──
1281 {
1282 let mut tbl = doc
1283 .add_table(8, 5)
1284 .borders(BorderStyle::Single, 2, "B22222")
1285 .width_pct(100.0);
1286 let hdrs = ["Description", "Qty", "Unit Price", "Tax", "Amount"];
1287 for (c, h) in hdrs.iter().enumerate() {
1288 tbl.cell(0, c).unwrap().set_text(h);
1289 tbl.cell(0, c).unwrap().shading("B22222");
1290 }
1291 let items = [
1292 (
1293 "ML Infrastructure Setup — Phase 1",
1294 "1",
1295 "$45,000.00",
1296 "$3,881.25",
1297 "$48,881.25",
1298 ),
1299 (
1300 "Data Pipeline Development (160 hrs)",
1301 "160",
1302 "$225.00",
1303 "$3,105.00",
1304 "$39,105.00",
1305 ),
1306 (
1307 "GPU Cluster Configuration",
1308 "1",
1309 "$12,000.00",
1310 "$1,035.00",
1311 "$13,035.00",
1312 ),
1313 (
1314 "API Gateway Implementation",
1315 "1",
1316 "$18,500.00",
1317 "$1,595.63",
1318 "$20,095.63",
1319 ),
1320 (
1321 "Load Testing & QA (80 hrs)",
1322 "80",
1323 "$195.00",
1324 "$1,345.50",
1325 "$16,945.50",
1326 ),
1327 (
1328 "Documentation & Training",
1329 "1",
1330 "$8,000.00",
1331 "$690.00",
1332 "$8,690.00",
1333 ),
1334 (
1335 "Project Management (3 months)",
1336 "3",
1337 "$6,500.00",
1338 "$1,679.63",
1339 "$21,179.63",
1340 ),
1341 ];
1342 for (i, (desc, qty, unit, tax, amt)) in items.iter().enumerate() {
1343 let r = i + 1;
1344 tbl.cell(r, 0).unwrap().set_text(desc);
1345 tbl.cell(r, 1).unwrap().set_text(qty);
1346 tbl.cell(r, 2).unwrap().set_text(unit);
1347 tbl.cell(r, 3).unwrap().set_text(tax);
1348 tbl.cell(r, 4).unwrap().set_text(amt);
1349 if i % 2 == 0 {
1350 for c in 0..5 {
1351 tbl.cell(r, c).unwrap().shading("FAF0F0");
1352 }
1353 }
1354 }
1355 }
1356
1357 doc.add_paragraph("");
1358
1359 // ── Totals ──
1360 {
1361 let mut tbl = doc
1362 .add_table(4, 2)
1363 .borders(BorderStyle::Single, 2, "B22222")
1364 .width(Length::inches(3.0))
1365 .alignment(Alignment::Right);
1366 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1367 tbl.cell(0, 1).unwrap().set_text("$154,600.00");
1368 tbl.cell(1, 0).unwrap().set_text("Tax (8.625%)");
1369 tbl.cell(1, 1).unwrap().set_text("$13,334.25");
1370 tbl.cell(2, 0).unwrap().set_text("Discount (5%)");
1371 tbl.cell(2, 1).unwrap().set_text("-$7,730.00");
1372 tbl.cell(3, 0).unwrap().set_text("AMOUNT DUE");
1373 tbl.cell(3, 0).unwrap().shading("B22222");
1374 tbl.cell(3, 1).unwrap().set_text("$160,204.25");
1375 tbl.cell(3, 1).unwrap().shading("B22222");
1376 }
1377
1378 doc.add_paragraph("");
1379 doc.add_paragraph("");
1380
1381 // ── Payment Details ──
1382 {
1383 let mut p = doc.add_paragraph("");
1384 p.add_run("Payment Details")
1385 .bold(true)
1386 .size(14.0)
1387 .color("333333");
1388 }
1389 doc.add_paragraph("Bank: Silicon Valley Bank")
1390 .indent_left(Length::inches(0.3));
1391 doc.add_paragraph("Account: Tensorbee Inc. — 0847-2953-1120")
1392 .indent_left(Length::inches(0.3));
1393 doc.add_paragraph("Routing: 121140399")
1394 .indent_left(Length::inches(0.3));
1395 doc.add_paragraph("Swift: SVBKUS6S")
1396 .indent_left(Length::inches(0.3));
1397
1398 doc.add_paragraph("");
1399
1400 doc.add_paragraph("Please include invoice number INV-2026-0392 in the payment reference.")
1401 .shading("FAF0F0")
1402 .border_all(BorderStyle::Single, 2, "B22222");
1403
1404 doc
1405}
1406
1407// =============================================================================
1408// 5. REPORT — Forest green + earth tones, with images & hierarchical sections
1409// =============================================================================
1410fn generate_report(_samples_dir: &Path) -> Document {
1411 let mut doc = Document::new();
1412
1413 // Colors: Forest #2D5016, Sage #6B8E23, Earth #8B7355, Cream #FFFAF0
1414 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1415 doc.set_margins(
1416 Length::inches(1.0),
1417 Length::inches(1.0),
1418 Length::inches(1.0),
1419 Length::inches(1.0),
1420 );
1421 doc.set_title("Q4 2025 Environmental Impact Report");
1422 doc.set_author("Walter White");
1423 doc.set_subject("Quarterly Environmental Report");
1424 doc.set_keywords("environment, sustainability, report, Tensorbee");
1425
1426 doc.set_different_first_page(true);
1427 doc.set_first_page_header("");
1428 doc.set_header("Tensorbee — Q4 2025 Environmental Impact Report");
1429 doc.set_footer("CONFIDENTIAL — Page");
1430
1431 // ── Cover ──
1432 let cover_bg = create_sample_png(612, 792, [20, 50, 15]);
1433 doc.add_background_image(&cover_bg, "report_cover.png");
1434
1435 for _ in 0..5 {
1436 doc.add_paragraph("");
1437 }
1438 {
1439 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1440 p.add_run("Q4 2025")
1441 .bold(true)
1442 .size(48.0)
1443 .color("FFFFFF")
1444 .font("Georgia");
1445 }
1446 {
1447 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1448 p.add_run("Environmental Impact Report")
1449 .size(24.0)
1450 .color("90EE90")
1451 .font("Georgia")
1452 .italic(true);
1453 }
1454 doc.add_paragraph("");
1455 {
1456 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1457 p.add_run("Tensorbee — Sustainability Division")
1458 .size(14.0)
1459 .color("C0C0C0");
1460 }
1461 {
1462 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1463 p.add_run("Prepared by Walter White, Chief Sustainability Officer")
1464 .size(11.0)
1465 .color("AAAAAA");
1466 }
1467
1468 // ── TOC ──
1469 doc.add_paragraph("").page_break_before(true);
1470 doc.insert_toc(doc.content_count(), 3);
1471
1472 // ── Section 1: Executive Overview ──
1473 doc.add_paragraph("").page_break_before(true);
1474 doc.add_paragraph("Executive Overview").style("Heading1");
1475 doc.add_paragraph(
1476 "This report presents Tensorbee's environmental performance for Q4 2025. Our \
1477 sustainability initiatives have yielded a 34% reduction in carbon emissions compared \
1478 to Q4 2024, exceeding our target of 25%. Key achievements include the transition to \
1479 100% renewable energy in our primary data centers and the launch of our carbon offset \
1480 marketplace.",
1481 )
1482 .first_line_indent(Length::inches(0.3));
1483
1484 // Image: performance chart
1485 let chart_img = create_chart_png(400, 200);
1486 doc.add_paragraph("");
1487 doc.add_picture(
1488 &chart_img,
1489 "performance_chart.png",
1490 Length::inches(5.0),
1491 Length::inches(2.5),
1492 )
1493 .alignment(Alignment::Center);
1494 {
1495 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1496 p.add_run("Figure 1: Quarterly Carbon Emissions (tonnes CO2e)")
1497 .italic(true)
1498 .size(9.0)
1499 .color("666666");
1500 }
1501
1502 // ── Section 2: Energy Consumption ──
1503 doc.add_paragraph("").page_break_before(true);
1504 doc.add_paragraph("Energy Consumption").style("Heading1");
1505
1506 doc.add_paragraph("Data Center Operations")
1507 .style("Heading2");
1508 doc.add_paragraph(
1509 "Our three primary data centers consumed a combined 4.2 GWh in Q4 2025, \
1510 a 12% reduction from Q3 2025 driven by improved cooling efficiency and \
1511 server consolidation.",
1512 );
1513
1514 // Data table
1515 {
1516 let mut tbl = doc
1517 .add_table(5, 4)
1518 .borders(BorderStyle::Single, 4, "2D5016")
1519 .width_pct(100.0);
1520 let hdrs = ["Data Center", "Capacity (MW)", "Usage (GWh)", "PUE"];
1521 for (c, h) in hdrs.iter().enumerate() {
1522 tbl.cell(0, c).unwrap().set_text(h);
1523 tbl.cell(0, c).unwrap().shading("2D5016");
1524 }
1525 let rows = [
1526 ("San Francisco (Primary)", "3.2", "1.8", "1.12"),
1527 ("Dublin (EU)", "2.1", "1.2", "1.18"),
1528 ("Singapore (APAC)", "1.8", "1.2", "1.24"),
1529 ("TOTAL", "7.1", "4.2", "1.17 avg"),
1530 ];
1531 for (i, (dc, cap, use_, pue)) in rows.iter().enumerate() {
1532 tbl.cell(i + 1, 0).unwrap().set_text(dc);
1533 tbl.cell(i + 1, 1).unwrap().set_text(cap);
1534 tbl.cell(i + 1, 2).unwrap().set_text(use_);
1535 tbl.cell(i + 1, 3).unwrap().set_text(pue);
1536 if i == 3 {
1537 for c in 0..4 {
1538 tbl.cell(i + 1, c).unwrap().shading("E2EFDA");
1539 }
1540 }
1541 }
1542 }
1543
1544 doc.add_paragraph("");
1545
1546 doc.add_paragraph("Renewable Energy Mix").style("Heading2");
1547 doc.add_paragraph("Breakdown of energy sources across all facilities:");
1548
1549 doc.add_bullet_list_item("Solar PV: 42% (1.76 GWh)", 0);
1550 doc.add_bullet_list_item("Wind Power Purchase Agreements: 38% (1.60 GWh)", 0);
1551 doc.add_bullet_list_item("Hydroelectric: 12% (0.50 GWh)", 0);
1552 doc.add_bullet_list_item("Grid (non-renewable): 8% (0.34 GWh)", 0);
1553
1554 // ── Section 3: Water & Waste ──
1555 doc.add_paragraph("Water & Waste Management")
1556 .style("Heading1");
1557
1558 doc.add_paragraph("Water Usage").style("Heading2");
1559 doc.add_paragraph(
1560 "Total water consumption was 12.4 million gallons, a 15% reduction from Q3. \
1561 Our closed-loop cooling systems now recycle 78% of water used in cooling operations.",
1562 );
1563
1564 doc.add_paragraph("Waste Reduction").style("Heading2");
1565 doc.add_paragraph("E-waste management results for Q4:")
1566 .keep_with_next(true);
1567 {
1568 let mut tbl = doc
1569 .add_table(4, 3)
1570 .borders(BorderStyle::Single, 2, "6B8E23")
1571 .width_pct(80.0);
1572 tbl.cell(0, 0).unwrap().set_text("Category");
1573 tbl.cell(0, 0).unwrap().shading("6B8E23");
1574 tbl.cell(0, 1).unwrap().set_text("Weight (kg)");
1575 tbl.cell(0, 1).unwrap().shading("6B8E23");
1576 tbl.cell(0, 2).unwrap().set_text("Recycled %");
1577 tbl.cell(0, 2).unwrap().shading("6B8E23");
1578 let rows = [
1579 ("Server Hardware", "2,450", "94%"),
1580 ("Networking Equipment", "820", "91%"),
1581 ("Storage Media", "340", "99%"),
1582 ];
1583 for (i, (cat, wt, pct)) in rows.iter().enumerate() {
1584 tbl.cell(i + 1, 0).unwrap().set_text(cat);
1585 tbl.cell(i + 1, 1).unwrap().set_text(wt);
1586 tbl.cell(i + 1, 2).unwrap().set_text(pct);
1587 }
1588 }
1589
1590 // ── Section 4: Initiatives ──
1591 doc.add_paragraph("").page_break_before(true);
1592 doc.add_paragraph("Q1 2026 Initiatives").style("Heading1");
1593
1594 doc.add_paragraph("Planned Programs").style("Heading2");
1595 doc.add_numbered_list_item(
1596 "Deploy on-site battery storage at SF data center (2 MWh capacity)",
1597 0,
1598 );
1599 doc.add_numbered_list_item("Pilot immersion cooling in Dublin facility", 0);
1600 doc.add_numbered_list_item("Launch employee commute carbon offset program", 0);
1601 doc.add_numbered_list_item("Achieve ISO 14001 certification for Singapore facility", 0);
1602
1603 doc.add_paragraph("Investment Targets").style("Heading2");
1604 doc.add_paragraph("Sustainability CapEx allocation for FY2026:")
1605 .keep_with_next(true);
1606 {
1607 let mut tbl = doc
1608 .add_table(5, 2)
1609 .borders(BorderStyle::Single, 4, "2D5016");
1610 tbl.cell(0, 0).unwrap().set_text("Initiative");
1611 tbl.cell(0, 0).unwrap().shading("2D5016");
1612 tbl.cell(0, 1).unwrap().set_text("Budget");
1613 tbl.cell(0, 1).unwrap().shading("2D5016");
1614 let rows = [
1615 ("Battery Storage", "$1.2M"),
1616 ("Immersion Cooling Pilot", "$800K"),
1617 ("Solar Panel Expansion", "$2.1M"),
1618 ("Carbon Credits", "$500K"),
1619 ];
1620 for (i, (init, budget)) in rows.iter().enumerate() {
1621 tbl.cell(i + 1, 0).unwrap().set_text(init);
1622 tbl.cell(i + 1, 1).unwrap().set_text(budget);
1623 if i % 2 == 0 {
1624 tbl.cell(i + 1, 0).unwrap().shading("FFFAF0");
1625 tbl.cell(i + 1, 1).unwrap().shading("FFFAF0");
1626 }
1627 }
1628 }
1629
1630 // Image: sustainability roadmap
1631 let roadmap_img = create_sample_png(500, 100, [40, 80, 30]);
1632 doc.add_paragraph("");
1633 doc.add_picture(
1634 &roadmap_img,
1635 "roadmap.png",
1636 Length::inches(6.0),
1637 Length::inches(1.2),
1638 )
1639 .alignment(Alignment::Center);
1640 {
1641 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1642 p.add_run("Figure 2: 2026 Sustainability Roadmap")
1643 .italic(true)
1644 .size(9.0)
1645 .color("666666");
1646 }
1647
1648 doc.add_paragraph("");
1649 doc.add_paragraph(
1650 "For questions about this report, contact Walter White at sustainability@tensorbee.com.",
1651 )
1652 .shading("FFFAF0")
1653 .border_all(BorderStyle::Single, 2, "2D5016");
1654
1655 doc
1656}Sourcepub fn insert_paragraph(&mut self, index: usize, text: &str) -> Paragraph<'_>
pub fn insert_paragraph(&mut self, index: usize, text: &str) -> Paragraph<'_>
Insert a paragraph at the given body index.
Returns a mutable Paragraph for further configuration.
§Panics
Panics if index > content_count(). (Unlike Self::insert_document
and Self::insert_toc, which clamp an out-of-range index to the end.)
Examples found in repository?
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
97fn generate_feature_showcase(_samples_dir: &Path) -> Document {
98 let mut doc = Document::new();
99
100 // ── Page Setup & Metadata ──
101 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
102 doc.set_margins(
103 Length::inches(1.0),
104 Length::inches(1.0),
105 Length::inches(1.0),
106 Length::inches(1.0),
107 );
108 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
109 doc.set_gutter(Length::twips(0));
110 doc.set_title("rdocx Feature Showcase");
111 doc.set_author("rdocx Sample Generator");
112 doc.set_subject("Comprehensive feature demonstration");
113 doc.set_keywords("rdocx, docx, rust, sample, showcase");
114
115 // Headers & Footers with different first page
116 doc.set_different_first_page(true);
117 doc.set_first_page_header("rdocx");
118 doc.set_first_page_footer("Feature Showcase — Cover Page");
119 doc.set_header("rdocx Feature Showcase");
120 doc.set_footer("Generated by rdocx");
121
122 // ── COVER PAGE ──
123 let bg = create_sample_png(612, 792, [20, 45, 90]);
124 doc.add_background_image(&bg, "cover_bg.png");
125
126 for _ in 0..3 {
127 doc.add_paragraph("");
128 }
129 {
130 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
131 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
132 }
133 {
134 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
135 p.add_run("Complete Feature Showcase")
136 .size(28.0)
137 .color("FFFFFF")
138 .italic(true);
139 }
140 doc.add_paragraph("");
141 {
142 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
143 p.add_run("Every feature of the rdocx Rust library")
144 .size(13.0)
145 .color("B0C4DE");
146 }
147 {
148 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
149 p.add_run("demonstrated in a single document.")
150 .size(13.0)
151 .color("B0C4DE");
152 }
153
154 // ── TABLE OF CONTENTS ──
155 doc.add_paragraph("").page_break_before(true);
156 doc.insert_toc(doc.content_count(), 3);
157
158 // ── SECTION 1: TEXT FORMATTING ──
159 doc.add_paragraph("").page_break_before(true);
160 doc.add_paragraph("1. Text Formatting").style("Heading1");
161
162 // Paragraph Alignment
163 doc.add_paragraph("1.1 Paragraph Alignment")
164 .style("Heading2");
165 doc.add_paragraph("Left-aligned paragraph (default).")
166 .alignment(Alignment::Left);
167 doc.add_paragraph("Center-aligned paragraph.")
168 .alignment(Alignment::Center);
169 doc.add_paragraph("Right-aligned paragraph.")
170 .alignment(Alignment::Right);
171 doc.add_paragraph(
172 "Justified paragraph — this text is long enough to demonstrate how justified alignment \
173 distributes extra space across word gaps so lines fill the full width of the text area.",
174 )
175 .alignment(Alignment::Justify);
176
177 doc.add_paragraph("");
178
179 // Run Formatting
180 doc.add_paragraph("1.2 Run Formatting").style("Heading2");
181 {
182 let mut p = doc.add_paragraph("");
183 p.add_run("Bold").bold(true);
184 p.add_run(" | ");
185 p.add_run("Italic").italic(true);
186 p.add_run(" | ");
187 p.add_run("Bold Italic").bold(true).italic(true);
188 p.add_run(" | ");
189 p.add_run("Underline").underline(true);
190 p.add_run(" | ");
191 p.add_run("Strikethrough").strike(true);
192 p.add_run(" | ");
193 p.add_run("Double Strike").double_strike(true);
194 }
195 {
196 let mut p = doc.add_paragraph("");
197 p.add_run("Red").color("FF0000");
198 p.add_run(" | ");
199 p.add_run("Blue").color("0000FF");
200 p.add_run(" | ");
201 p.add_run("Green").color("00AA00");
202 p.add_run(" | ");
203 p.add_run("Highlighted").highlight("FFFF00");
204 }
205 {
206 let mut p = doc.add_paragraph("");
207 p.add_run("8pt").size(8.0);
208 p.add_run(" | ");
209 p.add_run("11pt").size(11.0);
210 p.add_run(" | ");
211 p.add_run("16pt").size(16.0);
212 p.add_run(" | ");
213 p.add_run("24pt").size(24.0);
214 }
215 {
216 let mut p = doc.add_paragraph("");
217 p.add_run("Arial").font("Arial");
218 p.add_run(" | ");
219 p.add_run("Times New Roman").font("Times New Roman");
220 p.add_run(" | ");
221 p.add_run("Courier New").font("Courier New");
222 }
223 {
224 let mut p = doc.add_paragraph("");
225 p.add_run("H");
226 p.add_run("2").subscript();
227 p.add_run("O (subscript) | E=mc");
228 p.add_run("2").superscript();
229 p.add_run(" (superscript)");
230 }
231 {
232 let mut p = doc.add_paragraph("");
233 p.add_run("ALL CAPS").all_caps(true);
234 p.add_run(" | ");
235 p.add_run("Small Caps").small_caps(true);
236 p.add_run(" | ");
237 p.add_run("Expanded").character_spacing(Length::twips(40));
238 p.add_run(" | ");
239 p.add_run("Hidden text (not visible)").hidden(true);
240 }
241
242 doc.add_paragraph("");
243
244 // Underline Styles
245 doc.add_paragraph("1.3 Underline Styles").style("Heading2");
246 {
247 let mut p = doc.add_paragraph("");
248 p.add_run("Single ")
249 .underline_style(rdocx::UnderlineStyle::Single);
250 p.add_run("Double ")
251 .underline_style(rdocx::UnderlineStyle::Double);
252 p.add_run("Thick ")
253 .underline_style(rdocx::UnderlineStyle::Thick);
254 p.add_run("Dotted ")
255 .underline_style(rdocx::UnderlineStyle::Dotted);
256 p.add_run("Dash ")
257 .underline_style(rdocx::UnderlineStyle::Dash);
258 p.add_run("Wave ")
259 .underline_style(rdocx::UnderlineStyle::Wave);
260 }
261
262 // ── SECTION 2: PARAGRAPH FORMATTING ──
263 doc.add_paragraph("").page_break_before(true);
264 doc.add_paragraph("2. Paragraph Formatting")
265 .style("Heading1");
266
267 doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
268 doc.add_paragraph("Paragraph with light green shading")
269 .shading("E2EFDA");
270 doc.add_paragraph("Paragraph with bottom border")
271 .border_bottom(BorderStyle::Single, 6, "2E75B6");
272 doc.add_paragraph("Paragraph with all borders (red)")
273 .border_all(BorderStyle::Single, 4, "FF0000");
274
275 doc.add_paragraph("2.2 Indentation").style("Heading2");
276 doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
277 .indent_left(Length::inches(1.0))
278 .hanging_indent(Length::inches(0.5));
279 doc.add_paragraph("0.5-inch first-line indent on this paragraph")
280 .first_line_indent(Length::inches(0.5));
281 doc.add_paragraph("Both left and right indent (0.75 inches each)")
282 .indent_left(Length::inches(0.75))
283 .indent_right(Length::inches(0.75));
284
285 doc.add_paragraph("2.3 Spacing & Line Height")
286 .style("Heading2");
287 doc.add_paragraph("Extra space before (24pt) and after (12pt)")
288 .space_before(Length::pt(24.0))
289 .space_after(Length::pt(12.0));
290 doc.add_paragraph(
291 "Double line spacing paragraph. This text should have extra vertical space between \
292 lines to demonstrate line_spacing_multiple(2.0).",
293 )
294 .line_spacing_multiple(2.0);
295 doc.add_paragraph("Exact 20pt line spacing (fixed height).")
296 .line_spacing(20.0);
297
298 doc.add_paragraph("2.4 Pagination Controls")
299 .style("Heading2");
300 doc.add_paragraph("keep_with_next — this paragraph stays with the next")
301 .keep_with_next(true);
302 doc.add_paragraph("(This paragraph was kept with the one above.)");
303 doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
304 .keep_together(true);
305 doc.add_paragraph("widow_control — prevents widow/orphan lines")
306 .widow_control(true);
307
308 // ── SECTION 3: LISTS ──
309 doc.add_paragraph("").page_break_before(true);
310 doc.add_paragraph("3. Lists").style("Heading1");
311
312 doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
313 doc.add_bullet_list_item("First item", 0);
314 doc.add_bullet_list_item("Second item", 0);
315 doc.add_bullet_list_item("Nested level 1", 1);
316 doc.add_bullet_list_item("Nested level 2", 2);
317 doc.add_bullet_list_item("Back to level 1", 1);
318 doc.add_bullet_list_item("Third item", 0);
319
320 doc.add_paragraph("");
321
322 doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
323 doc.add_numbered_list_item("First numbered item", 0);
324 doc.add_numbered_list_item("Second numbered item", 0);
325 doc.add_numbered_list_item("Sub-item A", 1);
326 doc.add_numbered_list_item("Sub-item B", 1);
327 doc.add_numbered_list_item("Third numbered item", 0);
328
329 // ── SECTION 4: TAB STOPS ──
330 doc.add_paragraph("");
331 doc.add_paragraph("4. Tab Stops").style("Heading1");
332
333 doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
334 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
335 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
336 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
337 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
338 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
339
340 doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
341 doc.add_paragraph("Item\t\tPrice")
342 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
343 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
344 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
345 doc.add_paragraph("Widget\t\t$19.99")
346 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
347 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
348 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
349 doc.add_paragraph("Gadget\t\t$249.50")
350 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
351 .add_tab_stop_with_leader(
352 TabAlignment::Right,
353 Length::inches(4.0),
354 TabLeader::Underscore,
355 )
356 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
357
358 // ── SECTION 5: TABLES ──
359 doc.add_paragraph("").page_break_before(true);
360 doc.add_paragraph("5. Tables").style("Heading1");
361
362 doc.add_paragraph("5.1 Basic Table").style("Heading2");
363 {
364 let mut tbl = doc
365 .add_table(4, 3)
366 .borders(BorderStyle::Single, 4, "000000");
367 for col in 0..3 {
368 tbl.cell(0, col).unwrap().shading("2E75B6");
369 }
370 tbl.cell(0, 0).unwrap().set_text("Name");
371 tbl.cell(0, 1).unwrap().set_text("Role");
372 tbl.cell(0, 2).unwrap().set_text("Location");
373 tbl.cell(1, 0).unwrap().set_text("Walter White");
374 tbl.cell(1, 1).unwrap().set_text("CEO");
375 tbl.cell(1, 2).unwrap().set_text("Albuquerque");
376 tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
377 tbl.cell(2, 1).unwrap().set_text("CTO");
378 tbl.cell(2, 2).unwrap().set_text("Remote");
379 tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
380 tbl.cell(3, 1).unwrap().set_text("Security");
381 tbl.cell(3, 2).unwrap().set_text("Washington");
382 }
383
384 doc.add_paragraph("");
385
386 doc.add_paragraph("5.2 Column Span & Cell Shading")
387 .style("Heading2");
388 {
389 let mut tbl = doc
390 .add_table(3, 4)
391 .borders(BorderStyle::Single, 4, "000000")
392 .width_pct(100.0);
393 tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
394 tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
395 tbl.cell(1, 0).unwrap().set_text("Region");
396 tbl.cell(1, 0).unwrap().shading("D6E4F0");
397 tbl.cell(1, 1).unwrap().set_text("Q1");
398 tbl.cell(1, 1).unwrap().shading("D6E4F0");
399 tbl.cell(1, 2).unwrap().set_text("Q2");
400 tbl.cell(1, 2).unwrap().shading("D6E4F0");
401 tbl.cell(1, 3).unwrap().set_text("Total");
402 tbl.cell(1, 3).unwrap().shading("D6E4F0");
403 tbl.cell(2, 0).unwrap().set_text("Americas");
404 tbl.cell(2, 1).unwrap().set_text("$2.4M");
405 tbl.cell(2, 2).unwrap().set_text("$2.7M");
406 tbl.cell(2, 3).unwrap().set_text("$5.1M");
407 }
408
409 doc.add_paragraph("");
410
411 doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
412 {
413 let mut tbl = doc
414 .add_table(4, 3)
415 .borders(BorderStyle::Single, 4, "000000");
416 tbl.cell(0, 0).unwrap().set_text("Category");
417 tbl.cell(0, 0).unwrap().shading("E2EFDA");
418 tbl.cell(0, 1).unwrap().set_text("Item");
419 tbl.cell(0, 1).unwrap().shading("E2EFDA");
420 tbl.cell(0, 2).unwrap().set_text("Price");
421 tbl.cell(0, 2).unwrap().shading("E2EFDA");
422 tbl.cell(1, 0).unwrap().set_text("Hardware");
423 tbl.cell(1, 0).unwrap().v_merge_restart();
424 tbl.cell(1, 1).unwrap().set_text("Laptop");
425 tbl.cell(1, 2).unwrap().set_text("$1,200");
426 tbl.cell(2, 0).unwrap().v_merge_continue();
427 tbl.cell(2, 1).unwrap().set_text("Monitor");
428 tbl.cell(2, 2).unwrap().set_text("$450");
429 tbl.cell(3, 0).unwrap().set_text("Software");
430 tbl.cell(3, 1).unwrap().set_text("IDE License");
431 tbl.cell(3, 2).unwrap().set_text("$200/yr");
432 }
433
434 doc.add_paragraph("");
435
436 doc.add_paragraph("5.4 Nested Table").style("Heading2");
437 {
438 let mut tbl = doc
439 .add_table(2, 2)
440 .borders(BorderStyle::Single, 6, "2E75B6");
441 tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
442 tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
443 tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
444 {
445 let mut cell = tbl.cell(1, 1).unwrap();
446 cell.set_text("Nested table below:");
447 let mut nested = cell
448 .add_table(2, 2)
449 .borders(BorderStyle::Single, 2, "FF6600");
450 nested.cell(0, 0).unwrap().set_text("A");
451 nested.cell(0, 1).unwrap().set_text("B");
452 nested.cell(1, 0).unwrap().set_text("C");
453 nested.cell(1, 1).unwrap().set_text("D");
454 }
455 }
456
457 doc.add_paragraph("");
458
459 doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
460 .style("Heading2");
461 {
462 let mut tbl = doc
463 .add_table(2, 3)
464 .borders(BorderStyle::Single, 4, "666666");
465 tbl.row(0).unwrap().height(Length::pt(50.0)).header();
466 tbl.cell(0, 0).unwrap().set_text("Top");
467 tbl.cell(0, 0)
468 .unwrap()
469 .vertical_alignment(VerticalAlignment::Top)
470 .shading("FFF2CC");
471 tbl.cell(0, 1).unwrap().set_text("Center");
472 tbl.cell(0, 1)
473 .unwrap()
474 .vertical_alignment(VerticalAlignment::Center)
475 .shading("D9E2F3");
476 tbl.cell(0, 2).unwrap().set_text("Bottom");
477 tbl.cell(0, 2)
478 .unwrap()
479 .vertical_alignment(VerticalAlignment::Bottom)
480 .shading("E2EFDA");
481 tbl.row(1).unwrap().cant_split();
482 tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
483 tbl.cell(1, 0).unwrap().no_wrap();
484 tbl.cell(1, 1).unwrap().set_text("Fixed width");
485 tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
486 tbl.cell(1, 2).unwrap().set_text("Normal");
487 }
488
489 // ── SECTION 6: IMAGES ──
490 doc.add_paragraph("").page_break_before(true);
491 doc.add_paragraph("6. Images").style("Heading1");
492
493 doc.add_paragraph("6.1 Inline Image").style("Heading2");
494 doc.add_paragraph("A blue gradient image below:");
495 let img = create_sample_png(200, 50, [0, 80, 200]);
496 doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
497
498 doc.add_paragraph("");
499 doc.add_paragraph("6.2 Header Image").style("Heading2");
500 let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
501 doc.set_header_image(
502 &hdr_img,
503 "header_logo.png",
504 Length::inches(2.0),
505 Length::inches(0.2),
506 );
507 doc.add_paragraph("The header now contains an inline image (check the top of this page).");
508
509 doc.add_paragraph("");
510 doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
511
512 // ── SECTION 7: CONTENT MANIPULATION ──
513 doc.add_paragraph("").page_break_before(true);
514 doc.add_paragraph("7. Content Manipulation")
515 .style("Heading1");
516
517 doc.add_paragraph("7.1 Placeholder Replacement")
518 .style("Heading2");
519 doc.add_paragraph("Customer: {{customer}}");
520 doc.add_paragraph("Date: {{date}}");
521 doc.add_paragraph("Reference: {{ref_number}}");
522 {
523 let mut tbl = doc
524 .add_table(2, 2)
525 .borders(BorderStyle::Single, 4, "000000");
526 tbl.cell(0, 0).unwrap().set_text("Project");
527 tbl.cell(0, 0).unwrap().shading("D6E4F0");
528 tbl.cell(0, 1).unwrap().set_text("{{project}}");
529 tbl.cell(1, 0).unwrap().set_text("Status");
530 tbl.cell(1, 0).unwrap().shading("D6E4F0");
531 tbl.cell(1, 1).unwrap().set_text("{{status}}");
532 }
533
534 let mut replacements = HashMap::new();
535 replacements.insert("{{customer}}", "Tensorbee Inc.");
536 replacements.insert("{{date}}", "February 22, 2026");
537 replacements.insert("{{ref_number}}", "TB-2026-001");
538 replacements.insert("{{project}}", "Infrastructure Upgrade");
539 replacements.insert("{{status}}", "In Progress");
540 let n = doc.replace_all(&replacements);
541 doc.add_paragraph(&format!("({n} placeholders replaced above)"));
542
543 doc.add_paragraph("");
544
545 doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
546 doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
547 let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
548 doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
549
550 doc.add_paragraph("");
551
552 doc.add_paragraph("7.3 Content Insertion").style("Heading2");
553 doc.add_paragraph("Section A: First content.");
554 doc.add_paragraph("Section C: Third content.");
555 if let Some(idx) = doc.find_content_index("Section C") {
556 doc.insert_paragraph(
557 idx,
558 "Section B: Inserted between A and C via find_content_index().",
559 );
560 }
561
562 // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
563 doc.add_paragraph("").section_break(SectionBreak::NextPage);
564 doc.add_paragraph("8. Section Breaks & Orientation")
565 .style("Heading1");
566 doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
567
568 {
569 let mut tbl = doc
570 .add_table(3, 7)
571 .borders(BorderStyle::Single, 4, "2E75B6");
572 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
573 for (c, h) in headers.iter().enumerate() {
574 tbl.cell(0, c).unwrap().set_text(h);
575 tbl.cell(0, c).unwrap().shading("2E75B6");
576 }
577 let data = [
578 [
579 "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
580 ],
581 [
582 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
583 ],
584 ];
585 for (r, row) in data.iter().enumerate() {
586 for (c, v) in row.iter().enumerate() {
587 tbl.cell(r + 1, c).unwrap().set_text(v);
588 }
589 }
590 }
591
592 doc.add_paragraph("")
593 .section_break(SectionBreak::NextPage)
594 .section_landscape();
595
596 // ── SECTION 9: CUSTOM STYLES ──
597 doc.add_paragraph("9. Custom Styles").style("Heading1");
598 doc.add_style(
599 StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
600 .based_on("Normal")
601 .paragraph_properties(rdocx_oxml::properties::CT_PPr {
602 shading: Some(rdocx_oxml::properties::CT_Shd {
603 val: "clear".to_string(),
604 color: None,
605 fill: Some("FFF2CC".to_string()),
606 }),
607 ..Default::default()
608 })
609 .run_properties(rdocx_oxml::properties::CT_RPr {
610 bold: Some(true),
611 color: Some("C45911".to_string()),
612 ..Default::default()
613 }),
614 );
615 doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
616 .style("CustomHighlight");
617
618 doc.add_paragraph("");
619
620 // ── SECTION 10: DOCUMENT INTELLIGENCE ──
621 doc.add_paragraph("10. Document Intelligence API")
622 .style("Heading1");
623
624 let wc = doc.word_count();
625 let hc = doc.headings().len();
626 let ic = doc.images().len();
627 let lc = doc.links().len();
628 doc.add_paragraph(&format!("Word count: {wc}"));
629 doc.add_paragraph(&format!("Heading count: {hc}"));
630 doc.add_paragraph(&format!("Image count: {ic}"));
631 doc.add_paragraph(&format!("Link count: {lc}"));
632
633 let outline = doc.document_outline();
634 doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
635
636 let issues = doc.audit_accessibility();
637 doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
638 for issue in &issues {
639 doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
640 }
641
642 // ── SECTION 11: DOCUMENT MERGING ──
643 doc.add_paragraph("");
644 doc.add_paragraph("11. Document Merging").style("Heading1");
645 {
646 let mut other = Document::new();
647 other.add_paragraph("This paragraph was merged from another document using append().");
648 doc.append(&other);
649 }
650
651 doc.add_paragraph("");
652
653 // ── SUMMARY ──
654 doc.add_paragraph("Summary of Demonstrated Features")
655 .style("Heading1");
656 let features = [
657 "Page setup: size, margins, header/footer distance, gutter",
658 "Document metadata: title, author, subject, keywords",
659 "Headers and footers: text, images, different first page",
660 "Background images (full-page behind text)",
661 "Table of Contents generation with bookmarks",
662 "Text formatting: bold, italic, underline styles, strike, color, size, font",
663 "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
664 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
665 "Pagination controls: keep-with-next, keep-together, widow control",
666 "Bullet and numbered lists with nesting",
667 "Tab stops with dot/underscore/hyphen leaders",
668 "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
669 "Row properties: header rows, exact height, cant-split",
670 "Cell properties: width, no-wrap, vertical alignment",
671 "Inline images and header images",
672 "Placeholder replacement (single and batch)",
673 "Regex-based find and replace",
674 "Content insertion at specific positions",
675 "Section breaks with mixed portrait/landscape",
676 "Custom paragraph and character styles",
677 "Document intelligence: word count, headings, outline, images, links",
678 "Accessibility audit",
679 "Document merging (append)",
680 "Export: DOCX, PDF, HTML, Markdown",
681 ];
682 for f in &features {
683 doc.add_bullet_list_item(f, 0);
684 }
685 doc.add_paragraph("");
686 doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
687 .alignment(Alignment::Center)
688 .shading("E2EFDA")
689 .border_all(BorderStyle::Single, 2, "00AA00");
690
691 doc
692}Sourcepub fn insert_table(
&mut self,
index: usize,
rows: usize,
cols: usize,
) -> Table<'_>
pub fn insert_table( &mut self, index: usize, rows: usize, cols: usize, ) -> Table<'_>
Insert a table at the given body index.
Returns a mutable Table for further configuration.
A cols of 0 produces a table with no columns rather than panicking.
§Panics
Panics if index > content_count(). (Unlike Self::insert_document
and Self::insert_toc, which clamp an out-of-range index to the end.)
Examples found in repository?
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}Sourcepub fn find_content_index(&self, text: &str) -> Option<usize>
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?
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
97fn generate_feature_showcase(_samples_dir: &Path) -> Document {
98 let mut doc = Document::new();
99
100 // ── Page Setup & Metadata ──
101 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
102 doc.set_margins(
103 Length::inches(1.0),
104 Length::inches(1.0),
105 Length::inches(1.0),
106 Length::inches(1.0),
107 );
108 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
109 doc.set_gutter(Length::twips(0));
110 doc.set_title("rdocx Feature Showcase");
111 doc.set_author("rdocx Sample Generator");
112 doc.set_subject("Comprehensive feature demonstration");
113 doc.set_keywords("rdocx, docx, rust, sample, showcase");
114
115 // Headers & Footers with different first page
116 doc.set_different_first_page(true);
117 doc.set_first_page_header("rdocx");
118 doc.set_first_page_footer("Feature Showcase — Cover Page");
119 doc.set_header("rdocx Feature Showcase");
120 doc.set_footer("Generated by rdocx");
121
122 // ── COVER PAGE ──
123 let bg = create_sample_png(612, 792, [20, 45, 90]);
124 doc.add_background_image(&bg, "cover_bg.png");
125
126 for _ in 0..3 {
127 doc.add_paragraph("");
128 }
129 {
130 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
131 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
132 }
133 {
134 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
135 p.add_run("Complete Feature Showcase")
136 .size(28.0)
137 .color("FFFFFF")
138 .italic(true);
139 }
140 doc.add_paragraph("");
141 {
142 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
143 p.add_run("Every feature of the rdocx Rust library")
144 .size(13.0)
145 .color("B0C4DE");
146 }
147 {
148 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
149 p.add_run("demonstrated in a single document.")
150 .size(13.0)
151 .color("B0C4DE");
152 }
153
154 // ── TABLE OF CONTENTS ──
155 doc.add_paragraph("").page_break_before(true);
156 doc.insert_toc(doc.content_count(), 3);
157
158 // ── SECTION 1: TEXT FORMATTING ──
159 doc.add_paragraph("").page_break_before(true);
160 doc.add_paragraph("1. Text Formatting").style("Heading1");
161
162 // Paragraph Alignment
163 doc.add_paragraph("1.1 Paragraph Alignment")
164 .style("Heading2");
165 doc.add_paragraph("Left-aligned paragraph (default).")
166 .alignment(Alignment::Left);
167 doc.add_paragraph("Center-aligned paragraph.")
168 .alignment(Alignment::Center);
169 doc.add_paragraph("Right-aligned paragraph.")
170 .alignment(Alignment::Right);
171 doc.add_paragraph(
172 "Justified paragraph — this text is long enough to demonstrate how justified alignment \
173 distributes extra space across word gaps so lines fill the full width of the text area.",
174 )
175 .alignment(Alignment::Justify);
176
177 doc.add_paragraph("");
178
179 // Run Formatting
180 doc.add_paragraph("1.2 Run Formatting").style("Heading2");
181 {
182 let mut p = doc.add_paragraph("");
183 p.add_run("Bold").bold(true);
184 p.add_run(" | ");
185 p.add_run("Italic").italic(true);
186 p.add_run(" | ");
187 p.add_run("Bold Italic").bold(true).italic(true);
188 p.add_run(" | ");
189 p.add_run("Underline").underline(true);
190 p.add_run(" | ");
191 p.add_run("Strikethrough").strike(true);
192 p.add_run(" | ");
193 p.add_run("Double Strike").double_strike(true);
194 }
195 {
196 let mut p = doc.add_paragraph("");
197 p.add_run("Red").color("FF0000");
198 p.add_run(" | ");
199 p.add_run("Blue").color("0000FF");
200 p.add_run(" | ");
201 p.add_run("Green").color("00AA00");
202 p.add_run(" | ");
203 p.add_run("Highlighted").highlight("FFFF00");
204 }
205 {
206 let mut p = doc.add_paragraph("");
207 p.add_run("8pt").size(8.0);
208 p.add_run(" | ");
209 p.add_run("11pt").size(11.0);
210 p.add_run(" | ");
211 p.add_run("16pt").size(16.0);
212 p.add_run(" | ");
213 p.add_run("24pt").size(24.0);
214 }
215 {
216 let mut p = doc.add_paragraph("");
217 p.add_run("Arial").font("Arial");
218 p.add_run(" | ");
219 p.add_run("Times New Roman").font("Times New Roman");
220 p.add_run(" | ");
221 p.add_run("Courier New").font("Courier New");
222 }
223 {
224 let mut p = doc.add_paragraph("");
225 p.add_run("H");
226 p.add_run("2").subscript();
227 p.add_run("O (subscript) | E=mc");
228 p.add_run("2").superscript();
229 p.add_run(" (superscript)");
230 }
231 {
232 let mut p = doc.add_paragraph("");
233 p.add_run("ALL CAPS").all_caps(true);
234 p.add_run(" | ");
235 p.add_run("Small Caps").small_caps(true);
236 p.add_run(" | ");
237 p.add_run("Expanded").character_spacing(Length::twips(40));
238 p.add_run(" | ");
239 p.add_run("Hidden text (not visible)").hidden(true);
240 }
241
242 doc.add_paragraph("");
243
244 // Underline Styles
245 doc.add_paragraph("1.3 Underline Styles").style("Heading2");
246 {
247 let mut p = doc.add_paragraph("");
248 p.add_run("Single ")
249 .underline_style(rdocx::UnderlineStyle::Single);
250 p.add_run("Double ")
251 .underline_style(rdocx::UnderlineStyle::Double);
252 p.add_run("Thick ")
253 .underline_style(rdocx::UnderlineStyle::Thick);
254 p.add_run("Dotted ")
255 .underline_style(rdocx::UnderlineStyle::Dotted);
256 p.add_run("Dash ")
257 .underline_style(rdocx::UnderlineStyle::Dash);
258 p.add_run("Wave ")
259 .underline_style(rdocx::UnderlineStyle::Wave);
260 }
261
262 // ── SECTION 2: PARAGRAPH FORMATTING ──
263 doc.add_paragraph("").page_break_before(true);
264 doc.add_paragraph("2. Paragraph Formatting")
265 .style("Heading1");
266
267 doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
268 doc.add_paragraph("Paragraph with light green shading")
269 .shading("E2EFDA");
270 doc.add_paragraph("Paragraph with bottom border")
271 .border_bottom(BorderStyle::Single, 6, "2E75B6");
272 doc.add_paragraph("Paragraph with all borders (red)")
273 .border_all(BorderStyle::Single, 4, "FF0000");
274
275 doc.add_paragraph("2.2 Indentation").style("Heading2");
276 doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
277 .indent_left(Length::inches(1.0))
278 .hanging_indent(Length::inches(0.5));
279 doc.add_paragraph("0.5-inch first-line indent on this paragraph")
280 .first_line_indent(Length::inches(0.5));
281 doc.add_paragraph("Both left and right indent (0.75 inches each)")
282 .indent_left(Length::inches(0.75))
283 .indent_right(Length::inches(0.75));
284
285 doc.add_paragraph("2.3 Spacing & Line Height")
286 .style("Heading2");
287 doc.add_paragraph("Extra space before (24pt) and after (12pt)")
288 .space_before(Length::pt(24.0))
289 .space_after(Length::pt(12.0));
290 doc.add_paragraph(
291 "Double line spacing paragraph. This text should have extra vertical space between \
292 lines to demonstrate line_spacing_multiple(2.0).",
293 )
294 .line_spacing_multiple(2.0);
295 doc.add_paragraph("Exact 20pt line spacing (fixed height).")
296 .line_spacing(20.0);
297
298 doc.add_paragraph("2.4 Pagination Controls")
299 .style("Heading2");
300 doc.add_paragraph("keep_with_next — this paragraph stays with the next")
301 .keep_with_next(true);
302 doc.add_paragraph("(This paragraph was kept with the one above.)");
303 doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
304 .keep_together(true);
305 doc.add_paragraph("widow_control — prevents widow/orphan lines")
306 .widow_control(true);
307
308 // ── SECTION 3: LISTS ──
309 doc.add_paragraph("").page_break_before(true);
310 doc.add_paragraph("3. Lists").style("Heading1");
311
312 doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
313 doc.add_bullet_list_item("First item", 0);
314 doc.add_bullet_list_item("Second item", 0);
315 doc.add_bullet_list_item("Nested level 1", 1);
316 doc.add_bullet_list_item("Nested level 2", 2);
317 doc.add_bullet_list_item("Back to level 1", 1);
318 doc.add_bullet_list_item("Third item", 0);
319
320 doc.add_paragraph("");
321
322 doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
323 doc.add_numbered_list_item("First numbered item", 0);
324 doc.add_numbered_list_item("Second numbered item", 0);
325 doc.add_numbered_list_item("Sub-item A", 1);
326 doc.add_numbered_list_item("Sub-item B", 1);
327 doc.add_numbered_list_item("Third numbered item", 0);
328
329 // ── SECTION 4: TAB STOPS ──
330 doc.add_paragraph("");
331 doc.add_paragraph("4. Tab Stops").style("Heading1");
332
333 doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
334 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
335 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
336 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
337 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
338 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
339
340 doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
341 doc.add_paragraph("Item\t\tPrice")
342 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
343 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
344 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
345 doc.add_paragraph("Widget\t\t$19.99")
346 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
347 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
348 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
349 doc.add_paragraph("Gadget\t\t$249.50")
350 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
351 .add_tab_stop_with_leader(
352 TabAlignment::Right,
353 Length::inches(4.0),
354 TabLeader::Underscore,
355 )
356 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
357
358 // ── SECTION 5: TABLES ──
359 doc.add_paragraph("").page_break_before(true);
360 doc.add_paragraph("5. Tables").style("Heading1");
361
362 doc.add_paragraph("5.1 Basic Table").style("Heading2");
363 {
364 let mut tbl = doc
365 .add_table(4, 3)
366 .borders(BorderStyle::Single, 4, "000000");
367 for col in 0..3 {
368 tbl.cell(0, col).unwrap().shading("2E75B6");
369 }
370 tbl.cell(0, 0).unwrap().set_text("Name");
371 tbl.cell(0, 1).unwrap().set_text("Role");
372 tbl.cell(0, 2).unwrap().set_text("Location");
373 tbl.cell(1, 0).unwrap().set_text("Walter White");
374 tbl.cell(1, 1).unwrap().set_text("CEO");
375 tbl.cell(1, 2).unwrap().set_text("Albuquerque");
376 tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
377 tbl.cell(2, 1).unwrap().set_text("CTO");
378 tbl.cell(2, 2).unwrap().set_text("Remote");
379 tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
380 tbl.cell(3, 1).unwrap().set_text("Security");
381 tbl.cell(3, 2).unwrap().set_text("Washington");
382 }
383
384 doc.add_paragraph("");
385
386 doc.add_paragraph("5.2 Column Span & Cell Shading")
387 .style("Heading2");
388 {
389 let mut tbl = doc
390 .add_table(3, 4)
391 .borders(BorderStyle::Single, 4, "000000")
392 .width_pct(100.0);
393 tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
394 tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
395 tbl.cell(1, 0).unwrap().set_text("Region");
396 tbl.cell(1, 0).unwrap().shading("D6E4F0");
397 tbl.cell(1, 1).unwrap().set_text("Q1");
398 tbl.cell(1, 1).unwrap().shading("D6E4F0");
399 tbl.cell(1, 2).unwrap().set_text("Q2");
400 tbl.cell(1, 2).unwrap().shading("D6E4F0");
401 tbl.cell(1, 3).unwrap().set_text("Total");
402 tbl.cell(1, 3).unwrap().shading("D6E4F0");
403 tbl.cell(2, 0).unwrap().set_text("Americas");
404 tbl.cell(2, 1).unwrap().set_text("$2.4M");
405 tbl.cell(2, 2).unwrap().set_text("$2.7M");
406 tbl.cell(2, 3).unwrap().set_text("$5.1M");
407 }
408
409 doc.add_paragraph("");
410
411 doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
412 {
413 let mut tbl = doc
414 .add_table(4, 3)
415 .borders(BorderStyle::Single, 4, "000000");
416 tbl.cell(0, 0).unwrap().set_text("Category");
417 tbl.cell(0, 0).unwrap().shading("E2EFDA");
418 tbl.cell(0, 1).unwrap().set_text("Item");
419 tbl.cell(0, 1).unwrap().shading("E2EFDA");
420 tbl.cell(0, 2).unwrap().set_text("Price");
421 tbl.cell(0, 2).unwrap().shading("E2EFDA");
422 tbl.cell(1, 0).unwrap().set_text("Hardware");
423 tbl.cell(1, 0).unwrap().v_merge_restart();
424 tbl.cell(1, 1).unwrap().set_text("Laptop");
425 tbl.cell(1, 2).unwrap().set_text("$1,200");
426 tbl.cell(2, 0).unwrap().v_merge_continue();
427 tbl.cell(2, 1).unwrap().set_text("Monitor");
428 tbl.cell(2, 2).unwrap().set_text("$450");
429 tbl.cell(3, 0).unwrap().set_text("Software");
430 tbl.cell(3, 1).unwrap().set_text("IDE License");
431 tbl.cell(3, 2).unwrap().set_text("$200/yr");
432 }
433
434 doc.add_paragraph("");
435
436 doc.add_paragraph("5.4 Nested Table").style("Heading2");
437 {
438 let mut tbl = doc
439 .add_table(2, 2)
440 .borders(BorderStyle::Single, 6, "2E75B6");
441 tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
442 tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
443 tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
444 {
445 let mut cell = tbl.cell(1, 1).unwrap();
446 cell.set_text("Nested table below:");
447 let mut nested = cell
448 .add_table(2, 2)
449 .borders(BorderStyle::Single, 2, "FF6600");
450 nested.cell(0, 0).unwrap().set_text("A");
451 nested.cell(0, 1).unwrap().set_text("B");
452 nested.cell(1, 0).unwrap().set_text("C");
453 nested.cell(1, 1).unwrap().set_text("D");
454 }
455 }
456
457 doc.add_paragraph("");
458
459 doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
460 .style("Heading2");
461 {
462 let mut tbl = doc
463 .add_table(2, 3)
464 .borders(BorderStyle::Single, 4, "666666");
465 tbl.row(0).unwrap().height(Length::pt(50.0)).header();
466 tbl.cell(0, 0).unwrap().set_text("Top");
467 tbl.cell(0, 0)
468 .unwrap()
469 .vertical_alignment(VerticalAlignment::Top)
470 .shading("FFF2CC");
471 tbl.cell(0, 1).unwrap().set_text("Center");
472 tbl.cell(0, 1)
473 .unwrap()
474 .vertical_alignment(VerticalAlignment::Center)
475 .shading("D9E2F3");
476 tbl.cell(0, 2).unwrap().set_text("Bottom");
477 tbl.cell(0, 2)
478 .unwrap()
479 .vertical_alignment(VerticalAlignment::Bottom)
480 .shading("E2EFDA");
481 tbl.row(1).unwrap().cant_split();
482 tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
483 tbl.cell(1, 0).unwrap().no_wrap();
484 tbl.cell(1, 1).unwrap().set_text("Fixed width");
485 tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
486 tbl.cell(1, 2).unwrap().set_text("Normal");
487 }
488
489 // ── SECTION 6: IMAGES ──
490 doc.add_paragraph("").page_break_before(true);
491 doc.add_paragraph("6. Images").style("Heading1");
492
493 doc.add_paragraph("6.1 Inline Image").style("Heading2");
494 doc.add_paragraph("A blue gradient image below:");
495 let img = create_sample_png(200, 50, [0, 80, 200]);
496 doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
497
498 doc.add_paragraph("");
499 doc.add_paragraph("6.2 Header Image").style("Heading2");
500 let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
501 doc.set_header_image(
502 &hdr_img,
503 "header_logo.png",
504 Length::inches(2.0),
505 Length::inches(0.2),
506 );
507 doc.add_paragraph("The header now contains an inline image (check the top of this page).");
508
509 doc.add_paragraph("");
510 doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
511
512 // ── SECTION 7: CONTENT MANIPULATION ──
513 doc.add_paragraph("").page_break_before(true);
514 doc.add_paragraph("7. Content Manipulation")
515 .style("Heading1");
516
517 doc.add_paragraph("7.1 Placeholder Replacement")
518 .style("Heading2");
519 doc.add_paragraph("Customer: {{customer}}");
520 doc.add_paragraph("Date: {{date}}");
521 doc.add_paragraph("Reference: {{ref_number}}");
522 {
523 let mut tbl = doc
524 .add_table(2, 2)
525 .borders(BorderStyle::Single, 4, "000000");
526 tbl.cell(0, 0).unwrap().set_text("Project");
527 tbl.cell(0, 0).unwrap().shading("D6E4F0");
528 tbl.cell(0, 1).unwrap().set_text("{{project}}");
529 tbl.cell(1, 0).unwrap().set_text("Status");
530 tbl.cell(1, 0).unwrap().shading("D6E4F0");
531 tbl.cell(1, 1).unwrap().set_text("{{status}}");
532 }
533
534 let mut replacements = HashMap::new();
535 replacements.insert("{{customer}}", "Tensorbee Inc.");
536 replacements.insert("{{date}}", "February 22, 2026");
537 replacements.insert("{{ref_number}}", "TB-2026-001");
538 replacements.insert("{{project}}", "Infrastructure Upgrade");
539 replacements.insert("{{status}}", "In Progress");
540 let n = doc.replace_all(&replacements);
541 doc.add_paragraph(&format!("({n} placeholders replaced above)"));
542
543 doc.add_paragraph("");
544
545 doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
546 doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
547 let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
548 doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
549
550 doc.add_paragraph("");
551
552 doc.add_paragraph("7.3 Content Insertion").style("Heading2");
553 doc.add_paragraph("Section A: First content.");
554 doc.add_paragraph("Section C: Third content.");
555 if let Some(idx) = doc.find_content_index("Section C") {
556 doc.insert_paragraph(
557 idx,
558 "Section B: Inserted between A and C via find_content_index().",
559 );
560 }
561
562 // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
563 doc.add_paragraph("").section_break(SectionBreak::NextPage);
564 doc.add_paragraph("8. Section Breaks & Orientation")
565 .style("Heading1");
566 doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
567
568 {
569 let mut tbl = doc
570 .add_table(3, 7)
571 .borders(BorderStyle::Single, 4, "2E75B6");
572 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
573 for (c, h) in headers.iter().enumerate() {
574 tbl.cell(0, c).unwrap().set_text(h);
575 tbl.cell(0, c).unwrap().shading("2E75B6");
576 }
577 let data = [
578 [
579 "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
580 ],
581 [
582 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
583 ],
584 ];
585 for (r, row) in data.iter().enumerate() {
586 for (c, v) in row.iter().enumerate() {
587 tbl.cell(r + 1, c).unwrap().set_text(v);
588 }
589 }
590 }
591
592 doc.add_paragraph("")
593 .section_break(SectionBreak::NextPage)
594 .section_landscape();
595
596 // ── SECTION 9: CUSTOM STYLES ──
597 doc.add_paragraph("9. Custom Styles").style("Heading1");
598 doc.add_style(
599 StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
600 .based_on("Normal")
601 .paragraph_properties(rdocx_oxml::properties::CT_PPr {
602 shading: Some(rdocx_oxml::properties::CT_Shd {
603 val: "clear".to_string(),
604 color: None,
605 fill: Some("FFF2CC".to_string()),
606 }),
607 ..Default::default()
608 })
609 .run_properties(rdocx_oxml::properties::CT_RPr {
610 bold: Some(true),
611 color: Some("C45911".to_string()),
612 ..Default::default()
613 }),
614 );
615 doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
616 .style("CustomHighlight");
617
618 doc.add_paragraph("");
619
620 // ── SECTION 10: DOCUMENT INTELLIGENCE ──
621 doc.add_paragraph("10. Document Intelligence API")
622 .style("Heading1");
623
624 let wc = doc.word_count();
625 let hc = doc.headings().len();
626 let ic = doc.images().len();
627 let lc = doc.links().len();
628 doc.add_paragraph(&format!("Word count: {wc}"));
629 doc.add_paragraph(&format!("Heading count: {hc}"));
630 doc.add_paragraph(&format!("Image count: {ic}"));
631 doc.add_paragraph(&format!("Link count: {lc}"));
632
633 let outline = doc.document_outline();
634 doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
635
636 let issues = doc.audit_accessibility();
637 doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
638 for issue in &issues {
639 doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
640 }
641
642 // ── SECTION 11: DOCUMENT MERGING ──
643 doc.add_paragraph("");
644 doc.add_paragraph("11. Document Merging").style("Heading1");
645 {
646 let mut other = Document::new();
647 other.add_paragraph("This paragraph was merged from another document using append().");
648 doc.append(&other);
649 }
650
651 doc.add_paragraph("");
652
653 // ── SUMMARY ──
654 doc.add_paragraph("Summary of Demonstrated Features")
655 .style("Heading1");
656 let features = [
657 "Page setup: size, margins, header/footer distance, gutter",
658 "Document metadata: title, author, subject, keywords",
659 "Headers and footers: text, images, different first page",
660 "Background images (full-page behind text)",
661 "Table of Contents generation with bookmarks",
662 "Text formatting: bold, italic, underline styles, strike, color, size, font",
663 "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
664 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
665 "Pagination controls: keep-with-next, keep-together, widow control",
666 "Bullet and numbered lists with nesting",
667 "Tab stops with dot/underscore/hyphen leaders",
668 "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
669 "Row properties: header rows, exact height, cant-split",
670 "Cell properties: width, no-wrap, vertical alignment",
671 "Inline images and header images",
672 "Placeholder replacement (single and batch)",
673 "Regex-based find and replace",
674 "Content insertion at specific positions",
675 "Section breaks with mixed portrait/landscape",
676 "Custom paragraph and character styles",
677 "Document intelligence: word count, headings, outline, images, links",
678 "Accessibility audit",
679 "Document merging (append)",
680 "Export: DOCX, PDF, HTML, Markdown",
681 ];
682 for f in &features {
683 doc.add_bullet_list_item(f, 0);
684 }
685 doc.add_paragraph("");
686 doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
687 .alignment(Alignment::Center)
688 .shading("E2EFDA")
689 .border_all(BorderStyle::Single, 2, "00AA00");
690
691 doc
692}Sourcepub fn remove_content(&mut self, index: usize) -> bool
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?
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}Sourcepub fn add_picture(
&mut self,
image_data: &[u8],
image_filename: &str,
width: Length,
height: Length,
) -> Paragraph<'_>
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?
97fn generate_feature_showcase(_samples_dir: &Path) -> Document {
98 let mut doc = Document::new();
99
100 // ── Page Setup & Metadata ──
101 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
102 doc.set_margins(
103 Length::inches(1.0),
104 Length::inches(1.0),
105 Length::inches(1.0),
106 Length::inches(1.0),
107 );
108 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
109 doc.set_gutter(Length::twips(0));
110 doc.set_title("rdocx Feature Showcase");
111 doc.set_author("rdocx Sample Generator");
112 doc.set_subject("Comprehensive feature demonstration");
113 doc.set_keywords("rdocx, docx, rust, sample, showcase");
114
115 // Headers & Footers with different first page
116 doc.set_different_first_page(true);
117 doc.set_first_page_header("rdocx");
118 doc.set_first_page_footer("Feature Showcase — Cover Page");
119 doc.set_header("rdocx Feature Showcase");
120 doc.set_footer("Generated by rdocx");
121
122 // ── COVER PAGE ──
123 let bg = create_sample_png(612, 792, [20, 45, 90]);
124 doc.add_background_image(&bg, "cover_bg.png");
125
126 for _ in 0..3 {
127 doc.add_paragraph("");
128 }
129 {
130 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
131 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
132 }
133 {
134 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
135 p.add_run("Complete Feature Showcase")
136 .size(28.0)
137 .color("FFFFFF")
138 .italic(true);
139 }
140 doc.add_paragraph("");
141 {
142 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
143 p.add_run("Every feature of the rdocx Rust library")
144 .size(13.0)
145 .color("B0C4DE");
146 }
147 {
148 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
149 p.add_run("demonstrated in a single document.")
150 .size(13.0)
151 .color("B0C4DE");
152 }
153
154 // ── TABLE OF CONTENTS ──
155 doc.add_paragraph("").page_break_before(true);
156 doc.insert_toc(doc.content_count(), 3);
157
158 // ── SECTION 1: TEXT FORMATTING ──
159 doc.add_paragraph("").page_break_before(true);
160 doc.add_paragraph("1. Text Formatting").style("Heading1");
161
162 // Paragraph Alignment
163 doc.add_paragraph("1.1 Paragraph Alignment")
164 .style("Heading2");
165 doc.add_paragraph("Left-aligned paragraph (default).")
166 .alignment(Alignment::Left);
167 doc.add_paragraph("Center-aligned paragraph.")
168 .alignment(Alignment::Center);
169 doc.add_paragraph("Right-aligned paragraph.")
170 .alignment(Alignment::Right);
171 doc.add_paragraph(
172 "Justified paragraph — this text is long enough to demonstrate how justified alignment \
173 distributes extra space across word gaps so lines fill the full width of the text area.",
174 )
175 .alignment(Alignment::Justify);
176
177 doc.add_paragraph("");
178
179 // Run Formatting
180 doc.add_paragraph("1.2 Run Formatting").style("Heading2");
181 {
182 let mut p = doc.add_paragraph("");
183 p.add_run("Bold").bold(true);
184 p.add_run(" | ");
185 p.add_run("Italic").italic(true);
186 p.add_run(" | ");
187 p.add_run("Bold Italic").bold(true).italic(true);
188 p.add_run(" | ");
189 p.add_run("Underline").underline(true);
190 p.add_run(" | ");
191 p.add_run("Strikethrough").strike(true);
192 p.add_run(" | ");
193 p.add_run("Double Strike").double_strike(true);
194 }
195 {
196 let mut p = doc.add_paragraph("");
197 p.add_run("Red").color("FF0000");
198 p.add_run(" | ");
199 p.add_run("Blue").color("0000FF");
200 p.add_run(" | ");
201 p.add_run("Green").color("00AA00");
202 p.add_run(" | ");
203 p.add_run("Highlighted").highlight("FFFF00");
204 }
205 {
206 let mut p = doc.add_paragraph("");
207 p.add_run("8pt").size(8.0);
208 p.add_run(" | ");
209 p.add_run("11pt").size(11.0);
210 p.add_run(" | ");
211 p.add_run("16pt").size(16.0);
212 p.add_run(" | ");
213 p.add_run("24pt").size(24.0);
214 }
215 {
216 let mut p = doc.add_paragraph("");
217 p.add_run("Arial").font("Arial");
218 p.add_run(" | ");
219 p.add_run("Times New Roman").font("Times New Roman");
220 p.add_run(" | ");
221 p.add_run("Courier New").font("Courier New");
222 }
223 {
224 let mut p = doc.add_paragraph("");
225 p.add_run("H");
226 p.add_run("2").subscript();
227 p.add_run("O (subscript) | E=mc");
228 p.add_run("2").superscript();
229 p.add_run(" (superscript)");
230 }
231 {
232 let mut p = doc.add_paragraph("");
233 p.add_run("ALL CAPS").all_caps(true);
234 p.add_run(" | ");
235 p.add_run("Small Caps").small_caps(true);
236 p.add_run(" | ");
237 p.add_run("Expanded").character_spacing(Length::twips(40));
238 p.add_run(" | ");
239 p.add_run("Hidden text (not visible)").hidden(true);
240 }
241
242 doc.add_paragraph("");
243
244 // Underline Styles
245 doc.add_paragraph("1.3 Underline Styles").style("Heading2");
246 {
247 let mut p = doc.add_paragraph("");
248 p.add_run("Single ")
249 .underline_style(rdocx::UnderlineStyle::Single);
250 p.add_run("Double ")
251 .underline_style(rdocx::UnderlineStyle::Double);
252 p.add_run("Thick ")
253 .underline_style(rdocx::UnderlineStyle::Thick);
254 p.add_run("Dotted ")
255 .underline_style(rdocx::UnderlineStyle::Dotted);
256 p.add_run("Dash ")
257 .underline_style(rdocx::UnderlineStyle::Dash);
258 p.add_run("Wave ")
259 .underline_style(rdocx::UnderlineStyle::Wave);
260 }
261
262 // ── SECTION 2: PARAGRAPH FORMATTING ──
263 doc.add_paragraph("").page_break_before(true);
264 doc.add_paragraph("2. Paragraph Formatting")
265 .style("Heading1");
266
267 doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
268 doc.add_paragraph("Paragraph with light green shading")
269 .shading("E2EFDA");
270 doc.add_paragraph("Paragraph with bottom border")
271 .border_bottom(BorderStyle::Single, 6, "2E75B6");
272 doc.add_paragraph("Paragraph with all borders (red)")
273 .border_all(BorderStyle::Single, 4, "FF0000");
274
275 doc.add_paragraph("2.2 Indentation").style("Heading2");
276 doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
277 .indent_left(Length::inches(1.0))
278 .hanging_indent(Length::inches(0.5));
279 doc.add_paragraph("0.5-inch first-line indent on this paragraph")
280 .first_line_indent(Length::inches(0.5));
281 doc.add_paragraph("Both left and right indent (0.75 inches each)")
282 .indent_left(Length::inches(0.75))
283 .indent_right(Length::inches(0.75));
284
285 doc.add_paragraph("2.3 Spacing & Line Height")
286 .style("Heading2");
287 doc.add_paragraph("Extra space before (24pt) and after (12pt)")
288 .space_before(Length::pt(24.0))
289 .space_after(Length::pt(12.0));
290 doc.add_paragraph(
291 "Double line spacing paragraph. This text should have extra vertical space between \
292 lines to demonstrate line_spacing_multiple(2.0).",
293 )
294 .line_spacing_multiple(2.0);
295 doc.add_paragraph("Exact 20pt line spacing (fixed height).")
296 .line_spacing(20.0);
297
298 doc.add_paragraph("2.4 Pagination Controls")
299 .style("Heading2");
300 doc.add_paragraph("keep_with_next — this paragraph stays with the next")
301 .keep_with_next(true);
302 doc.add_paragraph("(This paragraph was kept with the one above.)");
303 doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
304 .keep_together(true);
305 doc.add_paragraph("widow_control — prevents widow/orphan lines")
306 .widow_control(true);
307
308 // ── SECTION 3: LISTS ──
309 doc.add_paragraph("").page_break_before(true);
310 doc.add_paragraph("3. Lists").style("Heading1");
311
312 doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
313 doc.add_bullet_list_item("First item", 0);
314 doc.add_bullet_list_item("Second item", 0);
315 doc.add_bullet_list_item("Nested level 1", 1);
316 doc.add_bullet_list_item("Nested level 2", 2);
317 doc.add_bullet_list_item("Back to level 1", 1);
318 doc.add_bullet_list_item("Third item", 0);
319
320 doc.add_paragraph("");
321
322 doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
323 doc.add_numbered_list_item("First numbered item", 0);
324 doc.add_numbered_list_item("Second numbered item", 0);
325 doc.add_numbered_list_item("Sub-item A", 1);
326 doc.add_numbered_list_item("Sub-item B", 1);
327 doc.add_numbered_list_item("Third numbered item", 0);
328
329 // ── SECTION 4: TAB STOPS ──
330 doc.add_paragraph("");
331 doc.add_paragraph("4. Tab Stops").style("Heading1");
332
333 doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
334 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
335 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
336 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
337 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
338 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
339
340 doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
341 doc.add_paragraph("Item\t\tPrice")
342 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
343 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
344 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
345 doc.add_paragraph("Widget\t\t$19.99")
346 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
347 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
348 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
349 doc.add_paragraph("Gadget\t\t$249.50")
350 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
351 .add_tab_stop_with_leader(
352 TabAlignment::Right,
353 Length::inches(4.0),
354 TabLeader::Underscore,
355 )
356 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
357
358 // ── SECTION 5: TABLES ──
359 doc.add_paragraph("").page_break_before(true);
360 doc.add_paragraph("5. Tables").style("Heading1");
361
362 doc.add_paragraph("5.1 Basic Table").style("Heading2");
363 {
364 let mut tbl = doc
365 .add_table(4, 3)
366 .borders(BorderStyle::Single, 4, "000000");
367 for col in 0..3 {
368 tbl.cell(0, col).unwrap().shading("2E75B6");
369 }
370 tbl.cell(0, 0).unwrap().set_text("Name");
371 tbl.cell(0, 1).unwrap().set_text("Role");
372 tbl.cell(0, 2).unwrap().set_text("Location");
373 tbl.cell(1, 0).unwrap().set_text("Walter White");
374 tbl.cell(1, 1).unwrap().set_text("CEO");
375 tbl.cell(1, 2).unwrap().set_text("Albuquerque");
376 tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
377 tbl.cell(2, 1).unwrap().set_text("CTO");
378 tbl.cell(2, 2).unwrap().set_text("Remote");
379 tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
380 tbl.cell(3, 1).unwrap().set_text("Security");
381 tbl.cell(3, 2).unwrap().set_text("Washington");
382 }
383
384 doc.add_paragraph("");
385
386 doc.add_paragraph("5.2 Column Span & Cell Shading")
387 .style("Heading2");
388 {
389 let mut tbl = doc
390 .add_table(3, 4)
391 .borders(BorderStyle::Single, 4, "000000")
392 .width_pct(100.0);
393 tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
394 tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
395 tbl.cell(1, 0).unwrap().set_text("Region");
396 tbl.cell(1, 0).unwrap().shading("D6E4F0");
397 tbl.cell(1, 1).unwrap().set_text("Q1");
398 tbl.cell(1, 1).unwrap().shading("D6E4F0");
399 tbl.cell(1, 2).unwrap().set_text("Q2");
400 tbl.cell(1, 2).unwrap().shading("D6E4F0");
401 tbl.cell(1, 3).unwrap().set_text("Total");
402 tbl.cell(1, 3).unwrap().shading("D6E4F0");
403 tbl.cell(2, 0).unwrap().set_text("Americas");
404 tbl.cell(2, 1).unwrap().set_text("$2.4M");
405 tbl.cell(2, 2).unwrap().set_text("$2.7M");
406 tbl.cell(2, 3).unwrap().set_text("$5.1M");
407 }
408
409 doc.add_paragraph("");
410
411 doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
412 {
413 let mut tbl = doc
414 .add_table(4, 3)
415 .borders(BorderStyle::Single, 4, "000000");
416 tbl.cell(0, 0).unwrap().set_text("Category");
417 tbl.cell(0, 0).unwrap().shading("E2EFDA");
418 tbl.cell(0, 1).unwrap().set_text("Item");
419 tbl.cell(0, 1).unwrap().shading("E2EFDA");
420 tbl.cell(0, 2).unwrap().set_text("Price");
421 tbl.cell(0, 2).unwrap().shading("E2EFDA");
422 tbl.cell(1, 0).unwrap().set_text("Hardware");
423 tbl.cell(1, 0).unwrap().v_merge_restart();
424 tbl.cell(1, 1).unwrap().set_text("Laptop");
425 tbl.cell(1, 2).unwrap().set_text("$1,200");
426 tbl.cell(2, 0).unwrap().v_merge_continue();
427 tbl.cell(2, 1).unwrap().set_text("Monitor");
428 tbl.cell(2, 2).unwrap().set_text("$450");
429 tbl.cell(3, 0).unwrap().set_text("Software");
430 tbl.cell(3, 1).unwrap().set_text("IDE License");
431 tbl.cell(3, 2).unwrap().set_text("$200/yr");
432 }
433
434 doc.add_paragraph("");
435
436 doc.add_paragraph("5.4 Nested Table").style("Heading2");
437 {
438 let mut tbl = doc
439 .add_table(2, 2)
440 .borders(BorderStyle::Single, 6, "2E75B6");
441 tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
442 tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
443 tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
444 {
445 let mut cell = tbl.cell(1, 1).unwrap();
446 cell.set_text("Nested table below:");
447 let mut nested = cell
448 .add_table(2, 2)
449 .borders(BorderStyle::Single, 2, "FF6600");
450 nested.cell(0, 0).unwrap().set_text("A");
451 nested.cell(0, 1).unwrap().set_text("B");
452 nested.cell(1, 0).unwrap().set_text("C");
453 nested.cell(1, 1).unwrap().set_text("D");
454 }
455 }
456
457 doc.add_paragraph("");
458
459 doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
460 .style("Heading2");
461 {
462 let mut tbl = doc
463 .add_table(2, 3)
464 .borders(BorderStyle::Single, 4, "666666");
465 tbl.row(0).unwrap().height(Length::pt(50.0)).header();
466 tbl.cell(0, 0).unwrap().set_text("Top");
467 tbl.cell(0, 0)
468 .unwrap()
469 .vertical_alignment(VerticalAlignment::Top)
470 .shading("FFF2CC");
471 tbl.cell(0, 1).unwrap().set_text("Center");
472 tbl.cell(0, 1)
473 .unwrap()
474 .vertical_alignment(VerticalAlignment::Center)
475 .shading("D9E2F3");
476 tbl.cell(0, 2).unwrap().set_text("Bottom");
477 tbl.cell(0, 2)
478 .unwrap()
479 .vertical_alignment(VerticalAlignment::Bottom)
480 .shading("E2EFDA");
481 tbl.row(1).unwrap().cant_split();
482 tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
483 tbl.cell(1, 0).unwrap().no_wrap();
484 tbl.cell(1, 1).unwrap().set_text("Fixed width");
485 tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
486 tbl.cell(1, 2).unwrap().set_text("Normal");
487 }
488
489 // ── SECTION 6: IMAGES ──
490 doc.add_paragraph("").page_break_before(true);
491 doc.add_paragraph("6. Images").style("Heading1");
492
493 doc.add_paragraph("6.1 Inline Image").style("Heading2");
494 doc.add_paragraph("A blue gradient image below:");
495 let img = create_sample_png(200, 50, [0, 80, 200]);
496 doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
497
498 doc.add_paragraph("");
499 doc.add_paragraph("6.2 Header Image").style("Heading2");
500 let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
501 doc.set_header_image(
502 &hdr_img,
503 "header_logo.png",
504 Length::inches(2.0),
505 Length::inches(0.2),
506 );
507 doc.add_paragraph("The header now contains an inline image (check the top of this page).");
508
509 doc.add_paragraph("");
510 doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
511
512 // ── SECTION 7: CONTENT MANIPULATION ──
513 doc.add_paragraph("").page_break_before(true);
514 doc.add_paragraph("7. Content Manipulation")
515 .style("Heading1");
516
517 doc.add_paragraph("7.1 Placeholder Replacement")
518 .style("Heading2");
519 doc.add_paragraph("Customer: {{customer}}");
520 doc.add_paragraph("Date: {{date}}");
521 doc.add_paragraph("Reference: {{ref_number}}");
522 {
523 let mut tbl = doc
524 .add_table(2, 2)
525 .borders(BorderStyle::Single, 4, "000000");
526 tbl.cell(0, 0).unwrap().set_text("Project");
527 tbl.cell(0, 0).unwrap().shading("D6E4F0");
528 tbl.cell(0, 1).unwrap().set_text("{{project}}");
529 tbl.cell(1, 0).unwrap().set_text("Status");
530 tbl.cell(1, 0).unwrap().shading("D6E4F0");
531 tbl.cell(1, 1).unwrap().set_text("{{status}}");
532 }
533
534 let mut replacements = HashMap::new();
535 replacements.insert("{{customer}}", "Tensorbee Inc.");
536 replacements.insert("{{date}}", "February 22, 2026");
537 replacements.insert("{{ref_number}}", "TB-2026-001");
538 replacements.insert("{{project}}", "Infrastructure Upgrade");
539 replacements.insert("{{status}}", "In Progress");
540 let n = doc.replace_all(&replacements);
541 doc.add_paragraph(&format!("({n} placeholders replaced above)"));
542
543 doc.add_paragraph("");
544
545 doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
546 doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
547 let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
548 doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
549
550 doc.add_paragraph("");
551
552 doc.add_paragraph("7.3 Content Insertion").style("Heading2");
553 doc.add_paragraph("Section A: First content.");
554 doc.add_paragraph("Section C: Third content.");
555 if let Some(idx) = doc.find_content_index("Section C") {
556 doc.insert_paragraph(
557 idx,
558 "Section B: Inserted between A and C via find_content_index().",
559 );
560 }
561
562 // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
563 doc.add_paragraph("").section_break(SectionBreak::NextPage);
564 doc.add_paragraph("8. Section Breaks & Orientation")
565 .style("Heading1");
566 doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
567
568 {
569 let mut tbl = doc
570 .add_table(3, 7)
571 .borders(BorderStyle::Single, 4, "2E75B6");
572 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
573 for (c, h) in headers.iter().enumerate() {
574 tbl.cell(0, c).unwrap().set_text(h);
575 tbl.cell(0, c).unwrap().shading("2E75B6");
576 }
577 let data = [
578 [
579 "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
580 ],
581 [
582 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
583 ],
584 ];
585 for (r, row) in data.iter().enumerate() {
586 for (c, v) in row.iter().enumerate() {
587 tbl.cell(r + 1, c).unwrap().set_text(v);
588 }
589 }
590 }
591
592 doc.add_paragraph("")
593 .section_break(SectionBreak::NextPage)
594 .section_landscape();
595
596 // ── SECTION 9: CUSTOM STYLES ──
597 doc.add_paragraph("9. Custom Styles").style("Heading1");
598 doc.add_style(
599 StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
600 .based_on("Normal")
601 .paragraph_properties(rdocx_oxml::properties::CT_PPr {
602 shading: Some(rdocx_oxml::properties::CT_Shd {
603 val: "clear".to_string(),
604 color: None,
605 fill: Some("FFF2CC".to_string()),
606 }),
607 ..Default::default()
608 })
609 .run_properties(rdocx_oxml::properties::CT_RPr {
610 bold: Some(true),
611 color: Some("C45911".to_string()),
612 ..Default::default()
613 }),
614 );
615 doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
616 .style("CustomHighlight");
617
618 doc.add_paragraph("");
619
620 // ── SECTION 10: DOCUMENT INTELLIGENCE ──
621 doc.add_paragraph("10. Document Intelligence API")
622 .style("Heading1");
623
624 let wc = doc.word_count();
625 let hc = doc.headings().len();
626 let ic = doc.images().len();
627 let lc = doc.links().len();
628 doc.add_paragraph(&format!("Word count: {wc}"));
629 doc.add_paragraph(&format!("Heading count: {hc}"));
630 doc.add_paragraph(&format!("Image count: {ic}"));
631 doc.add_paragraph(&format!("Link count: {lc}"));
632
633 let outline = doc.document_outline();
634 doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
635
636 let issues = doc.audit_accessibility();
637 doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
638 for issue in &issues {
639 doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
640 }
641
642 // ── SECTION 11: DOCUMENT MERGING ──
643 doc.add_paragraph("");
644 doc.add_paragraph("11. Document Merging").style("Heading1");
645 {
646 let mut other = Document::new();
647 other.add_paragraph("This paragraph was merged from another document using append().");
648 doc.append(&other);
649 }
650
651 doc.add_paragraph("");
652
653 // ── SUMMARY ──
654 doc.add_paragraph("Summary of Demonstrated Features")
655 .style("Heading1");
656 let features = [
657 "Page setup: size, margins, header/footer distance, gutter",
658 "Document metadata: title, author, subject, keywords",
659 "Headers and footers: text, images, different first page",
660 "Background images (full-page behind text)",
661 "Table of Contents generation with bookmarks",
662 "Text formatting: bold, italic, underline styles, strike, color, size, font",
663 "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
664 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
665 "Pagination controls: keep-with-next, keep-together, widow control",
666 "Bullet and numbered lists with nesting",
667 "Tab stops with dot/underscore/hyphen leaders",
668 "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
669 "Row properties: header rows, exact height, cant-split",
670 "Cell properties: width, no-wrap, vertical alignment",
671 "Inline images and header images",
672 "Placeholder replacement (single and batch)",
673 "Regex-based find and replace",
674 "Content insertion at specific positions",
675 "Section breaks with mixed portrait/landscape",
676 "Custom paragraph and character styles",
677 "Document intelligence: word count, headings, outline, images, links",
678 "Accessibility audit",
679 "Document merging (append)",
680 "Export: DOCX, PDF, HTML, Markdown",
681 ];
682 for f in &features {
683 doc.add_bullet_list_item(f, 0);
684 }
685 doc.add_paragraph("");
686 doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
687 .alignment(Alignment::Center)
688 .shading("E2EFDA")
689 .border_all(BorderStyle::Single, 2, "00AA00");
690
691 doc
692}
693
694// =============================================================================
695// 2. PROPOSAL DOCUMENT — Deep navy + gold scheme
696// =============================================================================
697fn generate_proposal(_samples_dir: &Path) -> Document {
698 let mut doc = Document::new();
699
700 // Colors: Navy #1B2A4A, Gold #C5922E, Light #F4F1EB
701 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
702 doc.set_margins(
703 Length::inches(1.0),
704 Length::inches(1.0),
705 Length::inches(1.0),
706 Length::inches(1.0),
707 );
708 doc.set_title("AI Platform Modernization Proposal");
709 doc.set_author("Walter White");
710 doc.set_subject("Technology Proposal");
711 doc.set_keywords("proposal, AI, modernization, Tensorbee");
712
713 // Banner header
714 let logo_img = create_logo_png(220, 48);
715 let banner = build_header_banner_xml(
716 "rId1",
717 &BannerOpts {
718 bg_color: "1B2A4A",
719 banner_width: 7772400,
720 banner_height: 914400,
721 logo_width: 2011680,
722 logo_height: 438912,
723 logo_x_offset: 295125,
724 logo_y_offset: 237744,
725 },
726 );
727 doc.set_raw_header_with_images(
728 banner,
729 &[("rId1", &logo_img, "logo.png")],
730 rdocx_oxml::header_footer::HdrFtrType::Default,
731 );
732 doc.set_different_first_page(true);
733 let cover_banner = build_header_banner_xml(
734 "rId1",
735 &BannerOpts {
736 bg_color: "C5922E",
737 banner_width: 7772400,
738 banner_height: 914400,
739 logo_width: 2011680,
740 logo_height: 438912,
741 logo_x_offset: 295125,
742 logo_y_offset: 237744,
743 },
744 );
745 doc.set_raw_header_with_images(
746 cover_banner,
747 &[("rId1", &logo_img, "logo.png")],
748 rdocx_oxml::header_footer::HdrFtrType::First,
749 );
750 doc.set_margins(
751 Length::twips(2292),
752 Length::twips(1440),
753 Length::twips(1440),
754 Length::twips(1440),
755 );
756 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
757 doc.set_footer("Tensorbee — Confidential");
758
759 // Custom styles
760 doc.add_style(
761 StyleBuilder::paragraph("ProposalTitle", "Proposal Title")
762 .based_on("Normal")
763 .run_properties(rdocx_oxml::properties::CT_RPr {
764 bold: Some(true),
765 font_ascii: Some("Georgia".to_string()),
766 font_hansi: Some("Georgia".to_string()),
767 sz: Some(rdocx_oxml::HalfPoint(56)), // half-points → 28pt
768 color: Some("1B2A4A".to_string()),
769 ..Default::default()
770 }),
771 );
772
773 // ── Cover Page ──
774 for _ in 0..4 {
775 doc.add_paragraph("");
776 }
777 doc.add_paragraph("AI Platform Modernization")
778 .style("ProposalTitle")
779 .alignment(Alignment::Center);
780 doc.add_paragraph("")
781 .alignment(Alignment::Center)
782 .border_bottom(BorderStyle::Single, 8, "C5922E");
783 {
784 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
785 p.add_run("Prepared for Global Financial Corp.")
786 .size(14.0)
787 .color("666666")
788 .italic(true);
789 }
790 {
791 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
792 p.add_run("Prepared by: Walter White, CEO — Tensorbee")
793 .size(12.0)
794 .color("888888");
795 }
796 {
797 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
798 p.add_run("February 22, 2026").size(12.0).color("888888");
799 }
800
801 // ── TOC ──
802 doc.add_paragraph("").page_break_before(true);
803 doc.add_paragraph("Table of Contents").style("Heading1");
804 // We'll add a manual-style TOC since the insert_toc goes at a specific position
805 doc.insert_toc(doc.content_count(), 2);
806
807 // ── Executive Summary ──
808 doc.add_paragraph("").page_break_before(true);
809 doc.add_paragraph("Executive Summary").style("Heading1");
810 doc.add_paragraph(
811 "Tensorbee proposes a comprehensive modernization of Global Financial Corp's AI platform, \
812 transitioning from legacy batch-processing systems to a real-time inference architecture. \
813 This transformation will reduce model deployment time from weeks to hours, improve \
814 prediction accuracy by an estimated 23%, and deliver $4.2M in annual operational savings.",
815 )
816 .first_line_indent(Length::inches(0.3));
817 doc.add_paragraph(
818 "The project spans three phases over 18 months, with a total investment of $2.8M. \
819 Tensorbee brings deep expertise in ML infrastructure, having successfully completed \
820 similar transformations for three Fortune 500 financial institutions.",
821 )
822 .first_line_indent(Length::inches(0.3));
823
824 // Key metrics table
825 doc.add_paragraph("");
826 {
827 let mut tbl = doc
828 .add_table(5, 2)
829 .borders(BorderStyle::Single, 4, "1B2A4A")
830 .width_pct(80.0);
831 tbl.cell(0, 0).unwrap().set_text("Key Metric");
832 tbl.cell(0, 0).unwrap().shading("1B2A4A");
833 tbl.cell(0, 1).unwrap().set_text("Value");
834 tbl.cell(0, 1).unwrap().shading("1B2A4A");
835 let rows = [
836 ("Total Investment", "$2.8M"),
837 ("Annual Savings", "$4.2M"),
838 ("ROI (Year 1)", "150%"),
839 ("Timeline", "18 months"),
840 ];
841 for (i, (k, v)) in rows.iter().enumerate() {
842 tbl.cell(i + 1, 0).unwrap().set_text(k);
843 if i % 2 == 0 {
844 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
845 }
846 tbl.cell(i + 1, 1).unwrap().set_text(v);
847 if i % 2 == 0 {
848 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
849 }
850 }
851 }
852
853 // ── Problem Statement ──
854 doc.add_paragraph("").page_break_before(true);
855 doc.add_paragraph("Problem Statement").style("Heading1");
856 doc.add_paragraph(
857 "Global Financial Corp's current AI infrastructure faces three critical challenges:",
858 );
859 doc.add_numbered_list_item("Deployment Latency — New models take 3-4 weeks to deploy to production, compared to the industry benchmark of 2-3 days.", 0);
860 doc.add_numbered_list_item("Scalability Constraints — The batch processing architecture cannot handle real-time inference demands during peak trading hours.", 0);
861 doc.add_numbered_list_item("Technical Debt — Legacy Python 2.7 codebases and manual deployment scripts create reliability risks and slow iteration speed.", 0);
862
863 // ── Proposed Solution ──
864 doc.add_paragraph("").page_break_before(true);
865 doc.add_paragraph("Proposed Solution").style("Heading1");
866
867 doc.add_paragraph("Phase 1: Foundation (Months 1-6)")
868 .style("Heading2");
869 doc.add_bullet_list_item("Deploy Kubernetes-based ML serving infrastructure", 0);
870 doc.add_bullet_list_item("Implement CI/CD pipeline for model deployment", 0);
871 doc.add_bullet_list_item("Migrate top 5 production models to new platform", 0);
872
873 doc.add_paragraph("Phase 2: Expansion (Months 7-12)")
874 .style("Heading2");
875 doc.add_bullet_list_item(
876 "Real-time feature engineering pipeline (Apache Kafka + Flink)",
877 0,
878 );
879 doc.add_bullet_list_item("A/B testing framework for model variants", 0);
880 doc.add_bullet_list_item("Automated model monitoring and drift detection", 0);
881
882 doc.add_paragraph("Phase 3: Optimization (Months 13-18)")
883 .style("Heading2");
884 doc.add_bullet_list_item("GPU inference optimization (TensorRT/ONNX Runtime)", 0);
885 doc.add_bullet_list_item("Multi-region deployment for latency reduction", 0);
886 doc.add_bullet_list_item(
887 "Self-service model deployment portal for data science teams",
888 0,
889 );
890
891 // ── Budget ──
892 doc.add_paragraph("Budget Breakdown").style("Heading1");
893 {
894 let mut tbl = doc
895 .add_table(6, 3)
896 .borders(BorderStyle::Single, 4, "1B2A4A")
897 .width_pct(100.0);
898 tbl.cell(0, 0).unwrap().set_text("Category");
899 tbl.cell(0, 0).unwrap().shading("1B2A4A");
900 tbl.cell(0, 1).unwrap().set_text("Cost");
901 tbl.cell(0, 1).unwrap().shading("1B2A4A");
902 tbl.cell(0, 2).unwrap().set_text("Notes");
903 tbl.cell(0, 2).unwrap().shading("1B2A4A");
904 let items = [
905 (
906 "Infrastructure",
907 "$450,000",
908 "Cloud compute + GPU instances",
909 ),
910 ("Engineering Services", "$1,600,000", "12 FTE x 18 months"),
911 (
912 "Software Licenses",
913 "$350,000",
914 "Kafka, monitoring, CI/CD tools",
915 ),
916 (
917 "Training & Enablement",
918 "$200,000",
919 "Team training, documentation",
920 ),
921 ("Contingency (10%)", "$200,000", "Risk buffer"),
922 ];
923 for (i, (cat, cost, note)) in items.iter().enumerate() {
924 tbl.cell(i + 1, 0).unwrap().set_text(cat);
925 tbl.cell(i + 1, 1).unwrap().set_text(cost);
926 tbl.cell(i + 1, 2).unwrap().set_text(note);
927 if i % 2 == 0 {
928 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
929 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
930 tbl.cell(i + 1, 2).unwrap().shading("F4F1EB");
931 }
932 }
933 }
934
935 // ── Closing ──
936 doc.add_paragraph("");
937 doc.add_paragraph("Next Steps").style("Heading1");
938 doc.add_numbered_list_item(
939 "Schedule technical deep-dive with GFC engineering team (Week 1)",
940 0,
941 );
942 doc.add_numbered_list_item("Finalize scope and sign SOW (Week 2-3)", 0);
943 doc.add_numbered_list_item("Kick off Phase 1 with joint planning session (Week 4)", 0);
944
945 doc.add_paragraph("");
946 {
947 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
948 p.add_run("Walter White")
949 .bold(true)
950 .size(14.0)
951 .color("1B2A4A");
952 }
953 {
954 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
955 p.add_run("CEO, Tensorbee | walter@tensorbee.com")
956 .size(10.0)
957 .color("888888");
958 }
959
960 doc
961}
962
963// =============================================================================
964// 3. QUOTE / BILL OF MATERIALS — Teal + orange scheme
965// =============================================================================
966fn generate_quote(_samples_dir: &Path) -> Document {
967 let mut doc = Document::new();
968
969 // Colors: Teal #008B8B, Dark #1A3C3C, Orange #E07020, Light #F0F8F8
970 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
971 doc.set_margins(
972 Length::inches(0.75),
973 Length::inches(0.75),
974 Length::inches(0.75),
975 Length::inches(0.75),
976 );
977 doc.set_title("Quotation QT-2026-0147");
978 doc.set_author("Walter White");
979 doc.set_keywords("quote, BOM, Tensorbee");
980
981 doc.set_header("Tensorbee — Quotation");
982 doc.set_footer("QT-2026-0147 | Page");
983
984 // ── Header Block ──
985 {
986 let mut p = doc.add_paragraph("").alignment(Alignment::Left);
987 p.add_run("TENSORBEE")
988 .bold(true)
989 .size(28.0)
990 .color("008B8B")
991 .font("Helvetica");
992 }
993 doc.add_paragraph("123 Innovation Drive, Suite 400")
994 .alignment(Alignment::Left);
995 doc.add_paragraph("San Francisco, CA 94105 | +1 (415) 555-0199")
996 .alignment(Alignment::Left);
997 doc.add_paragraph("")
998 .border_bottom(BorderStyle::Single, 8, "008B8B");
999
1000 // Quote meta
1001 doc.add_paragraph("");
1002 {
1003 let mut tbl = doc.add_table(4, 4).width_pct(100.0);
1004 tbl.cell(0, 0).unwrap().set_text("QUOTATION");
1005 tbl.cell(0, 0).unwrap().shading("008B8B").grid_span(2);
1006 tbl.cell(0, 2).unwrap().set_text("DATE");
1007 tbl.cell(0, 2).unwrap().shading("008B8B");
1008 tbl.cell(0, 3).unwrap().set_text("VALID UNTIL");
1009 tbl.cell(0, 3).unwrap().shading("008B8B");
1010 tbl.cell(1, 0).unwrap().set_text("Quote #:");
1011 tbl.cell(1, 1).unwrap().set_text("QT-2026-0147");
1012 tbl.cell(1, 2).unwrap().set_text("Feb 22, 2026");
1013 tbl.cell(1, 3).unwrap().set_text("Mar 22, 2026");
1014 tbl.cell(2, 0).unwrap().set_text("Prepared by:");
1015 tbl.cell(2, 1).unwrap().set_text("Walter White");
1016 tbl.cell(2, 2).unwrap().set_text("Payment:");
1017 tbl.cell(2, 3).unwrap().set_text("Net 30");
1018 tbl.cell(3, 0).unwrap().set_text("Customer:");
1019 tbl.cell(3, 1).unwrap().set_text("Meridian Dynamics LLC");
1020 tbl.cell(3, 2).unwrap().set_text("Currency:");
1021 tbl.cell(3, 3).unwrap().set_text("USD");
1022 }
1023
1024 doc.add_paragraph("");
1025
1026 // ── Bill of Materials ──
1027 {
1028 let mut p = doc.add_paragraph("");
1029 p.add_run("Bill of Materials")
1030 .bold(true)
1031 .size(16.0)
1032 .color("1A3C3C");
1033 }
1034 doc.add_paragraph("");
1035
1036 {
1037 let mut tbl = doc
1038 .add_table(10, 6)
1039 .borders(BorderStyle::Single, 2, "008B8B")
1040 .width_pct(100.0)
1041 .layout_fixed();
1042 // Headers
1043 let hdrs = [
1044 "#",
1045 "Part Number",
1046 "Description",
1047 "Qty",
1048 "Unit Price",
1049 "Total",
1050 ];
1051 for (c, h) in hdrs.iter().enumerate() {
1052 tbl.cell(0, c).unwrap().set_text(h);
1053 tbl.cell(0, c).unwrap().shading("008B8B");
1054 }
1055
1056 let items: Vec<(&str, &str, &str, &str, &str)> = vec![
1057 (
1058 "TB-GPU-A100",
1059 "NVIDIA A100 80GB GPU",
1060 "4",
1061 "$12,500.00",
1062 "$50,000.00",
1063 ),
1064 (
1065 "TB-SRV-R750",
1066 "Dell R750xa Server Chassis",
1067 "2",
1068 "$8,200.00",
1069 "$16,400.00",
1070 ),
1071 (
1072 "TB-RAM-256G",
1073 "256GB DDR5 ECC Memory Module",
1074 "8",
1075 "$890.00",
1076 "$7,120.00",
1077 ),
1078 (
1079 "TB-SSD-3840",
1080 "3.84TB NVMe U.2 SSD",
1081 "8",
1082 "$1,150.00",
1083 "$9,200.00",
1084 ),
1085 (
1086 "TB-NET-CX7",
1087 "ConnectX-7 200GbE NIC",
1088 "4",
1089 "$1,800.00",
1090 "$7,200.00",
1091 ),
1092 (
1093 "TB-SW-48P",
1094 "48-Port 100GbE Switch",
1095 "1",
1096 "$22,000.00",
1097 "$22,000.00",
1098 ),
1099 (
1100 "TB-CAB-RACK",
1101 "42U Server Rack + PDU",
1102 "1",
1103 "$4,500.00",
1104 "$4,500.00",
1105 ),
1106 (
1107 "TB-SVC-INST",
1108 "Installation & Configuration",
1109 "1",
1110 "$8,500.00",
1111 "$8,500.00",
1112 ),
1113 (
1114 "TB-SVC-SUPP",
1115 "3-Year Premium Support",
1116 "1",
1117 "$15,000.00",
1118 "$15,000.00",
1119 ),
1120 ];
1121
1122 for (i, (pn, desc, qty, unit, total)) in items.iter().enumerate() {
1123 let row = i + 1;
1124 tbl.cell(row, 0).unwrap().set_text(&format!("{}", i + 1));
1125 tbl.cell(row, 1).unwrap().set_text(pn);
1126 tbl.cell(row, 2).unwrap().set_text(desc);
1127 tbl.cell(row, 3).unwrap().set_text(qty);
1128 tbl.cell(row, 4).unwrap().set_text(unit);
1129 tbl.cell(row, 5).unwrap().set_text(total);
1130 if i % 2 == 0 {
1131 for c in 0..6 {
1132 tbl.cell(row, c).unwrap().shading("F0F8F8");
1133 }
1134 }
1135 }
1136 }
1137
1138 doc.add_paragraph("");
1139
1140 // ── Totals ──
1141 {
1142 let mut tbl = doc
1143 .add_table(4, 2)
1144 .borders(BorderStyle::Single, 2, "008B8B")
1145 .width(Length::inches(3.5))
1146 .alignment(Alignment::Right);
1147 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1148 tbl.cell(0, 1).unwrap().set_text("$139,920.00");
1149 tbl.cell(1, 0).unwrap().set_text("Shipping & Handling");
1150 tbl.cell(1, 1).unwrap().set_text("$2,500.00");
1151 tbl.cell(2, 0).unwrap().set_text("Tax (8.625%)");
1152 tbl.cell(2, 1).unwrap().set_text("$12,068.10");
1153 tbl.cell(3, 0).unwrap().set_text("TOTAL");
1154 tbl.cell(3, 0).unwrap().shading("E07020");
1155 tbl.cell(3, 1).unwrap().set_text("$154,488.10");
1156 tbl.cell(3, 1).unwrap().shading("E07020");
1157 }
1158
1159 doc.add_paragraph("");
1160
1161 // ── Terms & Conditions ──
1162 {
1163 let mut p = doc.add_paragraph("");
1164 p.add_run("Terms & Conditions")
1165 .bold(true)
1166 .size(14.0)
1167 .color("1A3C3C");
1168 }
1169 doc.add_numbered_list_item(
1170 "This quotation is valid for 30 calendar days from the date of issue.",
1171 0,
1172 );
1173 doc.add_numbered_list_item(
1174 "All prices are in USD and exclusive of applicable taxes unless stated.",
1175 0,
1176 );
1177 doc.add_numbered_list_item("Standard lead time is 4-6 weeks from PO receipt.", 0);
1178 doc.add_numbered_list_item(
1179 "Payment terms: Net 30 from invoice date. 2% discount for payment within 10 days.",
1180 0,
1181 );
1182 doc.add_numbered_list_item(
1183 "Warranty: 3-year manufacturer warranty on all hardware components.",
1184 0,
1185 );
1186 doc.add_numbered_list_item(
1187 "Returns subject to 15% restocking fee if initiated after 14 days.",
1188 0,
1189 );
1190
1191 doc.add_paragraph("");
1192 doc.add_paragraph("")
1193 .border_bottom(BorderStyle::Single, 4, "008B8B");
1194 doc.add_paragraph("");
1195 {
1196 let mut p = doc.add_paragraph("");
1197 p.add_run("Accepted by: ").bold(true);
1198 p.add_run("___________________________ Date: ___________");
1199 }
1200 {
1201 let mut p = doc.add_paragraph("");
1202 p.add_run("Print Name: ").bold(true);
1203 p.add_run("___________________________ Title: ___________");
1204 }
1205
1206 doc
1207}
1208
1209// =============================================================================
1210// 4. INVOICE — Crimson + charcoal scheme
1211// =============================================================================
1212fn generate_invoice(_samples_dir: &Path) -> Document {
1213 let mut doc = Document::new();
1214
1215 // Colors: Crimson #B22222, Charcoal #333333, Light #FAF0F0
1216 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1217 doc.set_margins(
1218 Length::inches(0.75),
1219 Length::inches(0.75),
1220 Length::inches(0.75),
1221 Length::inches(0.75),
1222 );
1223 doc.set_title("Invoice INV-2026-0392");
1224 doc.set_author("Walter White");
1225 doc.set_keywords("invoice, Tensorbee");
1226
1227 doc.set_footer("Tensorbee — Thank you for your business!");
1228
1229 // ── Company & Invoice Header ──
1230 {
1231 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1232 // Company name left, INVOICE right
1233 {
1234 let mut cell = tbl.cell(0, 0).unwrap();
1235 let mut p = cell.add_paragraph("");
1236 p.add_run("TENSORBEE")
1237 .bold(true)
1238 .size(32.0)
1239 .color("B22222")
1240 .font("Helvetica");
1241 }
1242 {
1243 let mut cell = tbl.cell(0, 1).unwrap();
1244 let mut p = cell.add_paragraph("");
1245 p.add_run("INVOICE").bold(true).size(32.0).color("333333");
1246 }
1247 tbl.cell(1, 0)
1248 .unwrap()
1249 .set_text("123 Innovation Drive, Suite 400");
1250 tbl.cell(1, 1).unwrap().set_text("Invoice #: INV-2026-0392");
1251 tbl.cell(2, 0).unwrap().set_text("San Francisco, CA 94105");
1252 tbl.cell(2, 1).unwrap().set_text("Date: February 22, 2026");
1253 tbl.cell(3, 0).unwrap().set_text("walter@tensorbee.com");
1254 tbl.cell(3, 1).unwrap().set_text("Due Date: March 24, 2026");
1255 }
1256
1257 doc.add_paragraph("")
1258 .border_bottom(BorderStyle::Thick, 8, "B22222");
1259 doc.add_paragraph("");
1260
1261 // ── Bill To / Ship To ──
1262 {
1263 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1264 tbl.cell(0, 0).unwrap().set_text("BILL TO");
1265 tbl.cell(0, 0).unwrap().shading("B22222");
1266 tbl.cell(0, 1).unwrap().set_text("SHIP TO");
1267 tbl.cell(0, 1).unwrap().shading("B22222");
1268 tbl.cell(1, 0).unwrap().set_text("Meridian Dynamics LLC");
1269 tbl.cell(1, 1).unwrap().set_text("Meridian Dynamics LLC");
1270 tbl.cell(2, 0).unwrap().set_text("456 Enterprise Blvd");
1271 tbl.cell(2, 1).unwrap().set_text("Attn: Server Room B");
1272 tbl.cell(3, 0).unwrap().set_text("Austin, TX 78701");
1273 tbl.cell(3, 1)
1274 .unwrap()
1275 .set_text("456 Enterprise Blvd, Austin, TX 78701");
1276 }
1277
1278 doc.add_paragraph("");
1279
1280 // ── Line Items ──
1281 {
1282 let mut tbl = doc
1283 .add_table(8, 5)
1284 .borders(BorderStyle::Single, 2, "B22222")
1285 .width_pct(100.0);
1286 let hdrs = ["Description", "Qty", "Unit Price", "Tax", "Amount"];
1287 for (c, h) in hdrs.iter().enumerate() {
1288 tbl.cell(0, c).unwrap().set_text(h);
1289 tbl.cell(0, c).unwrap().shading("B22222");
1290 }
1291 let items = [
1292 (
1293 "ML Infrastructure Setup — Phase 1",
1294 "1",
1295 "$45,000.00",
1296 "$3,881.25",
1297 "$48,881.25",
1298 ),
1299 (
1300 "Data Pipeline Development (160 hrs)",
1301 "160",
1302 "$225.00",
1303 "$3,105.00",
1304 "$39,105.00",
1305 ),
1306 (
1307 "GPU Cluster Configuration",
1308 "1",
1309 "$12,000.00",
1310 "$1,035.00",
1311 "$13,035.00",
1312 ),
1313 (
1314 "API Gateway Implementation",
1315 "1",
1316 "$18,500.00",
1317 "$1,595.63",
1318 "$20,095.63",
1319 ),
1320 (
1321 "Load Testing & QA (80 hrs)",
1322 "80",
1323 "$195.00",
1324 "$1,345.50",
1325 "$16,945.50",
1326 ),
1327 (
1328 "Documentation & Training",
1329 "1",
1330 "$8,000.00",
1331 "$690.00",
1332 "$8,690.00",
1333 ),
1334 (
1335 "Project Management (3 months)",
1336 "3",
1337 "$6,500.00",
1338 "$1,679.63",
1339 "$21,179.63",
1340 ),
1341 ];
1342 for (i, (desc, qty, unit, tax, amt)) in items.iter().enumerate() {
1343 let r = i + 1;
1344 tbl.cell(r, 0).unwrap().set_text(desc);
1345 tbl.cell(r, 1).unwrap().set_text(qty);
1346 tbl.cell(r, 2).unwrap().set_text(unit);
1347 tbl.cell(r, 3).unwrap().set_text(tax);
1348 tbl.cell(r, 4).unwrap().set_text(amt);
1349 if i % 2 == 0 {
1350 for c in 0..5 {
1351 tbl.cell(r, c).unwrap().shading("FAF0F0");
1352 }
1353 }
1354 }
1355 }
1356
1357 doc.add_paragraph("");
1358
1359 // ── Totals ──
1360 {
1361 let mut tbl = doc
1362 .add_table(4, 2)
1363 .borders(BorderStyle::Single, 2, "B22222")
1364 .width(Length::inches(3.0))
1365 .alignment(Alignment::Right);
1366 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1367 tbl.cell(0, 1).unwrap().set_text("$154,600.00");
1368 tbl.cell(1, 0).unwrap().set_text("Tax (8.625%)");
1369 tbl.cell(1, 1).unwrap().set_text("$13,334.25");
1370 tbl.cell(2, 0).unwrap().set_text("Discount (5%)");
1371 tbl.cell(2, 1).unwrap().set_text("-$7,730.00");
1372 tbl.cell(3, 0).unwrap().set_text("AMOUNT DUE");
1373 tbl.cell(3, 0).unwrap().shading("B22222");
1374 tbl.cell(3, 1).unwrap().set_text("$160,204.25");
1375 tbl.cell(3, 1).unwrap().shading("B22222");
1376 }
1377
1378 doc.add_paragraph("");
1379 doc.add_paragraph("");
1380
1381 // ── Payment Details ──
1382 {
1383 let mut p = doc.add_paragraph("");
1384 p.add_run("Payment Details")
1385 .bold(true)
1386 .size(14.0)
1387 .color("333333");
1388 }
1389 doc.add_paragraph("Bank: Silicon Valley Bank")
1390 .indent_left(Length::inches(0.3));
1391 doc.add_paragraph("Account: Tensorbee Inc. — 0847-2953-1120")
1392 .indent_left(Length::inches(0.3));
1393 doc.add_paragraph("Routing: 121140399")
1394 .indent_left(Length::inches(0.3));
1395 doc.add_paragraph("Swift: SVBKUS6S")
1396 .indent_left(Length::inches(0.3));
1397
1398 doc.add_paragraph("");
1399
1400 doc.add_paragraph("Please include invoice number INV-2026-0392 in the payment reference.")
1401 .shading("FAF0F0")
1402 .border_all(BorderStyle::Single, 2, "B22222");
1403
1404 doc
1405}
1406
1407// =============================================================================
1408// 5. REPORT — Forest green + earth tones, with images & hierarchical sections
1409// =============================================================================
1410fn generate_report(_samples_dir: &Path) -> Document {
1411 let mut doc = Document::new();
1412
1413 // Colors: Forest #2D5016, Sage #6B8E23, Earth #8B7355, Cream #FFFAF0
1414 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1415 doc.set_margins(
1416 Length::inches(1.0),
1417 Length::inches(1.0),
1418 Length::inches(1.0),
1419 Length::inches(1.0),
1420 );
1421 doc.set_title("Q4 2025 Environmental Impact Report");
1422 doc.set_author("Walter White");
1423 doc.set_subject("Quarterly Environmental Report");
1424 doc.set_keywords("environment, sustainability, report, Tensorbee");
1425
1426 doc.set_different_first_page(true);
1427 doc.set_first_page_header("");
1428 doc.set_header("Tensorbee — Q4 2025 Environmental Impact Report");
1429 doc.set_footer("CONFIDENTIAL — Page");
1430
1431 // ── Cover ──
1432 let cover_bg = create_sample_png(612, 792, [20, 50, 15]);
1433 doc.add_background_image(&cover_bg, "report_cover.png");
1434
1435 for _ in 0..5 {
1436 doc.add_paragraph("");
1437 }
1438 {
1439 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1440 p.add_run("Q4 2025")
1441 .bold(true)
1442 .size(48.0)
1443 .color("FFFFFF")
1444 .font("Georgia");
1445 }
1446 {
1447 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1448 p.add_run("Environmental Impact Report")
1449 .size(24.0)
1450 .color("90EE90")
1451 .font("Georgia")
1452 .italic(true);
1453 }
1454 doc.add_paragraph("");
1455 {
1456 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1457 p.add_run("Tensorbee — Sustainability Division")
1458 .size(14.0)
1459 .color("C0C0C0");
1460 }
1461 {
1462 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1463 p.add_run("Prepared by Walter White, Chief Sustainability Officer")
1464 .size(11.0)
1465 .color("AAAAAA");
1466 }
1467
1468 // ── TOC ──
1469 doc.add_paragraph("").page_break_before(true);
1470 doc.insert_toc(doc.content_count(), 3);
1471
1472 // ── Section 1: Executive Overview ──
1473 doc.add_paragraph("").page_break_before(true);
1474 doc.add_paragraph("Executive Overview").style("Heading1");
1475 doc.add_paragraph(
1476 "This report presents Tensorbee's environmental performance for Q4 2025. Our \
1477 sustainability initiatives have yielded a 34% reduction in carbon emissions compared \
1478 to Q4 2024, exceeding our target of 25%. Key achievements include the transition to \
1479 100% renewable energy in our primary data centers and the launch of our carbon offset \
1480 marketplace.",
1481 )
1482 .first_line_indent(Length::inches(0.3));
1483
1484 // Image: performance chart
1485 let chart_img = create_chart_png(400, 200);
1486 doc.add_paragraph("");
1487 doc.add_picture(
1488 &chart_img,
1489 "performance_chart.png",
1490 Length::inches(5.0),
1491 Length::inches(2.5),
1492 )
1493 .alignment(Alignment::Center);
1494 {
1495 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1496 p.add_run("Figure 1: Quarterly Carbon Emissions (tonnes CO2e)")
1497 .italic(true)
1498 .size(9.0)
1499 .color("666666");
1500 }
1501
1502 // ── Section 2: Energy Consumption ──
1503 doc.add_paragraph("").page_break_before(true);
1504 doc.add_paragraph("Energy Consumption").style("Heading1");
1505
1506 doc.add_paragraph("Data Center Operations")
1507 .style("Heading2");
1508 doc.add_paragraph(
1509 "Our three primary data centers consumed a combined 4.2 GWh in Q4 2025, \
1510 a 12% reduction from Q3 2025 driven by improved cooling efficiency and \
1511 server consolidation.",
1512 );
1513
1514 // Data table
1515 {
1516 let mut tbl = doc
1517 .add_table(5, 4)
1518 .borders(BorderStyle::Single, 4, "2D5016")
1519 .width_pct(100.0);
1520 let hdrs = ["Data Center", "Capacity (MW)", "Usage (GWh)", "PUE"];
1521 for (c, h) in hdrs.iter().enumerate() {
1522 tbl.cell(0, c).unwrap().set_text(h);
1523 tbl.cell(0, c).unwrap().shading("2D5016");
1524 }
1525 let rows = [
1526 ("San Francisco (Primary)", "3.2", "1.8", "1.12"),
1527 ("Dublin (EU)", "2.1", "1.2", "1.18"),
1528 ("Singapore (APAC)", "1.8", "1.2", "1.24"),
1529 ("TOTAL", "7.1", "4.2", "1.17 avg"),
1530 ];
1531 for (i, (dc, cap, use_, pue)) in rows.iter().enumerate() {
1532 tbl.cell(i + 1, 0).unwrap().set_text(dc);
1533 tbl.cell(i + 1, 1).unwrap().set_text(cap);
1534 tbl.cell(i + 1, 2).unwrap().set_text(use_);
1535 tbl.cell(i + 1, 3).unwrap().set_text(pue);
1536 if i == 3 {
1537 for c in 0..4 {
1538 tbl.cell(i + 1, c).unwrap().shading("E2EFDA");
1539 }
1540 }
1541 }
1542 }
1543
1544 doc.add_paragraph("");
1545
1546 doc.add_paragraph("Renewable Energy Mix").style("Heading2");
1547 doc.add_paragraph("Breakdown of energy sources across all facilities:");
1548
1549 doc.add_bullet_list_item("Solar PV: 42% (1.76 GWh)", 0);
1550 doc.add_bullet_list_item("Wind Power Purchase Agreements: 38% (1.60 GWh)", 0);
1551 doc.add_bullet_list_item("Hydroelectric: 12% (0.50 GWh)", 0);
1552 doc.add_bullet_list_item("Grid (non-renewable): 8% (0.34 GWh)", 0);
1553
1554 // ── Section 3: Water & Waste ──
1555 doc.add_paragraph("Water & Waste Management")
1556 .style("Heading1");
1557
1558 doc.add_paragraph("Water Usage").style("Heading2");
1559 doc.add_paragraph(
1560 "Total water consumption was 12.4 million gallons, a 15% reduction from Q3. \
1561 Our closed-loop cooling systems now recycle 78% of water used in cooling operations.",
1562 );
1563
1564 doc.add_paragraph("Waste Reduction").style("Heading2");
1565 doc.add_paragraph("E-waste management results for Q4:")
1566 .keep_with_next(true);
1567 {
1568 let mut tbl = doc
1569 .add_table(4, 3)
1570 .borders(BorderStyle::Single, 2, "6B8E23")
1571 .width_pct(80.0);
1572 tbl.cell(0, 0).unwrap().set_text("Category");
1573 tbl.cell(0, 0).unwrap().shading("6B8E23");
1574 tbl.cell(0, 1).unwrap().set_text("Weight (kg)");
1575 tbl.cell(0, 1).unwrap().shading("6B8E23");
1576 tbl.cell(0, 2).unwrap().set_text("Recycled %");
1577 tbl.cell(0, 2).unwrap().shading("6B8E23");
1578 let rows = [
1579 ("Server Hardware", "2,450", "94%"),
1580 ("Networking Equipment", "820", "91%"),
1581 ("Storage Media", "340", "99%"),
1582 ];
1583 for (i, (cat, wt, pct)) in rows.iter().enumerate() {
1584 tbl.cell(i + 1, 0).unwrap().set_text(cat);
1585 tbl.cell(i + 1, 1).unwrap().set_text(wt);
1586 tbl.cell(i + 1, 2).unwrap().set_text(pct);
1587 }
1588 }
1589
1590 // ── Section 4: Initiatives ──
1591 doc.add_paragraph("").page_break_before(true);
1592 doc.add_paragraph("Q1 2026 Initiatives").style("Heading1");
1593
1594 doc.add_paragraph("Planned Programs").style("Heading2");
1595 doc.add_numbered_list_item(
1596 "Deploy on-site battery storage at SF data center (2 MWh capacity)",
1597 0,
1598 );
1599 doc.add_numbered_list_item("Pilot immersion cooling in Dublin facility", 0);
1600 doc.add_numbered_list_item("Launch employee commute carbon offset program", 0);
1601 doc.add_numbered_list_item("Achieve ISO 14001 certification for Singapore facility", 0);
1602
1603 doc.add_paragraph("Investment Targets").style("Heading2");
1604 doc.add_paragraph("Sustainability CapEx allocation for FY2026:")
1605 .keep_with_next(true);
1606 {
1607 let mut tbl = doc
1608 .add_table(5, 2)
1609 .borders(BorderStyle::Single, 4, "2D5016");
1610 tbl.cell(0, 0).unwrap().set_text("Initiative");
1611 tbl.cell(0, 0).unwrap().shading("2D5016");
1612 tbl.cell(0, 1).unwrap().set_text("Budget");
1613 tbl.cell(0, 1).unwrap().shading("2D5016");
1614 let rows = [
1615 ("Battery Storage", "$1.2M"),
1616 ("Immersion Cooling Pilot", "$800K"),
1617 ("Solar Panel Expansion", "$2.1M"),
1618 ("Carbon Credits", "$500K"),
1619 ];
1620 for (i, (init, budget)) in rows.iter().enumerate() {
1621 tbl.cell(i + 1, 0).unwrap().set_text(init);
1622 tbl.cell(i + 1, 1).unwrap().set_text(budget);
1623 if i % 2 == 0 {
1624 tbl.cell(i + 1, 0).unwrap().shading("FFFAF0");
1625 tbl.cell(i + 1, 1).unwrap().shading("FFFAF0");
1626 }
1627 }
1628 }
1629
1630 // Image: sustainability roadmap
1631 let roadmap_img = create_sample_png(500, 100, [40, 80, 30]);
1632 doc.add_paragraph("");
1633 doc.add_picture(
1634 &roadmap_img,
1635 "roadmap.png",
1636 Length::inches(6.0),
1637 Length::inches(1.2),
1638 )
1639 .alignment(Alignment::Center);
1640 {
1641 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1642 p.add_run("Figure 2: 2026 Sustainability Roadmap")
1643 .italic(true)
1644 .size(9.0)
1645 .color("666666");
1646 }
1647
1648 doc.add_paragraph("");
1649 doc.add_paragraph(
1650 "For questions about this report, contact Walter White at sustainability@tensorbee.com.",
1651 )
1652 .shading("FFFAF0")
1653 .border_all(BorderStyle::Single, 2, "2D5016");
1654
1655 doc
1656}Sourcepub fn add_picture_auto(
&mut self,
image_data: &[u8],
image_filename: &str,
) -> Result<Paragraph<'_>>
pub fn add_picture_auto( &mut self, image_data: &[u8], image_filename: &str, ) -> Result<Paragraph<'_>>
Add an inline image at its native size using 72 DPI when none is declared.
Returns an error without changing the document when the image dimensions cannot be determined.
Sourcepub fn add_background_image(
&mut self,
image_data: &[u8],
image_filename: &str,
) -> Paragraph<'_>
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?
97fn generate_feature_showcase(_samples_dir: &Path) -> Document {
98 let mut doc = Document::new();
99
100 // ── Page Setup & Metadata ──
101 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
102 doc.set_margins(
103 Length::inches(1.0),
104 Length::inches(1.0),
105 Length::inches(1.0),
106 Length::inches(1.0),
107 );
108 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
109 doc.set_gutter(Length::twips(0));
110 doc.set_title("rdocx Feature Showcase");
111 doc.set_author("rdocx Sample Generator");
112 doc.set_subject("Comprehensive feature demonstration");
113 doc.set_keywords("rdocx, docx, rust, sample, showcase");
114
115 // Headers & Footers with different first page
116 doc.set_different_first_page(true);
117 doc.set_first_page_header("rdocx");
118 doc.set_first_page_footer("Feature Showcase — Cover Page");
119 doc.set_header("rdocx Feature Showcase");
120 doc.set_footer("Generated by rdocx");
121
122 // ── COVER PAGE ──
123 let bg = create_sample_png(612, 792, [20, 45, 90]);
124 doc.add_background_image(&bg, "cover_bg.png");
125
126 for _ in 0..3 {
127 doc.add_paragraph("");
128 }
129 {
130 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
131 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
132 }
133 {
134 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
135 p.add_run("Complete Feature Showcase")
136 .size(28.0)
137 .color("FFFFFF")
138 .italic(true);
139 }
140 doc.add_paragraph("");
141 {
142 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
143 p.add_run("Every feature of the rdocx Rust library")
144 .size(13.0)
145 .color("B0C4DE");
146 }
147 {
148 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
149 p.add_run("demonstrated in a single document.")
150 .size(13.0)
151 .color("B0C4DE");
152 }
153
154 // ── TABLE OF CONTENTS ──
155 doc.add_paragraph("").page_break_before(true);
156 doc.insert_toc(doc.content_count(), 3);
157
158 // ── SECTION 1: TEXT FORMATTING ──
159 doc.add_paragraph("").page_break_before(true);
160 doc.add_paragraph("1. Text Formatting").style("Heading1");
161
162 // Paragraph Alignment
163 doc.add_paragraph("1.1 Paragraph Alignment")
164 .style("Heading2");
165 doc.add_paragraph("Left-aligned paragraph (default).")
166 .alignment(Alignment::Left);
167 doc.add_paragraph("Center-aligned paragraph.")
168 .alignment(Alignment::Center);
169 doc.add_paragraph("Right-aligned paragraph.")
170 .alignment(Alignment::Right);
171 doc.add_paragraph(
172 "Justified paragraph — this text is long enough to demonstrate how justified alignment \
173 distributes extra space across word gaps so lines fill the full width of the text area.",
174 )
175 .alignment(Alignment::Justify);
176
177 doc.add_paragraph("");
178
179 // Run Formatting
180 doc.add_paragraph("1.2 Run Formatting").style("Heading2");
181 {
182 let mut p = doc.add_paragraph("");
183 p.add_run("Bold").bold(true);
184 p.add_run(" | ");
185 p.add_run("Italic").italic(true);
186 p.add_run(" | ");
187 p.add_run("Bold Italic").bold(true).italic(true);
188 p.add_run(" | ");
189 p.add_run("Underline").underline(true);
190 p.add_run(" | ");
191 p.add_run("Strikethrough").strike(true);
192 p.add_run(" | ");
193 p.add_run("Double Strike").double_strike(true);
194 }
195 {
196 let mut p = doc.add_paragraph("");
197 p.add_run("Red").color("FF0000");
198 p.add_run(" | ");
199 p.add_run("Blue").color("0000FF");
200 p.add_run(" | ");
201 p.add_run("Green").color("00AA00");
202 p.add_run(" | ");
203 p.add_run("Highlighted").highlight("FFFF00");
204 }
205 {
206 let mut p = doc.add_paragraph("");
207 p.add_run("8pt").size(8.0);
208 p.add_run(" | ");
209 p.add_run("11pt").size(11.0);
210 p.add_run(" | ");
211 p.add_run("16pt").size(16.0);
212 p.add_run(" | ");
213 p.add_run("24pt").size(24.0);
214 }
215 {
216 let mut p = doc.add_paragraph("");
217 p.add_run("Arial").font("Arial");
218 p.add_run(" | ");
219 p.add_run("Times New Roman").font("Times New Roman");
220 p.add_run(" | ");
221 p.add_run("Courier New").font("Courier New");
222 }
223 {
224 let mut p = doc.add_paragraph("");
225 p.add_run("H");
226 p.add_run("2").subscript();
227 p.add_run("O (subscript) | E=mc");
228 p.add_run("2").superscript();
229 p.add_run(" (superscript)");
230 }
231 {
232 let mut p = doc.add_paragraph("");
233 p.add_run("ALL CAPS").all_caps(true);
234 p.add_run(" | ");
235 p.add_run("Small Caps").small_caps(true);
236 p.add_run(" | ");
237 p.add_run("Expanded").character_spacing(Length::twips(40));
238 p.add_run(" | ");
239 p.add_run("Hidden text (not visible)").hidden(true);
240 }
241
242 doc.add_paragraph("");
243
244 // Underline Styles
245 doc.add_paragraph("1.3 Underline Styles").style("Heading2");
246 {
247 let mut p = doc.add_paragraph("");
248 p.add_run("Single ")
249 .underline_style(rdocx::UnderlineStyle::Single);
250 p.add_run("Double ")
251 .underline_style(rdocx::UnderlineStyle::Double);
252 p.add_run("Thick ")
253 .underline_style(rdocx::UnderlineStyle::Thick);
254 p.add_run("Dotted ")
255 .underline_style(rdocx::UnderlineStyle::Dotted);
256 p.add_run("Dash ")
257 .underline_style(rdocx::UnderlineStyle::Dash);
258 p.add_run("Wave ")
259 .underline_style(rdocx::UnderlineStyle::Wave);
260 }
261
262 // ── SECTION 2: PARAGRAPH FORMATTING ──
263 doc.add_paragraph("").page_break_before(true);
264 doc.add_paragraph("2. Paragraph Formatting")
265 .style("Heading1");
266
267 doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
268 doc.add_paragraph("Paragraph with light green shading")
269 .shading("E2EFDA");
270 doc.add_paragraph("Paragraph with bottom border")
271 .border_bottom(BorderStyle::Single, 6, "2E75B6");
272 doc.add_paragraph("Paragraph with all borders (red)")
273 .border_all(BorderStyle::Single, 4, "FF0000");
274
275 doc.add_paragraph("2.2 Indentation").style("Heading2");
276 doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
277 .indent_left(Length::inches(1.0))
278 .hanging_indent(Length::inches(0.5));
279 doc.add_paragraph("0.5-inch first-line indent on this paragraph")
280 .first_line_indent(Length::inches(0.5));
281 doc.add_paragraph("Both left and right indent (0.75 inches each)")
282 .indent_left(Length::inches(0.75))
283 .indent_right(Length::inches(0.75));
284
285 doc.add_paragraph("2.3 Spacing & Line Height")
286 .style("Heading2");
287 doc.add_paragraph("Extra space before (24pt) and after (12pt)")
288 .space_before(Length::pt(24.0))
289 .space_after(Length::pt(12.0));
290 doc.add_paragraph(
291 "Double line spacing paragraph. This text should have extra vertical space between \
292 lines to demonstrate line_spacing_multiple(2.0).",
293 )
294 .line_spacing_multiple(2.0);
295 doc.add_paragraph("Exact 20pt line spacing (fixed height).")
296 .line_spacing(20.0);
297
298 doc.add_paragraph("2.4 Pagination Controls")
299 .style("Heading2");
300 doc.add_paragraph("keep_with_next — this paragraph stays with the next")
301 .keep_with_next(true);
302 doc.add_paragraph("(This paragraph was kept with the one above.)");
303 doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
304 .keep_together(true);
305 doc.add_paragraph("widow_control — prevents widow/orphan lines")
306 .widow_control(true);
307
308 // ── SECTION 3: LISTS ──
309 doc.add_paragraph("").page_break_before(true);
310 doc.add_paragraph("3. Lists").style("Heading1");
311
312 doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
313 doc.add_bullet_list_item("First item", 0);
314 doc.add_bullet_list_item("Second item", 0);
315 doc.add_bullet_list_item("Nested level 1", 1);
316 doc.add_bullet_list_item("Nested level 2", 2);
317 doc.add_bullet_list_item("Back to level 1", 1);
318 doc.add_bullet_list_item("Third item", 0);
319
320 doc.add_paragraph("");
321
322 doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
323 doc.add_numbered_list_item("First numbered item", 0);
324 doc.add_numbered_list_item("Second numbered item", 0);
325 doc.add_numbered_list_item("Sub-item A", 1);
326 doc.add_numbered_list_item("Sub-item B", 1);
327 doc.add_numbered_list_item("Third numbered item", 0);
328
329 // ── SECTION 4: TAB STOPS ──
330 doc.add_paragraph("");
331 doc.add_paragraph("4. Tab Stops").style("Heading1");
332
333 doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
334 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
335 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
336 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
337 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
338 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
339
340 doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
341 doc.add_paragraph("Item\t\tPrice")
342 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
343 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
344 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
345 doc.add_paragraph("Widget\t\t$19.99")
346 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
347 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
348 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
349 doc.add_paragraph("Gadget\t\t$249.50")
350 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
351 .add_tab_stop_with_leader(
352 TabAlignment::Right,
353 Length::inches(4.0),
354 TabLeader::Underscore,
355 )
356 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
357
358 // ── SECTION 5: TABLES ──
359 doc.add_paragraph("").page_break_before(true);
360 doc.add_paragraph("5. Tables").style("Heading1");
361
362 doc.add_paragraph("5.1 Basic Table").style("Heading2");
363 {
364 let mut tbl = doc
365 .add_table(4, 3)
366 .borders(BorderStyle::Single, 4, "000000");
367 for col in 0..3 {
368 tbl.cell(0, col).unwrap().shading("2E75B6");
369 }
370 tbl.cell(0, 0).unwrap().set_text("Name");
371 tbl.cell(0, 1).unwrap().set_text("Role");
372 tbl.cell(0, 2).unwrap().set_text("Location");
373 tbl.cell(1, 0).unwrap().set_text("Walter White");
374 tbl.cell(1, 1).unwrap().set_text("CEO");
375 tbl.cell(1, 2).unwrap().set_text("Albuquerque");
376 tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
377 tbl.cell(2, 1).unwrap().set_text("CTO");
378 tbl.cell(2, 2).unwrap().set_text("Remote");
379 tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
380 tbl.cell(3, 1).unwrap().set_text("Security");
381 tbl.cell(3, 2).unwrap().set_text("Washington");
382 }
383
384 doc.add_paragraph("");
385
386 doc.add_paragraph("5.2 Column Span & Cell Shading")
387 .style("Heading2");
388 {
389 let mut tbl = doc
390 .add_table(3, 4)
391 .borders(BorderStyle::Single, 4, "000000")
392 .width_pct(100.0);
393 tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
394 tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
395 tbl.cell(1, 0).unwrap().set_text("Region");
396 tbl.cell(1, 0).unwrap().shading("D6E4F0");
397 tbl.cell(1, 1).unwrap().set_text("Q1");
398 tbl.cell(1, 1).unwrap().shading("D6E4F0");
399 tbl.cell(1, 2).unwrap().set_text("Q2");
400 tbl.cell(1, 2).unwrap().shading("D6E4F0");
401 tbl.cell(1, 3).unwrap().set_text("Total");
402 tbl.cell(1, 3).unwrap().shading("D6E4F0");
403 tbl.cell(2, 0).unwrap().set_text("Americas");
404 tbl.cell(2, 1).unwrap().set_text("$2.4M");
405 tbl.cell(2, 2).unwrap().set_text("$2.7M");
406 tbl.cell(2, 3).unwrap().set_text("$5.1M");
407 }
408
409 doc.add_paragraph("");
410
411 doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
412 {
413 let mut tbl = doc
414 .add_table(4, 3)
415 .borders(BorderStyle::Single, 4, "000000");
416 tbl.cell(0, 0).unwrap().set_text("Category");
417 tbl.cell(0, 0).unwrap().shading("E2EFDA");
418 tbl.cell(0, 1).unwrap().set_text("Item");
419 tbl.cell(0, 1).unwrap().shading("E2EFDA");
420 tbl.cell(0, 2).unwrap().set_text("Price");
421 tbl.cell(0, 2).unwrap().shading("E2EFDA");
422 tbl.cell(1, 0).unwrap().set_text("Hardware");
423 tbl.cell(1, 0).unwrap().v_merge_restart();
424 tbl.cell(1, 1).unwrap().set_text("Laptop");
425 tbl.cell(1, 2).unwrap().set_text("$1,200");
426 tbl.cell(2, 0).unwrap().v_merge_continue();
427 tbl.cell(2, 1).unwrap().set_text("Monitor");
428 tbl.cell(2, 2).unwrap().set_text("$450");
429 tbl.cell(3, 0).unwrap().set_text("Software");
430 tbl.cell(3, 1).unwrap().set_text("IDE License");
431 tbl.cell(3, 2).unwrap().set_text("$200/yr");
432 }
433
434 doc.add_paragraph("");
435
436 doc.add_paragraph("5.4 Nested Table").style("Heading2");
437 {
438 let mut tbl = doc
439 .add_table(2, 2)
440 .borders(BorderStyle::Single, 6, "2E75B6");
441 tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
442 tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
443 tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
444 {
445 let mut cell = tbl.cell(1, 1).unwrap();
446 cell.set_text("Nested table below:");
447 let mut nested = cell
448 .add_table(2, 2)
449 .borders(BorderStyle::Single, 2, "FF6600");
450 nested.cell(0, 0).unwrap().set_text("A");
451 nested.cell(0, 1).unwrap().set_text("B");
452 nested.cell(1, 0).unwrap().set_text("C");
453 nested.cell(1, 1).unwrap().set_text("D");
454 }
455 }
456
457 doc.add_paragraph("");
458
459 doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
460 .style("Heading2");
461 {
462 let mut tbl = doc
463 .add_table(2, 3)
464 .borders(BorderStyle::Single, 4, "666666");
465 tbl.row(0).unwrap().height(Length::pt(50.0)).header();
466 tbl.cell(0, 0).unwrap().set_text("Top");
467 tbl.cell(0, 0)
468 .unwrap()
469 .vertical_alignment(VerticalAlignment::Top)
470 .shading("FFF2CC");
471 tbl.cell(0, 1).unwrap().set_text("Center");
472 tbl.cell(0, 1)
473 .unwrap()
474 .vertical_alignment(VerticalAlignment::Center)
475 .shading("D9E2F3");
476 tbl.cell(0, 2).unwrap().set_text("Bottom");
477 tbl.cell(0, 2)
478 .unwrap()
479 .vertical_alignment(VerticalAlignment::Bottom)
480 .shading("E2EFDA");
481 tbl.row(1).unwrap().cant_split();
482 tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
483 tbl.cell(1, 0).unwrap().no_wrap();
484 tbl.cell(1, 1).unwrap().set_text("Fixed width");
485 tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
486 tbl.cell(1, 2).unwrap().set_text("Normal");
487 }
488
489 // ── SECTION 6: IMAGES ──
490 doc.add_paragraph("").page_break_before(true);
491 doc.add_paragraph("6. Images").style("Heading1");
492
493 doc.add_paragraph("6.1 Inline Image").style("Heading2");
494 doc.add_paragraph("A blue gradient image below:");
495 let img = create_sample_png(200, 50, [0, 80, 200]);
496 doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
497
498 doc.add_paragraph("");
499 doc.add_paragraph("6.2 Header Image").style("Heading2");
500 let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
501 doc.set_header_image(
502 &hdr_img,
503 "header_logo.png",
504 Length::inches(2.0),
505 Length::inches(0.2),
506 );
507 doc.add_paragraph("The header now contains an inline image (check the top of this page).");
508
509 doc.add_paragraph("");
510 doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
511
512 // ── SECTION 7: CONTENT MANIPULATION ──
513 doc.add_paragraph("").page_break_before(true);
514 doc.add_paragraph("7. Content Manipulation")
515 .style("Heading1");
516
517 doc.add_paragraph("7.1 Placeholder Replacement")
518 .style("Heading2");
519 doc.add_paragraph("Customer: {{customer}}");
520 doc.add_paragraph("Date: {{date}}");
521 doc.add_paragraph("Reference: {{ref_number}}");
522 {
523 let mut tbl = doc
524 .add_table(2, 2)
525 .borders(BorderStyle::Single, 4, "000000");
526 tbl.cell(0, 0).unwrap().set_text("Project");
527 tbl.cell(0, 0).unwrap().shading("D6E4F0");
528 tbl.cell(0, 1).unwrap().set_text("{{project}}");
529 tbl.cell(1, 0).unwrap().set_text("Status");
530 tbl.cell(1, 0).unwrap().shading("D6E4F0");
531 tbl.cell(1, 1).unwrap().set_text("{{status}}");
532 }
533
534 let mut replacements = HashMap::new();
535 replacements.insert("{{customer}}", "Tensorbee Inc.");
536 replacements.insert("{{date}}", "February 22, 2026");
537 replacements.insert("{{ref_number}}", "TB-2026-001");
538 replacements.insert("{{project}}", "Infrastructure Upgrade");
539 replacements.insert("{{status}}", "In Progress");
540 let n = doc.replace_all(&replacements);
541 doc.add_paragraph(&format!("({n} placeholders replaced above)"));
542
543 doc.add_paragraph("");
544
545 doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
546 doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
547 let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
548 doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
549
550 doc.add_paragraph("");
551
552 doc.add_paragraph("7.3 Content Insertion").style("Heading2");
553 doc.add_paragraph("Section A: First content.");
554 doc.add_paragraph("Section C: Third content.");
555 if let Some(idx) = doc.find_content_index("Section C") {
556 doc.insert_paragraph(
557 idx,
558 "Section B: Inserted between A and C via find_content_index().",
559 );
560 }
561
562 // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
563 doc.add_paragraph("").section_break(SectionBreak::NextPage);
564 doc.add_paragraph("8. Section Breaks & Orientation")
565 .style("Heading1");
566 doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
567
568 {
569 let mut tbl = doc
570 .add_table(3, 7)
571 .borders(BorderStyle::Single, 4, "2E75B6");
572 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
573 for (c, h) in headers.iter().enumerate() {
574 tbl.cell(0, c).unwrap().set_text(h);
575 tbl.cell(0, c).unwrap().shading("2E75B6");
576 }
577 let data = [
578 [
579 "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
580 ],
581 [
582 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
583 ],
584 ];
585 for (r, row) in data.iter().enumerate() {
586 for (c, v) in row.iter().enumerate() {
587 tbl.cell(r + 1, c).unwrap().set_text(v);
588 }
589 }
590 }
591
592 doc.add_paragraph("")
593 .section_break(SectionBreak::NextPage)
594 .section_landscape();
595
596 // ── SECTION 9: CUSTOM STYLES ──
597 doc.add_paragraph("9. Custom Styles").style("Heading1");
598 doc.add_style(
599 StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
600 .based_on("Normal")
601 .paragraph_properties(rdocx_oxml::properties::CT_PPr {
602 shading: Some(rdocx_oxml::properties::CT_Shd {
603 val: "clear".to_string(),
604 color: None,
605 fill: Some("FFF2CC".to_string()),
606 }),
607 ..Default::default()
608 })
609 .run_properties(rdocx_oxml::properties::CT_RPr {
610 bold: Some(true),
611 color: Some("C45911".to_string()),
612 ..Default::default()
613 }),
614 );
615 doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
616 .style("CustomHighlight");
617
618 doc.add_paragraph("");
619
620 // ── SECTION 10: DOCUMENT INTELLIGENCE ──
621 doc.add_paragraph("10. Document Intelligence API")
622 .style("Heading1");
623
624 let wc = doc.word_count();
625 let hc = doc.headings().len();
626 let ic = doc.images().len();
627 let lc = doc.links().len();
628 doc.add_paragraph(&format!("Word count: {wc}"));
629 doc.add_paragraph(&format!("Heading count: {hc}"));
630 doc.add_paragraph(&format!("Image count: {ic}"));
631 doc.add_paragraph(&format!("Link count: {lc}"));
632
633 let outline = doc.document_outline();
634 doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
635
636 let issues = doc.audit_accessibility();
637 doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
638 for issue in &issues {
639 doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
640 }
641
642 // ── SECTION 11: DOCUMENT MERGING ──
643 doc.add_paragraph("");
644 doc.add_paragraph("11. Document Merging").style("Heading1");
645 {
646 let mut other = Document::new();
647 other.add_paragraph("This paragraph was merged from another document using append().");
648 doc.append(&other);
649 }
650
651 doc.add_paragraph("");
652
653 // ── SUMMARY ──
654 doc.add_paragraph("Summary of Demonstrated Features")
655 .style("Heading1");
656 let features = [
657 "Page setup: size, margins, header/footer distance, gutter",
658 "Document metadata: title, author, subject, keywords",
659 "Headers and footers: text, images, different first page",
660 "Background images (full-page behind text)",
661 "Table of Contents generation with bookmarks",
662 "Text formatting: bold, italic, underline styles, strike, color, size, font",
663 "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
664 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
665 "Pagination controls: keep-with-next, keep-together, widow control",
666 "Bullet and numbered lists with nesting",
667 "Tab stops with dot/underscore/hyphen leaders",
668 "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
669 "Row properties: header rows, exact height, cant-split",
670 "Cell properties: width, no-wrap, vertical alignment",
671 "Inline images and header images",
672 "Placeholder replacement (single and batch)",
673 "Regex-based find and replace",
674 "Content insertion at specific positions",
675 "Section breaks with mixed portrait/landscape",
676 "Custom paragraph and character styles",
677 "Document intelligence: word count, headings, outline, images, links",
678 "Accessibility audit",
679 "Document merging (append)",
680 "Export: DOCX, PDF, HTML, Markdown",
681 ];
682 for f in &features {
683 doc.add_bullet_list_item(f, 0);
684 }
685 doc.add_paragraph("");
686 doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
687 .alignment(Alignment::Center)
688 .shading("E2EFDA")
689 .border_all(BorderStyle::Single, 2, "00AA00");
690
691 doc
692}
693
694// =============================================================================
695// 2. PROPOSAL DOCUMENT — Deep navy + gold scheme
696// =============================================================================
697fn generate_proposal(_samples_dir: &Path) -> Document {
698 let mut doc = Document::new();
699
700 // Colors: Navy #1B2A4A, Gold #C5922E, Light #F4F1EB
701 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
702 doc.set_margins(
703 Length::inches(1.0),
704 Length::inches(1.0),
705 Length::inches(1.0),
706 Length::inches(1.0),
707 );
708 doc.set_title("AI Platform Modernization Proposal");
709 doc.set_author("Walter White");
710 doc.set_subject("Technology Proposal");
711 doc.set_keywords("proposal, AI, modernization, Tensorbee");
712
713 // Banner header
714 let logo_img = create_logo_png(220, 48);
715 let banner = build_header_banner_xml(
716 "rId1",
717 &BannerOpts {
718 bg_color: "1B2A4A",
719 banner_width: 7772400,
720 banner_height: 914400,
721 logo_width: 2011680,
722 logo_height: 438912,
723 logo_x_offset: 295125,
724 logo_y_offset: 237744,
725 },
726 );
727 doc.set_raw_header_with_images(
728 banner,
729 &[("rId1", &logo_img, "logo.png")],
730 rdocx_oxml::header_footer::HdrFtrType::Default,
731 );
732 doc.set_different_first_page(true);
733 let cover_banner = build_header_banner_xml(
734 "rId1",
735 &BannerOpts {
736 bg_color: "C5922E",
737 banner_width: 7772400,
738 banner_height: 914400,
739 logo_width: 2011680,
740 logo_height: 438912,
741 logo_x_offset: 295125,
742 logo_y_offset: 237744,
743 },
744 );
745 doc.set_raw_header_with_images(
746 cover_banner,
747 &[("rId1", &logo_img, "logo.png")],
748 rdocx_oxml::header_footer::HdrFtrType::First,
749 );
750 doc.set_margins(
751 Length::twips(2292),
752 Length::twips(1440),
753 Length::twips(1440),
754 Length::twips(1440),
755 );
756 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
757 doc.set_footer("Tensorbee — Confidential");
758
759 // Custom styles
760 doc.add_style(
761 StyleBuilder::paragraph("ProposalTitle", "Proposal Title")
762 .based_on("Normal")
763 .run_properties(rdocx_oxml::properties::CT_RPr {
764 bold: Some(true),
765 font_ascii: Some("Georgia".to_string()),
766 font_hansi: Some("Georgia".to_string()),
767 sz: Some(rdocx_oxml::HalfPoint(56)), // half-points → 28pt
768 color: Some("1B2A4A".to_string()),
769 ..Default::default()
770 }),
771 );
772
773 // ── Cover Page ──
774 for _ in 0..4 {
775 doc.add_paragraph("");
776 }
777 doc.add_paragraph("AI Platform Modernization")
778 .style("ProposalTitle")
779 .alignment(Alignment::Center);
780 doc.add_paragraph("")
781 .alignment(Alignment::Center)
782 .border_bottom(BorderStyle::Single, 8, "C5922E");
783 {
784 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
785 p.add_run("Prepared for Global Financial Corp.")
786 .size(14.0)
787 .color("666666")
788 .italic(true);
789 }
790 {
791 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
792 p.add_run("Prepared by: Walter White, CEO — Tensorbee")
793 .size(12.0)
794 .color("888888");
795 }
796 {
797 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
798 p.add_run("February 22, 2026").size(12.0).color("888888");
799 }
800
801 // ── TOC ──
802 doc.add_paragraph("").page_break_before(true);
803 doc.add_paragraph("Table of Contents").style("Heading1");
804 // We'll add a manual-style TOC since the insert_toc goes at a specific position
805 doc.insert_toc(doc.content_count(), 2);
806
807 // ── Executive Summary ──
808 doc.add_paragraph("").page_break_before(true);
809 doc.add_paragraph("Executive Summary").style("Heading1");
810 doc.add_paragraph(
811 "Tensorbee proposes a comprehensive modernization of Global Financial Corp's AI platform, \
812 transitioning from legacy batch-processing systems to a real-time inference architecture. \
813 This transformation will reduce model deployment time from weeks to hours, improve \
814 prediction accuracy by an estimated 23%, and deliver $4.2M in annual operational savings.",
815 )
816 .first_line_indent(Length::inches(0.3));
817 doc.add_paragraph(
818 "The project spans three phases over 18 months, with a total investment of $2.8M. \
819 Tensorbee brings deep expertise in ML infrastructure, having successfully completed \
820 similar transformations for three Fortune 500 financial institutions.",
821 )
822 .first_line_indent(Length::inches(0.3));
823
824 // Key metrics table
825 doc.add_paragraph("");
826 {
827 let mut tbl = doc
828 .add_table(5, 2)
829 .borders(BorderStyle::Single, 4, "1B2A4A")
830 .width_pct(80.0);
831 tbl.cell(0, 0).unwrap().set_text("Key Metric");
832 tbl.cell(0, 0).unwrap().shading("1B2A4A");
833 tbl.cell(0, 1).unwrap().set_text("Value");
834 tbl.cell(0, 1).unwrap().shading("1B2A4A");
835 let rows = [
836 ("Total Investment", "$2.8M"),
837 ("Annual Savings", "$4.2M"),
838 ("ROI (Year 1)", "150%"),
839 ("Timeline", "18 months"),
840 ];
841 for (i, (k, v)) in rows.iter().enumerate() {
842 tbl.cell(i + 1, 0).unwrap().set_text(k);
843 if i % 2 == 0 {
844 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
845 }
846 tbl.cell(i + 1, 1).unwrap().set_text(v);
847 if i % 2 == 0 {
848 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
849 }
850 }
851 }
852
853 // ── Problem Statement ──
854 doc.add_paragraph("").page_break_before(true);
855 doc.add_paragraph("Problem Statement").style("Heading1");
856 doc.add_paragraph(
857 "Global Financial Corp's current AI infrastructure faces three critical challenges:",
858 );
859 doc.add_numbered_list_item("Deployment Latency — New models take 3-4 weeks to deploy to production, compared to the industry benchmark of 2-3 days.", 0);
860 doc.add_numbered_list_item("Scalability Constraints — The batch processing architecture cannot handle real-time inference demands during peak trading hours.", 0);
861 doc.add_numbered_list_item("Technical Debt — Legacy Python 2.7 codebases and manual deployment scripts create reliability risks and slow iteration speed.", 0);
862
863 // ── Proposed Solution ──
864 doc.add_paragraph("").page_break_before(true);
865 doc.add_paragraph("Proposed Solution").style("Heading1");
866
867 doc.add_paragraph("Phase 1: Foundation (Months 1-6)")
868 .style("Heading2");
869 doc.add_bullet_list_item("Deploy Kubernetes-based ML serving infrastructure", 0);
870 doc.add_bullet_list_item("Implement CI/CD pipeline for model deployment", 0);
871 doc.add_bullet_list_item("Migrate top 5 production models to new platform", 0);
872
873 doc.add_paragraph("Phase 2: Expansion (Months 7-12)")
874 .style("Heading2");
875 doc.add_bullet_list_item(
876 "Real-time feature engineering pipeline (Apache Kafka + Flink)",
877 0,
878 );
879 doc.add_bullet_list_item("A/B testing framework for model variants", 0);
880 doc.add_bullet_list_item("Automated model monitoring and drift detection", 0);
881
882 doc.add_paragraph("Phase 3: Optimization (Months 13-18)")
883 .style("Heading2");
884 doc.add_bullet_list_item("GPU inference optimization (TensorRT/ONNX Runtime)", 0);
885 doc.add_bullet_list_item("Multi-region deployment for latency reduction", 0);
886 doc.add_bullet_list_item(
887 "Self-service model deployment portal for data science teams",
888 0,
889 );
890
891 // ── Budget ──
892 doc.add_paragraph("Budget Breakdown").style("Heading1");
893 {
894 let mut tbl = doc
895 .add_table(6, 3)
896 .borders(BorderStyle::Single, 4, "1B2A4A")
897 .width_pct(100.0);
898 tbl.cell(0, 0).unwrap().set_text("Category");
899 tbl.cell(0, 0).unwrap().shading("1B2A4A");
900 tbl.cell(0, 1).unwrap().set_text("Cost");
901 tbl.cell(0, 1).unwrap().shading("1B2A4A");
902 tbl.cell(0, 2).unwrap().set_text("Notes");
903 tbl.cell(0, 2).unwrap().shading("1B2A4A");
904 let items = [
905 (
906 "Infrastructure",
907 "$450,000",
908 "Cloud compute + GPU instances",
909 ),
910 ("Engineering Services", "$1,600,000", "12 FTE x 18 months"),
911 (
912 "Software Licenses",
913 "$350,000",
914 "Kafka, monitoring, CI/CD tools",
915 ),
916 (
917 "Training & Enablement",
918 "$200,000",
919 "Team training, documentation",
920 ),
921 ("Contingency (10%)", "$200,000", "Risk buffer"),
922 ];
923 for (i, (cat, cost, note)) in items.iter().enumerate() {
924 tbl.cell(i + 1, 0).unwrap().set_text(cat);
925 tbl.cell(i + 1, 1).unwrap().set_text(cost);
926 tbl.cell(i + 1, 2).unwrap().set_text(note);
927 if i % 2 == 0 {
928 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
929 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
930 tbl.cell(i + 1, 2).unwrap().shading("F4F1EB");
931 }
932 }
933 }
934
935 // ── Closing ──
936 doc.add_paragraph("");
937 doc.add_paragraph("Next Steps").style("Heading1");
938 doc.add_numbered_list_item(
939 "Schedule technical deep-dive with GFC engineering team (Week 1)",
940 0,
941 );
942 doc.add_numbered_list_item("Finalize scope and sign SOW (Week 2-3)", 0);
943 doc.add_numbered_list_item("Kick off Phase 1 with joint planning session (Week 4)", 0);
944
945 doc.add_paragraph("");
946 {
947 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
948 p.add_run("Walter White")
949 .bold(true)
950 .size(14.0)
951 .color("1B2A4A");
952 }
953 {
954 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
955 p.add_run("CEO, Tensorbee | walter@tensorbee.com")
956 .size(10.0)
957 .color("888888");
958 }
959
960 doc
961}
962
963// =============================================================================
964// 3. QUOTE / BILL OF MATERIALS — Teal + orange scheme
965// =============================================================================
966fn generate_quote(_samples_dir: &Path) -> Document {
967 let mut doc = Document::new();
968
969 // Colors: Teal #008B8B, Dark #1A3C3C, Orange #E07020, Light #F0F8F8
970 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
971 doc.set_margins(
972 Length::inches(0.75),
973 Length::inches(0.75),
974 Length::inches(0.75),
975 Length::inches(0.75),
976 );
977 doc.set_title("Quotation QT-2026-0147");
978 doc.set_author("Walter White");
979 doc.set_keywords("quote, BOM, Tensorbee");
980
981 doc.set_header("Tensorbee — Quotation");
982 doc.set_footer("QT-2026-0147 | Page");
983
984 // ── Header Block ──
985 {
986 let mut p = doc.add_paragraph("").alignment(Alignment::Left);
987 p.add_run("TENSORBEE")
988 .bold(true)
989 .size(28.0)
990 .color("008B8B")
991 .font("Helvetica");
992 }
993 doc.add_paragraph("123 Innovation Drive, Suite 400")
994 .alignment(Alignment::Left);
995 doc.add_paragraph("San Francisco, CA 94105 | +1 (415) 555-0199")
996 .alignment(Alignment::Left);
997 doc.add_paragraph("")
998 .border_bottom(BorderStyle::Single, 8, "008B8B");
999
1000 // Quote meta
1001 doc.add_paragraph("");
1002 {
1003 let mut tbl = doc.add_table(4, 4).width_pct(100.0);
1004 tbl.cell(0, 0).unwrap().set_text("QUOTATION");
1005 tbl.cell(0, 0).unwrap().shading("008B8B").grid_span(2);
1006 tbl.cell(0, 2).unwrap().set_text("DATE");
1007 tbl.cell(0, 2).unwrap().shading("008B8B");
1008 tbl.cell(0, 3).unwrap().set_text("VALID UNTIL");
1009 tbl.cell(0, 3).unwrap().shading("008B8B");
1010 tbl.cell(1, 0).unwrap().set_text("Quote #:");
1011 tbl.cell(1, 1).unwrap().set_text("QT-2026-0147");
1012 tbl.cell(1, 2).unwrap().set_text("Feb 22, 2026");
1013 tbl.cell(1, 3).unwrap().set_text("Mar 22, 2026");
1014 tbl.cell(2, 0).unwrap().set_text("Prepared by:");
1015 tbl.cell(2, 1).unwrap().set_text("Walter White");
1016 tbl.cell(2, 2).unwrap().set_text("Payment:");
1017 tbl.cell(2, 3).unwrap().set_text("Net 30");
1018 tbl.cell(3, 0).unwrap().set_text("Customer:");
1019 tbl.cell(3, 1).unwrap().set_text("Meridian Dynamics LLC");
1020 tbl.cell(3, 2).unwrap().set_text("Currency:");
1021 tbl.cell(3, 3).unwrap().set_text("USD");
1022 }
1023
1024 doc.add_paragraph("");
1025
1026 // ── Bill of Materials ──
1027 {
1028 let mut p = doc.add_paragraph("");
1029 p.add_run("Bill of Materials")
1030 .bold(true)
1031 .size(16.0)
1032 .color("1A3C3C");
1033 }
1034 doc.add_paragraph("");
1035
1036 {
1037 let mut tbl = doc
1038 .add_table(10, 6)
1039 .borders(BorderStyle::Single, 2, "008B8B")
1040 .width_pct(100.0)
1041 .layout_fixed();
1042 // Headers
1043 let hdrs = [
1044 "#",
1045 "Part Number",
1046 "Description",
1047 "Qty",
1048 "Unit Price",
1049 "Total",
1050 ];
1051 for (c, h) in hdrs.iter().enumerate() {
1052 tbl.cell(0, c).unwrap().set_text(h);
1053 tbl.cell(0, c).unwrap().shading("008B8B");
1054 }
1055
1056 let items: Vec<(&str, &str, &str, &str, &str)> = vec![
1057 (
1058 "TB-GPU-A100",
1059 "NVIDIA A100 80GB GPU",
1060 "4",
1061 "$12,500.00",
1062 "$50,000.00",
1063 ),
1064 (
1065 "TB-SRV-R750",
1066 "Dell R750xa Server Chassis",
1067 "2",
1068 "$8,200.00",
1069 "$16,400.00",
1070 ),
1071 (
1072 "TB-RAM-256G",
1073 "256GB DDR5 ECC Memory Module",
1074 "8",
1075 "$890.00",
1076 "$7,120.00",
1077 ),
1078 (
1079 "TB-SSD-3840",
1080 "3.84TB NVMe U.2 SSD",
1081 "8",
1082 "$1,150.00",
1083 "$9,200.00",
1084 ),
1085 (
1086 "TB-NET-CX7",
1087 "ConnectX-7 200GbE NIC",
1088 "4",
1089 "$1,800.00",
1090 "$7,200.00",
1091 ),
1092 (
1093 "TB-SW-48P",
1094 "48-Port 100GbE Switch",
1095 "1",
1096 "$22,000.00",
1097 "$22,000.00",
1098 ),
1099 (
1100 "TB-CAB-RACK",
1101 "42U Server Rack + PDU",
1102 "1",
1103 "$4,500.00",
1104 "$4,500.00",
1105 ),
1106 (
1107 "TB-SVC-INST",
1108 "Installation & Configuration",
1109 "1",
1110 "$8,500.00",
1111 "$8,500.00",
1112 ),
1113 (
1114 "TB-SVC-SUPP",
1115 "3-Year Premium Support",
1116 "1",
1117 "$15,000.00",
1118 "$15,000.00",
1119 ),
1120 ];
1121
1122 for (i, (pn, desc, qty, unit, total)) in items.iter().enumerate() {
1123 let row = i + 1;
1124 tbl.cell(row, 0).unwrap().set_text(&format!("{}", i + 1));
1125 tbl.cell(row, 1).unwrap().set_text(pn);
1126 tbl.cell(row, 2).unwrap().set_text(desc);
1127 tbl.cell(row, 3).unwrap().set_text(qty);
1128 tbl.cell(row, 4).unwrap().set_text(unit);
1129 tbl.cell(row, 5).unwrap().set_text(total);
1130 if i % 2 == 0 {
1131 for c in 0..6 {
1132 tbl.cell(row, c).unwrap().shading("F0F8F8");
1133 }
1134 }
1135 }
1136 }
1137
1138 doc.add_paragraph("");
1139
1140 // ── Totals ──
1141 {
1142 let mut tbl = doc
1143 .add_table(4, 2)
1144 .borders(BorderStyle::Single, 2, "008B8B")
1145 .width(Length::inches(3.5))
1146 .alignment(Alignment::Right);
1147 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1148 tbl.cell(0, 1).unwrap().set_text("$139,920.00");
1149 tbl.cell(1, 0).unwrap().set_text("Shipping & Handling");
1150 tbl.cell(1, 1).unwrap().set_text("$2,500.00");
1151 tbl.cell(2, 0).unwrap().set_text("Tax (8.625%)");
1152 tbl.cell(2, 1).unwrap().set_text("$12,068.10");
1153 tbl.cell(3, 0).unwrap().set_text("TOTAL");
1154 tbl.cell(3, 0).unwrap().shading("E07020");
1155 tbl.cell(3, 1).unwrap().set_text("$154,488.10");
1156 tbl.cell(3, 1).unwrap().shading("E07020");
1157 }
1158
1159 doc.add_paragraph("");
1160
1161 // ── Terms & Conditions ──
1162 {
1163 let mut p = doc.add_paragraph("");
1164 p.add_run("Terms & Conditions")
1165 .bold(true)
1166 .size(14.0)
1167 .color("1A3C3C");
1168 }
1169 doc.add_numbered_list_item(
1170 "This quotation is valid for 30 calendar days from the date of issue.",
1171 0,
1172 );
1173 doc.add_numbered_list_item(
1174 "All prices are in USD and exclusive of applicable taxes unless stated.",
1175 0,
1176 );
1177 doc.add_numbered_list_item("Standard lead time is 4-6 weeks from PO receipt.", 0);
1178 doc.add_numbered_list_item(
1179 "Payment terms: Net 30 from invoice date. 2% discount for payment within 10 days.",
1180 0,
1181 );
1182 doc.add_numbered_list_item(
1183 "Warranty: 3-year manufacturer warranty on all hardware components.",
1184 0,
1185 );
1186 doc.add_numbered_list_item(
1187 "Returns subject to 15% restocking fee if initiated after 14 days.",
1188 0,
1189 );
1190
1191 doc.add_paragraph("");
1192 doc.add_paragraph("")
1193 .border_bottom(BorderStyle::Single, 4, "008B8B");
1194 doc.add_paragraph("");
1195 {
1196 let mut p = doc.add_paragraph("");
1197 p.add_run("Accepted by: ").bold(true);
1198 p.add_run("___________________________ Date: ___________");
1199 }
1200 {
1201 let mut p = doc.add_paragraph("");
1202 p.add_run("Print Name: ").bold(true);
1203 p.add_run("___________________________ Title: ___________");
1204 }
1205
1206 doc
1207}
1208
1209// =============================================================================
1210// 4. INVOICE — Crimson + charcoal scheme
1211// =============================================================================
1212fn generate_invoice(_samples_dir: &Path) -> Document {
1213 let mut doc = Document::new();
1214
1215 // Colors: Crimson #B22222, Charcoal #333333, Light #FAF0F0
1216 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1217 doc.set_margins(
1218 Length::inches(0.75),
1219 Length::inches(0.75),
1220 Length::inches(0.75),
1221 Length::inches(0.75),
1222 );
1223 doc.set_title("Invoice INV-2026-0392");
1224 doc.set_author("Walter White");
1225 doc.set_keywords("invoice, Tensorbee");
1226
1227 doc.set_footer("Tensorbee — Thank you for your business!");
1228
1229 // ── Company & Invoice Header ──
1230 {
1231 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1232 // Company name left, INVOICE right
1233 {
1234 let mut cell = tbl.cell(0, 0).unwrap();
1235 let mut p = cell.add_paragraph("");
1236 p.add_run("TENSORBEE")
1237 .bold(true)
1238 .size(32.0)
1239 .color("B22222")
1240 .font("Helvetica");
1241 }
1242 {
1243 let mut cell = tbl.cell(0, 1).unwrap();
1244 let mut p = cell.add_paragraph("");
1245 p.add_run("INVOICE").bold(true).size(32.0).color("333333");
1246 }
1247 tbl.cell(1, 0)
1248 .unwrap()
1249 .set_text("123 Innovation Drive, Suite 400");
1250 tbl.cell(1, 1).unwrap().set_text("Invoice #: INV-2026-0392");
1251 tbl.cell(2, 0).unwrap().set_text("San Francisco, CA 94105");
1252 tbl.cell(2, 1).unwrap().set_text("Date: February 22, 2026");
1253 tbl.cell(3, 0).unwrap().set_text("walter@tensorbee.com");
1254 tbl.cell(3, 1).unwrap().set_text("Due Date: March 24, 2026");
1255 }
1256
1257 doc.add_paragraph("")
1258 .border_bottom(BorderStyle::Thick, 8, "B22222");
1259 doc.add_paragraph("");
1260
1261 // ── Bill To / Ship To ──
1262 {
1263 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1264 tbl.cell(0, 0).unwrap().set_text("BILL TO");
1265 tbl.cell(0, 0).unwrap().shading("B22222");
1266 tbl.cell(0, 1).unwrap().set_text("SHIP TO");
1267 tbl.cell(0, 1).unwrap().shading("B22222");
1268 tbl.cell(1, 0).unwrap().set_text("Meridian Dynamics LLC");
1269 tbl.cell(1, 1).unwrap().set_text("Meridian Dynamics LLC");
1270 tbl.cell(2, 0).unwrap().set_text("456 Enterprise Blvd");
1271 tbl.cell(2, 1).unwrap().set_text("Attn: Server Room B");
1272 tbl.cell(3, 0).unwrap().set_text("Austin, TX 78701");
1273 tbl.cell(3, 1)
1274 .unwrap()
1275 .set_text("456 Enterprise Blvd, Austin, TX 78701");
1276 }
1277
1278 doc.add_paragraph("");
1279
1280 // ── Line Items ──
1281 {
1282 let mut tbl = doc
1283 .add_table(8, 5)
1284 .borders(BorderStyle::Single, 2, "B22222")
1285 .width_pct(100.0);
1286 let hdrs = ["Description", "Qty", "Unit Price", "Tax", "Amount"];
1287 for (c, h) in hdrs.iter().enumerate() {
1288 tbl.cell(0, c).unwrap().set_text(h);
1289 tbl.cell(0, c).unwrap().shading("B22222");
1290 }
1291 let items = [
1292 (
1293 "ML Infrastructure Setup — Phase 1",
1294 "1",
1295 "$45,000.00",
1296 "$3,881.25",
1297 "$48,881.25",
1298 ),
1299 (
1300 "Data Pipeline Development (160 hrs)",
1301 "160",
1302 "$225.00",
1303 "$3,105.00",
1304 "$39,105.00",
1305 ),
1306 (
1307 "GPU Cluster Configuration",
1308 "1",
1309 "$12,000.00",
1310 "$1,035.00",
1311 "$13,035.00",
1312 ),
1313 (
1314 "API Gateway Implementation",
1315 "1",
1316 "$18,500.00",
1317 "$1,595.63",
1318 "$20,095.63",
1319 ),
1320 (
1321 "Load Testing & QA (80 hrs)",
1322 "80",
1323 "$195.00",
1324 "$1,345.50",
1325 "$16,945.50",
1326 ),
1327 (
1328 "Documentation & Training",
1329 "1",
1330 "$8,000.00",
1331 "$690.00",
1332 "$8,690.00",
1333 ),
1334 (
1335 "Project Management (3 months)",
1336 "3",
1337 "$6,500.00",
1338 "$1,679.63",
1339 "$21,179.63",
1340 ),
1341 ];
1342 for (i, (desc, qty, unit, tax, amt)) in items.iter().enumerate() {
1343 let r = i + 1;
1344 tbl.cell(r, 0).unwrap().set_text(desc);
1345 tbl.cell(r, 1).unwrap().set_text(qty);
1346 tbl.cell(r, 2).unwrap().set_text(unit);
1347 tbl.cell(r, 3).unwrap().set_text(tax);
1348 tbl.cell(r, 4).unwrap().set_text(amt);
1349 if i % 2 == 0 {
1350 for c in 0..5 {
1351 tbl.cell(r, c).unwrap().shading("FAF0F0");
1352 }
1353 }
1354 }
1355 }
1356
1357 doc.add_paragraph("");
1358
1359 // ── Totals ──
1360 {
1361 let mut tbl = doc
1362 .add_table(4, 2)
1363 .borders(BorderStyle::Single, 2, "B22222")
1364 .width(Length::inches(3.0))
1365 .alignment(Alignment::Right);
1366 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1367 tbl.cell(0, 1).unwrap().set_text("$154,600.00");
1368 tbl.cell(1, 0).unwrap().set_text("Tax (8.625%)");
1369 tbl.cell(1, 1).unwrap().set_text("$13,334.25");
1370 tbl.cell(2, 0).unwrap().set_text("Discount (5%)");
1371 tbl.cell(2, 1).unwrap().set_text("-$7,730.00");
1372 tbl.cell(3, 0).unwrap().set_text("AMOUNT DUE");
1373 tbl.cell(3, 0).unwrap().shading("B22222");
1374 tbl.cell(3, 1).unwrap().set_text("$160,204.25");
1375 tbl.cell(3, 1).unwrap().shading("B22222");
1376 }
1377
1378 doc.add_paragraph("");
1379 doc.add_paragraph("");
1380
1381 // ── Payment Details ──
1382 {
1383 let mut p = doc.add_paragraph("");
1384 p.add_run("Payment Details")
1385 .bold(true)
1386 .size(14.0)
1387 .color("333333");
1388 }
1389 doc.add_paragraph("Bank: Silicon Valley Bank")
1390 .indent_left(Length::inches(0.3));
1391 doc.add_paragraph("Account: Tensorbee Inc. — 0847-2953-1120")
1392 .indent_left(Length::inches(0.3));
1393 doc.add_paragraph("Routing: 121140399")
1394 .indent_left(Length::inches(0.3));
1395 doc.add_paragraph("Swift: SVBKUS6S")
1396 .indent_left(Length::inches(0.3));
1397
1398 doc.add_paragraph("");
1399
1400 doc.add_paragraph("Please include invoice number INV-2026-0392 in the payment reference.")
1401 .shading("FAF0F0")
1402 .border_all(BorderStyle::Single, 2, "B22222");
1403
1404 doc
1405}
1406
1407// =============================================================================
1408// 5. REPORT — Forest green + earth tones, with images & hierarchical sections
1409// =============================================================================
1410fn generate_report(_samples_dir: &Path) -> Document {
1411 let mut doc = Document::new();
1412
1413 // Colors: Forest #2D5016, Sage #6B8E23, Earth #8B7355, Cream #FFFAF0
1414 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1415 doc.set_margins(
1416 Length::inches(1.0),
1417 Length::inches(1.0),
1418 Length::inches(1.0),
1419 Length::inches(1.0),
1420 );
1421 doc.set_title("Q4 2025 Environmental Impact Report");
1422 doc.set_author("Walter White");
1423 doc.set_subject("Quarterly Environmental Report");
1424 doc.set_keywords("environment, sustainability, report, Tensorbee");
1425
1426 doc.set_different_first_page(true);
1427 doc.set_first_page_header("");
1428 doc.set_header("Tensorbee — Q4 2025 Environmental Impact Report");
1429 doc.set_footer("CONFIDENTIAL — Page");
1430
1431 // ── Cover ──
1432 let cover_bg = create_sample_png(612, 792, [20, 50, 15]);
1433 doc.add_background_image(&cover_bg, "report_cover.png");
1434
1435 for _ in 0..5 {
1436 doc.add_paragraph("");
1437 }
1438 {
1439 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1440 p.add_run("Q4 2025")
1441 .bold(true)
1442 .size(48.0)
1443 .color("FFFFFF")
1444 .font("Georgia");
1445 }
1446 {
1447 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1448 p.add_run("Environmental Impact Report")
1449 .size(24.0)
1450 .color("90EE90")
1451 .font("Georgia")
1452 .italic(true);
1453 }
1454 doc.add_paragraph("");
1455 {
1456 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1457 p.add_run("Tensorbee — Sustainability Division")
1458 .size(14.0)
1459 .color("C0C0C0");
1460 }
1461 {
1462 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1463 p.add_run("Prepared by Walter White, Chief Sustainability Officer")
1464 .size(11.0)
1465 .color("AAAAAA");
1466 }
1467
1468 // ── TOC ──
1469 doc.add_paragraph("").page_break_before(true);
1470 doc.insert_toc(doc.content_count(), 3);
1471
1472 // ── Section 1: Executive Overview ──
1473 doc.add_paragraph("").page_break_before(true);
1474 doc.add_paragraph("Executive Overview").style("Heading1");
1475 doc.add_paragraph(
1476 "This report presents Tensorbee's environmental performance for Q4 2025. Our \
1477 sustainability initiatives have yielded a 34% reduction in carbon emissions compared \
1478 to Q4 2024, exceeding our target of 25%. Key achievements include the transition to \
1479 100% renewable energy in our primary data centers and the launch of our carbon offset \
1480 marketplace.",
1481 )
1482 .first_line_indent(Length::inches(0.3));
1483
1484 // Image: performance chart
1485 let chart_img = create_chart_png(400, 200);
1486 doc.add_paragraph("");
1487 doc.add_picture(
1488 &chart_img,
1489 "performance_chart.png",
1490 Length::inches(5.0),
1491 Length::inches(2.5),
1492 )
1493 .alignment(Alignment::Center);
1494 {
1495 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1496 p.add_run("Figure 1: Quarterly Carbon Emissions (tonnes CO2e)")
1497 .italic(true)
1498 .size(9.0)
1499 .color("666666");
1500 }
1501
1502 // ── Section 2: Energy Consumption ──
1503 doc.add_paragraph("").page_break_before(true);
1504 doc.add_paragraph("Energy Consumption").style("Heading1");
1505
1506 doc.add_paragraph("Data Center Operations")
1507 .style("Heading2");
1508 doc.add_paragraph(
1509 "Our three primary data centers consumed a combined 4.2 GWh in Q4 2025, \
1510 a 12% reduction from Q3 2025 driven by improved cooling efficiency and \
1511 server consolidation.",
1512 );
1513
1514 // Data table
1515 {
1516 let mut tbl = doc
1517 .add_table(5, 4)
1518 .borders(BorderStyle::Single, 4, "2D5016")
1519 .width_pct(100.0);
1520 let hdrs = ["Data Center", "Capacity (MW)", "Usage (GWh)", "PUE"];
1521 for (c, h) in hdrs.iter().enumerate() {
1522 tbl.cell(0, c).unwrap().set_text(h);
1523 tbl.cell(0, c).unwrap().shading("2D5016");
1524 }
1525 let rows = [
1526 ("San Francisco (Primary)", "3.2", "1.8", "1.12"),
1527 ("Dublin (EU)", "2.1", "1.2", "1.18"),
1528 ("Singapore (APAC)", "1.8", "1.2", "1.24"),
1529 ("TOTAL", "7.1", "4.2", "1.17 avg"),
1530 ];
1531 for (i, (dc, cap, use_, pue)) in rows.iter().enumerate() {
1532 tbl.cell(i + 1, 0).unwrap().set_text(dc);
1533 tbl.cell(i + 1, 1).unwrap().set_text(cap);
1534 tbl.cell(i + 1, 2).unwrap().set_text(use_);
1535 tbl.cell(i + 1, 3).unwrap().set_text(pue);
1536 if i == 3 {
1537 for c in 0..4 {
1538 tbl.cell(i + 1, c).unwrap().shading("E2EFDA");
1539 }
1540 }
1541 }
1542 }
1543
1544 doc.add_paragraph("");
1545
1546 doc.add_paragraph("Renewable Energy Mix").style("Heading2");
1547 doc.add_paragraph("Breakdown of energy sources across all facilities:");
1548
1549 doc.add_bullet_list_item("Solar PV: 42% (1.76 GWh)", 0);
1550 doc.add_bullet_list_item("Wind Power Purchase Agreements: 38% (1.60 GWh)", 0);
1551 doc.add_bullet_list_item("Hydroelectric: 12% (0.50 GWh)", 0);
1552 doc.add_bullet_list_item("Grid (non-renewable): 8% (0.34 GWh)", 0);
1553
1554 // ── Section 3: Water & Waste ──
1555 doc.add_paragraph("Water & Waste Management")
1556 .style("Heading1");
1557
1558 doc.add_paragraph("Water Usage").style("Heading2");
1559 doc.add_paragraph(
1560 "Total water consumption was 12.4 million gallons, a 15% reduction from Q3. \
1561 Our closed-loop cooling systems now recycle 78% of water used in cooling operations.",
1562 );
1563
1564 doc.add_paragraph("Waste Reduction").style("Heading2");
1565 doc.add_paragraph("E-waste management results for Q4:")
1566 .keep_with_next(true);
1567 {
1568 let mut tbl = doc
1569 .add_table(4, 3)
1570 .borders(BorderStyle::Single, 2, "6B8E23")
1571 .width_pct(80.0);
1572 tbl.cell(0, 0).unwrap().set_text("Category");
1573 tbl.cell(0, 0).unwrap().shading("6B8E23");
1574 tbl.cell(0, 1).unwrap().set_text("Weight (kg)");
1575 tbl.cell(0, 1).unwrap().shading("6B8E23");
1576 tbl.cell(0, 2).unwrap().set_text("Recycled %");
1577 tbl.cell(0, 2).unwrap().shading("6B8E23");
1578 let rows = [
1579 ("Server Hardware", "2,450", "94%"),
1580 ("Networking Equipment", "820", "91%"),
1581 ("Storage Media", "340", "99%"),
1582 ];
1583 for (i, (cat, wt, pct)) in rows.iter().enumerate() {
1584 tbl.cell(i + 1, 0).unwrap().set_text(cat);
1585 tbl.cell(i + 1, 1).unwrap().set_text(wt);
1586 tbl.cell(i + 1, 2).unwrap().set_text(pct);
1587 }
1588 }
1589
1590 // ── Section 4: Initiatives ──
1591 doc.add_paragraph("").page_break_before(true);
1592 doc.add_paragraph("Q1 2026 Initiatives").style("Heading1");
1593
1594 doc.add_paragraph("Planned Programs").style("Heading2");
1595 doc.add_numbered_list_item(
1596 "Deploy on-site battery storage at SF data center (2 MWh capacity)",
1597 0,
1598 );
1599 doc.add_numbered_list_item("Pilot immersion cooling in Dublin facility", 0);
1600 doc.add_numbered_list_item("Launch employee commute carbon offset program", 0);
1601 doc.add_numbered_list_item("Achieve ISO 14001 certification for Singapore facility", 0);
1602
1603 doc.add_paragraph("Investment Targets").style("Heading2");
1604 doc.add_paragraph("Sustainability CapEx allocation for FY2026:")
1605 .keep_with_next(true);
1606 {
1607 let mut tbl = doc
1608 .add_table(5, 2)
1609 .borders(BorderStyle::Single, 4, "2D5016");
1610 tbl.cell(0, 0).unwrap().set_text("Initiative");
1611 tbl.cell(0, 0).unwrap().shading("2D5016");
1612 tbl.cell(0, 1).unwrap().set_text("Budget");
1613 tbl.cell(0, 1).unwrap().shading("2D5016");
1614 let rows = [
1615 ("Battery Storage", "$1.2M"),
1616 ("Immersion Cooling Pilot", "$800K"),
1617 ("Solar Panel Expansion", "$2.1M"),
1618 ("Carbon Credits", "$500K"),
1619 ];
1620 for (i, (init, budget)) in rows.iter().enumerate() {
1621 tbl.cell(i + 1, 0).unwrap().set_text(init);
1622 tbl.cell(i + 1, 1).unwrap().set_text(budget);
1623 if i % 2 == 0 {
1624 tbl.cell(i + 1, 0).unwrap().shading("FFFAF0");
1625 tbl.cell(i + 1, 1).unwrap().shading("FFFAF0");
1626 }
1627 }
1628 }
1629
1630 // Image: sustainability roadmap
1631 let roadmap_img = create_sample_png(500, 100, [40, 80, 30]);
1632 doc.add_paragraph("");
1633 doc.add_picture(
1634 &roadmap_img,
1635 "roadmap.png",
1636 Length::inches(6.0),
1637 Length::inches(1.2),
1638 )
1639 .alignment(Alignment::Center);
1640 {
1641 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1642 p.add_run("Figure 2: 2026 Sustainability Roadmap")
1643 .italic(true)
1644 .size(9.0)
1645 .color("666666");
1646 }
1647
1648 doc.add_paragraph("");
1649 doc.add_paragraph(
1650 "For questions about this report, contact Walter White at sustainability@tensorbee.com.",
1651 )
1652 .shading("FFFAF0")
1653 .border_all(BorderStyle::Single, 2, "2D5016");
1654
1655 doc
1656}Sourcepub fn add_anchored_image(
&mut self,
image_data: &[u8],
image_filename: &str,
width: Length,
height: Length,
behind_text: bool,
) -> Paragraph<'_>
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.
Sourcepub fn embed_image(&mut self, image_data: &[u8], filename: &str) -> String
pub fn embed_image(&mut self, image_data: &[u8], filename: &str) -> String
Embed an image into the OPC package and return the relationship ID.
Public so callers can pre-embed an image and then pass the returned
rel_id to crate::Cell::add_picture for inline cell images.
Sourcepub fn numbering_is_bullet(&self, num_id: u32) -> Option<bool>
pub fn numbering_is_bullet(&self, num_id: u32) -> Option<bool>
Whether the given numbering definition renders as bullets (true) or numbers (false). None if the id is unknown.
Sourcepub fn append_hyperlink(&mut self, text: &str, url: &str)
pub fn append_hyperlink(&mut self, text: &str, url: &str)
Append an external hyperlink to the last paragraph (creating one if the document is empty): adds the External relationship and wraps the new run in a hyperlink span.
Sourcepub fn add_hyperlink_relationship(&mut self, url: &str) -> String
pub fn add_hyperlink_relationship(&mut self, url: &str) -> String
Add an external hyperlink relationship and return its relationship ID.
Use this with crate::Paragraph::add_hyperlink when the target
paragraph is not the last body paragraph, such as a paragraph inside a
table cell.
Sourcepub fn last_paragraph_mut(&mut self) -> Option<Paragraph<'_>>
pub fn last_paragraph_mut(&mut self) -> Option<Paragraph<'_>>
Get a builder for the last paragraph in the body, if any. Lets
callers interleave plain runs with append_hyperlink calls.
Sourcepub fn image_data(&self, rel_id: &str) -> Option<Vec<u8>>
pub fn image_data(&self, rel_id: &str) -> Option<Vec<u8>>
Fetch the raw bytes of an embedded image by its relationship ID.
Sourcepub fn hyperlink_url(&self, rel_id: &str) -> Option<String>
pub fn hyperlink_url(&self, rel_id: &str) -> Option<String>
Resolve a hyperlink relationship ID to its external URL.
Sourcepub fn set_header(&mut self, text: &str)
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?
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
97fn generate_feature_showcase(_samples_dir: &Path) -> Document {
98 let mut doc = Document::new();
99
100 // ── Page Setup & Metadata ──
101 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
102 doc.set_margins(
103 Length::inches(1.0),
104 Length::inches(1.0),
105 Length::inches(1.0),
106 Length::inches(1.0),
107 );
108 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
109 doc.set_gutter(Length::twips(0));
110 doc.set_title("rdocx Feature Showcase");
111 doc.set_author("rdocx Sample Generator");
112 doc.set_subject("Comprehensive feature demonstration");
113 doc.set_keywords("rdocx, docx, rust, sample, showcase");
114
115 // Headers & Footers with different first page
116 doc.set_different_first_page(true);
117 doc.set_first_page_header("rdocx");
118 doc.set_first_page_footer("Feature Showcase — Cover Page");
119 doc.set_header("rdocx Feature Showcase");
120 doc.set_footer("Generated by rdocx");
121
122 // ── COVER PAGE ──
123 let bg = create_sample_png(612, 792, [20, 45, 90]);
124 doc.add_background_image(&bg, "cover_bg.png");
125
126 for _ in 0..3 {
127 doc.add_paragraph("");
128 }
129 {
130 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
131 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
132 }
133 {
134 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
135 p.add_run("Complete Feature Showcase")
136 .size(28.0)
137 .color("FFFFFF")
138 .italic(true);
139 }
140 doc.add_paragraph("");
141 {
142 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
143 p.add_run("Every feature of the rdocx Rust library")
144 .size(13.0)
145 .color("B0C4DE");
146 }
147 {
148 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
149 p.add_run("demonstrated in a single document.")
150 .size(13.0)
151 .color("B0C4DE");
152 }
153
154 // ── TABLE OF CONTENTS ──
155 doc.add_paragraph("").page_break_before(true);
156 doc.insert_toc(doc.content_count(), 3);
157
158 // ── SECTION 1: TEXT FORMATTING ──
159 doc.add_paragraph("").page_break_before(true);
160 doc.add_paragraph("1. Text Formatting").style("Heading1");
161
162 // Paragraph Alignment
163 doc.add_paragraph("1.1 Paragraph Alignment")
164 .style("Heading2");
165 doc.add_paragraph("Left-aligned paragraph (default).")
166 .alignment(Alignment::Left);
167 doc.add_paragraph("Center-aligned paragraph.")
168 .alignment(Alignment::Center);
169 doc.add_paragraph("Right-aligned paragraph.")
170 .alignment(Alignment::Right);
171 doc.add_paragraph(
172 "Justified paragraph — this text is long enough to demonstrate how justified alignment \
173 distributes extra space across word gaps so lines fill the full width of the text area.",
174 )
175 .alignment(Alignment::Justify);
176
177 doc.add_paragraph("");
178
179 // Run Formatting
180 doc.add_paragraph("1.2 Run Formatting").style("Heading2");
181 {
182 let mut p = doc.add_paragraph("");
183 p.add_run("Bold").bold(true);
184 p.add_run(" | ");
185 p.add_run("Italic").italic(true);
186 p.add_run(" | ");
187 p.add_run("Bold Italic").bold(true).italic(true);
188 p.add_run(" | ");
189 p.add_run("Underline").underline(true);
190 p.add_run(" | ");
191 p.add_run("Strikethrough").strike(true);
192 p.add_run(" | ");
193 p.add_run("Double Strike").double_strike(true);
194 }
195 {
196 let mut p = doc.add_paragraph("");
197 p.add_run("Red").color("FF0000");
198 p.add_run(" | ");
199 p.add_run("Blue").color("0000FF");
200 p.add_run(" | ");
201 p.add_run("Green").color("00AA00");
202 p.add_run(" | ");
203 p.add_run("Highlighted").highlight("FFFF00");
204 }
205 {
206 let mut p = doc.add_paragraph("");
207 p.add_run("8pt").size(8.0);
208 p.add_run(" | ");
209 p.add_run("11pt").size(11.0);
210 p.add_run(" | ");
211 p.add_run("16pt").size(16.0);
212 p.add_run(" | ");
213 p.add_run("24pt").size(24.0);
214 }
215 {
216 let mut p = doc.add_paragraph("");
217 p.add_run("Arial").font("Arial");
218 p.add_run(" | ");
219 p.add_run("Times New Roman").font("Times New Roman");
220 p.add_run(" | ");
221 p.add_run("Courier New").font("Courier New");
222 }
223 {
224 let mut p = doc.add_paragraph("");
225 p.add_run("H");
226 p.add_run("2").subscript();
227 p.add_run("O (subscript) | E=mc");
228 p.add_run("2").superscript();
229 p.add_run(" (superscript)");
230 }
231 {
232 let mut p = doc.add_paragraph("");
233 p.add_run("ALL CAPS").all_caps(true);
234 p.add_run(" | ");
235 p.add_run("Small Caps").small_caps(true);
236 p.add_run(" | ");
237 p.add_run("Expanded").character_spacing(Length::twips(40));
238 p.add_run(" | ");
239 p.add_run("Hidden text (not visible)").hidden(true);
240 }
241
242 doc.add_paragraph("");
243
244 // Underline Styles
245 doc.add_paragraph("1.3 Underline Styles").style("Heading2");
246 {
247 let mut p = doc.add_paragraph("");
248 p.add_run("Single ")
249 .underline_style(rdocx::UnderlineStyle::Single);
250 p.add_run("Double ")
251 .underline_style(rdocx::UnderlineStyle::Double);
252 p.add_run("Thick ")
253 .underline_style(rdocx::UnderlineStyle::Thick);
254 p.add_run("Dotted ")
255 .underline_style(rdocx::UnderlineStyle::Dotted);
256 p.add_run("Dash ")
257 .underline_style(rdocx::UnderlineStyle::Dash);
258 p.add_run("Wave ")
259 .underline_style(rdocx::UnderlineStyle::Wave);
260 }
261
262 // ── SECTION 2: PARAGRAPH FORMATTING ──
263 doc.add_paragraph("").page_break_before(true);
264 doc.add_paragraph("2. Paragraph Formatting")
265 .style("Heading1");
266
267 doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
268 doc.add_paragraph("Paragraph with light green shading")
269 .shading("E2EFDA");
270 doc.add_paragraph("Paragraph with bottom border")
271 .border_bottom(BorderStyle::Single, 6, "2E75B6");
272 doc.add_paragraph("Paragraph with all borders (red)")
273 .border_all(BorderStyle::Single, 4, "FF0000");
274
275 doc.add_paragraph("2.2 Indentation").style("Heading2");
276 doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
277 .indent_left(Length::inches(1.0))
278 .hanging_indent(Length::inches(0.5));
279 doc.add_paragraph("0.5-inch first-line indent on this paragraph")
280 .first_line_indent(Length::inches(0.5));
281 doc.add_paragraph("Both left and right indent (0.75 inches each)")
282 .indent_left(Length::inches(0.75))
283 .indent_right(Length::inches(0.75));
284
285 doc.add_paragraph("2.3 Spacing & Line Height")
286 .style("Heading2");
287 doc.add_paragraph("Extra space before (24pt) and after (12pt)")
288 .space_before(Length::pt(24.0))
289 .space_after(Length::pt(12.0));
290 doc.add_paragraph(
291 "Double line spacing paragraph. This text should have extra vertical space between \
292 lines to demonstrate line_spacing_multiple(2.0).",
293 )
294 .line_spacing_multiple(2.0);
295 doc.add_paragraph("Exact 20pt line spacing (fixed height).")
296 .line_spacing(20.0);
297
298 doc.add_paragraph("2.4 Pagination Controls")
299 .style("Heading2");
300 doc.add_paragraph("keep_with_next — this paragraph stays with the next")
301 .keep_with_next(true);
302 doc.add_paragraph("(This paragraph was kept with the one above.)");
303 doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
304 .keep_together(true);
305 doc.add_paragraph("widow_control — prevents widow/orphan lines")
306 .widow_control(true);
307
308 // ── SECTION 3: LISTS ──
309 doc.add_paragraph("").page_break_before(true);
310 doc.add_paragraph("3. Lists").style("Heading1");
311
312 doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
313 doc.add_bullet_list_item("First item", 0);
314 doc.add_bullet_list_item("Second item", 0);
315 doc.add_bullet_list_item("Nested level 1", 1);
316 doc.add_bullet_list_item("Nested level 2", 2);
317 doc.add_bullet_list_item("Back to level 1", 1);
318 doc.add_bullet_list_item("Third item", 0);
319
320 doc.add_paragraph("");
321
322 doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
323 doc.add_numbered_list_item("First numbered item", 0);
324 doc.add_numbered_list_item("Second numbered item", 0);
325 doc.add_numbered_list_item("Sub-item A", 1);
326 doc.add_numbered_list_item("Sub-item B", 1);
327 doc.add_numbered_list_item("Third numbered item", 0);
328
329 // ── SECTION 4: TAB STOPS ──
330 doc.add_paragraph("");
331 doc.add_paragraph("4. Tab Stops").style("Heading1");
332
333 doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
334 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
335 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
336 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
337 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
338 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
339
340 doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
341 doc.add_paragraph("Item\t\tPrice")
342 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
343 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
344 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
345 doc.add_paragraph("Widget\t\t$19.99")
346 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
347 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
348 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
349 doc.add_paragraph("Gadget\t\t$249.50")
350 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
351 .add_tab_stop_with_leader(
352 TabAlignment::Right,
353 Length::inches(4.0),
354 TabLeader::Underscore,
355 )
356 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
357
358 // ── SECTION 5: TABLES ──
359 doc.add_paragraph("").page_break_before(true);
360 doc.add_paragraph("5. Tables").style("Heading1");
361
362 doc.add_paragraph("5.1 Basic Table").style("Heading2");
363 {
364 let mut tbl = doc
365 .add_table(4, 3)
366 .borders(BorderStyle::Single, 4, "000000");
367 for col in 0..3 {
368 tbl.cell(0, col).unwrap().shading("2E75B6");
369 }
370 tbl.cell(0, 0).unwrap().set_text("Name");
371 tbl.cell(0, 1).unwrap().set_text("Role");
372 tbl.cell(0, 2).unwrap().set_text("Location");
373 tbl.cell(1, 0).unwrap().set_text("Walter White");
374 tbl.cell(1, 1).unwrap().set_text("CEO");
375 tbl.cell(1, 2).unwrap().set_text("Albuquerque");
376 tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
377 tbl.cell(2, 1).unwrap().set_text("CTO");
378 tbl.cell(2, 2).unwrap().set_text("Remote");
379 tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
380 tbl.cell(3, 1).unwrap().set_text("Security");
381 tbl.cell(3, 2).unwrap().set_text("Washington");
382 }
383
384 doc.add_paragraph("");
385
386 doc.add_paragraph("5.2 Column Span & Cell Shading")
387 .style("Heading2");
388 {
389 let mut tbl = doc
390 .add_table(3, 4)
391 .borders(BorderStyle::Single, 4, "000000")
392 .width_pct(100.0);
393 tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
394 tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
395 tbl.cell(1, 0).unwrap().set_text("Region");
396 tbl.cell(1, 0).unwrap().shading("D6E4F0");
397 tbl.cell(1, 1).unwrap().set_text("Q1");
398 tbl.cell(1, 1).unwrap().shading("D6E4F0");
399 tbl.cell(1, 2).unwrap().set_text("Q2");
400 tbl.cell(1, 2).unwrap().shading("D6E4F0");
401 tbl.cell(1, 3).unwrap().set_text("Total");
402 tbl.cell(1, 3).unwrap().shading("D6E4F0");
403 tbl.cell(2, 0).unwrap().set_text("Americas");
404 tbl.cell(2, 1).unwrap().set_text("$2.4M");
405 tbl.cell(2, 2).unwrap().set_text("$2.7M");
406 tbl.cell(2, 3).unwrap().set_text("$5.1M");
407 }
408
409 doc.add_paragraph("");
410
411 doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
412 {
413 let mut tbl = doc
414 .add_table(4, 3)
415 .borders(BorderStyle::Single, 4, "000000");
416 tbl.cell(0, 0).unwrap().set_text("Category");
417 tbl.cell(0, 0).unwrap().shading("E2EFDA");
418 tbl.cell(0, 1).unwrap().set_text("Item");
419 tbl.cell(0, 1).unwrap().shading("E2EFDA");
420 tbl.cell(0, 2).unwrap().set_text("Price");
421 tbl.cell(0, 2).unwrap().shading("E2EFDA");
422 tbl.cell(1, 0).unwrap().set_text("Hardware");
423 tbl.cell(1, 0).unwrap().v_merge_restart();
424 tbl.cell(1, 1).unwrap().set_text("Laptop");
425 tbl.cell(1, 2).unwrap().set_text("$1,200");
426 tbl.cell(2, 0).unwrap().v_merge_continue();
427 tbl.cell(2, 1).unwrap().set_text("Monitor");
428 tbl.cell(2, 2).unwrap().set_text("$450");
429 tbl.cell(3, 0).unwrap().set_text("Software");
430 tbl.cell(3, 1).unwrap().set_text("IDE License");
431 tbl.cell(3, 2).unwrap().set_text("$200/yr");
432 }
433
434 doc.add_paragraph("");
435
436 doc.add_paragraph("5.4 Nested Table").style("Heading2");
437 {
438 let mut tbl = doc
439 .add_table(2, 2)
440 .borders(BorderStyle::Single, 6, "2E75B6");
441 tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
442 tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
443 tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
444 {
445 let mut cell = tbl.cell(1, 1).unwrap();
446 cell.set_text("Nested table below:");
447 let mut nested = cell
448 .add_table(2, 2)
449 .borders(BorderStyle::Single, 2, "FF6600");
450 nested.cell(0, 0).unwrap().set_text("A");
451 nested.cell(0, 1).unwrap().set_text("B");
452 nested.cell(1, 0).unwrap().set_text("C");
453 nested.cell(1, 1).unwrap().set_text("D");
454 }
455 }
456
457 doc.add_paragraph("");
458
459 doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
460 .style("Heading2");
461 {
462 let mut tbl = doc
463 .add_table(2, 3)
464 .borders(BorderStyle::Single, 4, "666666");
465 tbl.row(0).unwrap().height(Length::pt(50.0)).header();
466 tbl.cell(0, 0).unwrap().set_text("Top");
467 tbl.cell(0, 0)
468 .unwrap()
469 .vertical_alignment(VerticalAlignment::Top)
470 .shading("FFF2CC");
471 tbl.cell(0, 1).unwrap().set_text("Center");
472 tbl.cell(0, 1)
473 .unwrap()
474 .vertical_alignment(VerticalAlignment::Center)
475 .shading("D9E2F3");
476 tbl.cell(0, 2).unwrap().set_text("Bottom");
477 tbl.cell(0, 2)
478 .unwrap()
479 .vertical_alignment(VerticalAlignment::Bottom)
480 .shading("E2EFDA");
481 tbl.row(1).unwrap().cant_split();
482 tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
483 tbl.cell(1, 0).unwrap().no_wrap();
484 tbl.cell(1, 1).unwrap().set_text("Fixed width");
485 tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
486 tbl.cell(1, 2).unwrap().set_text("Normal");
487 }
488
489 // ── SECTION 6: IMAGES ──
490 doc.add_paragraph("").page_break_before(true);
491 doc.add_paragraph("6. Images").style("Heading1");
492
493 doc.add_paragraph("6.1 Inline Image").style("Heading2");
494 doc.add_paragraph("A blue gradient image below:");
495 let img = create_sample_png(200, 50, [0, 80, 200]);
496 doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
497
498 doc.add_paragraph("");
499 doc.add_paragraph("6.2 Header Image").style("Heading2");
500 let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
501 doc.set_header_image(
502 &hdr_img,
503 "header_logo.png",
504 Length::inches(2.0),
505 Length::inches(0.2),
506 );
507 doc.add_paragraph("The header now contains an inline image (check the top of this page).");
508
509 doc.add_paragraph("");
510 doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
511
512 // ── SECTION 7: CONTENT MANIPULATION ──
513 doc.add_paragraph("").page_break_before(true);
514 doc.add_paragraph("7. Content Manipulation")
515 .style("Heading1");
516
517 doc.add_paragraph("7.1 Placeholder Replacement")
518 .style("Heading2");
519 doc.add_paragraph("Customer: {{customer}}");
520 doc.add_paragraph("Date: {{date}}");
521 doc.add_paragraph("Reference: {{ref_number}}");
522 {
523 let mut tbl = doc
524 .add_table(2, 2)
525 .borders(BorderStyle::Single, 4, "000000");
526 tbl.cell(0, 0).unwrap().set_text("Project");
527 tbl.cell(0, 0).unwrap().shading("D6E4F0");
528 tbl.cell(0, 1).unwrap().set_text("{{project}}");
529 tbl.cell(1, 0).unwrap().set_text("Status");
530 tbl.cell(1, 0).unwrap().shading("D6E4F0");
531 tbl.cell(1, 1).unwrap().set_text("{{status}}");
532 }
533
534 let mut replacements = HashMap::new();
535 replacements.insert("{{customer}}", "Tensorbee Inc.");
536 replacements.insert("{{date}}", "February 22, 2026");
537 replacements.insert("{{ref_number}}", "TB-2026-001");
538 replacements.insert("{{project}}", "Infrastructure Upgrade");
539 replacements.insert("{{status}}", "In Progress");
540 let n = doc.replace_all(&replacements);
541 doc.add_paragraph(&format!("({n} placeholders replaced above)"));
542
543 doc.add_paragraph("");
544
545 doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
546 doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
547 let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
548 doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
549
550 doc.add_paragraph("");
551
552 doc.add_paragraph("7.3 Content Insertion").style("Heading2");
553 doc.add_paragraph("Section A: First content.");
554 doc.add_paragraph("Section C: Third content.");
555 if let Some(idx) = doc.find_content_index("Section C") {
556 doc.insert_paragraph(
557 idx,
558 "Section B: Inserted between A and C via find_content_index().",
559 );
560 }
561
562 // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
563 doc.add_paragraph("").section_break(SectionBreak::NextPage);
564 doc.add_paragraph("8. Section Breaks & Orientation")
565 .style("Heading1");
566 doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
567
568 {
569 let mut tbl = doc
570 .add_table(3, 7)
571 .borders(BorderStyle::Single, 4, "2E75B6");
572 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
573 for (c, h) in headers.iter().enumerate() {
574 tbl.cell(0, c).unwrap().set_text(h);
575 tbl.cell(0, c).unwrap().shading("2E75B6");
576 }
577 let data = [
578 [
579 "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
580 ],
581 [
582 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
583 ],
584 ];
585 for (r, row) in data.iter().enumerate() {
586 for (c, v) in row.iter().enumerate() {
587 tbl.cell(r + 1, c).unwrap().set_text(v);
588 }
589 }
590 }
591
592 doc.add_paragraph("")
593 .section_break(SectionBreak::NextPage)
594 .section_landscape();
595
596 // ── SECTION 9: CUSTOM STYLES ──
597 doc.add_paragraph("9. Custom Styles").style("Heading1");
598 doc.add_style(
599 StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
600 .based_on("Normal")
601 .paragraph_properties(rdocx_oxml::properties::CT_PPr {
602 shading: Some(rdocx_oxml::properties::CT_Shd {
603 val: "clear".to_string(),
604 color: None,
605 fill: Some("FFF2CC".to_string()),
606 }),
607 ..Default::default()
608 })
609 .run_properties(rdocx_oxml::properties::CT_RPr {
610 bold: Some(true),
611 color: Some("C45911".to_string()),
612 ..Default::default()
613 }),
614 );
615 doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
616 .style("CustomHighlight");
617
618 doc.add_paragraph("");
619
620 // ── SECTION 10: DOCUMENT INTELLIGENCE ──
621 doc.add_paragraph("10. Document Intelligence API")
622 .style("Heading1");
623
624 let wc = doc.word_count();
625 let hc = doc.headings().len();
626 let ic = doc.images().len();
627 let lc = doc.links().len();
628 doc.add_paragraph(&format!("Word count: {wc}"));
629 doc.add_paragraph(&format!("Heading count: {hc}"));
630 doc.add_paragraph(&format!("Image count: {ic}"));
631 doc.add_paragraph(&format!("Link count: {lc}"));
632
633 let outline = doc.document_outline();
634 doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
635
636 let issues = doc.audit_accessibility();
637 doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
638 for issue in &issues {
639 doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
640 }
641
642 // ── SECTION 11: DOCUMENT MERGING ──
643 doc.add_paragraph("");
644 doc.add_paragraph("11. Document Merging").style("Heading1");
645 {
646 let mut other = Document::new();
647 other.add_paragraph("This paragraph was merged from another document using append().");
648 doc.append(&other);
649 }
650
651 doc.add_paragraph("");
652
653 // ── SUMMARY ──
654 doc.add_paragraph("Summary of Demonstrated Features")
655 .style("Heading1");
656 let features = [
657 "Page setup: size, margins, header/footer distance, gutter",
658 "Document metadata: title, author, subject, keywords",
659 "Headers and footers: text, images, different first page",
660 "Background images (full-page behind text)",
661 "Table of Contents generation with bookmarks",
662 "Text formatting: bold, italic, underline styles, strike, color, size, font",
663 "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
664 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
665 "Pagination controls: keep-with-next, keep-together, widow control",
666 "Bullet and numbered lists with nesting",
667 "Tab stops with dot/underscore/hyphen leaders",
668 "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
669 "Row properties: header rows, exact height, cant-split",
670 "Cell properties: width, no-wrap, vertical alignment",
671 "Inline images and header images",
672 "Placeholder replacement (single and batch)",
673 "Regex-based find and replace",
674 "Content insertion at specific positions",
675 "Section breaks with mixed portrait/landscape",
676 "Custom paragraph and character styles",
677 "Document intelligence: word count, headings, outline, images, links",
678 "Accessibility audit",
679 "Document merging (append)",
680 "Export: DOCX, PDF, HTML, Markdown",
681 ];
682 for f in &features {
683 doc.add_bullet_list_item(f, 0);
684 }
685 doc.add_paragraph("");
686 doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
687 .alignment(Alignment::Center)
688 .shading("E2EFDA")
689 .border_all(BorderStyle::Single, 2, "00AA00");
690
691 doc
692}
693
694// =============================================================================
695// 2. PROPOSAL DOCUMENT — Deep navy + gold scheme
696// =============================================================================
697fn generate_proposal(_samples_dir: &Path) -> Document {
698 let mut doc = Document::new();
699
700 // Colors: Navy #1B2A4A, Gold #C5922E, Light #F4F1EB
701 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
702 doc.set_margins(
703 Length::inches(1.0),
704 Length::inches(1.0),
705 Length::inches(1.0),
706 Length::inches(1.0),
707 );
708 doc.set_title("AI Platform Modernization Proposal");
709 doc.set_author("Walter White");
710 doc.set_subject("Technology Proposal");
711 doc.set_keywords("proposal, AI, modernization, Tensorbee");
712
713 // Banner header
714 let logo_img = create_logo_png(220, 48);
715 let banner = build_header_banner_xml(
716 "rId1",
717 &BannerOpts {
718 bg_color: "1B2A4A",
719 banner_width: 7772400,
720 banner_height: 914400,
721 logo_width: 2011680,
722 logo_height: 438912,
723 logo_x_offset: 295125,
724 logo_y_offset: 237744,
725 },
726 );
727 doc.set_raw_header_with_images(
728 banner,
729 &[("rId1", &logo_img, "logo.png")],
730 rdocx_oxml::header_footer::HdrFtrType::Default,
731 );
732 doc.set_different_first_page(true);
733 let cover_banner = build_header_banner_xml(
734 "rId1",
735 &BannerOpts {
736 bg_color: "C5922E",
737 banner_width: 7772400,
738 banner_height: 914400,
739 logo_width: 2011680,
740 logo_height: 438912,
741 logo_x_offset: 295125,
742 logo_y_offset: 237744,
743 },
744 );
745 doc.set_raw_header_with_images(
746 cover_banner,
747 &[("rId1", &logo_img, "logo.png")],
748 rdocx_oxml::header_footer::HdrFtrType::First,
749 );
750 doc.set_margins(
751 Length::twips(2292),
752 Length::twips(1440),
753 Length::twips(1440),
754 Length::twips(1440),
755 );
756 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
757 doc.set_footer("Tensorbee — Confidential");
758
759 // Custom styles
760 doc.add_style(
761 StyleBuilder::paragraph("ProposalTitle", "Proposal Title")
762 .based_on("Normal")
763 .run_properties(rdocx_oxml::properties::CT_RPr {
764 bold: Some(true),
765 font_ascii: Some("Georgia".to_string()),
766 font_hansi: Some("Georgia".to_string()),
767 sz: Some(rdocx_oxml::HalfPoint(56)), // half-points → 28pt
768 color: Some("1B2A4A".to_string()),
769 ..Default::default()
770 }),
771 );
772
773 // ── Cover Page ──
774 for _ in 0..4 {
775 doc.add_paragraph("");
776 }
777 doc.add_paragraph("AI Platform Modernization")
778 .style("ProposalTitle")
779 .alignment(Alignment::Center);
780 doc.add_paragraph("")
781 .alignment(Alignment::Center)
782 .border_bottom(BorderStyle::Single, 8, "C5922E");
783 {
784 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
785 p.add_run("Prepared for Global Financial Corp.")
786 .size(14.0)
787 .color("666666")
788 .italic(true);
789 }
790 {
791 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
792 p.add_run("Prepared by: Walter White, CEO — Tensorbee")
793 .size(12.0)
794 .color("888888");
795 }
796 {
797 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
798 p.add_run("February 22, 2026").size(12.0).color("888888");
799 }
800
801 // ── TOC ──
802 doc.add_paragraph("").page_break_before(true);
803 doc.add_paragraph("Table of Contents").style("Heading1");
804 // We'll add a manual-style TOC since the insert_toc goes at a specific position
805 doc.insert_toc(doc.content_count(), 2);
806
807 // ── Executive Summary ──
808 doc.add_paragraph("").page_break_before(true);
809 doc.add_paragraph("Executive Summary").style("Heading1");
810 doc.add_paragraph(
811 "Tensorbee proposes a comprehensive modernization of Global Financial Corp's AI platform, \
812 transitioning from legacy batch-processing systems to a real-time inference architecture. \
813 This transformation will reduce model deployment time from weeks to hours, improve \
814 prediction accuracy by an estimated 23%, and deliver $4.2M in annual operational savings.",
815 )
816 .first_line_indent(Length::inches(0.3));
817 doc.add_paragraph(
818 "The project spans three phases over 18 months, with a total investment of $2.8M. \
819 Tensorbee brings deep expertise in ML infrastructure, having successfully completed \
820 similar transformations for three Fortune 500 financial institutions.",
821 )
822 .first_line_indent(Length::inches(0.3));
823
824 // Key metrics table
825 doc.add_paragraph("");
826 {
827 let mut tbl = doc
828 .add_table(5, 2)
829 .borders(BorderStyle::Single, 4, "1B2A4A")
830 .width_pct(80.0);
831 tbl.cell(0, 0).unwrap().set_text("Key Metric");
832 tbl.cell(0, 0).unwrap().shading("1B2A4A");
833 tbl.cell(0, 1).unwrap().set_text("Value");
834 tbl.cell(0, 1).unwrap().shading("1B2A4A");
835 let rows = [
836 ("Total Investment", "$2.8M"),
837 ("Annual Savings", "$4.2M"),
838 ("ROI (Year 1)", "150%"),
839 ("Timeline", "18 months"),
840 ];
841 for (i, (k, v)) in rows.iter().enumerate() {
842 tbl.cell(i + 1, 0).unwrap().set_text(k);
843 if i % 2 == 0 {
844 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
845 }
846 tbl.cell(i + 1, 1).unwrap().set_text(v);
847 if i % 2 == 0 {
848 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
849 }
850 }
851 }
852
853 // ── Problem Statement ──
854 doc.add_paragraph("").page_break_before(true);
855 doc.add_paragraph("Problem Statement").style("Heading1");
856 doc.add_paragraph(
857 "Global Financial Corp's current AI infrastructure faces three critical challenges:",
858 );
859 doc.add_numbered_list_item("Deployment Latency — New models take 3-4 weeks to deploy to production, compared to the industry benchmark of 2-3 days.", 0);
860 doc.add_numbered_list_item("Scalability Constraints — The batch processing architecture cannot handle real-time inference demands during peak trading hours.", 0);
861 doc.add_numbered_list_item("Technical Debt — Legacy Python 2.7 codebases and manual deployment scripts create reliability risks and slow iteration speed.", 0);
862
863 // ── Proposed Solution ──
864 doc.add_paragraph("").page_break_before(true);
865 doc.add_paragraph("Proposed Solution").style("Heading1");
866
867 doc.add_paragraph("Phase 1: Foundation (Months 1-6)")
868 .style("Heading2");
869 doc.add_bullet_list_item("Deploy Kubernetes-based ML serving infrastructure", 0);
870 doc.add_bullet_list_item("Implement CI/CD pipeline for model deployment", 0);
871 doc.add_bullet_list_item("Migrate top 5 production models to new platform", 0);
872
873 doc.add_paragraph("Phase 2: Expansion (Months 7-12)")
874 .style("Heading2");
875 doc.add_bullet_list_item(
876 "Real-time feature engineering pipeline (Apache Kafka + Flink)",
877 0,
878 );
879 doc.add_bullet_list_item("A/B testing framework for model variants", 0);
880 doc.add_bullet_list_item("Automated model monitoring and drift detection", 0);
881
882 doc.add_paragraph("Phase 3: Optimization (Months 13-18)")
883 .style("Heading2");
884 doc.add_bullet_list_item("GPU inference optimization (TensorRT/ONNX Runtime)", 0);
885 doc.add_bullet_list_item("Multi-region deployment for latency reduction", 0);
886 doc.add_bullet_list_item(
887 "Self-service model deployment portal for data science teams",
888 0,
889 );
890
891 // ── Budget ──
892 doc.add_paragraph("Budget Breakdown").style("Heading1");
893 {
894 let mut tbl = doc
895 .add_table(6, 3)
896 .borders(BorderStyle::Single, 4, "1B2A4A")
897 .width_pct(100.0);
898 tbl.cell(0, 0).unwrap().set_text("Category");
899 tbl.cell(0, 0).unwrap().shading("1B2A4A");
900 tbl.cell(0, 1).unwrap().set_text("Cost");
901 tbl.cell(0, 1).unwrap().shading("1B2A4A");
902 tbl.cell(0, 2).unwrap().set_text("Notes");
903 tbl.cell(0, 2).unwrap().shading("1B2A4A");
904 let items = [
905 (
906 "Infrastructure",
907 "$450,000",
908 "Cloud compute + GPU instances",
909 ),
910 ("Engineering Services", "$1,600,000", "12 FTE x 18 months"),
911 (
912 "Software Licenses",
913 "$350,000",
914 "Kafka, monitoring, CI/CD tools",
915 ),
916 (
917 "Training & Enablement",
918 "$200,000",
919 "Team training, documentation",
920 ),
921 ("Contingency (10%)", "$200,000", "Risk buffer"),
922 ];
923 for (i, (cat, cost, note)) in items.iter().enumerate() {
924 tbl.cell(i + 1, 0).unwrap().set_text(cat);
925 tbl.cell(i + 1, 1).unwrap().set_text(cost);
926 tbl.cell(i + 1, 2).unwrap().set_text(note);
927 if i % 2 == 0 {
928 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
929 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
930 tbl.cell(i + 1, 2).unwrap().shading("F4F1EB");
931 }
932 }
933 }
934
935 // ── Closing ──
936 doc.add_paragraph("");
937 doc.add_paragraph("Next Steps").style("Heading1");
938 doc.add_numbered_list_item(
939 "Schedule technical deep-dive with GFC engineering team (Week 1)",
940 0,
941 );
942 doc.add_numbered_list_item("Finalize scope and sign SOW (Week 2-3)", 0);
943 doc.add_numbered_list_item("Kick off Phase 1 with joint planning session (Week 4)", 0);
944
945 doc.add_paragraph("");
946 {
947 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
948 p.add_run("Walter White")
949 .bold(true)
950 .size(14.0)
951 .color("1B2A4A");
952 }
953 {
954 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
955 p.add_run("CEO, Tensorbee | walter@tensorbee.com")
956 .size(10.0)
957 .color("888888");
958 }
959
960 doc
961}
962
963// =============================================================================
964// 3. QUOTE / BILL OF MATERIALS — Teal + orange scheme
965// =============================================================================
966fn generate_quote(_samples_dir: &Path) -> Document {
967 let mut doc = Document::new();
968
969 // Colors: Teal #008B8B, Dark #1A3C3C, Orange #E07020, Light #F0F8F8
970 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
971 doc.set_margins(
972 Length::inches(0.75),
973 Length::inches(0.75),
974 Length::inches(0.75),
975 Length::inches(0.75),
976 );
977 doc.set_title("Quotation QT-2026-0147");
978 doc.set_author("Walter White");
979 doc.set_keywords("quote, BOM, Tensorbee");
980
981 doc.set_header("Tensorbee — Quotation");
982 doc.set_footer("QT-2026-0147 | Page");
983
984 // ── Header Block ──
985 {
986 let mut p = doc.add_paragraph("").alignment(Alignment::Left);
987 p.add_run("TENSORBEE")
988 .bold(true)
989 .size(28.0)
990 .color("008B8B")
991 .font("Helvetica");
992 }
993 doc.add_paragraph("123 Innovation Drive, Suite 400")
994 .alignment(Alignment::Left);
995 doc.add_paragraph("San Francisco, CA 94105 | +1 (415) 555-0199")
996 .alignment(Alignment::Left);
997 doc.add_paragraph("")
998 .border_bottom(BorderStyle::Single, 8, "008B8B");
999
1000 // Quote meta
1001 doc.add_paragraph("");
1002 {
1003 let mut tbl = doc.add_table(4, 4).width_pct(100.0);
1004 tbl.cell(0, 0).unwrap().set_text("QUOTATION");
1005 tbl.cell(0, 0).unwrap().shading("008B8B").grid_span(2);
1006 tbl.cell(0, 2).unwrap().set_text("DATE");
1007 tbl.cell(0, 2).unwrap().shading("008B8B");
1008 tbl.cell(0, 3).unwrap().set_text("VALID UNTIL");
1009 tbl.cell(0, 3).unwrap().shading("008B8B");
1010 tbl.cell(1, 0).unwrap().set_text("Quote #:");
1011 tbl.cell(1, 1).unwrap().set_text("QT-2026-0147");
1012 tbl.cell(1, 2).unwrap().set_text("Feb 22, 2026");
1013 tbl.cell(1, 3).unwrap().set_text("Mar 22, 2026");
1014 tbl.cell(2, 0).unwrap().set_text("Prepared by:");
1015 tbl.cell(2, 1).unwrap().set_text("Walter White");
1016 tbl.cell(2, 2).unwrap().set_text("Payment:");
1017 tbl.cell(2, 3).unwrap().set_text("Net 30");
1018 tbl.cell(3, 0).unwrap().set_text("Customer:");
1019 tbl.cell(3, 1).unwrap().set_text("Meridian Dynamics LLC");
1020 tbl.cell(3, 2).unwrap().set_text("Currency:");
1021 tbl.cell(3, 3).unwrap().set_text("USD");
1022 }
1023
1024 doc.add_paragraph("");
1025
1026 // ── Bill of Materials ──
1027 {
1028 let mut p = doc.add_paragraph("");
1029 p.add_run("Bill of Materials")
1030 .bold(true)
1031 .size(16.0)
1032 .color("1A3C3C");
1033 }
1034 doc.add_paragraph("");
1035
1036 {
1037 let mut tbl = doc
1038 .add_table(10, 6)
1039 .borders(BorderStyle::Single, 2, "008B8B")
1040 .width_pct(100.0)
1041 .layout_fixed();
1042 // Headers
1043 let hdrs = [
1044 "#",
1045 "Part Number",
1046 "Description",
1047 "Qty",
1048 "Unit Price",
1049 "Total",
1050 ];
1051 for (c, h) in hdrs.iter().enumerate() {
1052 tbl.cell(0, c).unwrap().set_text(h);
1053 tbl.cell(0, c).unwrap().shading("008B8B");
1054 }
1055
1056 let items: Vec<(&str, &str, &str, &str, &str)> = vec![
1057 (
1058 "TB-GPU-A100",
1059 "NVIDIA A100 80GB GPU",
1060 "4",
1061 "$12,500.00",
1062 "$50,000.00",
1063 ),
1064 (
1065 "TB-SRV-R750",
1066 "Dell R750xa Server Chassis",
1067 "2",
1068 "$8,200.00",
1069 "$16,400.00",
1070 ),
1071 (
1072 "TB-RAM-256G",
1073 "256GB DDR5 ECC Memory Module",
1074 "8",
1075 "$890.00",
1076 "$7,120.00",
1077 ),
1078 (
1079 "TB-SSD-3840",
1080 "3.84TB NVMe U.2 SSD",
1081 "8",
1082 "$1,150.00",
1083 "$9,200.00",
1084 ),
1085 (
1086 "TB-NET-CX7",
1087 "ConnectX-7 200GbE NIC",
1088 "4",
1089 "$1,800.00",
1090 "$7,200.00",
1091 ),
1092 (
1093 "TB-SW-48P",
1094 "48-Port 100GbE Switch",
1095 "1",
1096 "$22,000.00",
1097 "$22,000.00",
1098 ),
1099 (
1100 "TB-CAB-RACK",
1101 "42U Server Rack + PDU",
1102 "1",
1103 "$4,500.00",
1104 "$4,500.00",
1105 ),
1106 (
1107 "TB-SVC-INST",
1108 "Installation & Configuration",
1109 "1",
1110 "$8,500.00",
1111 "$8,500.00",
1112 ),
1113 (
1114 "TB-SVC-SUPP",
1115 "3-Year Premium Support",
1116 "1",
1117 "$15,000.00",
1118 "$15,000.00",
1119 ),
1120 ];
1121
1122 for (i, (pn, desc, qty, unit, total)) in items.iter().enumerate() {
1123 let row = i + 1;
1124 tbl.cell(row, 0).unwrap().set_text(&format!("{}", i + 1));
1125 tbl.cell(row, 1).unwrap().set_text(pn);
1126 tbl.cell(row, 2).unwrap().set_text(desc);
1127 tbl.cell(row, 3).unwrap().set_text(qty);
1128 tbl.cell(row, 4).unwrap().set_text(unit);
1129 tbl.cell(row, 5).unwrap().set_text(total);
1130 if i % 2 == 0 {
1131 for c in 0..6 {
1132 tbl.cell(row, c).unwrap().shading("F0F8F8");
1133 }
1134 }
1135 }
1136 }
1137
1138 doc.add_paragraph("");
1139
1140 // ── Totals ──
1141 {
1142 let mut tbl = doc
1143 .add_table(4, 2)
1144 .borders(BorderStyle::Single, 2, "008B8B")
1145 .width(Length::inches(3.5))
1146 .alignment(Alignment::Right);
1147 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1148 tbl.cell(0, 1).unwrap().set_text("$139,920.00");
1149 tbl.cell(1, 0).unwrap().set_text("Shipping & Handling");
1150 tbl.cell(1, 1).unwrap().set_text("$2,500.00");
1151 tbl.cell(2, 0).unwrap().set_text("Tax (8.625%)");
1152 tbl.cell(2, 1).unwrap().set_text("$12,068.10");
1153 tbl.cell(3, 0).unwrap().set_text("TOTAL");
1154 tbl.cell(3, 0).unwrap().shading("E07020");
1155 tbl.cell(3, 1).unwrap().set_text("$154,488.10");
1156 tbl.cell(3, 1).unwrap().shading("E07020");
1157 }
1158
1159 doc.add_paragraph("");
1160
1161 // ── Terms & Conditions ──
1162 {
1163 let mut p = doc.add_paragraph("");
1164 p.add_run("Terms & Conditions")
1165 .bold(true)
1166 .size(14.0)
1167 .color("1A3C3C");
1168 }
1169 doc.add_numbered_list_item(
1170 "This quotation is valid for 30 calendar days from the date of issue.",
1171 0,
1172 );
1173 doc.add_numbered_list_item(
1174 "All prices are in USD and exclusive of applicable taxes unless stated.",
1175 0,
1176 );
1177 doc.add_numbered_list_item("Standard lead time is 4-6 weeks from PO receipt.", 0);
1178 doc.add_numbered_list_item(
1179 "Payment terms: Net 30 from invoice date. 2% discount for payment within 10 days.",
1180 0,
1181 );
1182 doc.add_numbered_list_item(
1183 "Warranty: 3-year manufacturer warranty on all hardware components.",
1184 0,
1185 );
1186 doc.add_numbered_list_item(
1187 "Returns subject to 15% restocking fee if initiated after 14 days.",
1188 0,
1189 );
1190
1191 doc.add_paragraph("");
1192 doc.add_paragraph("")
1193 .border_bottom(BorderStyle::Single, 4, "008B8B");
1194 doc.add_paragraph("");
1195 {
1196 let mut p = doc.add_paragraph("");
1197 p.add_run("Accepted by: ").bold(true);
1198 p.add_run("___________________________ Date: ___________");
1199 }
1200 {
1201 let mut p = doc.add_paragraph("");
1202 p.add_run("Print Name: ").bold(true);
1203 p.add_run("___________________________ Title: ___________");
1204 }
1205
1206 doc
1207}
1208
1209// =============================================================================
1210// 4. INVOICE — Crimson + charcoal scheme
1211// =============================================================================
1212fn generate_invoice(_samples_dir: &Path) -> Document {
1213 let mut doc = Document::new();
1214
1215 // Colors: Crimson #B22222, Charcoal #333333, Light #FAF0F0
1216 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1217 doc.set_margins(
1218 Length::inches(0.75),
1219 Length::inches(0.75),
1220 Length::inches(0.75),
1221 Length::inches(0.75),
1222 );
1223 doc.set_title("Invoice INV-2026-0392");
1224 doc.set_author("Walter White");
1225 doc.set_keywords("invoice, Tensorbee");
1226
1227 doc.set_footer("Tensorbee — Thank you for your business!");
1228
1229 // ── Company & Invoice Header ──
1230 {
1231 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1232 // Company name left, INVOICE right
1233 {
1234 let mut cell = tbl.cell(0, 0).unwrap();
1235 let mut p = cell.add_paragraph("");
1236 p.add_run("TENSORBEE")
1237 .bold(true)
1238 .size(32.0)
1239 .color("B22222")
1240 .font("Helvetica");
1241 }
1242 {
1243 let mut cell = tbl.cell(0, 1).unwrap();
1244 let mut p = cell.add_paragraph("");
1245 p.add_run("INVOICE").bold(true).size(32.0).color("333333");
1246 }
1247 tbl.cell(1, 0)
1248 .unwrap()
1249 .set_text("123 Innovation Drive, Suite 400");
1250 tbl.cell(1, 1).unwrap().set_text("Invoice #: INV-2026-0392");
1251 tbl.cell(2, 0).unwrap().set_text("San Francisco, CA 94105");
1252 tbl.cell(2, 1).unwrap().set_text("Date: February 22, 2026");
1253 tbl.cell(3, 0).unwrap().set_text("walter@tensorbee.com");
1254 tbl.cell(3, 1).unwrap().set_text("Due Date: March 24, 2026");
1255 }
1256
1257 doc.add_paragraph("")
1258 .border_bottom(BorderStyle::Thick, 8, "B22222");
1259 doc.add_paragraph("");
1260
1261 // ── Bill To / Ship To ──
1262 {
1263 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1264 tbl.cell(0, 0).unwrap().set_text("BILL TO");
1265 tbl.cell(0, 0).unwrap().shading("B22222");
1266 tbl.cell(0, 1).unwrap().set_text("SHIP TO");
1267 tbl.cell(0, 1).unwrap().shading("B22222");
1268 tbl.cell(1, 0).unwrap().set_text("Meridian Dynamics LLC");
1269 tbl.cell(1, 1).unwrap().set_text("Meridian Dynamics LLC");
1270 tbl.cell(2, 0).unwrap().set_text("456 Enterprise Blvd");
1271 tbl.cell(2, 1).unwrap().set_text("Attn: Server Room B");
1272 tbl.cell(3, 0).unwrap().set_text("Austin, TX 78701");
1273 tbl.cell(3, 1)
1274 .unwrap()
1275 .set_text("456 Enterprise Blvd, Austin, TX 78701");
1276 }
1277
1278 doc.add_paragraph("");
1279
1280 // ── Line Items ──
1281 {
1282 let mut tbl = doc
1283 .add_table(8, 5)
1284 .borders(BorderStyle::Single, 2, "B22222")
1285 .width_pct(100.0);
1286 let hdrs = ["Description", "Qty", "Unit Price", "Tax", "Amount"];
1287 for (c, h) in hdrs.iter().enumerate() {
1288 tbl.cell(0, c).unwrap().set_text(h);
1289 tbl.cell(0, c).unwrap().shading("B22222");
1290 }
1291 let items = [
1292 (
1293 "ML Infrastructure Setup — Phase 1",
1294 "1",
1295 "$45,000.00",
1296 "$3,881.25",
1297 "$48,881.25",
1298 ),
1299 (
1300 "Data Pipeline Development (160 hrs)",
1301 "160",
1302 "$225.00",
1303 "$3,105.00",
1304 "$39,105.00",
1305 ),
1306 (
1307 "GPU Cluster Configuration",
1308 "1",
1309 "$12,000.00",
1310 "$1,035.00",
1311 "$13,035.00",
1312 ),
1313 (
1314 "API Gateway Implementation",
1315 "1",
1316 "$18,500.00",
1317 "$1,595.63",
1318 "$20,095.63",
1319 ),
1320 (
1321 "Load Testing & QA (80 hrs)",
1322 "80",
1323 "$195.00",
1324 "$1,345.50",
1325 "$16,945.50",
1326 ),
1327 (
1328 "Documentation & Training",
1329 "1",
1330 "$8,000.00",
1331 "$690.00",
1332 "$8,690.00",
1333 ),
1334 (
1335 "Project Management (3 months)",
1336 "3",
1337 "$6,500.00",
1338 "$1,679.63",
1339 "$21,179.63",
1340 ),
1341 ];
1342 for (i, (desc, qty, unit, tax, amt)) in items.iter().enumerate() {
1343 let r = i + 1;
1344 tbl.cell(r, 0).unwrap().set_text(desc);
1345 tbl.cell(r, 1).unwrap().set_text(qty);
1346 tbl.cell(r, 2).unwrap().set_text(unit);
1347 tbl.cell(r, 3).unwrap().set_text(tax);
1348 tbl.cell(r, 4).unwrap().set_text(amt);
1349 if i % 2 == 0 {
1350 for c in 0..5 {
1351 tbl.cell(r, c).unwrap().shading("FAF0F0");
1352 }
1353 }
1354 }
1355 }
1356
1357 doc.add_paragraph("");
1358
1359 // ── Totals ──
1360 {
1361 let mut tbl = doc
1362 .add_table(4, 2)
1363 .borders(BorderStyle::Single, 2, "B22222")
1364 .width(Length::inches(3.0))
1365 .alignment(Alignment::Right);
1366 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1367 tbl.cell(0, 1).unwrap().set_text("$154,600.00");
1368 tbl.cell(1, 0).unwrap().set_text("Tax (8.625%)");
1369 tbl.cell(1, 1).unwrap().set_text("$13,334.25");
1370 tbl.cell(2, 0).unwrap().set_text("Discount (5%)");
1371 tbl.cell(2, 1).unwrap().set_text("-$7,730.00");
1372 tbl.cell(3, 0).unwrap().set_text("AMOUNT DUE");
1373 tbl.cell(3, 0).unwrap().shading("B22222");
1374 tbl.cell(3, 1).unwrap().set_text("$160,204.25");
1375 tbl.cell(3, 1).unwrap().shading("B22222");
1376 }
1377
1378 doc.add_paragraph("");
1379 doc.add_paragraph("");
1380
1381 // ── Payment Details ──
1382 {
1383 let mut p = doc.add_paragraph("");
1384 p.add_run("Payment Details")
1385 .bold(true)
1386 .size(14.0)
1387 .color("333333");
1388 }
1389 doc.add_paragraph("Bank: Silicon Valley Bank")
1390 .indent_left(Length::inches(0.3));
1391 doc.add_paragraph("Account: Tensorbee Inc. — 0847-2953-1120")
1392 .indent_left(Length::inches(0.3));
1393 doc.add_paragraph("Routing: 121140399")
1394 .indent_left(Length::inches(0.3));
1395 doc.add_paragraph("Swift: SVBKUS6S")
1396 .indent_left(Length::inches(0.3));
1397
1398 doc.add_paragraph("");
1399
1400 doc.add_paragraph("Please include invoice number INV-2026-0392 in the payment reference.")
1401 .shading("FAF0F0")
1402 .border_all(BorderStyle::Single, 2, "B22222");
1403
1404 doc
1405}
1406
1407// =============================================================================
1408// 5. REPORT — Forest green + earth tones, with images & hierarchical sections
1409// =============================================================================
1410fn generate_report(_samples_dir: &Path) -> Document {
1411 let mut doc = Document::new();
1412
1413 // Colors: Forest #2D5016, Sage #6B8E23, Earth #8B7355, Cream #FFFAF0
1414 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1415 doc.set_margins(
1416 Length::inches(1.0),
1417 Length::inches(1.0),
1418 Length::inches(1.0),
1419 Length::inches(1.0),
1420 );
1421 doc.set_title("Q4 2025 Environmental Impact Report");
1422 doc.set_author("Walter White");
1423 doc.set_subject("Quarterly Environmental Report");
1424 doc.set_keywords("environment, sustainability, report, Tensorbee");
1425
1426 doc.set_different_first_page(true);
1427 doc.set_first_page_header("");
1428 doc.set_header("Tensorbee — Q4 2025 Environmental Impact Report");
1429 doc.set_footer("CONFIDENTIAL — Page");
1430
1431 // ── Cover ──
1432 let cover_bg = create_sample_png(612, 792, [20, 50, 15]);
1433 doc.add_background_image(&cover_bg, "report_cover.png");
1434
1435 for _ in 0..5 {
1436 doc.add_paragraph("");
1437 }
1438 {
1439 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1440 p.add_run("Q4 2025")
1441 .bold(true)
1442 .size(48.0)
1443 .color("FFFFFF")
1444 .font("Georgia");
1445 }
1446 {
1447 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1448 p.add_run("Environmental Impact Report")
1449 .size(24.0)
1450 .color("90EE90")
1451 .font("Georgia")
1452 .italic(true);
1453 }
1454 doc.add_paragraph("");
1455 {
1456 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1457 p.add_run("Tensorbee — Sustainability Division")
1458 .size(14.0)
1459 .color("C0C0C0");
1460 }
1461 {
1462 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1463 p.add_run("Prepared by Walter White, Chief Sustainability Officer")
1464 .size(11.0)
1465 .color("AAAAAA");
1466 }
1467
1468 // ── TOC ──
1469 doc.add_paragraph("").page_break_before(true);
1470 doc.insert_toc(doc.content_count(), 3);
1471
1472 // ── Section 1: Executive Overview ──
1473 doc.add_paragraph("").page_break_before(true);
1474 doc.add_paragraph("Executive Overview").style("Heading1");
1475 doc.add_paragraph(
1476 "This report presents Tensorbee's environmental performance for Q4 2025. Our \
1477 sustainability initiatives have yielded a 34% reduction in carbon emissions compared \
1478 to Q4 2024, exceeding our target of 25%. Key achievements include the transition to \
1479 100% renewable energy in our primary data centers and the launch of our carbon offset \
1480 marketplace.",
1481 )
1482 .first_line_indent(Length::inches(0.3));
1483
1484 // Image: performance chart
1485 let chart_img = create_chart_png(400, 200);
1486 doc.add_paragraph("");
1487 doc.add_picture(
1488 &chart_img,
1489 "performance_chart.png",
1490 Length::inches(5.0),
1491 Length::inches(2.5),
1492 )
1493 .alignment(Alignment::Center);
1494 {
1495 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1496 p.add_run("Figure 1: Quarterly Carbon Emissions (tonnes CO2e)")
1497 .italic(true)
1498 .size(9.0)
1499 .color("666666");
1500 }
1501
1502 // ── Section 2: Energy Consumption ──
1503 doc.add_paragraph("").page_break_before(true);
1504 doc.add_paragraph("Energy Consumption").style("Heading1");
1505
1506 doc.add_paragraph("Data Center Operations")
1507 .style("Heading2");
1508 doc.add_paragraph(
1509 "Our three primary data centers consumed a combined 4.2 GWh in Q4 2025, \
1510 a 12% reduction from Q3 2025 driven by improved cooling efficiency and \
1511 server consolidation.",
1512 );
1513
1514 // Data table
1515 {
1516 let mut tbl = doc
1517 .add_table(5, 4)
1518 .borders(BorderStyle::Single, 4, "2D5016")
1519 .width_pct(100.0);
1520 let hdrs = ["Data Center", "Capacity (MW)", "Usage (GWh)", "PUE"];
1521 for (c, h) in hdrs.iter().enumerate() {
1522 tbl.cell(0, c).unwrap().set_text(h);
1523 tbl.cell(0, c).unwrap().shading("2D5016");
1524 }
1525 let rows = [
1526 ("San Francisco (Primary)", "3.2", "1.8", "1.12"),
1527 ("Dublin (EU)", "2.1", "1.2", "1.18"),
1528 ("Singapore (APAC)", "1.8", "1.2", "1.24"),
1529 ("TOTAL", "7.1", "4.2", "1.17 avg"),
1530 ];
1531 for (i, (dc, cap, use_, pue)) in rows.iter().enumerate() {
1532 tbl.cell(i + 1, 0).unwrap().set_text(dc);
1533 tbl.cell(i + 1, 1).unwrap().set_text(cap);
1534 tbl.cell(i + 1, 2).unwrap().set_text(use_);
1535 tbl.cell(i + 1, 3).unwrap().set_text(pue);
1536 if i == 3 {
1537 for c in 0..4 {
1538 tbl.cell(i + 1, c).unwrap().shading("E2EFDA");
1539 }
1540 }
1541 }
1542 }
1543
1544 doc.add_paragraph("");
1545
1546 doc.add_paragraph("Renewable Energy Mix").style("Heading2");
1547 doc.add_paragraph("Breakdown of energy sources across all facilities:");
1548
1549 doc.add_bullet_list_item("Solar PV: 42% (1.76 GWh)", 0);
1550 doc.add_bullet_list_item("Wind Power Purchase Agreements: 38% (1.60 GWh)", 0);
1551 doc.add_bullet_list_item("Hydroelectric: 12% (0.50 GWh)", 0);
1552 doc.add_bullet_list_item("Grid (non-renewable): 8% (0.34 GWh)", 0);
1553
1554 // ── Section 3: Water & Waste ──
1555 doc.add_paragraph("Water & Waste Management")
1556 .style("Heading1");
1557
1558 doc.add_paragraph("Water Usage").style("Heading2");
1559 doc.add_paragraph(
1560 "Total water consumption was 12.4 million gallons, a 15% reduction from Q3. \
1561 Our closed-loop cooling systems now recycle 78% of water used in cooling operations.",
1562 );
1563
1564 doc.add_paragraph("Waste Reduction").style("Heading2");
1565 doc.add_paragraph("E-waste management results for Q4:")
1566 .keep_with_next(true);
1567 {
1568 let mut tbl = doc
1569 .add_table(4, 3)
1570 .borders(BorderStyle::Single, 2, "6B8E23")
1571 .width_pct(80.0);
1572 tbl.cell(0, 0).unwrap().set_text("Category");
1573 tbl.cell(0, 0).unwrap().shading("6B8E23");
1574 tbl.cell(0, 1).unwrap().set_text("Weight (kg)");
1575 tbl.cell(0, 1).unwrap().shading("6B8E23");
1576 tbl.cell(0, 2).unwrap().set_text("Recycled %");
1577 tbl.cell(0, 2).unwrap().shading("6B8E23");
1578 let rows = [
1579 ("Server Hardware", "2,450", "94%"),
1580 ("Networking Equipment", "820", "91%"),
1581 ("Storage Media", "340", "99%"),
1582 ];
1583 for (i, (cat, wt, pct)) in rows.iter().enumerate() {
1584 tbl.cell(i + 1, 0).unwrap().set_text(cat);
1585 tbl.cell(i + 1, 1).unwrap().set_text(wt);
1586 tbl.cell(i + 1, 2).unwrap().set_text(pct);
1587 }
1588 }
1589
1590 // ── Section 4: Initiatives ──
1591 doc.add_paragraph("").page_break_before(true);
1592 doc.add_paragraph("Q1 2026 Initiatives").style("Heading1");
1593
1594 doc.add_paragraph("Planned Programs").style("Heading2");
1595 doc.add_numbered_list_item(
1596 "Deploy on-site battery storage at SF data center (2 MWh capacity)",
1597 0,
1598 );
1599 doc.add_numbered_list_item("Pilot immersion cooling in Dublin facility", 0);
1600 doc.add_numbered_list_item("Launch employee commute carbon offset program", 0);
1601 doc.add_numbered_list_item("Achieve ISO 14001 certification for Singapore facility", 0);
1602
1603 doc.add_paragraph("Investment Targets").style("Heading2");
1604 doc.add_paragraph("Sustainability CapEx allocation for FY2026:")
1605 .keep_with_next(true);
1606 {
1607 let mut tbl = doc
1608 .add_table(5, 2)
1609 .borders(BorderStyle::Single, 4, "2D5016");
1610 tbl.cell(0, 0).unwrap().set_text("Initiative");
1611 tbl.cell(0, 0).unwrap().shading("2D5016");
1612 tbl.cell(0, 1).unwrap().set_text("Budget");
1613 tbl.cell(0, 1).unwrap().shading("2D5016");
1614 let rows = [
1615 ("Battery Storage", "$1.2M"),
1616 ("Immersion Cooling Pilot", "$800K"),
1617 ("Solar Panel Expansion", "$2.1M"),
1618 ("Carbon Credits", "$500K"),
1619 ];
1620 for (i, (init, budget)) in rows.iter().enumerate() {
1621 tbl.cell(i + 1, 0).unwrap().set_text(init);
1622 tbl.cell(i + 1, 1).unwrap().set_text(budget);
1623 if i % 2 == 0 {
1624 tbl.cell(i + 1, 0).unwrap().shading("FFFAF0");
1625 tbl.cell(i + 1, 1).unwrap().shading("FFFAF0");
1626 }
1627 }
1628 }
1629
1630 // Image: sustainability roadmap
1631 let roadmap_img = create_sample_png(500, 100, [40, 80, 30]);
1632 doc.add_paragraph("");
1633 doc.add_picture(
1634 &roadmap_img,
1635 "roadmap.png",
1636 Length::inches(6.0),
1637 Length::inches(1.2),
1638 )
1639 .alignment(Alignment::Center);
1640 {
1641 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1642 p.add_run("Figure 2: 2026 Sustainability Roadmap")
1643 .italic(true)
1644 .size(9.0)
1645 .color("666666");
1646 }
1647
1648 doc.add_paragraph("");
1649 doc.add_paragraph(
1650 "For questions about this report, contact Walter White at sustainability@tensorbee.com.",
1651 )
1652 .shading("FFFAF0")
1653 .border_all(BorderStyle::Single, 2, "2D5016");
1654
1655 doc
1656}
1657
1658// =============================================================================
1659// 6. LETTER — Slate blue + silver scheme
1660// =============================================================================
1661fn generate_letter(_samples_dir: &Path) -> Document {
1662 let mut doc = Document::new();
1663
1664 // Colors: Slate #4A5568, Blue accent #3182CE
1665 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1666 doc.set_margins(
1667 Length::inches(1.25),
1668 Length::inches(1.25),
1669 Length::inches(1.25),
1670 Length::inches(1.25),
1671 );
1672 doc.set_title("Business Letter");
1673 doc.set_author("Walter White");
1674
1675 // Banner header using raw XML
1676 let logo_img = create_logo_png(220, 48);
1677 let banner = build_header_banner_xml(
1678 "rId1",
1679 &BannerOpts {
1680 bg_color: "4A5568",
1681 banner_width: 7772400,
1682 banner_height: 731520, // ~0.8"
1683 logo_width: 2011680,
1684 logo_height: 438912,
1685 logo_x_offset: 295125,
1686 logo_y_offset: 146304,
1687 },
1688 );
1689 doc.set_raw_header_with_images(
1690 banner,
1691 &[("rId1", &logo_img, "logo.png")],
1692 rdocx_oxml::header_footer::HdrFtrType::Default,
1693 );
1694 doc.set_margins(
1695 Length::twips(2000),
1696 Length::twips(1800),
1697 Length::twips(1440),
1698 Length::twips(1800),
1699 );
1700 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
1701
1702 // Footer with contact
1703 doc.set_footer(
1704 "Tensorbee | 123 Innovation Drive, Suite 400 | San Francisco, CA 94105 | tensorbee.com",
1705 );
1706
1707 // ── Sender Address ──
1708 doc.add_paragraph("Walter White");
1709 doc.add_paragraph("Chief Executive Officer");
1710 doc.add_paragraph("Tensorbee");
1711 doc.add_paragraph("123 Innovation Drive, Suite 400");
1712 doc.add_paragraph("San Francisco, CA 94105");
1713 doc.add_paragraph("");
1714 doc.add_paragraph("February 22, 2026");
1715
1716 doc.add_paragraph("");
1717
1718 // ── Recipient ──
1719 doc.add_paragraph("Ms. Sarah Chen");
1720 doc.add_paragraph("VP of Engineering");
1721 doc.add_paragraph("Meridian Dynamics LLC");
1722 doc.add_paragraph("456 Enterprise Boulevard");
1723 doc.add_paragraph("Austin, TX 78701");
1724
1725 doc.add_paragraph("");
1726
1727 // ── Subject ──
1728 {
1729 let mut p = doc.add_paragraph("");
1730 p.add_run("Re: Strategic Technology Partnership — Phase 2 Expansion")
1731 .bold(true);
1732 }
1733
1734 doc.add_paragraph("");
1735
1736 // ── Body ──
1737 doc.add_paragraph("Dear Ms. Chen,");
1738 doc.add_paragraph("");
1739
1740 doc.add_paragraph(
1741 "I am writing to express Tensorbee's enthusiasm for expanding our strategic technology \
1742 partnership with Meridian Dynamics. Following the successful completion of Phase 1, \
1743 which delivered a 40% improvement in your ML inference pipeline throughput, we believe \
1744 the foundation is firmly established for an ambitious Phase 2 engagement.",
1745 )
1746 .first_line_indent(Length::inches(0.5));
1747
1748 doc.add_paragraph(
1749 "During our recent executive review, your team highlighted three priority areas for \
1750 the next phase of collaboration:",
1751 )
1752 .first_line_indent(Length::inches(0.5));
1753
1754 doc.add_numbered_list_item(
1755 "Real-time anomaly detection for your financial transaction monitoring system, \
1756 targeting sub-100ms latency at 50,000 transactions per second.",
1757 0,
1758 );
1759 doc.add_numbered_list_item(
1760 "Federated learning infrastructure to enable model training across your distributed \
1761 data centers without centralizing sensitive financial data.",
1762 0,
1763 );
1764 doc.add_numbered_list_item(
1765 "MLOps automation to reduce your model deployment cycle from the current 5 days \
1766 to under 4 hours.",
1767 0,
1768 );
1769
1770 doc.add_paragraph(
1771 "Our engineering team has prepared a detailed technical proposal addressing each of \
1772 these areas. We have allocated a dedicated team of eight senior engineers, led by \
1773 our CTO, to ensure continuity with the Phase 1 team your organization has already \
1774 built a productive working relationship with.",
1775 )
1776 .first_line_indent(Length::inches(0.5));
1777
1778 doc.add_paragraph(
1779 "I would welcome the opportunity to present our Phase 2 proposal in person. My \
1780 assistant will reach out to coordinate a meeting at your convenience during the \
1781 first week of March.",
1782 )
1783 .first_line_indent(Length::inches(0.5));
1784
1785 doc.add_paragraph("");
1786
1787 doc.add_paragraph("Warm regards,");
1788 doc.add_paragraph("");
1789 doc.add_paragraph("");
1790
1791 // Signature line
1792 doc.add_paragraph("")
1793 .border_bottom(BorderStyle::Single, 4, "4A5568");
1794 {
1795 let mut p = doc.add_paragraph("");
1796 p.add_run("Walter White").bold(true).size(12.0);
1797 }
1798 {
1799 let mut p = doc.add_paragraph("");
1800 p.add_run("Chief Executive Officer, Tensorbee")
1801 .italic(true)
1802 .size(10.0)
1803 .color("666666");
1804 }
1805 {
1806 let mut p = doc.add_paragraph("");
1807 p.add_run("walter@tensorbee.com | +1 (415) 555-0199")
1808 .size(10.0)
1809 .color("888888");
1810 }
1811
1812 doc
1813}
1814
1815// =============================================================================
1816// 7. EMPLOYMENT CONTRACT — Purple + charcoal scheme
1817// =============================================================================
1818fn generate_contract(_samples_dir: &Path) -> Document {
1819 let mut doc = Document::new();
1820
1821 // Colors: Purple #5B2C6F, Plum #8E44AD, Gray #2C3E50
1822 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1823 doc.set_margins(
1824 Length::inches(1.25),
1825 Length::inches(1.0),
1826 Length::inches(1.0),
1827 Length::inches(1.0),
1828 );
1829 doc.set_title("Employment Agreement");
1830 doc.set_author("Tensorbee HR Department");
1831 doc.set_subject("Employment Contract — Walter White");
1832 doc.set_keywords("employment, contract, agreement, Tensorbee");
1833
1834 doc.set_header("TENSORBEE — Employment Agreement");
1835 doc.set_footer("Employment Agreement — Walter White — Page");
1836
1837 // ── Title Block ──
1838 doc.add_paragraph("")
1839 .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1840 {
1841 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1842 p.add_run("EMPLOYMENT AGREEMENT")
1843 .bold(true)
1844 .size(24.0)
1845 .color("5B2C6F")
1846 .font("Georgia");
1847 }
1848 doc.add_paragraph("")
1849 .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1850 doc.add_paragraph("");
1851
1852 // ── Parties ──
1853 doc.add_paragraph(
1854 "This Employment Agreement (\"Agreement\") is entered into as of February 22, 2026 \
1855 (\"Effective Date\"), by and between:",
1856 );
1857
1858 doc.add_paragraph("");
1859
1860 {
1861 let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1862 p.add_run("EMPLOYER: ").bold(true);
1863 p.add_run(
1864 "Tensorbee, Inc., a Delaware corporation with its principal place of business at \
1865 123 Innovation Drive, Suite 400, San Francisco, CA 94105 (\"Company\")",
1866 );
1867 }
1868 doc.add_paragraph("");
1869 {
1870 let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1871 p.add_run("EMPLOYEE: ").bold(true);
1872 p.add_run(
1873 "Walter White, residing at 308 Negra Arroyo Lane, Albuquerque, NM 87104 (\"Employee\")",
1874 );
1875 }
1876
1877 doc.add_paragraph("");
1878 doc.add_paragraph("The Company and Employee are collectively referred to as the \"Parties.\"");
1879 doc.add_paragraph("");
1880
1881 // ── Article 1: Position and Duties ──
1882 doc.add_paragraph("Article 1 — Position and Duties")
1883 .style("Heading1");
1884 {
1885 let mut p = doc.add_paragraph("");
1886 p.add_run("1.1 ").bold(true);
1887 p.add_run("The Company hereby employs the Employee as ");
1888 p.add_run("Chief Executive Officer (CEO)")
1889 .bold(true)
1890 .italic(true);
1891 p.add_run(", reporting directly to the Board of Directors.");
1892 }
1893 {
1894 let mut p = doc.add_paragraph("");
1895 p.add_run("1.2 ").bold(true);
1896 p.add_run("The Employee shall devote full working time, attention, and best efforts to \
1897 the performance of duties as reasonably assigned by the Board, including but not limited to:");
1898 }
1899 doc.add_bullet_list_item(
1900 "Setting and executing the Company's strategic vision and business plan",
1901 0,
1902 );
1903 doc.add_bullet_list_item(
1904 "Overseeing all operations, engineering, and commercial activities",
1905 0,
1906 );
1907 doc.add_bullet_list_item(
1908 "Representing the Company to investors, customers, and the public",
1909 0,
1910 );
1911 doc.add_bullet_list_item("Recruiting, developing, and retaining key talent", 0);
1912
1913 {
1914 let mut p = doc.add_paragraph("");
1915 p.add_run("1.3 ").bold(true);
1916 p.add_run(
1917 "The Employee's primary work location shall be the Company's San Francisco \
1918 headquarters, with reasonable travel as required by business needs.",
1919 );
1920 }
1921
1922 // ── Article 2: Compensation ──
1923 doc.add_paragraph("Article 2 — Compensation and Benefits")
1924 .style("Heading1");
1925 {
1926 let mut p = doc.add_paragraph("");
1927 p.add_run("2.1 Base Salary. ").bold(true);
1928 p.add_run("The Company shall pay the Employee an annual base salary of ");
1929 p.add_run("$375,000.00").bold(true);
1930 p.add_run(" (Three Hundred Seventy-Five Thousand Dollars), payable in accordance with the \
1931 Company's standard payroll schedule, less applicable withholdings and deductions.");
1932 }
1933 {
1934 let mut p = doc.add_paragraph("");
1935 p.add_run("2.2 Annual Bonus. ").bold(true);
1936 p.add_run("The Employee shall be eligible for an annual performance bonus of up to ");
1937 p.add_run("40%").bold(true);
1938 p.add_run(
1939 " of base salary, based on achievement of mutually agreed performance objectives.",
1940 );
1941 }
1942 {
1943 let mut p = doc.add_paragraph("");
1944 p.add_run("2.3 Equity. ").bold(true);
1945 p.add_run("Subject to Board approval, the Employee shall receive a stock option grant of ");
1946 p.add_run("500,000 shares").bold(true);
1947 p.add_run(
1948 " of the Company's common stock, vesting over four (4) years with a one-year cliff, \
1949 at an exercise price equal to the fair market value on the date of grant.",
1950 );
1951 }
1952
1953 // Compensation summary table
1954 doc.add_paragraph("");
1955 {
1956 let mut tbl = doc
1957 .add_table(5, 2)
1958 .borders(BorderStyle::Single, 4, "5B2C6F")
1959 .width_pct(70.0)
1960 .alignment(Alignment::Center);
1961 tbl.cell(0, 0).unwrap().set_text("Compensation Element");
1962 tbl.cell(0, 0).unwrap().shading("5B2C6F");
1963 tbl.cell(0, 1).unwrap().set_text("Value");
1964 tbl.cell(0, 1).unwrap().shading("5B2C6F");
1965 let items = [
1966 ("Base Salary", "$375,000/year"),
1967 ("Target Bonus", "Up to 40% ($150,000)"),
1968 ("Equity Grant", "500,000 shares (4yr vest)"),
1969 ("Total Target Comp", "$525,000 + equity"),
1970 ];
1971 for (i, (elem, val)) in items.iter().enumerate() {
1972 tbl.cell(i + 1, 0).unwrap().set_text(elem);
1973 tbl.cell(i + 1, 1).unwrap().set_text(val);
1974 if i == 3 {
1975 tbl.cell(i + 1, 0).unwrap().shading("E8DAEF");
1976 tbl.cell(i + 1, 1).unwrap().shading("E8DAEF");
1977 }
1978 }
1979 }
1980
1981 // ── Article 3: Benefits ──
1982 doc.add_paragraph("").page_break_before(true);
1983 doc.add_paragraph("Article 3 — Benefits").style("Heading1");
1984 {
1985 let mut p = doc.add_paragraph("");
1986 p.add_run("3.1 ").bold(true);
1987 p.add_run(
1988 "The Employee shall be entitled to participate in all benefit programs \
1989 generally available to senior executives, including:",
1990 );
1991 }
1992 doc.add_bullet_list_item(
1993 "Medical, dental, and vision insurance (100% premium coverage for employee and dependents)",
1994 0,
1995 );
1996 doc.add_bullet_list_item("401(k) retirement plan with 6% company match", 0);
1997 doc.add_bullet_list_item("Life insurance and long-term disability coverage", 0);
1998 doc.add_bullet_list_item("Annual professional development allowance of $10,000", 0);
1999
2000 {
2001 let mut p = doc.add_paragraph("");
2002 p.add_run("3.2 Paid Time Off. ").bold(true);
2003 p.add_run(
2004 "The Employee shall receive 25 days of paid vacation per year, plus Company holidays, \
2005 accruing on a monthly basis.",
2006 );
2007 }
2008
2009 // ── Article 4: Term and Termination ──
2010 doc.add_paragraph("Article 4 — Term and Termination")
2011 .style("Heading1");
2012 {
2013 let mut p = doc.add_paragraph("");
2014 p.add_run("4.1 At-Will Employment. ").bold(true);
2015 p.add_run(
2016 "This Agreement is for at-will employment and may be terminated by either Party \
2017 at any time, with or without cause, subject to the notice provisions herein.",
2018 );
2019 }
2020 {
2021 let mut p = doc.add_paragraph("");
2022 p.add_run("4.2 Notice Period. ").bold(true);
2023 p.add_run("Either Party shall provide ");
2024 p.add_run("ninety (90) days").bold(true);
2025 p.add_run(" written notice of termination, or pay in lieu thereof.");
2026 }
2027 {
2028 let mut p = doc.add_paragraph("");
2029 p.add_run("4.3 Severance. ").bold(true);
2030 p.add_run("In the event of termination by the Company without Cause, the Employee shall \
2031 receive (a) twelve (12) months of base salary continuation, (b) pro-rata bonus \
2032 for the year of termination, and (c) twelve (12) months of COBRA premium coverage.");
2033 }
2034
2035 // ── Article 5: Confidentiality ──
2036 doc.add_paragraph("Article 5 — Confidentiality and IP")
2037 .style("Heading1");
2038 {
2039 let mut p = doc.add_paragraph("");
2040 p.add_run("5.1 ").bold(true);
2041 p.add_run(
2042 "The Employee agrees to maintain strict confidentiality of all proprietary \
2043 information, trade secrets, and business plans of the Company during and after \
2044 employment.",
2045 );
2046 }
2047 {
2048 let mut p = doc.add_paragraph("");
2049 p.add_run("5.2 ").bold(true);
2050 p.add_run(
2051 "All intellectual property, inventions, and works of authorship created by the \
2052 Employee during the term of employment and within the scope of duties shall be \
2053 the sole and exclusive property of the Company.",
2054 );
2055 }
2056
2057 // ── Article 6: Non-Compete ──
2058 doc.add_paragraph("Article 6 — Non-Competition")
2059 .style("Heading1");
2060 {
2061 let mut p = doc.add_paragraph("");
2062 p.add_run("6.1 ").bold(true);
2063 p.add_run("For a period of twelve (12) months following termination, the Employee agrees \
2064 not to engage in any business that directly competes with the Company's core \
2065 business of AI infrastructure and ML operations services, within the United States.");
2066 }
2067
2068 // ── Article 7: General Provisions ──
2069 doc.add_paragraph("Article 7 — General Provisions")
2070 .style("Heading1");
2071 {
2072 let mut p = doc.add_paragraph("");
2073 p.add_run("7.1 Governing Law. ").bold(true);
2074 p.add_run(
2075 "This Agreement shall be governed by and construed in accordance with the laws of \
2076 the State of California.",
2077 );
2078 }
2079 {
2080 let mut p = doc.add_paragraph("");
2081 p.add_run("7.2 Entire Agreement. ").bold(true);
2082 p.add_run(
2083 "This Agreement constitutes the entire agreement between the Parties and supersedes \
2084 all prior negotiations, representations, and agreements.",
2085 );
2086 }
2087 {
2088 let mut p = doc.add_paragraph("");
2089 p.add_run("7.3 Amendment. ").bold(true);
2090 p.add_run(
2091 "This Agreement may only be amended by a written instrument signed by both Parties.",
2092 );
2093 }
2094
2095 // ── Signature Block ──
2096 doc.add_paragraph("").page_break_before(true);
2097 doc.add_paragraph("IN WITNESS WHEREOF, the Parties have executed this Employment Agreement as of the Effective Date.")
2098 .space_after(Length::pt(24.0));
2099
2100 // Signature table
2101 {
2102 let mut tbl = doc.add_table(6, 2).width_pct(100.0);
2103 tbl.cell(0, 0).unwrap().set_text("FOR THE COMPANY:");
2104 tbl.cell(0, 0).unwrap().shading("5B2C6F");
2105 tbl.cell(0, 1).unwrap().set_text("EMPLOYEE:");
2106 tbl.cell(0, 1).unwrap().shading("5B2C6F");
2107
2108 tbl.cell(1, 0).unwrap().set_text("");
2109 tbl.cell(1, 1).unwrap().set_text("");
2110 tbl.row(1).unwrap().height(Length::pt(40.0));
2111
2112 tbl.cell(2, 0)
2113 .unwrap()
2114 .set_text("Signature: ___________________________");
2115 tbl.cell(2, 1)
2116 .unwrap()
2117 .set_text("Signature: ___________________________");
2118 tbl.cell(3, 0)
2119 .unwrap()
2120 .set_text("Name: Board Representative");
2121 tbl.cell(3, 1).unwrap().set_text("Name: Walter White");
2122 tbl.cell(4, 0)
2123 .unwrap()
2124 .set_text("Title: Chair, Board of Directors");
2125 tbl.cell(4, 1)
2126 .unwrap()
2127 .set_text("Title: Chief Executive Officer");
2128 tbl.cell(5, 0).unwrap().set_text("Date: _______________");
2129 tbl.cell(5, 1).unwrap().set_text("Date: _______________");
2130 }
2131
2132 doc
2133}Set the default footer text.
Examples found in repository?
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
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}97fn generate_feature_showcase(_samples_dir: &Path) -> Document {
98 let mut doc = Document::new();
99
100 // ── Page Setup & Metadata ──
101 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
102 doc.set_margins(
103 Length::inches(1.0),
104 Length::inches(1.0),
105 Length::inches(1.0),
106 Length::inches(1.0),
107 );
108 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
109 doc.set_gutter(Length::twips(0));
110 doc.set_title("rdocx Feature Showcase");
111 doc.set_author("rdocx Sample Generator");
112 doc.set_subject("Comprehensive feature demonstration");
113 doc.set_keywords("rdocx, docx, rust, sample, showcase");
114
115 // Headers & Footers with different first page
116 doc.set_different_first_page(true);
117 doc.set_first_page_header("rdocx");
118 doc.set_first_page_footer("Feature Showcase — Cover Page");
119 doc.set_header("rdocx Feature Showcase");
120 doc.set_footer("Generated by rdocx");
121
122 // ── COVER PAGE ──
123 let bg = create_sample_png(612, 792, [20, 45, 90]);
124 doc.add_background_image(&bg, "cover_bg.png");
125
126 for _ in 0..3 {
127 doc.add_paragraph("");
128 }
129 {
130 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
131 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
132 }
133 {
134 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
135 p.add_run("Complete Feature Showcase")
136 .size(28.0)
137 .color("FFFFFF")
138 .italic(true);
139 }
140 doc.add_paragraph("");
141 {
142 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
143 p.add_run("Every feature of the rdocx Rust library")
144 .size(13.0)
145 .color("B0C4DE");
146 }
147 {
148 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
149 p.add_run("demonstrated in a single document.")
150 .size(13.0)
151 .color("B0C4DE");
152 }
153
154 // ── TABLE OF CONTENTS ──
155 doc.add_paragraph("").page_break_before(true);
156 doc.insert_toc(doc.content_count(), 3);
157
158 // ── SECTION 1: TEXT FORMATTING ──
159 doc.add_paragraph("").page_break_before(true);
160 doc.add_paragraph("1. Text Formatting").style("Heading1");
161
162 // Paragraph Alignment
163 doc.add_paragraph("1.1 Paragraph Alignment")
164 .style("Heading2");
165 doc.add_paragraph("Left-aligned paragraph (default).")
166 .alignment(Alignment::Left);
167 doc.add_paragraph("Center-aligned paragraph.")
168 .alignment(Alignment::Center);
169 doc.add_paragraph("Right-aligned paragraph.")
170 .alignment(Alignment::Right);
171 doc.add_paragraph(
172 "Justified paragraph — this text is long enough to demonstrate how justified alignment \
173 distributes extra space across word gaps so lines fill the full width of the text area.",
174 )
175 .alignment(Alignment::Justify);
176
177 doc.add_paragraph("");
178
179 // Run Formatting
180 doc.add_paragraph("1.2 Run Formatting").style("Heading2");
181 {
182 let mut p = doc.add_paragraph("");
183 p.add_run("Bold").bold(true);
184 p.add_run(" | ");
185 p.add_run("Italic").italic(true);
186 p.add_run(" | ");
187 p.add_run("Bold Italic").bold(true).italic(true);
188 p.add_run(" | ");
189 p.add_run("Underline").underline(true);
190 p.add_run(" | ");
191 p.add_run("Strikethrough").strike(true);
192 p.add_run(" | ");
193 p.add_run("Double Strike").double_strike(true);
194 }
195 {
196 let mut p = doc.add_paragraph("");
197 p.add_run("Red").color("FF0000");
198 p.add_run(" | ");
199 p.add_run("Blue").color("0000FF");
200 p.add_run(" | ");
201 p.add_run("Green").color("00AA00");
202 p.add_run(" | ");
203 p.add_run("Highlighted").highlight("FFFF00");
204 }
205 {
206 let mut p = doc.add_paragraph("");
207 p.add_run("8pt").size(8.0);
208 p.add_run(" | ");
209 p.add_run("11pt").size(11.0);
210 p.add_run(" | ");
211 p.add_run("16pt").size(16.0);
212 p.add_run(" | ");
213 p.add_run("24pt").size(24.0);
214 }
215 {
216 let mut p = doc.add_paragraph("");
217 p.add_run("Arial").font("Arial");
218 p.add_run(" | ");
219 p.add_run("Times New Roman").font("Times New Roman");
220 p.add_run(" | ");
221 p.add_run("Courier New").font("Courier New");
222 }
223 {
224 let mut p = doc.add_paragraph("");
225 p.add_run("H");
226 p.add_run("2").subscript();
227 p.add_run("O (subscript) | E=mc");
228 p.add_run("2").superscript();
229 p.add_run(" (superscript)");
230 }
231 {
232 let mut p = doc.add_paragraph("");
233 p.add_run("ALL CAPS").all_caps(true);
234 p.add_run(" | ");
235 p.add_run("Small Caps").small_caps(true);
236 p.add_run(" | ");
237 p.add_run("Expanded").character_spacing(Length::twips(40));
238 p.add_run(" | ");
239 p.add_run("Hidden text (not visible)").hidden(true);
240 }
241
242 doc.add_paragraph("");
243
244 // Underline Styles
245 doc.add_paragraph("1.3 Underline Styles").style("Heading2");
246 {
247 let mut p = doc.add_paragraph("");
248 p.add_run("Single ")
249 .underline_style(rdocx::UnderlineStyle::Single);
250 p.add_run("Double ")
251 .underline_style(rdocx::UnderlineStyle::Double);
252 p.add_run("Thick ")
253 .underline_style(rdocx::UnderlineStyle::Thick);
254 p.add_run("Dotted ")
255 .underline_style(rdocx::UnderlineStyle::Dotted);
256 p.add_run("Dash ")
257 .underline_style(rdocx::UnderlineStyle::Dash);
258 p.add_run("Wave ")
259 .underline_style(rdocx::UnderlineStyle::Wave);
260 }
261
262 // ── SECTION 2: PARAGRAPH FORMATTING ──
263 doc.add_paragraph("").page_break_before(true);
264 doc.add_paragraph("2. Paragraph Formatting")
265 .style("Heading1");
266
267 doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
268 doc.add_paragraph("Paragraph with light green shading")
269 .shading("E2EFDA");
270 doc.add_paragraph("Paragraph with bottom border")
271 .border_bottom(BorderStyle::Single, 6, "2E75B6");
272 doc.add_paragraph("Paragraph with all borders (red)")
273 .border_all(BorderStyle::Single, 4, "FF0000");
274
275 doc.add_paragraph("2.2 Indentation").style("Heading2");
276 doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
277 .indent_left(Length::inches(1.0))
278 .hanging_indent(Length::inches(0.5));
279 doc.add_paragraph("0.5-inch first-line indent on this paragraph")
280 .first_line_indent(Length::inches(0.5));
281 doc.add_paragraph("Both left and right indent (0.75 inches each)")
282 .indent_left(Length::inches(0.75))
283 .indent_right(Length::inches(0.75));
284
285 doc.add_paragraph("2.3 Spacing & Line Height")
286 .style("Heading2");
287 doc.add_paragraph("Extra space before (24pt) and after (12pt)")
288 .space_before(Length::pt(24.0))
289 .space_after(Length::pt(12.0));
290 doc.add_paragraph(
291 "Double line spacing paragraph. This text should have extra vertical space between \
292 lines to demonstrate line_spacing_multiple(2.0).",
293 )
294 .line_spacing_multiple(2.0);
295 doc.add_paragraph("Exact 20pt line spacing (fixed height).")
296 .line_spacing(20.0);
297
298 doc.add_paragraph("2.4 Pagination Controls")
299 .style("Heading2");
300 doc.add_paragraph("keep_with_next — this paragraph stays with the next")
301 .keep_with_next(true);
302 doc.add_paragraph("(This paragraph was kept with the one above.)");
303 doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
304 .keep_together(true);
305 doc.add_paragraph("widow_control — prevents widow/orphan lines")
306 .widow_control(true);
307
308 // ── SECTION 3: LISTS ──
309 doc.add_paragraph("").page_break_before(true);
310 doc.add_paragraph("3. Lists").style("Heading1");
311
312 doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
313 doc.add_bullet_list_item("First item", 0);
314 doc.add_bullet_list_item("Second item", 0);
315 doc.add_bullet_list_item("Nested level 1", 1);
316 doc.add_bullet_list_item("Nested level 2", 2);
317 doc.add_bullet_list_item("Back to level 1", 1);
318 doc.add_bullet_list_item("Third item", 0);
319
320 doc.add_paragraph("");
321
322 doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
323 doc.add_numbered_list_item("First numbered item", 0);
324 doc.add_numbered_list_item("Second numbered item", 0);
325 doc.add_numbered_list_item("Sub-item A", 1);
326 doc.add_numbered_list_item("Sub-item B", 1);
327 doc.add_numbered_list_item("Third numbered item", 0);
328
329 // ── SECTION 4: TAB STOPS ──
330 doc.add_paragraph("");
331 doc.add_paragraph("4. Tab Stops").style("Heading1");
332
333 doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
334 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
335 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
336 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
337 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
338 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
339
340 doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
341 doc.add_paragraph("Item\t\tPrice")
342 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
343 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
344 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
345 doc.add_paragraph("Widget\t\t$19.99")
346 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
347 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
348 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
349 doc.add_paragraph("Gadget\t\t$249.50")
350 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
351 .add_tab_stop_with_leader(
352 TabAlignment::Right,
353 Length::inches(4.0),
354 TabLeader::Underscore,
355 )
356 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
357
358 // ── SECTION 5: TABLES ──
359 doc.add_paragraph("").page_break_before(true);
360 doc.add_paragraph("5. Tables").style("Heading1");
361
362 doc.add_paragraph("5.1 Basic Table").style("Heading2");
363 {
364 let mut tbl = doc
365 .add_table(4, 3)
366 .borders(BorderStyle::Single, 4, "000000");
367 for col in 0..3 {
368 tbl.cell(0, col).unwrap().shading("2E75B6");
369 }
370 tbl.cell(0, 0).unwrap().set_text("Name");
371 tbl.cell(0, 1).unwrap().set_text("Role");
372 tbl.cell(0, 2).unwrap().set_text("Location");
373 tbl.cell(1, 0).unwrap().set_text("Walter White");
374 tbl.cell(1, 1).unwrap().set_text("CEO");
375 tbl.cell(1, 2).unwrap().set_text("Albuquerque");
376 tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
377 tbl.cell(2, 1).unwrap().set_text("CTO");
378 tbl.cell(2, 2).unwrap().set_text("Remote");
379 tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
380 tbl.cell(3, 1).unwrap().set_text("Security");
381 tbl.cell(3, 2).unwrap().set_text("Washington");
382 }
383
384 doc.add_paragraph("");
385
386 doc.add_paragraph("5.2 Column Span & Cell Shading")
387 .style("Heading2");
388 {
389 let mut tbl = doc
390 .add_table(3, 4)
391 .borders(BorderStyle::Single, 4, "000000")
392 .width_pct(100.0);
393 tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
394 tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
395 tbl.cell(1, 0).unwrap().set_text("Region");
396 tbl.cell(1, 0).unwrap().shading("D6E4F0");
397 tbl.cell(1, 1).unwrap().set_text("Q1");
398 tbl.cell(1, 1).unwrap().shading("D6E4F0");
399 tbl.cell(1, 2).unwrap().set_text("Q2");
400 tbl.cell(1, 2).unwrap().shading("D6E4F0");
401 tbl.cell(1, 3).unwrap().set_text("Total");
402 tbl.cell(1, 3).unwrap().shading("D6E4F0");
403 tbl.cell(2, 0).unwrap().set_text("Americas");
404 tbl.cell(2, 1).unwrap().set_text("$2.4M");
405 tbl.cell(2, 2).unwrap().set_text("$2.7M");
406 tbl.cell(2, 3).unwrap().set_text("$5.1M");
407 }
408
409 doc.add_paragraph("");
410
411 doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
412 {
413 let mut tbl = doc
414 .add_table(4, 3)
415 .borders(BorderStyle::Single, 4, "000000");
416 tbl.cell(0, 0).unwrap().set_text("Category");
417 tbl.cell(0, 0).unwrap().shading("E2EFDA");
418 tbl.cell(0, 1).unwrap().set_text("Item");
419 tbl.cell(0, 1).unwrap().shading("E2EFDA");
420 tbl.cell(0, 2).unwrap().set_text("Price");
421 tbl.cell(0, 2).unwrap().shading("E2EFDA");
422 tbl.cell(1, 0).unwrap().set_text("Hardware");
423 tbl.cell(1, 0).unwrap().v_merge_restart();
424 tbl.cell(1, 1).unwrap().set_text("Laptop");
425 tbl.cell(1, 2).unwrap().set_text("$1,200");
426 tbl.cell(2, 0).unwrap().v_merge_continue();
427 tbl.cell(2, 1).unwrap().set_text("Monitor");
428 tbl.cell(2, 2).unwrap().set_text("$450");
429 tbl.cell(3, 0).unwrap().set_text("Software");
430 tbl.cell(3, 1).unwrap().set_text("IDE License");
431 tbl.cell(3, 2).unwrap().set_text("$200/yr");
432 }
433
434 doc.add_paragraph("");
435
436 doc.add_paragraph("5.4 Nested Table").style("Heading2");
437 {
438 let mut tbl = doc
439 .add_table(2, 2)
440 .borders(BorderStyle::Single, 6, "2E75B6");
441 tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
442 tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
443 tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
444 {
445 let mut cell = tbl.cell(1, 1).unwrap();
446 cell.set_text("Nested table below:");
447 let mut nested = cell
448 .add_table(2, 2)
449 .borders(BorderStyle::Single, 2, "FF6600");
450 nested.cell(0, 0).unwrap().set_text("A");
451 nested.cell(0, 1).unwrap().set_text("B");
452 nested.cell(1, 0).unwrap().set_text("C");
453 nested.cell(1, 1).unwrap().set_text("D");
454 }
455 }
456
457 doc.add_paragraph("");
458
459 doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
460 .style("Heading2");
461 {
462 let mut tbl = doc
463 .add_table(2, 3)
464 .borders(BorderStyle::Single, 4, "666666");
465 tbl.row(0).unwrap().height(Length::pt(50.0)).header();
466 tbl.cell(0, 0).unwrap().set_text("Top");
467 tbl.cell(0, 0)
468 .unwrap()
469 .vertical_alignment(VerticalAlignment::Top)
470 .shading("FFF2CC");
471 tbl.cell(0, 1).unwrap().set_text("Center");
472 tbl.cell(0, 1)
473 .unwrap()
474 .vertical_alignment(VerticalAlignment::Center)
475 .shading("D9E2F3");
476 tbl.cell(0, 2).unwrap().set_text("Bottom");
477 tbl.cell(0, 2)
478 .unwrap()
479 .vertical_alignment(VerticalAlignment::Bottom)
480 .shading("E2EFDA");
481 tbl.row(1).unwrap().cant_split();
482 tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
483 tbl.cell(1, 0).unwrap().no_wrap();
484 tbl.cell(1, 1).unwrap().set_text("Fixed width");
485 tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
486 tbl.cell(1, 2).unwrap().set_text("Normal");
487 }
488
489 // ── SECTION 6: IMAGES ──
490 doc.add_paragraph("").page_break_before(true);
491 doc.add_paragraph("6. Images").style("Heading1");
492
493 doc.add_paragraph("6.1 Inline Image").style("Heading2");
494 doc.add_paragraph("A blue gradient image below:");
495 let img = create_sample_png(200, 50, [0, 80, 200]);
496 doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
497
498 doc.add_paragraph("");
499 doc.add_paragraph("6.2 Header Image").style("Heading2");
500 let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
501 doc.set_header_image(
502 &hdr_img,
503 "header_logo.png",
504 Length::inches(2.0),
505 Length::inches(0.2),
506 );
507 doc.add_paragraph("The header now contains an inline image (check the top of this page).");
508
509 doc.add_paragraph("");
510 doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
511
512 // ── SECTION 7: CONTENT MANIPULATION ──
513 doc.add_paragraph("").page_break_before(true);
514 doc.add_paragraph("7. Content Manipulation")
515 .style("Heading1");
516
517 doc.add_paragraph("7.1 Placeholder Replacement")
518 .style("Heading2");
519 doc.add_paragraph("Customer: {{customer}}");
520 doc.add_paragraph("Date: {{date}}");
521 doc.add_paragraph("Reference: {{ref_number}}");
522 {
523 let mut tbl = doc
524 .add_table(2, 2)
525 .borders(BorderStyle::Single, 4, "000000");
526 tbl.cell(0, 0).unwrap().set_text("Project");
527 tbl.cell(0, 0).unwrap().shading("D6E4F0");
528 tbl.cell(0, 1).unwrap().set_text("{{project}}");
529 tbl.cell(1, 0).unwrap().set_text("Status");
530 tbl.cell(1, 0).unwrap().shading("D6E4F0");
531 tbl.cell(1, 1).unwrap().set_text("{{status}}");
532 }
533
534 let mut replacements = HashMap::new();
535 replacements.insert("{{customer}}", "Tensorbee Inc.");
536 replacements.insert("{{date}}", "February 22, 2026");
537 replacements.insert("{{ref_number}}", "TB-2026-001");
538 replacements.insert("{{project}}", "Infrastructure Upgrade");
539 replacements.insert("{{status}}", "In Progress");
540 let n = doc.replace_all(&replacements);
541 doc.add_paragraph(&format!("({n} placeholders replaced above)"));
542
543 doc.add_paragraph("");
544
545 doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
546 doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
547 let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
548 doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
549
550 doc.add_paragraph("");
551
552 doc.add_paragraph("7.3 Content Insertion").style("Heading2");
553 doc.add_paragraph("Section A: First content.");
554 doc.add_paragraph("Section C: Third content.");
555 if let Some(idx) = doc.find_content_index("Section C") {
556 doc.insert_paragraph(
557 idx,
558 "Section B: Inserted between A and C via find_content_index().",
559 );
560 }
561
562 // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
563 doc.add_paragraph("").section_break(SectionBreak::NextPage);
564 doc.add_paragraph("8. Section Breaks & Orientation")
565 .style("Heading1");
566 doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
567
568 {
569 let mut tbl = doc
570 .add_table(3, 7)
571 .borders(BorderStyle::Single, 4, "2E75B6");
572 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
573 for (c, h) in headers.iter().enumerate() {
574 tbl.cell(0, c).unwrap().set_text(h);
575 tbl.cell(0, c).unwrap().shading("2E75B6");
576 }
577 let data = [
578 [
579 "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
580 ],
581 [
582 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
583 ],
584 ];
585 for (r, row) in data.iter().enumerate() {
586 for (c, v) in row.iter().enumerate() {
587 tbl.cell(r + 1, c).unwrap().set_text(v);
588 }
589 }
590 }
591
592 doc.add_paragraph("")
593 .section_break(SectionBreak::NextPage)
594 .section_landscape();
595
596 // ── SECTION 9: CUSTOM STYLES ──
597 doc.add_paragraph("9. Custom Styles").style("Heading1");
598 doc.add_style(
599 StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
600 .based_on("Normal")
601 .paragraph_properties(rdocx_oxml::properties::CT_PPr {
602 shading: Some(rdocx_oxml::properties::CT_Shd {
603 val: "clear".to_string(),
604 color: None,
605 fill: Some("FFF2CC".to_string()),
606 }),
607 ..Default::default()
608 })
609 .run_properties(rdocx_oxml::properties::CT_RPr {
610 bold: Some(true),
611 color: Some("C45911".to_string()),
612 ..Default::default()
613 }),
614 );
615 doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
616 .style("CustomHighlight");
617
618 doc.add_paragraph("");
619
620 // ── SECTION 10: DOCUMENT INTELLIGENCE ──
621 doc.add_paragraph("10. Document Intelligence API")
622 .style("Heading1");
623
624 let wc = doc.word_count();
625 let hc = doc.headings().len();
626 let ic = doc.images().len();
627 let lc = doc.links().len();
628 doc.add_paragraph(&format!("Word count: {wc}"));
629 doc.add_paragraph(&format!("Heading count: {hc}"));
630 doc.add_paragraph(&format!("Image count: {ic}"));
631 doc.add_paragraph(&format!("Link count: {lc}"));
632
633 let outline = doc.document_outline();
634 doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
635
636 let issues = doc.audit_accessibility();
637 doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
638 for issue in &issues {
639 doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
640 }
641
642 // ── SECTION 11: DOCUMENT MERGING ──
643 doc.add_paragraph("");
644 doc.add_paragraph("11. Document Merging").style("Heading1");
645 {
646 let mut other = Document::new();
647 other.add_paragraph("This paragraph was merged from another document using append().");
648 doc.append(&other);
649 }
650
651 doc.add_paragraph("");
652
653 // ── SUMMARY ──
654 doc.add_paragraph("Summary of Demonstrated Features")
655 .style("Heading1");
656 let features = [
657 "Page setup: size, margins, header/footer distance, gutter",
658 "Document metadata: title, author, subject, keywords",
659 "Headers and footers: text, images, different first page",
660 "Background images (full-page behind text)",
661 "Table of Contents generation with bookmarks",
662 "Text formatting: bold, italic, underline styles, strike, color, size, font",
663 "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
664 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
665 "Pagination controls: keep-with-next, keep-together, widow control",
666 "Bullet and numbered lists with nesting",
667 "Tab stops with dot/underscore/hyphen leaders",
668 "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
669 "Row properties: header rows, exact height, cant-split",
670 "Cell properties: width, no-wrap, vertical alignment",
671 "Inline images and header images",
672 "Placeholder replacement (single and batch)",
673 "Regex-based find and replace",
674 "Content insertion at specific positions",
675 "Section breaks with mixed portrait/landscape",
676 "Custom paragraph and character styles",
677 "Document intelligence: word count, headings, outline, images, links",
678 "Accessibility audit",
679 "Document merging (append)",
680 "Export: DOCX, PDF, HTML, Markdown",
681 ];
682 for f in &features {
683 doc.add_bullet_list_item(f, 0);
684 }
685 doc.add_paragraph("");
686 doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
687 .alignment(Alignment::Center)
688 .shading("E2EFDA")
689 .border_all(BorderStyle::Single, 2, "00AA00");
690
691 doc
692}
693
694// =============================================================================
695// 2. PROPOSAL DOCUMENT — Deep navy + gold scheme
696// =============================================================================
697fn generate_proposal(_samples_dir: &Path) -> Document {
698 let mut doc = Document::new();
699
700 // Colors: Navy #1B2A4A, Gold #C5922E, Light #F4F1EB
701 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
702 doc.set_margins(
703 Length::inches(1.0),
704 Length::inches(1.0),
705 Length::inches(1.0),
706 Length::inches(1.0),
707 );
708 doc.set_title("AI Platform Modernization Proposal");
709 doc.set_author("Walter White");
710 doc.set_subject("Technology Proposal");
711 doc.set_keywords("proposal, AI, modernization, Tensorbee");
712
713 // Banner header
714 let logo_img = create_logo_png(220, 48);
715 let banner = build_header_banner_xml(
716 "rId1",
717 &BannerOpts {
718 bg_color: "1B2A4A",
719 banner_width: 7772400,
720 banner_height: 914400,
721 logo_width: 2011680,
722 logo_height: 438912,
723 logo_x_offset: 295125,
724 logo_y_offset: 237744,
725 },
726 );
727 doc.set_raw_header_with_images(
728 banner,
729 &[("rId1", &logo_img, "logo.png")],
730 rdocx_oxml::header_footer::HdrFtrType::Default,
731 );
732 doc.set_different_first_page(true);
733 let cover_banner = build_header_banner_xml(
734 "rId1",
735 &BannerOpts {
736 bg_color: "C5922E",
737 banner_width: 7772400,
738 banner_height: 914400,
739 logo_width: 2011680,
740 logo_height: 438912,
741 logo_x_offset: 295125,
742 logo_y_offset: 237744,
743 },
744 );
745 doc.set_raw_header_with_images(
746 cover_banner,
747 &[("rId1", &logo_img, "logo.png")],
748 rdocx_oxml::header_footer::HdrFtrType::First,
749 );
750 doc.set_margins(
751 Length::twips(2292),
752 Length::twips(1440),
753 Length::twips(1440),
754 Length::twips(1440),
755 );
756 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
757 doc.set_footer("Tensorbee — Confidential");
758
759 // Custom styles
760 doc.add_style(
761 StyleBuilder::paragraph("ProposalTitle", "Proposal Title")
762 .based_on("Normal")
763 .run_properties(rdocx_oxml::properties::CT_RPr {
764 bold: Some(true),
765 font_ascii: Some("Georgia".to_string()),
766 font_hansi: Some("Georgia".to_string()),
767 sz: Some(rdocx_oxml::HalfPoint(56)), // half-points → 28pt
768 color: Some("1B2A4A".to_string()),
769 ..Default::default()
770 }),
771 );
772
773 // ── Cover Page ──
774 for _ in 0..4 {
775 doc.add_paragraph("");
776 }
777 doc.add_paragraph("AI Platform Modernization")
778 .style("ProposalTitle")
779 .alignment(Alignment::Center);
780 doc.add_paragraph("")
781 .alignment(Alignment::Center)
782 .border_bottom(BorderStyle::Single, 8, "C5922E");
783 {
784 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
785 p.add_run("Prepared for Global Financial Corp.")
786 .size(14.0)
787 .color("666666")
788 .italic(true);
789 }
790 {
791 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
792 p.add_run("Prepared by: Walter White, CEO — Tensorbee")
793 .size(12.0)
794 .color("888888");
795 }
796 {
797 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
798 p.add_run("February 22, 2026").size(12.0).color("888888");
799 }
800
801 // ── TOC ──
802 doc.add_paragraph("").page_break_before(true);
803 doc.add_paragraph("Table of Contents").style("Heading1");
804 // We'll add a manual-style TOC since the insert_toc goes at a specific position
805 doc.insert_toc(doc.content_count(), 2);
806
807 // ── Executive Summary ──
808 doc.add_paragraph("").page_break_before(true);
809 doc.add_paragraph("Executive Summary").style("Heading1");
810 doc.add_paragraph(
811 "Tensorbee proposes a comprehensive modernization of Global Financial Corp's AI platform, \
812 transitioning from legacy batch-processing systems to a real-time inference architecture. \
813 This transformation will reduce model deployment time from weeks to hours, improve \
814 prediction accuracy by an estimated 23%, and deliver $4.2M in annual operational savings.",
815 )
816 .first_line_indent(Length::inches(0.3));
817 doc.add_paragraph(
818 "The project spans three phases over 18 months, with a total investment of $2.8M. \
819 Tensorbee brings deep expertise in ML infrastructure, having successfully completed \
820 similar transformations for three Fortune 500 financial institutions.",
821 )
822 .first_line_indent(Length::inches(0.3));
823
824 // Key metrics table
825 doc.add_paragraph("");
826 {
827 let mut tbl = doc
828 .add_table(5, 2)
829 .borders(BorderStyle::Single, 4, "1B2A4A")
830 .width_pct(80.0);
831 tbl.cell(0, 0).unwrap().set_text("Key Metric");
832 tbl.cell(0, 0).unwrap().shading("1B2A4A");
833 tbl.cell(0, 1).unwrap().set_text("Value");
834 tbl.cell(0, 1).unwrap().shading("1B2A4A");
835 let rows = [
836 ("Total Investment", "$2.8M"),
837 ("Annual Savings", "$4.2M"),
838 ("ROI (Year 1)", "150%"),
839 ("Timeline", "18 months"),
840 ];
841 for (i, (k, v)) in rows.iter().enumerate() {
842 tbl.cell(i + 1, 0).unwrap().set_text(k);
843 if i % 2 == 0 {
844 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
845 }
846 tbl.cell(i + 1, 1).unwrap().set_text(v);
847 if i % 2 == 0 {
848 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
849 }
850 }
851 }
852
853 // ── Problem Statement ──
854 doc.add_paragraph("").page_break_before(true);
855 doc.add_paragraph("Problem Statement").style("Heading1");
856 doc.add_paragraph(
857 "Global Financial Corp's current AI infrastructure faces three critical challenges:",
858 );
859 doc.add_numbered_list_item("Deployment Latency — New models take 3-4 weeks to deploy to production, compared to the industry benchmark of 2-3 days.", 0);
860 doc.add_numbered_list_item("Scalability Constraints — The batch processing architecture cannot handle real-time inference demands during peak trading hours.", 0);
861 doc.add_numbered_list_item("Technical Debt — Legacy Python 2.7 codebases and manual deployment scripts create reliability risks and slow iteration speed.", 0);
862
863 // ── Proposed Solution ──
864 doc.add_paragraph("").page_break_before(true);
865 doc.add_paragraph("Proposed Solution").style("Heading1");
866
867 doc.add_paragraph("Phase 1: Foundation (Months 1-6)")
868 .style("Heading2");
869 doc.add_bullet_list_item("Deploy Kubernetes-based ML serving infrastructure", 0);
870 doc.add_bullet_list_item("Implement CI/CD pipeline for model deployment", 0);
871 doc.add_bullet_list_item("Migrate top 5 production models to new platform", 0);
872
873 doc.add_paragraph("Phase 2: Expansion (Months 7-12)")
874 .style("Heading2");
875 doc.add_bullet_list_item(
876 "Real-time feature engineering pipeline (Apache Kafka + Flink)",
877 0,
878 );
879 doc.add_bullet_list_item("A/B testing framework for model variants", 0);
880 doc.add_bullet_list_item("Automated model monitoring and drift detection", 0);
881
882 doc.add_paragraph("Phase 3: Optimization (Months 13-18)")
883 .style("Heading2");
884 doc.add_bullet_list_item("GPU inference optimization (TensorRT/ONNX Runtime)", 0);
885 doc.add_bullet_list_item("Multi-region deployment for latency reduction", 0);
886 doc.add_bullet_list_item(
887 "Self-service model deployment portal for data science teams",
888 0,
889 );
890
891 // ── Budget ──
892 doc.add_paragraph("Budget Breakdown").style("Heading1");
893 {
894 let mut tbl = doc
895 .add_table(6, 3)
896 .borders(BorderStyle::Single, 4, "1B2A4A")
897 .width_pct(100.0);
898 tbl.cell(0, 0).unwrap().set_text("Category");
899 tbl.cell(0, 0).unwrap().shading("1B2A4A");
900 tbl.cell(0, 1).unwrap().set_text("Cost");
901 tbl.cell(0, 1).unwrap().shading("1B2A4A");
902 tbl.cell(0, 2).unwrap().set_text("Notes");
903 tbl.cell(0, 2).unwrap().shading("1B2A4A");
904 let items = [
905 (
906 "Infrastructure",
907 "$450,000",
908 "Cloud compute + GPU instances",
909 ),
910 ("Engineering Services", "$1,600,000", "12 FTE x 18 months"),
911 (
912 "Software Licenses",
913 "$350,000",
914 "Kafka, monitoring, CI/CD tools",
915 ),
916 (
917 "Training & Enablement",
918 "$200,000",
919 "Team training, documentation",
920 ),
921 ("Contingency (10%)", "$200,000", "Risk buffer"),
922 ];
923 for (i, (cat, cost, note)) in items.iter().enumerate() {
924 tbl.cell(i + 1, 0).unwrap().set_text(cat);
925 tbl.cell(i + 1, 1).unwrap().set_text(cost);
926 tbl.cell(i + 1, 2).unwrap().set_text(note);
927 if i % 2 == 0 {
928 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
929 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
930 tbl.cell(i + 1, 2).unwrap().shading("F4F1EB");
931 }
932 }
933 }
934
935 // ── Closing ──
936 doc.add_paragraph("");
937 doc.add_paragraph("Next Steps").style("Heading1");
938 doc.add_numbered_list_item(
939 "Schedule technical deep-dive with GFC engineering team (Week 1)",
940 0,
941 );
942 doc.add_numbered_list_item("Finalize scope and sign SOW (Week 2-3)", 0);
943 doc.add_numbered_list_item("Kick off Phase 1 with joint planning session (Week 4)", 0);
944
945 doc.add_paragraph("");
946 {
947 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
948 p.add_run("Walter White")
949 .bold(true)
950 .size(14.0)
951 .color("1B2A4A");
952 }
953 {
954 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
955 p.add_run("CEO, Tensorbee | walter@tensorbee.com")
956 .size(10.0)
957 .color("888888");
958 }
959
960 doc
961}
962
963// =============================================================================
964// 3. QUOTE / BILL OF MATERIALS — Teal + orange scheme
965// =============================================================================
966fn generate_quote(_samples_dir: &Path) -> Document {
967 let mut doc = Document::new();
968
969 // Colors: Teal #008B8B, Dark #1A3C3C, Orange #E07020, Light #F0F8F8
970 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
971 doc.set_margins(
972 Length::inches(0.75),
973 Length::inches(0.75),
974 Length::inches(0.75),
975 Length::inches(0.75),
976 );
977 doc.set_title("Quotation QT-2026-0147");
978 doc.set_author("Walter White");
979 doc.set_keywords("quote, BOM, Tensorbee");
980
981 doc.set_header("Tensorbee — Quotation");
982 doc.set_footer("QT-2026-0147 | Page");
983
984 // ── Header Block ──
985 {
986 let mut p = doc.add_paragraph("").alignment(Alignment::Left);
987 p.add_run("TENSORBEE")
988 .bold(true)
989 .size(28.0)
990 .color("008B8B")
991 .font("Helvetica");
992 }
993 doc.add_paragraph("123 Innovation Drive, Suite 400")
994 .alignment(Alignment::Left);
995 doc.add_paragraph("San Francisco, CA 94105 | +1 (415) 555-0199")
996 .alignment(Alignment::Left);
997 doc.add_paragraph("")
998 .border_bottom(BorderStyle::Single, 8, "008B8B");
999
1000 // Quote meta
1001 doc.add_paragraph("");
1002 {
1003 let mut tbl = doc.add_table(4, 4).width_pct(100.0);
1004 tbl.cell(0, 0).unwrap().set_text("QUOTATION");
1005 tbl.cell(0, 0).unwrap().shading("008B8B").grid_span(2);
1006 tbl.cell(0, 2).unwrap().set_text("DATE");
1007 tbl.cell(0, 2).unwrap().shading("008B8B");
1008 tbl.cell(0, 3).unwrap().set_text("VALID UNTIL");
1009 tbl.cell(0, 3).unwrap().shading("008B8B");
1010 tbl.cell(1, 0).unwrap().set_text("Quote #:");
1011 tbl.cell(1, 1).unwrap().set_text("QT-2026-0147");
1012 tbl.cell(1, 2).unwrap().set_text("Feb 22, 2026");
1013 tbl.cell(1, 3).unwrap().set_text("Mar 22, 2026");
1014 tbl.cell(2, 0).unwrap().set_text("Prepared by:");
1015 tbl.cell(2, 1).unwrap().set_text("Walter White");
1016 tbl.cell(2, 2).unwrap().set_text("Payment:");
1017 tbl.cell(2, 3).unwrap().set_text("Net 30");
1018 tbl.cell(3, 0).unwrap().set_text("Customer:");
1019 tbl.cell(3, 1).unwrap().set_text("Meridian Dynamics LLC");
1020 tbl.cell(3, 2).unwrap().set_text("Currency:");
1021 tbl.cell(3, 3).unwrap().set_text("USD");
1022 }
1023
1024 doc.add_paragraph("");
1025
1026 // ── Bill of Materials ──
1027 {
1028 let mut p = doc.add_paragraph("");
1029 p.add_run("Bill of Materials")
1030 .bold(true)
1031 .size(16.0)
1032 .color("1A3C3C");
1033 }
1034 doc.add_paragraph("");
1035
1036 {
1037 let mut tbl = doc
1038 .add_table(10, 6)
1039 .borders(BorderStyle::Single, 2, "008B8B")
1040 .width_pct(100.0)
1041 .layout_fixed();
1042 // Headers
1043 let hdrs = [
1044 "#",
1045 "Part Number",
1046 "Description",
1047 "Qty",
1048 "Unit Price",
1049 "Total",
1050 ];
1051 for (c, h) in hdrs.iter().enumerate() {
1052 tbl.cell(0, c).unwrap().set_text(h);
1053 tbl.cell(0, c).unwrap().shading("008B8B");
1054 }
1055
1056 let items: Vec<(&str, &str, &str, &str, &str)> = vec![
1057 (
1058 "TB-GPU-A100",
1059 "NVIDIA A100 80GB GPU",
1060 "4",
1061 "$12,500.00",
1062 "$50,000.00",
1063 ),
1064 (
1065 "TB-SRV-R750",
1066 "Dell R750xa Server Chassis",
1067 "2",
1068 "$8,200.00",
1069 "$16,400.00",
1070 ),
1071 (
1072 "TB-RAM-256G",
1073 "256GB DDR5 ECC Memory Module",
1074 "8",
1075 "$890.00",
1076 "$7,120.00",
1077 ),
1078 (
1079 "TB-SSD-3840",
1080 "3.84TB NVMe U.2 SSD",
1081 "8",
1082 "$1,150.00",
1083 "$9,200.00",
1084 ),
1085 (
1086 "TB-NET-CX7",
1087 "ConnectX-7 200GbE NIC",
1088 "4",
1089 "$1,800.00",
1090 "$7,200.00",
1091 ),
1092 (
1093 "TB-SW-48P",
1094 "48-Port 100GbE Switch",
1095 "1",
1096 "$22,000.00",
1097 "$22,000.00",
1098 ),
1099 (
1100 "TB-CAB-RACK",
1101 "42U Server Rack + PDU",
1102 "1",
1103 "$4,500.00",
1104 "$4,500.00",
1105 ),
1106 (
1107 "TB-SVC-INST",
1108 "Installation & Configuration",
1109 "1",
1110 "$8,500.00",
1111 "$8,500.00",
1112 ),
1113 (
1114 "TB-SVC-SUPP",
1115 "3-Year Premium Support",
1116 "1",
1117 "$15,000.00",
1118 "$15,000.00",
1119 ),
1120 ];
1121
1122 for (i, (pn, desc, qty, unit, total)) in items.iter().enumerate() {
1123 let row = i + 1;
1124 tbl.cell(row, 0).unwrap().set_text(&format!("{}", i + 1));
1125 tbl.cell(row, 1).unwrap().set_text(pn);
1126 tbl.cell(row, 2).unwrap().set_text(desc);
1127 tbl.cell(row, 3).unwrap().set_text(qty);
1128 tbl.cell(row, 4).unwrap().set_text(unit);
1129 tbl.cell(row, 5).unwrap().set_text(total);
1130 if i % 2 == 0 {
1131 for c in 0..6 {
1132 tbl.cell(row, c).unwrap().shading("F0F8F8");
1133 }
1134 }
1135 }
1136 }
1137
1138 doc.add_paragraph("");
1139
1140 // ── Totals ──
1141 {
1142 let mut tbl = doc
1143 .add_table(4, 2)
1144 .borders(BorderStyle::Single, 2, "008B8B")
1145 .width(Length::inches(3.5))
1146 .alignment(Alignment::Right);
1147 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1148 tbl.cell(0, 1).unwrap().set_text("$139,920.00");
1149 tbl.cell(1, 0).unwrap().set_text("Shipping & Handling");
1150 tbl.cell(1, 1).unwrap().set_text("$2,500.00");
1151 tbl.cell(2, 0).unwrap().set_text("Tax (8.625%)");
1152 tbl.cell(2, 1).unwrap().set_text("$12,068.10");
1153 tbl.cell(3, 0).unwrap().set_text("TOTAL");
1154 tbl.cell(3, 0).unwrap().shading("E07020");
1155 tbl.cell(3, 1).unwrap().set_text("$154,488.10");
1156 tbl.cell(3, 1).unwrap().shading("E07020");
1157 }
1158
1159 doc.add_paragraph("");
1160
1161 // ── Terms & Conditions ──
1162 {
1163 let mut p = doc.add_paragraph("");
1164 p.add_run("Terms & Conditions")
1165 .bold(true)
1166 .size(14.0)
1167 .color("1A3C3C");
1168 }
1169 doc.add_numbered_list_item(
1170 "This quotation is valid for 30 calendar days from the date of issue.",
1171 0,
1172 );
1173 doc.add_numbered_list_item(
1174 "All prices are in USD and exclusive of applicable taxes unless stated.",
1175 0,
1176 );
1177 doc.add_numbered_list_item("Standard lead time is 4-6 weeks from PO receipt.", 0);
1178 doc.add_numbered_list_item(
1179 "Payment terms: Net 30 from invoice date. 2% discount for payment within 10 days.",
1180 0,
1181 );
1182 doc.add_numbered_list_item(
1183 "Warranty: 3-year manufacturer warranty on all hardware components.",
1184 0,
1185 );
1186 doc.add_numbered_list_item(
1187 "Returns subject to 15% restocking fee if initiated after 14 days.",
1188 0,
1189 );
1190
1191 doc.add_paragraph("");
1192 doc.add_paragraph("")
1193 .border_bottom(BorderStyle::Single, 4, "008B8B");
1194 doc.add_paragraph("");
1195 {
1196 let mut p = doc.add_paragraph("");
1197 p.add_run("Accepted by: ").bold(true);
1198 p.add_run("___________________________ Date: ___________");
1199 }
1200 {
1201 let mut p = doc.add_paragraph("");
1202 p.add_run("Print Name: ").bold(true);
1203 p.add_run("___________________________ Title: ___________");
1204 }
1205
1206 doc
1207}
1208
1209// =============================================================================
1210// 4. INVOICE — Crimson + charcoal scheme
1211// =============================================================================
1212fn generate_invoice(_samples_dir: &Path) -> Document {
1213 let mut doc = Document::new();
1214
1215 // Colors: Crimson #B22222, Charcoal #333333, Light #FAF0F0
1216 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1217 doc.set_margins(
1218 Length::inches(0.75),
1219 Length::inches(0.75),
1220 Length::inches(0.75),
1221 Length::inches(0.75),
1222 );
1223 doc.set_title("Invoice INV-2026-0392");
1224 doc.set_author("Walter White");
1225 doc.set_keywords("invoice, Tensorbee");
1226
1227 doc.set_footer("Tensorbee — Thank you for your business!");
1228
1229 // ── Company & Invoice Header ──
1230 {
1231 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1232 // Company name left, INVOICE right
1233 {
1234 let mut cell = tbl.cell(0, 0).unwrap();
1235 let mut p = cell.add_paragraph("");
1236 p.add_run("TENSORBEE")
1237 .bold(true)
1238 .size(32.0)
1239 .color("B22222")
1240 .font("Helvetica");
1241 }
1242 {
1243 let mut cell = tbl.cell(0, 1).unwrap();
1244 let mut p = cell.add_paragraph("");
1245 p.add_run("INVOICE").bold(true).size(32.0).color("333333");
1246 }
1247 tbl.cell(1, 0)
1248 .unwrap()
1249 .set_text("123 Innovation Drive, Suite 400");
1250 tbl.cell(1, 1).unwrap().set_text("Invoice #: INV-2026-0392");
1251 tbl.cell(2, 0).unwrap().set_text("San Francisco, CA 94105");
1252 tbl.cell(2, 1).unwrap().set_text("Date: February 22, 2026");
1253 tbl.cell(3, 0).unwrap().set_text("walter@tensorbee.com");
1254 tbl.cell(3, 1).unwrap().set_text("Due Date: March 24, 2026");
1255 }
1256
1257 doc.add_paragraph("")
1258 .border_bottom(BorderStyle::Thick, 8, "B22222");
1259 doc.add_paragraph("");
1260
1261 // ── Bill To / Ship To ──
1262 {
1263 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1264 tbl.cell(0, 0).unwrap().set_text("BILL TO");
1265 tbl.cell(0, 0).unwrap().shading("B22222");
1266 tbl.cell(0, 1).unwrap().set_text("SHIP TO");
1267 tbl.cell(0, 1).unwrap().shading("B22222");
1268 tbl.cell(1, 0).unwrap().set_text("Meridian Dynamics LLC");
1269 tbl.cell(1, 1).unwrap().set_text("Meridian Dynamics LLC");
1270 tbl.cell(2, 0).unwrap().set_text("456 Enterprise Blvd");
1271 tbl.cell(2, 1).unwrap().set_text("Attn: Server Room B");
1272 tbl.cell(3, 0).unwrap().set_text("Austin, TX 78701");
1273 tbl.cell(3, 1)
1274 .unwrap()
1275 .set_text("456 Enterprise Blvd, Austin, TX 78701");
1276 }
1277
1278 doc.add_paragraph("");
1279
1280 // ── Line Items ──
1281 {
1282 let mut tbl = doc
1283 .add_table(8, 5)
1284 .borders(BorderStyle::Single, 2, "B22222")
1285 .width_pct(100.0);
1286 let hdrs = ["Description", "Qty", "Unit Price", "Tax", "Amount"];
1287 for (c, h) in hdrs.iter().enumerate() {
1288 tbl.cell(0, c).unwrap().set_text(h);
1289 tbl.cell(0, c).unwrap().shading("B22222");
1290 }
1291 let items = [
1292 (
1293 "ML Infrastructure Setup — Phase 1",
1294 "1",
1295 "$45,000.00",
1296 "$3,881.25",
1297 "$48,881.25",
1298 ),
1299 (
1300 "Data Pipeline Development (160 hrs)",
1301 "160",
1302 "$225.00",
1303 "$3,105.00",
1304 "$39,105.00",
1305 ),
1306 (
1307 "GPU Cluster Configuration",
1308 "1",
1309 "$12,000.00",
1310 "$1,035.00",
1311 "$13,035.00",
1312 ),
1313 (
1314 "API Gateway Implementation",
1315 "1",
1316 "$18,500.00",
1317 "$1,595.63",
1318 "$20,095.63",
1319 ),
1320 (
1321 "Load Testing & QA (80 hrs)",
1322 "80",
1323 "$195.00",
1324 "$1,345.50",
1325 "$16,945.50",
1326 ),
1327 (
1328 "Documentation & Training",
1329 "1",
1330 "$8,000.00",
1331 "$690.00",
1332 "$8,690.00",
1333 ),
1334 (
1335 "Project Management (3 months)",
1336 "3",
1337 "$6,500.00",
1338 "$1,679.63",
1339 "$21,179.63",
1340 ),
1341 ];
1342 for (i, (desc, qty, unit, tax, amt)) in items.iter().enumerate() {
1343 let r = i + 1;
1344 tbl.cell(r, 0).unwrap().set_text(desc);
1345 tbl.cell(r, 1).unwrap().set_text(qty);
1346 tbl.cell(r, 2).unwrap().set_text(unit);
1347 tbl.cell(r, 3).unwrap().set_text(tax);
1348 tbl.cell(r, 4).unwrap().set_text(amt);
1349 if i % 2 == 0 {
1350 for c in 0..5 {
1351 tbl.cell(r, c).unwrap().shading("FAF0F0");
1352 }
1353 }
1354 }
1355 }
1356
1357 doc.add_paragraph("");
1358
1359 // ── Totals ──
1360 {
1361 let mut tbl = doc
1362 .add_table(4, 2)
1363 .borders(BorderStyle::Single, 2, "B22222")
1364 .width(Length::inches(3.0))
1365 .alignment(Alignment::Right);
1366 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1367 tbl.cell(0, 1).unwrap().set_text("$154,600.00");
1368 tbl.cell(1, 0).unwrap().set_text("Tax (8.625%)");
1369 tbl.cell(1, 1).unwrap().set_text("$13,334.25");
1370 tbl.cell(2, 0).unwrap().set_text("Discount (5%)");
1371 tbl.cell(2, 1).unwrap().set_text("-$7,730.00");
1372 tbl.cell(3, 0).unwrap().set_text("AMOUNT DUE");
1373 tbl.cell(3, 0).unwrap().shading("B22222");
1374 tbl.cell(3, 1).unwrap().set_text("$160,204.25");
1375 tbl.cell(3, 1).unwrap().shading("B22222");
1376 }
1377
1378 doc.add_paragraph("");
1379 doc.add_paragraph("");
1380
1381 // ── Payment Details ──
1382 {
1383 let mut p = doc.add_paragraph("");
1384 p.add_run("Payment Details")
1385 .bold(true)
1386 .size(14.0)
1387 .color("333333");
1388 }
1389 doc.add_paragraph("Bank: Silicon Valley Bank")
1390 .indent_left(Length::inches(0.3));
1391 doc.add_paragraph("Account: Tensorbee Inc. — 0847-2953-1120")
1392 .indent_left(Length::inches(0.3));
1393 doc.add_paragraph("Routing: 121140399")
1394 .indent_left(Length::inches(0.3));
1395 doc.add_paragraph("Swift: SVBKUS6S")
1396 .indent_left(Length::inches(0.3));
1397
1398 doc.add_paragraph("");
1399
1400 doc.add_paragraph("Please include invoice number INV-2026-0392 in the payment reference.")
1401 .shading("FAF0F0")
1402 .border_all(BorderStyle::Single, 2, "B22222");
1403
1404 doc
1405}
1406
1407// =============================================================================
1408// 5. REPORT — Forest green + earth tones, with images & hierarchical sections
1409// =============================================================================
1410fn generate_report(_samples_dir: &Path) -> Document {
1411 let mut doc = Document::new();
1412
1413 // Colors: Forest #2D5016, Sage #6B8E23, Earth #8B7355, Cream #FFFAF0
1414 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1415 doc.set_margins(
1416 Length::inches(1.0),
1417 Length::inches(1.0),
1418 Length::inches(1.0),
1419 Length::inches(1.0),
1420 );
1421 doc.set_title("Q4 2025 Environmental Impact Report");
1422 doc.set_author("Walter White");
1423 doc.set_subject("Quarterly Environmental Report");
1424 doc.set_keywords("environment, sustainability, report, Tensorbee");
1425
1426 doc.set_different_first_page(true);
1427 doc.set_first_page_header("");
1428 doc.set_header("Tensorbee — Q4 2025 Environmental Impact Report");
1429 doc.set_footer("CONFIDENTIAL — Page");
1430
1431 // ── Cover ──
1432 let cover_bg = create_sample_png(612, 792, [20, 50, 15]);
1433 doc.add_background_image(&cover_bg, "report_cover.png");
1434
1435 for _ in 0..5 {
1436 doc.add_paragraph("");
1437 }
1438 {
1439 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1440 p.add_run("Q4 2025")
1441 .bold(true)
1442 .size(48.0)
1443 .color("FFFFFF")
1444 .font("Georgia");
1445 }
1446 {
1447 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1448 p.add_run("Environmental Impact Report")
1449 .size(24.0)
1450 .color("90EE90")
1451 .font("Georgia")
1452 .italic(true);
1453 }
1454 doc.add_paragraph("");
1455 {
1456 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1457 p.add_run("Tensorbee — Sustainability Division")
1458 .size(14.0)
1459 .color("C0C0C0");
1460 }
1461 {
1462 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1463 p.add_run("Prepared by Walter White, Chief Sustainability Officer")
1464 .size(11.0)
1465 .color("AAAAAA");
1466 }
1467
1468 // ── TOC ──
1469 doc.add_paragraph("").page_break_before(true);
1470 doc.insert_toc(doc.content_count(), 3);
1471
1472 // ── Section 1: Executive Overview ──
1473 doc.add_paragraph("").page_break_before(true);
1474 doc.add_paragraph("Executive Overview").style("Heading1");
1475 doc.add_paragraph(
1476 "This report presents Tensorbee's environmental performance for Q4 2025. Our \
1477 sustainability initiatives have yielded a 34% reduction in carbon emissions compared \
1478 to Q4 2024, exceeding our target of 25%. Key achievements include the transition to \
1479 100% renewable energy in our primary data centers and the launch of our carbon offset \
1480 marketplace.",
1481 )
1482 .first_line_indent(Length::inches(0.3));
1483
1484 // Image: performance chart
1485 let chart_img = create_chart_png(400, 200);
1486 doc.add_paragraph("");
1487 doc.add_picture(
1488 &chart_img,
1489 "performance_chart.png",
1490 Length::inches(5.0),
1491 Length::inches(2.5),
1492 )
1493 .alignment(Alignment::Center);
1494 {
1495 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1496 p.add_run("Figure 1: Quarterly Carbon Emissions (tonnes CO2e)")
1497 .italic(true)
1498 .size(9.0)
1499 .color("666666");
1500 }
1501
1502 // ── Section 2: Energy Consumption ──
1503 doc.add_paragraph("").page_break_before(true);
1504 doc.add_paragraph("Energy Consumption").style("Heading1");
1505
1506 doc.add_paragraph("Data Center Operations")
1507 .style("Heading2");
1508 doc.add_paragraph(
1509 "Our three primary data centers consumed a combined 4.2 GWh in Q4 2025, \
1510 a 12% reduction from Q3 2025 driven by improved cooling efficiency and \
1511 server consolidation.",
1512 );
1513
1514 // Data table
1515 {
1516 let mut tbl = doc
1517 .add_table(5, 4)
1518 .borders(BorderStyle::Single, 4, "2D5016")
1519 .width_pct(100.0);
1520 let hdrs = ["Data Center", "Capacity (MW)", "Usage (GWh)", "PUE"];
1521 for (c, h) in hdrs.iter().enumerate() {
1522 tbl.cell(0, c).unwrap().set_text(h);
1523 tbl.cell(0, c).unwrap().shading("2D5016");
1524 }
1525 let rows = [
1526 ("San Francisco (Primary)", "3.2", "1.8", "1.12"),
1527 ("Dublin (EU)", "2.1", "1.2", "1.18"),
1528 ("Singapore (APAC)", "1.8", "1.2", "1.24"),
1529 ("TOTAL", "7.1", "4.2", "1.17 avg"),
1530 ];
1531 for (i, (dc, cap, use_, pue)) in rows.iter().enumerate() {
1532 tbl.cell(i + 1, 0).unwrap().set_text(dc);
1533 tbl.cell(i + 1, 1).unwrap().set_text(cap);
1534 tbl.cell(i + 1, 2).unwrap().set_text(use_);
1535 tbl.cell(i + 1, 3).unwrap().set_text(pue);
1536 if i == 3 {
1537 for c in 0..4 {
1538 tbl.cell(i + 1, c).unwrap().shading("E2EFDA");
1539 }
1540 }
1541 }
1542 }
1543
1544 doc.add_paragraph("");
1545
1546 doc.add_paragraph("Renewable Energy Mix").style("Heading2");
1547 doc.add_paragraph("Breakdown of energy sources across all facilities:");
1548
1549 doc.add_bullet_list_item("Solar PV: 42% (1.76 GWh)", 0);
1550 doc.add_bullet_list_item("Wind Power Purchase Agreements: 38% (1.60 GWh)", 0);
1551 doc.add_bullet_list_item("Hydroelectric: 12% (0.50 GWh)", 0);
1552 doc.add_bullet_list_item("Grid (non-renewable): 8% (0.34 GWh)", 0);
1553
1554 // ── Section 3: Water & Waste ──
1555 doc.add_paragraph("Water & Waste Management")
1556 .style("Heading1");
1557
1558 doc.add_paragraph("Water Usage").style("Heading2");
1559 doc.add_paragraph(
1560 "Total water consumption was 12.4 million gallons, a 15% reduction from Q3. \
1561 Our closed-loop cooling systems now recycle 78% of water used in cooling operations.",
1562 );
1563
1564 doc.add_paragraph("Waste Reduction").style("Heading2");
1565 doc.add_paragraph("E-waste management results for Q4:")
1566 .keep_with_next(true);
1567 {
1568 let mut tbl = doc
1569 .add_table(4, 3)
1570 .borders(BorderStyle::Single, 2, "6B8E23")
1571 .width_pct(80.0);
1572 tbl.cell(0, 0).unwrap().set_text("Category");
1573 tbl.cell(0, 0).unwrap().shading("6B8E23");
1574 tbl.cell(0, 1).unwrap().set_text("Weight (kg)");
1575 tbl.cell(0, 1).unwrap().shading("6B8E23");
1576 tbl.cell(0, 2).unwrap().set_text("Recycled %");
1577 tbl.cell(0, 2).unwrap().shading("6B8E23");
1578 let rows = [
1579 ("Server Hardware", "2,450", "94%"),
1580 ("Networking Equipment", "820", "91%"),
1581 ("Storage Media", "340", "99%"),
1582 ];
1583 for (i, (cat, wt, pct)) in rows.iter().enumerate() {
1584 tbl.cell(i + 1, 0).unwrap().set_text(cat);
1585 tbl.cell(i + 1, 1).unwrap().set_text(wt);
1586 tbl.cell(i + 1, 2).unwrap().set_text(pct);
1587 }
1588 }
1589
1590 // ── Section 4: Initiatives ──
1591 doc.add_paragraph("").page_break_before(true);
1592 doc.add_paragraph("Q1 2026 Initiatives").style("Heading1");
1593
1594 doc.add_paragraph("Planned Programs").style("Heading2");
1595 doc.add_numbered_list_item(
1596 "Deploy on-site battery storage at SF data center (2 MWh capacity)",
1597 0,
1598 );
1599 doc.add_numbered_list_item("Pilot immersion cooling in Dublin facility", 0);
1600 doc.add_numbered_list_item("Launch employee commute carbon offset program", 0);
1601 doc.add_numbered_list_item("Achieve ISO 14001 certification for Singapore facility", 0);
1602
1603 doc.add_paragraph("Investment Targets").style("Heading2");
1604 doc.add_paragraph("Sustainability CapEx allocation for FY2026:")
1605 .keep_with_next(true);
1606 {
1607 let mut tbl = doc
1608 .add_table(5, 2)
1609 .borders(BorderStyle::Single, 4, "2D5016");
1610 tbl.cell(0, 0).unwrap().set_text("Initiative");
1611 tbl.cell(0, 0).unwrap().shading("2D5016");
1612 tbl.cell(0, 1).unwrap().set_text("Budget");
1613 tbl.cell(0, 1).unwrap().shading("2D5016");
1614 let rows = [
1615 ("Battery Storage", "$1.2M"),
1616 ("Immersion Cooling Pilot", "$800K"),
1617 ("Solar Panel Expansion", "$2.1M"),
1618 ("Carbon Credits", "$500K"),
1619 ];
1620 for (i, (init, budget)) in rows.iter().enumerate() {
1621 tbl.cell(i + 1, 0).unwrap().set_text(init);
1622 tbl.cell(i + 1, 1).unwrap().set_text(budget);
1623 if i % 2 == 0 {
1624 tbl.cell(i + 1, 0).unwrap().shading("FFFAF0");
1625 tbl.cell(i + 1, 1).unwrap().shading("FFFAF0");
1626 }
1627 }
1628 }
1629
1630 // Image: sustainability roadmap
1631 let roadmap_img = create_sample_png(500, 100, [40, 80, 30]);
1632 doc.add_paragraph("");
1633 doc.add_picture(
1634 &roadmap_img,
1635 "roadmap.png",
1636 Length::inches(6.0),
1637 Length::inches(1.2),
1638 )
1639 .alignment(Alignment::Center);
1640 {
1641 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1642 p.add_run("Figure 2: 2026 Sustainability Roadmap")
1643 .italic(true)
1644 .size(9.0)
1645 .color("666666");
1646 }
1647
1648 doc.add_paragraph("");
1649 doc.add_paragraph(
1650 "For questions about this report, contact Walter White at sustainability@tensorbee.com.",
1651 )
1652 .shading("FFFAF0")
1653 .border_all(BorderStyle::Single, 2, "2D5016");
1654
1655 doc
1656}
1657
1658// =============================================================================
1659// 6. LETTER — Slate blue + silver scheme
1660// =============================================================================
1661fn generate_letter(_samples_dir: &Path) -> Document {
1662 let mut doc = Document::new();
1663
1664 // Colors: Slate #4A5568, Blue accent #3182CE
1665 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1666 doc.set_margins(
1667 Length::inches(1.25),
1668 Length::inches(1.25),
1669 Length::inches(1.25),
1670 Length::inches(1.25),
1671 );
1672 doc.set_title("Business Letter");
1673 doc.set_author("Walter White");
1674
1675 // Banner header using raw XML
1676 let logo_img = create_logo_png(220, 48);
1677 let banner = build_header_banner_xml(
1678 "rId1",
1679 &BannerOpts {
1680 bg_color: "4A5568",
1681 banner_width: 7772400,
1682 banner_height: 731520, // ~0.8"
1683 logo_width: 2011680,
1684 logo_height: 438912,
1685 logo_x_offset: 295125,
1686 logo_y_offset: 146304,
1687 },
1688 );
1689 doc.set_raw_header_with_images(
1690 banner,
1691 &[("rId1", &logo_img, "logo.png")],
1692 rdocx_oxml::header_footer::HdrFtrType::Default,
1693 );
1694 doc.set_margins(
1695 Length::twips(2000),
1696 Length::twips(1800),
1697 Length::twips(1440),
1698 Length::twips(1800),
1699 );
1700 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
1701
1702 // Footer with contact
1703 doc.set_footer(
1704 "Tensorbee | 123 Innovation Drive, Suite 400 | San Francisco, CA 94105 | tensorbee.com",
1705 );
1706
1707 // ── Sender Address ──
1708 doc.add_paragraph("Walter White");
1709 doc.add_paragraph("Chief Executive Officer");
1710 doc.add_paragraph("Tensorbee");
1711 doc.add_paragraph("123 Innovation Drive, Suite 400");
1712 doc.add_paragraph("San Francisco, CA 94105");
1713 doc.add_paragraph("");
1714 doc.add_paragraph("February 22, 2026");
1715
1716 doc.add_paragraph("");
1717
1718 // ── Recipient ──
1719 doc.add_paragraph("Ms. Sarah Chen");
1720 doc.add_paragraph("VP of Engineering");
1721 doc.add_paragraph("Meridian Dynamics LLC");
1722 doc.add_paragraph("456 Enterprise Boulevard");
1723 doc.add_paragraph("Austin, TX 78701");
1724
1725 doc.add_paragraph("");
1726
1727 // ── Subject ──
1728 {
1729 let mut p = doc.add_paragraph("");
1730 p.add_run("Re: Strategic Technology Partnership — Phase 2 Expansion")
1731 .bold(true);
1732 }
1733
1734 doc.add_paragraph("");
1735
1736 // ── Body ──
1737 doc.add_paragraph("Dear Ms. Chen,");
1738 doc.add_paragraph("");
1739
1740 doc.add_paragraph(
1741 "I am writing to express Tensorbee's enthusiasm for expanding our strategic technology \
1742 partnership with Meridian Dynamics. Following the successful completion of Phase 1, \
1743 which delivered a 40% improvement in your ML inference pipeline throughput, we believe \
1744 the foundation is firmly established for an ambitious Phase 2 engagement.",
1745 )
1746 .first_line_indent(Length::inches(0.5));
1747
1748 doc.add_paragraph(
1749 "During our recent executive review, your team highlighted three priority areas for \
1750 the next phase of collaboration:",
1751 )
1752 .first_line_indent(Length::inches(0.5));
1753
1754 doc.add_numbered_list_item(
1755 "Real-time anomaly detection for your financial transaction monitoring system, \
1756 targeting sub-100ms latency at 50,000 transactions per second.",
1757 0,
1758 );
1759 doc.add_numbered_list_item(
1760 "Federated learning infrastructure to enable model training across your distributed \
1761 data centers without centralizing sensitive financial data.",
1762 0,
1763 );
1764 doc.add_numbered_list_item(
1765 "MLOps automation to reduce your model deployment cycle from the current 5 days \
1766 to under 4 hours.",
1767 0,
1768 );
1769
1770 doc.add_paragraph(
1771 "Our engineering team has prepared a detailed technical proposal addressing each of \
1772 these areas. We have allocated a dedicated team of eight senior engineers, led by \
1773 our CTO, to ensure continuity with the Phase 1 team your organization has already \
1774 built a productive working relationship with.",
1775 )
1776 .first_line_indent(Length::inches(0.5));
1777
1778 doc.add_paragraph(
1779 "I would welcome the opportunity to present our Phase 2 proposal in person. My \
1780 assistant will reach out to coordinate a meeting at your convenience during the \
1781 first week of March.",
1782 )
1783 .first_line_indent(Length::inches(0.5));
1784
1785 doc.add_paragraph("");
1786
1787 doc.add_paragraph("Warm regards,");
1788 doc.add_paragraph("");
1789 doc.add_paragraph("");
1790
1791 // Signature line
1792 doc.add_paragraph("")
1793 .border_bottom(BorderStyle::Single, 4, "4A5568");
1794 {
1795 let mut p = doc.add_paragraph("");
1796 p.add_run("Walter White").bold(true).size(12.0);
1797 }
1798 {
1799 let mut p = doc.add_paragraph("");
1800 p.add_run("Chief Executive Officer, Tensorbee")
1801 .italic(true)
1802 .size(10.0)
1803 .color("666666");
1804 }
1805 {
1806 let mut p = doc.add_paragraph("");
1807 p.add_run("walter@tensorbee.com | +1 (415) 555-0199")
1808 .size(10.0)
1809 .color("888888");
1810 }
1811
1812 doc
1813}
1814
1815// =============================================================================
1816// 7. EMPLOYMENT CONTRACT — Purple + charcoal scheme
1817// =============================================================================
1818fn generate_contract(_samples_dir: &Path) -> Document {
1819 let mut doc = Document::new();
1820
1821 // Colors: Purple #5B2C6F, Plum #8E44AD, Gray #2C3E50
1822 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1823 doc.set_margins(
1824 Length::inches(1.25),
1825 Length::inches(1.0),
1826 Length::inches(1.0),
1827 Length::inches(1.0),
1828 );
1829 doc.set_title("Employment Agreement");
1830 doc.set_author("Tensorbee HR Department");
1831 doc.set_subject("Employment Contract — Walter White");
1832 doc.set_keywords("employment, contract, agreement, Tensorbee");
1833
1834 doc.set_header("TENSORBEE — Employment Agreement");
1835 doc.set_footer("Employment Agreement — Walter White — Page");
1836
1837 // ── Title Block ──
1838 doc.add_paragraph("")
1839 .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1840 {
1841 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1842 p.add_run("EMPLOYMENT AGREEMENT")
1843 .bold(true)
1844 .size(24.0)
1845 .color("5B2C6F")
1846 .font("Georgia");
1847 }
1848 doc.add_paragraph("")
1849 .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1850 doc.add_paragraph("");
1851
1852 // ── Parties ──
1853 doc.add_paragraph(
1854 "This Employment Agreement (\"Agreement\") is entered into as of February 22, 2026 \
1855 (\"Effective Date\"), by and between:",
1856 );
1857
1858 doc.add_paragraph("");
1859
1860 {
1861 let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1862 p.add_run("EMPLOYER: ").bold(true);
1863 p.add_run(
1864 "Tensorbee, Inc., a Delaware corporation with its principal place of business at \
1865 123 Innovation Drive, Suite 400, San Francisco, CA 94105 (\"Company\")",
1866 );
1867 }
1868 doc.add_paragraph("");
1869 {
1870 let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1871 p.add_run("EMPLOYEE: ").bold(true);
1872 p.add_run(
1873 "Walter White, residing at 308 Negra Arroyo Lane, Albuquerque, NM 87104 (\"Employee\")",
1874 );
1875 }
1876
1877 doc.add_paragraph("");
1878 doc.add_paragraph("The Company and Employee are collectively referred to as the \"Parties.\"");
1879 doc.add_paragraph("");
1880
1881 // ── Article 1: Position and Duties ──
1882 doc.add_paragraph("Article 1 — Position and Duties")
1883 .style("Heading1");
1884 {
1885 let mut p = doc.add_paragraph("");
1886 p.add_run("1.1 ").bold(true);
1887 p.add_run("The Company hereby employs the Employee as ");
1888 p.add_run("Chief Executive Officer (CEO)")
1889 .bold(true)
1890 .italic(true);
1891 p.add_run(", reporting directly to the Board of Directors.");
1892 }
1893 {
1894 let mut p = doc.add_paragraph("");
1895 p.add_run("1.2 ").bold(true);
1896 p.add_run("The Employee shall devote full working time, attention, and best efforts to \
1897 the performance of duties as reasonably assigned by the Board, including but not limited to:");
1898 }
1899 doc.add_bullet_list_item(
1900 "Setting and executing the Company's strategic vision and business plan",
1901 0,
1902 );
1903 doc.add_bullet_list_item(
1904 "Overseeing all operations, engineering, and commercial activities",
1905 0,
1906 );
1907 doc.add_bullet_list_item(
1908 "Representing the Company to investors, customers, and the public",
1909 0,
1910 );
1911 doc.add_bullet_list_item("Recruiting, developing, and retaining key talent", 0);
1912
1913 {
1914 let mut p = doc.add_paragraph("");
1915 p.add_run("1.3 ").bold(true);
1916 p.add_run(
1917 "The Employee's primary work location shall be the Company's San Francisco \
1918 headquarters, with reasonable travel as required by business needs.",
1919 );
1920 }
1921
1922 // ── Article 2: Compensation ──
1923 doc.add_paragraph("Article 2 — Compensation and Benefits")
1924 .style("Heading1");
1925 {
1926 let mut p = doc.add_paragraph("");
1927 p.add_run("2.1 Base Salary. ").bold(true);
1928 p.add_run("The Company shall pay the Employee an annual base salary of ");
1929 p.add_run("$375,000.00").bold(true);
1930 p.add_run(" (Three Hundred Seventy-Five Thousand Dollars), payable in accordance with the \
1931 Company's standard payroll schedule, less applicable withholdings and deductions.");
1932 }
1933 {
1934 let mut p = doc.add_paragraph("");
1935 p.add_run("2.2 Annual Bonus. ").bold(true);
1936 p.add_run("The Employee shall be eligible for an annual performance bonus of up to ");
1937 p.add_run("40%").bold(true);
1938 p.add_run(
1939 " of base salary, based on achievement of mutually agreed performance objectives.",
1940 );
1941 }
1942 {
1943 let mut p = doc.add_paragraph("");
1944 p.add_run("2.3 Equity. ").bold(true);
1945 p.add_run("Subject to Board approval, the Employee shall receive a stock option grant of ");
1946 p.add_run("500,000 shares").bold(true);
1947 p.add_run(
1948 " of the Company's common stock, vesting over four (4) years with a one-year cliff, \
1949 at an exercise price equal to the fair market value on the date of grant.",
1950 );
1951 }
1952
1953 // Compensation summary table
1954 doc.add_paragraph("");
1955 {
1956 let mut tbl = doc
1957 .add_table(5, 2)
1958 .borders(BorderStyle::Single, 4, "5B2C6F")
1959 .width_pct(70.0)
1960 .alignment(Alignment::Center);
1961 tbl.cell(0, 0).unwrap().set_text("Compensation Element");
1962 tbl.cell(0, 0).unwrap().shading("5B2C6F");
1963 tbl.cell(0, 1).unwrap().set_text("Value");
1964 tbl.cell(0, 1).unwrap().shading("5B2C6F");
1965 let items = [
1966 ("Base Salary", "$375,000/year"),
1967 ("Target Bonus", "Up to 40% ($150,000)"),
1968 ("Equity Grant", "500,000 shares (4yr vest)"),
1969 ("Total Target Comp", "$525,000 + equity"),
1970 ];
1971 for (i, (elem, val)) in items.iter().enumerate() {
1972 tbl.cell(i + 1, 0).unwrap().set_text(elem);
1973 tbl.cell(i + 1, 1).unwrap().set_text(val);
1974 if i == 3 {
1975 tbl.cell(i + 1, 0).unwrap().shading("E8DAEF");
1976 tbl.cell(i + 1, 1).unwrap().shading("E8DAEF");
1977 }
1978 }
1979 }
1980
1981 // ── Article 3: Benefits ──
1982 doc.add_paragraph("").page_break_before(true);
1983 doc.add_paragraph("Article 3 — Benefits").style("Heading1");
1984 {
1985 let mut p = doc.add_paragraph("");
1986 p.add_run("3.1 ").bold(true);
1987 p.add_run(
1988 "The Employee shall be entitled to participate in all benefit programs \
1989 generally available to senior executives, including:",
1990 );
1991 }
1992 doc.add_bullet_list_item(
1993 "Medical, dental, and vision insurance (100% premium coverage for employee and dependents)",
1994 0,
1995 );
1996 doc.add_bullet_list_item("401(k) retirement plan with 6% company match", 0);
1997 doc.add_bullet_list_item("Life insurance and long-term disability coverage", 0);
1998 doc.add_bullet_list_item("Annual professional development allowance of $10,000", 0);
1999
2000 {
2001 let mut p = doc.add_paragraph("");
2002 p.add_run("3.2 Paid Time Off. ").bold(true);
2003 p.add_run(
2004 "The Employee shall receive 25 days of paid vacation per year, plus Company holidays, \
2005 accruing on a monthly basis.",
2006 );
2007 }
2008
2009 // ── Article 4: Term and Termination ──
2010 doc.add_paragraph("Article 4 — Term and Termination")
2011 .style("Heading1");
2012 {
2013 let mut p = doc.add_paragraph("");
2014 p.add_run("4.1 At-Will Employment. ").bold(true);
2015 p.add_run(
2016 "This Agreement is for at-will employment and may be terminated by either Party \
2017 at any time, with or without cause, subject to the notice provisions herein.",
2018 );
2019 }
2020 {
2021 let mut p = doc.add_paragraph("");
2022 p.add_run("4.2 Notice Period. ").bold(true);
2023 p.add_run("Either Party shall provide ");
2024 p.add_run("ninety (90) days").bold(true);
2025 p.add_run(" written notice of termination, or pay in lieu thereof.");
2026 }
2027 {
2028 let mut p = doc.add_paragraph("");
2029 p.add_run("4.3 Severance. ").bold(true);
2030 p.add_run("In the event of termination by the Company without Cause, the Employee shall \
2031 receive (a) twelve (12) months of base salary continuation, (b) pro-rata bonus \
2032 for the year of termination, and (c) twelve (12) months of COBRA premium coverage.");
2033 }
2034
2035 // ── Article 5: Confidentiality ──
2036 doc.add_paragraph("Article 5 — Confidentiality and IP")
2037 .style("Heading1");
2038 {
2039 let mut p = doc.add_paragraph("");
2040 p.add_run("5.1 ").bold(true);
2041 p.add_run(
2042 "The Employee agrees to maintain strict confidentiality of all proprietary \
2043 information, trade secrets, and business plans of the Company during and after \
2044 employment.",
2045 );
2046 }
2047 {
2048 let mut p = doc.add_paragraph("");
2049 p.add_run("5.2 ").bold(true);
2050 p.add_run(
2051 "All intellectual property, inventions, and works of authorship created by the \
2052 Employee during the term of employment and within the scope of duties shall be \
2053 the sole and exclusive property of the Company.",
2054 );
2055 }
2056
2057 // ── Article 6: Non-Compete ──
2058 doc.add_paragraph("Article 6 — Non-Competition")
2059 .style("Heading1");
2060 {
2061 let mut p = doc.add_paragraph("");
2062 p.add_run("6.1 ").bold(true);
2063 p.add_run("For a period of twelve (12) months following termination, the Employee agrees \
2064 not to engage in any business that directly competes with the Company's core \
2065 business of AI infrastructure and ML operations services, within the United States.");
2066 }
2067
2068 // ── Article 7: General Provisions ──
2069 doc.add_paragraph("Article 7 — General Provisions")
2070 .style("Heading1");
2071 {
2072 let mut p = doc.add_paragraph("");
2073 p.add_run("7.1 Governing Law. ").bold(true);
2074 p.add_run(
2075 "This Agreement shall be governed by and construed in accordance with the laws of \
2076 the State of California.",
2077 );
2078 }
2079 {
2080 let mut p = doc.add_paragraph("");
2081 p.add_run("7.2 Entire Agreement. ").bold(true);
2082 p.add_run(
2083 "This Agreement constitutes the entire agreement between the Parties and supersedes \
2084 all prior negotiations, representations, and agreements.",
2085 );
2086 }
2087 {
2088 let mut p = doc.add_paragraph("");
2089 p.add_run("7.3 Amendment. ").bold(true);
2090 p.add_run(
2091 "This Agreement may only be amended by a written instrument signed by both Parties.",
2092 );
2093 }
2094
2095 // ── Signature Block ──
2096 doc.add_paragraph("").page_break_before(true);
2097 doc.add_paragraph("IN WITNESS WHEREOF, the Parties have executed this Employment Agreement as of the Effective Date.")
2098 .space_after(Length::pt(24.0));
2099
2100 // Signature table
2101 {
2102 let mut tbl = doc.add_table(6, 2).width_pct(100.0);
2103 tbl.cell(0, 0).unwrap().set_text("FOR THE COMPANY:");
2104 tbl.cell(0, 0).unwrap().shading("5B2C6F");
2105 tbl.cell(0, 1).unwrap().set_text("EMPLOYEE:");
2106 tbl.cell(0, 1).unwrap().shading("5B2C6F");
2107
2108 tbl.cell(1, 0).unwrap().set_text("");
2109 tbl.cell(1, 1).unwrap().set_text("");
2110 tbl.row(1).unwrap().height(Length::pt(40.0));
2111
2112 tbl.cell(2, 0)
2113 .unwrap()
2114 .set_text("Signature: ___________________________");
2115 tbl.cell(2, 1)
2116 .unwrap()
2117 .set_text("Signature: ___________________________");
2118 tbl.cell(3, 0)
2119 .unwrap()
2120 .set_text("Name: Board Representative");
2121 tbl.cell(3, 1).unwrap().set_text("Name: Walter White");
2122 tbl.cell(4, 0)
2123 .unwrap()
2124 .set_text("Title: Chair, Board of Directors");
2125 tbl.cell(4, 1)
2126 .unwrap()
2127 .set_text("Title: Chief Executive Officer");
2128 tbl.cell(5, 0).unwrap().set_text("Date: _______________");
2129 tbl.cell(5, 1).unwrap().set_text("Date: _______________");
2130 }
2131
2132 doc
2133}Sourcepub fn set_first_page_header(&mut self, text: &str)
pub fn set_first_page_header(&mut self, text: &str)
Set the first-page header text.
Examples found in repository?
97fn generate_feature_showcase(_samples_dir: &Path) -> Document {
98 let mut doc = Document::new();
99
100 // ── Page Setup & Metadata ──
101 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
102 doc.set_margins(
103 Length::inches(1.0),
104 Length::inches(1.0),
105 Length::inches(1.0),
106 Length::inches(1.0),
107 );
108 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
109 doc.set_gutter(Length::twips(0));
110 doc.set_title("rdocx Feature Showcase");
111 doc.set_author("rdocx Sample Generator");
112 doc.set_subject("Comprehensive feature demonstration");
113 doc.set_keywords("rdocx, docx, rust, sample, showcase");
114
115 // Headers & Footers with different first page
116 doc.set_different_first_page(true);
117 doc.set_first_page_header("rdocx");
118 doc.set_first_page_footer("Feature Showcase — Cover Page");
119 doc.set_header("rdocx Feature Showcase");
120 doc.set_footer("Generated by rdocx");
121
122 // ── COVER PAGE ──
123 let bg = create_sample_png(612, 792, [20, 45, 90]);
124 doc.add_background_image(&bg, "cover_bg.png");
125
126 for _ in 0..3 {
127 doc.add_paragraph("");
128 }
129 {
130 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
131 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
132 }
133 {
134 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
135 p.add_run("Complete Feature Showcase")
136 .size(28.0)
137 .color("FFFFFF")
138 .italic(true);
139 }
140 doc.add_paragraph("");
141 {
142 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
143 p.add_run("Every feature of the rdocx Rust library")
144 .size(13.0)
145 .color("B0C4DE");
146 }
147 {
148 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
149 p.add_run("demonstrated in a single document.")
150 .size(13.0)
151 .color("B0C4DE");
152 }
153
154 // ── TABLE OF CONTENTS ──
155 doc.add_paragraph("").page_break_before(true);
156 doc.insert_toc(doc.content_count(), 3);
157
158 // ── SECTION 1: TEXT FORMATTING ──
159 doc.add_paragraph("").page_break_before(true);
160 doc.add_paragraph("1. Text Formatting").style("Heading1");
161
162 // Paragraph Alignment
163 doc.add_paragraph("1.1 Paragraph Alignment")
164 .style("Heading2");
165 doc.add_paragraph("Left-aligned paragraph (default).")
166 .alignment(Alignment::Left);
167 doc.add_paragraph("Center-aligned paragraph.")
168 .alignment(Alignment::Center);
169 doc.add_paragraph("Right-aligned paragraph.")
170 .alignment(Alignment::Right);
171 doc.add_paragraph(
172 "Justified paragraph — this text is long enough to demonstrate how justified alignment \
173 distributes extra space across word gaps so lines fill the full width of the text area.",
174 )
175 .alignment(Alignment::Justify);
176
177 doc.add_paragraph("");
178
179 // Run Formatting
180 doc.add_paragraph("1.2 Run Formatting").style("Heading2");
181 {
182 let mut p = doc.add_paragraph("");
183 p.add_run("Bold").bold(true);
184 p.add_run(" | ");
185 p.add_run("Italic").italic(true);
186 p.add_run(" | ");
187 p.add_run("Bold Italic").bold(true).italic(true);
188 p.add_run(" | ");
189 p.add_run("Underline").underline(true);
190 p.add_run(" | ");
191 p.add_run("Strikethrough").strike(true);
192 p.add_run(" | ");
193 p.add_run("Double Strike").double_strike(true);
194 }
195 {
196 let mut p = doc.add_paragraph("");
197 p.add_run("Red").color("FF0000");
198 p.add_run(" | ");
199 p.add_run("Blue").color("0000FF");
200 p.add_run(" | ");
201 p.add_run("Green").color("00AA00");
202 p.add_run(" | ");
203 p.add_run("Highlighted").highlight("FFFF00");
204 }
205 {
206 let mut p = doc.add_paragraph("");
207 p.add_run("8pt").size(8.0);
208 p.add_run(" | ");
209 p.add_run("11pt").size(11.0);
210 p.add_run(" | ");
211 p.add_run("16pt").size(16.0);
212 p.add_run(" | ");
213 p.add_run("24pt").size(24.0);
214 }
215 {
216 let mut p = doc.add_paragraph("");
217 p.add_run("Arial").font("Arial");
218 p.add_run(" | ");
219 p.add_run("Times New Roman").font("Times New Roman");
220 p.add_run(" | ");
221 p.add_run("Courier New").font("Courier New");
222 }
223 {
224 let mut p = doc.add_paragraph("");
225 p.add_run("H");
226 p.add_run("2").subscript();
227 p.add_run("O (subscript) | E=mc");
228 p.add_run("2").superscript();
229 p.add_run(" (superscript)");
230 }
231 {
232 let mut p = doc.add_paragraph("");
233 p.add_run("ALL CAPS").all_caps(true);
234 p.add_run(" | ");
235 p.add_run("Small Caps").small_caps(true);
236 p.add_run(" | ");
237 p.add_run("Expanded").character_spacing(Length::twips(40));
238 p.add_run(" | ");
239 p.add_run("Hidden text (not visible)").hidden(true);
240 }
241
242 doc.add_paragraph("");
243
244 // Underline Styles
245 doc.add_paragraph("1.3 Underline Styles").style("Heading2");
246 {
247 let mut p = doc.add_paragraph("");
248 p.add_run("Single ")
249 .underline_style(rdocx::UnderlineStyle::Single);
250 p.add_run("Double ")
251 .underline_style(rdocx::UnderlineStyle::Double);
252 p.add_run("Thick ")
253 .underline_style(rdocx::UnderlineStyle::Thick);
254 p.add_run("Dotted ")
255 .underline_style(rdocx::UnderlineStyle::Dotted);
256 p.add_run("Dash ")
257 .underline_style(rdocx::UnderlineStyle::Dash);
258 p.add_run("Wave ")
259 .underline_style(rdocx::UnderlineStyle::Wave);
260 }
261
262 // ── SECTION 2: PARAGRAPH FORMATTING ──
263 doc.add_paragraph("").page_break_before(true);
264 doc.add_paragraph("2. Paragraph Formatting")
265 .style("Heading1");
266
267 doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
268 doc.add_paragraph("Paragraph with light green shading")
269 .shading("E2EFDA");
270 doc.add_paragraph("Paragraph with bottom border")
271 .border_bottom(BorderStyle::Single, 6, "2E75B6");
272 doc.add_paragraph("Paragraph with all borders (red)")
273 .border_all(BorderStyle::Single, 4, "FF0000");
274
275 doc.add_paragraph("2.2 Indentation").style("Heading2");
276 doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
277 .indent_left(Length::inches(1.0))
278 .hanging_indent(Length::inches(0.5));
279 doc.add_paragraph("0.5-inch first-line indent on this paragraph")
280 .first_line_indent(Length::inches(0.5));
281 doc.add_paragraph("Both left and right indent (0.75 inches each)")
282 .indent_left(Length::inches(0.75))
283 .indent_right(Length::inches(0.75));
284
285 doc.add_paragraph("2.3 Spacing & Line Height")
286 .style("Heading2");
287 doc.add_paragraph("Extra space before (24pt) and after (12pt)")
288 .space_before(Length::pt(24.0))
289 .space_after(Length::pt(12.0));
290 doc.add_paragraph(
291 "Double line spacing paragraph. This text should have extra vertical space between \
292 lines to demonstrate line_spacing_multiple(2.0).",
293 )
294 .line_spacing_multiple(2.0);
295 doc.add_paragraph("Exact 20pt line spacing (fixed height).")
296 .line_spacing(20.0);
297
298 doc.add_paragraph("2.4 Pagination Controls")
299 .style("Heading2");
300 doc.add_paragraph("keep_with_next — this paragraph stays with the next")
301 .keep_with_next(true);
302 doc.add_paragraph("(This paragraph was kept with the one above.)");
303 doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
304 .keep_together(true);
305 doc.add_paragraph("widow_control — prevents widow/orphan lines")
306 .widow_control(true);
307
308 // ── SECTION 3: LISTS ──
309 doc.add_paragraph("").page_break_before(true);
310 doc.add_paragraph("3. Lists").style("Heading1");
311
312 doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
313 doc.add_bullet_list_item("First item", 0);
314 doc.add_bullet_list_item("Second item", 0);
315 doc.add_bullet_list_item("Nested level 1", 1);
316 doc.add_bullet_list_item("Nested level 2", 2);
317 doc.add_bullet_list_item("Back to level 1", 1);
318 doc.add_bullet_list_item("Third item", 0);
319
320 doc.add_paragraph("");
321
322 doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
323 doc.add_numbered_list_item("First numbered item", 0);
324 doc.add_numbered_list_item("Second numbered item", 0);
325 doc.add_numbered_list_item("Sub-item A", 1);
326 doc.add_numbered_list_item("Sub-item B", 1);
327 doc.add_numbered_list_item("Third numbered item", 0);
328
329 // ── SECTION 4: TAB STOPS ──
330 doc.add_paragraph("");
331 doc.add_paragraph("4. Tab Stops").style("Heading1");
332
333 doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
334 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
335 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
336 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
337 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
338 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
339
340 doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
341 doc.add_paragraph("Item\t\tPrice")
342 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
343 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
344 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
345 doc.add_paragraph("Widget\t\t$19.99")
346 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
347 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
348 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
349 doc.add_paragraph("Gadget\t\t$249.50")
350 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
351 .add_tab_stop_with_leader(
352 TabAlignment::Right,
353 Length::inches(4.0),
354 TabLeader::Underscore,
355 )
356 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
357
358 // ── SECTION 5: TABLES ──
359 doc.add_paragraph("").page_break_before(true);
360 doc.add_paragraph("5. Tables").style("Heading1");
361
362 doc.add_paragraph("5.1 Basic Table").style("Heading2");
363 {
364 let mut tbl = doc
365 .add_table(4, 3)
366 .borders(BorderStyle::Single, 4, "000000");
367 for col in 0..3 {
368 tbl.cell(0, col).unwrap().shading("2E75B6");
369 }
370 tbl.cell(0, 0).unwrap().set_text("Name");
371 tbl.cell(0, 1).unwrap().set_text("Role");
372 tbl.cell(0, 2).unwrap().set_text("Location");
373 tbl.cell(1, 0).unwrap().set_text("Walter White");
374 tbl.cell(1, 1).unwrap().set_text("CEO");
375 tbl.cell(1, 2).unwrap().set_text("Albuquerque");
376 tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
377 tbl.cell(2, 1).unwrap().set_text("CTO");
378 tbl.cell(2, 2).unwrap().set_text("Remote");
379 tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
380 tbl.cell(3, 1).unwrap().set_text("Security");
381 tbl.cell(3, 2).unwrap().set_text("Washington");
382 }
383
384 doc.add_paragraph("");
385
386 doc.add_paragraph("5.2 Column Span & Cell Shading")
387 .style("Heading2");
388 {
389 let mut tbl = doc
390 .add_table(3, 4)
391 .borders(BorderStyle::Single, 4, "000000")
392 .width_pct(100.0);
393 tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
394 tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
395 tbl.cell(1, 0).unwrap().set_text("Region");
396 tbl.cell(1, 0).unwrap().shading("D6E4F0");
397 tbl.cell(1, 1).unwrap().set_text("Q1");
398 tbl.cell(1, 1).unwrap().shading("D6E4F0");
399 tbl.cell(1, 2).unwrap().set_text("Q2");
400 tbl.cell(1, 2).unwrap().shading("D6E4F0");
401 tbl.cell(1, 3).unwrap().set_text("Total");
402 tbl.cell(1, 3).unwrap().shading("D6E4F0");
403 tbl.cell(2, 0).unwrap().set_text("Americas");
404 tbl.cell(2, 1).unwrap().set_text("$2.4M");
405 tbl.cell(2, 2).unwrap().set_text("$2.7M");
406 tbl.cell(2, 3).unwrap().set_text("$5.1M");
407 }
408
409 doc.add_paragraph("");
410
411 doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
412 {
413 let mut tbl = doc
414 .add_table(4, 3)
415 .borders(BorderStyle::Single, 4, "000000");
416 tbl.cell(0, 0).unwrap().set_text("Category");
417 tbl.cell(0, 0).unwrap().shading("E2EFDA");
418 tbl.cell(0, 1).unwrap().set_text("Item");
419 tbl.cell(0, 1).unwrap().shading("E2EFDA");
420 tbl.cell(0, 2).unwrap().set_text("Price");
421 tbl.cell(0, 2).unwrap().shading("E2EFDA");
422 tbl.cell(1, 0).unwrap().set_text("Hardware");
423 tbl.cell(1, 0).unwrap().v_merge_restart();
424 tbl.cell(1, 1).unwrap().set_text("Laptop");
425 tbl.cell(1, 2).unwrap().set_text("$1,200");
426 tbl.cell(2, 0).unwrap().v_merge_continue();
427 tbl.cell(2, 1).unwrap().set_text("Monitor");
428 tbl.cell(2, 2).unwrap().set_text("$450");
429 tbl.cell(3, 0).unwrap().set_text("Software");
430 tbl.cell(3, 1).unwrap().set_text("IDE License");
431 tbl.cell(3, 2).unwrap().set_text("$200/yr");
432 }
433
434 doc.add_paragraph("");
435
436 doc.add_paragraph("5.4 Nested Table").style("Heading2");
437 {
438 let mut tbl = doc
439 .add_table(2, 2)
440 .borders(BorderStyle::Single, 6, "2E75B6");
441 tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
442 tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
443 tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
444 {
445 let mut cell = tbl.cell(1, 1).unwrap();
446 cell.set_text("Nested table below:");
447 let mut nested = cell
448 .add_table(2, 2)
449 .borders(BorderStyle::Single, 2, "FF6600");
450 nested.cell(0, 0).unwrap().set_text("A");
451 nested.cell(0, 1).unwrap().set_text("B");
452 nested.cell(1, 0).unwrap().set_text("C");
453 nested.cell(1, 1).unwrap().set_text("D");
454 }
455 }
456
457 doc.add_paragraph("");
458
459 doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
460 .style("Heading2");
461 {
462 let mut tbl = doc
463 .add_table(2, 3)
464 .borders(BorderStyle::Single, 4, "666666");
465 tbl.row(0).unwrap().height(Length::pt(50.0)).header();
466 tbl.cell(0, 0).unwrap().set_text("Top");
467 tbl.cell(0, 0)
468 .unwrap()
469 .vertical_alignment(VerticalAlignment::Top)
470 .shading("FFF2CC");
471 tbl.cell(0, 1).unwrap().set_text("Center");
472 tbl.cell(0, 1)
473 .unwrap()
474 .vertical_alignment(VerticalAlignment::Center)
475 .shading("D9E2F3");
476 tbl.cell(0, 2).unwrap().set_text("Bottom");
477 tbl.cell(0, 2)
478 .unwrap()
479 .vertical_alignment(VerticalAlignment::Bottom)
480 .shading("E2EFDA");
481 tbl.row(1).unwrap().cant_split();
482 tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
483 tbl.cell(1, 0).unwrap().no_wrap();
484 tbl.cell(1, 1).unwrap().set_text("Fixed width");
485 tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
486 tbl.cell(1, 2).unwrap().set_text("Normal");
487 }
488
489 // ── SECTION 6: IMAGES ──
490 doc.add_paragraph("").page_break_before(true);
491 doc.add_paragraph("6. Images").style("Heading1");
492
493 doc.add_paragraph("6.1 Inline Image").style("Heading2");
494 doc.add_paragraph("A blue gradient image below:");
495 let img = create_sample_png(200, 50, [0, 80, 200]);
496 doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
497
498 doc.add_paragraph("");
499 doc.add_paragraph("6.2 Header Image").style("Heading2");
500 let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
501 doc.set_header_image(
502 &hdr_img,
503 "header_logo.png",
504 Length::inches(2.0),
505 Length::inches(0.2),
506 );
507 doc.add_paragraph("The header now contains an inline image (check the top of this page).");
508
509 doc.add_paragraph("");
510 doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
511
512 // ── SECTION 7: CONTENT MANIPULATION ──
513 doc.add_paragraph("").page_break_before(true);
514 doc.add_paragraph("7. Content Manipulation")
515 .style("Heading1");
516
517 doc.add_paragraph("7.1 Placeholder Replacement")
518 .style("Heading2");
519 doc.add_paragraph("Customer: {{customer}}");
520 doc.add_paragraph("Date: {{date}}");
521 doc.add_paragraph("Reference: {{ref_number}}");
522 {
523 let mut tbl = doc
524 .add_table(2, 2)
525 .borders(BorderStyle::Single, 4, "000000");
526 tbl.cell(0, 0).unwrap().set_text("Project");
527 tbl.cell(0, 0).unwrap().shading("D6E4F0");
528 tbl.cell(0, 1).unwrap().set_text("{{project}}");
529 tbl.cell(1, 0).unwrap().set_text("Status");
530 tbl.cell(1, 0).unwrap().shading("D6E4F0");
531 tbl.cell(1, 1).unwrap().set_text("{{status}}");
532 }
533
534 let mut replacements = HashMap::new();
535 replacements.insert("{{customer}}", "Tensorbee Inc.");
536 replacements.insert("{{date}}", "February 22, 2026");
537 replacements.insert("{{ref_number}}", "TB-2026-001");
538 replacements.insert("{{project}}", "Infrastructure Upgrade");
539 replacements.insert("{{status}}", "In Progress");
540 let n = doc.replace_all(&replacements);
541 doc.add_paragraph(&format!("({n} placeholders replaced above)"));
542
543 doc.add_paragraph("");
544
545 doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
546 doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
547 let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
548 doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
549
550 doc.add_paragraph("");
551
552 doc.add_paragraph("7.3 Content Insertion").style("Heading2");
553 doc.add_paragraph("Section A: First content.");
554 doc.add_paragraph("Section C: Third content.");
555 if let Some(idx) = doc.find_content_index("Section C") {
556 doc.insert_paragraph(
557 idx,
558 "Section B: Inserted between A and C via find_content_index().",
559 );
560 }
561
562 // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
563 doc.add_paragraph("").section_break(SectionBreak::NextPage);
564 doc.add_paragraph("8. Section Breaks & Orientation")
565 .style("Heading1");
566 doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
567
568 {
569 let mut tbl = doc
570 .add_table(3, 7)
571 .borders(BorderStyle::Single, 4, "2E75B6");
572 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
573 for (c, h) in headers.iter().enumerate() {
574 tbl.cell(0, c).unwrap().set_text(h);
575 tbl.cell(0, c).unwrap().shading("2E75B6");
576 }
577 let data = [
578 [
579 "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
580 ],
581 [
582 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
583 ],
584 ];
585 for (r, row) in data.iter().enumerate() {
586 for (c, v) in row.iter().enumerate() {
587 tbl.cell(r + 1, c).unwrap().set_text(v);
588 }
589 }
590 }
591
592 doc.add_paragraph("")
593 .section_break(SectionBreak::NextPage)
594 .section_landscape();
595
596 // ── SECTION 9: CUSTOM STYLES ──
597 doc.add_paragraph("9. Custom Styles").style("Heading1");
598 doc.add_style(
599 StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
600 .based_on("Normal")
601 .paragraph_properties(rdocx_oxml::properties::CT_PPr {
602 shading: Some(rdocx_oxml::properties::CT_Shd {
603 val: "clear".to_string(),
604 color: None,
605 fill: Some("FFF2CC".to_string()),
606 }),
607 ..Default::default()
608 })
609 .run_properties(rdocx_oxml::properties::CT_RPr {
610 bold: Some(true),
611 color: Some("C45911".to_string()),
612 ..Default::default()
613 }),
614 );
615 doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
616 .style("CustomHighlight");
617
618 doc.add_paragraph("");
619
620 // ── SECTION 10: DOCUMENT INTELLIGENCE ──
621 doc.add_paragraph("10. Document Intelligence API")
622 .style("Heading1");
623
624 let wc = doc.word_count();
625 let hc = doc.headings().len();
626 let ic = doc.images().len();
627 let lc = doc.links().len();
628 doc.add_paragraph(&format!("Word count: {wc}"));
629 doc.add_paragraph(&format!("Heading count: {hc}"));
630 doc.add_paragraph(&format!("Image count: {ic}"));
631 doc.add_paragraph(&format!("Link count: {lc}"));
632
633 let outline = doc.document_outline();
634 doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
635
636 let issues = doc.audit_accessibility();
637 doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
638 for issue in &issues {
639 doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
640 }
641
642 // ── SECTION 11: DOCUMENT MERGING ──
643 doc.add_paragraph("");
644 doc.add_paragraph("11. Document Merging").style("Heading1");
645 {
646 let mut other = Document::new();
647 other.add_paragraph("This paragraph was merged from another document using append().");
648 doc.append(&other);
649 }
650
651 doc.add_paragraph("");
652
653 // ── SUMMARY ──
654 doc.add_paragraph("Summary of Demonstrated Features")
655 .style("Heading1");
656 let features = [
657 "Page setup: size, margins, header/footer distance, gutter",
658 "Document metadata: title, author, subject, keywords",
659 "Headers and footers: text, images, different first page",
660 "Background images (full-page behind text)",
661 "Table of Contents generation with bookmarks",
662 "Text formatting: bold, italic, underline styles, strike, color, size, font",
663 "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
664 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
665 "Pagination controls: keep-with-next, keep-together, widow control",
666 "Bullet and numbered lists with nesting",
667 "Tab stops with dot/underscore/hyphen leaders",
668 "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
669 "Row properties: header rows, exact height, cant-split",
670 "Cell properties: width, no-wrap, vertical alignment",
671 "Inline images and header images",
672 "Placeholder replacement (single and batch)",
673 "Regex-based find and replace",
674 "Content insertion at specific positions",
675 "Section breaks with mixed portrait/landscape",
676 "Custom paragraph and character styles",
677 "Document intelligence: word count, headings, outline, images, links",
678 "Accessibility audit",
679 "Document merging (append)",
680 "Export: DOCX, PDF, HTML, Markdown",
681 ];
682 for f in &features {
683 doc.add_bullet_list_item(f, 0);
684 }
685 doc.add_paragraph("");
686 doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
687 .alignment(Alignment::Center)
688 .shading("E2EFDA")
689 .border_all(BorderStyle::Single, 2, "00AA00");
690
691 doc
692}
693
694// =============================================================================
695// 2. PROPOSAL DOCUMENT — Deep navy + gold scheme
696// =============================================================================
697fn generate_proposal(_samples_dir: &Path) -> Document {
698 let mut doc = Document::new();
699
700 // Colors: Navy #1B2A4A, Gold #C5922E, Light #F4F1EB
701 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
702 doc.set_margins(
703 Length::inches(1.0),
704 Length::inches(1.0),
705 Length::inches(1.0),
706 Length::inches(1.0),
707 );
708 doc.set_title("AI Platform Modernization Proposal");
709 doc.set_author("Walter White");
710 doc.set_subject("Technology Proposal");
711 doc.set_keywords("proposal, AI, modernization, Tensorbee");
712
713 // Banner header
714 let logo_img = create_logo_png(220, 48);
715 let banner = build_header_banner_xml(
716 "rId1",
717 &BannerOpts {
718 bg_color: "1B2A4A",
719 banner_width: 7772400,
720 banner_height: 914400,
721 logo_width: 2011680,
722 logo_height: 438912,
723 logo_x_offset: 295125,
724 logo_y_offset: 237744,
725 },
726 );
727 doc.set_raw_header_with_images(
728 banner,
729 &[("rId1", &logo_img, "logo.png")],
730 rdocx_oxml::header_footer::HdrFtrType::Default,
731 );
732 doc.set_different_first_page(true);
733 let cover_banner = build_header_banner_xml(
734 "rId1",
735 &BannerOpts {
736 bg_color: "C5922E",
737 banner_width: 7772400,
738 banner_height: 914400,
739 logo_width: 2011680,
740 logo_height: 438912,
741 logo_x_offset: 295125,
742 logo_y_offset: 237744,
743 },
744 );
745 doc.set_raw_header_with_images(
746 cover_banner,
747 &[("rId1", &logo_img, "logo.png")],
748 rdocx_oxml::header_footer::HdrFtrType::First,
749 );
750 doc.set_margins(
751 Length::twips(2292),
752 Length::twips(1440),
753 Length::twips(1440),
754 Length::twips(1440),
755 );
756 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
757 doc.set_footer("Tensorbee — Confidential");
758
759 // Custom styles
760 doc.add_style(
761 StyleBuilder::paragraph("ProposalTitle", "Proposal Title")
762 .based_on("Normal")
763 .run_properties(rdocx_oxml::properties::CT_RPr {
764 bold: Some(true),
765 font_ascii: Some("Georgia".to_string()),
766 font_hansi: Some("Georgia".to_string()),
767 sz: Some(rdocx_oxml::HalfPoint(56)), // half-points → 28pt
768 color: Some("1B2A4A".to_string()),
769 ..Default::default()
770 }),
771 );
772
773 // ── Cover Page ──
774 for _ in 0..4 {
775 doc.add_paragraph("");
776 }
777 doc.add_paragraph("AI Platform Modernization")
778 .style("ProposalTitle")
779 .alignment(Alignment::Center);
780 doc.add_paragraph("")
781 .alignment(Alignment::Center)
782 .border_bottom(BorderStyle::Single, 8, "C5922E");
783 {
784 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
785 p.add_run("Prepared for Global Financial Corp.")
786 .size(14.0)
787 .color("666666")
788 .italic(true);
789 }
790 {
791 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
792 p.add_run("Prepared by: Walter White, CEO — Tensorbee")
793 .size(12.0)
794 .color("888888");
795 }
796 {
797 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
798 p.add_run("February 22, 2026").size(12.0).color("888888");
799 }
800
801 // ── TOC ──
802 doc.add_paragraph("").page_break_before(true);
803 doc.add_paragraph("Table of Contents").style("Heading1");
804 // We'll add a manual-style TOC since the insert_toc goes at a specific position
805 doc.insert_toc(doc.content_count(), 2);
806
807 // ── Executive Summary ──
808 doc.add_paragraph("").page_break_before(true);
809 doc.add_paragraph("Executive Summary").style("Heading1");
810 doc.add_paragraph(
811 "Tensorbee proposes a comprehensive modernization of Global Financial Corp's AI platform, \
812 transitioning from legacy batch-processing systems to a real-time inference architecture. \
813 This transformation will reduce model deployment time from weeks to hours, improve \
814 prediction accuracy by an estimated 23%, and deliver $4.2M in annual operational savings.",
815 )
816 .first_line_indent(Length::inches(0.3));
817 doc.add_paragraph(
818 "The project spans three phases over 18 months, with a total investment of $2.8M. \
819 Tensorbee brings deep expertise in ML infrastructure, having successfully completed \
820 similar transformations for three Fortune 500 financial institutions.",
821 )
822 .first_line_indent(Length::inches(0.3));
823
824 // Key metrics table
825 doc.add_paragraph("");
826 {
827 let mut tbl = doc
828 .add_table(5, 2)
829 .borders(BorderStyle::Single, 4, "1B2A4A")
830 .width_pct(80.0);
831 tbl.cell(0, 0).unwrap().set_text("Key Metric");
832 tbl.cell(0, 0).unwrap().shading("1B2A4A");
833 tbl.cell(0, 1).unwrap().set_text("Value");
834 tbl.cell(0, 1).unwrap().shading("1B2A4A");
835 let rows = [
836 ("Total Investment", "$2.8M"),
837 ("Annual Savings", "$4.2M"),
838 ("ROI (Year 1)", "150%"),
839 ("Timeline", "18 months"),
840 ];
841 for (i, (k, v)) in rows.iter().enumerate() {
842 tbl.cell(i + 1, 0).unwrap().set_text(k);
843 if i % 2 == 0 {
844 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
845 }
846 tbl.cell(i + 1, 1).unwrap().set_text(v);
847 if i % 2 == 0 {
848 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
849 }
850 }
851 }
852
853 // ── Problem Statement ──
854 doc.add_paragraph("").page_break_before(true);
855 doc.add_paragraph("Problem Statement").style("Heading1");
856 doc.add_paragraph(
857 "Global Financial Corp's current AI infrastructure faces three critical challenges:",
858 );
859 doc.add_numbered_list_item("Deployment Latency — New models take 3-4 weeks to deploy to production, compared to the industry benchmark of 2-3 days.", 0);
860 doc.add_numbered_list_item("Scalability Constraints — The batch processing architecture cannot handle real-time inference demands during peak trading hours.", 0);
861 doc.add_numbered_list_item("Technical Debt — Legacy Python 2.7 codebases and manual deployment scripts create reliability risks and slow iteration speed.", 0);
862
863 // ── Proposed Solution ──
864 doc.add_paragraph("").page_break_before(true);
865 doc.add_paragraph("Proposed Solution").style("Heading1");
866
867 doc.add_paragraph("Phase 1: Foundation (Months 1-6)")
868 .style("Heading2");
869 doc.add_bullet_list_item("Deploy Kubernetes-based ML serving infrastructure", 0);
870 doc.add_bullet_list_item("Implement CI/CD pipeline for model deployment", 0);
871 doc.add_bullet_list_item("Migrate top 5 production models to new platform", 0);
872
873 doc.add_paragraph("Phase 2: Expansion (Months 7-12)")
874 .style("Heading2");
875 doc.add_bullet_list_item(
876 "Real-time feature engineering pipeline (Apache Kafka + Flink)",
877 0,
878 );
879 doc.add_bullet_list_item("A/B testing framework for model variants", 0);
880 doc.add_bullet_list_item("Automated model monitoring and drift detection", 0);
881
882 doc.add_paragraph("Phase 3: Optimization (Months 13-18)")
883 .style("Heading2");
884 doc.add_bullet_list_item("GPU inference optimization (TensorRT/ONNX Runtime)", 0);
885 doc.add_bullet_list_item("Multi-region deployment for latency reduction", 0);
886 doc.add_bullet_list_item(
887 "Self-service model deployment portal for data science teams",
888 0,
889 );
890
891 // ── Budget ──
892 doc.add_paragraph("Budget Breakdown").style("Heading1");
893 {
894 let mut tbl = doc
895 .add_table(6, 3)
896 .borders(BorderStyle::Single, 4, "1B2A4A")
897 .width_pct(100.0);
898 tbl.cell(0, 0).unwrap().set_text("Category");
899 tbl.cell(0, 0).unwrap().shading("1B2A4A");
900 tbl.cell(0, 1).unwrap().set_text("Cost");
901 tbl.cell(0, 1).unwrap().shading("1B2A4A");
902 tbl.cell(0, 2).unwrap().set_text("Notes");
903 tbl.cell(0, 2).unwrap().shading("1B2A4A");
904 let items = [
905 (
906 "Infrastructure",
907 "$450,000",
908 "Cloud compute + GPU instances",
909 ),
910 ("Engineering Services", "$1,600,000", "12 FTE x 18 months"),
911 (
912 "Software Licenses",
913 "$350,000",
914 "Kafka, monitoring, CI/CD tools",
915 ),
916 (
917 "Training & Enablement",
918 "$200,000",
919 "Team training, documentation",
920 ),
921 ("Contingency (10%)", "$200,000", "Risk buffer"),
922 ];
923 for (i, (cat, cost, note)) in items.iter().enumerate() {
924 tbl.cell(i + 1, 0).unwrap().set_text(cat);
925 tbl.cell(i + 1, 1).unwrap().set_text(cost);
926 tbl.cell(i + 1, 2).unwrap().set_text(note);
927 if i % 2 == 0 {
928 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
929 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
930 tbl.cell(i + 1, 2).unwrap().shading("F4F1EB");
931 }
932 }
933 }
934
935 // ── Closing ──
936 doc.add_paragraph("");
937 doc.add_paragraph("Next Steps").style("Heading1");
938 doc.add_numbered_list_item(
939 "Schedule technical deep-dive with GFC engineering team (Week 1)",
940 0,
941 );
942 doc.add_numbered_list_item("Finalize scope and sign SOW (Week 2-3)", 0);
943 doc.add_numbered_list_item("Kick off Phase 1 with joint planning session (Week 4)", 0);
944
945 doc.add_paragraph("");
946 {
947 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
948 p.add_run("Walter White")
949 .bold(true)
950 .size(14.0)
951 .color("1B2A4A");
952 }
953 {
954 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
955 p.add_run("CEO, Tensorbee | walter@tensorbee.com")
956 .size(10.0)
957 .color("888888");
958 }
959
960 doc
961}
962
963// =============================================================================
964// 3. QUOTE / BILL OF MATERIALS — Teal + orange scheme
965// =============================================================================
966fn generate_quote(_samples_dir: &Path) -> Document {
967 let mut doc = Document::new();
968
969 // Colors: Teal #008B8B, Dark #1A3C3C, Orange #E07020, Light #F0F8F8
970 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
971 doc.set_margins(
972 Length::inches(0.75),
973 Length::inches(0.75),
974 Length::inches(0.75),
975 Length::inches(0.75),
976 );
977 doc.set_title("Quotation QT-2026-0147");
978 doc.set_author("Walter White");
979 doc.set_keywords("quote, BOM, Tensorbee");
980
981 doc.set_header("Tensorbee — Quotation");
982 doc.set_footer("QT-2026-0147 | Page");
983
984 // ── Header Block ──
985 {
986 let mut p = doc.add_paragraph("").alignment(Alignment::Left);
987 p.add_run("TENSORBEE")
988 .bold(true)
989 .size(28.0)
990 .color("008B8B")
991 .font("Helvetica");
992 }
993 doc.add_paragraph("123 Innovation Drive, Suite 400")
994 .alignment(Alignment::Left);
995 doc.add_paragraph("San Francisco, CA 94105 | +1 (415) 555-0199")
996 .alignment(Alignment::Left);
997 doc.add_paragraph("")
998 .border_bottom(BorderStyle::Single, 8, "008B8B");
999
1000 // Quote meta
1001 doc.add_paragraph("");
1002 {
1003 let mut tbl = doc.add_table(4, 4).width_pct(100.0);
1004 tbl.cell(0, 0).unwrap().set_text("QUOTATION");
1005 tbl.cell(0, 0).unwrap().shading("008B8B").grid_span(2);
1006 tbl.cell(0, 2).unwrap().set_text("DATE");
1007 tbl.cell(0, 2).unwrap().shading("008B8B");
1008 tbl.cell(0, 3).unwrap().set_text("VALID UNTIL");
1009 tbl.cell(0, 3).unwrap().shading("008B8B");
1010 tbl.cell(1, 0).unwrap().set_text("Quote #:");
1011 tbl.cell(1, 1).unwrap().set_text("QT-2026-0147");
1012 tbl.cell(1, 2).unwrap().set_text("Feb 22, 2026");
1013 tbl.cell(1, 3).unwrap().set_text("Mar 22, 2026");
1014 tbl.cell(2, 0).unwrap().set_text("Prepared by:");
1015 tbl.cell(2, 1).unwrap().set_text("Walter White");
1016 tbl.cell(2, 2).unwrap().set_text("Payment:");
1017 tbl.cell(2, 3).unwrap().set_text("Net 30");
1018 tbl.cell(3, 0).unwrap().set_text("Customer:");
1019 tbl.cell(3, 1).unwrap().set_text("Meridian Dynamics LLC");
1020 tbl.cell(3, 2).unwrap().set_text("Currency:");
1021 tbl.cell(3, 3).unwrap().set_text("USD");
1022 }
1023
1024 doc.add_paragraph("");
1025
1026 // ── Bill of Materials ──
1027 {
1028 let mut p = doc.add_paragraph("");
1029 p.add_run("Bill of Materials")
1030 .bold(true)
1031 .size(16.0)
1032 .color("1A3C3C");
1033 }
1034 doc.add_paragraph("");
1035
1036 {
1037 let mut tbl = doc
1038 .add_table(10, 6)
1039 .borders(BorderStyle::Single, 2, "008B8B")
1040 .width_pct(100.0)
1041 .layout_fixed();
1042 // Headers
1043 let hdrs = [
1044 "#",
1045 "Part Number",
1046 "Description",
1047 "Qty",
1048 "Unit Price",
1049 "Total",
1050 ];
1051 for (c, h) in hdrs.iter().enumerate() {
1052 tbl.cell(0, c).unwrap().set_text(h);
1053 tbl.cell(0, c).unwrap().shading("008B8B");
1054 }
1055
1056 let items: Vec<(&str, &str, &str, &str, &str)> = vec![
1057 (
1058 "TB-GPU-A100",
1059 "NVIDIA A100 80GB GPU",
1060 "4",
1061 "$12,500.00",
1062 "$50,000.00",
1063 ),
1064 (
1065 "TB-SRV-R750",
1066 "Dell R750xa Server Chassis",
1067 "2",
1068 "$8,200.00",
1069 "$16,400.00",
1070 ),
1071 (
1072 "TB-RAM-256G",
1073 "256GB DDR5 ECC Memory Module",
1074 "8",
1075 "$890.00",
1076 "$7,120.00",
1077 ),
1078 (
1079 "TB-SSD-3840",
1080 "3.84TB NVMe U.2 SSD",
1081 "8",
1082 "$1,150.00",
1083 "$9,200.00",
1084 ),
1085 (
1086 "TB-NET-CX7",
1087 "ConnectX-7 200GbE NIC",
1088 "4",
1089 "$1,800.00",
1090 "$7,200.00",
1091 ),
1092 (
1093 "TB-SW-48P",
1094 "48-Port 100GbE Switch",
1095 "1",
1096 "$22,000.00",
1097 "$22,000.00",
1098 ),
1099 (
1100 "TB-CAB-RACK",
1101 "42U Server Rack + PDU",
1102 "1",
1103 "$4,500.00",
1104 "$4,500.00",
1105 ),
1106 (
1107 "TB-SVC-INST",
1108 "Installation & Configuration",
1109 "1",
1110 "$8,500.00",
1111 "$8,500.00",
1112 ),
1113 (
1114 "TB-SVC-SUPP",
1115 "3-Year Premium Support",
1116 "1",
1117 "$15,000.00",
1118 "$15,000.00",
1119 ),
1120 ];
1121
1122 for (i, (pn, desc, qty, unit, total)) in items.iter().enumerate() {
1123 let row = i + 1;
1124 tbl.cell(row, 0).unwrap().set_text(&format!("{}", i + 1));
1125 tbl.cell(row, 1).unwrap().set_text(pn);
1126 tbl.cell(row, 2).unwrap().set_text(desc);
1127 tbl.cell(row, 3).unwrap().set_text(qty);
1128 tbl.cell(row, 4).unwrap().set_text(unit);
1129 tbl.cell(row, 5).unwrap().set_text(total);
1130 if i % 2 == 0 {
1131 for c in 0..6 {
1132 tbl.cell(row, c).unwrap().shading("F0F8F8");
1133 }
1134 }
1135 }
1136 }
1137
1138 doc.add_paragraph("");
1139
1140 // ── Totals ──
1141 {
1142 let mut tbl = doc
1143 .add_table(4, 2)
1144 .borders(BorderStyle::Single, 2, "008B8B")
1145 .width(Length::inches(3.5))
1146 .alignment(Alignment::Right);
1147 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1148 tbl.cell(0, 1).unwrap().set_text("$139,920.00");
1149 tbl.cell(1, 0).unwrap().set_text("Shipping & Handling");
1150 tbl.cell(1, 1).unwrap().set_text("$2,500.00");
1151 tbl.cell(2, 0).unwrap().set_text("Tax (8.625%)");
1152 tbl.cell(2, 1).unwrap().set_text("$12,068.10");
1153 tbl.cell(3, 0).unwrap().set_text("TOTAL");
1154 tbl.cell(3, 0).unwrap().shading("E07020");
1155 tbl.cell(3, 1).unwrap().set_text("$154,488.10");
1156 tbl.cell(3, 1).unwrap().shading("E07020");
1157 }
1158
1159 doc.add_paragraph("");
1160
1161 // ── Terms & Conditions ──
1162 {
1163 let mut p = doc.add_paragraph("");
1164 p.add_run("Terms & Conditions")
1165 .bold(true)
1166 .size(14.0)
1167 .color("1A3C3C");
1168 }
1169 doc.add_numbered_list_item(
1170 "This quotation is valid for 30 calendar days from the date of issue.",
1171 0,
1172 );
1173 doc.add_numbered_list_item(
1174 "All prices are in USD and exclusive of applicable taxes unless stated.",
1175 0,
1176 );
1177 doc.add_numbered_list_item("Standard lead time is 4-6 weeks from PO receipt.", 0);
1178 doc.add_numbered_list_item(
1179 "Payment terms: Net 30 from invoice date. 2% discount for payment within 10 days.",
1180 0,
1181 );
1182 doc.add_numbered_list_item(
1183 "Warranty: 3-year manufacturer warranty on all hardware components.",
1184 0,
1185 );
1186 doc.add_numbered_list_item(
1187 "Returns subject to 15% restocking fee if initiated after 14 days.",
1188 0,
1189 );
1190
1191 doc.add_paragraph("");
1192 doc.add_paragraph("")
1193 .border_bottom(BorderStyle::Single, 4, "008B8B");
1194 doc.add_paragraph("");
1195 {
1196 let mut p = doc.add_paragraph("");
1197 p.add_run("Accepted by: ").bold(true);
1198 p.add_run("___________________________ Date: ___________");
1199 }
1200 {
1201 let mut p = doc.add_paragraph("");
1202 p.add_run("Print Name: ").bold(true);
1203 p.add_run("___________________________ Title: ___________");
1204 }
1205
1206 doc
1207}
1208
1209// =============================================================================
1210// 4. INVOICE — Crimson + charcoal scheme
1211// =============================================================================
1212fn generate_invoice(_samples_dir: &Path) -> Document {
1213 let mut doc = Document::new();
1214
1215 // Colors: Crimson #B22222, Charcoal #333333, Light #FAF0F0
1216 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1217 doc.set_margins(
1218 Length::inches(0.75),
1219 Length::inches(0.75),
1220 Length::inches(0.75),
1221 Length::inches(0.75),
1222 );
1223 doc.set_title("Invoice INV-2026-0392");
1224 doc.set_author("Walter White");
1225 doc.set_keywords("invoice, Tensorbee");
1226
1227 doc.set_footer("Tensorbee — Thank you for your business!");
1228
1229 // ── Company & Invoice Header ──
1230 {
1231 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1232 // Company name left, INVOICE right
1233 {
1234 let mut cell = tbl.cell(0, 0).unwrap();
1235 let mut p = cell.add_paragraph("");
1236 p.add_run("TENSORBEE")
1237 .bold(true)
1238 .size(32.0)
1239 .color("B22222")
1240 .font("Helvetica");
1241 }
1242 {
1243 let mut cell = tbl.cell(0, 1).unwrap();
1244 let mut p = cell.add_paragraph("");
1245 p.add_run("INVOICE").bold(true).size(32.0).color("333333");
1246 }
1247 tbl.cell(1, 0)
1248 .unwrap()
1249 .set_text("123 Innovation Drive, Suite 400");
1250 tbl.cell(1, 1).unwrap().set_text("Invoice #: INV-2026-0392");
1251 tbl.cell(2, 0).unwrap().set_text("San Francisco, CA 94105");
1252 tbl.cell(2, 1).unwrap().set_text("Date: February 22, 2026");
1253 tbl.cell(3, 0).unwrap().set_text("walter@tensorbee.com");
1254 tbl.cell(3, 1).unwrap().set_text("Due Date: March 24, 2026");
1255 }
1256
1257 doc.add_paragraph("")
1258 .border_bottom(BorderStyle::Thick, 8, "B22222");
1259 doc.add_paragraph("");
1260
1261 // ── Bill To / Ship To ──
1262 {
1263 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1264 tbl.cell(0, 0).unwrap().set_text("BILL TO");
1265 tbl.cell(0, 0).unwrap().shading("B22222");
1266 tbl.cell(0, 1).unwrap().set_text("SHIP TO");
1267 tbl.cell(0, 1).unwrap().shading("B22222");
1268 tbl.cell(1, 0).unwrap().set_text("Meridian Dynamics LLC");
1269 tbl.cell(1, 1).unwrap().set_text("Meridian Dynamics LLC");
1270 tbl.cell(2, 0).unwrap().set_text("456 Enterprise Blvd");
1271 tbl.cell(2, 1).unwrap().set_text("Attn: Server Room B");
1272 tbl.cell(3, 0).unwrap().set_text("Austin, TX 78701");
1273 tbl.cell(3, 1)
1274 .unwrap()
1275 .set_text("456 Enterprise Blvd, Austin, TX 78701");
1276 }
1277
1278 doc.add_paragraph("");
1279
1280 // ── Line Items ──
1281 {
1282 let mut tbl = doc
1283 .add_table(8, 5)
1284 .borders(BorderStyle::Single, 2, "B22222")
1285 .width_pct(100.0);
1286 let hdrs = ["Description", "Qty", "Unit Price", "Tax", "Amount"];
1287 for (c, h) in hdrs.iter().enumerate() {
1288 tbl.cell(0, c).unwrap().set_text(h);
1289 tbl.cell(0, c).unwrap().shading("B22222");
1290 }
1291 let items = [
1292 (
1293 "ML Infrastructure Setup — Phase 1",
1294 "1",
1295 "$45,000.00",
1296 "$3,881.25",
1297 "$48,881.25",
1298 ),
1299 (
1300 "Data Pipeline Development (160 hrs)",
1301 "160",
1302 "$225.00",
1303 "$3,105.00",
1304 "$39,105.00",
1305 ),
1306 (
1307 "GPU Cluster Configuration",
1308 "1",
1309 "$12,000.00",
1310 "$1,035.00",
1311 "$13,035.00",
1312 ),
1313 (
1314 "API Gateway Implementation",
1315 "1",
1316 "$18,500.00",
1317 "$1,595.63",
1318 "$20,095.63",
1319 ),
1320 (
1321 "Load Testing & QA (80 hrs)",
1322 "80",
1323 "$195.00",
1324 "$1,345.50",
1325 "$16,945.50",
1326 ),
1327 (
1328 "Documentation & Training",
1329 "1",
1330 "$8,000.00",
1331 "$690.00",
1332 "$8,690.00",
1333 ),
1334 (
1335 "Project Management (3 months)",
1336 "3",
1337 "$6,500.00",
1338 "$1,679.63",
1339 "$21,179.63",
1340 ),
1341 ];
1342 for (i, (desc, qty, unit, tax, amt)) in items.iter().enumerate() {
1343 let r = i + 1;
1344 tbl.cell(r, 0).unwrap().set_text(desc);
1345 tbl.cell(r, 1).unwrap().set_text(qty);
1346 tbl.cell(r, 2).unwrap().set_text(unit);
1347 tbl.cell(r, 3).unwrap().set_text(tax);
1348 tbl.cell(r, 4).unwrap().set_text(amt);
1349 if i % 2 == 0 {
1350 for c in 0..5 {
1351 tbl.cell(r, c).unwrap().shading("FAF0F0");
1352 }
1353 }
1354 }
1355 }
1356
1357 doc.add_paragraph("");
1358
1359 // ── Totals ──
1360 {
1361 let mut tbl = doc
1362 .add_table(4, 2)
1363 .borders(BorderStyle::Single, 2, "B22222")
1364 .width(Length::inches(3.0))
1365 .alignment(Alignment::Right);
1366 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1367 tbl.cell(0, 1).unwrap().set_text("$154,600.00");
1368 tbl.cell(1, 0).unwrap().set_text("Tax (8.625%)");
1369 tbl.cell(1, 1).unwrap().set_text("$13,334.25");
1370 tbl.cell(2, 0).unwrap().set_text("Discount (5%)");
1371 tbl.cell(2, 1).unwrap().set_text("-$7,730.00");
1372 tbl.cell(3, 0).unwrap().set_text("AMOUNT DUE");
1373 tbl.cell(3, 0).unwrap().shading("B22222");
1374 tbl.cell(3, 1).unwrap().set_text("$160,204.25");
1375 tbl.cell(3, 1).unwrap().shading("B22222");
1376 }
1377
1378 doc.add_paragraph("");
1379 doc.add_paragraph("");
1380
1381 // ── Payment Details ──
1382 {
1383 let mut p = doc.add_paragraph("");
1384 p.add_run("Payment Details")
1385 .bold(true)
1386 .size(14.0)
1387 .color("333333");
1388 }
1389 doc.add_paragraph("Bank: Silicon Valley Bank")
1390 .indent_left(Length::inches(0.3));
1391 doc.add_paragraph("Account: Tensorbee Inc. — 0847-2953-1120")
1392 .indent_left(Length::inches(0.3));
1393 doc.add_paragraph("Routing: 121140399")
1394 .indent_left(Length::inches(0.3));
1395 doc.add_paragraph("Swift: SVBKUS6S")
1396 .indent_left(Length::inches(0.3));
1397
1398 doc.add_paragraph("");
1399
1400 doc.add_paragraph("Please include invoice number INV-2026-0392 in the payment reference.")
1401 .shading("FAF0F0")
1402 .border_all(BorderStyle::Single, 2, "B22222");
1403
1404 doc
1405}
1406
1407// =============================================================================
1408// 5. REPORT — Forest green + earth tones, with images & hierarchical sections
1409// =============================================================================
1410fn generate_report(_samples_dir: &Path) -> Document {
1411 let mut doc = Document::new();
1412
1413 // Colors: Forest #2D5016, Sage #6B8E23, Earth #8B7355, Cream #FFFAF0
1414 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1415 doc.set_margins(
1416 Length::inches(1.0),
1417 Length::inches(1.0),
1418 Length::inches(1.0),
1419 Length::inches(1.0),
1420 );
1421 doc.set_title("Q4 2025 Environmental Impact Report");
1422 doc.set_author("Walter White");
1423 doc.set_subject("Quarterly Environmental Report");
1424 doc.set_keywords("environment, sustainability, report, Tensorbee");
1425
1426 doc.set_different_first_page(true);
1427 doc.set_first_page_header("");
1428 doc.set_header("Tensorbee — Q4 2025 Environmental Impact Report");
1429 doc.set_footer("CONFIDENTIAL — Page");
1430
1431 // ── Cover ──
1432 let cover_bg = create_sample_png(612, 792, [20, 50, 15]);
1433 doc.add_background_image(&cover_bg, "report_cover.png");
1434
1435 for _ in 0..5 {
1436 doc.add_paragraph("");
1437 }
1438 {
1439 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1440 p.add_run("Q4 2025")
1441 .bold(true)
1442 .size(48.0)
1443 .color("FFFFFF")
1444 .font("Georgia");
1445 }
1446 {
1447 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1448 p.add_run("Environmental Impact Report")
1449 .size(24.0)
1450 .color("90EE90")
1451 .font("Georgia")
1452 .italic(true);
1453 }
1454 doc.add_paragraph("");
1455 {
1456 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1457 p.add_run("Tensorbee — Sustainability Division")
1458 .size(14.0)
1459 .color("C0C0C0");
1460 }
1461 {
1462 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1463 p.add_run("Prepared by Walter White, Chief Sustainability Officer")
1464 .size(11.0)
1465 .color("AAAAAA");
1466 }
1467
1468 // ── TOC ──
1469 doc.add_paragraph("").page_break_before(true);
1470 doc.insert_toc(doc.content_count(), 3);
1471
1472 // ── Section 1: Executive Overview ──
1473 doc.add_paragraph("").page_break_before(true);
1474 doc.add_paragraph("Executive Overview").style("Heading1");
1475 doc.add_paragraph(
1476 "This report presents Tensorbee's environmental performance for Q4 2025. Our \
1477 sustainability initiatives have yielded a 34% reduction in carbon emissions compared \
1478 to Q4 2024, exceeding our target of 25%. Key achievements include the transition to \
1479 100% renewable energy in our primary data centers and the launch of our carbon offset \
1480 marketplace.",
1481 )
1482 .first_line_indent(Length::inches(0.3));
1483
1484 // Image: performance chart
1485 let chart_img = create_chart_png(400, 200);
1486 doc.add_paragraph("");
1487 doc.add_picture(
1488 &chart_img,
1489 "performance_chart.png",
1490 Length::inches(5.0),
1491 Length::inches(2.5),
1492 )
1493 .alignment(Alignment::Center);
1494 {
1495 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1496 p.add_run("Figure 1: Quarterly Carbon Emissions (tonnes CO2e)")
1497 .italic(true)
1498 .size(9.0)
1499 .color("666666");
1500 }
1501
1502 // ── Section 2: Energy Consumption ──
1503 doc.add_paragraph("").page_break_before(true);
1504 doc.add_paragraph("Energy Consumption").style("Heading1");
1505
1506 doc.add_paragraph("Data Center Operations")
1507 .style("Heading2");
1508 doc.add_paragraph(
1509 "Our three primary data centers consumed a combined 4.2 GWh in Q4 2025, \
1510 a 12% reduction from Q3 2025 driven by improved cooling efficiency and \
1511 server consolidation.",
1512 );
1513
1514 // Data table
1515 {
1516 let mut tbl = doc
1517 .add_table(5, 4)
1518 .borders(BorderStyle::Single, 4, "2D5016")
1519 .width_pct(100.0);
1520 let hdrs = ["Data Center", "Capacity (MW)", "Usage (GWh)", "PUE"];
1521 for (c, h) in hdrs.iter().enumerate() {
1522 tbl.cell(0, c).unwrap().set_text(h);
1523 tbl.cell(0, c).unwrap().shading("2D5016");
1524 }
1525 let rows = [
1526 ("San Francisco (Primary)", "3.2", "1.8", "1.12"),
1527 ("Dublin (EU)", "2.1", "1.2", "1.18"),
1528 ("Singapore (APAC)", "1.8", "1.2", "1.24"),
1529 ("TOTAL", "7.1", "4.2", "1.17 avg"),
1530 ];
1531 for (i, (dc, cap, use_, pue)) in rows.iter().enumerate() {
1532 tbl.cell(i + 1, 0).unwrap().set_text(dc);
1533 tbl.cell(i + 1, 1).unwrap().set_text(cap);
1534 tbl.cell(i + 1, 2).unwrap().set_text(use_);
1535 tbl.cell(i + 1, 3).unwrap().set_text(pue);
1536 if i == 3 {
1537 for c in 0..4 {
1538 tbl.cell(i + 1, c).unwrap().shading("E2EFDA");
1539 }
1540 }
1541 }
1542 }
1543
1544 doc.add_paragraph("");
1545
1546 doc.add_paragraph("Renewable Energy Mix").style("Heading2");
1547 doc.add_paragraph("Breakdown of energy sources across all facilities:");
1548
1549 doc.add_bullet_list_item("Solar PV: 42% (1.76 GWh)", 0);
1550 doc.add_bullet_list_item("Wind Power Purchase Agreements: 38% (1.60 GWh)", 0);
1551 doc.add_bullet_list_item("Hydroelectric: 12% (0.50 GWh)", 0);
1552 doc.add_bullet_list_item("Grid (non-renewable): 8% (0.34 GWh)", 0);
1553
1554 // ── Section 3: Water & Waste ──
1555 doc.add_paragraph("Water & Waste Management")
1556 .style("Heading1");
1557
1558 doc.add_paragraph("Water Usage").style("Heading2");
1559 doc.add_paragraph(
1560 "Total water consumption was 12.4 million gallons, a 15% reduction from Q3. \
1561 Our closed-loop cooling systems now recycle 78% of water used in cooling operations.",
1562 );
1563
1564 doc.add_paragraph("Waste Reduction").style("Heading2");
1565 doc.add_paragraph("E-waste management results for Q4:")
1566 .keep_with_next(true);
1567 {
1568 let mut tbl = doc
1569 .add_table(4, 3)
1570 .borders(BorderStyle::Single, 2, "6B8E23")
1571 .width_pct(80.0);
1572 tbl.cell(0, 0).unwrap().set_text("Category");
1573 tbl.cell(0, 0).unwrap().shading("6B8E23");
1574 tbl.cell(0, 1).unwrap().set_text("Weight (kg)");
1575 tbl.cell(0, 1).unwrap().shading("6B8E23");
1576 tbl.cell(0, 2).unwrap().set_text("Recycled %");
1577 tbl.cell(0, 2).unwrap().shading("6B8E23");
1578 let rows = [
1579 ("Server Hardware", "2,450", "94%"),
1580 ("Networking Equipment", "820", "91%"),
1581 ("Storage Media", "340", "99%"),
1582 ];
1583 for (i, (cat, wt, pct)) in rows.iter().enumerate() {
1584 tbl.cell(i + 1, 0).unwrap().set_text(cat);
1585 tbl.cell(i + 1, 1).unwrap().set_text(wt);
1586 tbl.cell(i + 1, 2).unwrap().set_text(pct);
1587 }
1588 }
1589
1590 // ── Section 4: Initiatives ──
1591 doc.add_paragraph("").page_break_before(true);
1592 doc.add_paragraph("Q1 2026 Initiatives").style("Heading1");
1593
1594 doc.add_paragraph("Planned Programs").style("Heading2");
1595 doc.add_numbered_list_item(
1596 "Deploy on-site battery storage at SF data center (2 MWh capacity)",
1597 0,
1598 );
1599 doc.add_numbered_list_item("Pilot immersion cooling in Dublin facility", 0);
1600 doc.add_numbered_list_item("Launch employee commute carbon offset program", 0);
1601 doc.add_numbered_list_item("Achieve ISO 14001 certification for Singapore facility", 0);
1602
1603 doc.add_paragraph("Investment Targets").style("Heading2");
1604 doc.add_paragraph("Sustainability CapEx allocation for FY2026:")
1605 .keep_with_next(true);
1606 {
1607 let mut tbl = doc
1608 .add_table(5, 2)
1609 .borders(BorderStyle::Single, 4, "2D5016");
1610 tbl.cell(0, 0).unwrap().set_text("Initiative");
1611 tbl.cell(0, 0).unwrap().shading("2D5016");
1612 tbl.cell(0, 1).unwrap().set_text("Budget");
1613 tbl.cell(0, 1).unwrap().shading("2D5016");
1614 let rows = [
1615 ("Battery Storage", "$1.2M"),
1616 ("Immersion Cooling Pilot", "$800K"),
1617 ("Solar Panel Expansion", "$2.1M"),
1618 ("Carbon Credits", "$500K"),
1619 ];
1620 for (i, (init, budget)) in rows.iter().enumerate() {
1621 tbl.cell(i + 1, 0).unwrap().set_text(init);
1622 tbl.cell(i + 1, 1).unwrap().set_text(budget);
1623 if i % 2 == 0 {
1624 tbl.cell(i + 1, 0).unwrap().shading("FFFAF0");
1625 tbl.cell(i + 1, 1).unwrap().shading("FFFAF0");
1626 }
1627 }
1628 }
1629
1630 // Image: sustainability roadmap
1631 let roadmap_img = create_sample_png(500, 100, [40, 80, 30]);
1632 doc.add_paragraph("");
1633 doc.add_picture(
1634 &roadmap_img,
1635 "roadmap.png",
1636 Length::inches(6.0),
1637 Length::inches(1.2),
1638 )
1639 .alignment(Alignment::Center);
1640 {
1641 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1642 p.add_run("Figure 2: 2026 Sustainability Roadmap")
1643 .italic(true)
1644 .size(9.0)
1645 .color("666666");
1646 }
1647
1648 doc.add_paragraph("");
1649 doc.add_paragraph(
1650 "For questions about this report, contact Walter White at sustainability@tensorbee.com.",
1651 )
1652 .shading("FFFAF0")
1653 .border_all(BorderStyle::Single, 2, "2D5016");
1654
1655 doc
1656}Set the first-page footer text.
Examples found in repository?
97fn generate_feature_showcase(_samples_dir: &Path) -> Document {
98 let mut doc = Document::new();
99
100 // ── Page Setup & Metadata ──
101 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
102 doc.set_margins(
103 Length::inches(1.0),
104 Length::inches(1.0),
105 Length::inches(1.0),
106 Length::inches(1.0),
107 );
108 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
109 doc.set_gutter(Length::twips(0));
110 doc.set_title("rdocx Feature Showcase");
111 doc.set_author("rdocx Sample Generator");
112 doc.set_subject("Comprehensive feature demonstration");
113 doc.set_keywords("rdocx, docx, rust, sample, showcase");
114
115 // Headers & Footers with different first page
116 doc.set_different_first_page(true);
117 doc.set_first_page_header("rdocx");
118 doc.set_first_page_footer("Feature Showcase — Cover Page");
119 doc.set_header("rdocx Feature Showcase");
120 doc.set_footer("Generated by rdocx");
121
122 // ── COVER PAGE ──
123 let bg = create_sample_png(612, 792, [20, 45, 90]);
124 doc.add_background_image(&bg, "cover_bg.png");
125
126 for _ in 0..3 {
127 doc.add_paragraph("");
128 }
129 {
130 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
131 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
132 }
133 {
134 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
135 p.add_run("Complete Feature Showcase")
136 .size(28.0)
137 .color("FFFFFF")
138 .italic(true);
139 }
140 doc.add_paragraph("");
141 {
142 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
143 p.add_run("Every feature of the rdocx Rust library")
144 .size(13.0)
145 .color("B0C4DE");
146 }
147 {
148 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
149 p.add_run("demonstrated in a single document.")
150 .size(13.0)
151 .color("B0C4DE");
152 }
153
154 // ── TABLE OF CONTENTS ──
155 doc.add_paragraph("").page_break_before(true);
156 doc.insert_toc(doc.content_count(), 3);
157
158 // ── SECTION 1: TEXT FORMATTING ──
159 doc.add_paragraph("").page_break_before(true);
160 doc.add_paragraph("1. Text Formatting").style("Heading1");
161
162 // Paragraph Alignment
163 doc.add_paragraph("1.1 Paragraph Alignment")
164 .style("Heading2");
165 doc.add_paragraph("Left-aligned paragraph (default).")
166 .alignment(Alignment::Left);
167 doc.add_paragraph("Center-aligned paragraph.")
168 .alignment(Alignment::Center);
169 doc.add_paragraph("Right-aligned paragraph.")
170 .alignment(Alignment::Right);
171 doc.add_paragraph(
172 "Justified paragraph — this text is long enough to demonstrate how justified alignment \
173 distributes extra space across word gaps so lines fill the full width of the text area.",
174 )
175 .alignment(Alignment::Justify);
176
177 doc.add_paragraph("");
178
179 // Run Formatting
180 doc.add_paragraph("1.2 Run Formatting").style("Heading2");
181 {
182 let mut p = doc.add_paragraph("");
183 p.add_run("Bold").bold(true);
184 p.add_run(" | ");
185 p.add_run("Italic").italic(true);
186 p.add_run(" | ");
187 p.add_run("Bold Italic").bold(true).italic(true);
188 p.add_run(" | ");
189 p.add_run("Underline").underline(true);
190 p.add_run(" | ");
191 p.add_run("Strikethrough").strike(true);
192 p.add_run(" | ");
193 p.add_run("Double Strike").double_strike(true);
194 }
195 {
196 let mut p = doc.add_paragraph("");
197 p.add_run("Red").color("FF0000");
198 p.add_run(" | ");
199 p.add_run("Blue").color("0000FF");
200 p.add_run(" | ");
201 p.add_run("Green").color("00AA00");
202 p.add_run(" | ");
203 p.add_run("Highlighted").highlight("FFFF00");
204 }
205 {
206 let mut p = doc.add_paragraph("");
207 p.add_run("8pt").size(8.0);
208 p.add_run(" | ");
209 p.add_run("11pt").size(11.0);
210 p.add_run(" | ");
211 p.add_run("16pt").size(16.0);
212 p.add_run(" | ");
213 p.add_run("24pt").size(24.0);
214 }
215 {
216 let mut p = doc.add_paragraph("");
217 p.add_run("Arial").font("Arial");
218 p.add_run(" | ");
219 p.add_run("Times New Roman").font("Times New Roman");
220 p.add_run(" | ");
221 p.add_run("Courier New").font("Courier New");
222 }
223 {
224 let mut p = doc.add_paragraph("");
225 p.add_run("H");
226 p.add_run("2").subscript();
227 p.add_run("O (subscript) | E=mc");
228 p.add_run("2").superscript();
229 p.add_run(" (superscript)");
230 }
231 {
232 let mut p = doc.add_paragraph("");
233 p.add_run("ALL CAPS").all_caps(true);
234 p.add_run(" | ");
235 p.add_run("Small Caps").small_caps(true);
236 p.add_run(" | ");
237 p.add_run("Expanded").character_spacing(Length::twips(40));
238 p.add_run(" | ");
239 p.add_run("Hidden text (not visible)").hidden(true);
240 }
241
242 doc.add_paragraph("");
243
244 // Underline Styles
245 doc.add_paragraph("1.3 Underline Styles").style("Heading2");
246 {
247 let mut p = doc.add_paragraph("");
248 p.add_run("Single ")
249 .underline_style(rdocx::UnderlineStyle::Single);
250 p.add_run("Double ")
251 .underline_style(rdocx::UnderlineStyle::Double);
252 p.add_run("Thick ")
253 .underline_style(rdocx::UnderlineStyle::Thick);
254 p.add_run("Dotted ")
255 .underline_style(rdocx::UnderlineStyle::Dotted);
256 p.add_run("Dash ")
257 .underline_style(rdocx::UnderlineStyle::Dash);
258 p.add_run("Wave ")
259 .underline_style(rdocx::UnderlineStyle::Wave);
260 }
261
262 // ── SECTION 2: PARAGRAPH FORMATTING ──
263 doc.add_paragraph("").page_break_before(true);
264 doc.add_paragraph("2. Paragraph Formatting")
265 .style("Heading1");
266
267 doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
268 doc.add_paragraph("Paragraph with light green shading")
269 .shading("E2EFDA");
270 doc.add_paragraph("Paragraph with bottom border")
271 .border_bottom(BorderStyle::Single, 6, "2E75B6");
272 doc.add_paragraph("Paragraph with all borders (red)")
273 .border_all(BorderStyle::Single, 4, "FF0000");
274
275 doc.add_paragraph("2.2 Indentation").style("Heading2");
276 doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
277 .indent_left(Length::inches(1.0))
278 .hanging_indent(Length::inches(0.5));
279 doc.add_paragraph("0.5-inch first-line indent on this paragraph")
280 .first_line_indent(Length::inches(0.5));
281 doc.add_paragraph("Both left and right indent (0.75 inches each)")
282 .indent_left(Length::inches(0.75))
283 .indent_right(Length::inches(0.75));
284
285 doc.add_paragraph("2.3 Spacing & Line Height")
286 .style("Heading2");
287 doc.add_paragraph("Extra space before (24pt) and after (12pt)")
288 .space_before(Length::pt(24.0))
289 .space_after(Length::pt(12.0));
290 doc.add_paragraph(
291 "Double line spacing paragraph. This text should have extra vertical space between \
292 lines to demonstrate line_spacing_multiple(2.0).",
293 )
294 .line_spacing_multiple(2.0);
295 doc.add_paragraph("Exact 20pt line spacing (fixed height).")
296 .line_spacing(20.0);
297
298 doc.add_paragraph("2.4 Pagination Controls")
299 .style("Heading2");
300 doc.add_paragraph("keep_with_next — this paragraph stays with the next")
301 .keep_with_next(true);
302 doc.add_paragraph("(This paragraph was kept with the one above.)");
303 doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
304 .keep_together(true);
305 doc.add_paragraph("widow_control — prevents widow/orphan lines")
306 .widow_control(true);
307
308 // ── SECTION 3: LISTS ──
309 doc.add_paragraph("").page_break_before(true);
310 doc.add_paragraph("3. Lists").style("Heading1");
311
312 doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
313 doc.add_bullet_list_item("First item", 0);
314 doc.add_bullet_list_item("Second item", 0);
315 doc.add_bullet_list_item("Nested level 1", 1);
316 doc.add_bullet_list_item("Nested level 2", 2);
317 doc.add_bullet_list_item("Back to level 1", 1);
318 doc.add_bullet_list_item("Third item", 0);
319
320 doc.add_paragraph("");
321
322 doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
323 doc.add_numbered_list_item("First numbered item", 0);
324 doc.add_numbered_list_item("Second numbered item", 0);
325 doc.add_numbered_list_item("Sub-item A", 1);
326 doc.add_numbered_list_item("Sub-item B", 1);
327 doc.add_numbered_list_item("Third numbered item", 0);
328
329 // ── SECTION 4: TAB STOPS ──
330 doc.add_paragraph("");
331 doc.add_paragraph("4. Tab Stops").style("Heading1");
332
333 doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
334 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
335 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
336 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
337 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
338 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
339
340 doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
341 doc.add_paragraph("Item\t\tPrice")
342 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
343 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
344 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
345 doc.add_paragraph("Widget\t\t$19.99")
346 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
347 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
348 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
349 doc.add_paragraph("Gadget\t\t$249.50")
350 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
351 .add_tab_stop_with_leader(
352 TabAlignment::Right,
353 Length::inches(4.0),
354 TabLeader::Underscore,
355 )
356 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
357
358 // ── SECTION 5: TABLES ──
359 doc.add_paragraph("").page_break_before(true);
360 doc.add_paragraph("5. Tables").style("Heading1");
361
362 doc.add_paragraph("5.1 Basic Table").style("Heading2");
363 {
364 let mut tbl = doc
365 .add_table(4, 3)
366 .borders(BorderStyle::Single, 4, "000000");
367 for col in 0..3 {
368 tbl.cell(0, col).unwrap().shading("2E75B6");
369 }
370 tbl.cell(0, 0).unwrap().set_text("Name");
371 tbl.cell(0, 1).unwrap().set_text("Role");
372 tbl.cell(0, 2).unwrap().set_text("Location");
373 tbl.cell(1, 0).unwrap().set_text("Walter White");
374 tbl.cell(1, 1).unwrap().set_text("CEO");
375 tbl.cell(1, 2).unwrap().set_text("Albuquerque");
376 tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
377 tbl.cell(2, 1).unwrap().set_text("CTO");
378 tbl.cell(2, 2).unwrap().set_text("Remote");
379 tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
380 tbl.cell(3, 1).unwrap().set_text("Security");
381 tbl.cell(3, 2).unwrap().set_text("Washington");
382 }
383
384 doc.add_paragraph("");
385
386 doc.add_paragraph("5.2 Column Span & Cell Shading")
387 .style("Heading2");
388 {
389 let mut tbl = doc
390 .add_table(3, 4)
391 .borders(BorderStyle::Single, 4, "000000")
392 .width_pct(100.0);
393 tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
394 tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
395 tbl.cell(1, 0).unwrap().set_text("Region");
396 tbl.cell(1, 0).unwrap().shading("D6E4F0");
397 tbl.cell(1, 1).unwrap().set_text("Q1");
398 tbl.cell(1, 1).unwrap().shading("D6E4F0");
399 tbl.cell(1, 2).unwrap().set_text("Q2");
400 tbl.cell(1, 2).unwrap().shading("D6E4F0");
401 tbl.cell(1, 3).unwrap().set_text("Total");
402 tbl.cell(1, 3).unwrap().shading("D6E4F0");
403 tbl.cell(2, 0).unwrap().set_text("Americas");
404 tbl.cell(2, 1).unwrap().set_text("$2.4M");
405 tbl.cell(2, 2).unwrap().set_text("$2.7M");
406 tbl.cell(2, 3).unwrap().set_text("$5.1M");
407 }
408
409 doc.add_paragraph("");
410
411 doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
412 {
413 let mut tbl = doc
414 .add_table(4, 3)
415 .borders(BorderStyle::Single, 4, "000000");
416 tbl.cell(0, 0).unwrap().set_text("Category");
417 tbl.cell(0, 0).unwrap().shading("E2EFDA");
418 tbl.cell(0, 1).unwrap().set_text("Item");
419 tbl.cell(0, 1).unwrap().shading("E2EFDA");
420 tbl.cell(0, 2).unwrap().set_text("Price");
421 tbl.cell(0, 2).unwrap().shading("E2EFDA");
422 tbl.cell(1, 0).unwrap().set_text("Hardware");
423 tbl.cell(1, 0).unwrap().v_merge_restart();
424 tbl.cell(1, 1).unwrap().set_text("Laptop");
425 tbl.cell(1, 2).unwrap().set_text("$1,200");
426 tbl.cell(2, 0).unwrap().v_merge_continue();
427 tbl.cell(2, 1).unwrap().set_text("Monitor");
428 tbl.cell(2, 2).unwrap().set_text("$450");
429 tbl.cell(3, 0).unwrap().set_text("Software");
430 tbl.cell(3, 1).unwrap().set_text("IDE License");
431 tbl.cell(3, 2).unwrap().set_text("$200/yr");
432 }
433
434 doc.add_paragraph("");
435
436 doc.add_paragraph("5.4 Nested Table").style("Heading2");
437 {
438 let mut tbl = doc
439 .add_table(2, 2)
440 .borders(BorderStyle::Single, 6, "2E75B6");
441 tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
442 tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
443 tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
444 {
445 let mut cell = tbl.cell(1, 1).unwrap();
446 cell.set_text("Nested table below:");
447 let mut nested = cell
448 .add_table(2, 2)
449 .borders(BorderStyle::Single, 2, "FF6600");
450 nested.cell(0, 0).unwrap().set_text("A");
451 nested.cell(0, 1).unwrap().set_text("B");
452 nested.cell(1, 0).unwrap().set_text("C");
453 nested.cell(1, 1).unwrap().set_text("D");
454 }
455 }
456
457 doc.add_paragraph("");
458
459 doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
460 .style("Heading2");
461 {
462 let mut tbl = doc
463 .add_table(2, 3)
464 .borders(BorderStyle::Single, 4, "666666");
465 tbl.row(0).unwrap().height(Length::pt(50.0)).header();
466 tbl.cell(0, 0).unwrap().set_text("Top");
467 tbl.cell(0, 0)
468 .unwrap()
469 .vertical_alignment(VerticalAlignment::Top)
470 .shading("FFF2CC");
471 tbl.cell(0, 1).unwrap().set_text("Center");
472 tbl.cell(0, 1)
473 .unwrap()
474 .vertical_alignment(VerticalAlignment::Center)
475 .shading("D9E2F3");
476 tbl.cell(0, 2).unwrap().set_text("Bottom");
477 tbl.cell(0, 2)
478 .unwrap()
479 .vertical_alignment(VerticalAlignment::Bottom)
480 .shading("E2EFDA");
481 tbl.row(1).unwrap().cant_split();
482 tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
483 tbl.cell(1, 0).unwrap().no_wrap();
484 tbl.cell(1, 1).unwrap().set_text("Fixed width");
485 tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
486 tbl.cell(1, 2).unwrap().set_text("Normal");
487 }
488
489 // ── SECTION 6: IMAGES ──
490 doc.add_paragraph("").page_break_before(true);
491 doc.add_paragraph("6. Images").style("Heading1");
492
493 doc.add_paragraph("6.1 Inline Image").style("Heading2");
494 doc.add_paragraph("A blue gradient image below:");
495 let img = create_sample_png(200, 50, [0, 80, 200]);
496 doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
497
498 doc.add_paragraph("");
499 doc.add_paragraph("6.2 Header Image").style("Heading2");
500 let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
501 doc.set_header_image(
502 &hdr_img,
503 "header_logo.png",
504 Length::inches(2.0),
505 Length::inches(0.2),
506 );
507 doc.add_paragraph("The header now contains an inline image (check the top of this page).");
508
509 doc.add_paragraph("");
510 doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
511
512 // ── SECTION 7: CONTENT MANIPULATION ──
513 doc.add_paragraph("").page_break_before(true);
514 doc.add_paragraph("7. Content Manipulation")
515 .style("Heading1");
516
517 doc.add_paragraph("7.1 Placeholder Replacement")
518 .style("Heading2");
519 doc.add_paragraph("Customer: {{customer}}");
520 doc.add_paragraph("Date: {{date}}");
521 doc.add_paragraph("Reference: {{ref_number}}");
522 {
523 let mut tbl = doc
524 .add_table(2, 2)
525 .borders(BorderStyle::Single, 4, "000000");
526 tbl.cell(0, 0).unwrap().set_text("Project");
527 tbl.cell(0, 0).unwrap().shading("D6E4F0");
528 tbl.cell(0, 1).unwrap().set_text("{{project}}");
529 tbl.cell(1, 0).unwrap().set_text("Status");
530 tbl.cell(1, 0).unwrap().shading("D6E4F0");
531 tbl.cell(1, 1).unwrap().set_text("{{status}}");
532 }
533
534 let mut replacements = HashMap::new();
535 replacements.insert("{{customer}}", "Tensorbee Inc.");
536 replacements.insert("{{date}}", "February 22, 2026");
537 replacements.insert("{{ref_number}}", "TB-2026-001");
538 replacements.insert("{{project}}", "Infrastructure Upgrade");
539 replacements.insert("{{status}}", "In Progress");
540 let n = doc.replace_all(&replacements);
541 doc.add_paragraph(&format!("({n} placeholders replaced above)"));
542
543 doc.add_paragraph("");
544
545 doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
546 doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
547 let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
548 doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
549
550 doc.add_paragraph("");
551
552 doc.add_paragraph("7.3 Content Insertion").style("Heading2");
553 doc.add_paragraph("Section A: First content.");
554 doc.add_paragraph("Section C: Third content.");
555 if let Some(idx) = doc.find_content_index("Section C") {
556 doc.insert_paragraph(
557 idx,
558 "Section B: Inserted between A and C via find_content_index().",
559 );
560 }
561
562 // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
563 doc.add_paragraph("").section_break(SectionBreak::NextPage);
564 doc.add_paragraph("8. Section Breaks & Orientation")
565 .style("Heading1");
566 doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
567
568 {
569 let mut tbl = doc
570 .add_table(3, 7)
571 .borders(BorderStyle::Single, 4, "2E75B6");
572 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
573 for (c, h) in headers.iter().enumerate() {
574 tbl.cell(0, c).unwrap().set_text(h);
575 tbl.cell(0, c).unwrap().shading("2E75B6");
576 }
577 let data = [
578 [
579 "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
580 ],
581 [
582 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
583 ],
584 ];
585 for (r, row) in data.iter().enumerate() {
586 for (c, v) in row.iter().enumerate() {
587 tbl.cell(r + 1, c).unwrap().set_text(v);
588 }
589 }
590 }
591
592 doc.add_paragraph("")
593 .section_break(SectionBreak::NextPage)
594 .section_landscape();
595
596 // ── SECTION 9: CUSTOM STYLES ──
597 doc.add_paragraph("9. Custom Styles").style("Heading1");
598 doc.add_style(
599 StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
600 .based_on("Normal")
601 .paragraph_properties(rdocx_oxml::properties::CT_PPr {
602 shading: Some(rdocx_oxml::properties::CT_Shd {
603 val: "clear".to_string(),
604 color: None,
605 fill: Some("FFF2CC".to_string()),
606 }),
607 ..Default::default()
608 })
609 .run_properties(rdocx_oxml::properties::CT_RPr {
610 bold: Some(true),
611 color: Some("C45911".to_string()),
612 ..Default::default()
613 }),
614 );
615 doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
616 .style("CustomHighlight");
617
618 doc.add_paragraph("");
619
620 // ── SECTION 10: DOCUMENT INTELLIGENCE ──
621 doc.add_paragraph("10. Document Intelligence API")
622 .style("Heading1");
623
624 let wc = doc.word_count();
625 let hc = doc.headings().len();
626 let ic = doc.images().len();
627 let lc = doc.links().len();
628 doc.add_paragraph(&format!("Word count: {wc}"));
629 doc.add_paragraph(&format!("Heading count: {hc}"));
630 doc.add_paragraph(&format!("Image count: {ic}"));
631 doc.add_paragraph(&format!("Link count: {lc}"));
632
633 let outline = doc.document_outline();
634 doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
635
636 let issues = doc.audit_accessibility();
637 doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
638 for issue in &issues {
639 doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
640 }
641
642 // ── SECTION 11: DOCUMENT MERGING ──
643 doc.add_paragraph("");
644 doc.add_paragraph("11. Document Merging").style("Heading1");
645 {
646 let mut other = Document::new();
647 other.add_paragraph("This paragraph was merged from another document using append().");
648 doc.append(&other);
649 }
650
651 doc.add_paragraph("");
652
653 // ── SUMMARY ──
654 doc.add_paragraph("Summary of Demonstrated Features")
655 .style("Heading1");
656 let features = [
657 "Page setup: size, margins, header/footer distance, gutter",
658 "Document metadata: title, author, subject, keywords",
659 "Headers and footers: text, images, different first page",
660 "Background images (full-page behind text)",
661 "Table of Contents generation with bookmarks",
662 "Text formatting: bold, italic, underline styles, strike, color, size, font",
663 "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
664 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
665 "Pagination controls: keep-with-next, keep-together, widow control",
666 "Bullet and numbered lists with nesting",
667 "Tab stops with dot/underscore/hyphen leaders",
668 "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
669 "Row properties: header rows, exact height, cant-split",
670 "Cell properties: width, no-wrap, vertical alignment",
671 "Inline images and header images",
672 "Placeholder replacement (single and batch)",
673 "Regex-based find and replace",
674 "Content insertion at specific positions",
675 "Section breaks with mixed portrait/landscape",
676 "Custom paragraph and character styles",
677 "Document intelligence: word count, headings, outline, images, links",
678 "Accessibility audit",
679 "Document merging (append)",
680 "Export: DOCX, PDF, HTML, Markdown",
681 ];
682 for f in &features {
683 doc.add_bullet_list_item(f, 0);
684 }
685 doc.add_paragraph("");
686 doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
687 .alignment(Alignment::Center)
688 .shading("E2EFDA")
689 .border_all(BorderStyle::Single, 2, "00AA00");
690
691 doc
692}Sourcepub fn header_text(&self) -> Option<String>
pub fn header_text(&self) -> Option<String>
Get the default header text, if set.
Get the default footer text, if set.
Sourcepub fn set_header_image(
&mut self,
image_data: &[u8],
image_filename: &str,
width: Length,
height: Length,
)
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?
97fn generate_feature_showcase(_samples_dir: &Path) -> Document {
98 let mut doc = Document::new();
99
100 // ── Page Setup & Metadata ──
101 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
102 doc.set_margins(
103 Length::inches(1.0),
104 Length::inches(1.0),
105 Length::inches(1.0),
106 Length::inches(1.0),
107 );
108 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
109 doc.set_gutter(Length::twips(0));
110 doc.set_title("rdocx Feature Showcase");
111 doc.set_author("rdocx Sample Generator");
112 doc.set_subject("Comprehensive feature demonstration");
113 doc.set_keywords("rdocx, docx, rust, sample, showcase");
114
115 // Headers & Footers with different first page
116 doc.set_different_first_page(true);
117 doc.set_first_page_header("rdocx");
118 doc.set_first_page_footer("Feature Showcase — Cover Page");
119 doc.set_header("rdocx Feature Showcase");
120 doc.set_footer("Generated by rdocx");
121
122 // ── COVER PAGE ──
123 let bg = create_sample_png(612, 792, [20, 45, 90]);
124 doc.add_background_image(&bg, "cover_bg.png");
125
126 for _ in 0..3 {
127 doc.add_paragraph("");
128 }
129 {
130 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
131 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
132 }
133 {
134 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
135 p.add_run("Complete Feature Showcase")
136 .size(28.0)
137 .color("FFFFFF")
138 .italic(true);
139 }
140 doc.add_paragraph("");
141 {
142 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
143 p.add_run("Every feature of the rdocx Rust library")
144 .size(13.0)
145 .color("B0C4DE");
146 }
147 {
148 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
149 p.add_run("demonstrated in a single document.")
150 .size(13.0)
151 .color("B0C4DE");
152 }
153
154 // ── TABLE OF CONTENTS ──
155 doc.add_paragraph("").page_break_before(true);
156 doc.insert_toc(doc.content_count(), 3);
157
158 // ── SECTION 1: TEXT FORMATTING ──
159 doc.add_paragraph("").page_break_before(true);
160 doc.add_paragraph("1. Text Formatting").style("Heading1");
161
162 // Paragraph Alignment
163 doc.add_paragraph("1.1 Paragraph Alignment")
164 .style("Heading2");
165 doc.add_paragraph("Left-aligned paragraph (default).")
166 .alignment(Alignment::Left);
167 doc.add_paragraph("Center-aligned paragraph.")
168 .alignment(Alignment::Center);
169 doc.add_paragraph("Right-aligned paragraph.")
170 .alignment(Alignment::Right);
171 doc.add_paragraph(
172 "Justified paragraph — this text is long enough to demonstrate how justified alignment \
173 distributes extra space across word gaps so lines fill the full width of the text area.",
174 )
175 .alignment(Alignment::Justify);
176
177 doc.add_paragraph("");
178
179 // Run Formatting
180 doc.add_paragraph("1.2 Run Formatting").style("Heading2");
181 {
182 let mut p = doc.add_paragraph("");
183 p.add_run("Bold").bold(true);
184 p.add_run(" | ");
185 p.add_run("Italic").italic(true);
186 p.add_run(" | ");
187 p.add_run("Bold Italic").bold(true).italic(true);
188 p.add_run(" | ");
189 p.add_run("Underline").underline(true);
190 p.add_run(" | ");
191 p.add_run("Strikethrough").strike(true);
192 p.add_run(" | ");
193 p.add_run("Double Strike").double_strike(true);
194 }
195 {
196 let mut p = doc.add_paragraph("");
197 p.add_run("Red").color("FF0000");
198 p.add_run(" | ");
199 p.add_run("Blue").color("0000FF");
200 p.add_run(" | ");
201 p.add_run("Green").color("00AA00");
202 p.add_run(" | ");
203 p.add_run("Highlighted").highlight("FFFF00");
204 }
205 {
206 let mut p = doc.add_paragraph("");
207 p.add_run("8pt").size(8.0);
208 p.add_run(" | ");
209 p.add_run("11pt").size(11.0);
210 p.add_run(" | ");
211 p.add_run("16pt").size(16.0);
212 p.add_run(" | ");
213 p.add_run("24pt").size(24.0);
214 }
215 {
216 let mut p = doc.add_paragraph("");
217 p.add_run("Arial").font("Arial");
218 p.add_run(" | ");
219 p.add_run("Times New Roman").font("Times New Roman");
220 p.add_run(" | ");
221 p.add_run("Courier New").font("Courier New");
222 }
223 {
224 let mut p = doc.add_paragraph("");
225 p.add_run("H");
226 p.add_run("2").subscript();
227 p.add_run("O (subscript) | E=mc");
228 p.add_run("2").superscript();
229 p.add_run(" (superscript)");
230 }
231 {
232 let mut p = doc.add_paragraph("");
233 p.add_run("ALL CAPS").all_caps(true);
234 p.add_run(" | ");
235 p.add_run("Small Caps").small_caps(true);
236 p.add_run(" | ");
237 p.add_run("Expanded").character_spacing(Length::twips(40));
238 p.add_run(" | ");
239 p.add_run("Hidden text (not visible)").hidden(true);
240 }
241
242 doc.add_paragraph("");
243
244 // Underline Styles
245 doc.add_paragraph("1.3 Underline Styles").style("Heading2");
246 {
247 let mut p = doc.add_paragraph("");
248 p.add_run("Single ")
249 .underline_style(rdocx::UnderlineStyle::Single);
250 p.add_run("Double ")
251 .underline_style(rdocx::UnderlineStyle::Double);
252 p.add_run("Thick ")
253 .underline_style(rdocx::UnderlineStyle::Thick);
254 p.add_run("Dotted ")
255 .underline_style(rdocx::UnderlineStyle::Dotted);
256 p.add_run("Dash ")
257 .underline_style(rdocx::UnderlineStyle::Dash);
258 p.add_run("Wave ")
259 .underline_style(rdocx::UnderlineStyle::Wave);
260 }
261
262 // ── SECTION 2: PARAGRAPH FORMATTING ──
263 doc.add_paragraph("").page_break_before(true);
264 doc.add_paragraph("2. Paragraph Formatting")
265 .style("Heading1");
266
267 doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
268 doc.add_paragraph("Paragraph with light green shading")
269 .shading("E2EFDA");
270 doc.add_paragraph("Paragraph with bottom border")
271 .border_bottom(BorderStyle::Single, 6, "2E75B6");
272 doc.add_paragraph("Paragraph with all borders (red)")
273 .border_all(BorderStyle::Single, 4, "FF0000");
274
275 doc.add_paragraph("2.2 Indentation").style("Heading2");
276 doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
277 .indent_left(Length::inches(1.0))
278 .hanging_indent(Length::inches(0.5));
279 doc.add_paragraph("0.5-inch first-line indent on this paragraph")
280 .first_line_indent(Length::inches(0.5));
281 doc.add_paragraph("Both left and right indent (0.75 inches each)")
282 .indent_left(Length::inches(0.75))
283 .indent_right(Length::inches(0.75));
284
285 doc.add_paragraph("2.3 Spacing & Line Height")
286 .style("Heading2");
287 doc.add_paragraph("Extra space before (24pt) and after (12pt)")
288 .space_before(Length::pt(24.0))
289 .space_after(Length::pt(12.0));
290 doc.add_paragraph(
291 "Double line spacing paragraph. This text should have extra vertical space between \
292 lines to demonstrate line_spacing_multiple(2.0).",
293 )
294 .line_spacing_multiple(2.0);
295 doc.add_paragraph("Exact 20pt line spacing (fixed height).")
296 .line_spacing(20.0);
297
298 doc.add_paragraph("2.4 Pagination Controls")
299 .style("Heading2");
300 doc.add_paragraph("keep_with_next — this paragraph stays with the next")
301 .keep_with_next(true);
302 doc.add_paragraph("(This paragraph was kept with the one above.)");
303 doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
304 .keep_together(true);
305 doc.add_paragraph("widow_control — prevents widow/orphan lines")
306 .widow_control(true);
307
308 // ── SECTION 3: LISTS ──
309 doc.add_paragraph("").page_break_before(true);
310 doc.add_paragraph("3. Lists").style("Heading1");
311
312 doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
313 doc.add_bullet_list_item("First item", 0);
314 doc.add_bullet_list_item("Second item", 0);
315 doc.add_bullet_list_item("Nested level 1", 1);
316 doc.add_bullet_list_item("Nested level 2", 2);
317 doc.add_bullet_list_item("Back to level 1", 1);
318 doc.add_bullet_list_item("Third item", 0);
319
320 doc.add_paragraph("");
321
322 doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
323 doc.add_numbered_list_item("First numbered item", 0);
324 doc.add_numbered_list_item("Second numbered item", 0);
325 doc.add_numbered_list_item("Sub-item A", 1);
326 doc.add_numbered_list_item("Sub-item B", 1);
327 doc.add_numbered_list_item("Third numbered item", 0);
328
329 // ── SECTION 4: TAB STOPS ──
330 doc.add_paragraph("");
331 doc.add_paragraph("4. Tab Stops").style("Heading1");
332
333 doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
334 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
335 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
336 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
337 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
338 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
339
340 doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
341 doc.add_paragraph("Item\t\tPrice")
342 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
343 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
344 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
345 doc.add_paragraph("Widget\t\t$19.99")
346 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
347 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
348 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
349 doc.add_paragraph("Gadget\t\t$249.50")
350 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
351 .add_tab_stop_with_leader(
352 TabAlignment::Right,
353 Length::inches(4.0),
354 TabLeader::Underscore,
355 )
356 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
357
358 // ── SECTION 5: TABLES ──
359 doc.add_paragraph("").page_break_before(true);
360 doc.add_paragraph("5. Tables").style("Heading1");
361
362 doc.add_paragraph("5.1 Basic Table").style("Heading2");
363 {
364 let mut tbl = doc
365 .add_table(4, 3)
366 .borders(BorderStyle::Single, 4, "000000");
367 for col in 0..3 {
368 tbl.cell(0, col).unwrap().shading("2E75B6");
369 }
370 tbl.cell(0, 0).unwrap().set_text("Name");
371 tbl.cell(0, 1).unwrap().set_text("Role");
372 tbl.cell(0, 2).unwrap().set_text("Location");
373 tbl.cell(1, 0).unwrap().set_text("Walter White");
374 tbl.cell(1, 1).unwrap().set_text("CEO");
375 tbl.cell(1, 2).unwrap().set_text("Albuquerque");
376 tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
377 tbl.cell(2, 1).unwrap().set_text("CTO");
378 tbl.cell(2, 2).unwrap().set_text("Remote");
379 tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
380 tbl.cell(3, 1).unwrap().set_text("Security");
381 tbl.cell(3, 2).unwrap().set_text("Washington");
382 }
383
384 doc.add_paragraph("");
385
386 doc.add_paragraph("5.2 Column Span & Cell Shading")
387 .style("Heading2");
388 {
389 let mut tbl = doc
390 .add_table(3, 4)
391 .borders(BorderStyle::Single, 4, "000000")
392 .width_pct(100.0);
393 tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
394 tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
395 tbl.cell(1, 0).unwrap().set_text("Region");
396 tbl.cell(1, 0).unwrap().shading("D6E4F0");
397 tbl.cell(1, 1).unwrap().set_text("Q1");
398 tbl.cell(1, 1).unwrap().shading("D6E4F0");
399 tbl.cell(1, 2).unwrap().set_text("Q2");
400 tbl.cell(1, 2).unwrap().shading("D6E4F0");
401 tbl.cell(1, 3).unwrap().set_text("Total");
402 tbl.cell(1, 3).unwrap().shading("D6E4F0");
403 tbl.cell(2, 0).unwrap().set_text("Americas");
404 tbl.cell(2, 1).unwrap().set_text("$2.4M");
405 tbl.cell(2, 2).unwrap().set_text("$2.7M");
406 tbl.cell(2, 3).unwrap().set_text("$5.1M");
407 }
408
409 doc.add_paragraph("");
410
411 doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
412 {
413 let mut tbl = doc
414 .add_table(4, 3)
415 .borders(BorderStyle::Single, 4, "000000");
416 tbl.cell(0, 0).unwrap().set_text("Category");
417 tbl.cell(0, 0).unwrap().shading("E2EFDA");
418 tbl.cell(0, 1).unwrap().set_text("Item");
419 tbl.cell(0, 1).unwrap().shading("E2EFDA");
420 tbl.cell(0, 2).unwrap().set_text("Price");
421 tbl.cell(0, 2).unwrap().shading("E2EFDA");
422 tbl.cell(1, 0).unwrap().set_text("Hardware");
423 tbl.cell(1, 0).unwrap().v_merge_restart();
424 tbl.cell(1, 1).unwrap().set_text("Laptop");
425 tbl.cell(1, 2).unwrap().set_text("$1,200");
426 tbl.cell(2, 0).unwrap().v_merge_continue();
427 tbl.cell(2, 1).unwrap().set_text("Monitor");
428 tbl.cell(2, 2).unwrap().set_text("$450");
429 tbl.cell(3, 0).unwrap().set_text("Software");
430 tbl.cell(3, 1).unwrap().set_text("IDE License");
431 tbl.cell(3, 2).unwrap().set_text("$200/yr");
432 }
433
434 doc.add_paragraph("");
435
436 doc.add_paragraph("5.4 Nested Table").style("Heading2");
437 {
438 let mut tbl = doc
439 .add_table(2, 2)
440 .borders(BorderStyle::Single, 6, "2E75B6");
441 tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
442 tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
443 tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
444 {
445 let mut cell = tbl.cell(1, 1).unwrap();
446 cell.set_text("Nested table below:");
447 let mut nested = cell
448 .add_table(2, 2)
449 .borders(BorderStyle::Single, 2, "FF6600");
450 nested.cell(0, 0).unwrap().set_text("A");
451 nested.cell(0, 1).unwrap().set_text("B");
452 nested.cell(1, 0).unwrap().set_text("C");
453 nested.cell(1, 1).unwrap().set_text("D");
454 }
455 }
456
457 doc.add_paragraph("");
458
459 doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
460 .style("Heading2");
461 {
462 let mut tbl = doc
463 .add_table(2, 3)
464 .borders(BorderStyle::Single, 4, "666666");
465 tbl.row(0).unwrap().height(Length::pt(50.0)).header();
466 tbl.cell(0, 0).unwrap().set_text("Top");
467 tbl.cell(0, 0)
468 .unwrap()
469 .vertical_alignment(VerticalAlignment::Top)
470 .shading("FFF2CC");
471 tbl.cell(0, 1).unwrap().set_text("Center");
472 tbl.cell(0, 1)
473 .unwrap()
474 .vertical_alignment(VerticalAlignment::Center)
475 .shading("D9E2F3");
476 tbl.cell(0, 2).unwrap().set_text("Bottom");
477 tbl.cell(0, 2)
478 .unwrap()
479 .vertical_alignment(VerticalAlignment::Bottom)
480 .shading("E2EFDA");
481 tbl.row(1).unwrap().cant_split();
482 tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
483 tbl.cell(1, 0).unwrap().no_wrap();
484 tbl.cell(1, 1).unwrap().set_text("Fixed width");
485 tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
486 tbl.cell(1, 2).unwrap().set_text("Normal");
487 }
488
489 // ── SECTION 6: IMAGES ──
490 doc.add_paragraph("").page_break_before(true);
491 doc.add_paragraph("6. Images").style("Heading1");
492
493 doc.add_paragraph("6.1 Inline Image").style("Heading2");
494 doc.add_paragraph("A blue gradient image below:");
495 let img = create_sample_png(200, 50, [0, 80, 200]);
496 doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
497
498 doc.add_paragraph("");
499 doc.add_paragraph("6.2 Header Image").style("Heading2");
500 let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
501 doc.set_header_image(
502 &hdr_img,
503 "header_logo.png",
504 Length::inches(2.0),
505 Length::inches(0.2),
506 );
507 doc.add_paragraph("The header now contains an inline image (check the top of this page).");
508
509 doc.add_paragraph("");
510 doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
511
512 // ── SECTION 7: CONTENT MANIPULATION ──
513 doc.add_paragraph("").page_break_before(true);
514 doc.add_paragraph("7. Content Manipulation")
515 .style("Heading1");
516
517 doc.add_paragraph("7.1 Placeholder Replacement")
518 .style("Heading2");
519 doc.add_paragraph("Customer: {{customer}}");
520 doc.add_paragraph("Date: {{date}}");
521 doc.add_paragraph("Reference: {{ref_number}}");
522 {
523 let mut tbl = doc
524 .add_table(2, 2)
525 .borders(BorderStyle::Single, 4, "000000");
526 tbl.cell(0, 0).unwrap().set_text("Project");
527 tbl.cell(0, 0).unwrap().shading("D6E4F0");
528 tbl.cell(0, 1).unwrap().set_text("{{project}}");
529 tbl.cell(1, 0).unwrap().set_text("Status");
530 tbl.cell(1, 0).unwrap().shading("D6E4F0");
531 tbl.cell(1, 1).unwrap().set_text("{{status}}");
532 }
533
534 let mut replacements = HashMap::new();
535 replacements.insert("{{customer}}", "Tensorbee Inc.");
536 replacements.insert("{{date}}", "February 22, 2026");
537 replacements.insert("{{ref_number}}", "TB-2026-001");
538 replacements.insert("{{project}}", "Infrastructure Upgrade");
539 replacements.insert("{{status}}", "In Progress");
540 let n = doc.replace_all(&replacements);
541 doc.add_paragraph(&format!("({n} placeholders replaced above)"));
542
543 doc.add_paragraph("");
544
545 doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
546 doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
547 let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
548 doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
549
550 doc.add_paragraph("");
551
552 doc.add_paragraph("7.3 Content Insertion").style("Heading2");
553 doc.add_paragraph("Section A: First content.");
554 doc.add_paragraph("Section C: Third content.");
555 if let Some(idx) = doc.find_content_index("Section C") {
556 doc.insert_paragraph(
557 idx,
558 "Section B: Inserted between A and C via find_content_index().",
559 );
560 }
561
562 // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
563 doc.add_paragraph("").section_break(SectionBreak::NextPage);
564 doc.add_paragraph("8. Section Breaks & Orientation")
565 .style("Heading1");
566 doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
567
568 {
569 let mut tbl = doc
570 .add_table(3, 7)
571 .borders(BorderStyle::Single, 4, "2E75B6");
572 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
573 for (c, h) in headers.iter().enumerate() {
574 tbl.cell(0, c).unwrap().set_text(h);
575 tbl.cell(0, c).unwrap().shading("2E75B6");
576 }
577 let data = [
578 [
579 "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
580 ],
581 [
582 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
583 ],
584 ];
585 for (r, row) in data.iter().enumerate() {
586 for (c, v) in row.iter().enumerate() {
587 tbl.cell(r + 1, c).unwrap().set_text(v);
588 }
589 }
590 }
591
592 doc.add_paragraph("")
593 .section_break(SectionBreak::NextPage)
594 .section_landscape();
595
596 // ── SECTION 9: CUSTOM STYLES ──
597 doc.add_paragraph("9. Custom Styles").style("Heading1");
598 doc.add_style(
599 StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
600 .based_on("Normal")
601 .paragraph_properties(rdocx_oxml::properties::CT_PPr {
602 shading: Some(rdocx_oxml::properties::CT_Shd {
603 val: "clear".to_string(),
604 color: None,
605 fill: Some("FFF2CC".to_string()),
606 }),
607 ..Default::default()
608 })
609 .run_properties(rdocx_oxml::properties::CT_RPr {
610 bold: Some(true),
611 color: Some("C45911".to_string()),
612 ..Default::default()
613 }),
614 );
615 doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
616 .style("CustomHighlight");
617
618 doc.add_paragraph("");
619
620 // ── SECTION 10: DOCUMENT INTELLIGENCE ──
621 doc.add_paragraph("10. Document Intelligence API")
622 .style("Heading1");
623
624 let wc = doc.word_count();
625 let hc = doc.headings().len();
626 let ic = doc.images().len();
627 let lc = doc.links().len();
628 doc.add_paragraph(&format!("Word count: {wc}"));
629 doc.add_paragraph(&format!("Heading count: {hc}"));
630 doc.add_paragraph(&format!("Image count: {ic}"));
631 doc.add_paragraph(&format!("Link count: {lc}"));
632
633 let outline = doc.document_outline();
634 doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
635
636 let issues = doc.audit_accessibility();
637 doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
638 for issue in &issues {
639 doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
640 }
641
642 // ── SECTION 11: DOCUMENT MERGING ──
643 doc.add_paragraph("");
644 doc.add_paragraph("11. Document Merging").style("Heading1");
645 {
646 let mut other = Document::new();
647 other.add_paragraph("This paragraph was merged from another document using append().");
648 doc.append(&other);
649 }
650
651 doc.add_paragraph("");
652
653 // ── SUMMARY ──
654 doc.add_paragraph("Summary of Demonstrated Features")
655 .style("Heading1");
656 let features = [
657 "Page setup: size, margins, header/footer distance, gutter",
658 "Document metadata: title, author, subject, keywords",
659 "Headers and footers: text, images, different first page",
660 "Background images (full-page behind text)",
661 "Table of Contents generation with bookmarks",
662 "Text formatting: bold, italic, underline styles, strike, color, size, font",
663 "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
664 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
665 "Pagination controls: keep-with-next, keep-together, widow control",
666 "Bullet and numbered lists with nesting",
667 "Tab stops with dot/underscore/hyphen leaders",
668 "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
669 "Row properties: header rows, exact height, cant-split",
670 "Cell properties: width, no-wrap, vertical alignment",
671 "Inline images and header images",
672 "Placeholder replacement (single and batch)",
673 "Regex-based find and replace",
674 "Content insertion at specific positions",
675 "Section breaks with mixed portrait/landscape",
676 "Custom paragraph and character styles",
677 "Document intelligence: word count, headings, outline, images, links",
678 "Accessibility audit",
679 "Document merging (append)",
680 "Export: DOCX, PDF, HTML, Markdown",
681 ];
682 for f in &features {
683 doc.add_bullet_list_item(f, 0);
684 }
685 doc.add_paragraph("");
686 doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
687 .alignment(Alignment::Center)
688 .shading("E2EFDA")
689 .border_all(BorderStyle::Single, 2, "00AA00");
690
691 doc
692}Set the default footer to an inline image.
Sourcepub fn set_raw_header_with_images(
&mut self,
header_xml: Vec<u8>,
images: &[(&str, &[u8], &str)],
hdr_type: HdrFtrType,
)
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 bytesimage_filename: used to derive the part name and content type (e.g. “image5.png”)
Examples found in repository?
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
697fn generate_proposal(_samples_dir: &Path) -> Document {
698 let mut doc = Document::new();
699
700 // Colors: Navy #1B2A4A, Gold #C5922E, Light #F4F1EB
701 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
702 doc.set_margins(
703 Length::inches(1.0),
704 Length::inches(1.0),
705 Length::inches(1.0),
706 Length::inches(1.0),
707 );
708 doc.set_title("AI Platform Modernization Proposal");
709 doc.set_author("Walter White");
710 doc.set_subject("Technology Proposal");
711 doc.set_keywords("proposal, AI, modernization, Tensorbee");
712
713 // Banner header
714 let logo_img = create_logo_png(220, 48);
715 let banner = build_header_banner_xml(
716 "rId1",
717 &BannerOpts {
718 bg_color: "1B2A4A",
719 banner_width: 7772400,
720 banner_height: 914400,
721 logo_width: 2011680,
722 logo_height: 438912,
723 logo_x_offset: 295125,
724 logo_y_offset: 237744,
725 },
726 );
727 doc.set_raw_header_with_images(
728 banner,
729 &[("rId1", &logo_img, "logo.png")],
730 rdocx_oxml::header_footer::HdrFtrType::Default,
731 );
732 doc.set_different_first_page(true);
733 let cover_banner = build_header_banner_xml(
734 "rId1",
735 &BannerOpts {
736 bg_color: "C5922E",
737 banner_width: 7772400,
738 banner_height: 914400,
739 logo_width: 2011680,
740 logo_height: 438912,
741 logo_x_offset: 295125,
742 logo_y_offset: 237744,
743 },
744 );
745 doc.set_raw_header_with_images(
746 cover_banner,
747 &[("rId1", &logo_img, "logo.png")],
748 rdocx_oxml::header_footer::HdrFtrType::First,
749 );
750 doc.set_margins(
751 Length::twips(2292),
752 Length::twips(1440),
753 Length::twips(1440),
754 Length::twips(1440),
755 );
756 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
757 doc.set_footer("Tensorbee — Confidential");
758
759 // Custom styles
760 doc.add_style(
761 StyleBuilder::paragraph("ProposalTitle", "Proposal Title")
762 .based_on("Normal")
763 .run_properties(rdocx_oxml::properties::CT_RPr {
764 bold: Some(true),
765 font_ascii: Some("Georgia".to_string()),
766 font_hansi: Some("Georgia".to_string()),
767 sz: Some(rdocx_oxml::HalfPoint(56)), // half-points → 28pt
768 color: Some("1B2A4A".to_string()),
769 ..Default::default()
770 }),
771 );
772
773 // ── Cover Page ──
774 for _ in 0..4 {
775 doc.add_paragraph("");
776 }
777 doc.add_paragraph("AI Platform Modernization")
778 .style("ProposalTitle")
779 .alignment(Alignment::Center);
780 doc.add_paragraph("")
781 .alignment(Alignment::Center)
782 .border_bottom(BorderStyle::Single, 8, "C5922E");
783 {
784 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
785 p.add_run("Prepared for Global Financial Corp.")
786 .size(14.0)
787 .color("666666")
788 .italic(true);
789 }
790 {
791 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
792 p.add_run("Prepared by: Walter White, CEO — Tensorbee")
793 .size(12.0)
794 .color("888888");
795 }
796 {
797 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
798 p.add_run("February 22, 2026").size(12.0).color("888888");
799 }
800
801 // ── TOC ──
802 doc.add_paragraph("").page_break_before(true);
803 doc.add_paragraph("Table of Contents").style("Heading1");
804 // We'll add a manual-style TOC since the insert_toc goes at a specific position
805 doc.insert_toc(doc.content_count(), 2);
806
807 // ── Executive Summary ──
808 doc.add_paragraph("").page_break_before(true);
809 doc.add_paragraph("Executive Summary").style("Heading1");
810 doc.add_paragraph(
811 "Tensorbee proposes a comprehensive modernization of Global Financial Corp's AI platform, \
812 transitioning from legacy batch-processing systems to a real-time inference architecture. \
813 This transformation will reduce model deployment time from weeks to hours, improve \
814 prediction accuracy by an estimated 23%, and deliver $4.2M in annual operational savings.",
815 )
816 .first_line_indent(Length::inches(0.3));
817 doc.add_paragraph(
818 "The project spans three phases over 18 months, with a total investment of $2.8M. \
819 Tensorbee brings deep expertise in ML infrastructure, having successfully completed \
820 similar transformations for three Fortune 500 financial institutions.",
821 )
822 .first_line_indent(Length::inches(0.3));
823
824 // Key metrics table
825 doc.add_paragraph("");
826 {
827 let mut tbl = doc
828 .add_table(5, 2)
829 .borders(BorderStyle::Single, 4, "1B2A4A")
830 .width_pct(80.0);
831 tbl.cell(0, 0).unwrap().set_text("Key Metric");
832 tbl.cell(0, 0).unwrap().shading("1B2A4A");
833 tbl.cell(0, 1).unwrap().set_text("Value");
834 tbl.cell(0, 1).unwrap().shading("1B2A4A");
835 let rows = [
836 ("Total Investment", "$2.8M"),
837 ("Annual Savings", "$4.2M"),
838 ("ROI (Year 1)", "150%"),
839 ("Timeline", "18 months"),
840 ];
841 for (i, (k, v)) in rows.iter().enumerate() {
842 tbl.cell(i + 1, 0).unwrap().set_text(k);
843 if i % 2 == 0 {
844 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
845 }
846 tbl.cell(i + 1, 1).unwrap().set_text(v);
847 if i % 2 == 0 {
848 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
849 }
850 }
851 }
852
853 // ── Problem Statement ──
854 doc.add_paragraph("").page_break_before(true);
855 doc.add_paragraph("Problem Statement").style("Heading1");
856 doc.add_paragraph(
857 "Global Financial Corp's current AI infrastructure faces three critical challenges:",
858 );
859 doc.add_numbered_list_item("Deployment Latency — New models take 3-4 weeks to deploy to production, compared to the industry benchmark of 2-3 days.", 0);
860 doc.add_numbered_list_item("Scalability Constraints — The batch processing architecture cannot handle real-time inference demands during peak trading hours.", 0);
861 doc.add_numbered_list_item("Technical Debt — Legacy Python 2.7 codebases and manual deployment scripts create reliability risks and slow iteration speed.", 0);
862
863 // ── Proposed Solution ──
864 doc.add_paragraph("").page_break_before(true);
865 doc.add_paragraph("Proposed Solution").style("Heading1");
866
867 doc.add_paragraph("Phase 1: Foundation (Months 1-6)")
868 .style("Heading2");
869 doc.add_bullet_list_item("Deploy Kubernetes-based ML serving infrastructure", 0);
870 doc.add_bullet_list_item("Implement CI/CD pipeline for model deployment", 0);
871 doc.add_bullet_list_item("Migrate top 5 production models to new platform", 0);
872
873 doc.add_paragraph("Phase 2: Expansion (Months 7-12)")
874 .style("Heading2");
875 doc.add_bullet_list_item(
876 "Real-time feature engineering pipeline (Apache Kafka + Flink)",
877 0,
878 );
879 doc.add_bullet_list_item("A/B testing framework for model variants", 0);
880 doc.add_bullet_list_item("Automated model monitoring and drift detection", 0);
881
882 doc.add_paragraph("Phase 3: Optimization (Months 13-18)")
883 .style("Heading2");
884 doc.add_bullet_list_item("GPU inference optimization (TensorRT/ONNX Runtime)", 0);
885 doc.add_bullet_list_item("Multi-region deployment for latency reduction", 0);
886 doc.add_bullet_list_item(
887 "Self-service model deployment portal for data science teams",
888 0,
889 );
890
891 // ── Budget ──
892 doc.add_paragraph("Budget Breakdown").style("Heading1");
893 {
894 let mut tbl = doc
895 .add_table(6, 3)
896 .borders(BorderStyle::Single, 4, "1B2A4A")
897 .width_pct(100.0);
898 tbl.cell(0, 0).unwrap().set_text("Category");
899 tbl.cell(0, 0).unwrap().shading("1B2A4A");
900 tbl.cell(0, 1).unwrap().set_text("Cost");
901 tbl.cell(0, 1).unwrap().shading("1B2A4A");
902 tbl.cell(0, 2).unwrap().set_text("Notes");
903 tbl.cell(0, 2).unwrap().shading("1B2A4A");
904 let items = [
905 (
906 "Infrastructure",
907 "$450,000",
908 "Cloud compute + GPU instances",
909 ),
910 ("Engineering Services", "$1,600,000", "12 FTE x 18 months"),
911 (
912 "Software Licenses",
913 "$350,000",
914 "Kafka, monitoring, CI/CD tools",
915 ),
916 (
917 "Training & Enablement",
918 "$200,000",
919 "Team training, documentation",
920 ),
921 ("Contingency (10%)", "$200,000", "Risk buffer"),
922 ];
923 for (i, (cat, cost, note)) in items.iter().enumerate() {
924 tbl.cell(i + 1, 0).unwrap().set_text(cat);
925 tbl.cell(i + 1, 1).unwrap().set_text(cost);
926 tbl.cell(i + 1, 2).unwrap().set_text(note);
927 if i % 2 == 0 {
928 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
929 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
930 tbl.cell(i + 1, 2).unwrap().shading("F4F1EB");
931 }
932 }
933 }
934
935 // ── Closing ──
936 doc.add_paragraph("");
937 doc.add_paragraph("Next Steps").style("Heading1");
938 doc.add_numbered_list_item(
939 "Schedule technical deep-dive with GFC engineering team (Week 1)",
940 0,
941 );
942 doc.add_numbered_list_item("Finalize scope and sign SOW (Week 2-3)", 0);
943 doc.add_numbered_list_item("Kick off Phase 1 with joint planning session (Week 4)", 0);
944
945 doc.add_paragraph("");
946 {
947 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
948 p.add_run("Walter White")
949 .bold(true)
950 .size(14.0)
951 .color("1B2A4A");
952 }
953 {
954 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
955 p.add_run("CEO, Tensorbee | walter@tensorbee.com")
956 .size(10.0)
957 .color("888888");
958 }
959
960 doc
961}
962
963// =============================================================================
964// 3. QUOTE / BILL OF MATERIALS — Teal + orange scheme
965// =============================================================================
966fn generate_quote(_samples_dir: &Path) -> Document {
967 let mut doc = Document::new();
968
969 // Colors: Teal #008B8B, Dark #1A3C3C, Orange #E07020, Light #F0F8F8
970 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
971 doc.set_margins(
972 Length::inches(0.75),
973 Length::inches(0.75),
974 Length::inches(0.75),
975 Length::inches(0.75),
976 );
977 doc.set_title("Quotation QT-2026-0147");
978 doc.set_author("Walter White");
979 doc.set_keywords("quote, BOM, Tensorbee");
980
981 doc.set_header("Tensorbee — Quotation");
982 doc.set_footer("QT-2026-0147 | Page");
983
984 // ── Header Block ──
985 {
986 let mut p = doc.add_paragraph("").alignment(Alignment::Left);
987 p.add_run("TENSORBEE")
988 .bold(true)
989 .size(28.0)
990 .color("008B8B")
991 .font("Helvetica");
992 }
993 doc.add_paragraph("123 Innovation Drive, Suite 400")
994 .alignment(Alignment::Left);
995 doc.add_paragraph("San Francisco, CA 94105 | +1 (415) 555-0199")
996 .alignment(Alignment::Left);
997 doc.add_paragraph("")
998 .border_bottom(BorderStyle::Single, 8, "008B8B");
999
1000 // Quote meta
1001 doc.add_paragraph("");
1002 {
1003 let mut tbl = doc.add_table(4, 4).width_pct(100.0);
1004 tbl.cell(0, 0).unwrap().set_text("QUOTATION");
1005 tbl.cell(0, 0).unwrap().shading("008B8B").grid_span(2);
1006 tbl.cell(0, 2).unwrap().set_text("DATE");
1007 tbl.cell(0, 2).unwrap().shading("008B8B");
1008 tbl.cell(0, 3).unwrap().set_text("VALID UNTIL");
1009 tbl.cell(0, 3).unwrap().shading("008B8B");
1010 tbl.cell(1, 0).unwrap().set_text("Quote #:");
1011 tbl.cell(1, 1).unwrap().set_text("QT-2026-0147");
1012 tbl.cell(1, 2).unwrap().set_text("Feb 22, 2026");
1013 tbl.cell(1, 3).unwrap().set_text("Mar 22, 2026");
1014 tbl.cell(2, 0).unwrap().set_text("Prepared by:");
1015 tbl.cell(2, 1).unwrap().set_text("Walter White");
1016 tbl.cell(2, 2).unwrap().set_text("Payment:");
1017 tbl.cell(2, 3).unwrap().set_text("Net 30");
1018 tbl.cell(3, 0).unwrap().set_text("Customer:");
1019 tbl.cell(3, 1).unwrap().set_text("Meridian Dynamics LLC");
1020 tbl.cell(3, 2).unwrap().set_text("Currency:");
1021 tbl.cell(3, 3).unwrap().set_text("USD");
1022 }
1023
1024 doc.add_paragraph("");
1025
1026 // ── Bill of Materials ──
1027 {
1028 let mut p = doc.add_paragraph("");
1029 p.add_run("Bill of Materials")
1030 .bold(true)
1031 .size(16.0)
1032 .color("1A3C3C");
1033 }
1034 doc.add_paragraph("");
1035
1036 {
1037 let mut tbl = doc
1038 .add_table(10, 6)
1039 .borders(BorderStyle::Single, 2, "008B8B")
1040 .width_pct(100.0)
1041 .layout_fixed();
1042 // Headers
1043 let hdrs = [
1044 "#",
1045 "Part Number",
1046 "Description",
1047 "Qty",
1048 "Unit Price",
1049 "Total",
1050 ];
1051 for (c, h) in hdrs.iter().enumerate() {
1052 tbl.cell(0, c).unwrap().set_text(h);
1053 tbl.cell(0, c).unwrap().shading("008B8B");
1054 }
1055
1056 let items: Vec<(&str, &str, &str, &str, &str)> = vec![
1057 (
1058 "TB-GPU-A100",
1059 "NVIDIA A100 80GB GPU",
1060 "4",
1061 "$12,500.00",
1062 "$50,000.00",
1063 ),
1064 (
1065 "TB-SRV-R750",
1066 "Dell R750xa Server Chassis",
1067 "2",
1068 "$8,200.00",
1069 "$16,400.00",
1070 ),
1071 (
1072 "TB-RAM-256G",
1073 "256GB DDR5 ECC Memory Module",
1074 "8",
1075 "$890.00",
1076 "$7,120.00",
1077 ),
1078 (
1079 "TB-SSD-3840",
1080 "3.84TB NVMe U.2 SSD",
1081 "8",
1082 "$1,150.00",
1083 "$9,200.00",
1084 ),
1085 (
1086 "TB-NET-CX7",
1087 "ConnectX-7 200GbE NIC",
1088 "4",
1089 "$1,800.00",
1090 "$7,200.00",
1091 ),
1092 (
1093 "TB-SW-48P",
1094 "48-Port 100GbE Switch",
1095 "1",
1096 "$22,000.00",
1097 "$22,000.00",
1098 ),
1099 (
1100 "TB-CAB-RACK",
1101 "42U Server Rack + PDU",
1102 "1",
1103 "$4,500.00",
1104 "$4,500.00",
1105 ),
1106 (
1107 "TB-SVC-INST",
1108 "Installation & Configuration",
1109 "1",
1110 "$8,500.00",
1111 "$8,500.00",
1112 ),
1113 (
1114 "TB-SVC-SUPP",
1115 "3-Year Premium Support",
1116 "1",
1117 "$15,000.00",
1118 "$15,000.00",
1119 ),
1120 ];
1121
1122 for (i, (pn, desc, qty, unit, total)) in items.iter().enumerate() {
1123 let row = i + 1;
1124 tbl.cell(row, 0).unwrap().set_text(&format!("{}", i + 1));
1125 tbl.cell(row, 1).unwrap().set_text(pn);
1126 tbl.cell(row, 2).unwrap().set_text(desc);
1127 tbl.cell(row, 3).unwrap().set_text(qty);
1128 tbl.cell(row, 4).unwrap().set_text(unit);
1129 tbl.cell(row, 5).unwrap().set_text(total);
1130 if i % 2 == 0 {
1131 for c in 0..6 {
1132 tbl.cell(row, c).unwrap().shading("F0F8F8");
1133 }
1134 }
1135 }
1136 }
1137
1138 doc.add_paragraph("");
1139
1140 // ── Totals ──
1141 {
1142 let mut tbl = doc
1143 .add_table(4, 2)
1144 .borders(BorderStyle::Single, 2, "008B8B")
1145 .width(Length::inches(3.5))
1146 .alignment(Alignment::Right);
1147 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1148 tbl.cell(0, 1).unwrap().set_text("$139,920.00");
1149 tbl.cell(1, 0).unwrap().set_text("Shipping & Handling");
1150 tbl.cell(1, 1).unwrap().set_text("$2,500.00");
1151 tbl.cell(2, 0).unwrap().set_text("Tax (8.625%)");
1152 tbl.cell(2, 1).unwrap().set_text("$12,068.10");
1153 tbl.cell(3, 0).unwrap().set_text("TOTAL");
1154 tbl.cell(3, 0).unwrap().shading("E07020");
1155 tbl.cell(3, 1).unwrap().set_text("$154,488.10");
1156 tbl.cell(3, 1).unwrap().shading("E07020");
1157 }
1158
1159 doc.add_paragraph("");
1160
1161 // ── Terms & Conditions ──
1162 {
1163 let mut p = doc.add_paragraph("");
1164 p.add_run("Terms & Conditions")
1165 .bold(true)
1166 .size(14.0)
1167 .color("1A3C3C");
1168 }
1169 doc.add_numbered_list_item(
1170 "This quotation is valid for 30 calendar days from the date of issue.",
1171 0,
1172 );
1173 doc.add_numbered_list_item(
1174 "All prices are in USD and exclusive of applicable taxes unless stated.",
1175 0,
1176 );
1177 doc.add_numbered_list_item("Standard lead time is 4-6 weeks from PO receipt.", 0);
1178 doc.add_numbered_list_item(
1179 "Payment terms: Net 30 from invoice date. 2% discount for payment within 10 days.",
1180 0,
1181 );
1182 doc.add_numbered_list_item(
1183 "Warranty: 3-year manufacturer warranty on all hardware components.",
1184 0,
1185 );
1186 doc.add_numbered_list_item(
1187 "Returns subject to 15% restocking fee if initiated after 14 days.",
1188 0,
1189 );
1190
1191 doc.add_paragraph("");
1192 doc.add_paragraph("")
1193 .border_bottom(BorderStyle::Single, 4, "008B8B");
1194 doc.add_paragraph("");
1195 {
1196 let mut p = doc.add_paragraph("");
1197 p.add_run("Accepted by: ").bold(true);
1198 p.add_run("___________________________ Date: ___________");
1199 }
1200 {
1201 let mut p = doc.add_paragraph("");
1202 p.add_run("Print Name: ").bold(true);
1203 p.add_run("___________________________ Title: ___________");
1204 }
1205
1206 doc
1207}
1208
1209// =============================================================================
1210// 4. INVOICE — Crimson + charcoal scheme
1211// =============================================================================
1212fn generate_invoice(_samples_dir: &Path) -> Document {
1213 let mut doc = Document::new();
1214
1215 // Colors: Crimson #B22222, Charcoal #333333, Light #FAF0F0
1216 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1217 doc.set_margins(
1218 Length::inches(0.75),
1219 Length::inches(0.75),
1220 Length::inches(0.75),
1221 Length::inches(0.75),
1222 );
1223 doc.set_title("Invoice INV-2026-0392");
1224 doc.set_author("Walter White");
1225 doc.set_keywords("invoice, Tensorbee");
1226
1227 doc.set_footer("Tensorbee — Thank you for your business!");
1228
1229 // ── Company & Invoice Header ──
1230 {
1231 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1232 // Company name left, INVOICE right
1233 {
1234 let mut cell = tbl.cell(0, 0).unwrap();
1235 let mut p = cell.add_paragraph("");
1236 p.add_run("TENSORBEE")
1237 .bold(true)
1238 .size(32.0)
1239 .color("B22222")
1240 .font("Helvetica");
1241 }
1242 {
1243 let mut cell = tbl.cell(0, 1).unwrap();
1244 let mut p = cell.add_paragraph("");
1245 p.add_run("INVOICE").bold(true).size(32.0).color("333333");
1246 }
1247 tbl.cell(1, 0)
1248 .unwrap()
1249 .set_text("123 Innovation Drive, Suite 400");
1250 tbl.cell(1, 1).unwrap().set_text("Invoice #: INV-2026-0392");
1251 tbl.cell(2, 0).unwrap().set_text("San Francisco, CA 94105");
1252 tbl.cell(2, 1).unwrap().set_text("Date: February 22, 2026");
1253 tbl.cell(3, 0).unwrap().set_text("walter@tensorbee.com");
1254 tbl.cell(3, 1).unwrap().set_text("Due Date: March 24, 2026");
1255 }
1256
1257 doc.add_paragraph("")
1258 .border_bottom(BorderStyle::Thick, 8, "B22222");
1259 doc.add_paragraph("");
1260
1261 // ── Bill To / Ship To ──
1262 {
1263 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1264 tbl.cell(0, 0).unwrap().set_text("BILL TO");
1265 tbl.cell(0, 0).unwrap().shading("B22222");
1266 tbl.cell(0, 1).unwrap().set_text("SHIP TO");
1267 tbl.cell(0, 1).unwrap().shading("B22222");
1268 tbl.cell(1, 0).unwrap().set_text("Meridian Dynamics LLC");
1269 tbl.cell(1, 1).unwrap().set_text("Meridian Dynamics LLC");
1270 tbl.cell(2, 0).unwrap().set_text("456 Enterprise Blvd");
1271 tbl.cell(2, 1).unwrap().set_text("Attn: Server Room B");
1272 tbl.cell(3, 0).unwrap().set_text("Austin, TX 78701");
1273 tbl.cell(3, 1)
1274 .unwrap()
1275 .set_text("456 Enterprise Blvd, Austin, TX 78701");
1276 }
1277
1278 doc.add_paragraph("");
1279
1280 // ── Line Items ──
1281 {
1282 let mut tbl = doc
1283 .add_table(8, 5)
1284 .borders(BorderStyle::Single, 2, "B22222")
1285 .width_pct(100.0);
1286 let hdrs = ["Description", "Qty", "Unit Price", "Tax", "Amount"];
1287 for (c, h) in hdrs.iter().enumerate() {
1288 tbl.cell(0, c).unwrap().set_text(h);
1289 tbl.cell(0, c).unwrap().shading("B22222");
1290 }
1291 let items = [
1292 (
1293 "ML Infrastructure Setup — Phase 1",
1294 "1",
1295 "$45,000.00",
1296 "$3,881.25",
1297 "$48,881.25",
1298 ),
1299 (
1300 "Data Pipeline Development (160 hrs)",
1301 "160",
1302 "$225.00",
1303 "$3,105.00",
1304 "$39,105.00",
1305 ),
1306 (
1307 "GPU Cluster Configuration",
1308 "1",
1309 "$12,000.00",
1310 "$1,035.00",
1311 "$13,035.00",
1312 ),
1313 (
1314 "API Gateway Implementation",
1315 "1",
1316 "$18,500.00",
1317 "$1,595.63",
1318 "$20,095.63",
1319 ),
1320 (
1321 "Load Testing & QA (80 hrs)",
1322 "80",
1323 "$195.00",
1324 "$1,345.50",
1325 "$16,945.50",
1326 ),
1327 (
1328 "Documentation & Training",
1329 "1",
1330 "$8,000.00",
1331 "$690.00",
1332 "$8,690.00",
1333 ),
1334 (
1335 "Project Management (3 months)",
1336 "3",
1337 "$6,500.00",
1338 "$1,679.63",
1339 "$21,179.63",
1340 ),
1341 ];
1342 for (i, (desc, qty, unit, tax, amt)) in items.iter().enumerate() {
1343 let r = i + 1;
1344 tbl.cell(r, 0).unwrap().set_text(desc);
1345 tbl.cell(r, 1).unwrap().set_text(qty);
1346 tbl.cell(r, 2).unwrap().set_text(unit);
1347 tbl.cell(r, 3).unwrap().set_text(tax);
1348 tbl.cell(r, 4).unwrap().set_text(amt);
1349 if i % 2 == 0 {
1350 for c in 0..5 {
1351 tbl.cell(r, c).unwrap().shading("FAF0F0");
1352 }
1353 }
1354 }
1355 }
1356
1357 doc.add_paragraph("");
1358
1359 // ── Totals ──
1360 {
1361 let mut tbl = doc
1362 .add_table(4, 2)
1363 .borders(BorderStyle::Single, 2, "B22222")
1364 .width(Length::inches(3.0))
1365 .alignment(Alignment::Right);
1366 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1367 tbl.cell(0, 1).unwrap().set_text("$154,600.00");
1368 tbl.cell(1, 0).unwrap().set_text("Tax (8.625%)");
1369 tbl.cell(1, 1).unwrap().set_text("$13,334.25");
1370 tbl.cell(2, 0).unwrap().set_text("Discount (5%)");
1371 tbl.cell(2, 1).unwrap().set_text("-$7,730.00");
1372 tbl.cell(3, 0).unwrap().set_text("AMOUNT DUE");
1373 tbl.cell(3, 0).unwrap().shading("B22222");
1374 tbl.cell(3, 1).unwrap().set_text("$160,204.25");
1375 tbl.cell(3, 1).unwrap().shading("B22222");
1376 }
1377
1378 doc.add_paragraph("");
1379 doc.add_paragraph("");
1380
1381 // ── Payment Details ──
1382 {
1383 let mut p = doc.add_paragraph("");
1384 p.add_run("Payment Details")
1385 .bold(true)
1386 .size(14.0)
1387 .color("333333");
1388 }
1389 doc.add_paragraph("Bank: Silicon Valley Bank")
1390 .indent_left(Length::inches(0.3));
1391 doc.add_paragraph("Account: Tensorbee Inc. — 0847-2953-1120")
1392 .indent_left(Length::inches(0.3));
1393 doc.add_paragraph("Routing: 121140399")
1394 .indent_left(Length::inches(0.3));
1395 doc.add_paragraph("Swift: SVBKUS6S")
1396 .indent_left(Length::inches(0.3));
1397
1398 doc.add_paragraph("");
1399
1400 doc.add_paragraph("Please include invoice number INV-2026-0392 in the payment reference.")
1401 .shading("FAF0F0")
1402 .border_all(BorderStyle::Single, 2, "B22222");
1403
1404 doc
1405}
1406
1407// =============================================================================
1408// 5. REPORT — Forest green + earth tones, with images & hierarchical sections
1409// =============================================================================
1410fn generate_report(_samples_dir: &Path) -> Document {
1411 let mut doc = Document::new();
1412
1413 // Colors: Forest #2D5016, Sage #6B8E23, Earth #8B7355, Cream #FFFAF0
1414 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1415 doc.set_margins(
1416 Length::inches(1.0),
1417 Length::inches(1.0),
1418 Length::inches(1.0),
1419 Length::inches(1.0),
1420 );
1421 doc.set_title("Q4 2025 Environmental Impact Report");
1422 doc.set_author("Walter White");
1423 doc.set_subject("Quarterly Environmental Report");
1424 doc.set_keywords("environment, sustainability, report, Tensorbee");
1425
1426 doc.set_different_first_page(true);
1427 doc.set_first_page_header("");
1428 doc.set_header("Tensorbee — Q4 2025 Environmental Impact Report");
1429 doc.set_footer("CONFIDENTIAL — Page");
1430
1431 // ── Cover ──
1432 let cover_bg = create_sample_png(612, 792, [20, 50, 15]);
1433 doc.add_background_image(&cover_bg, "report_cover.png");
1434
1435 for _ in 0..5 {
1436 doc.add_paragraph("");
1437 }
1438 {
1439 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1440 p.add_run("Q4 2025")
1441 .bold(true)
1442 .size(48.0)
1443 .color("FFFFFF")
1444 .font("Georgia");
1445 }
1446 {
1447 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1448 p.add_run("Environmental Impact Report")
1449 .size(24.0)
1450 .color("90EE90")
1451 .font("Georgia")
1452 .italic(true);
1453 }
1454 doc.add_paragraph("");
1455 {
1456 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1457 p.add_run("Tensorbee — Sustainability Division")
1458 .size(14.0)
1459 .color("C0C0C0");
1460 }
1461 {
1462 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1463 p.add_run("Prepared by Walter White, Chief Sustainability Officer")
1464 .size(11.0)
1465 .color("AAAAAA");
1466 }
1467
1468 // ── TOC ──
1469 doc.add_paragraph("").page_break_before(true);
1470 doc.insert_toc(doc.content_count(), 3);
1471
1472 // ── Section 1: Executive Overview ──
1473 doc.add_paragraph("").page_break_before(true);
1474 doc.add_paragraph("Executive Overview").style("Heading1");
1475 doc.add_paragraph(
1476 "This report presents Tensorbee's environmental performance for Q4 2025. Our \
1477 sustainability initiatives have yielded a 34% reduction in carbon emissions compared \
1478 to Q4 2024, exceeding our target of 25%. Key achievements include the transition to \
1479 100% renewable energy in our primary data centers and the launch of our carbon offset \
1480 marketplace.",
1481 )
1482 .first_line_indent(Length::inches(0.3));
1483
1484 // Image: performance chart
1485 let chart_img = create_chart_png(400, 200);
1486 doc.add_paragraph("");
1487 doc.add_picture(
1488 &chart_img,
1489 "performance_chart.png",
1490 Length::inches(5.0),
1491 Length::inches(2.5),
1492 )
1493 .alignment(Alignment::Center);
1494 {
1495 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1496 p.add_run("Figure 1: Quarterly Carbon Emissions (tonnes CO2e)")
1497 .italic(true)
1498 .size(9.0)
1499 .color("666666");
1500 }
1501
1502 // ── Section 2: Energy Consumption ──
1503 doc.add_paragraph("").page_break_before(true);
1504 doc.add_paragraph("Energy Consumption").style("Heading1");
1505
1506 doc.add_paragraph("Data Center Operations")
1507 .style("Heading2");
1508 doc.add_paragraph(
1509 "Our three primary data centers consumed a combined 4.2 GWh in Q4 2025, \
1510 a 12% reduction from Q3 2025 driven by improved cooling efficiency and \
1511 server consolidation.",
1512 );
1513
1514 // Data table
1515 {
1516 let mut tbl = doc
1517 .add_table(5, 4)
1518 .borders(BorderStyle::Single, 4, "2D5016")
1519 .width_pct(100.0);
1520 let hdrs = ["Data Center", "Capacity (MW)", "Usage (GWh)", "PUE"];
1521 for (c, h) in hdrs.iter().enumerate() {
1522 tbl.cell(0, c).unwrap().set_text(h);
1523 tbl.cell(0, c).unwrap().shading("2D5016");
1524 }
1525 let rows = [
1526 ("San Francisco (Primary)", "3.2", "1.8", "1.12"),
1527 ("Dublin (EU)", "2.1", "1.2", "1.18"),
1528 ("Singapore (APAC)", "1.8", "1.2", "1.24"),
1529 ("TOTAL", "7.1", "4.2", "1.17 avg"),
1530 ];
1531 for (i, (dc, cap, use_, pue)) in rows.iter().enumerate() {
1532 tbl.cell(i + 1, 0).unwrap().set_text(dc);
1533 tbl.cell(i + 1, 1).unwrap().set_text(cap);
1534 tbl.cell(i + 1, 2).unwrap().set_text(use_);
1535 tbl.cell(i + 1, 3).unwrap().set_text(pue);
1536 if i == 3 {
1537 for c in 0..4 {
1538 tbl.cell(i + 1, c).unwrap().shading("E2EFDA");
1539 }
1540 }
1541 }
1542 }
1543
1544 doc.add_paragraph("");
1545
1546 doc.add_paragraph("Renewable Energy Mix").style("Heading2");
1547 doc.add_paragraph("Breakdown of energy sources across all facilities:");
1548
1549 doc.add_bullet_list_item("Solar PV: 42% (1.76 GWh)", 0);
1550 doc.add_bullet_list_item("Wind Power Purchase Agreements: 38% (1.60 GWh)", 0);
1551 doc.add_bullet_list_item("Hydroelectric: 12% (0.50 GWh)", 0);
1552 doc.add_bullet_list_item("Grid (non-renewable): 8% (0.34 GWh)", 0);
1553
1554 // ── Section 3: Water & Waste ──
1555 doc.add_paragraph("Water & Waste Management")
1556 .style("Heading1");
1557
1558 doc.add_paragraph("Water Usage").style("Heading2");
1559 doc.add_paragraph(
1560 "Total water consumption was 12.4 million gallons, a 15% reduction from Q3. \
1561 Our closed-loop cooling systems now recycle 78% of water used in cooling operations.",
1562 );
1563
1564 doc.add_paragraph("Waste Reduction").style("Heading2");
1565 doc.add_paragraph("E-waste management results for Q4:")
1566 .keep_with_next(true);
1567 {
1568 let mut tbl = doc
1569 .add_table(4, 3)
1570 .borders(BorderStyle::Single, 2, "6B8E23")
1571 .width_pct(80.0);
1572 tbl.cell(0, 0).unwrap().set_text("Category");
1573 tbl.cell(0, 0).unwrap().shading("6B8E23");
1574 tbl.cell(0, 1).unwrap().set_text("Weight (kg)");
1575 tbl.cell(0, 1).unwrap().shading("6B8E23");
1576 tbl.cell(0, 2).unwrap().set_text("Recycled %");
1577 tbl.cell(0, 2).unwrap().shading("6B8E23");
1578 let rows = [
1579 ("Server Hardware", "2,450", "94%"),
1580 ("Networking Equipment", "820", "91%"),
1581 ("Storage Media", "340", "99%"),
1582 ];
1583 for (i, (cat, wt, pct)) in rows.iter().enumerate() {
1584 tbl.cell(i + 1, 0).unwrap().set_text(cat);
1585 tbl.cell(i + 1, 1).unwrap().set_text(wt);
1586 tbl.cell(i + 1, 2).unwrap().set_text(pct);
1587 }
1588 }
1589
1590 // ── Section 4: Initiatives ──
1591 doc.add_paragraph("").page_break_before(true);
1592 doc.add_paragraph("Q1 2026 Initiatives").style("Heading1");
1593
1594 doc.add_paragraph("Planned Programs").style("Heading2");
1595 doc.add_numbered_list_item(
1596 "Deploy on-site battery storage at SF data center (2 MWh capacity)",
1597 0,
1598 );
1599 doc.add_numbered_list_item("Pilot immersion cooling in Dublin facility", 0);
1600 doc.add_numbered_list_item("Launch employee commute carbon offset program", 0);
1601 doc.add_numbered_list_item("Achieve ISO 14001 certification for Singapore facility", 0);
1602
1603 doc.add_paragraph("Investment Targets").style("Heading2");
1604 doc.add_paragraph("Sustainability CapEx allocation for FY2026:")
1605 .keep_with_next(true);
1606 {
1607 let mut tbl = doc
1608 .add_table(5, 2)
1609 .borders(BorderStyle::Single, 4, "2D5016");
1610 tbl.cell(0, 0).unwrap().set_text("Initiative");
1611 tbl.cell(0, 0).unwrap().shading("2D5016");
1612 tbl.cell(0, 1).unwrap().set_text("Budget");
1613 tbl.cell(0, 1).unwrap().shading("2D5016");
1614 let rows = [
1615 ("Battery Storage", "$1.2M"),
1616 ("Immersion Cooling Pilot", "$800K"),
1617 ("Solar Panel Expansion", "$2.1M"),
1618 ("Carbon Credits", "$500K"),
1619 ];
1620 for (i, (init, budget)) in rows.iter().enumerate() {
1621 tbl.cell(i + 1, 0).unwrap().set_text(init);
1622 tbl.cell(i + 1, 1).unwrap().set_text(budget);
1623 if i % 2 == 0 {
1624 tbl.cell(i + 1, 0).unwrap().shading("FFFAF0");
1625 tbl.cell(i + 1, 1).unwrap().shading("FFFAF0");
1626 }
1627 }
1628 }
1629
1630 // Image: sustainability roadmap
1631 let roadmap_img = create_sample_png(500, 100, [40, 80, 30]);
1632 doc.add_paragraph("");
1633 doc.add_picture(
1634 &roadmap_img,
1635 "roadmap.png",
1636 Length::inches(6.0),
1637 Length::inches(1.2),
1638 )
1639 .alignment(Alignment::Center);
1640 {
1641 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1642 p.add_run("Figure 2: 2026 Sustainability Roadmap")
1643 .italic(true)
1644 .size(9.0)
1645 .color("666666");
1646 }
1647
1648 doc.add_paragraph("");
1649 doc.add_paragraph(
1650 "For questions about this report, contact Walter White at sustainability@tensorbee.com.",
1651 )
1652 .shading("FFFAF0")
1653 .border_all(BorderStyle::Single, 2, "2D5016");
1654
1655 doc
1656}
1657
1658// =============================================================================
1659// 6. LETTER — Slate blue + silver scheme
1660// =============================================================================
1661fn generate_letter(_samples_dir: &Path) -> Document {
1662 let mut doc = Document::new();
1663
1664 // Colors: Slate #4A5568, Blue accent #3182CE
1665 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1666 doc.set_margins(
1667 Length::inches(1.25),
1668 Length::inches(1.25),
1669 Length::inches(1.25),
1670 Length::inches(1.25),
1671 );
1672 doc.set_title("Business Letter");
1673 doc.set_author("Walter White");
1674
1675 // Banner header using raw XML
1676 let logo_img = create_logo_png(220, 48);
1677 let banner = build_header_banner_xml(
1678 "rId1",
1679 &BannerOpts {
1680 bg_color: "4A5568",
1681 banner_width: 7772400,
1682 banner_height: 731520, // ~0.8"
1683 logo_width: 2011680,
1684 logo_height: 438912,
1685 logo_x_offset: 295125,
1686 logo_y_offset: 146304,
1687 },
1688 );
1689 doc.set_raw_header_with_images(
1690 banner,
1691 &[("rId1", &logo_img, "logo.png")],
1692 rdocx_oxml::header_footer::HdrFtrType::Default,
1693 );
1694 doc.set_margins(
1695 Length::twips(2000),
1696 Length::twips(1800),
1697 Length::twips(1440),
1698 Length::twips(1800),
1699 );
1700 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
1701
1702 // Footer with contact
1703 doc.set_footer(
1704 "Tensorbee | 123 Innovation Drive, Suite 400 | San Francisco, CA 94105 | tensorbee.com",
1705 );
1706
1707 // ── Sender Address ──
1708 doc.add_paragraph("Walter White");
1709 doc.add_paragraph("Chief Executive Officer");
1710 doc.add_paragraph("Tensorbee");
1711 doc.add_paragraph("123 Innovation Drive, Suite 400");
1712 doc.add_paragraph("San Francisco, CA 94105");
1713 doc.add_paragraph("");
1714 doc.add_paragraph("February 22, 2026");
1715
1716 doc.add_paragraph("");
1717
1718 // ── Recipient ──
1719 doc.add_paragraph("Ms. Sarah Chen");
1720 doc.add_paragraph("VP of Engineering");
1721 doc.add_paragraph("Meridian Dynamics LLC");
1722 doc.add_paragraph("456 Enterprise Boulevard");
1723 doc.add_paragraph("Austin, TX 78701");
1724
1725 doc.add_paragraph("");
1726
1727 // ── Subject ──
1728 {
1729 let mut p = doc.add_paragraph("");
1730 p.add_run("Re: Strategic Technology Partnership — Phase 2 Expansion")
1731 .bold(true);
1732 }
1733
1734 doc.add_paragraph("");
1735
1736 // ── Body ──
1737 doc.add_paragraph("Dear Ms. Chen,");
1738 doc.add_paragraph("");
1739
1740 doc.add_paragraph(
1741 "I am writing to express Tensorbee's enthusiasm for expanding our strategic technology \
1742 partnership with Meridian Dynamics. Following the successful completion of Phase 1, \
1743 which delivered a 40% improvement in your ML inference pipeline throughput, we believe \
1744 the foundation is firmly established for an ambitious Phase 2 engagement.",
1745 )
1746 .first_line_indent(Length::inches(0.5));
1747
1748 doc.add_paragraph(
1749 "During our recent executive review, your team highlighted three priority areas for \
1750 the next phase of collaboration:",
1751 )
1752 .first_line_indent(Length::inches(0.5));
1753
1754 doc.add_numbered_list_item(
1755 "Real-time anomaly detection for your financial transaction monitoring system, \
1756 targeting sub-100ms latency at 50,000 transactions per second.",
1757 0,
1758 );
1759 doc.add_numbered_list_item(
1760 "Federated learning infrastructure to enable model training across your distributed \
1761 data centers without centralizing sensitive financial data.",
1762 0,
1763 );
1764 doc.add_numbered_list_item(
1765 "MLOps automation to reduce your model deployment cycle from the current 5 days \
1766 to under 4 hours.",
1767 0,
1768 );
1769
1770 doc.add_paragraph(
1771 "Our engineering team has prepared a detailed technical proposal addressing each of \
1772 these areas. We have allocated a dedicated team of eight senior engineers, led by \
1773 our CTO, to ensure continuity with the Phase 1 team your organization has already \
1774 built a productive working relationship with.",
1775 )
1776 .first_line_indent(Length::inches(0.5));
1777
1778 doc.add_paragraph(
1779 "I would welcome the opportunity to present our Phase 2 proposal in person. My \
1780 assistant will reach out to coordinate a meeting at your convenience during the \
1781 first week of March.",
1782 )
1783 .first_line_indent(Length::inches(0.5));
1784
1785 doc.add_paragraph("");
1786
1787 doc.add_paragraph("Warm regards,");
1788 doc.add_paragraph("");
1789 doc.add_paragraph("");
1790
1791 // Signature line
1792 doc.add_paragraph("")
1793 .border_bottom(BorderStyle::Single, 4, "4A5568");
1794 {
1795 let mut p = doc.add_paragraph("");
1796 p.add_run("Walter White").bold(true).size(12.0);
1797 }
1798 {
1799 let mut p = doc.add_paragraph("");
1800 p.add_run("Chief Executive Officer, Tensorbee")
1801 .italic(true)
1802 .size(10.0)
1803 .color("666666");
1804 }
1805 {
1806 let mut p = doc.add_paragraph("");
1807 p.add_run("walter@tensorbee.com | +1 (415) 555-0199")
1808 .size(10.0)
1809 .color("888888");
1810 }
1811
1812 doc
1813}Set a footer from raw XML bytes with associated images.
Sourcepub fn set_header_image_with_background(
&mut self,
image_data: &[u8],
image_filename: &str,
width: Length,
height: Length,
bg_color: &str,
)
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.
Sourcepub fn set_first_page_header_image(
&mut self,
image_data: &[u8],
image_filename: &str,
width: Length,
height: Length,
)
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.
Sourcepub fn add_bullet_list_item(&mut self, text: &str, level: u32) -> Paragraph<'_>
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?
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
97fn generate_feature_showcase(_samples_dir: &Path) -> Document {
98 let mut doc = Document::new();
99
100 // ── Page Setup & Metadata ──
101 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
102 doc.set_margins(
103 Length::inches(1.0),
104 Length::inches(1.0),
105 Length::inches(1.0),
106 Length::inches(1.0),
107 );
108 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
109 doc.set_gutter(Length::twips(0));
110 doc.set_title("rdocx Feature Showcase");
111 doc.set_author("rdocx Sample Generator");
112 doc.set_subject("Comprehensive feature demonstration");
113 doc.set_keywords("rdocx, docx, rust, sample, showcase");
114
115 // Headers & Footers with different first page
116 doc.set_different_first_page(true);
117 doc.set_first_page_header("rdocx");
118 doc.set_first_page_footer("Feature Showcase — Cover Page");
119 doc.set_header("rdocx Feature Showcase");
120 doc.set_footer("Generated by rdocx");
121
122 // ── COVER PAGE ──
123 let bg = create_sample_png(612, 792, [20, 45, 90]);
124 doc.add_background_image(&bg, "cover_bg.png");
125
126 for _ in 0..3 {
127 doc.add_paragraph("");
128 }
129 {
130 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
131 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
132 }
133 {
134 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
135 p.add_run("Complete Feature Showcase")
136 .size(28.0)
137 .color("FFFFFF")
138 .italic(true);
139 }
140 doc.add_paragraph("");
141 {
142 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
143 p.add_run("Every feature of the rdocx Rust library")
144 .size(13.0)
145 .color("B0C4DE");
146 }
147 {
148 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
149 p.add_run("demonstrated in a single document.")
150 .size(13.0)
151 .color("B0C4DE");
152 }
153
154 // ── TABLE OF CONTENTS ──
155 doc.add_paragraph("").page_break_before(true);
156 doc.insert_toc(doc.content_count(), 3);
157
158 // ── SECTION 1: TEXT FORMATTING ──
159 doc.add_paragraph("").page_break_before(true);
160 doc.add_paragraph("1. Text Formatting").style("Heading1");
161
162 // Paragraph Alignment
163 doc.add_paragraph("1.1 Paragraph Alignment")
164 .style("Heading2");
165 doc.add_paragraph("Left-aligned paragraph (default).")
166 .alignment(Alignment::Left);
167 doc.add_paragraph("Center-aligned paragraph.")
168 .alignment(Alignment::Center);
169 doc.add_paragraph("Right-aligned paragraph.")
170 .alignment(Alignment::Right);
171 doc.add_paragraph(
172 "Justified paragraph — this text is long enough to demonstrate how justified alignment \
173 distributes extra space across word gaps so lines fill the full width of the text area.",
174 )
175 .alignment(Alignment::Justify);
176
177 doc.add_paragraph("");
178
179 // Run Formatting
180 doc.add_paragraph("1.2 Run Formatting").style("Heading2");
181 {
182 let mut p = doc.add_paragraph("");
183 p.add_run("Bold").bold(true);
184 p.add_run(" | ");
185 p.add_run("Italic").italic(true);
186 p.add_run(" | ");
187 p.add_run("Bold Italic").bold(true).italic(true);
188 p.add_run(" | ");
189 p.add_run("Underline").underline(true);
190 p.add_run(" | ");
191 p.add_run("Strikethrough").strike(true);
192 p.add_run(" | ");
193 p.add_run("Double Strike").double_strike(true);
194 }
195 {
196 let mut p = doc.add_paragraph("");
197 p.add_run("Red").color("FF0000");
198 p.add_run(" | ");
199 p.add_run("Blue").color("0000FF");
200 p.add_run(" | ");
201 p.add_run("Green").color("00AA00");
202 p.add_run(" | ");
203 p.add_run("Highlighted").highlight("FFFF00");
204 }
205 {
206 let mut p = doc.add_paragraph("");
207 p.add_run("8pt").size(8.0);
208 p.add_run(" | ");
209 p.add_run("11pt").size(11.0);
210 p.add_run(" | ");
211 p.add_run("16pt").size(16.0);
212 p.add_run(" | ");
213 p.add_run("24pt").size(24.0);
214 }
215 {
216 let mut p = doc.add_paragraph("");
217 p.add_run("Arial").font("Arial");
218 p.add_run(" | ");
219 p.add_run("Times New Roman").font("Times New Roman");
220 p.add_run(" | ");
221 p.add_run("Courier New").font("Courier New");
222 }
223 {
224 let mut p = doc.add_paragraph("");
225 p.add_run("H");
226 p.add_run("2").subscript();
227 p.add_run("O (subscript) | E=mc");
228 p.add_run("2").superscript();
229 p.add_run(" (superscript)");
230 }
231 {
232 let mut p = doc.add_paragraph("");
233 p.add_run("ALL CAPS").all_caps(true);
234 p.add_run(" | ");
235 p.add_run("Small Caps").small_caps(true);
236 p.add_run(" | ");
237 p.add_run("Expanded").character_spacing(Length::twips(40));
238 p.add_run(" | ");
239 p.add_run("Hidden text (not visible)").hidden(true);
240 }
241
242 doc.add_paragraph("");
243
244 // Underline Styles
245 doc.add_paragraph("1.3 Underline Styles").style("Heading2");
246 {
247 let mut p = doc.add_paragraph("");
248 p.add_run("Single ")
249 .underline_style(rdocx::UnderlineStyle::Single);
250 p.add_run("Double ")
251 .underline_style(rdocx::UnderlineStyle::Double);
252 p.add_run("Thick ")
253 .underline_style(rdocx::UnderlineStyle::Thick);
254 p.add_run("Dotted ")
255 .underline_style(rdocx::UnderlineStyle::Dotted);
256 p.add_run("Dash ")
257 .underline_style(rdocx::UnderlineStyle::Dash);
258 p.add_run("Wave ")
259 .underline_style(rdocx::UnderlineStyle::Wave);
260 }
261
262 // ── SECTION 2: PARAGRAPH FORMATTING ──
263 doc.add_paragraph("").page_break_before(true);
264 doc.add_paragraph("2. Paragraph Formatting")
265 .style("Heading1");
266
267 doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
268 doc.add_paragraph("Paragraph with light green shading")
269 .shading("E2EFDA");
270 doc.add_paragraph("Paragraph with bottom border")
271 .border_bottom(BorderStyle::Single, 6, "2E75B6");
272 doc.add_paragraph("Paragraph with all borders (red)")
273 .border_all(BorderStyle::Single, 4, "FF0000");
274
275 doc.add_paragraph("2.2 Indentation").style("Heading2");
276 doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
277 .indent_left(Length::inches(1.0))
278 .hanging_indent(Length::inches(0.5));
279 doc.add_paragraph("0.5-inch first-line indent on this paragraph")
280 .first_line_indent(Length::inches(0.5));
281 doc.add_paragraph("Both left and right indent (0.75 inches each)")
282 .indent_left(Length::inches(0.75))
283 .indent_right(Length::inches(0.75));
284
285 doc.add_paragraph("2.3 Spacing & Line Height")
286 .style("Heading2");
287 doc.add_paragraph("Extra space before (24pt) and after (12pt)")
288 .space_before(Length::pt(24.0))
289 .space_after(Length::pt(12.0));
290 doc.add_paragraph(
291 "Double line spacing paragraph. This text should have extra vertical space between \
292 lines to demonstrate line_spacing_multiple(2.0).",
293 )
294 .line_spacing_multiple(2.0);
295 doc.add_paragraph("Exact 20pt line spacing (fixed height).")
296 .line_spacing(20.0);
297
298 doc.add_paragraph("2.4 Pagination Controls")
299 .style("Heading2");
300 doc.add_paragraph("keep_with_next — this paragraph stays with the next")
301 .keep_with_next(true);
302 doc.add_paragraph("(This paragraph was kept with the one above.)");
303 doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
304 .keep_together(true);
305 doc.add_paragraph("widow_control — prevents widow/orphan lines")
306 .widow_control(true);
307
308 // ── SECTION 3: LISTS ──
309 doc.add_paragraph("").page_break_before(true);
310 doc.add_paragraph("3. Lists").style("Heading1");
311
312 doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
313 doc.add_bullet_list_item("First item", 0);
314 doc.add_bullet_list_item("Second item", 0);
315 doc.add_bullet_list_item("Nested level 1", 1);
316 doc.add_bullet_list_item("Nested level 2", 2);
317 doc.add_bullet_list_item("Back to level 1", 1);
318 doc.add_bullet_list_item("Third item", 0);
319
320 doc.add_paragraph("");
321
322 doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
323 doc.add_numbered_list_item("First numbered item", 0);
324 doc.add_numbered_list_item("Second numbered item", 0);
325 doc.add_numbered_list_item("Sub-item A", 1);
326 doc.add_numbered_list_item("Sub-item B", 1);
327 doc.add_numbered_list_item("Third numbered item", 0);
328
329 // ── SECTION 4: TAB STOPS ──
330 doc.add_paragraph("");
331 doc.add_paragraph("4. Tab Stops").style("Heading1");
332
333 doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
334 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
335 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
336 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
337 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
338 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
339
340 doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
341 doc.add_paragraph("Item\t\tPrice")
342 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
343 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
344 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
345 doc.add_paragraph("Widget\t\t$19.99")
346 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
347 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
348 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
349 doc.add_paragraph("Gadget\t\t$249.50")
350 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
351 .add_tab_stop_with_leader(
352 TabAlignment::Right,
353 Length::inches(4.0),
354 TabLeader::Underscore,
355 )
356 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
357
358 // ── SECTION 5: TABLES ──
359 doc.add_paragraph("").page_break_before(true);
360 doc.add_paragraph("5. Tables").style("Heading1");
361
362 doc.add_paragraph("5.1 Basic Table").style("Heading2");
363 {
364 let mut tbl = doc
365 .add_table(4, 3)
366 .borders(BorderStyle::Single, 4, "000000");
367 for col in 0..3 {
368 tbl.cell(0, col).unwrap().shading("2E75B6");
369 }
370 tbl.cell(0, 0).unwrap().set_text("Name");
371 tbl.cell(0, 1).unwrap().set_text("Role");
372 tbl.cell(0, 2).unwrap().set_text("Location");
373 tbl.cell(1, 0).unwrap().set_text("Walter White");
374 tbl.cell(1, 1).unwrap().set_text("CEO");
375 tbl.cell(1, 2).unwrap().set_text("Albuquerque");
376 tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
377 tbl.cell(2, 1).unwrap().set_text("CTO");
378 tbl.cell(2, 2).unwrap().set_text("Remote");
379 tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
380 tbl.cell(3, 1).unwrap().set_text("Security");
381 tbl.cell(3, 2).unwrap().set_text("Washington");
382 }
383
384 doc.add_paragraph("");
385
386 doc.add_paragraph("5.2 Column Span & Cell Shading")
387 .style("Heading2");
388 {
389 let mut tbl = doc
390 .add_table(3, 4)
391 .borders(BorderStyle::Single, 4, "000000")
392 .width_pct(100.0);
393 tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
394 tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
395 tbl.cell(1, 0).unwrap().set_text("Region");
396 tbl.cell(1, 0).unwrap().shading("D6E4F0");
397 tbl.cell(1, 1).unwrap().set_text("Q1");
398 tbl.cell(1, 1).unwrap().shading("D6E4F0");
399 tbl.cell(1, 2).unwrap().set_text("Q2");
400 tbl.cell(1, 2).unwrap().shading("D6E4F0");
401 tbl.cell(1, 3).unwrap().set_text("Total");
402 tbl.cell(1, 3).unwrap().shading("D6E4F0");
403 tbl.cell(2, 0).unwrap().set_text("Americas");
404 tbl.cell(2, 1).unwrap().set_text("$2.4M");
405 tbl.cell(2, 2).unwrap().set_text("$2.7M");
406 tbl.cell(2, 3).unwrap().set_text("$5.1M");
407 }
408
409 doc.add_paragraph("");
410
411 doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
412 {
413 let mut tbl = doc
414 .add_table(4, 3)
415 .borders(BorderStyle::Single, 4, "000000");
416 tbl.cell(0, 0).unwrap().set_text("Category");
417 tbl.cell(0, 0).unwrap().shading("E2EFDA");
418 tbl.cell(0, 1).unwrap().set_text("Item");
419 tbl.cell(0, 1).unwrap().shading("E2EFDA");
420 tbl.cell(0, 2).unwrap().set_text("Price");
421 tbl.cell(0, 2).unwrap().shading("E2EFDA");
422 tbl.cell(1, 0).unwrap().set_text("Hardware");
423 tbl.cell(1, 0).unwrap().v_merge_restart();
424 tbl.cell(1, 1).unwrap().set_text("Laptop");
425 tbl.cell(1, 2).unwrap().set_text("$1,200");
426 tbl.cell(2, 0).unwrap().v_merge_continue();
427 tbl.cell(2, 1).unwrap().set_text("Monitor");
428 tbl.cell(2, 2).unwrap().set_text("$450");
429 tbl.cell(3, 0).unwrap().set_text("Software");
430 tbl.cell(3, 1).unwrap().set_text("IDE License");
431 tbl.cell(3, 2).unwrap().set_text("$200/yr");
432 }
433
434 doc.add_paragraph("");
435
436 doc.add_paragraph("5.4 Nested Table").style("Heading2");
437 {
438 let mut tbl = doc
439 .add_table(2, 2)
440 .borders(BorderStyle::Single, 6, "2E75B6");
441 tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
442 tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
443 tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
444 {
445 let mut cell = tbl.cell(1, 1).unwrap();
446 cell.set_text("Nested table below:");
447 let mut nested = cell
448 .add_table(2, 2)
449 .borders(BorderStyle::Single, 2, "FF6600");
450 nested.cell(0, 0).unwrap().set_text("A");
451 nested.cell(0, 1).unwrap().set_text("B");
452 nested.cell(1, 0).unwrap().set_text("C");
453 nested.cell(1, 1).unwrap().set_text("D");
454 }
455 }
456
457 doc.add_paragraph("");
458
459 doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
460 .style("Heading2");
461 {
462 let mut tbl = doc
463 .add_table(2, 3)
464 .borders(BorderStyle::Single, 4, "666666");
465 tbl.row(0).unwrap().height(Length::pt(50.0)).header();
466 tbl.cell(0, 0).unwrap().set_text("Top");
467 tbl.cell(0, 0)
468 .unwrap()
469 .vertical_alignment(VerticalAlignment::Top)
470 .shading("FFF2CC");
471 tbl.cell(0, 1).unwrap().set_text("Center");
472 tbl.cell(0, 1)
473 .unwrap()
474 .vertical_alignment(VerticalAlignment::Center)
475 .shading("D9E2F3");
476 tbl.cell(0, 2).unwrap().set_text("Bottom");
477 tbl.cell(0, 2)
478 .unwrap()
479 .vertical_alignment(VerticalAlignment::Bottom)
480 .shading("E2EFDA");
481 tbl.row(1).unwrap().cant_split();
482 tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
483 tbl.cell(1, 0).unwrap().no_wrap();
484 tbl.cell(1, 1).unwrap().set_text("Fixed width");
485 tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
486 tbl.cell(1, 2).unwrap().set_text("Normal");
487 }
488
489 // ── SECTION 6: IMAGES ──
490 doc.add_paragraph("").page_break_before(true);
491 doc.add_paragraph("6. Images").style("Heading1");
492
493 doc.add_paragraph("6.1 Inline Image").style("Heading2");
494 doc.add_paragraph("A blue gradient image below:");
495 let img = create_sample_png(200, 50, [0, 80, 200]);
496 doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
497
498 doc.add_paragraph("");
499 doc.add_paragraph("6.2 Header Image").style("Heading2");
500 let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
501 doc.set_header_image(
502 &hdr_img,
503 "header_logo.png",
504 Length::inches(2.0),
505 Length::inches(0.2),
506 );
507 doc.add_paragraph("The header now contains an inline image (check the top of this page).");
508
509 doc.add_paragraph("");
510 doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
511
512 // ── SECTION 7: CONTENT MANIPULATION ──
513 doc.add_paragraph("").page_break_before(true);
514 doc.add_paragraph("7. Content Manipulation")
515 .style("Heading1");
516
517 doc.add_paragraph("7.1 Placeholder Replacement")
518 .style("Heading2");
519 doc.add_paragraph("Customer: {{customer}}");
520 doc.add_paragraph("Date: {{date}}");
521 doc.add_paragraph("Reference: {{ref_number}}");
522 {
523 let mut tbl = doc
524 .add_table(2, 2)
525 .borders(BorderStyle::Single, 4, "000000");
526 tbl.cell(0, 0).unwrap().set_text("Project");
527 tbl.cell(0, 0).unwrap().shading("D6E4F0");
528 tbl.cell(0, 1).unwrap().set_text("{{project}}");
529 tbl.cell(1, 0).unwrap().set_text("Status");
530 tbl.cell(1, 0).unwrap().shading("D6E4F0");
531 tbl.cell(1, 1).unwrap().set_text("{{status}}");
532 }
533
534 let mut replacements = HashMap::new();
535 replacements.insert("{{customer}}", "Tensorbee Inc.");
536 replacements.insert("{{date}}", "February 22, 2026");
537 replacements.insert("{{ref_number}}", "TB-2026-001");
538 replacements.insert("{{project}}", "Infrastructure Upgrade");
539 replacements.insert("{{status}}", "In Progress");
540 let n = doc.replace_all(&replacements);
541 doc.add_paragraph(&format!("({n} placeholders replaced above)"));
542
543 doc.add_paragraph("");
544
545 doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
546 doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
547 let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
548 doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
549
550 doc.add_paragraph("");
551
552 doc.add_paragraph("7.3 Content Insertion").style("Heading2");
553 doc.add_paragraph("Section A: First content.");
554 doc.add_paragraph("Section C: Third content.");
555 if let Some(idx) = doc.find_content_index("Section C") {
556 doc.insert_paragraph(
557 idx,
558 "Section B: Inserted between A and C via find_content_index().",
559 );
560 }
561
562 // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
563 doc.add_paragraph("").section_break(SectionBreak::NextPage);
564 doc.add_paragraph("8. Section Breaks & Orientation")
565 .style("Heading1");
566 doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
567
568 {
569 let mut tbl = doc
570 .add_table(3, 7)
571 .borders(BorderStyle::Single, 4, "2E75B6");
572 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
573 for (c, h) in headers.iter().enumerate() {
574 tbl.cell(0, c).unwrap().set_text(h);
575 tbl.cell(0, c).unwrap().shading("2E75B6");
576 }
577 let data = [
578 [
579 "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
580 ],
581 [
582 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
583 ],
584 ];
585 for (r, row) in data.iter().enumerate() {
586 for (c, v) in row.iter().enumerate() {
587 tbl.cell(r + 1, c).unwrap().set_text(v);
588 }
589 }
590 }
591
592 doc.add_paragraph("")
593 .section_break(SectionBreak::NextPage)
594 .section_landscape();
595
596 // ── SECTION 9: CUSTOM STYLES ──
597 doc.add_paragraph("9. Custom Styles").style("Heading1");
598 doc.add_style(
599 StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
600 .based_on("Normal")
601 .paragraph_properties(rdocx_oxml::properties::CT_PPr {
602 shading: Some(rdocx_oxml::properties::CT_Shd {
603 val: "clear".to_string(),
604 color: None,
605 fill: Some("FFF2CC".to_string()),
606 }),
607 ..Default::default()
608 })
609 .run_properties(rdocx_oxml::properties::CT_RPr {
610 bold: Some(true),
611 color: Some("C45911".to_string()),
612 ..Default::default()
613 }),
614 );
615 doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
616 .style("CustomHighlight");
617
618 doc.add_paragraph("");
619
620 // ── SECTION 10: DOCUMENT INTELLIGENCE ──
621 doc.add_paragraph("10. Document Intelligence API")
622 .style("Heading1");
623
624 let wc = doc.word_count();
625 let hc = doc.headings().len();
626 let ic = doc.images().len();
627 let lc = doc.links().len();
628 doc.add_paragraph(&format!("Word count: {wc}"));
629 doc.add_paragraph(&format!("Heading count: {hc}"));
630 doc.add_paragraph(&format!("Image count: {ic}"));
631 doc.add_paragraph(&format!("Link count: {lc}"));
632
633 let outline = doc.document_outline();
634 doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
635
636 let issues = doc.audit_accessibility();
637 doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
638 for issue in &issues {
639 doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
640 }
641
642 // ── SECTION 11: DOCUMENT MERGING ──
643 doc.add_paragraph("");
644 doc.add_paragraph("11. Document Merging").style("Heading1");
645 {
646 let mut other = Document::new();
647 other.add_paragraph("This paragraph was merged from another document using append().");
648 doc.append(&other);
649 }
650
651 doc.add_paragraph("");
652
653 // ── SUMMARY ──
654 doc.add_paragraph("Summary of Demonstrated Features")
655 .style("Heading1");
656 let features = [
657 "Page setup: size, margins, header/footer distance, gutter",
658 "Document metadata: title, author, subject, keywords",
659 "Headers and footers: text, images, different first page",
660 "Background images (full-page behind text)",
661 "Table of Contents generation with bookmarks",
662 "Text formatting: bold, italic, underline styles, strike, color, size, font",
663 "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
664 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
665 "Pagination controls: keep-with-next, keep-together, widow control",
666 "Bullet and numbered lists with nesting",
667 "Tab stops with dot/underscore/hyphen leaders",
668 "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
669 "Row properties: header rows, exact height, cant-split",
670 "Cell properties: width, no-wrap, vertical alignment",
671 "Inline images and header images",
672 "Placeholder replacement (single and batch)",
673 "Regex-based find and replace",
674 "Content insertion at specific positions",
675 "Section breaks with mixed portrait/landscape",
676 "Custom paragraph and character styles",
677 "Document intelligence: word count, headings, outline, images, links",
678 "Accessibility audit",
679 "Document merging (append)",
680 "Export: DOCX, PDF, HTML, Markdown",
681 ];
682 for f in &features {
683 doc.add_bullet_list_item(f, 0);
684 }
685 doc.add_paragraph("");
686 doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
687 .alignment(Alignment::Center)
688 .shading("E2EFDA")
689 .border_all(BorderStyle::Single, 2, "00AA00");
690
691 doc
692}
693
694// =============================================================================
695// 2. PROPOSAL DOCUMENT — Deep navy + gold scheme
696// =============================================================================
697fn generate_proposal(_samples_dir: &Path) -> Document {
698 let mut doc = Document::new();
699
700 // Colors: Navy #1B2A4A, Gold #C5922E, Light #F4F1EB
701 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
702 doc.set_margins(
703 Length::inches(1.0),
704 Length::inches(1.0),
705 Length::inches(1.0),
706 Length::inches(1.0),
707 );
708 doc.set_title("AI Platform Modernization Proposal");
709 doc.set_author("Walter White");
710 doc.set_subject("Technology Proposal");
711 doc.set_keywords("proposal, AI, modernization, Tensorbee");
712
713 // Banner header
714 let logo_img = create_logo_png(220, 48);
715 let banner = build_header_banner_xml(
716 "rId1",
717 &BannerOpts {
718 bg_color: "1B2A4A",
719 banner_width: 7772400,
720 banner_height: 914400,
721 logo_width: 2011680,
722 logo_height: 438912,
723 logo_x_offset: 295125,
724 logo_y_offset: 237744,
725 },
726 );
727 doc.set_raw_header_with_images(
728 banner,
729 &[("rId1", &logo_img, "logo.png")],
730 rdocx_oxml::header_footer::HdrFtrType::Default,
731 );
732 doc.set_different_first_page(true);
733 let cover_banner = build_header_banner_xml(
734 "rId1",
735 &BannerOpts {
736 bg_color: "C5922E",
737 banner_width: 7772400,
738 banner_height: 914400,
739 logo_width: 2011680,
740 logo_height: 438912,
741 logo_x_offset: 295125,
742 logo_y_offset: 237744,
743 },
744 );
745 doc.set_raw_header_with_images(
746 cover_banner,
747 &[("rId1", &logo_img, "logo.png")],
748 rdocx_oxml::header_footer::HdrFtrType::First,
749 );
750 doc.set_margins(
751 Length::twips(2292),
752 Length::twips(1440),
753 Length::twips(1440),
754 Length::twips(1440),
755 );
756 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
757 doc.set_footer("Tensorbee — Confidential");
758
759 // Custom styles
760 doc.add_style(
761 StyleBuilder::paragraph("ProposalTitle", "Proposal Title")
762 .based_on("Normal")
763 .run_properties(rdocx_oxml::properties::CT_RPr {
764 bold: Some(true),
765 font_ascii: Some("Georgia".to_string()),
766 font_hansi: Some("Georgia".to_string()),
767 sz: Some(rdocx_oxml::HalfPoint(56)), // half-points → 28pt
768 color: Some("1B2A4A".to_string()),
769 ..Default::default()
770 }),
771 );
772
773 // ── Cover Page ──
774 for _ in 0..4 {
775 doc.add_paragraph("");
776 }
777 doc.add_paragraph("AI Platform Modernization")
778 .style("ProposalTitle")
779 .alignment(Alignment::Center);
780 doc.add_paragraph("")
781 .alignment(Alignment::Center)
782 .border_bottom(BorderStyle::Single, 8, "C5922E");
783 {
784 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
785 p.add_run("Prepared for Global Financial Corp.")
786 .size(14.0)
787 .color("666666")
788 .italic(true);
789 }
790 {
791 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
792 p.add_run("Prepared by: Walter White, CEO — Tensorbee")
793 .size(12.0)
794 .color("888888");
795 }
796 {
797 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
798 p.add_run("February 22, 2026").size(12.0).color("888888");
799 }
800
801 // ── TOC ──
802 doc.add_paragraph("").page_break_before(true);
803 doc.add_paragraph("Table of Contents").style("Heading1");
804 // We'll add a manual-style TOC since the insert_toc goes at a specific position
805 doc.insert_toc(doc.content_count(), 2);
806
807 // ── Executive Summary ──
808 doc.add_paragraph("").page_break_before(true);
809 doc.add_paragraph("Executive Summary").style("Heading1");
810 doc.add_paragraph(
811 "Tensorbee proposes a comprehensive modernization of Global Financial Corp's AI platform, \
812 transitioning from legacy batch-processing systems to a real-time inference architecture. \
813 This transformation will reduce model deployment time from weeks to hours, improve \
814 prediction accuracy by an estimated 23%, and deliver $4.2M in annual operational savings.",
815 )
816 .first_line_indent(Length::inches(0.3));
817 doc.add_paragraph(
818 "The project spans three phases over 18 months, with a total investment of $2.8M. \
819 Tensorbee brings deep expertise in ML infrastructure, having successfully completed \
820 similar transformations for three Fortune 500 financial institutions.",
821 )
822 .first_line_indent(Length::inches(0.3));
823
824 // Key metrics table
825 doc.add_paragraph("");
826 {
827 let mut tbl = doc
828 .add_table(5, 2)
829 .borders(BorderStyle::Single, 4, "1B2A4A")
830 .width_pct(80.0);
831 tbl.cell(0, 0).unwrap().set_text("Key Metric");
832 tbl.cell(0, 0).unwrap().shading("1B2A4A");
833 tbl.cell(0, 1).unwrap().set_text("Value");
834 tbl.cell(0, 1).unwrap().shading("1B2A4A");
835 let rows = [
836 ("Total Investment", "$2.8M"),
837 ("Annual Savings", "$4.2M"),
838 ("ROI (Year 1)", "150%"),
839 ("Timeline", "18 months"),
840 ];
841 for (i, (k, v)) in rows.iter().enumerate() {
842 tbl.cell(i + 1, 0).unwrap().set_text(k);
843 if i % 2 == 0 {
844 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
845 }
846 tbl.cell(i + 1, 1).unwrap().set_text(v);
847 if i % 2 == 0 {
848 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
849 }
850 }
851 }
852
853 // ── Problem Statement ──
854 doc.add_paragraph("").page_break_before(true);
855 doc.add_paragraph("Problem Statement").style("Heading1");
856 doc.add_paragraph(
857 "Global Financial Corp's current AI infrastructure faces three critical challenges:",
858 );
859 doc.add_numbered_list_item("Deployment Latency — New models take 3-4 weeks to deploy to production, compared to the industry benchmark of 2-3 days.", 0);
860 doc.add_numbered_list_item("Scalability Constraints — The batch processing architecture cannot handle real-time inference demands during peak trading hours.", 0);
861 doc.add_numbered_list_item("Technical Debt — Legacy Python 2.7 codebases and manual deployment scripts create reliability risks and slow iteration speed.", 0);
862
863 // ── Proposed Solution ──
864 doc.add_paragraph("").page_break_before(true);
865 doc.add_paragraph("Proposed Solution").style("Heading1");
866
867 doc.add_paragraph("Phase 1: Foundation (Months 1-6)")
868 .style("Heading2");
869 doc.add_bullet_list_item("Deploy Kubernetes-based ML serving infrastructure", 0);
870 doc.add_bullet_list_item("Implement CI/CD pipeline for model deployment", 0);
871 doc.add_bullet_list_item("Migrate top 5 production models to new platform", 0);
872
873 doc.add_paragraph("Phase 2: Expansion (Months 7-12)")
874 .style("Heading2");
875 doc.add_bullet_list_item(
876 "Real-time feature engineering pipeline (Apache Kafka + Flink)",
877 0,
878 );
879 doc.add_bullet_list_item("A/B testing framework for model variants", 0);
880 doc.add_bullet_list_item("Automated model monitoring and drift detection", 0);
881
882 doc.add_paragraph("Phase 3: Optimization (Months 13-18)")
883 .style("Heading2");
884 doc.add_bullet_list_item("GPU inference optimization (TensorRT/ONNX Runtime)", 0);
885 doc.add_bullet_list_item("Multi-region deployment for latency reduction", 0);
886 doc.add_bullet_list_item(
887 "Self-service model deployment portal for data science teams",
888 0,
889 );
890
891 // ── Budget ──
892 doc.add_paragraph("Budget Breakdown").style("Heading1");
893 {
894 let mut tbl = doc
895 .add_table(6, 3)
896 .borders(BorderStyle::Single, 4, "1B2A4A")
897 .width_pct(100.0);
898 tbl.cell(0, 0).unwrap().set_text("Category");
899 tbl.cell(0, 0).unwrap().shading("1B2A4A");
900 tbl.cell(0, 1).unwrap().set_text("Cost");
901 tbl.cell(0, 1).unwrap().shading("1B2A4A");
902 tbl.cell(0, 2).unwrap().set_text("Notes");
903 tbl.cell(0, 2).unwrap().shading("1B2A4A");
904 let items = [
905 (
906 "Infrastructure",
907 "$450,000",
908 "Cloud compute + GPU instances",
909 ),
910 ("Engineering Services", "$1,600,000", "12 FTE x 18 months"),
911 (
912 "Software Licenses",
913 "$350,000",
914 "Kafka, monitoring, CI/CD tools",
915 ),
916 (
917 "Training & Enablement",
918 "$200,000",
919 "Team training, documentation",
920 ),
921 ("Contingency (10%)", "$200,000", "Risk buffer"),
922 ];
923 for (i, (cat, cost, note)) in items.iter().enumerate() {
924 tbl.cell(i + 1, 0).unwrap().set_text(cat);
925 tbl.cell(i + 1, 1).unwrap().set_text(cost);
926 tbl.cell(i + 1, 2).unwrap().set_text(note);
927 if i % 2 == 0 {
928 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
929 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
930 tbl.cell(i + 1, 2).unwrap().shading("F4F1EB");
931 }
932 }
933 }
934
935 // ── Closing ──
936 doc.add_paragraph("");
937 doc.add_paragraph("Next Steps").style("Heading1");
938 doc.add_numbered_list_item(
939 "Schedule technical deep-dive with GFC engineering team (Week 1)",
940 0,
941 );
942 doc.add_numbered_list_item("Finalize scope and sign SOW (Week 2-3)", 0);
943 doc.add_numbered_list_item("Kick off Phase 1 with joint planning session (Week 4)", 0);
944
945 doc.add_paragraph("");
946 {
947 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
948 p.add_run("Walter White")
949 .bold(true)
950 .size(14.0)
951 .color("1B2A4A");
952 }
953 {
954 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
955 p.add_run("CEO, Tensorbee | walter@tensorbee.com")
956 .size(10.0)
957 .color("888888");
958 }
959
960 doc
961}
962
963// =============================================================================
964// 3. QUOTE / BILL OF MATERIALS — Teal + orange scheme
965// =============================================================================
966fn generate_quote(_samples_dir: &Path) -> Document {
967 let mut doc = Document::new();
968
969 // Colors: Teal #008B8B, Dark #1A3C3C, Orange #E07020, Light #F0F8F8
970 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
971 doc.set_margins(
972 Length::inches(0.75),
973 Length::inches(0.75),
974 Length::inches(0.75),
975 Length::inches(0.75),
976 );
977 doc.set_title("Quotation QT-2026-0147");
978 doc.set_author("Walter White");
979 doc.set_keywords("quote, BOM, Tensorbee");
980
981 doc.set_header("Tensorbee — Quotation");
982 doc.set_footer("QT-2026-0147 | Page");
983
984 // ── Header Block ──
985 {
986 let mut p = doc.add_paragraph("").alignment(Alignment::Left);
987 p.add_run("TENSORBEE")
988 .bold(true)
989 .size(28.0)
990 .color("008B8B")
991 .font("Helvetica");
992 }
993 doc.add_paragraph("123 Innovation Drive, Suite 400")
994 .alignment(Alignment::Left);
995 doc.add_paragraph("San Francisco, CA 94105 | +1 (415) 555-0199")
996 .alignment(Alignment::Left);
997 doc.add_paragraph("")
998 .border_bottom(BorderStyle::Single, 8, "008B8B");
999
1000 // Quote meta
1001 doc.add_paragraph("");
1002 {
1003 let mut tbl = doc.add_table(4, 4).width_pct(100.0);
1004 tbl.cell(0, 0).unwrap().set_text("QUOTATION");
1005 tbl.cell(0, 0).unwrap().shading("008B8B").grid_span(2);
1006 tbl.cell(0, 2).unwrap().set_text("DATE");
1007 tbl.cell(0, 2).unwrap().shading("008B8B");
1008 tbl.cell(0, 3).unwrap().set_text("VALID UNTIL");
1009 tbl.cell(0, 3).unwrap().shading("008B8B");
1010 tbl.cell(1, 0).unwrap().set_text("Quote #:");
1011 tbl.cell(1, 1).unwrap().set_text("QT-2026-0147");
1012 tbl.cell(1, 2).unwrap().set_text("Feb 22, 2026");
1013 tbl.cell(1, 3).unwrap().set_text("Mar 22, 2026");
1014 tbl.cell(2, 0).unwrap().set_text("Prepared by:");
1015 tbl.cell(2, 1).unwrap().set_text("Walter White");
1016 tbl.cell(2, 2).unwrap().set_text("Payment:");
1017 tbl.cell(2, 3).unwrap().set_text("Net 30");
1018 tbl.cell(3, 0).unwrap().set_text("Customer:");
1019 tbl.cell(3, 1).unwrap().set_text("Meridian Dynamics LLC");
1020 tbl.cell(3, 2).unwrap().set_text("Currency:");
1021 tbl.cell(3, 3).unwrap().set_text("USD");
1022 }
1023
1024 doc.add_paragraph("");
1025
1026 // ── Bill of Materials ──
1027 {
1028 let mut p = doc.add_paragraph("");
1029 p.add_run("Bill of Materials")
1030 .bold(true)
1031 .size(16.0)
1032 .color("1A3C3C");
1033 }
1034 doc.add_paragraph("");
1035
1036 {
1037 let mut tbl = doc
1038 .add_table(10, 6)
1039 .borders(BorderStyle::Single, 2, "008B8B")
1040 .width_pct(100.0)
1041 .layout_fixed();
1042 // Headers
1043 let hdrs = [
1044 "#",
1045 "Part Number",
1046 "Description",
1047 "Qty",
1048 "Unit Price",
1049 "Total",
1050 ];
1051 for (c, h) in hdrs.iter().enumerate() {
1052 tbl.cell(0, c).unwrap().set_text(h);
1053 tbl.cell(0, c).unwrap().shading("008B8B");
1054 }
1055
1056 let items: Vec<(&str, &str, &str, &str, &str)> = vec![
1057 (
1058 "TB-GPU-A100",
1059 "NVIDIA A100 80GB GPU",
1060 "4",
1061 "$12,500.00",
1062 "$50,000.00",
1063 ),
1064 (
1065 "TB-SRV-R750",
1066 "Dell R750xa Server Chassis",
1067 "2",
1068 "$8,200.00",
1069 "$16,400.00",
1070 ),
1071 (
1072 "TB-RAM-256G",
1073 "256GB DDR5 ECC Memory Module",
1074 "8",
1075 "$890.00",
1076 "$7,120.00",
1077 ),
1078 (
1079 "TB-SSD-3840",
1080 "3.84TB NVMe U.2 SSD",
1081 "8",
1082 "$1,150.00",
1083 "$9,200.00",
1084 ),
1085 (
1086 "TB-NET-CX7",
1087 "ConnectX-7 200GbE NIC",
1088 "4",
1089 "$1,800.00",
1090 "$7,200.00",
1091 ),
1092 (
1093 "TB-SW-48P",
1094 "48-Port 100GbE Switch",
1095 "1",
1096 "$22,000.00",
1097 "$22,000.00",
1098 ),
1099 (
1100 "TB-CAB-RACK",
1101 "42U Server Rack + PDU",
1102 "1",
1103 "$4,500.00",
1104 "$4,500.00",
1105 ),
1106 (
1107 "TB-SVC-INST",
1108 "Installation & Configuration",
1109 "1",
1110 "$8,500.00",
1111 "$8,500.00",
1112 ),
1113 (
1114 "TB-SVC-SUPP",
1115 "3-Year Premium Support",
1116 "1",
1117 "$15,000.00",
1118 "$15,000.00",
1119 ),
1120 ];
1121
1122 for (i, (pn, desc, qty, unit, total)) in items.iter().enumerate() {
1123 let row = i + 1;
1124 tbl.cell(row, 0).unwrap().set_text(&format!("{}", i + 1));
1125 tbl.cell(row, 1).unwrap().set_text(pn);
1126 tbl.cell(row, 2).unwrap().set_text(desc);
1127 tbl.cell(row, 3).unwrap().set_text(qty);
1128 tbl.cell(row, 4).unwrap().set_text(unit);
1129 tbl.cell(row, 5).unwrap().set_text(total);
1130 if i % 2 == 0 {
1131 for c in 0..6 {
1132 tbl.cell(row, c).unwrap().shading("F0F8F8");
1133 }
1134 }
1135 }
1136 }
1137
1138 doc.add_paragraph("");
1139
1140 // ── Totals ──
1141 {
1142 let mut tbl = doc
1143 .add_table(4, 2)
1144 .borders(BorderStyle::Single, 2, "008B8B")
1145 .width(Length::inches(3.5))
1146 .alignment(Alignment::Right);
1147 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1148 tbl.cell(0, 1).unwrap().set_text("$139,920.00");
1149 tbl.cell(1, 0).unwrap().set_text("Shipping & Handling");
1150 tbl.cell(1, 1).unwrap().set_text("$2,500.00");
1151 tbl.cell(2, 0).unwrap().set_text("Tax (8.625%)");
1152 tbl.cell(2, 1).unwrap().set_text("$12,068.10");
1153 tbl.cell(3, 0).unwrap().set_text("TOTAL");
1154 tbl.cell(3, 0).unwrap().shading("E07020");
1155 tbl.cell(3, 1).unwrap().set_text("$154,488.10");
1156 tbl.cell(3, 1).unwrap().shading("E07020");
1157 }
1158
1159 doc.add_paragraph("");
1160
1161 // ── Terms & Conditions ──
1162 {
1163 let mut p = doc.add_paragraph("");
1164 p.add_run("Terms & Conditions")
1165 .bold(true)
1166 .size(14.0)
1167 .color("1A3C3C");
1168 }
1169 doc.add_numbered_list_item(
1170 "This quotation is valid for 30 calendar days from the date of issue.",
1171 0,
1172 );
1173 doc.add_numbered_list_item(
1174 "All prices are in USD and exclusive of applicable taxes unless stated.",
1175 0,
1176 );
1177 doc.add_numbered_list_item("Standard lead time is 4-6 weeks from PO receipt.", 0);
1178 doc.add_numbered_list_item(
1179 "Payment terms: Net 30 from invoice date. 2% discount for payment within 10 days.",
1180 0,
1181 );
1182 doc.add_numbered_list_item(
1183 "Warranty: 3-year manufacturer warranty on all hardware components.",
1184 0,
1185 );
1186 doc.add_numbered_list_item(
1187 "Returns subject to 15% restocking fee if initiated after 14 days.",
1188 0,
1189 );
1190
1191 doc.add_paragraph("");
1192 doc.add_paragraph("")
1193 .border_bottom(BorderStyle::Single, 4, "008B8B");
1194 doc.add_paragraph("");
1195 {
1196 let mut p = doc.add_paragraph("");
1197 p.add_run("Accepted by: ").bold(true);
1198 p.add_run("___________________________ Date: ___________");
1199 }
1200 {
1201 let mut p = doc.add_paragraph("");
1202 p.add_run("Print Name: ").bold(true);
1203 p.add_run("___________________________ Title: ___________");
1204 }
1205
1206 doc
1207}
1208
1209// =============================================================================
1210// 4. INVOICE — Crimson + charcoal scheme
1211// =============================================================================
1212fn generate_invoice(_samples_dir: &Path) -> Document {
1213 let mut doc = Document::new();
1214
1215 // Colors: Crimson #B22222, Charcoal #333333, Light #FAF0F0
1216 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1217 doc.set_margins(
1218 Length::inches(0.75),
1219 Length::inches(0.75),
1220 Length::inches(0.75),
1221 Length::inches(0.75),
1222 );
1223 doc.set_title("Invoice INV-2026-0392");
1224 doc.set_author("Walter White");
1225 doc.set_keywords("invoice, Tensorbee");
1226
1227 doc.set_footer("Tensorbee — Thank you for your business!");
1228
1229 // ── Company & Invoice Header ──
1230 {
1231 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1232 // Company name left, INVOICE right
1233 {
1234 let mut cell = tbl.cell(0, 0).unwrap();
1235 let mut p = cell.add_paragraph("");
1236 p.add_run("TENSORBEE")
1237 .bold(true)
1238 .size(32.0)
1239 .color("B22222")
1240 .font("Helvetica");
1241 }
1242 {
1243 let mut cell = tbl.cell(0, 1).unwrap();
1244 let mut p = cell.add_paragraph("");
1245 p.add_run("INVOICE").bold(true).size(32.0).color("333333");
1246 }
1247 tbl.cell(1, 0)
1248 .unwrap()
1249 .set_text("123 Innovation Drive, Suite 400");
1250 tbl.cell(1, 1).unwrap().set_text("Invoice #: INV-2026-0392");
1251 tbl.cell(2, 0).unwrap().set_text("San Francisco, CA 94105");
1252 tbl.cell(2, 1).unwrap().set_text("Date: February 22, 2026");
1253 tbl.cell(3, 0).unwrap().set_text("walter@tensorbee.com");
1254 tbl.cell(3, 1).unwrap().set_text("Due Date: March 24, 2026");
1255 }
1256
1257 doc.add_paragraph("")
1258 .border_bottom(BorderStyle::Thick, 8, "B22222");
1259 doc.add_paragraph("");
1260
1261 // ── Bill To / Ship To ──
1262 {
1263 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1264 tbl.cell(0, 0).unwrap().set_text("BILL TO");
1265 tbl.cell(0, 0).unwrap().shading("B22222");
1266 tbl.cell(0, 1).unwrap().set_text("SHIP TO");
1267 tbl.cell(0, 1).unwrap().shading("B22222");
1268 tbl.cell(1, 0).unwrap().set_text("Meridian Dynamics LLC");
1269 tbl.cell(1, 1).unwrap().set_text("Meridian Dynamics LLC");
1270 tbl.cell(2, 0).unwrap().set_text("456 Enterprise Blvd");
1271 tbl.cell(2, 1).unwrap().set_text("Attn: Server Room B");
1272 tbl.cell(3, 0).unwrap().set_text("Austin, TX 78701");
1273 tbl.cell(3, 1)
1274 .unwrap()
1275 .set_text("456 Enterprise Blvd, Austin, TX 78701");
1276 }
1277
1278 doc.add_paragraph("");
1279
1280 // ── Line Items ──
1281 {
1282 let mut tbl = doc
1283 .add_table(8, 5)
1284 .borders(BorderStyle::Single, 2, "B22222")
1285 .width_pct(100.0);
1286 let hdrs = ["Description", "Qty", "Unit Price", "Tax", "Amount"];
1287 for (c, h) in hdrs.iter().enumerate() {
1288 tbl.cell(0, c).unwrap().set_text(h);
1289 tbl.cell(0, c).unwrap().shading("B22222");
1290 }
1291 let items = [
1292 (
1293 "ML Infrastructure Setup — Phase 1",
1294 "1",
1295 "$45,000.00",
1296 "$3,881.25",
1297 "$48,881.25",
1298 ),
1299 (
1300 "Data Pipeline Development (160 hrs)",
1301 "160",
1302 "$225.00",
1303 "$3,105.00",
1304 "$39,105.00",
1305 ),
1306 (
1307 "GPU Cluster Configuration",
1308 "1",
1309 "$12,000.00",
1310 "$1,035.00",
1311 "$13,035.00",
1312 ),
1313 (
1314 "API Gateway Implementation",
1315 "1",
1316 "$18,500.00",
1317 "$1,595.63",
1318 "$20,095.63",
1319 ),
1320 (
1321 "Load Testing & QA (80 hrs)",
1322 "80",
1323 "$195.00",
1324 "$1,345.50",
1325 "$16,945.50",
1326 ),
1327 (
1328 "Documentation & Training",
1329 "1",
1330 "$8,000.00",
1331 "$690.00",
1332 "$8,690.00",
1333 ),
1334 (
1335 "Project Management (3 months)",
1336 "3",
1337 "$6,500.00",
1338 "$1,679.63",
1339 "$21,179.63",
1340 ),
1341 ];
1342 for (i, (desc, qty, unit, tax, amt)) in items.iter().enumerate() {
1343 let r = i + 1;
1344 tbl.cell(r, 0).unwrap().set_text(desc);
1345 tbl.cell(r, 1).unwrap().set_text(qty);
1346 tbl.cell(r, 2).unwrap().set_text(unit);
1347 tbl.cell(r, 3).unwrap().set_text(tax);
1348 tbl.cell(r, 4).unwrap().set_text(amt);
1349 if i % 2 == 0 {
1350 for c in 0..5 {
1351 tbl.cell(r, c).unwrap().shading("FAF0F0");
1352 }
1353 }
1354 }
1355 }
1356
1357 doc.add_paragraph("");
1358
1359 // ── Totals ──
1360 {
1361 let mut tbl = doc
1362 .add_table(4, 2)
1363 .borders(BorderStyle::Single, 2, "B22222")
1364 .width(Length::inches(3.0))
1365 .alignment(Alignment::Right);
1366 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1367 tbl.cell(0, 1).unwrap().set_text("$154,600.00");
1368 tbl.cell(1, 0).unwrap().set_text("Tax (8.625%)");
1369 tbl.cell(1, 1).unwrap().set_text("$13,334.25");
1370 tbl.cell(2, 0).unwrap().set_text("Discount (5%)");
1371 tbl.cell(2, 1).unwrap().set_text("-$7,730.00");
1372 tbl.cell(3, 0).unwrap().set_text("AMOUNT DUE");
1373 tbl.cell(3, 0).unwrap().shading("B22222");
1374 tbl.cell(3, 1).unwrap().set_text("$160,204.25");
1375 tbl.cell(3, 1).unwrap().shading("B22222");
1376 }
1377
1378 doc.add_paragraph("");
1379 doc.add_paragraph("");
1380
1381 // ── Payment Details ──
1382 {
1383 let mut p = doc.add_paragraph("");
1384 p.add_run("Payment Details")
1385 .bold(true)
1386 .size(14.0)
1387 .color("333333");
1388 }
1389 doc.add_paragraph("Bank: Silicon Valley Bank")
1390 .indent_left(Length::inches(0.3));
1391 doc.add_paragraph("Account: Tensorbee Inc. — 0847-2953-1120")
1392 .indent_left(Length::inches(0.3));
1393 doc.add_paragraph("Routing: 121140399")
1394 .indent_left(Length::inches(0.3));
1395 doc.add_paragraph("Swift: SVBKUS6S")
1396 .indent_left(Length::inches(0.3));
1397
1398 doc.add_paragraph("");
1399
1400 doc.add_paragraph("Please include invoice number INV-2026-0392 in the payment reference.")
1401 .shading("FAF0F0")
1402 .border_all(BorderStyle::Single, 2, "B22222");
1403
1404 doc
1405}
1406
1407// =============================================================================
1408// 5. REPORT — Forest green + earth tones, with images & hierarchical sections
1409// =============================================================================
1410fn generate_report(_samples_dir: &Path) -> Document {
1411 let mut doc = Document::new();
1412
1413 // Colors: Forest #2D5016, Sage #6B8E23, Earth #8B7355, Cream #FFFAF0
1414 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1415 doc.set_margins(
1416 Length::inches(1.0),
1417 Length::inches(1.0),
1418 Length::inches(1.0),
1419 Length::inches(1.0),
1420 );
1421 doc.set_title("Q4 2025 Environmental Impact Report");
1422 doc.set_author("Walter White");
1423 doc.set_subject("Quarterly Environmental Report");
1424 doc.set_keywords("environment, sustainability, report, Tensorbee");
1425
1426 doc.set_different_first_page(true);
1427 doc.set_first_page_header("");
1428 doc.set_header("Tensorbee — Q4 2025 Environmental Impact Report");
1429 doc.set_footer("CONFIDENTIAL — Page");
1430
1431 // ── Cover ──
1432 let cover_bg = create_sample_png(612, 792, [20, 50, 15]);
1433 doc.add_background_image(&cover_bg, "report_cover.png");
1434
1435 for _ in 0..5 {
1436 doc.add_paragraph("");
1437 }
1438 {
1439 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1440 p.add_run("Q4 2025")
1441 .bold(true)
1442 .size(48.0)
1443 .color("FFFFFF")
1444 .font("Georgia");
1445 }
1446 {
1447 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1448 p.add_run("Environmental Impact Report")
1449 .size(24.0)
1450 .color("90EE90")
1451 .font("Georgia")
1452 .italic(true);
1453 }
1454 doc.add_paragraph("");
1455 {
1456 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1457 p.add_run("Tensorbee — Sustainability Division")
1458 .size(14.0)
1459 .color("C0C0C0");
1460 }
1461 {
1462 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1463 p.add_run("Prepared by Walter White, Chief Sustainability Officer")
1464 .size(11.0)
1465 .color("AAAAAA");
1466 }
1467
1468 // ── TOC ──
1469 doc.add_paragraph("").page_break_before(true);
1470 doc.insert_toc(doc.content_count(), 3);
1471
1472 // ── Section 1: Executive Overview ──
1473 doc.add_paragraph("").page_break_before(true);
1474 doc.add_paragraph("Executive Overview").style("Heading1");
1475 doc.add_paragraph(
1476 "This report presents Tensorbee's environmental performance for Q4 2025. Our \
1477 sustainability initiatives have yielded a 34% reduction in carbon emissions compared \
1478 to Q4 2024, exceeding our target of 25%. Key achievements include the transition to \
1479 100% renewable energy in our primary data centers and the launch of our carbon offset \
1480 marketplace.",
1481 )
1482 .first_line_indent(Length::inches(0.3));
1483
1484 // Image: performance chart
1485 let chart_img = create_chart_png(400, 200);
1486 doc.add_paragraph("");
1487 doc.add_picture(
1488 &chart_img,
1489 "performance_chart.png",
1490 Length::inches(5.0),
1491 Length::inches(2.5),
1492 )
1493 .alignment(Alignment::Center);
1494 {
1495 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1496 p.add_run("Figure 1: Quarterly Carbon Emissions (tonnes CO2e)")
1497 .italic(true)
1498 .size(9.0)
1499 .color("666666");
1500 }
1501
1502 // ── Section 2: Energy Consumption ──
1503 doc.add_paragraph("").page_break_before(true);
1504 doc.add_paragraph("Energy Consumption").style("Heading1");
1505
1506 doc.add_paragraph("Data Center Operations")
1507 .style("Heading2");
1508 doc.add_paragraph(
1509 "Our three primary data centers consumed a combined 4.2 GWh in Q4 2025, \
1510 a 12% reduction from Q3 2025 driven by improved cooling efficiency and \
1511 server consolidation.",
1512 );
1513
1514 // Data table
1515 {
1516 let mut tbl = doc
1517 .add_table(5, 4)
1518 .borders(BorderStyle::Single, 4, "2D5016")
1519 .width_pct(100.0);
1520 let hdrs = ["Data Center", "Capacity (MW)", "Usage (GWh)", "PUE"];
1521 for (c, h) in hdrs.iter().enumerate() {
1522 tbl.cell(0, c).unwrap().set_text(h);
1523 tbl.cell(0, c).unwrap().shading("2D5016");
1524 }
1525 let rows = [
1526 ("San Francisco (Primary)", "3.2", "1.8", "1.12"),
1527 ("Dublin (EU)", "2.1", "1.2", "1.18"),
1528 ("Singapore (APAC)", "1.8", "1.2", "1.24"),
1529 ("TOTAL", "7.1", "4.2", "1.17 avg"),
1530 ];
1531 for (i, (dc, cap, use_, pue)) in rows.iter().enumerate() {
1532 tbl.cell(i + 1, 0).unwrap().set_text(dc);
1533 tbl.cell(i + 1, 1).unwrap().set_text(cap);
1534 tbl.cell(i + 1, 2).unwrap().set_text(use_);
1535 tbl.cell(i + 1, 3).unwrap().set_text(pue);
1536 if i == 3 {
1537 for c in 0..4 {
1538 tbl.cell(i + 1, c).unwrap().shading("E2EFDA");
1539 }
1540 }
1541 }
1542 }
1543
1544 doc.add_paragraph("");
1545
1546 doc.add_paragraph("Renewable Energy Mix").style("Heading2");
1547 doc.add_paragraph("Breakdown of energy sources across all facilities:");
1548
1549 doc.add_bullet_list_item("Solar PV: 42% (1.76 GWh)", 0);
1550 doc.add_bullet_list_item("Wind Power Purchase Agreements: 38% (1.60 GWh)", 0);
1551 doc.add_bullet_list_item("Hydroelectric: 12% (0.50 GWh)", 0);
1552 doc.add_bullet_list_item("Grid (non-renewable): 8% (0.34 GWh)", 0);
1553
1554 // ── Section 3: Water & Waste ──
1555 doc.add_paragraph("Water & Waste Management")
1556 .style("Heading1");
1557
1558 doc.add_paragraph("Water Usage").style("Heading2");
1559 doc.add_paragraph(
1560 "Total water consumption was 12.4 million gallons, a 15% reduction from Q3. \
1561 Our closed-loop cooling systems now recycle 78% of water used in cooling operations.",
1562 );
1563
1564 doc.add_paragraph("Waste Reduction").style("Heading2");
1565 doc.add_paragraph("E-waste management results for Q4:")
1566 .keep_with_next(true);
1567 {
1568 let mut tbl = doc
1569 .add_table(4, 3)
1570 .borders(BorderStyle::Single, 2, "6B8E23")
1571 .width_pct(80.0);
1572 tbl.cell(0, 0).unwrap().set_text("Category");
1573 tbl.cell(0, 0).unwrap().shading("6B8E23");
1574 tbl.cell(0, 1).unwrap().set_text("Weight (kg)");
1575 tbl.cell(0, 1).unwrap().shading("6B8E23");
1576 tbl.cell(0, 2).unwrap().set_text("Recycled %");
1577 tbl.cell(0, 2).unwrap().shading("6B8E23");
1578 let rows = [
1579 ("Server Hardware", "2,450", "94%"),
1580 ("Networking Equipment", "820", "91%"),
1581 ("Storage Media", "340", "99%"),
1582 ];
1583 for (i, (cat, wt, pct)) in rows.iter().enumerate() {
1584 tbl.cell(i + 1, 0).unwrap().set_text(cat);
1585 tbl.cell(i + 1, 1).unwrap().set_text(wt);
1586 tbl.cell(i + 1, 2).unwrap().set_text(pct);
1587 }
1588 }
1589
1590 // ── Section 4: Initiatives ──
1591 doc.add_paragraph("").page_break_before(true);
1592 doc.add_paragraph("Q1 2026 Initiatives").style("Heading1");
1593
1594 doc.add_paragraph("Planned Programs").style("Heading2");
1595 doc.add_numbered_list_item(
1596 "Deploy on-site battery storage at SF data center (2 MWh capacity)",
1597 0,
1598 );
1599 doc.add_numbered_list_item("Pilot immersion cooling in Dublin facility", 0);
1600 doc.add_numbered_list_item("Launch employee commute carbon offset program", 0);
1601 doc.add_numbered_list_item("Achieve ISO 14001 certification for Singapore facility", 0);
1602
1603 doc.add_paragraph("Investment Targets").style("Heading2");
1604 doc.add_paragraph("Sustainability CapEx allocation for FY2026:")
1605 .keep_with_next(true);
1606 {
1607 let mut tbl = doc
1608 .add_table(5, 2)
1609 .borders(BorderStyle::Single, 4, "2D5016");
1610 tbl.cell(0, 0).unwrap().set_text("Initiative");
1611 tbl.cell(0, 0).unwrap().shading("2D5016");
1612 tbl.cell(0, 1).unwrap().set_text("Budget");
1613 tbl.cell(0, 1).unwrap().shading("2D5016");
1614 let rows = [
1615 ("Battery Storage", "$1.2M"),
1616 ("Immersion Cooling Pilot", "$800K"),
1617 ("Solar Panel Expansion", "$2.1M"),
1618 ("Carbon Credits", "$500K"),
1619 ];
1620 for (i, (init, budget)) in rows.iter().enumerate() {
1621 tbl.cell(i + 1, 0).unwrap().set_text(init);
1622 tbl.cell(i + 1, 1).unwrap().set_text(budget);
1623 if i % 2 == 0 {
1624 tbl.cell(i + 1, 0).unwrap().shading("FFFAF0");
1625 tbl.cell(i + 1, 1).unwrap().shading("FFFAF0");
1626 }
1627 }
1628 }
1629
1630 // Image: sustainability roadmap
1631 let roadmap_img = create_sample_png(500, 100, [40, 80, 30]);
1632 doc.add_paragraph("");
1633 doc.add_picture(
1634 &roadmap_img,
1635 "roadmap.png",
1636 Length::inches(6.0),
1637 Length::inches(1.2),
1638 )
1639 .alignment(Alignment::Center);
1640 {
1641 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1642 p.add_run("Figure 2: 2026 Sustainability Roadmap")
1643 .italic(true)
1644 .size(9.0)
1645 .color("666666");
1646 }
1647
1648 doc.add_paragraph("");
1649 doc.add_paragraph(
1650 "For questions about this report, contact Walter White at sustainability@tensorbee.com.",
1651 )
1652 .shading("FFFAF0")
1653 .border_all(BorderStyle::Single, 2, "2D5016");
1654
1655 doc
1656}
1657
1658// =============================================================================
1659// 6. LETTER — Slate blue + silver scheme
1660// =============================================================================
1661fn generate_letter(_samples_dir: &Path) -> Document {
1662 let mut doc = Document::new();
1663
1664 // Colors: Slate #4A5568, Blue accent #3182CE
1665 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1666 doc.set_margins(
1667 Length::inches(1.25),
1668 Length::inches(1.25),
1669 Length::inches(1.25),
1670 Length::inches(1.25),
1671 );
1672 doc.set_title("Business Letter");
1673 doc.set_author("Walter White");
1674
1675 // Banner header using raw XML
1676 let logo_img = create_logo_png(220, 48);
1677 let banner = build_header_banner_xml(
1678 "rId1",
1679 &BannerOpts {
1680 bg_color: "4A5568",
1681 banner_width: 7772400,
1682 banner_height: 731520, // ~0.8"
1683 logo_width: 2011680,
1684 logo_height: 438912,
1685 logo_x_offset: 295125,
1686 logo_y_offset: 146304,
1687 },
1688 );
1689 doc.set_raw_header_with_images(
1690 banner,
1691 &[("rId1", &logo_img, "logo.png")],
1692 rdocx_oxml::header_footer::HdrFtrType::Default,
1693 );
1694 doc.set_margins(
1695 Length::twips(2000),
1696 Length::twips(1800),
1697 Length::twips(1440),
1698 Length::twips(1800),
1699 );
1700 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
1701
1702 // Footer with contact
1703 doc.set_footer(
1704 "Tensorbee | 123 Innovation Drive, Suite 400 | San Francisco, CA 94105 | tensorbee.com",
1705 );
1706
1707 // ── Sender Address ──
1708 doc.add_paragraph("Walter White");
1709 doc.add_paragraph("Chief Executive Officer");
1710 doc.add_paragraph("Tensorbee");
1711 doc.add_paragraph("123 Innovation Drive, Suite 400");
1712 doc.add_paragraph("San Francisco, CA 94105");
1713 doc.add_paragraph("");
1714 doc.add_paragraph("February 22, 2026");
1715
1716 doc.add_paragraph("");
1717
1718 // ── Recipient ──
1719 doc.add_paragraph("Ms. Sarah Chen");
1720 doc.add_paragraph("VP of Engineering");
1721 doc.add_paragraph("Meridian Dynamics LLC");
1722 doc.add_paragraph("456 Enterprise Boulevard");
1723 doc.add_paragraph("Austin, TX 78701");
1724
1725 doc.add_paragraph("");
1726
1727 // ── Subject ──
1728 {
1729 let mut p = doc.add_paragraph("");
1730 p.add_run("Re: Strategic Technology Partnership — Phase 2 Expansion")
1731 .bold(true);
1732 }
1733
1734 doc.add_paragraph("");
1735
1736 // ── Body ──
1737 doc.add_paragraph("Dear Ms. Chen,");
1738 doc.add_paragraph("");
1739
1740 doc.add_paragraph(
1741 "I am writing to express Tensorbee's enthusiasm for expanding our strategic technology \
1742 partnership with Meridian Dynamics. Following the successful completion of Phase 1, \
1743 which delivered a 40% improvement in your ML inference pipeline throughput, we believe \
1744 the foundation is firmly established for an ambitious Phase 2 engagement.",
1745 )
1746 .first_line_indent(Length::inches(0.5));
1747
1748 doc.add_paragraph(
1749 "During our recent executive review, your team highlighted three priority areas for \
1750 the next phase of collaboration:",
1751 )
1752 .first_line_indent(Length::inches(0.5));
1753
1754 doc.add_numbered_list_item(
1755 "Real-time anomaly detection for your financial transaction monitoring system, \
1756 targeting sub-100ms latency at 50,000 transactions per second.",
1757 0,
1758 );
1759 doc.add_numbered_list_item(
1760 "Federated learning infrastructure to enable model training across your distributed \
1761 data centers without centralizing sensitive financial data.",
1762 0,
1763 );
1764 doc.add_numbered_list_item(
1765 "MLOps automation to reduce your model deployment cycle from the current 5 days \
1766 to under 4 hours.",
1767 0,
1768 );
1769
1770 doc.add_paragraph(
1771 "Our engineering team has prepared a detailed technical proposal addressing each of \
1772 these areas. We have allocated a dedicated team of eight senior engineers, led by \
1773 our CTO, to ensure continuity with the Phase 1 team your organization has already \
1774 built a productive working relationship with.",
1775 )
1776 .first_line_indent(Length::inches(0.5));
1777
1778 doc.add_paragraph(
1779 "I would welcome the opportunity to present our Phase 2 proposal in person. My \
1780 assistant will reach out to coordinate a meeting at your convenience during the \
1781 first week of March.",
1782 )
1783 .first_line_indent(Length::inches(0.5));
1784
1785 doc.add_paragraph("");
1786
1787 doc.add_paragraph("Warm regards,");
1788 doc.add_paragraph("");
1789 doc.add_paragraph("");
1790
1791 // Signature line
1792 doc.add_paragraph("")
1793 .border_bottom(BorderStyle::Single, 4, "4A5568");
1794 {
1795 let mut p = doc.add_paragraph("");
1796 p.add_run("Walter White").bold(true).size(12.0);
1797 }
1798 {
1799 let mut p = doc.add_paragraph("");
1800 p.add_run("Chief Executive Officer, Tensorbee")
1801 .italic(true)
1802 .size(10.0)
1803 .color("666666");
1804 }
1805 {
1806 let mut p = doc.add_paragraph("");
1807 p.add_run("walter@tensorbee.com | +1 (415) 555-0199")
1808 .size(10.0)
1809 .color("888888");
1810 }
1811
1812 doc
1813}
1814
1815// =============================================================================
1816// 7. EMPLOYMENT CONTRACT — Purple + charcoal scheme
1817// =============================================================================
1818fn generate_contract(_samples_dir: &Path) -> Document {
1819 let mut doc = Document::new();
1820
1821 // Colors: Purple #5B2C6F, Plum #8E44AD, Gray #2C3E50
1822 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1823 doc.set_margins(
1824 Length::inches(1.25),
1825 Length::inches(1.0),
1826 Length::inches(1.0),
1827 Length::inches(1.0),
1828 );
1829 doc.set_title("Employment Agreement");
1830 doc.set_author("Tensorbee HR Department");
1831 doc.set_subject("Employment Contract — Walter White");
1832 doc.set_keywords("employment, contract, agreement, Tensorbee");
1833
1834 doc.set_header("TENSORBEE — Employment Agreement");
1835 doc.set_footer("Employment Agreement — Walter White — Page");
1836
1837 // ── Title Block ──
1838 doc.add_paragraph("")
1839 .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1840 {
1841 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1842 p.add_run("EMPLOYMENT AGREEMENT")
1843 .bold(true)
1844 .size(24.0)
1845 .color("5B2C6F")
1846 .font("Georgia");
1847 }
1848 doc.add_paragraph("")
1849 .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1850 doc.add_paragraph("");
1851
1852 // ── Parties ──
1853 doc.add_paragraph(
1854 "This Employment Agreement (\"Agreement\") is entered into as of February 22, 2026 \
1855 (\"Effective Date\"), by and between:",
1856 );
1857
1858 doc.add_paragraph("");
1859
1860 {
1861 let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1862 p.add_run("EMPLOYER: ").bold(true);
1863 p.add_run(
1864 "Tensorbee, Inc., a Delaware corporation with its principal place of business at \
1865 123 Innovation Drive, Suite 400, San Francisco, CA 94105 (\"Company\")",
1866 );
1867 }
1868 doc.add_paragraph("");
1869 {
1870 let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1871 p.add_run("EMPLOYEE: ").bold(true);
1872 p.add_run(
1873 "Walter White, residing at 308 Negra Arroyo Lane, Albuquerque, NM 87104 (\"Employee\")",
1874 );
1875 }
1876
1877 doc.add_paragraph("");
1878 doc.add_paragraph("The Company and Employee are collectively referred to as the \"Parties.\"");
1879 doc.add_paragraph("");
1880
1881 // ── Article 1: Position and Duties ──
1882 doc.add_paragraph("Article 1 — Position and Duties")
1883 .style("Heading1");
1884 {
1885 let mut p = doc.add_paragraph("");
1886 p.add_run("1.1 ").bold(true);
1887 p.add_run("The Company hereby employs the Employee as ");
1888 p.add_run("Chief Executive Officer (CEO)")
1889 .bold(true)
1890 .italic(true);
1891 p.add_run(", reporting directly to the Board of Directors.");
1892 }
1893 {
1894 let mut p = doc.add_paragraph("");
1895 p.add_run("1.2 ").bold(true);
1896 p.add_run("The Employee shall devote full working time, attention, and best efforts to \
1897 the performance of duties as reasonably assigned by the Board, including but not limited to:");
1898 }
1899 doc.add_bullet_list_item(
1900 "Setting and executing the Company's strategic vision and business plan",
1901 0,
1902 );
1903 doc.add_bullet_list_item(
1904 "Overseeing all operations, engineering, and commercial activities",
1905 0,
1906 );
1907 doc.add_bullet_list_item(
1908 "Representing the Company to investors, customers, and the public",
1909 0,
1910 );
1911 doc.add_bullet_list_item("Recruiting, developing, and retaining key talent", 0);
1912
1913 {
1914 let mut p = doc.add_paragraph("");
1915 p.add_run("1.3 ").bold(true);
1916 p.add_run(
1917 "The Employee's primary work location shall be the Company's San Francisco \
1918 headquarters, with reasonable travel as required by business needs.",
1919 );
1920 }
1921
1922 // ── Article 2: Compensation ──
1923 doc.add_paragraph("Article 2 — Compensation and Benefits")
1924 .style("Heading1");
1925 {
1926 let mut p = doc.add_paragraph("");
1927 p.add_run("2.1 Base Salary. ").bold(true);
1928 p.add_run("The Company shall pay the Employee an annual base salary of ");
1929 p.add_run("$375,000.00").bold(true);
1930 p.add_run(" (Three Hundred Seventy-Five Thousand Dollars), payable in accordance with the \
1931 Company's standard payroll schedule, less applicable withholdings and deductions.");
1932 }
1933 {
1934 let mut p = doc.add_paragraph("");
1935 p.add_run("2.2 Annual Bonus. ").bold(true);
1936 p.add_run("The Employee shall be eligible for an annual performance bonus of up to ");
1937 p.add_run("40%").bold(true);
1938 p.add_run(
1939 " of base salary, based on achievement of mutually agreed performance objectives.",
1940 );
1941 }
1942 {
1943 let mut p = doc.add_paragraph("");
1944 p.add_run("2.3 Equity. ").bold(true);
1945 p.add_run("Subject to Board approval, the Employee shall receive a stock option grant of ");
1946 p.add_run("500,000 shares").bold(true);
1947 p.add_run(
1948 " of the Company's common stock, vesting over four (4) years with a one-year cliff, \
1949 at an exercise price equal to the fair market value on the date of grant.",
1950 );
1951 }
1952
1953 // Compensation summary table
1954 doc.add_paragraph("");
1955 {
1956 let mut tbl = doc
1957 .add_table(5, 2)
1958 .borders(BorderStyle::Single, 4, "5B2C6F")
1959 .width_pct(70.0)
1960 .alignment(Alignment::Center);
1961 tbl.cell(0, 0).unwrap().set_text("Compensation Element");
1962 tbl.cell(0, 0).unwrap().shading("5B2C6F");
1963 tbl.cell(0, 1).unwrap().set_text("Value");
1964 tbl.cell(0, 1).unwrap().shading("5B2C6F");
1965 let items = [
1966 ("Base Salary", "$375,000/year"),
1967 ("Target Bonus", "Up to 40% ($150,000)"),
1968 ("Equity Grant", "500,000 shares (4yr vest)"),
1969 ("Total Target Comp", "$525,000 + equity"),
1970 ];
1971 for (i, (elem, val)) in items.iter().enumerate() {
1972 tbl.cell(i + 1, 0).unwrap().set_text(elem);
1973 tbl.cell(i + 1, 1).unwrap().set_text(val);
1974 if i == 3 {
1975 tbl.cell(i + 1, 0).unwrap().shading("E8DAEF");
1976 tbl.cell(i + 1, 1).unwrap().shading("E8DAEF");
1977 }
1978 }
1979 }
1980
1981 // ── Article 3: Benefits ──
1982 doc.add_paragraph("").page_break_before(true);
1983 doc.add_paragraph("Article 3 — Benefits").style("Heading1");
1984 {
1985 let mut p = doc.add_paragraph("");
1986 p.add_run("3.1 ").bold(true);
1987 p.add_run(
1988 "The Employee shall be entitled to participate in all benefit programs \
1989 generally available to senior executives, including:",
1990 );
1991 }
1992 doc.add_bullet_list_item(
1993 "Medical, dental, and vision insurance (100% premium coverage for employee and dependents)",
1994 0,
1995 );
1996 doc.add_bullet_list_item("401(k) retirement plan with 6% company match", 0);
1997 doc.add_bullet_list_item("Life insurance and long-term disability coverage", 0);
1998 doc.add_bullet_list_item("Annual professional development allowance of $10,000", 0);
1999
2000 {
2001 let mut p = doc.add_paragraph("");
2002 p.add_run("3.2 Paid Time Off. ").bold(true);
2003 p.add_run(
2004 "The Employee shall receive 25 days of paid vacation per year, plus Company holidays, \
2005 accruing on a monthly basis.",
2006 );
2007 }
2008
2009 // ── Article 4: Term and Termination ──
2010 doc.add_paragraph("Article 4 — Term and Termination")
2011 .style("Heading1");
2012 {
2013 let mut p = doc.add_paragraph("");
2014 p.add_run("4.1 At-Will Employment. ").bold(true);
2015 p.add_run(
2016 "This Agreement is for at-will employment and may be terminated by either Party \
2017 at any time, with or without cause, subject to the notice provisions herein.",
2018 );
2019 }
2020 {
2021 let mut p = doc.add_paragraph("");
2022 p.add_run("4.2 Notice Period. ").bold(true);
2023 p.add_run("Either Party shall provide ");
2024 p.add_run("ninety (90) days").bold(true);
2025 p.add_run(" written notice of termination, or pay in lieu thereof.");
2026 }
2027 {
2028 let mut p = doc.add_paragraph("");
2029 p.add_run("4.3 Severance. ").bold(true);
2030 p.add_run("In the event of termination by the Company without Cause, the Employee shall \
2031 receive (a) twelve (12) months of base salary continuation, (b) pro-rata bonus \
2032 for the year of termination, and (c) twelve (12) months of COBRA premium coverage.");
2033 }
2034
2035 // ── Article 5: Confidentiality ──
2036 doc.add_paragraph("Article 5 — Confidentiality and IP")
2037 .style("Heading1");
2038 {
2039 let mut p = doc.add_paragraph("");
2040 p.add_run("5.1 ").bold(true);
2041 p.add_run(
2042 "The Employee agrees to maintain strict confidentiality of all proprietary \
2043 information, trade secrets, and business plans of the Company during and after \
2044 employment.",
2045 );
2046 }
2047 {
2048 let mut p = doc.add_paragraph("");
2049 p.add_run("5.2 ").bold(true);
2050 p.add_run(
2051 "All intellectual property, inventions, and works of authorship created by the \
2052 Employee during the term of employment and within the scope of duties shall be \
2053 the sole and exclusive property of the Company.",
2054 );
2055 }
2056
2057 // ── Article 6: Non-Compete ──
2058 doc.add_paragraph("Article 6 — Non-Competition")
2059 .style("Heading1");
2060 {
2061 let mut p = doc.add_paragraph("");
2062 p.add_run("6.1 ").bold(true);
2063 p.add_run("For a period of twelve (12) months following termination, the Employee agrees \
2064 not to engage in any business that directly competes with the Company's core \
2065 business of AI infrastructure and ML operations services, within the United States.");
2066 }
2067
2068 // ── Article 7: General Provisions ──
2069 doc.add_paragraph("Article 7 — General Provisions")
2070 .style("Heading1");
2071 {
2072 let mut p = doc.add_paragraph("");
2073 p.add_run("7.1 Governing Law. ").bold(true);
2074 p.add_run(
2075 "This Agreement shall be governed by and construed in accordance with the laws of \
2076 the State of California.",
2077 );
2078 }
2079 {
2080 let mut p = doc.add_paragraph("");
2081 p.add_run("7.2 Entire Agreement. ").bold(true);
2082 p.add_run(
2083 "This Agreement constitutes the entire agreement between the Parties and supersedes \
2084 all prior negotiations, representations, and agreements.",
2085 );
2086 }
2087 {
2088 let mut p = doc.add_paragraph("");
2089 p.add_run("7.3 Amendment. ").bold(true);
2090 p.add_run(
2091 "This Agreement may only be amended by a written instrument signed by both Parties.",
2092 );
2093 }
2094
2095 // ── Signature Block ──
2096 doc.add_paragraph("").page_break_before(true);
2097 doc.add_paragraph("IN WITNESS WHEREOF, the Parties have executed this Employment Agreement as of the Effective Date.")
2098 .space_after(Length::pt(24.0));
2099
2100 // Signature table
2101 {
2102 let mut tbl = doc.add_table(6, 2).width_pct(100.0);
2103 tbl.cell(0, 0).unwrap().set_text("FOR THE COMPANY:");
2104 tbl.cell(0, 0).unwrap().shading("5B2C6F");
2105 tbl.cell(0, 1).unwrap().set_text("EMPLOYEE:");
2106 tbl.cell(0, 1).unwrap().shading("5B2C6F");
2107
2108 tbl.cell(1, 0).unwrap().set_text("");
2109 tbl.cell(1, 1).unwrap().set_text("");
2110 tbl.row(1).unwrap().height(Length::pt(40.0));
2111
2112 tbl.cell(2, 0)
2113 .unwrap()
2114 .set_text("Signature: ___________________________");
2115 tbl.cell(2, 1)
2116 .unwrap()
2117 .set_text("Signature: ___________________________");
2118 tbl.cell(3, 0)
2119 .unwrap()
2120 .set_text("Name: Board Representative");
2121 tbl.cell(3, 1).unwrap().set_text("Name: Walter White");
2122 tbl.cell(4, 0)
2123 .unwrap()
2124 .set_text("Title: Chair, Board of Directors");
2125 tbl.cell(4, 1)
2126 .unwrap()
2127 .set_text("Title: Chief Executive Officer");
2128 tbl.cell(5, 0).unwrap().set_text("Date: _______________");
2129 tbl.cell(5, 1).unwrap().set_text("Date: _______________");
2130 }
2131
2132 doc
2133}Sourcepub fn add_numbered_list_item(
&mut self,
text: &str,
level: u32,
) -> Paragraph<'_>
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?
97fn generate_feature_showcase(_samples_dir: &Path) -> Document {
98 let mut doc = Document::new();
99
100 // ── Page Setup & Metadata ──
101 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
102 doc.set_margins(
103 Length::inches(1.0),
104 Length::inches(1.0),
105 Length::inches(1.0),
106 Length::inches(1.0),
107 );
108 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
109 doc.set_gutter(Length::twips(0));
110 doc.set_title("rdocx Feature Showcase");
111 doc.set_author("rdocx Sample Generator");
112 doc.set_subject("Comprehensive feature demonstration");
113 doc.set_keywords("rdocx, docx, rust, sample, showcase");
114
115 // Headers & Footers with different first page
116 doc.set_different_first_page(true);
117 doc.set_first_page_header("rdocx");
118 doc.set_first_page_footer("Feature Showcase — Cover Page");
119 doc.set_header("rdocx Feature Showcase");
120 doc.set_footer("Generated by rdocx");
121
122 // ── COVER PAGE ──
123 let bg = create_sample_png(612, 792, [20, 45, 90]);
124 doc.add_background_image(&bg, "cover_bg.png");
125
126 for _ in 0..3 {
127 doc.add_paragraph("");
128 }
129 {
130 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
131 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
132 }
133 {
134 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
135 p.add_run("Complete Feature Showcase")
136 .size(28.0)
137 .color("FFFFFF")
138 .italic(true);
139 }
140 doc.add_paragraph("");
141 {
142 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
143 p.add_run("Every feature of the rdocx Rust library")
144 .size(13.0)
145 .color("B0C4DE");
146 }
147 {
148 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
149 p.add_run("demonstrated in a single document.")
150 .size(13.0)
151 .color("B0C4DE");
152 }
153
154 // ── TABLE OF CONTENTS ──
155 doc.add_paragraph("").page_break_before(true);
156 doc.insert_toc(doc.content_count(), 3);
157
158 // ── SECTION 1: TEXT FORMATTING ──
159 doc.add_paragraph("").page_break_before(true);
160 doc.add_paragraph("1. Text Formatting").style("Heading1");
161
162 // Paragraph Alignment
163 doc.add_paragraph("1.1 Paragraph Alignment")
164 .style("Heading2");
165 doc.add_paragraph("Left-aligned paragraph (default).")
166 .alignment(Alignment::Left);
167 doc.add_paragraph("Center-aligned paragraph.")
168 .alignment(Alignment::Center);
169 doc.add_paragraph("Right-aligned paragraph.")
170 .alignment(Alignment::Right);
171 doc.add_paragraph(
172 "Justified paragraph — this text is long enough to demonstrate how justified alignment \
173 distributes extra space across word gaps so lines fill the full width of the text area.",
174 )
175 .alignment(Alignment::Justify);
176
177 doc.add_paragraph("");
178
179 // Run Formatting
180 doc.add_paragraph("1.2 Run Formatting").style("Heading2");
181 {
182 let mut p = doc.add_paragraph("");
183 p.add_run("Bold").bold(true);
184 p.add_run(" | ");
185 p.add_run("Italic").italic(true);
186 p.add_run(" | ");
187 p.add_run("Bold Italic").bold(true).italic(true);
188 p.add_run(" | ");
189 p.add_run("Underline").underline(true);
190 p.add_run(" | ");
191 p.add_run("Strikethrough").strike(true);
192 p.add_run(" | ");
193 p.add_run("Double Strike").double_strike(true);
194 }
195 {
196 let mut p = doc.add_paragraph("");
197 p.add_run("Red").color("FF0000");
198 p.add_run(" | ");
199 p.add_run("Blue").color("0000FF");
200 p.add_run(" | ");
201 p.add_run("Green").color("00AA00");
202 p.add_run(" | ");
203 p.add_run("Highlighted").highlight("FFFF00");
204 }
205 {
206 let mut p = doc.add_paragraph("");
207 p.add_run("8pt").size(8.0);
208 p.add_run(" | ");
209 p.add_run("11pt").size(11.0);
210 p.add_run(" | ");
211 p.add_run("16pt").size(16.0);
212 p.add_run(" | ");
213 p.add_run("24pt").size(24.0);
214 }
215 {
216 let mut p = doc.add_paragraph("");
217 p.add_run("Arial").font("Arial");
218 p.add_run(" | ");
219 p.add_run("Times New Roman").font("Times New Roman");
220 p.add_run(" | ");
221 p.add_run("Courier New").font("Courier New");
222 }
223 {
224 let mut p = doc.add_paragraph("");
225 p.add_run("H");
226 p.add_run("2").subscript();
227 p.add_run("O (subscript) | E=mc");
228 p.add_run("2").superscript();
229 p.add_run(" (superscript)");
230 }
231 {
232 let mut p = doc.add_paragraph("");
233 p.add_run("ALL CAPS").all_caps(true);
234 p.add_run(" | ");
235 p.add_run("Small Caps").small_caps(true);
236 p.add_run(" | ");
237 p.add_run("Expanded").character_spacing(Length::twips(40));
238 p.add_run(" | ");
239 p.add_run("Hidden text (not visible)").hidden(true);
240 }
241
242 doc.add_paragraph("");
243
244 // Underline Styles
245 doc.add_paragraph("1.3 Underline Styles").style("Heading2");
246 {
247 let mut p = doc.add_paragraph("");
248 p.add_run("Single ")
249 .underline_style(rdocx::UnderlineStyle::Single);
250 p.add_run("Double ")
251 .underline_style(rdocx::UnderlineStyle::Double);
252 p.add_run("Thick ")
253 .underline_style(rdocx::UnderlineStyle::Thick);
254 p.add_run("Dotted ")
255 .underline_style(rdocx::UnderlineStyle::Dotted);
256 p.add_run("Dash ")
257 .underline_style(rdocx::UnderlineStyle::Dash);
258 p.add_run("Wave ")
259 .underline_style(rdocx::UnderlineStyle::Wave);
260 }
261
262 // ── SECTION 2: PARAGRAPH FORMATTING ──
263 doc.add_paragraph("").page_break_before(true);
264 doc.add_paragraph("2. Paragraph Formatting")
265 .style("Heading1");
266
267 doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
268 doc.add_paragraph("Paragraph with light green shading")
269 .shading("E2EFDA");
270 doc.add_paragraph("Paragraph with bottom border")
271 .border_bottom(BorderStyle::Single, 6, "2E75B6");
272 doc.add_paragraph("Paragraph with all borders (red)")
273 .border_all(BorderStyle::Single, 4, "FF0000");
274
275 doc.add_paragraph("2.2 Indentation").style("Heading2");
276 doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
277 .indent_left(Length::inches(1.0))
278 .hanging_indent(Length::inches(0.5));
279 doc.add_paragraph("0.5-inch first-line indent on this paragraph")
280 .first_line_indent(Length::inches(0.5));
281 doc.add_paragraph("Both left and right indent (0.75 inches each)")
282 .indent_left(Length::inches(0.75))
283 .indent_right(Length::inches(0.75));
284
285 doc.add_paragraph("2.3 Spacing & Line Height")
286 .style("Heading2");
287 doc.add_paragraph("Extra space before (24pt) and after (12pt)")
288 .space_before(Length::pt(24.0))
289 .space_after(Length::pt(12.0));
290 doc.add_paragraph(
291 "Double line spacing paragraph. This text should have extra vertical space between \
292 lines to demonstrate line_spacing_multiple(2.0).",
293 )
294 .line_spacing_multiple(2.0);
295 doc.add_paragraph("Exact 20pt line spacing (fixed height).")
296 .line_spacing(20.0);
297
298 doc.add_paragraph("2.4 Pagination Controls")
299 .style("Heading2");
300 doc.add_paragraph("keep_with_next — this paragraph stays with the next")
301 .keep_with_next(true);
302 doc.add_paragraph("(This paragraph was kept with the one above.)");
303 doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
304 .keep_together(true);
305 doc.add_paragraph("widow_control — prevents widow/orphan lines")
306 .widow_control(true);
307
308 // ── SECTION 3: LISTS ──
309 doc.add_paragraph("").page_break_before(true);
310 doc.add_paragraph("3. Lists").style("Heading1");
311
312 doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
313 doc.add_bullet_list_item("First item", 0);
314 doc.add_bullet_list_item("Second item", 0);
315 doc.add_bullet_list_item("Nested level 1", 1);
316 doc.add_bullet_list_item("Nested level 2", 2);
317 doc.add_bullet_list_item("Back to level 1", 1);
318 doc.add_bullet_list_item("Third item", 0);
319
320 doc.add_paragraph("");
321
322 doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
323 doc.add_numbered_list_item("First numbered item", 0);
324 doc.add_numbered_list_item("Second numbered item", 0);
325 doc.add_numbered_list_item("Sub-item A", 1);
326 doc.add_numbered_list_item("Sub-item B", 1);
327 doc.add_numbered_list_item("Third numbered item", 0);
328
329 // ── SECTION 4: TAB STOPS ──
330 doc.add_paragraph("");
331 doc.add_paragraph("4. Tab Stops").style("Heading1");
332
333 doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
334 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
335 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
336 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
337 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
338 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
339
340 doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
341 doc.add_paragraph("Item\t\tPrice")
342 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
343 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
344 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
345 doc.add_paragraph("Widget\t\t$19.99")
346 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
347 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
348 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
349 doc.add_paragraph("Gadget\t\t$249.50")
350 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
351 .add_tab_stop_with_leader(
352 TabAlignment::Right,
353 Length::inches(4.0),
354 TabLeader::Underscore,
355 )
356 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
357
358 // ── SECTION 5: TABLES ──
359 doc.add_paragraph("").page_break_before(true);
360 doc.add_paragraph("5. Tables").style("Heading1");
361
362 doc.add_paragraph("5.1 Basic Table").style("Heading2");
363 {
364 let mut tbl = doc
365 .add_table(4, 3)
366 .borders(BorderStyle::Single, 4, "000000");
367 for col in 0..3 {
368 tbl.cell(0, col).unwrap().shading("2E75B6");
369 }
370 tbl.cell(0, 0).unwrap().set_text("Name");
371 tbl.cell(0, 1).unwrap().set_text("Role");
372 tbl.cell(0, 2).unwrap().set_text("Location");
373 tbl.cell(1, 0).unwrap().set_text("Walter White");
374 tbl.cell(1, 1).unwrap().set_text("CEO");
375 tbl.cell(1, 2).unwrap().set_text("Albuquerque");
376 tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
377 tbl.cell(2, 1).unwrap().set_text("CTO");
378 tbl.cell(2, 2).unwrap().set_text("Remote");
379 tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
380 tbl.cell(3, 1).unwrap().set_text("Security");
381 tbl.cell(3, 2).unwrap().set_text("Washington");
382 }
383
384 doc.add_paragraph("");
385
386 doc.add_paragraph("5.2 Column Span & Cell Shading")
387 .style("Heading2");
388 {
389 let mut tbl = doc
390 .add_table(3, 4)
391 .borders(BorderStyle::Single, 4, "000000")
392 .width_pct(100.0);
393 tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
394 tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
395 tbl.cell(1, 0).unwrap().set_text("Region");
396 tbl.cell(1, 0).unwrap().shading("D6E4F0");
397 tbl.cell(1, 1).unwrap().set_text("Q1");
398 tbl.cell(1, 1).unwrap().shading("D6E4F0");
399 tbl.cell(1, 2).unwrap().set_text("Q2");
400 tbl.cell(1, 2).unwrap().shading("D6E4F0");
401 tbl.cell(1, 3).unwrap().set_text("Total");
402 tbl.cell(1, 3).unwrap().shading("D6E4F0");
403 tbl.cell(2, 0).unwrap().set_text("Americas");
404 tbl.cell(2, 1).unwrap().set_text("$2.4M");
405 tbl.cell(2, 2).unwrap().set_text("$2.7M");
406 tbl.cell(2, 3).unwrap().set_text("$5.1M");
407 }
408
409 doc.add_paragraph("");
410
411 doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
412 {
413 let mut tbl = doc
414 .add_table(4, 3)
415 .borders(BorderStyle::Single, 4, "000000");
416 tbl.cell(0, 0).unwrap().set_text("Category");
417 tbl.cell(0, 0).unwrap().shading("E2EFDA");
418 tbl.cell(0, 1).unwrap().set_text("Item");
419 tbl.cell(0, 1).unwrap().shading("E2EFDA");
420 tbl.cell(0, 2).unwrap().set_text("Price");
421 tbl.cell(0, 2).unwrap().shading("E2EFDA");
422 tbl.cell(1, 0).unwrap().set_text("Hardware");
423 tbl.cell(1, 0).unwrap().v_merge_restart();
424 tbl.cell(1, 1).unwrap().set_text("Laptop");
425 tbl.cell(1, 2).unwrap().set_text("$1,200");
426 tbl.cell(2, 0).unwrap().v_merge_continue();
427 tbl.cell(2, 1).unwrap().set_text("Monitor");
428 tbl.cell(2, 2).unwrap().set_text("$450");
429 tbl.cell(3, 0).unwrap().set_text("Software");
430 tbl.cell(3, 1).unwrap().set_text("IDE License");
431 tbl.cell(3, 2).unwrap().set_text("$200/yr");
432 }
433
434 doc.add_paragraph("");
435
436 doc.add_paragraph("5.4 Nested Table").style("Heading2");
437 {
438 let mut tbl = doc
439 .add_table(2, 2)
440 .borders(BorderStyle::Single, 6, "2E75B6");
441 tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
442 tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
443 tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
444 {
445 let mut cell = tbl.cell(1, 1).unwrap();
446 cell.set_text("Nested table below:");
447 let mut nested = cell
448 .add_table(2, 2)
449 .borders(BorderStyle::Single, 2, "FF6600");
450 nested.cell(0, 0).unwrap().set_text("A");
451 nested.cell(0, 1).unwrap().set_text("B");
452 nested.cell(1, 0).unwrap().set_text("C");
453 nested.cell(1, 1).unwrap().set_text("D");
454 }
455 }
456
457 doc.add_paragraph("");
458
459 doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
460 .style("Heading2");
461 {
462 let mut tbl = doc
463 .add_table(2, 3)
464 .borders(BorderStyle::Single, 4, "666666");
465 tbl.row(0).unwrap().height(Length::pt(50.0)).header();
466 tbl.cell(0, 0).unwrap().set_text("Top");
467 tbl.cell(0, 0)
468 .unwrap()
469 .vertical_alignment(VerticalAlignment::Top)
470 .shading("FFF2CC");
471 tbl.cell(0, 1).unwrap().set_text("Center");
472 tbl.cell(0, 1)
473 .unwrap()
474 .vertical_alignment(VerticalAlignment::Center)
475 .shading("D9E2F3");
476 tbl.cell(0, 2).unwrap().set_text("Bottom");
477 tbl.cell(0, 2)
478 .unwrap()
479 .vertical_alignment(VerticalAlignment::Bottom)
480 .shading("E2EFDA");
481 tbl.row(1).unwrap().cant_split();
482 tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
483 tbl.cell(1, 0).unwrap().no_wrap();
484 tbl.cell(1, 1).unwrap().set_text("Fixed width");
485 tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
486 tbl.cell(1, 2).unwrap().set_text("Normal");
487 }
488
489 // ── SECTION 6: IMAGES ──
490 doc.add_paragraph("").page_break_before(true);
491 doc.add_paragraph("6. Images").style("Heading1");
492
493 doc.add_paragraph("6.1 Inline Image").style("Heading2");
494 doc.add_paragraph("A blue gradient image below:");
495 let img = create_sample_png(200, 50, [0, 80, 200]);
496 doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
497
498 doc.add_paragraph("");
499 doc.add_paragraph("6.2 Header Image").style("Heading2");
500 let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
501 doc.set_header_image(
502 &hdr_img,
503 "header_logo.png",
504 Length::inches(2.0),
505 Length::inches(0.2),
506 );
507 doc.add_paragraph("The header now contains an inline image (check the top of this page).");
508
509 doc.add_paragraph("");
510 doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
511
512 // ── SECTION 7: CONTENT MANIPULATION ──
513 doc.add_paragraph("").page_break_before(true);
514 doc.add_paragraph("7. Content Manipulation")
515 .style("Heading1");
516
517 doc.add_paragraph("7.1 Placeholder Replacement")
518 .style("Heading2");
519 doc.add_paragraph("Customer: {{customer}}");
520 doc.add_paragraph("Date: {{date}}");
521 doc.add_paragraph("Reference: {{ref_number}}");
522 {
523 let mut tbl = doc
524 .add_table(2, 2)
525 .borders(BorderStyle::Single, 4, "000000");
526 tbl.cell(0, 0).unwrap().set_text("Project");
527 tbl.cell(0, 0).unwrap().shading("D6E4F0");
528 tbl.cell(0, 1).unwrap().set_text("{{project}}");
529 tbl.cell(1, 0).unwrap().set_text("Status");
530 tbl.cell(1, 0).unwrap().shading("D6E4F0");
531 tbl.cell(1, 1).unwrap().set_text("{{status}}");
532 }
533
534 let mut replacements = HashMap::new();
535 replacements.insert("{{customer}}", "Tensorbee Inc.");
536 replacements.insert("{{date}}", "February 22, 2026");
537 replacements.insert("{{ref_number}}", "TB-2026-001");
538 replacements.insert("{{project}}", "Infrastructure Upgrade");
539 replacements.insert("{{status}}", "In Progress");
540 let n = doc.replace_all(&replacements);
541 doc.add_paragraph(&format!("({n} placeholders replaced above)"));
542
543 doc.add_paragraph("");
544
545 doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
546 doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
547 let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
548 doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
549
550 doc.add_paragraph("");
551
552 doc.add_paragraph("7.3 Content Insertion").style("Heading2");
553 doc.add_paragraph("Section A: First content.");
554 doc.add_paragraph("Section C: Third content.");
555 if let Some(idx) = doc.find_content_index("Section C") {
556 doc.insert_paragraph(
557 idx,
558 "Section B: Inserted between A and C via find_content_index().",
559 );
560 }
561
562 // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
563 doc.add_paragraph("").section_break(SectionBreak::NextPage);
564 doc.add_paragraph("8. Section Breaks & Orientation")
565 .style("Heading1");
566 doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
567
568 {
569 let mut tbl = doc
570 .add_table(3, 7)
571 .borders(BorderStyle::Single, 4, "2E75B6");
572 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
573 for (c, h) in headers.iter().enumerate() {
574 tbl.cell(0, c).unwrap().set_text(h);
575 tbl.cell(0, c).unwrap().shading("2E75B6");
576 }
577 let data = [
578 [
579 "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
580 ],
581 [
582 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
583 ],
584 ];
585 for (r, row) in data.iter().enumerate() {
586 for (c, v) in row.iter().enumerate() {
587 tbl.cell(r + 1, c).unwrap().set_text(v);
588 }
589 }
590 }
591
592 doc.add_paragraph("")
593 .section_break(SectionBreak::NextPage)
594 .section_landscape();
595
596 // ── SECTION 9: CUSTOM STYLES ──
597 doc.add_paragraph("9. Custom Styles").style("Heading1");
598 doc.add_style(
599 StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
600 .based_on("Normal")
601 .paragraph_properties(rdocx_oxml::properties::CT_PPr {
602 shading: Some(rdocx_oxml::properties::CT_Shd {
603 val: "clear".to_string(),
604 color: None,
605 fill: Some("FFF2CC".to_string()),
606 }),
607 ..Default::default()
608 })
609 .run_properties(rdocx_oxml::properties::CT_RPr {
610 bold: Some(true),
611 color: Some("C45911".to_string()),
612 ..Default::default()
613 }),
614 );
615 doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
616 .style("CustomHighlight");
617
618 doc.add_paragraph("");
619
620 // ── SECTION 10: DOCUMENT INTELLIGENCE ──
621 doc.add_paragraph("10. Document Intelligence API")
622 .style("Heading1");
623
624 let wc = doc.word_count();
625 let hc = doc.headings().len();
626 let ic = doc.images().len();
627 let lc = doc.links().len();
628 doc.add_paragraph(&format!("Word count: {wc}"));
629 doc.add_paragraph(&format!("Heading count: {hc}"));
630 doc.add_paragraph(&format!("Image count: {ic}"));
631 doc.add_paragraph(&format!("Link count: {lc}"));
632
633 let outline = doc.document_outline();
634 doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
635
636 let issues = doc.audit_accessibility();
637 doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
638 for issue in &issues {
639 doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
640 }
641
642 // ── SECTION 11: DOCUMENT MERGING ──
643 doc.add_paragraph("");
644 doc.add_paragraph("11. Document Merging").style("Heading1");
645 {
646 let mut other = Document::new();
647 other.add_paragraph("This paragraph was merged from another document using append().");
648 doc.append(&other);
649 }
650
651 doc.add_paragraph("");
652
653 // ── SUMMARY ──
654 doc.add_paragraph("Summary of Demonstrated Features")
655 .style("Heading1");
656 let features = [
657 "Page setup: size, margins, header/footer distance, gutter",
658 "Document metadata: title, author, subject, keywords",
659 "Headers and footers: text, images, different first page",
660 "Background images (full-page behind text)",
661 "Table of Contents generation with bookmarks",
662 "Text formatting: bold, italic, underline styles, strike, color, size, font",
663 "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
664 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
665 "Pagination controls: keep-with-next, keep-together, widow control",
666 "Bullet and numbered lists with nesting",
667 "Tab stops with dot/underscore/hyphen leaders",
668 "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
669 "Row properties: header rows, exact height, cant-split",
670 "Cell properties: width, no-wrap, vertical alignment",
671 "Inline images and header images",
672 "Placeholder replacement (single and batch)",
673 "Regex-based find and replace",
674 "Content insertion at specific positions",
675 "Section breaks with mixed portrait/landscape",
676 "Custom paragraph and character styles",
677 "Document intelligence: word count, headings, outline, images, links",
678 "Accessibility audit",
679 "Document merging (append)",
680 "Export: DOCX, PDF, HTML, Markdown",
681 ];
682 for f in &features {
683 doc.add_bullet_list_item(f, 0);
684 }
685 doc.add_paragraph("");
686 doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
687 .alignment(Alignment::Center)
688 .shading("E2EFDA")
689 .border_all(BorderStyle::Single, 2, "00AA00");
690
691 doc
692}
693
694// =============================================================================
695// 2. PROPOSAL DOCUMENT — Deep navy + gold scheme
696// =============================================================================
697fn generate_proposal(_samples_dir: &Path) -> Document {
698 let mut doc = Document::new();
699
700 // Colors: Navy #1B2A4A, Gold #C5922E, Light #F4F1EB
701 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
702 doc.set_margins(
703 Length::inches(1.0),
704 Length::inches(1.0),
705 Length::inches(1.0),
706 Length::inches(1.0),
707 );
708 doc.set_title("AI Platform Modernization Proposal");
709 doc.set_author("Walter White");
710 doc.set_subject("Technology Proposal");
711 doc.set_keywords("proposal, AI, modernization, Tensorbee");
712
713 // Banner header
714 let logo_img = create_logo_png(220, 48);
715 let banner = build_header_banner_xml(
716 "rId1",
717 &BannerOpts {
718 bg_color: "1B2A4A",
719 banner_width: 7772400,
720 banner_height: 914400,
721 logo_width: 2011680,
722 logo_height: 438912,
723 logo_x_offset: 295125,
724 logo_y_offset: 237744,
725 },
726 );
727 doc.set_raw_header_with_images(
728 banner,
729 &[("rId1", &logo_img, "logo.png")],
730 rdocx_oxml::header_footer::HdrFtrType::Default,
731 );
732 doc.set_different_first_page(true);
733 let cover_banner = build_header_banner_xml(
734 "rId1",
735 &BannerOpts {
736 bg_color: "C5922E",
737 banner_width: 7772400,
738 banner_height: 914400,
739 logo_width: 2011680,
740 logo_height: 438912,
741 logo_x_offset: 295125,
742 logo_y_offset: 237744,
743 },
744 );
745 doc.set_raw_header_with_images(
746 cover_banner,
747 &[("rId1", &logo_img, "logo.png")],
748 rdocx_oxml::header_footer::HdrFtrType::First,
749 );
750 doc.set_margins(
751 Length::twips(2292),
752 Length::twips(1440),
753 Length::twips(1440),
754 Length::twips(1440),
755 );
756 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
757 doc.set_footer("Tensorbee — Confidential");
758
759 // Custom styles
760 doc.add_style(
761 StyleBuilder::paragraph("ProposalTitle", "Proposal Title")
762 .based_on("Normal")
763 .run_properties(rdocx_oxml::properties::CT_RPr {
764 bold: Some(true),
765 font_ascii: Some("Georgia".to_string()),
766 font_hansi: Some("Georgia".to_string()),
767 sz: Some(rdocx_oxml::HalfPoint(56)), // half-points → 28pt
768 color: Some("1B2A4A".to_string()),
769 ..Default::default()
770 }),
771 );
772
773 // ── Cover Page ──
774 for _ in 0..4 {
775 doc.add_paragraph("");
776 }
777 doc.add_paragraph("AI Platform Modernization")
778 .style("ProposalTitle")
779 .alignment(Alignment::Center);
780 doc.add_paragraph("")
781 .alignment(Alignment::Center)
782 .border_bottom(BorderStyle::Single, 8, "C5922E");
783 {
784 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
785 p.add_run("Prepared for Global Financial Corp.")
786 .size(14.0)
787 .color("666666")
788 .italic(true);
789 }
790 {
791 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
792 p.add_run("Prepared by: Walter White, CEO — Tensorbee")
793 .size(12.0)
794 .color("888888");
795 }
796 {
797 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
798 p.add_run("February 22, 2026").size(12.0).color("888888");
799 }
800
801 // ── TOC ──
802 doc.add_paragraph("").page_break_before(true);
803 doc.add_paragraph("Table of Contents").style("Heading1");
804 // We'll add a manual-style TOC since the insert_toc goes at a specific position
805 doc.insert_toc(doc.content_count(), 2);
806
807 // ── Executive Summary ──
808 doc.add_paragraph("").page_break_before(true);
809 doc.add_paragraph("Executive Summary").style("Heading1");
810 doc.add_paragraph(
811 "Tensorbee proposes a comprehensive modernization of Global Financial Corp's AI platform, \
812 transitioning from legacy batch-processing systems to a real-time inference architecture. \
813 This transformation will reduce model deployment time from weeks to hours, improve \
814 prediction accuracy by an estimated 23%, and deliver $4.2M in annual operational savings.",
815 )
816 .first_line_indent(Length::inches(0.3));
817 doc.add_paragraph(
818 "The project spans three phases over 18 months, with a total investment of $2.8M. \
819 Tensorbee brings deep expertise in ML infrastructure, having successfully completed \
820 similar transformations for three Fortune 500 financial institutions.",
821 )
822 .first_line_indent(Length::inches(0.3));
823
824 // Key metrics table
825 doc.add_paragraph("");
826 {
827 let mut tbl = doc
828 .add_table(5, 2)
829 .borders(BorderStyle::Single, 4, "1B2A4A")
830 .width_pct(80.0);
831 tbl.cell(0, 0).unwrap().set_text("Key Metric");
832 tbl.cell(0, 0).unwrap().shading("1B2A4A");
833 tbl.cell(0, 1).unwrap().set_text("Value");
834 tbl.cell(0, 1).unwrap().shading("1B2A4A");
835 let rows = [
836 ("Total Investment", "$2.8M"),
837 ("Annual Savings", "$4.2M"),
838 ("ROI (Year 1)", "150%"),
839 ("Timeline", "18 months"),
840 ];
841 for (i, (k, v)) in rows.iter().enumerate() {
842 tbl.cell(i + 1, 0).unwrap().set_text(k);
843 if i % 2 == 0 {
844 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
845 }
846 tbl.cell(i + 1, 1).unwrap().set_text(v);
847 if i % 2 == 0 {
848 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
849 }
850 }
851 }
852
853 // ── Problem Statement ──
854 doc.add_paragraph("").page_break_before(true);
855 doc.add_paragraph("Problem Statement").style("Heading1");
856 doc.add_paragraph(
857 "Global Financial Corp's current AI infrastructure faces three critical challenges:",
858 );
859 doc.add_numbered_list_item("Deployment Latency — New models take 3-4 weeks to deploy to production, compared to the industry benchmark of 2-3 days.", 0);
860 doc.add_numbered_list_item("Scalability Constraints — The batch processing architecture cannot handle real-time inference demands during peak trading hours.", 0);
861 doc.add_numbered_list_item("Technical Debt — Legacy Python 2.7 codebases and manual deployment scripts create reliability risks and slow iteration speed.", 0);
862
863 // ── Proposed Solution ──
864 doc.add_paragraph("").page_break_before(true);
865 doc.add_paragraph("Proposed Solution").style("Heading1");
866
867 doc.add_paragraph("Phase 1: Foundation (Months 1-6)")
868 .style("Heading2");
869 doc.add_bullet_list_item("Deploy Kubernetes-based ML serving infrastructure", 0);
870 doc.add_bullet_list_item("Implement CI/CD pipeline for model deployment", 0);
871 doc.add_bullet_list_item("Migrate top 5 production models to new platform", 0);
872
873 doc.add_paragraph("Phase 2: Expansion (Months 7-12)")
874 .style("Heading2");
875 doc.add_bullet_list_item(
876 "Real-time feature engineering pipeline (Apache Kafka + Flink)",
877 0,
878 );
879 doc.add_bullet_list_item("A/B testing framework for model variants", 0);
880 doc.add_bullet_list_item("Automated model monitoring and drift detection", 0);
881
882 doc.add_paragraph("Phase 3: Optimization (Months 13-18)")
883 .style("Heading2");
884 doc.add_bullet_list_item("GPU inference optimization (TensorRT/ONNX Runtime)", 0);
885 doc.add_bullet_list_item("Multi-region deployment for latency reduction", 0);
886 doc.add_bullet_list_item(
887 "Self-service model deployment portal for data science teams",
888 0,
889 );
890
891 // ── Budget ──
892 doc.add_paragraph("Budget Breakdown").style("Heading1");
893 {
894 let mut tbl = doc
895 .add_table(6, 3)
896 .borders(BorderStyle::Single, 4, "1B2A4A")
897 .width_pct(100.0);
898 tbl.cell(0, 0).unwrap().set_text("Category");
899 tbl.cell(0, 0).unwrap().shading("1B2A4A");
900 tbl.cell(0, 1).unwrap().set_text("Cost");
901 tbl.cell(0, 1).unwrap().shading("1B2A4A");
902 tbl.cell(0, 2).unwrap().set_text("Notes");
903 tbl.cell(0, 2).unwrap().shading("1B2A4A");
904 let items = [
905 (
906 "Infrastructure",
907 "$450,000",
908 "Cloud compute + GPU instances",
909 ),
910 ("Engineering Services", "$1,600,000", "12 FTE x 18 months"),
911 (
912 "Software Licenses",
913 "$350,000",
914 "Kafka, monitoring, CI/CD tools",
915 ),
916 (
917 "Training & Enablement",
918 "$200,000",
919 "Team training, documentation",
920 ),
921 ("Contingency (10%)", "$200,000", "Risk buffer"),
922 ];
923 for (i, (cat, cost, note)) in items.iter().enumerate() {
924 tbl.cell(i + 1, 0).unwrap().set_text(cat);
925 tbl.cell(i + 1, 1).unwrap().set_text(cost);
926 tbl.cell(i + 1, 2).unwrap().set_text(note);
927 if i % 2 == 0 {
928 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
929 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
930 tbl.cell(i + 1, 2).unwrap().shading("F4F1EB");
931 }
932 }
933 }
934
935 // ── Closing ──
936 doc.add_paragraph("");
937 doc.add_paragraph("Next Steps").style("Heading1");
938 doc.add_numbered_list_item(
939 "Schedule technical deep-dive with GFC engineering team (Week 1)",
940 0,
941 );
942 doc.add_numbered_list_item("Finalize scope and sign SOW (Week 2-3)", 0);
943 doc.add_numbered_list_item("Kick off Phase 1 with joint planning session (Week 4)", 0);
944
945 doc.add_paragraph("");
946 {
947 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
948 p.add_run("Walter White")
949 .bold(true)
950 .size(14.0)
951 .color("1B2A4A");
952 }
953 {
954 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
955 p.add_run("CEO, Tensorbee | walter@tensorbee.com")
956 .size(10.0)
957 .color("888888");
958 }
959
960 doc
961}
962
963// =============================================================================
964// 3. QUOTE / BILL OF MATERIALS — Teal + orange scheme
965// =============================================================================
966fn generate_quote(_samples_dir: &Path) -> Document {
967 let mut doc = Document::new();
968
969 // Colors: Teal #008B8B, Dark #1A3C3C, Orange #E07020, Light #F0F8F8
970 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
971 doc.set_margins(
972 Length::inches(0.75),
973 Length::inches(0.75),
974 Length::inches(0.75),
975 Length::inches(0.75),
976 );
977 doc.set_title("Quotation QT-2026-0147");
978 doc.set_author("Walter White");
979 doc.set_keywords("quote, BOM, Tensorbee");
980
981 doc.set_header("Tensorbee — Quotation");
982 doc.set_footer("QT-2026-0147 | Page");
983
984 // ── Header Block ──
985 {
986 let mut p = doc.add_paragraph("").alignment(Alignment::Left);
987 p.add_run("TENSORBEE")
988 .bold(true)
989 .size(28.0)
990 .color("008B8B")
991 .font("Helvetica");
992 }
993 doc.add_paragraph("123 Innovation Drive, Suite 400")
994 .alignment(Alignment::Left);
995 doc.add_paragraph("San Francisco, CA 94105 | +1 (415) 555-0199")
996 .alignment(Alignment::Left);
997 doc.add_paragraph("")
998 .border_bottom(BorderStyle::Single, 8, "008B8B");
999
1000 // Quote meta
1001 doc.add_paragraph("");
1002 {
1003 let mut tbl = doc.add_table(4, 4).width_pct(100.0);
1004 tbl.cell(0, 0).unwrap().set_text("QUOTATION");
1005 tbl.cell(0, 0).unwrap().shading("008B8B").grid_span(2);
1006 tbl.cell(0, 2).unwrap().set_text("DATE");
1007 tbl.cell(0, 2).unwrap().shading("008B8B");
1008 tbl.cell(0, 3).unwrap().set_text("VALID UNTIL");
1009 tbl.cell(0, 3).unwrap().shading("008B8B");
1010 tbl.cell(1, 0).unwrap().set_text("Quote #:");
1011 tbl.cell(1, 1).unwrap().set_text("QT-2026-0147");
1012 tbl.cell(1, 2).unwrap().set_text("Feb 22, 2026");
1013 tbl.cell(1, 3).unwrap().set_text("Mar 22, 2026");
1014 tbl.cell(2, 0).unwrap().set_text("Prepared by:");
1015 tbl.cell(2, 1).unwrap().set_text("Walter White");
1016 tbl.cell(2, 2).unwrap().set_text("Payment:");
1017 tbl.cell(2, 3).unwrap().set_text("Net 30");
1018 tbl.cell(3, 0).unwrap().set_text("Customer:");
1019 tbl.cell(3, 1).unwrap().set_text("Meridian Dynamics LLC");
1020 tbl.cell(3, 2).unwrap().set_text("Currency:");
1021 tbl.cell(3, 3).unwrap().set_text("USD");
1022 }
1023
1024 doc.add_paragraph("");
1025
1026 // ── Bill of Materials ──
1027 {
1028 let mut p = doc.add_paragraph("");
1029 p.add_run("Bill of Materials")
1030 .bold(true)
1031 .size(16.0)
1032 .color("1A3C3C");
1033 }
1034 doc.add_paragraph("");
1035
1036 {
1037 let mut tbl = doc
1038 .add_table(10, 6)
1039 .borders(BorderStyle::Single, 2, "008B8B")
1040 .width_pct(100.0)
1041 .layout_fixed();
1042 // Headers
1043 let hdrs = [
1044 "#",
1045 "Part Number",
1046 "Description",
1047 "Qty",
1048 "Unit Price",
1049 "Total",
1050 ];
1051 for (c, h) in hdrs.iter().enumerate() {
1052 tbl.cell(0, c).unwrap().set_text(h);
1053 tbl.cell(0, c).unwrap().shading("008B8B");
1054 }
1055
1056 let items: Vec<(&str, &str, &str, &str, &str)> = vec![
1057 (
1058 "TB-GPU-A100",
1059 "NVIDIA A100 80GB GPU",
1060 "4",
1061 "$12,500.00",
1062 "$50,000.00",
1063 ),
1064 (
1065 "TB-SRV-R750",
1066 "Dell R750xa Server Chassis",
1067 "2",
1068 "$8,200.00",
1069 "$16,400.00",
1070 ),
1071 (
1072 "TB-RAM-256G",
1073 "256GB DDR5 ECC Memory Module",
1074 "8",
1075 "$890.00",
1076 "$7,120.00",
1077 ),
1078 (
1079 "TB-SSD-3840",
1080 "3.84TB NVMe U.2 SSD",
1081 "8",
1082 "$1,150.00",
1083 "$9,200.00",
1084 ),
1085 (
1086 "TB-NET-CX7",
1087 "ConnectX-7 200GbE NIC",
1088 "4",
1089 "$1,800.00",
1090 "$7,200.00",
1091 ),
1092 (
1093 "TB-SW-48P",
1094 "48-Port 100GbE Switch",
1095 "1",
1096 "$22,000.00",
1097 "$22,000.00",
1098 ),
1099 (
1100 "TB-CAB-RACK",
1101 "42U Server Rack + PDU",
1102 "1",
1103 "$4,500.00",
1104 "$4,500.00",
1105 ),
1106 (
1107 "TB-SVC-INST",
1108 "Installation & Configuration",
1109 "1",
1110 "$8,500.00",
1111 "$8,500.00",
1112 ),
1113 (
1114 "TB-SVC-SUPP",
1115 "3-Year Premium Support",
1116 "1",
1117 "$15,000.00",
1118 "$15,000.00",
1119 ),
1120 ];
1121
1122 for (i, (pn, desc, qty, unit, total)) in items.iter().enumerate() {
1123 let row = i + 1;
1124 tbl.cell(row, 0).unwrap().set_text(&format!("{}", i + 1));
1125 tbl.cell(row, 1).unwrap().set_text(pn);
1126 tbl.cell(row, 2).unwrap().set_text(desc);
1127 tbl.cell(row, 3).unwrap().set_text(qty);
1128 tbl.cell(row, 4).unwrap().set_text(unit);
1129 tbl.cell(row, 5).unwrap().set_text(total);
1130 if i % 2 == 0 {
1131 for c in 0..6 {
1132 tbl.cell(row, c).unwrap().shading("F0F8F8");
1133 }
1134 }
1135 }
1136 }
1137
1138 doc.add_paragraph("");
1139
1140 // ── Totals ──
1141 {
1142 let mut tbl = doc
1143 .add_table(4, 2)
1144 .borders(BorderStyle::Single, 2, "008B8B")
1145 .width(Length::inches(3.5))
1146 .alignment(Alignment::Right);
1147 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1148 tbl.cell(0, 1).unwrap().set_text("$139,920.00");
1149 tbl.cell(1, 0).unwrap().set_text("Shipping & Handling");
1150 tbl.cell(1, 1).unwrap().set_text("$2,500.00");
1151 tbl.cell(2, 0).unwrap().set_text("Tax (8.625%)");
1152 tbl.cell(2, 1).unwrap().set_text("$12,068.10");
1153 tbl.cell(3, 0).unwrap().set_text("TOTAL");
1154 tbl.cell(3, 0).unwrap().shading("E07020");
1155 tbl.cell(3, 1).unwrap().set_text("$154,488.10");
1156 tbl.cell(3, 1).unwrap().shading("E07020");
1157 }
1158
1159 doc.add_paragraph("");
1160
1161 // ── Terms & Conditions ──
1162 {
1163 let mut p = doc.add_paragraph("");
1164 p.add_run("Terms & Conditions")
1165 .bold(true)
1166 .size(14.0)
1167 .color("1A3C3C");
1168 }
1169 doc.add_numbered_list_item(
1170 "This quotation is valid for 30 calendar days from the date of issue.",
1171 0,
1172 );
1173 doc.add_numbered_list_item(
1174 "All prices are in USD and exclusive of applicable taxes unless stated.",
1175 0,
1176 );
1177 doc.add_numbered_list_item("Standard lead time is 4-6 weeks from PO receipt.", 0);
1178 doc.add_numbered_list_item(
1179 "Payment terms: Net 30 from invoice date. 2% discount for payment within 10 days.",
1180 0,
1181 );
1182 doc.add_numbered_list_item(
1183 "Warranty: 3-year manufacturer warranty on all hardware components.",
1184 0,
1185 );
1186 doc.add_numbered_list_item(
1187 "Returns subject to 15% restocking fee if initiated after 14 days.",
1188 0,
1189 );
1190
1191 doc.add_paragraph("");
1192 doc.add_paragraph("")
1193 .border_bottom(BorderStyle::Single, 4, "008B8B");
1194 doc.add_paragraph("");
1195 {
1196 let mut p = doc.add_paragraph("");
1197 p.add_run("Accepted by: ").bold(true);
1198 p.add_run("___________________________ Date: ___________");
1199 }
1200 {
1201 let mut p = doc.add_paragraph("");
1202 p.add_run("Print Name: ").bold(true);
1203 p.add_run("___________________________ Title: ___________");
1204 }
1205
1206 doc
1207}
1208
1209// =============================================================================
1210// 4. INVOICE — Crimson + charcoal scheme
1211// =============================================================================
1212fn generate_invoice(_samples_dir: &Path) -> Document {
1213 let mut doc = Document::new();
1214
1215 // Colors: Crimson #B22222, Charcoal #333333, Light #FAF0F0
1216 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1217 doc.set_margins(
1218 Length::inches(0.75),
1219 Length::inches(0.75),
1220 Length::inches(0.75),
1221 Length::inches(0.75),
1222 );
1223 doc.set_title("Invoice INV-2026-0392");
1224 doc.set_author("Walter White");
1225 doc.set_keywords("invoice, Tensorbee");
1226
1227 doc.set_footer("Tensorbee — Thank you for your business!");
1228
1229 // ── Company & Invoice Header ──
1230 {
1231 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1232 // Company name left, INVOICE right
1233 {
1234 let mut cell = tbl.cell(0, 0).unwrap();
1235 let mut p = cell.add_paragraph("");
1236 p.add_run("TENSORBEE")
1237 .bold(true)
1238 .size(32.0)
1239 .color("B22222")
1240 .font("Helvetica");
1241 }
1242 {
1243 let mut cell = tbl.cell(0, 1).unwrap();
1244 let mut p = cell.add_paragraph("");
1245 p.add_run("INVOICE").bold(true).size(32.0).color("333333");
1246 }
1247 tbl.cell(1, 0)
1248 .unwrap()
1249 .set_text("123 Innovation Drive, Suite 400");
1250 tbl.cell(1, 1).unwrap().set_text("Invoice #: INV-2026-0392");
1251 tbl.cell(2, 0).unwrap().set_text("San Francisco, CA 94105");
1252 tbl.cell(2, 1).unwrap().set_text("Date: February 22, 2026");
1253 tbl.cell(3, 0).unwrap().set_text("walter@tensorbee.com");
1254 tbl.cell(3, 1).unwrap().set_text("Due Date: March 24, 2026");
1255 }
1256
1257 doc.add_paragraph("")
1258 .border_bottom(BorderStyle::Thick, 8, "B22222");
1259 doc.add_paragraph("");
1260
1261 // ── Bill To / Ship To ──
1262 {
1263 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1264 tbl.cell(0, 0).unwrap().set_text("BILL TO");
1265 tbl.cell(0, 0).unwrap().shading("B22222");
1266 tbl.cell(0, 1).unwrap().set_text("SHIP TO");
1267 tbl.cell(0, 1).unwrap().shading("B22222");
1268 tbl.cell(1, 0).unwrap().set_text("Meridian Dynamics LLC");
1269 tbl.cell(1, 1).unwrap().set_text("Meridian Dynamics LLC");
1270 tbl.cell(2, 0).unwrap().set_text("456 Enterprise Blvd");
1271 tbl.cell(2, 1).unwrap().set_text("Attn: Server Room B");
1272 tbl.cell(3, 0).unwrap().set_text("Austin, TX 78701");
1273 tbl.cell(3, 1)
1274 .unwrap()
1275 .set_text("456 Enterprise Blvd, Austin, TX 78701");
1276 }
1277
1278 doc.add_paragraph("");
1279
1280 // ── Line Items ──
1281 {
1282 let mut tbl = doc
1283 .add_table(8, 5)
1284 .borders(BorderStyle::Single, 2, "B22222")
1285 .width_pct(100.0);
1286 let hdrs = ["Description", "Qty", "Unit Price", "Tax", "Amount"];
1287 for (c, h) in hdrs.iter().enumerate() {
1288 tbl.cell(0, c).unwrap().set_text(h);
1289 tbl.cell(0, c).unwrap().shading("B22222");
1290 }
1291 let items = [
1292 (
1293 "ML Infrastructure Setup — Phase 1",
1294 "1",
1295 "$45,000.00",
1296 "$3,881.25",
1297 "$48,881.25",
1298 ),
1299 (
1300 "Data Pipeline Development (160 hrs)",
1301 "160",
1302 "$225.00",
1303 "$3,105.00",
1304 "$39,105.00",
1305 ),
1306 (
1307 "GPU Cluster Configuration",
1308 "1",
1309 "$12,000.00",
1310 "$1,035.00",
1311 "$13,035.00",
1312 ),
1313 (
1314 "API Gateway Implementation",
1315 "1",
1316 "$18,500.00",
1317 "$1,595.63",
1318 "$20,095.63",
1319 ),
1320 (
1321 "Load Testing & QA (80 hrs)",
1322 "80",
1323 "$195.00",
1324 "$1,345.50",
1325 "$16,945.50",
1326 ),
1327 (
1328 "Documentation & Training",
1329 "1",
1330 "$8,000.00",
1331 "$690.00",
1332 "$8,690.00",
1333 ),
1334 (
1335 "Project Management (3 months)",
1336 "3",
1337 "$6,500.00",
1338 "$1,679.63",
1339 "$21,179.63",
1340 ),
1341 ];
1342 for (i, (desc, qty, unit, tax, amt)) in items.iter().enumerate() {
1343 let r = i + 1;
1344 tbl.cell(r, 0).unwrap().set_text(desc);
1345 tbl.cell(r, 1).unwrap().set_text(qty);
1346 tbl.cell(r, 2).unwrap().set_text(unit);
1347 tbl.cell(r, 3).unwrap().set_text(tax);
1348 tbl.cell(r, 4).unwrap().set_text(amt);
1349 if i % 2 == 0 {
1350 for c in 0..5 {
1351 tbl.cell(r, c).unwrap().shading("FAF0F0");
1352 }
1353 }
1354 }
1355 }
1356
1357 doc.add_paragraph("");
1358
1359 // ── Totals ──
1360 {
1361 let mut tbl = doc
1362 .add_table(4, 2)
1363 .borders(BorderStyle::Single, 2, "B22222")
1364 .width(Length::inches(3.0))
1365 .alignment(Alignment::Right);
1366 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1367 tbl.cell(0, 1).unwrap().set_text("$154,600.00");
1368 tbl.cell(1, 0).unwrap().set_text("Tax (8.625%)");
1369 tbl.cell(1, 1).unwrap().set_text("$13,334.25");
1370 tbl.cell(2, 0).unwrap().set_text("Discount (5%)");
1371 tbl.cell(2, 1).unwrap().set_text("-$7,730.00");
1372 tbl.cell(3, 0).unwrap().set_text("AMOUNT DUE");
1373 tbl.cell(3, 0).unwrap().shading("B22222");
1374 tbl.cell(3, 1).unwrap().set_text("$160,204.25");
1375 tbl.cell(3, 1).unwrap().shading("B22222");
1376 }
1377
1378 doc.add_paragraph("");
1379 doc.add_paragraph("");
1380
1381 // ── Payment Details ──
1382 {
1383 let mut p = doc.add_paragraph("");
1384 p.add_run("Payment Details")
1385 .bold(true)
1386 .size(14.0)
1387 .color("333333");
1388 }
1389 doc.add_paragraph("Bank: Silicon Valley Bank")
1390 .indent_left(Length::inches(0.3));
1391 doc.add_paragraph("Account: Tensorbee Inc. — 0847-2953-1120")
1392 .indent_left(Length::inches(0.3));
1393 doc.add_paragraph("Routing: 121140399")
1394 .indent_left(Length::inches(0.3));
1395 doc.add_paragraph("Swift: SVBKUS6S")
1396 .indent_left(Length::inches(0.3));
1397
1398 doc.add_paragraph("");
1399
1400 doc.add_paragraph("Please include invoice number INV-2026-0392 in the payment reference.")
1401 .shading("FAF0F0")
1402 .border_all(BorderStyle::Single, 2, "B22222");
1403
1404 doc
1405}
1406
1407// =============================================================================
1408// 5. REPORT — Forest green + earth tones, with images & hierarchical sections
1409// =============================================================================
1410fn generate_report(_samples_dir: &Path) -> Document {
1411 let mut doc = Document::new();
1412
1413 // Colors: Forest #2D5016, Sage #6B8E23, Earth #8B7355, Cream #FFFAF0
1414 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1415 doc.set_margins(
1416 Length::inches(1.0),
1417 Length::inches(1.0),
1418 Length::inches(1.0),
1419 Length::inches(1.0),
1420 );
1421 doc.set_title("Q4 2025 Environmental Impact Report");
1422 doc.set_author("Walter White");
1423 doc.set_subject("Quarterly Environmental Report");
1424 doc.set_keywords("environment, sustainability, report, Tensorbee");
1425
1426 doc.set_different_first_page(true);
1427 doc.set_first_page_header("");
1428 doc.set_header("Tensorbee — Q4 2025 Environmental Impact Report");
1429 doc.set_footer("CONFIDENTIAL — Page");
1430
1431 // ── Cover ──
1432 let cover_bg = create_sample_png(612, 792, [20, 50, 15]);
1433 doc.add_background_image(&cover_bg, "report_cover.png");
1434
1435 for _ in 0..5 {
1436 doc.add_paragraph("");
1437 }
1438 {
1439 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1440 p.add_run("Q4 2025")
1441 .bold(true)
1442 .size(48.0)
1443 .color("FFFFFF")
1444 .font("Georgia");
1445 }
1446 {
1447 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1448 p.add_run("Environmental Impact Report")
1449 .size(24.0)
1450 .color("90EE90")
1451 .font("Georgia")
1452 .italic(true);
1453 }
1454 doc.add_paragraph("");
1455 {
1456 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1457 p.add_run("Tensorbee — Sustainability Division")
1458 .size(14.0)
1459 .color("C0C0C0");
1460 }
1461 {
1462 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1463 p.add_run("Prepared by Walter White, Chief Sustainability Officer")
1464 .size(11.0)
1465 .color("AAAAAA");
1466 }
1467
1468 // ── TOC ──
1469 doc.add_paragraph("").page_break_before(true);
1470 doc.insert_toc(doc.content_count(), 3);
1471
1472 // ── Section 1: Executive Overview ──
1473 doc.add_paragraph("").page_break_before(true);
1474 doc.add_paragraph("Executive Overview").style("Heading1");
1475 doc.add_paragraph(
1476 "This report presents Tensorbee's environmental performance for Q4 2025. Our \
1477 sustainability initiatives have yielded a 34% reduction in carbon emissions compared \
1478 to Q4 2024, exceeding our target of 25%. Key achievements include the transition to \
1479 100% renewable energy in our primary data centers and the launch of our carbon offset \
1480 marketplace.",
1481 )
1482 .first_line_indent(Length::inches(0.3));
1483
1484 // Image: performance chart
1485 let chart_img = create_chart_png(400, 200);
1486 doc.add_paragraph("");
1487 doc.add_picture(
1488 &chart_img,
1489 "performance_chart.png",
1490 Length::inches(5.0),
1491 Length::inches(2.5),
1492 )
1493 .alignment(Alignment::Center);
1494 {
1495 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1496 p.add_run("Figure 1: Quarterly Carbon Emissions (tonnes CO2e)")
1497 .italic(true)
1498 .size(9.0)
1499 .color("666666");
1500 }
1501
1502 // ── Section 2: Energy Consumption ──
1503 doc.add_paragraph("").page_break_before(true);
1504 doc.add_paragraph("Energy Consumption").style("Heading1");
1505
1506 doc.add_paragraph("Data Center Operations")
1507 .style("Heading2");
1508 doc.add_paragraph(
1509 "Our three primary data centers consumed a combined 4.2 GWh in Q4 2025, \
1510 a 12% reduction from Q3 2025 driven by improved cooling efficiency and \
1511 server consolidation.",
1512 );
1513
1514 // Data table
1515 {
1516 let mut tbl = doc
1517 .add_table(5, 4)
1518 .borders(BorderStyle::Single, 4, "2D5016")
1519 .width_pct(100.0);
1520 let hdrs = ["Data Center", "Capacity (MW)", "Usage (GWh)", "PUE"];
1521 for (c, h) in hdrs.iter().enumerate() {
1522 tbl.cell(0, c).unwrap().set_text(h);
1523 tbl.cell(0, c).unwrap().shading("2D5016");
1524 }
1525 let rows = [
1526 ("San Francisco (Primary)", "3.2", "1.8", "1.12"),
1527 ("Dublin (EU)", "2.1", "1.2", "1.18"),
1528 ("Singapore (APAC)", "1.8", "1.2", "1.24"),
1529 ("TOTAL", "7.1", "4.2", "1.17 avg"),
1530 ];
1531 for (i, (dc, cap, use_, pue)) in rows.iter().enumerate() {
1532 tbl.cell(i + 1, 0).unwrap().set_text(dc);
1533 tbl.cell(i + 1, 1).unwrap().set_text(cap);
1534 tbl.cell(i + 1, 2).unwrap().set_text(use_);
1535 tbl.cell(i + 1, 3).unwrap().set_text(pue);
1536 if i == 3 {
1537 for c in 0..4 {
1538 tbl.cell(i + 1, c).unwrap().shading("E2EFDA");
1539 }
1540 }
1541 }
1542 }
1543
1544 doc.add_paragraph("");
1545
1546 doc.add_paragraph("Renewable Energy Mix").style("Heading2");
1547 doc.add_paragraph("Breakdown of energy sources across all facilities:");
1548
1549 doc.add_bullet_list_item("Solar PV: 42% (1.76 GWh)", 0);
1550 doc.add_bullet_list_item("Wind Power Purchase Agreements: 38% (1.60 GWh)", 0);
1551 doc.add_bullet_list_item("Hydroelectric: 12% (0.50 GWh)", 0);
1552 doc.add_bullet_list_item("Grid (non-renewable): 8% (0.34 GWh)", 0);
1553
1554 // ── Section 3: Water & Waste ──
1555 doc.add_paragraph("Water & Waste Management")
1556 .style("Heading1");
1557
1558 doc.add_paragraph("Water Usage").style("Heading2");
1559 doc.add_paragraph(
1560 "Total water consumption was 12.4 million gallons, a 15% reduction from Q3. \
1561 Our closed-loop cooling systems now recycle 78% of water used in cooling operations.",
1562 );
1563
1564 doc.add_paragraph("Waste Reduction").style("Heading2");
1565 doc.add_paragraph("E-waste management results for Q4:")
1566 .keep_with_next(true);
1567 {
1568 let mut tbl = doc
1569 .add_table(4, 3)
1570 .borders(BorderStyle::Single, 2, "6B8E23")
1571 .width_pct(80.0);
1572 tbl.cell(0, 0).unwrap().set_text("Category");
1573 tbl.cell(0, 0).unwrap().shading("6B8E23");
1574 tbl.cell(0, 1).unwrap().set_text("Weight (kg)");
1575 tbl.cell(0, 1).unwrap().shading("6B8E23");
1576 tbl.cell(0, 2).unwrap().set_text("Recycled %");
1577 tbl.cell(0, 2).unwrap().shading("6B8E23");
1578 let rows = [
1579 ("Server Hardware", "2,450", "94%"),
1580 ("Networking Equipment", "820", "91%"),
1581 ("Storage Media", "340", "99%"),
1582 ];
1583 for (i, (cat, wt, pct)) in rows.iter().enumerate() {
1584 tbl.cell(i + 1, 0).unwrap().set_text(cat);
1585 tbl.cell(i + 1, 1).unwrap().set_text(wt);
1586 tbl.cell(i + 1, 2).unwrap().set_text(pct);
1587 }
1588 }
1589
1590 // ── Section 4: Initiatives ──
1591 doc.add_paragraph("").page_break_before(true);
1592 doc.add_paragraph("Q1 2026 Initiatives").style("Heading1");
1593
1594 doc.add_paragraph("Planned Programs").style("Heading2");
1595 doc.add_numbered_list_item(
1596 "Deploy on-site battery storage at SF data center (2 MWh capacity)",
1597 0,
1598 );
1599 doc.add_numbered_list_item("Pilot immersion cooling in Dublin facility", 0);
1600 doc.add_numbered_list_item("Launch employee commute carbon offset program", 0);
1601 doc.add_numbered_list_item("Achieve ISO 14001 certification for Singapore facility", 0);
1602
1603 doc.add_paragraph("Investment Targets").style("Heading2");
1604 doc.add_paragraph("Sustainability CapEx allocation for FY2026:")
1605 .keep_with_next(true);
1606 {
1607 let mut tbl = doc
1608 .add_table(5, 2)
1609 .borders(BorderStyle::Single, 4, "2D5016");
1610 tbl.cell(0, 0).unwrap().set_text("Initiative");
1611 tbl.cell(0, 0).unwrap().shading("2D5016");
1612 tbl.cell(0, 1).unwrap().set_text("Budget");
1613 tbl.cell(0, 1).unwrap().shading("2D5016");
1614 let rows = [
1615 ("Battery Storage", "$1.2M"),
1616 ("Immersion Cooling Pilot", "$800K"),
1617 ("Solar Panel Expansion", "$2.1M"),
1618 ("Carbon Credits", "$500K"),
1619 ];
1620 for (i, (init, budget)) in rows.iter().enumerate() {
1621 tbl.cell(i + 1, 0).unwrap().set_text(init);
1622 tbl.cell(i + 1, 1).unwrap().set_text(budget);
1623 if i % 2 == 0 {
1624 tbl.cell(i + 1, 0).unwrap().shading("FFFAF0");
1625 tbl.cell(i + 1, 1).unwrap().shading("FFFAF0");
1626 }
1627 }
1628 }
1629
1630 // Image: sustainability roadmap
1631 let roadmap_img = create_sample_png(500, 100, [40, 80, 30]);
1632 doc.add_paragraph("");
1633 doc.add_picture(
1634 &roadmap_img,
1635 "roadmap.png",
1636 Length::inches(6.0),
1637 Length::inches(1.2),
1638 )
1639 .alignment(Alignment::Center);
1640 {
1641 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1642 p.add_run("Figure 2: 2026 Sustainability Roadmap")
1643 .italic(true)
1644 .size(9.0)
1645 .color("666666");
1646 }
1647
1648 doc.add_paragraph("");
1649 doc.add_paragraph(
1650 "For questions about this report, contact Walter White at sustainability@tensorbee.com.",
1651 )
1652 .shading("FFFAF0")
1653 .border_all(BorderStyle::Single, 2, "2D5016");
1654
1655 doc
1656}
1657
1658// =============================================================================
1659// 6. LETTER — Slate blue + silver scheme
1660// =============================================================================
1661fn generate_letter(_samples_dir: &Path) -> Document {
1662 let mut doc = Document::new();
1663
1664 // Colors: Slate #4A5568, Blue accent #3182CE
1665 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1666 doc.set_margins(
1667 Length::inches(1.25),
1668 Length::inches(1.25),
1669 Length::inches(1.25),
1670 Length::inches(1.25),
1671 );
1672 doc.set_title("Business Letter");
1673 doc.set_author("Walter White");
1674
1675 // Banner header using raw XML
1676 let logo_img = create_logo_png(220, 48);
1677 let banner = build_header_banner_xml(
1678 "rId1",
1679 &BannerOpts {
1680 bg_color: "4A5568",
1681 banner_width: 7772400,
1682 banner_height: 731520, // ~0.8"
1683 logo_width: 2011680,
1684 logo_height: 438912,
1685 logo_x_offset: 295125,
1686 logo_y_offset: 146304,
1687 },
1688 );
1689 doc.set_raw_header_with_images(
1690 banner,
1691 &[("rId1", &logo_img, "logo.png")],
1692 rdocx_oxml::header_footer::HdrFtrType::Default,
1693 );
1694 doc.set_margins(
1695 Length::twips(2000),
1696 Length::twips(1800),
1697 Length::twips(1440),
1698 Length::twips(1800),
1699 );
1700 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
1701
1702 // Footer with contact
1703 doc.set_footer(
1704 "Tensorbee | 123 Innovation Drive, Suite 400 | San Francisco, CA 94105 | tensorbee.com",
1705 );
1706
1707 // ── Sender Address ──
1708 doc.add_paragraph("Walter White");
1709 doc.add_paragraph("Chief Executive Officer");
1710 doc.add_paragraph("Tensorbee");
1711 doc.add_paragraph("123 Innovation Drive, Suite 400");
1712 doc.add_paragraph("San Francisco, CA 94105");
1713 doc.add_paragraph("");
1714 doc.add_paragraph("February 22, 2026");
1715
1716 doc.add_paragraph("");
1717
1718 // ── Recipient ──
1719 doc.add_paragraph("Ms. Sarah Chen");
1720 doc.add_paragraph("VP of Engineering");
1721 doc.add_paragraph("Meridian Dynamics LLC");
1722 doc.add_paragraph("456 Enterprise Boulevard");
1723 doc.add_paragraph("Austin, TX 78701");
1724
1725 doc.add_paragraph("");
1726
1727 // ── Subject ──
1728 {
1729 let mut p = doc.add_paragraph("");
1730 p.add_run("Re: Strategic Technology Partnership — Phase 2 Expansion")
1731 .bold(true);
1732 }
1733
1734 doc.add_paragraph("");
1735
1736 // ── Body ──
1737 doc.add_paragraph("Dear Ms. Chen,");
1738 doc.add_paragraph("");
1739
1740 doc.add_paragraph(
1741 "I am writing to express Tensorbee's enthusiasm for expanding our strategic technology \
1742 partnership with Meridian Dynamics. Following the successful completion of Phase 1, \
1743 which delivered a 40% improvement in your ML inference pipeline throughput, we believe \
1744 the foundation is firmly established for an ambitious Phase 2 engagement.",
1745 )
1746 .first_line_indent(Length::inches(0.5));
1747
1748 doc.add_paragraph(
1749 "During our recent executive review, your team highlighted three priority areas for \
1750 the next phase of collaboration:",
1751 )
1752 .first_line_indent(Length::inches(0.5));
1753
1754 doc.add_numbered_list_item(
1755 "Real-time anomaly detection for your financial transaction monitoring system, \
1756 targeting sub-100ms latency at 50,000 transactions per second.",
1757 0,
1758 );
1759 doc.add_numbered_list_item(
1760 "Federated learning infrastructure to enable model training across your distributed \
1761 data centers without centralizing sensitive financial data.",
1762 0,
1763 );
1764 doc.add_numbered_list_item(
1765 "MLOps automation to reduce your model deployment cycle from the current 5 days \
1766 to under 4 hours.",
1767 0,
1768 );
1769
1770 doc.add_paragraph(
1771 "Our engineering team has prepared a detailed technical proposal addressing each of \
1772 these areas. We have allocated a dedicated team of eight senior engineers, led by \
1773 our CTO, to ensure continuity with the Phase 1 team your organization has already \
1774 built a productive working relationship with.",
1775 )
1776 .first_line_indent(Length::inches(0.5));
1777
1778 doc.add_paragraph(
1779 "I would welcome the opportunity to present our Phase 2 proposal in person. My \
1780 assistant will reach out to coordinate a meeting at your convenience during the \
1781 first week of March.",
1782 )
1783 .first_line_indent(Length::inches(0.5));
1784
1785 doc.add_paragraph("");
1786
1787 doc.add_paragraph("Warm regards,");
1788 doc.add_paragraph("");
1789 doc.add_paragraph("");
1790
1791 // Signature line
1792 doc.add_paragraph("")
1793 .border_bottom(BorderStyle::Single, 4, "4A5568");
1794 {
1795 let mut p = doc.add_paragraph("");
1796 p.add_run("Walter White").bold(true).size(12.0);
1797 }
1798 {
1799 let mut p = doc.add_paragraph("");
1800 p.add_run("Chief Executive Officer, Tensorbee")
1801 .italic(true)
1802 .size(10.0)
1803 .color("666666");
1804 }
1805 {
1806 let mut p = doc.add_paragraph("");
1807 p.add_run("walter@tensorbee.com | +1 (415) 555-0199")
1808 .size(10.0)
1809 .color("888888");
1810 }
1811
1812 doc
1813}Sourcepub fn add_list_definition(&mut self, levels: &[ListLevel]) -> u32
pub fn add_list_definition(&mut self, levels: &[ListLevel]) -> u32
Create a list definition with explicit per-level formats and return its numId.
Unlike Self::add_bullet_list_item / Self::add_numbered_list_item,
which share one bullet and one numbered definition per document, every
call creates a fresh definition — so separate lists restart their
numbering, and one definition can mix formats across levels (e.g. a
bullet list whose nested level is decimal). Attach paragraphs with
crate::Paragraph::set_numbering.
levels[i] configures level i; deeper unspecified levels fall back
to the standard template rotation for the last specified format’s
family. An empty slice produces the standard numbered template. Word
supports nine levels, so entries after index eight are ignored.
use rdocx::{Document, ListLevel};
let mut doc = Document::new();
let num_id = doc.add_list_definition(&[
ListLevel::bullet(),
ListLevel::decimal().start(3),
]);
doc.add_paragraph("first bullet").set_numbering(num_id, 0);
doc.add_paragraph("third decimal").set_numbering(num_id, 1);Sourcepub fn set_list_level(
&mut self,
num_id: u32,
level: u32,
spec: ListLevel,
) -> bool
pub fn set_list_level( &mut self, num_id: u32, level: u32, spec: ListLevel, ) -> bool
Redefine one level (0–8) of an existing list definition, for callers that only learn a deeper level’s format when content first reaches it.
Returns false when num_id is unknown or level is out of range.
Sourcepub fn add_style(&mut self, builder: StyleBuilder)
pub fn add_style(&mut self, builder: StyleBuilder)
Add a custom style to the document.
Examples found in repository?
97fn generate_feature_showcase(_samples_dir: &Path) -> Document {
98 let mut doc = Document::new();
99
100 // ── Page Setup & Metadata ──
101 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
102 doc.set_margins(
103 Length::inches(1.0),
104 Length::inches(1.0),
105 Length::inches(1.0),
106 Length::inches(1.0),
107 );
108 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
109 doc.set_gutter(Length::twips(0));
110 doc.set_title("rdocx Feature Showcase");
111 doc.set_author("rdocx Sample Generator");
112 doc.set_subject("Comprehensive feature demonstration");
113 doc.set_keywords("rdocx, docx, rust, sample, showcase");
114
115 // Headers & Footers with different first page
116 doc.set_different_first_page(true);
117 doc.set_first_page_header("rdocx");
118 doc.set_first_page_footer("Feature Showcase — Cover Page");
119 doc.set_header("rdocx Feature Showcase");
120 doc.set_footer("Generated by rdocx");
121
122 // ── COVER PAGE ──
123 let bg = create_sample_png(612, 792, [20, 45, 90]);
124 doc.add_background_image(&bg, "cover_bg.png");
125
126 for _ in 0..3 {
127 doc.add_paragraph("");
128 }
129 {
130 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
131 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
132 }
133 {
134 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
135 p.add_run("Complete Feature Showcase")
136 .size(28.0)
137 .color("FFFFFF")
138 .italic(true);
139 }
140 doc.add_paragraph("");
141 {
142 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
143 p.add_run("Every feature of the rdocx Rust library")
144 .size(13.0)
145 .color("B0C4DE");
146 }
147 {
148 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
149 p.add_run("demonstrated in a single document.")
150 .size(13.0)
151 .color("B0C4DE");
152 }
153
154 // ── TABLE OF CONTENTS ──
155 doc.add_paragraph("").page_break_before(true);
156 doc.insert_toc(doc.content_count(), 3);
157
158 // ── SECTION 1: TEXT FORMATTING ──
159 doc.add_paragraph("").page_break_before(true);
160 doc.add_paragraph("1. Text Formatting").style("Heading1");
161
162 // Paragraph Alignment
163 doc.add_paragraph("1.1 Paragraph Alignment")
164 .style("Heading2");
165 doc.add_paragraph("Left-aligned paragraph (default).")
166 .alignment(Alignment::Left);
167 doc.add_paragraph("Center-aligned paragraph.")
168 .alignment(Alignment::Center);
169 doc.add_paragraph("Right-aligned paragraph.")
170 .alignment(Alignment::Right);
171 doc.add_paragraph(
172 "Justified paragraph — this text is long enough to demonstrate how justified alignment \
173 distributes extra space across word gaps so lines fill the full width of the text area.",
174 )
175 .alignment(Alignment::Justify);
176
177 doc.add_paragraph("");
178
179 // Run Formatting
180 doc.add_paragraph("1.2 Run Formatting").style("Heading2");
181 {
182 let mut p = doc.add_paragraph("");
183 p.add_run("Bold").bold(true);
184 p.add_run(" | ");
185 p.add_run("Italic").italic(true);
186 p.add_run(" | ");
187 p.add_run("Bold Italic").bold(true).italic(true);
188 p.add_run(" | ");
189 p.add_run("Underline").underline(true);
190 p.add_run(" | ");
191 p.add_run("Strikethrough").strike(true);
192 p.add_run(" | ");
193 p.add_run("Double Strike").double_strike(true);
194 }
195 {
196 let mut p = doc.add_paragraph("");
197 p.add_run("Red").color("FF0000");
198 p.add_run(" | ");
199 p.add_run("Blue").color("0000FF");
200 p.add_run(" | ");
201 p.add_run("Green").color("00AA00");
202 p.add_run(" | ");
203 p.add_run("Highlighted").highlight("FFFF00");
204 }
205 {
206 let mut p = doc.add_paragraph("");
207 p.add_run("8pt").size(8.0);
208 p.add_run(" | ");
209 p.add_run("11pt").size(11.0);
210 p.add_run(" | ");
211 p.add_run("16pt").size(16.0);
212 p.add_run(" | ");
213 p.add_run("24pt").size(24.0);
214 }
215 {
216 let mut p = doc.add_paragraph("");
217 p.add_run("Arial").font("Arial");
218 p.add_run(" | ");
219 p.add_run("Times New Roman").font("Times New Roman");
220 p.add_run(" | ");
221 p.add_run("Courier New").font("Courier New");
222 }
223 {
224 let mut p = doc.add_paragraph("");
225 p.add_run("H");
226 p.add_run("2").subscript();
227 p.add_run("O (subscript) | E=mc");
228 p.add_run("2").superscript();
229 p.add_run(" (superscript)");
230 }
231 {
232 let mut p = doc.add_paragraph("");
233 p.add_run("ALL CAPS").all_caps(true);
234 p.add_run(" | ");
235 p.add_run("Small Caps").small_caps(true);
236 p.add_run(" | ");
237 p.add_run("Expanded").character_spacing(Length::twips(40));
238 p.add_run(" | ");
239 p.add_run("Hidden text (not visible)").hidden(true);
240 }
241
242 doc.add_paragraph("");
243
244 // Underline Styles
245 doc.add_paragraph("1.3 Underline Styles").style("Heading2");
246 {
247 let mut p = doc.add_paragraph("");
248 p.add_run("Single ")
249 .underline_style(rdocx::UnderlineStyle::Single);
250 p.add_run("Double ")
251 .underline_style(rdocx::UnderlineStyle::Double);
252 p.add_run("Thick ")
253 .underline_style(rdocx::UnderlineStyle::Thick);
254 p.add_run("Dotted ")
255 .underline_style(rdocx::UnderlineStyle::Dotted);
256 p.add_run("Dash ")
257 .underline_style(rdocx::UnderlineStyle::Dash);
258 p.add_run("Wave ")
259 .underline_style(rdocx::UnderlineStyle::Wave);
260 }
261
262 // ── SECTION 2: PARAGRAPH FORMATTING ──
263 doc.add_paragraph("").page_break_before(true);
264 doc.add_paragraph("2. Paragraph Formatting")
265 .style("Heading1");
266
267 doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
268 doc.add_paragraph("Paragraph with light green shading")
269 .shading("E2EFDA");
270 doc.add_paragraph("Paragraph with bottom border")
271 .border_bottom(BorderStyle::Single, 6, "2E75B6");
272 doc.add_paragraph("Paragraph with all borders (red)")
273 .border_all(BorderStyle::Single, 4, "FF0000");
274
275 doc.add_paragraph("2.2 Indentation").style("Heading2");
276 doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
277 .indent_left(Length::inches(1.0))
278 .hanging_indent(Length::inches(0.5));
279 doc.add_paragraph("0.5-inch first-line indent on this paragraph")
280 .first_line_indent(Length::inches(0.5));
281 doc.add_paragraph("Both left and right indent (0.75 inches each)")
282 .indent_left(Length::inches(0.75))
283 .indent_right(Length::inches(0.75));
284
285 doc.add_paragraph("2.3 Spacing & Line Height")
286 .style("Heading2");
287 doc.add_paragraph("Extra space before (24pt) and after (12pt)")
288 .space_before(Length::pt(24.0))
289 .space_after(Length::pt(12.0));
290 doc.add_paragraph(
291 "Double line spacing paragraph. This text should have extra vertical space between \
292 lines to demonstrate line_spacing_multiple(2.0).",
293 )
294 .line_spacing_multiple(2.0);
295 doc.add_paragraph("Exact 20pt line spacing (fixed height).")
296 .line_spacing(20.0);
297
298 doc.add_paragraph("2.4 Pagination Controls")
299 .style("Heading2");
300 doc.add_paragraph("keep_with_next — this paragraph stays with the next")
301 .keep_with_next(true);
302 doc.add_paragraph("(This paragraph was kept with the one above.)");
303 doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
304 .keep_together(true);
305 doc.add_paragraph("widow_control — prevents widow/orphan lines")
306 .widow_control(true);
307
308 // ── SECTION 3: LISTS ──
309 doc.add_paragraph("").page_break_before(true);
310 doc.add_paragraph("3. Lists").style("Heading1");
311
312 doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
313 doc.add_bullet_list_item("First item", 0);
314 doc.add_bullet_list_item("Second item", 0);
315 doc.add_bullet_list_item("Nested level 1", 1);
316 doc.add_bullet_list_item("Nested level 2", 2);
317 doc.add_bullet_list_item("Back to level 1", 1);
318 doc.add_bullet_list_item("Third item", 0);
319
320 doc.add_paragraph("");
321
322 doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
323 doc.add_numbered_list_item("First numbered item", 0);
324 doc.add_numbered_list_item("Second numbered item", 0);
325 doc.add_numbered_list_item("Sub-item A", 1);
326 doc.add_numbered_list_item("Sub-item B", 1);
327 doc.add_numbered_list_item("Third numbered item", 0);
328
329 // ── SECTION 4: TAB STOPS ──
330 doc.add_paragraph("");
331 doc.add_paragraph("4. Tab Stops").style("Heading1");
332
333 doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
334 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
335 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
336 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
337 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
338 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
339
340 doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
341 doc.add_paragraph("Item\t\tPrice")
342 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
343 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
344 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
345 doc.add_paragraph("Widget\t\t$19.99")
346 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
347 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
348 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
349 doc.add_paragraph("Gadget\t\t$249.50")
350 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
351 .add_tab_stop_with_leader(
352 TabAlignment::Right,
353 Length::inches(4.0),
354 TabLeader::Underscore,
355 )
356 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
357
358 // ── SECTION 5: TABLES ──
359 doc.add_paragraph("").page_break_before(true);
360 doc.add_paragraph("5. Tables").style("Heading1");
361
362 doc.add_paragraph("5.1 Basic Table").style("Heading2");
363 {
364 let mut tbl = doc
365 .add_table(4, 3)
366 .borders(BorderStyle::Single, 4, "000000");
367 for col in 0..3 {
368 tbl.cell(0, col).unwrap().shading("2E75B6");
369 }
370 tbl.cell(0, 0).unwrap().set_text("Name");
371 tbl.cell(0, 1).unwrap().set_text("Role");
372 tbl.cell(0, 2).unwrap().set_text("Location");
373 tbl.cell(1, 0).unwrap().set_text("Walter White");
374 tbl.cell(1, 1).unwrap().set_text("CEO");
375 tbl.cell(1, 2).unwrap().set_text("Albuquerque");
376 tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
377 tbl.cell(2, 1).unwrap().set_text("CTO");
378 tbl.cell(2, 2).unwrap().set_text("Remote");
379 tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
380 tbl.cell(3, 1).unwrap().set_text("Security");
381 tbl.cell(3, 2).unwrap().set_text("Washington");
382 }
383
384 doc.add_paragraph("");
385
386 doc.add_paragraph("5.2 Column Span & Cell Shading")
387 .style("Heading2");
388 {
389 let mut tbl = doc
390 .add_table(3, 4)
391 .borders(BorderStyle::Single, 4, "000000")
392 .width_pct(100.0);
393 tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
394 tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
395 tbl.cell(1, 0).unwrap().set_text("Region");
396 tbl.cell(1, 0).unwrap().shading("D6E4F0");
397 tbl.cell(1, 1).unwrap().set_text("Q1");
398 tbl.cell(1, 1).unwrap().shading("D6E4F0");
399 tbl.cell(1, 2).unwrap().set_text("Q2");
400 tbl.cell(1, 2).unwrap().shading("D6E4F0");
401 tbl.cell(1, 3).unwrap().set_text("Total");
402 tbl.cell(1, 3).unwrap().shading("D6E4F0");
403 tbl.cell(2, 0).unwrap().set_text("Americas");
404 tbl.cell(2, 1).unwrap().set_text("$2.4M");
405 tbl.cell(2, 2).unwrap().set_text("$2.7M");
406 tbl.cell(2, 3).unwrap().set_text("$5.1M");
407 }
408
409 doc.add_paragraph("");
410
411 doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
412 {
413 let mut tbl = doc
414 .add_table(4, 3)
415 .borders(BorderStyle::Single, 4, "000000");
416 tbl.cell(0, 0).unwrap().set_text("Category");
417 tbl.cell(0, 0).unwrap().shading("E2EFDA");
418 tbl.cell(0, 1).unwrap().set_text("Item");
419 tbl.cell(0, 1).unwrap().shading("E2EFDA");
420 tbl.cell(0, 2).unwrap().set_text("Price");
421 tbl.cell(0, 2).unwrap().shading("E2EFDA");
422 tbl.cell(1, 0).unwrap().set_text("Hardware");
423 tbl.cell(1, 0).unwrap().v_merge_restart();
424 tbl.cell(1, 1).unwrap().set_text("Laptop");
425 tbl.cell(1, 2).unwrap().set_text("$1,200");
426 tbl.cell(2, 0).unwrap().v_merge_continue();
427 tbl.cell(2, 1).unwrap().set_text("Monitor");
428 tbl.cell(2, 2).unwrap().set_text("$450");
429 tbl.cell(3, 0).unwrap().set_text("Software");
430 tbl.cell(3, 1).unwrap().set_text("IDE License");
431 tbl.cell(3, 2).unwrap().set_text("$200/yr");
432 }
433
434 doc.add_paragraph("");
435
436 doc.add_paragraph("5.4 Nested Table").style("Heading2");
437 {
438 let mut tbl = doc
439 .add_table(2, 2)
440 .borders(BorderStyle::Single, 6, "2E75B6");
441 tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
442 tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
443 tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
444 {
445 let mut cell = tbl.cell(1, 1).unwrap();
446 cell.set_text("Nested table below:");
447 let mut nested = cell
448 .add_table(2, 2)
449 .borders(BorderStyle::Single, 2, "FF6600");
450 nested.cell(0, 0).unwrap().set_text("A");
451 nested.cell(0, 1).unwrap().set_text("B");
452 nested.cell(1, 0).unwrap().set_text("C");
453 nested.cell(1, 1).unwrap().set_text("D");
454 }
455 }
456
457 doc.add_paragraph("");
458
459 doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
460 .style("Heading2");
461 {
462 let mut tbl = doc
463 .add_table(2, 3)
464 .borders(BorderStyle::Single, 4, "666666");
465 tbl.row(0).unwrap().height(Length::pt(50.0)).header();
466 tbl.cell(0, 0).unwrap().set_text("Top");
467 tbl.cell(0, 0)
468 .unwrap()
469 .vertical_alignment(VerticalAlignment::Top)
470 .shading("FFF2CC");
471 tbl.cell(0, 1).unwrap().set_text("Center");
472 tbl.cell(0, 1)
473 .unwrap()
474 .vertical_alignment(VerticalAlignment::Center)
475 .shading("D9E2F3");
476 tbl.cell(0, 2).unwrap().set_text("Bottom");
477 tbl.cell(0, 2)
478 .unwrap()
479 .vertical_alignment(VerticalAlignment::Bottom)
480 .shading("E2EFDA");
481 tbl.row(1).unwrap().cant_split();
482 tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
483 tbl.cell(1, 0).unwrap().no_wrap();
484 tbl.cell(1, 1).unwrap().set_text("Fixed width");
485 tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
486 tbl.cell(1, 2).unwrap().set_text("Normal");
487 }
488
489 // ── SECTION 6: IMAGES ──
490 doc.add_paragraph("").page_break_before(true);
491 doc.add_paragraph("6. Images").style("Heading1");
492
493 doc.add_paragraph("6.1 Inline Image").style("Heading2");
494 doc.add_paragraph("A blue gradient image below:");
495 let img = create_sample_png(200, 50, [0, 80, 200]);
496 doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
497
498 doc.add_paragraph("");
499 doc.add_paragraph("6.2 Header Image").style("Heading2");
500 let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
501 doc.set_header_image(
502 &hdr_img,
503 "header_logo.png",
504 Length::inches(2.0),
505 Length::inches(0.2),
506 );
507 doc.add_paragraph("The header now contains an inline image (check the top of this page).");
508
509 doc.add_paragraph("");
510 doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
511
512 // ── SECTION 7: CONTENT MANIPULATION ──
513 doc.add_paragraph("").page_break_before(true);
514 doc.add_paragraph("7. Content Manipulation")
515 .style("Heading1");
516
517 doc.add_paragraph("7.1 Placeholder Replacement")
518 .style("Heading2");
519 doc.add_paragraph("Customer: {{customer}}");
520 doc.add_paragraph("Date: {{date}}");
521 doc.add_paragraph("Reference: {{ref_number}}");
522 {
523 let mut tbl = doc
524 .add_table(2, 2)
525 .borders(BorderStyle::Single, 4, "000000");
526 tbl.cell(0, 0).unwrap().set_text("Project");
527 tbl.cell(0, 0).unwrap().shading("D6E4F0");
528 tbl.cell(0, 1).unwrap().set_text("{{project}}");
529 tbl.cell(1, 0).unwrap().set_text("Status");
530 tbl.cell(1, 0).unwrap().shading("D6E4F0");
531 tbl.cell(1, 1).unwrap().set_text("{{status}}");
532 }
533
534 let mut replacements = HashMap::new();
535 replacements.insert("{{customer}}", "Tensorbee Inc.");
536 replacements.insert("{{date}}", "February 22, 2026");
537 replacements.insert("{{ref_number}}", "TB-2026-001");
538 replacements.insert("{{project}}", "Infrastructure Upgrade");
539 replacements.insert("{{status}}", "In Progress");
540 let n = doc.replace_all(&replacements);
541 doc.add_paragraph(&format!("({n} placeholders replaced above)"));
542
543 doc.add_paragraph("");
544
545 doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
546 doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
547 let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
548 doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
549
550 doc.add_paragraph("");
551
552 doc.add_paragraph("7.3 Content Insertion").style("Heading2");
553 doc.add_paragraph("Section A: First content.");
554 doc.add_paragraph("Section C: Third content.");
555 if let Some(idx) = doc.find_content_index("Section C") {
556 doc.insert_paragraph(
557 idx,
558 "Section B: Inserted between A and C via find_content_index().",
559 );
560 }
561
562 // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
563 doc.add_paragraph("").section_break(SectionBreak::NextPage);
564 doc.add_paragraph("8. Section Breaks & Orientation")
565 .style("Heading1");
566 doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
567
568 {
569 let mut tbl = doc
570 .add_table(3, 7)
571 .borders(BorderStyle::Single, 4, "2E75B6");
572 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
573 for (c, h) in headers.iter().enumerate() {
574 tbl.cell(0, c).unwrap().set_text(h);
575 tbl.cell(0, c).unwrap().shading("2E75B6");
576 }
577 let data = [
578 [
579 "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
580 ],
581 [
582 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
583 ],
584 ];
585 for (r, row) in data.iter().enumerate() {
586 for (c, v) in row.iter().enumerate() {
587 tbl.cell(r + 1, c).unwrap().set_text(v);
588 }
589 }
590 }
591
592 doc.add_paragraph("")
593 .section_break(SectionBreak::NextPage)
594 .section_landscape();
595
596 // ── SECTION 9: CUSTOM STYLES ──
597 doc.add_paragraph("9. Custom Styles").style("Heading1");
598 doc.add_style(
599 StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
600 .based_on("Normal")
601 .paragraph_properties(rdocx_oxml::properties::CT_PPr {
602 shading: Some(rdocx_oxml::properties::CT_Shd {
603 val: "clear".to_string(),
604 color: None,
605 fill: Some("FFF2CC".to_string()),
606 }),
607 ..Default::default()
608 })
609 .run_properties(rdocx_oxml::properties::CT_RPr {
610 bold: Some(true),
611 color: Some("C45911".to_string()),
612 ..Default::default()
613 }),
614 );
615 doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
616 .style("CustomHighlight");
617
618 doc.add_paragraph("");
619
620 // ── SECTION 10: DOCUMENT INTELLIGENCE ──
621 doc.add_paragraph("10. Document Intelligence API")
622 .style("Heading1");
623
624 let wc = doc.word_count();
625 let hc = doc.headings().len();
626 let ic = doc.images().len();
627 let lc = doc.links().len();
628 doc.add_paragraph(&format!("Word count: {wc}"));
629 doc.add_paragraph(&format!("Heading count: {hc}"));
630 doc.add_paragraph(&format!("Image count: {ic}"));
631 doc.add_paragraph(&format!("Link count: {lc}"));
632
633 let outline = doc.document_outline();
634 doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
635
636 let issues = doc.audit_accessibility();
637 doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
638 for issue in &issues {
639 doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
640 }
641
642 // ── SECTION 11: DOCUMENT MERGING ──
643 doc.add_paragraph("");
644 doc.add_paragraph("11. Document Merging").style("Heading1");
645 {
646 let mut other = Document::new();
647 other.add_paragraph("This paragraph was merged from another document using append().");
648 doc.append(&other);
649 }
650
651 doc.add_paragraph("");
652
653 // ── SUMMARY ──
654 doc.add_paragraph("Summary of Demonstrated Features")
655 .style("Heading1");
656 let features = [
657 "Page setup: size, margins, header/footer distance, gutter",
658 "Document metadata: title, author, subject, keywords",
659 "Headers and footers: text, images, different first page",
660 "Background images (full-page behind text)",
661 "Table of Contents generation with bookmarks",
662 "Text formatting: bold, italic, underline styles, strike, color, size, font",
663 "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
664 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
665 "Pagination controls: keep-with-next, keep-together, widow control",
666 "Bullet and numbered lists with nesting",
667 "Tab stops with dot/underscore/hyphen leaders",
668 "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
669 "Row properties: header rows, exact height, cant-split",
670 "Cell properties: width, no-wrap, vertical alignment",
671 "Inline images and header images",
672 "Placeholder replacement (single and batch)",
673 "Regex-based find and replace",
674 "Content insertion at specific positions",
675 "Section breaks with mixed portrait/landscape",
676 "Custom paragraph and character styles",
677 "Document intelligence: word count, headings, outline, images, links",
678 "Accessibility audit",
679 "Document merging (append)",
680 "Export: DOCX, PDF, HTML, Markdown",
681 ];
682 for f in &features {
683 doc.add_bullet_list_item(f, 0);
684 }
685 doc.add_paragraph("");
686 doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
687 .alignment(Alignment::Center)
688 .shading("E2EFDA")
689 .border_all(BorderStyle::Single, 2, "00AA00");
690
691 doc
692}
693
694// =============================================================================
695// 2. PROPOSAL DOCUMENT — Deep navy + gold scheme
696// =============================================================================
697fn generate_proposal(_samples_dir: &Path) -> Document {
698 let mut doc = Document::new();
699
700 // Colors: Navy #1B2A4A, Gold #C5922E, Light #F4F1EB
701 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
702 doc.set_margins(
703 Length::inches(1.0),
704 Length::inches(1.0),
705 Length::inches(1.0),
706 Length::inches(1.0),
707 );
708 doc.set_title("AI Platform Modernization Proposal");
709 doc.set_author("Walter White");
710 doc.set_subject("Technology Proposal");
711 doc.set_keywords("proposal, AI, modernization, Tensorbee");
712
713 // Banner header
714 let logo_img = create_logo_png(220, 48);
715 let banner = build_header_banner_xml(
716 "rId1",
717 &BannerOpts {
718 bg_color: "1B2A4A",
719 banner_width: 7772400,
720 banner_height: 914400,
721 logo_width: 2011680,
722 logo_height: 438912,
723 logo_x_offset: 295125,
724 logo_y_offset: 237744,
725 },
726 );
727 doc.set_raw_header_with_images(
728 banner,
729 &[("rId1", &logo_img, "logo.png")],
730 rdocx_oxml::header_footer::HdrFtrType::Default,
731 );
732 doc.set_different_first_page(true);
733 let cover_banner = build_header_banner_xml(
734 "rId1",
735 &BannerOpts {
736 bg_color: "C5922E",
737 banner_width: 7772400,
738 banner_height: 914400,
739 logo_width: 2011680,
740 logo_height: 438912,
741 logo_x_offset: 295125,
742 logo_y_offset: 237744,
743 },
744 );
745 doc.set_raw_header_with_images(
746 cover_banner,
747 &[("rId1", &logo_img, "logo.png")],
748 rdocx_oxml::header_footer::HdrFtrType::First,
749 );
750 doc.set_margins(
751 Length::twips(2292),
752 Length::twips(1440),
753 Length::twips(1440),
754 Length::twips(1440),
755 );
756 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
757 doc.set_footer("Tensorbee — Confidential");
758
759 // Custom styles
760 doc.add_style(
761 StyleBuilder::paragraph("ProposalTitle", "Proposal Title")
762 .based_on("Normal")
763 .run_properties(rdocx_oxml::properties::CT_RPr {
764 bold: Some(true),
765 font_ascii: Some("Georgia".to_string()),
766 font_hansi: Some("Georgia".to_string()),
767 sz: Some(rdocx_oxml::HalfPoint(56)), // half-points → 28pt
768 color: Some("1B2A4A".to_string()),
769 ..Default::default()
770 }),
771 );
772
773 // ── Cover Page ──
774 for _ in 0..4 {
775 doc.add_paragraph("");
776 }
777 doc.add_paragraph("AI Platform Modernization")
778 .style("ProposalTitle")
779 .alignment(Alignment::Center);
780 doc.add_paragraph("")
781 .alignment(Alignment::Center)
782 .border_bottom(BorderStyle::Single, 8, "C5922E");
783 {
784 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
785 p.add_run("Prepared for Global Financial Corp.")
786 .size(14.0)
787 .color("666666")
788 .italic(true);
789 }
790 {
791 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
792 p.add_run("Prepared by: Walter White, CEO — Tensorbee")
793 .size(12.0)
794 .color("888888");
795 }
796 {
797 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
798 p.add_run("February 22, 2026").size(12.0).color("888888");
799 }
800
801 // ── TOC ──
802 doc.add_paragraph("").page_break_before(true);
803 doc.add_paragraph("Table of Contents").style("Heading1");
804 // We'll add a manual-style TOC since the insert_toc goes at a specific position
805 doc.insert_toc(doc.content_count(), 2);
806
807 // ── Executive Summary ──
808 doc.add_paragraph("").page_break_before(true);
809 doc.add_paragraph("Executive Summary").style("Heading1");
810 doc.add_paragraph(
811 "Tensorbee proposes a comprehensive modernization of Global Financial Corp's AI platform, \
812 transitioning from legacy batch-processing systems to a real-time inference architecture. \
813 This transformation will reduce model deployment time from weeks to hours, improve \
814 prediction accuracy by an estimated 23%, and deliver $4.2M in annual operational savings.",
815 )
816 .first_line_indent(Length::inches(0.3));
817 doc.add_paragraph(
818 "The project spans three phases over 18 months, with a total investment of $2.8M. \
819 Tensorbee brings deep expertise in ML infrastructure, having successfully completed \
820 similar transformations for three Fortune 500 financial institutions.",
821 )
822 .first_line_indent(Length::inches(0.3));
823
824 // Key metrics table
825 doc.add_paragraph("");
826 {
827 let mut tbl = doc
828 .add_table(5, 2)
829 .borders(BorderStyle::Single, 4, "1B2A4A")
830 .width_pct(80.0);
831 tbl.cell(0, 0).unwrap().set_text("Key Metric");
832 tbl.cell(0, 0).unwrap().shading("1B2A4A");
833 tbl.cell(0, 1).unwrap().set_text("Value");
834 tbl.cell(0, 1).unwrap().shading("1B2A4A");
835 let rows = [
836 ("Total Investment", "$2.8M"),
837 ("Annual Savings", "$4.2M"),
838 ("ROI (Year 1)", "150%"),
839 ("Timeline", "18 months"),
840 ];
841 for (i, (k, v)) in rows.iter().enumerate() {
842 tbl.cell(i + 1, 0).unwrap().set_text(k);
843 if i % 2 == 0 {
844 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
845 }
846 tbl.cell(i + 1, 1).unwrap().set_text(v);
847 if i % 2 == 0 {
848 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
849 }
850 }
851 }
852
853 // ── Problem Statement ──
854 doc.add_paragraph("").page_break_before(true);
855 doc.add_paragraph("Problem Statement").style("Heading1");
856 doc.add_paragraph(
857 "Global Financial Corp's current AI infrastructure faces three critical challenges:",
858 );
859 doc.add_numbered_list_item("Deployment Latency — New models take 3-4 weeks to deploy to production, compared to the industry benchmark of 2-3 days.", 0);
860 doc.add_numbered_list_item("Scalability Constraints — The batch processing architecture cannot handle real-time inference demands during peak trading hours.", 0);
861 doc.add_numbered_list_item("Technical Debt — Legacy Python 2.7 codebases and manual deployment scripts create reliability risks and slow iteration speed.", 0);
862
863 // ── Proposed Solution ──
864 doc.add_paragraph("").page_break_before(true);
865 doc.add_paragraph("Proposed Solution").style("Heading1");
866
867 doc.add_paragraph("Phase 1: Foundation (Months 1-6)")
868 .style("Heading2");
869 doc.add_bullet_list_item("Deploy Kubernetes-based ML serving infrastructure", 0);
870 doc.add_bullet_list_item("Implement CI/CD pipeline for model deployment", 0);
871 doc.add_bullet_list_item("Migrate top 5 production models to new platform", 0);
872
873 doc.add_paragraph("Phase 2: Expansion (Months 7-12)")
874 .style("Heading2");
875 doc.add_bullet_list_item(
876 "Real-time feature engineering pipeline (Apache Kafka + Flink)",
877 0,
878 );
879 doc.add_bullet_list_item("A/B testing framework for model variants", 0);
880 doc.add_bullet_list_item("Automated model monitoring and drift detection", 0);
881
882 doc.add_paragraph("Phase 3: Optimization (Months 13-18)")
883 .style("Heading2");
884 doc.add_bullet_list_item("GPU inference optimization (TensorRT/ONNX Runtime)", 0);
885 doc.add_bullet_list_item("Multi-region deployment for latency reduction", 0);
886 doc.add_bullet_list_item(
887 "Self-service model deployment portal for data science teams",
888 0,
889 );
890
891 // ── Budget ──
892 doc.add_paragraph("Budget Breakdown").style("Heading1");
893 {
894 let mut tbl = doc
895 .add_table(6, 3)
896 .borders(BorderStyle::Single, 4, "1B2A4A")
897 .width_pct(100.0);
898 tbl.cell(0, 0).unwrap().set_text("Category");
899 tbl.cell(0, 0).unwrap().shading("1B2A4A");
900 tbl.cell(0, 1).unwrap().set_text("Cost");
901 tbl.cell(0, 1).unwrap().shading("1B2A4A");
902 tbl.cell(0, 2).unwrap().set_text("Notes");
903 tbl.cell(0, 2).unwrap().shading("1B2A4A");
904 let items = [
905 (
906 "Infrastructure",
907 "$450,000",
908 "Cloud compute + GPU instances",
909 ),
910 ("Engineering Services", "$1,600,000", "12 FTE x 18 months"),
911 (
912 "Software Licenses",
913 "$350,000",
914 "Kafka, monitoring, CI/CD tools",
915 ),
916 (
917 "Training & Enablement",
918 "$200,000",
919 "Team training, documentation",
920 ),
921 ("Contingency (10%)", "$200,000", "Risk buffer"),
922 ];
923 for (i, (cat, cost, note)) in items.iter().enumerate() {
924 tbl.cell(i + 1, 0).unwrap().set_text(cat);
925 tbl.cell(i + 1, 1).unwrap().set_text(cost);
926 tbl.cell(i + 1, 2).unwrap().set_text(note);
927 if i % 2 == 0 {
928 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
929 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
930 tbl.cell(i + 1, 2).unwrap().shading("F4F1EB");
931 }
932 }
933 }
934
935 // ── Closing ──
936 doc.add_paragraph("");
937 doc.add_paragraph("Next Steps").style("Heading1");
938 doc.add_numbered_list_item(
939 "Schedule technical deep-dive with GFC engineering team (Week 1)",
940 0,
941 );
942 doc.add_numbered_list_item("Finalize scope and sign SOW (Week 2-3)", 0);
943 doc.add_numbered_list_item("Kick off Phase 1 with joint planning session (Week 4)", 0);
944
945 doc.add_paragraph("");
946 {
947 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
948 p.add_run("Walter White")
949 .bold(true)
950 .size(14.0)
951 .color("1B2A4A");
952 }
953 {
954 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
955 p.add_run("CEO, Tensorbee | walter@tensorbee.com")
956 .size(10.0)
957 .color("888888");
958 }
959
960 doc
961}Sourcepub fn resolve_paragraph_properties(&self, style_id: Option<&str>) -> CT_PPr
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 → …).
Sourcepub fn resolve_run_properties(
&self,
para_style_id: Option<&str>,
run_style_id: Option<&str>,
) -> CT_RPr
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.
Sourcepub fn section_properties(&self) -> Option<&CT_SectPr>
pub fn section_properties(&self) -> Option<&CT_SectPr>
Get the section properties (page size, margins).
Sourcepub fn section_properties_mut(&mut self) -> &mut CT_SectPr
pub fn section_properties_mut(&mut self) -> &mut CT_SectPr
Get a mutable reference to section properties, creating defaults if needed.
Sourcepub fn set_page_size(&mut self, width: Length, height: Length)
pub fn set_page_size(&mut self, width: Length, height: Length)
Set page size.
Examples found in repository?
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
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}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}97fn generate_feature_showcase(_samples_dir: &Path) -> Document {
98 let mut doc = Document::new();
99
100 // ── Page Setup & Metadata ──
101 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
102 doc.set_margins(
103 Length::inches(1.0),
104 Length::inches(1.0),
105 Length::inches(1.0),
106 Length::inches(1.0),
107 );
108 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
109 doc.set_gutter(Length::twips(0));
110 doc.set_title("rdocx Feature Showcase");
111 doc.set_author("rdocx Sample Generator");
112 doc.set_subject("Comprehensive feature demonstration");
113 doc.set_keywords("rdocx, docx, rust, sample, showcase");
114
115 // Headers & Footers with different first page
116 doc.set_different_first_page(true);
117 doc.set_first_page_header("rdocx");
118 doc.set_first_page_footer("Feature Showcase — Cover Page");
119 doc.set_header("rdocx Feature Showcase");
120 doc.set_footer("Generated by rdocx");
121
122 // ── COVER PAGE ──
123 let bg = create_sample_png(612, 792, [20, 45, 90]);
124 doc.add_background_image(&bg, "cover_bg.png");
125
126 for _ in 0..3 {
127 doc.add_paragraph("");
128 }
129 {
130 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
131 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
132 }
133 {
134 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
135 p.add_run("Complete Feature Showcase")
136 .size(28.0)
137 .color("FFFFFF")
138 .italic(true);
139 }
140 doc.add_paragraph("");
141 {
142 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
143 p.add_run("Every feature of the rdocx Rust library")
144 .size(13.0)
145 .color("B0C4DE");
146 }
147 {
148 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
149 p.add_run("demonstrated in a single document.")
150 .size(13.0)
151 .color("B0C4DE");
152 }
153
154 // ── TABLE OF CONTENTS ──
155 doc.add_paragraph("").page_break_before(true);
156 doc.insert_toc(doc.content_count(), 3);
157
158 // ── SECTION 1: TEXT FORMATTING ──
159 doc.add_paragraph("").page_break_before(true);
160 doc.add_paragraph("1. Text Formatting").style("Heading1");
161
162 // Paragraph Alignment
163 doc.add_paragraph("1.1 Paragraph Alignment")
164 .style("Heading2");
165 doc.add_paragraph("Left-aligned paragraph (default).")
166 .alignment(Alignment::Left);
167 doc.add_paragraph("Center-aligned paragraph.")
168 .alignment(Alignment::Center);
169 doc.add_paragraph("Right-aligned paragraph.")
170 .alignment(Alignment::Right);
171 doc.add_paragraph(
172 "Justified paragraph — this text is long enough to demonstrate how justified alignment \
173 distributes extra space across word gaps so lines fill the full width of the text area.",
174 )
175 .alignment(Alignment::Justify);
176
177 doc.add_paragraph("");
178
179 // Run Formatting
180 doc.add_paragraph("1.2 Run Formatting").style("Heading2");
181 {
182 let mut p = doc.add_paragraph("");
183 p.add_run("Bold").bold(true);
184 p.add_run(" | ");
185 p.add_run("Italic").italic(true);
186 p.add_run(" | ");
187 p.add_run("Bold Italic").bold(true).italic(true);
188 p.add_run(" | ");
189 p.add_run("Underline").underline(true);
190 p.add_run(" | ");
191 p.add_run("Strikethrough").strike(true);
192 p.add_run(" | ");
193 p.add_run("Double Strike").double_strike(true);
194 }
195 {
196 let mut p = doc.add_paragraph("");
197 p.add_run("Red").color("FF0000");
198 p.add_run(" | ");
199 p.add_run("Blue").color("0000FF");
200 p.add_run(" | ");
201 p.add_run("Green").color("00AA00");
202 p.add_run(" | ");
203 p.add_run("Highlighted").highlight("FFFF00");
204 }
205 {
206 let mut p = doc.add_paragraph("");
207 p.add_run("8pt").size(8.0);
208 p.add_run(" | ");
209 p.add_run("11pt").size(11.0);
210 p.add_run(" | ");
211 p.add_run("16pt").size(16.0);
212 p.add_run(" | ");
213 p.add_run("24pt").size(24.0);
214 }
215 {
216 let mut p = doc.add_paragraph("");
217 p.add_run("Arial").font("Arial");
218 p.add_run(" | ");
219 p.add_run("Times New Roman").font("Times New Roman");
220 p.add_run(" | ");
221 p.add_run("Courier New").font("Courier New");
222 }
223 {
224 let mut p = doc.add_paragraph("");
225 p.add_run("H");
226 p.add_run("2").subscript();
227 p.add_run("O (subscript) | E=mc");
228 p.add_run("2").superscript();
229 p.add_run(" (superscript)");
230 }
231 {
232 let mut p = doc.add_paragraph("");
233 p.add_run("ALL CAPS").all_caps(true);
234 p.add_run(" | ");
235 p.add_run("Small Caps").small_caps(true);
236 p.add_run(" | ");
237 p.add_run("Expanded").character_spacing(Length::twips(40));
238 p.add_run(" | ");
239 p.add_run("Hidden text (not visible)").hidden(true);
240 }
241
242 doc.add_paragraph("");
243
244 // Underline Styles
245 doc.add_paragraph("1.3 Underline Styles").style("Heading2");
246 {
247 let mut p = doc.add_paragraph("");
248 p.add_run("Single ")
249 .underline_style(rdocx::UnderlineStyle::Single);
250 p.add_run("Double ")
251 .underline_style(rdocx::UnderlineStyle::Double);
252 p.add_run("Thick ")
253 .underline_style(rdocx::UnderlineStyle::Thick);
254 p.add_run("Dotted ")
255 .underline_style(rdocx::UnderlineStyle::Dotted);
256 p.add_run("Dash ")
257 .underline_style(rdocx::UnderlineStyle::Dash);
258 p.add_run("Wave ")
259 .underline_style(rdocx::UnderlineStyle::Wave);
260 }
261
262 // ── SECTION 2: PARAGRAPH FORMATTING ──
263 doc.add_paragraph("").page_break_before(true);
264 doc.add_paragraph("2. Paragraph Formatting")
265 .style("Heading1");
266
267 doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
268 doc.add_paragraph("Paragraph with light green shading")
269 .shading("E2EFDA");
270 doc.add_paragraph("Paragraph with bottom border")
271 .border_bottom(BorderStyle::Single, 6, "2E75B6");
272 doc.add_paragraph("Paragraph with all borders (red)")
273 .border_all(BorderStyle::Single, 4, "FF0000");
274
275 doc.add_paragraph("2.2 Indentation").style("Heading2");
276 doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
277 .indent_left(Length::inches(1.0))
278 .hanging_indent(Length::inches(0.5));
279 doc.add_paragraph("0.5-inch first-line indent on this paragraph")
280 .first_line_indent(Length::inches(0.5));
281 doc.add_paragraph("Both left and right indent (0.75 inches each)")
282 .indent_left(Length::inches(0.75))
283 .indent_right(Length::inches(0.75));
284
285 doc.add_paragraph("2.3 Spacing & Line Height")
286 .style("Heading2");
287 doc.add_paragraph("Extra space before (24pt) and after (12pt)")
288 .space_before(Length::pt(24.0))
289 .space_after(Length::pt(12.0));
290 doc.add_paragraph(
291 "Double line spacing paragraph. This text should have extra vertical space between \
292 lines to demonstrate line_spacing_multiple(2.0).",
293 )
294 .line_spacing_multiple(2.0);
295 doc.add_paragraph("Exact 20pt line spacing (fixed height).")
296 .line_spacing(20.0);
297
298 doc.add_paragraph("2.4 Pagination Controls")
299 .style("Heading2");
300 doc.add_paragraph("keep_with_next — this paragraph stays with the next")
301 .keep_with_next(true);
302 doc.add_paragraph("(This paragraph was kept with the one above.)");
303 doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
304 .keep_together(true);
305 doc.add_paragraph("widow_control — prevents widow/orphan lines")
306 .widow_control(true);
307
308 // ── SECTION 3: LISTS ──
309 doc.add_paragraph("").page_break_before(true);
310 doc.add_paragraph("3. Lists").style("Heading1");
311
312 doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
313 doc.add_bullet_list_item("First item", 0);
314 doc.add_bullet_list_item("Second item", 0);
315 doc.add_bullet_list_item("Nested level 1", 1);
316 doc.add_bullet_list_item("Nested level 2", 2);
317 doc.add_bullet_list_item("Back to level 1", 1);
318 doc.add_bullet_list_item("Third item", 0);
319
320 doc.add_paragraph("");
321
322 doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
323 doc.add_numbered_list_item("First numbered item", 0);
324 doc.add_numbered_list_item("Second numbered item", 0);
325 doc.add_numbered_list_item("Sub-item A", 1);
326 doc.add_numbered_list_item("Sub-item B", 1);
327 doc.add_numbered_list_item("Third numbered item", 0);
328
329 // ── SECTION 4: TAB STOPS ──
330 doc.add_paragraph("");
331 doc.add_paragraph("4. Tab Stops").style("Heading1");
332
333 doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
334 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
335 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
336 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
337 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
338 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
339
340 doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
341 doc.add_paragraph("Item\t\tPrice")
342 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
343 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
344 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
345 doc.add_paragraph("Widget\t\t$19.99")
346 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
347 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
348 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
349 doc.add_paragraph("Gadget\t\t$249.50")
350 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
351 .add_tab_stop_with_leader(
352 TabAlignment::Right,
353 Length::inches(4.0),
354 TabLeader::Underscore,
355 )
356 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
357
358 // ── SECTION 5: TABLES ──
359 doc.add_paragraph("").page_break_before(true);
360 doc.add_paragraph("5. Tables").style("Heading1");
361
362 doc.add_paragraph("5.1 Basic Table").style("Heading2");
363 {
364 let mut tbl = doc
365 .add_table(4, 3)
366 .borders(BorderStyle::Single, 4, "000000");
367 for col in 0..3 {
368 tbl.cell(0, col).unwrap().shading("2E75B6");
369 }
370 tbl.cell(0, 0).unwrap().set_text("Name");
371 tbl.cell(0, 1).unwrap().set_text("Role");
372 tbl.cell(0, 2).unwrap().set_text("Location");
373 tbl.cell(1, 0).unwrap().set_text("Walter White");
374 tbl.cell(1, 1).unwrap().set_text("CEO");
375 tbl.cell(1, 2).unwrap().set_text("Albuquerque");
376 tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
377 tbl.cell(2, 1).unwrap().set_text("CTO");
378 tbl.cell(2, 2).unwrap().set_text("Remote");
379 tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
380 tbl.cell(3, 1).unwrap().set_text("Security");
381 tbl.cell(3, 2).unwrap().set_text("Washington");
382 }
383
384 doc.add_paragraph("");
385
386 doc.add_paragraph("5.2 Column Span & Cell Shading")
387 .style("Heading2");
388 {
389 let mut tbl = doc
390 .add_table(3, 4)
391 .borders(BorderStyle::Single, 4, "000000")
392 .width_pct(100.0);
393 tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
394 tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
395 tbl.cell(1, 0).unwrap().set_text("Region");
396 tbl.cell(1, 0).unwrap().shading("D6E4F0");
397 tbl.cell(1, 1).unwrap().set_text("Q1");
398 tbl.cell(1, 1).unwrap().shading("D6E4F0");
399 tbl.cell(1, 2).unwrap().set_text("Q2");
400 tbl.cell(1, 2).unwrap().shading("D6E4F0");
401 tbl.cell(1, 3).unwrap().set_text("Total");
402 tbl.cell(1, 3).unwrap().shading("D6E4F0");
403 tbl.cell(2, 0).unwrap().set_text("Americas");
404 tbl.cell(2, 1).unwrap().set_text("$2.4M");
405 tbl.cell(2, 2).unwrap().set_text("$2.7M");
406 tbl.cell(2, 3).unwrap().set_text("$5.1M");
407 }
408
409 doc.add_paragraph("");
410
411 doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
412 {
413 let mut tbl = doc
414 .add_table(4, 3)
415 .borders(BorderStyle::Single, 4, "000000");
416 tbl.cell(0, 0).unwrap().set_text("Category");
417 tbl.cell(0, 0).unwrap().shading("E2EFDA");
418 tbl.cell(0, 1).unwrap().set_text("Item");
419 tbl.cell(0, 1).unwrap().shading("E2EFDA");
420 tbl.cell(0, 2).unwrap().set_text("Price");
421 tbl.cell(0, 2).unwrap().shading("E2EFDA");
422 tbl.cell(1, 0).unwrap().set_text("Hardware");
423 tbl.cell(1, 0).unwrap().v_merge_restart();
424 tbl.cell(1, 1).unwrap().set_text("Laptop");
425 tbl.cell(1, 2).unwrap().set_text("$1,200");
426 tbl.cell(2, 0).unwrap().v_merge_continue();
427 tbl.cell(2, 1).unwrap().set_text("Monitor");
428 tbl.cell(2, 2).unwrap().set_text("$450");
429 tbl.cell(3, 0).unwrap().set_text("Software");
430 tbl.cell(3, 1).unwrap().set_text("IDE License");
431 tbl.cell(3, 2).unwrap().set_text("$200/yr");
432 }
433
434 doc.add_paragraph("");
435
436 doc.add_paragraph("5.4 Nested Table").style("Heading2");
437 {
438 let mut tbl = doc
439 .add_table(2, 2)
440 .borders(BorderStyle::Single, 6, "2E75B6");
441 tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
442 tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
443 tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
444 {
445 let mut cell = tbl.cell(1, 1).unwrap();
446 cell.set_text("Nested table below:");
447 let mut nested = cell
448 .add_table(2, 2)
449 .borders(BorderStyle::Single, 2, "FF6600");
450 nested.cell(0, 0).unwrap().set_text("A");
451 nested.cell(0, 1).unwrap().set_text("B");
452 nested.cell(1, 0).unwrap().set_text("C");
453 nested.cell(1, 1).unwrap().set_text("D");
454 }
455 }
456
457 doc.add_paragraph("");
458
459 doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
460 .style("Heading2");
461 {
462 let mut tbl = doc
463 .add_table(2, 3)
464 .borders(BorderStyle::Single, 4, "666666");
465 tbl.row(0).unwrap().height(Length::pt(50.0)).header();
466 tbl.cell(0, 0).unwrap().set_text("Top");
467 tbl.cell(0, 0)
468 .unwrap()
469 .vertical_alignment(VerticalAlignment::Top)
470 .shading("FFF2CC");
471 tbl.cell(0, 1).unwrap().set_text("Center");
472 tbl.cell(0, 1)
473 .unwrap()
474 .vertical_alignment(VerticalAlignment::Center)
475 .shading("D9E2F3");
476 tbl.cell(0, 2).unwrap().set_text("Bottom");
477 tbl.cell(0, 2)
478 .unwrap()
479 .vertical_alignment(VerticalAlignment::Bottom)
480 .shading("E2EFDA");
481 tbl.row(1).unwrap().cant_split();
482 tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
483 tbl.cell(1, 0).unwrap().no_wrap();
484 tbl.cell(1, 1).unwrap().set_text("Fixed width");
485 tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
486 tbl.cell(1, 2).unwrap().set_text("Normal");
487 }
488
489 // ── SECTION 6: IMAGES ──
490 doc.add_paragraph("").page_break_before(true);
491 doc.add_paragraph("6. Images").style("Heading1");
492
493 doc.add_paragraph("6.1 Inline Image").style("Heading2");
494 doc.add_paragraph("A blue gradient image below:");
495 let img = create_sample_png(200, 50, [0, 80, 200]);
496 doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
497
498 doc.add_paragraph("");
499 doc.add_paragraph("6.2 Header Image").style("Heading2");
500 let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
501 doc.set_header_image(
502 &hdr_img,
503 "header_logo.png",
504 Length::inches(2.0),
505 Length::inches(0.2),
506 );
507 doc.add_paragraph("The header now contains an inline image (check the top of this page).");
508
509 doc.add_paragraph("");
510 doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
511
512 // ── SECTION 7: CONTENT MANIPULATION ──
513 doc.add_paragraph("").page_break_before(true);
514 doc.add_paragraph("7. Content Manipulation")
515 .style("Heading1");
516
517 doc.add_paragraph("7.1 Placeholder Replacement")
518 .style("Heading2");
519 doc.add_paragraph("Customer: {{customer}}");
520 doc.add_paragraph("Date: {{date}}");
521 doc.add_paragraph("Reference: {{ref_number}}");
522 {
523 let mut tbl = doc
524 .add_table(2, 2)
525 .borders(BorderStyle::Single, 4, "000000");
526 tbl.cell(0, 0).unwrap().set_text("Project");
527 tbl.cell(0, 0).unwrap().shading("D6E4F0");
528 tbl.cell(0, 1).unwrap().set_text("{{project}}");
529 tbl.cell(1, 0).unwrap().set_text("Status");
530 tbl.cell(1, 0).unwrap().shading("D6E4F0");
531 tbl.cell(1, 1).unwrap().set_text("{{status}}");
532 }
533
534 let mut replacements = HashMap::new();
535 replacements.insert("{{customer}}", "Tensorbee Inc.");
536 replacements.insert("{{date}}", "February 22, 2026");
537 replacements.insert("{{ref_number}}", "TB-2026-001");
538 replacements.insert("{{project}}", "Infrastructure Upgrade");
539 replacements.insert("{{status}}", "In Progress");
540 let n = doc.replace_all(&replacements);
541 doc.add_paragraph(&format!("({n} placeholders replaced above)"));
542
543 doc.add_paragraph("");
544
545 doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
546 doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
547 let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
548 doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
549
550 doc.add_paragraph("");
551
552 doc.add_paragraph("7.3 Content Insertion").style("Heading2");
553 doc.add_paragraph("Section A: First content.");
554 doc.add_paragraph("Section C: Third content.");
555 if let Some(idx) = doc.find_content_index("Section C") {
556 doc.insert_paragraph(
557 idx,
558 "Section B: Inserted between A and C via find_content_index().",
559 );
560 }
561
562 // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
563 doc.add_paragraph("").section_break(SectionBreak::NextPage);
564 doc.add_paragraph("8. Section Breaks & Orientation")
565 .style("Heading1");
566 doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
567
568 {
569 let mut tbl = doc
570 .add_table(3, 7)
571 .borders(BorderStyle::Single, 4, "2E75B6");
572 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
573 for (c, h) in headers.iter().enumerate() {
574 tbl.cell(0, c).unwrap().set_text(h);
575 tbl.cell(0, c).unwrap().shading("2E75B6");
576 }
577 let data = [
578 [
579 "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
580 ],
581 [
582 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
583 ],
584 ];
585 for (r, row) in data.iter().enumerate() {
586 for (c, v) in row.iter().enumerate() {
587 tbl.cell(r + 1, c).unwrap().set_text(v);
588 }
589 }
590 }
591
592 doc.add_paragraph("")
593 .section_break(SectionBreak::NextPage)
594 .section_landscape();
595
596 // ── SECTION 9: CUSTOM STYLES ──
597 doc.add_paragraph("9. Custom Styles").style("Heading1");
598 doc.add_style(
599 StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
600 .based_on("Normal")
601 .paragraph_properties(rdocx_oxml::properties::CT_PPr {
602 shading: Some(rdocx_oxml::properties::CT_Shd {
603 val: "clear".to_string(),
604 color: None,
605 fill: Some("FFF2CC".to_string()),
606 }),
607 ..Default::default()
608 })
609 .run_properties(rdocx_oxml::properties::CT_RPr {
610 bold: Some(true),
611 color: Some("C45911".to_string()),
612 ..Default::default()
613 }),
614 );
615 doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
616 .style("CustomHighlight");
617
618 doc.add_paragraph("");
619
620 // ── SECTION 10: DOCUMENT INTELLIGENCE ──
621 doc.add_paragraph("10. Document Intelligence API")
622 .style("Heading1");
623
624 let wc = doc.word_count();
625 let hc = doc.headings().len();
626 let ic = doc.images().len();
627 let lc = doc.links().len();
628 doc.add_paragraph(&format!("Word count: {wc}"));
629 doc.add_paragraph(&format!("Heading count: {hc}"));
630 doc.add_paragraph(&format!("Image count: {ic}"));
631 doc.add_paragraph(&format!("Link count: {lc}"));
632
633 let outline = doc.document_outline();
634 doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
635
636 let issues = doc.audit_accessibility();
637 doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
638 for issue in &issues {
639 doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
640 }
641
642 // ── SECTION 11: DOCUMENT MERGING ──
643 doc.add_paragraph("");
644 doc.add_paragraph("11. Document Merging").style("Heading1");
645 {
646 let mut other = Document::new();
647 other.add_paragraph("This paragraph was merged from another document using append().");
648 doc.append(&other);
649 }
650
651 doc.add_paragraph("");
652
653 // ── SUMMARY ──
654 doc.add_paragraph("Summary of Demonstrated Features")
655 .style("Heading1");
656 let features = [
657 "Page setup: size, margins, header/footer distance, gutter",
658 "Document metadata: title, author, subject, keywords",
659 "Headers and footers: text, images, different first page",
660 "Background images (full-page behind text)",
661 "Table of Contents generation with bookmarks",
662 "Text formatting: bold, italic, underline styles, strike, color, size, font",
663 "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
664 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
665 "Pagination controls: keep-with-next, keep-together, widow control",
666 "Bullet and numbered lists with nesting",
667 "Tab stops with dot/underscore/hyphen leaders",
668 "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
669 "Row properties: header rows, exact height, cant-split",
670 "Cell properties: width, no-wrap, vertical alignment",
671 "Inline images and header images",
672 "Placeholder replacement (single and batch)",
673 "Regex-based find and replace",
674 "Content insertion at specific positions",
675 "Section breaks with mixed portrait/landscape",
676 "Custom paragraph and character styles",
677 "Document intelligence: word count, headings, outline, images, links",
678 "Accessibility audit",
679 "Document merging (append)",
680 "Export: DOCX, PDF, HTML, Markdown",
681 ];
682 for f in &features {
683 doc.add_bullet_list_item(f, 0);
684 }
685 doc.add_paragraph("");
686 doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
687 .alignment(Alignment::Center)
688 .shading("E2EFDA")
689 .border_all(BorderStyle::Single, 2, "00AA00");
690
691 doc
692}
693
694// =============================================================================
695// 2. PROPOSAL DOCUMENT — Deep navy + gold scheme
696// =============================================================================
697fn generate_proposal(_samples_dir: &Path) -> Document {
698 let mut doc = Document::new();
699
700 // Colors: Navy #1B2A4A, Gold #C5922E, Light #F4F1EB
701 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
702 doc.set_margins(
703 Length::inches(1.0),
704 Length::inches(1.0),
705 Length::inches(1.0),
706 Length::inches(1.0),
707 );
708 doc.set_title("AI Platform Modernization Proposal");
709 doc.set_author("Walter White");
710 doc.set_subject("Technology Proposal");
711 doc.set_keywords("proposal, AI, modernization, Tensorbee");
712
713 // Banner header
714 let logo_img = create_logo_png(220, 48);
715 let banner = build_header_banner_xml(
716 "rId1",
717 &BannerOpts {
718 bg_color: "1B2A4A",
719 banner_width: 7772400,
720 banner_height: 914400,
721 logo_width: 2011680,
722 logo_height: 438912,
723 logo_x_offset: 295125,
724 logo_y_offset: 237744,
725 },
726 );
727 doc.set_raw_header_with_images(
728 banner,
729 &[("rId1", &logo_img, "logo.png")],
730 rdocx_oxml::header_footer::HdrFtrType::Default,
731 );
732 doc.set_different_first_page(true);
733 let cover_banner = build_header_banner_xml(
734 "rId1",
735 &BannerOpts {
736 bg_color: "C5922E",
737 banner_width: 7772400,
738 banner_height: 914400,
739 logo_width: 2011680,
740 logo_height: 438912,
741 logo_x_offset: 295125,
742 logo_y_offset: 237744,
743 },
744 );
745 doc.set_raw_header_with_images(
746 cover_banner,
747 &[("rId1", &logo_img, "logo.png")],
748 rdocx_oxml::header_footer::HdrFtrType::First,
749 );
750 doc.set_margins(
751 Length::twips(2292),
752 Length::twips(1440),
753 Length::twips(1440),
754 Length::twips(1440),
755 );
756 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
757 doc.set_footer("Tensorbee — Confidential");
758
759 // Custom styles
760 doc.add_style(
761 StyleBuilder::paragraph("ProposalTitle", "Proposal Title")
762 .based_on("Normal")
763 .run_properties(rdocx_oxml::properties::CT_RPr {
764 bold: Some(true),
765 font_ascii: Some("Georgia".to_string()),
766 font_hansi: Some("Georgia".to_string()),
767 sz: Some(rdocx_oxml::HalfPoint(56)), // half-points → 28pt
768 color: Some("1B2A4A".to_string()),
769 ..Default::default()
770 }),
771 );
772
773 // ── Cover Page ──
774 for _ in 0..4 {
775 doc.add_paragraph("");
776 }
777 doc.add_paragraph("AI Platform Modernization")
778 .style("ProposalTitle")
779 .alignment(Alignment::Center);
780 doc.add_paragraph("")
781 .alignment(Alignment::Center)
782 .border_bottom(BorderStyle::Single, 8, "C5922E");
783 {
784 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
785 p.add_run("Prepared for Global Financial Corp.")
786 .size(14.0)
787 .color("666666")
788 .italic(true);
789 }
790 {
791 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
792 p.add_run("Prepared by: Walter White, CEO — Tensorbee")
793 .size(12.0)
794 .color("888888");
795 }
796 {
797 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
798 p.add_run("February 22, 2026").size(12.0).color("888888");
799 }
800
801 // ── TOC ──
802 doc.add_paragraph("").page_break_before(true);
803 doc.add_paragraph("Table of Contents").style("Heading1");
804 // We'll add a manual-style TOC since the insert_toc goes at a specific position
805 doc.insert_toc(doc.content_count(), 2);
806
807 // ── Executive Summary ──
808 doc.add_paragraph("").page_break_before(true);
809 doc.add_paragraph("Executive Summary").style("Heading1");
810 doc.add_paragraph(
811 "Tensorbee proposes a comprehensive modernization of Global Financial Corp's AI platform, \
812 transitioning from legacy batch-processing systems to a real-time inference architecture. \
813 This transformation will reduce model deployment time from weeks to hours, improve \
814 prediction accuracy by an estimated 23%, and deliver $4.2M in annual operational savings.",
815 )
816 .first_line_indent(Length::inches(0.3));
817 doc.add_paragraph(
818 "The project spans three phases over 18 months, with a total investment of $2.8M. \
819 Tensorbee brings deep expertise in ML infrastructure, having successfully completed \
820 similar transformations for three Fortune 500 financial institutions.",
821 )
822 .first_line_indent(Length::inches(0.3));
823
824 // Key metrics table
825 doc.add_paragraph("");
826 {
827 let mut tbl = doc
828 .add_table(5, 2)
829 .borders(BorderStyle::Single, 4, "1B2A4A")
830 .width_pct(80.0);
831 tbl.cell(0, 0).unwrap().set_text("Key Metric");
832 tbl.cell(0, 0).unwrap().shading("1B2A4A");
833 tbl.cell(0, 1).unwrap().set_text("Value");
834 tbl.cell(0, 1).unwrap().shading("1B2A4A");
835 let rows = [
836 ("Total Investment", "$2.8M"),
837 ("Annual Savings", "$4.2M"),
838 ("ROI (Year 1)", "150%"),
839 ("Timeline", "18 months"),
840 ];
841 for (i, (k, v)) in rows.iter().enumerate() {
842 tbl.cell(i + 1, 0).unwrap().set_text(k);
843 if i % 2 == 0 {
844 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
845 }
846 tbl.cell(i + 1, 1).unwrap().set_text(v);
847 if i % 2 == 0 {
848 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
849 }
850 }
851 }
852
853 // ── Problem Statement ──
854 doc.add_paragraph("").page_break_before(true);
855 doc.add_paragraph("Problem Statement").style("Heading1");
856 doc.add_paragraph(
857 "Global Financial Corp's current AI infrastructure faces three critical challenges:",
858 );
859 doc.add_numbered_list_item("Deployment Latency — New models take 3-4 weeks to deploy to production, compared to the industry benchmark of 2-3 days.", 0);
860 doc.add_numbered_list_item("Scalability Constraints — The batch processing architecture cannot handle real-time inference demands during peak trading hours.", 0);
861 doc.add_numbered_list_item("Technical Debt — Legacy Python 2.7 codebases and manual deployment scripts create reliability risks and slow iteration speed.", 0);
862
863 // ── Proposed Solution ──
864 doc.add_paragraph("").page_break_before(true);
865 doc.add_paragraph("Proposed Solution").style("Heading1");
866
867 doc.add_paragraph("Phase 1: Foundation (Months 1-6)")
868 .style("Heading2");
869 doc.add_bullet_list_item("Deploy Kubernetes-based ML serving infrastructure", 0);
870 doc.add_bullet_list_item("Implement CI/CD pipeline for model deployment", 0);
871 doc.add_bullet_list_item("Migrate top 5 production models to new platform", 0);
872
873 doc.add_paragraph("Phase 2: Expansion (Months 7-12)")
874 .style("Heading2");
875 doc.add_bullet_list_item(
876 "Real-time feature engineering pipeline (Apache Kafka + Flink)",
877 0,
878 );
879 doc.add_bullet_list_item("A/B testing framework for model variants", 0);
880 doc.add_bullet_list_item("Automated model monitoring and drift detection", 0);
881
882 doc.add_paragraph("Phase 3: Optimization (Months 13-18)")
883 .style("Heading2");
884 doc.add_bullet_list_item("GPU inference optimization (TensorRT/ONNX Runtime)", 0);
885 doc.add_bullet_list_item("Multi-region deployment for latency reduction", 0);
886 doc.add_bullet_list_item(
887 "Self-service model deployment portal for data science teams",
888 0,
889 );
890
891 // ── Budget ──
892 doc.add_paragraph("Budget Breakdown").style("Heading1");
893 {
894 let mut tbl = doc
895 .add_table(6, 3)
896 .borders(BorderStyle::Single, 4, "1B2A4A")
897 .width_pct(100.0);
898 tbl.cell(0, 0).unwrap().set_text("Category");
899 tbl.cell(0, 0).unwrap().shading("1B2A4A");
900 tbl.cell(0, 1).unwrap().set_text("Cost");
901 tbl.cell(0, 1).unwrap().shading("1B2A4A");
902 tbl.cell(0, 2).unwrap().set_text("Notes");
903 tbl.cell(0, 2).unwrap().shading("1B2A4A");
904 let items = [
905 (
906 "Infrastructure",
907 "$450,000",
908 "Cloud compute + GPU instances",
909 ),
910 ("Engineering Services", "$1,600,000", "12 FTE x 18 months"),
911 (
912 "Software Licenses",
913 "$350,000",
914 "Kafka, monitoring, CI/CD tools",
915 ),
916 (
917 "Training & Enablement",
918 "$200,000",
919 "Team training, documentation",
920 ),
921 ("Contingency (10%)", "$200,000", "Risk buffer"),
922 ];
923 for (i, (cat, cost, note)) in items.iter().enumerate() {
924 tbl.cell(i + 1, 0).unwrap().set_text(cat);
925 tbl.cell(i + 1, 1).unwrap().set_text(cost);
926 tbl.cell(i + 1, 2).unwrap().set_text(note);
927 if i % 2 == 0 {
928 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
929 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
930 tbl.cell(i + 1, 2).unwrap().shading("F4F1EB");
931 }
932 }
933 }
934
935 // ── Closing ──
936 doc.add_paragraph("");
937 doc.add_paragraph("Next Steps").style("Heading1");
938 doc.add_numbered_list_item(
939 "Schedule technical deep-dive with GFC engineering team (Week 1)",
940 0,
941 );
942 doc.add_numbered_list_item("Finalize scope and sign SOW (Week 2-3)", 0);
943 doc.add_numbered_list_item("Kick off Phase 1 with joint planning session (Week 4)", 0);
944
945 doc.add_paragraph("");
946 {
947 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
948 p.add_run("Walter White")
949 .bold(true)
950 .size(14.0)
951 .color("1B2A4A");
952 }
953 {
954 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
955 p.add_run("CEO, Tensorbee | walter@tensorbee.com")
956 .size(10.0)
957 .color("888888");
958 }
959
960 doc
961}
962
963// =============================================================================
964// 3. QUOTE / BILL OF MATERIALS — Teal + orange scheme
965// =============================================================================
966fn generate_quote(_samples_dir: &Path) -> Document {
967 let mut doc = Document::new();
968
969 // Colors: Teal #008B8B, Dark #1A3C3C, Orange #E07020, Light #F0F8F8
970 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
971 doc.set_margins(
972 Length::inches(0.75),
973 Length::inches(0.75),
974 Length::inches(0.75),
975 Length::inches(0.75),
976 );
977 doc.set_title("Quotation QT-2026-0147");
978 doc.set_author("Walter White");
979 doc.set_keywords("quote, BOM, Tensorbee");
980
981 doc.set_header("Tensorbee — Quotation");
982 doc.set_footer("QT-2026-0147 | Page");
983
984 // ── Header Block ──
985 {
986 let mut p = doc.add_paragraph("").alignment(Alignment::Left);
987 p.add_run("TENSORBEE")
988 .bold(true)
989 .size(28.0)
990 .color("008B8B")
991 .font("Helvetica");
992 }
993 doc.add_paragraph("123 Innovation Drive, Suite 400")
994 .alignment(Alignment::Left);
995 doc.add_paragraph("San Francisco, CA 94105 | +1 (415) 555-0199")
996 .alignment(Alignment::Left);
997 doc.add_paragraph("")
998 .border_bottom(BorderStyle::Single, 8, "008B8B");
999
1000 // Quote meta
1001 doc.add_paragraph("");
1002 {
1003 let mut tbl = doc.add_table(4, 4).width_pct(100.0);
1004 tbl.cell(0, 0).unwrap().set_text("QUOTATION");
1005 tbl.cell(0, 0).unwrap().shading("008B8B").grid_span(2);
1006 tbl.cell(0, 2).unwrap().set_text("DATE");
1007 tbl.cell(0, 2).unwrap().shading("008B8B");
1008 tbl.cell(0, 3).unwrap().set_text("VALID UNTIL");
1009 tbl.cell(0, 3).unwrap().shading("008B8B");
1010 tbl.cell(1, 0).unwrap().set_text("Quote #:");
1011 tbl.cell(1, 1).unwrap().set_text("QT-2026-0147");
1012 tbl.cell(1, 2).unwrap().set_text("Feb 22, 2026");
1013 tbl.cell(1, 3).unwrap().set_text("Mar 22, 2026");
1014 tbl.cell(2, 0).unwrap().set_text("Prepared by:");
1015 tbl.cell(2, 1).unwrap().set_text("Walter White");
1016 tbl.cell(2, 2).unwrap().set_text("Payment:");
1017 tbl.cell(2, 3).unwrap().set_text("Net 30");
1018 tbl.cell(3, 0).unwrap().set_text("Customer:");
1019 tbl.cell(3, 1).unwrap().set_text("Meridian Dynamics LLC");
1020 tbl.cell(3, 2).unwrap().set_text("Currency:");
1021 tbl.cell(3, 3).unwrap().set_text("USD");
1022 }
1023
1024 doc.add_paragraph("");
1025
1026 // ── Bill of Materials ──
1027 {
1028 let mut p = doc.add_paragraph("");
1029 p.add_run("Bill of Materials")
1030 .bold(true)
1031 .size(16.0)
1032 .color("1A3C3C");
1033 }
1034 doc.add_paragraph("");
1035
1036 {
1037 let mut tbl = doc
1038 .add_table(10, 6)
1039 .borders(BorderStyle::Single, 2, "008B8B")
1040 .width_pct(100.0)
1041 .layout_fixed();
1042 // Headers
1043 let hdrs = [
1044 "#",
1045 "Part Number",
1046 "Description",
1047 "Qty",
1048 "Unit Price",
1049 "Total",
1050 ];
1051 for (c, h) in hdrs.iter().enumerate() {
1052 tbl.cell(0, c).unwrap().set_text(h);
1053 tbl.cell(0, c).unwrap().shading("008B8B");
1054 }
1055
1056 let items: Vec<(&str, &str, &str, &str, &str)> = vec![
1057 (
1058 "TB-GPU-A100",
1059 "NVIDIA A100 80GB GPU",
1060 "4",
1061 "$12,500.00",
1062 "$50,000.00",
1063 ),
1064 (
1065 "TB-SRV-R750",
1066 "Dell R750xa Server Chassis",
1067 "2",
1068 "$8,200.00",
1069 "$16,400.00",
1070 ),
1071 (
1072 "TB-RAM-256G",
1073 "256GB DDR5 ECC Memory Module",
1074 "8",
1075 "$890.00",
1076 "$7,120.00",
1077 ),
1078 (
1079 "TB-SSD-3840",
1080 "3.84TB NVMe U.2 SSD",
1081 "8",
1082 "$1,150.00",
1083 "$9,200.00",
1084 ),
1085 (
1086 "TB-NET-CX7",
1087 "ConnectX-7 200GbE NIC",
1088 "4",
1089 "$1,800.00",
1090 "$7,200.00",
1091 ),
1092 (
1093 "TB-SW-48P",
1094 "48-Port 100GbE Switch",
1095 "1",
1096 "$22,000.00",
1097 "$22,000.00",
1098 ),
1099 (
1100 "TB-CAB-RACK",
1101 "42U Server Rack + PDU",
1102 "1",
1103 "$4,500.00",
1104 "$4,500.00",
1105 ),
1106 (
1107 "TB-SVC-INST",
1108 "Installation & Configuration",
1109 "1",
1110 "$8,500.00",
1111 "$8,500.00",
1112 ),
1113 (
1114 "TB-SVC-SUPP",
1115 "3-Year Premium Support",
1116 "1",
1117 "$15,000.00",
1118 "$15,000.00",
1119 ),
1120 ];
1121
1122 for (i, (pn, desc, qty, unit, total)) in items.iter().enumerate() {
1123 let row = i + 1;
1124 tbl.cell(row, 0).unwrap().set_text(&format!("{}", i + 1));
1125 tbl.cell(row, 1).unwrap().set_text(pn);
1126 tbl.cell(row, 2).unwrap().set_text(desc);
1127 tbl.cell(row, 3).unwrap().set_text(qty);
1128 tbl.cell(row, 4).unwrap().set_text(unit);
1129 tbl.cell(row, 5).unwrap().set_text(total);
1130 if i % 2 == 0 {
1131 for c in 0..6 {
1132 tbl.cell(row, c).unwrap().shading("F0F8F8");
1133 }
1134 }
1135 }
1136 }
1137
1138 doc.add_paragraph("");
1139
1140 // ── Totals ──
1141 {
1142 let mut tbl = doc
1143 .add_table(4, 2)
1144 .borders(BorderStyle::Single, 2, "008B8B")
1145 .width(Length::inches(3.5))
1146 .alignment(Alignment::Right);
1147 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1148 tbl.cell(0, 1).unwrap().set_text("$139,920.00");
1149 tbl.cell(1, 0).unwrap().set_text("Shipping & Handling");
1150 tbl.cell(1, 1).unwrap().set_text("$2,500.00");
1151 tbl.cell(2, 0).unwrap().set_text("Tax (8.625%)");
1152 tbl.cell(2, 1).unwrap().set_text("$12,068.10");
1153 tbl.cell(3, 0).unwrap().set_text("TOTAL");
1154 tbl.cell(3, 0).unwrap().shading("E07020");
1155 tbl.cell(3, 1).unwrap().set_text("$154,488.10");
1156 tbl.cell(3, 1).unwrap().shading("E07020");
1157 }
1158
1159 doc.add_paragraph("");
1160
1161 // ── Terms & Conditions ──
1162 {
1163 let mut p = doc.add_paragraph("");
1164 p.add_run("Terms & Conditions")
1165 .bold(true)
1166 .size(14.0)
1167 .color("1A3C3C");
1168 }
1169 doc.add_numbered_list_item(
1170 "This quotation is valid for 30 calendar days from the date of issue.",
1171 0,
1172 );
1173 doc.add_numbered_list_item(
1174 "All prices are in USD and exclusive of applicable taxes unless stated.",
1175 0,
1176 );
1177 doc.add_numbered_list_item("Standard lead time is 4-6 weeks from PO receipt.", 0);
1178 doc.add_numbered_list_item(
1179 "Payment terms: Net 30 from invoice date. 2% discount for payment within 10 days.",
1180 0,
1181 );
1182 doc.add_numbered_list_item(
1183 "Warranty: 3-year manufacturer warranty on all hardware components.",
1184 0,
1185 );
1186 doc.add_numbered_list_item(
1187 "Returns subject to 15% restocking fee if initiated after 14 days.",
1188 0,
1189 );
1190
1191 doc.add_paragraph("");
1192 doc.add_paragraph("")
1193 .border_bottom(BorderStyle::Single, 4, "008B8B");
1194 doc.add_paragraph("");
1195 {
1196 let mut p = doc.add_paragraph("");
1197 p.add_run("Accepted by: ").bold(true);
1198 p.add_run("___________________________ Date: ___________");
1199 }
1200 {
1201 let mut p = doc.add_paragraph("");
1202 p.add_run("Print Name: ").bold(true);
1203 p.add_run("___________________________ Title: ___________");
1204 }
1205
1206 doc
1207}
1208
1209// =============================================================================
1210// 4. INVOICE — Crimson + charcoal scheme
1211// =============================================================================
1212fn generate_invoice(_samples_dir: &Path) -> Document {
1213 let mut doc = Document::new();
1214
1215 // Colors: Crimson #B22222, Charcoal #333333, Light #FAF0F0
1216 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1217 doc.set_margins(
1218 Length::inches(0.75),
1219 Length::inches(0.75),
1220 Length::inches(0.75),
1221 Length::inches(0.75),
1222 );
1223 doc.set_title("Invoice INV-2026-0392");
1224 doc.set_author("Walter White");
1225 doc.set_keywords("invoice, Tensorbee");
1226
1227 doc.set_footer("Tensorbee — Thank you for your business!");
1228
1229 // ── Company & Invoice Header ──
1230 {
1231 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1232 // Company name left, INVOICE right
1233 {
1234 let mut cell = tbl.cell(0, 0).unwrap();
1235 let mut p = cell.add_paragraph("");
1236 p.add_run("TENSORBEE")
1237 .bold(true)
1238 .size(32.0)
1239 .color("B22222")
1240 .font("Helvetica");
1241 }
1242 {
1243 let mut cell = tbl.cell(0, 1).unwrap();
1244 let mut p = cell.add_paragraph("");
1245 p.add_run("INVOICE").bold(true).size(32.0).color("333333");
1246 }
1247 tbl.cell(1, 0)
1248 .unwrap()
1249 .set_text("123 Innovation Drive, Suite 400");
1250 tbl.cell(1, 1).unwrap().set_text("Invoice #: INV-2026-0392");
1251 tbl.cell(2, 0).unwrap().set_text("San Francisco, CA 94105");
1252 tbl.cell(2, 1).unwrap().set_text("Date: February 22, 2026");
1253 tbl.cell(3, 0).unwrap().set_text("walter@tensorbee.com");
1254 tbl.cell(3, 1).unwrap().set_text("Due Date: March 24, 2026");
1255 }
1256
1257 doc.add_paragraph("")
1258 .border_bottom(BorderStyle::Thick, 8, "B22222");
1259 doc.add_paragraph("");
1260
1261 // ── Bill To / Ship To ──
1262 {
1263 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1264 tbl.cell(0, 0).unwrap().set_text("BILL TO");
1265 tbl.cell(0, 0).unwrap().shading("B22222");
1266 tbl.cell(0, 1).unwrap().set_text("SHIP TO");
1267 tbl.cell(0, 1).unwrap().shading("B22222");
1268 tbl.cell(1, 0).unwrap().set_text("Meridian Dynamics LLC");
1269 tbl.cell(1, 1).unwrap().set_text("Meridian Dynamics LLC");
1270 tbl.cell(2, 0).unwrap().set_text("456 Enterprise Blvd");
1271 tbl.cell(2, 1).unwrap().set_text("Attn: Server Room B");
1272 tbl.cell(3, 0).unwrap().set_text("Austin, TX 78701");
1273 tbl.cell(3, 1)
1274 .unwrap()
1275 .set_text("456 Enterprise Blvd, Austin, TX 78701");
1276 }
1277
1278 doc.add_paragraph("");
1279
1280 // ── Line Items ──
1281 {
1282 let mut tbl = doc
1283 .add_table(8, 5)
1284 .borders(BorderStyle::Single, 2, "B22222")
1285 .width_pct(100.0);
1286 let hdrs = ["Description", "Qty", "Unit Price", "Tax", "Amount"];
1287 for (c, h) in hdrs.iter().enumerate() {
1288 tbl.cell(0, c).unwrap().set_text(h);
1289 tbl.cell(0, c).unwrap().shading("B22222");
1290 }
1291 let items = [
1292 (
1293 "ML Infrastructure Setup — Phase 1",
1294 "1",
1295 "$45,000.00",
1296 "$3,881.25",
1297 "$48,881.25",
1298 ),
1299 (
1300 "Data Pipeline Development (160 hrs)",
1301 "160",
1302 "$225.00",
1303 "$3,105.00",
1304 "$39,105.00",
1305 ),
1306 (
1307 "GPU Cluster Configuration",
1308 "1",
1309 "$12,000.00",
1310 "$1,035.00",
1311 "$13,035.00",
1312 ),
1313 (
1314 "API Gateway Implementation",
1315 "1",
1316 "$18,500.00",
1317 "$1,595.63",
1318 "$20,095.63",
1319 ),
1320 (
1321 "Load Testing & QA (80 hrs)",
1322 "80",
1323 "$195.00",
1324 "$1,345.50",
1325 "$16,945.50",
1326 ),
1327 (
1328 "Documentation & Training",
1329 "1",
1330 "$8,000.00",
1331 "$690.00",
1332 "$8,690.00",
1333 ),
1334 (
1335 "Project Management (3 months)",
1336 "3",
1337 "$6,500.00",
1338 "$1,679.63",
1339 "$21,179.63",
1340 ),
1341 ];
1342 for (i, (desc, qty, unit, tax, amt)) in items.iter().enumerate() {
1343 let r = i + 1;
1344 tbl.cell(r, 0).unwrap().set_text(desc);
1345 tbl.cell(r, 1).unwrap().set_text(qty);
1346 tbl.cell(r, 2).unwrap().set_text(unit);
1347 tbl.cell(r, 3).unwrap().set_text(tax);
1348 tbl.cell(r, 4).unwrap().set_text(amt);
1349 if i % 2 == 0 {
1350 for c in 0..5 {
1351 tbl.cell(r, c).unwrap().shading("FAF0F0");
1352 }
1353 }
1354 }
1355 }
1356
1357 doc.add_paragraph("");
1358
1359 // ── Totals ──
1360 {
1361 let mut tbl = doc
1362 .add_table(4, 2)
1363 .borders(BorderStyle::Single, 2, "B22222")
1364 .width(Length::inches(3.0))
1365 .alignment(Alignment::Right);
1366 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1367 tbl.cell(0, 1).unwrap().set_text("$154,600.00");
1368 tbl.cell(1, 0).unwrap().set_text("Tax (8.625%)");
1369 tbl.cell(1, 1).unwrap().set_text("$13,334.25");
1370 tbl.cell(2, 0).unwrap().set_text("Discount (5%)");
1371 tbl.cell(2, 1).unwrap().set_text("-$7,730.00");
1372 tbl.cell(3, 0).unwrap().set_text("AMOUNT DUE");
1373 tbl.cell(3, 0).unwrap().shading("B22222");
1374 tbl.cell(3, 1).unwrap().set_text("$160,204.25");
1375 tbl.cell(3, 1).unwrap().shading("B22222");
1376 }
1377
1378 doc.add_paragraph("");
1379 doc.add_paragraph("");
1380
1381 // ── Payment Details ──
1382 {
1383 let mut p = doc.add_paragraph("");
1384 p.add_run("Payment Details")
1385 .bold(true)
1386 .size(14.0)
1387 .color("333333");
1388 }
1389 doc.add_paragraph("Bank: Silicon Valley Bank")
1390 .indent_left(Length::inches(0.3));
1391 doc.add_paragraph("Account: Tensorbee Inc. — 0847-2953-1120")
1392 .indent_left(Length::inches(0.3));
1393 doc.add_paragraph("Routing: 121140399")
1394 .indent_left(Length::inches(0.3));
1395 doc.add_paragraph("Swift: SVBKUS6S")
1396 .indent_left(Length::inches(0.3));
1397
1398 doc.add_paragraph("");
1399
1400 doc.add_paragraph("Please include invoice number INV-2026-0392 in the payment reference.")
1401 .shading("FAF0F0")
1402 .border_all(BorderStyle::Single, 2, "B22222");
1403
1404 doc
1405}
1406
1407// =============================================================================
1408// 5. REPORT — Forest green + earth tones, with images & hierarchical sections
1409// =============================================================================
1410fn generate_report(_samples_dir: &Path) -> Document {
1411 let mut doc = Document::new();
1412
1413 // Colors: Forest #2D5016, Sage #6B8E23, Earth #8B7355, Cream #FFFAF0
1414 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1415 doc.set_margins(
1416 Length::inches(1.0),
1417 Length::inches(1.0),
1418 Length::inches(1.0),
1419 Length::inches(1.0),
1420 );
1421 doc.set_title("Q4 2025 Environmental Impact Report");
1422 doc.set_author("Walter White");
1423 doc.set_subject("Quarterly Environmental Report");
1424 doc.set_keywords("environment, sustainability, report, Tensorbee");
1425
1426 doc.set_different_first_page(true);
1427 doc.set_first_page_header("");
1428 doc.set_header("Tensorbee — Q4 2025 Environmental Impact Report");
1429 doc.set_footer("CONFIDENTIAL — Page");
1430
1431 // ── Cover ──
1432 let cover_bg = create_sample_png(612, 792, [20, 50, 15]);
1433 doc.add_background_image(&cover_bg, "report_cover.png");
1434
1435 for _ in 0..5 {
1436 doc.add_paragraph("");
1437 }
1438 {
1439 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1440 p.add_run("Q4 2025")
1441 .bold(true)
1442 .size(48.0)
1443 .color("FFFFFF")
1444 .font("Georgia");
1445 }
1446 {
1447 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1448 p.add_run("Environmental Impact Report")
1449 .size(24.0)
1450 .color("90EE90")
1451 .font("Georgia")
1452 .italic(true);
1453 }
1454 doc.add_paragraph("");
1455 {
1456 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1457 p.add_run("Tensorbee — Sustainability Division")
1458 .size(14.0)
1459 .color("C0C0C0");
1460 }
1461 {
1462 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1463 p.add_run("Prepared by Walter White, Chief Sustainability Officer")
1464 .size(11.0)
1465 .color("AAAAAA");
1466 }
1467
1468 // ── TOC ──
1469 doc.add_paragraph("").page_break_before(true);
1470 doc.insert_toc(doc.content_count(), 3);
1471
1472 // ── Section 1: Executive Overview ──
1473 doc.add_paragraph("").page_break_before(true);
1474 doc.add_paragraph("Executive Overview").style("Heading1");
1475 doc.add_paragraph(
1476 "This report presents Tensorbee's environmental performance for Q4 2025. Our \
1477 sustainability initiatives have yielded a 34% reduction in carbon emissions compared \
1478 to Q4 2024, exceeding our target of 25%. Key achievements include the transition to \
1479 100% renewable energy in our primary data centers and the launch of our carbon offset \
1480 marketplace.",
1481 )
1482 .first_line_indent(Length::inches(0.3));
1483
1484 // Image: performance chart
1485 let chart_img = create_chart_png(400, 200);
1486 doc.add_paragraph("");
1487 doc.add_picture(
1488 &chart_img,
1489 "performance_chart.png",
1490 Length::inches(5.0),
1491 Length::inches(2.5),
1492 )
1493 .alignment(Alignment::Center);
1494 {
1495 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1496 p.add_run("Figure 1: Quarterly Carbon Emissions (tonnes CO2e)")
1497 .italic(true)
1498 .size(9.0)
1499 .color("666666");
1500 }
1501
1502 // ── Section 2: Energy Consumption ──
1503 doc.add_paragraph("").page_break_before(true);
1504 doc.add_paragraph("Energy Consumption").style("Heading1");
1505
1506 doc.add_paragraph("Data Center Operations")
1507 .style("Heading2");
1508 doc.add_paragraph(
1509 "Our three primary data centers consumed a combined 4.2 GWh in Q4 2025, \
1510 a 12% reduction from Q3 2025 driven by improved cooling efficiency and \
1511 server consolidation.",
1512 );
1513
1514 // Data table
1515 {
1516 let mut tbl = doc
1517 .add_table(5, 4)
1518 .borders(BorderStyle::Single, 4, "2D5016")
1519 .width_pct(100.0);
1520 let hdrs = ["Data Center", "Capacity (MW)", "Usage (GWh)", "PUE"];
1521 for (c, h) in hdrs.iter().enumerate() {
1522 tbl.cell(0, c).unwrap().set_text(h);
1523 tbl.cell(0, c).unwrap().shading("2D5016");
1524 }
1525 let rows = [
1526 ("San Francisco (Primary)", "3.2", "1.8", "1.12"),
1527 ("Dublin (EU)", "2.1", "1.2", "1.18"),
1528 ("Singapore (APAC)", "1.8", "1.2", "1.24"),
1529 ("TOTAL", "7.1", "4.2", "1.17 avg"),
1530 ];
1531 for (i, (dc, cap, use_, pue)) in rows.iter().enumerate() {
1532 tbl.cell(i + 1, 0).unwrap().set_text(dc);
1533 tbl.cell(i + 1, 1).unwrap().set_text(cap);
1534 tbl.cell(i + 1, 2).unwrap().set_text(use_);
1535 tbl.cell(i + 1, 3).unwrap().set_text(pue);
1536 if i == 3 {
1537 for c in 0..4 {
1538 tbl.cell(i + 1, c).unwrap().shading("E2EFDA");
1539 }
1540 }
1541 }
1542 }
1543
1544 doc.add_paragraph("");
1545
1546 doc.add_paragraph("Renewable Energy Mix").style("Heading2");
1547 doc.add_paragraph("Breakdown of energy sources across all facilities:");
1548
1549 doc.add_bullet_list_item("Solar PV: 42% (1.76 GWh)", 0);
1550 doc.add_bullet_list_item("Wind Power Purchase Agreements: 38% (1.60 GWh)", 0);
1551 doc.add_bullet_list_item("Hydroelectric: 12% (0.50 GWh)", 0);
1552 doc.add_bullet_list_item("Grid (non-renewable): 8% (0.34 GWh)", 0);
1553
1554 // ── Section 3: Water & Waste ──
1555 doc.add_paragraph("Water & Waste Management")
1556 .style("Heading1");
1557
1558 doc.add_paragraph("Water Usage").style("Heading2");
1559 doc.add_paragraph(
1560 "Total water consumption was 12.4 million gallons, a 15% reduction from Q3. \
1561 Our closed-loop cooling systems now recycle 78% of water used in cooling operations.",
1562 );
1563
1564 doc.add_paragraph("Waste Reduction").style("Heading2");
1565 doc.add_paragraph("E-waste management results for Q4:")
1566 .keep_with_next(true);
1567 {
1568 let mut tbl = doc
1569 .add_table(4, 3)
1570 .borders(BorderStyle::Single, 2, "6B8E23")
1571 .width_pct(80.0);
1572 tbl.cell(0, 0).unwrap().set_text("Category");
1573 tbl.cell(0, 0).unwrap().shading("6B8E23");
1574 tbl.cell(0, 1).unwrap().set_text("Weight (kg)");
1575 tbl.cell(0, 1).unwrap().shading("6B8E23");
1576 tbl.cell(0, 2).unwrap().set_text("Recycled %");
1577 tbl.cell(0, 2).unwrap().shading("6B8E23");
1578 let rows = [
1579 ("Server Hardware", "2,450", "94%"),
1580 ("Networking Equipment", "820", "91%"),
1581 ("Storage Media", "340", "99%"),
1582 ];
1583 for (i, (cat, wt, pct)) in rows.iter().enumerate() {
1584 tbl.cell(i + 1, 0).unwrap().set_text(cat);
1585 tbl.cell(i + 1, 1).unwrap().set_text(wt);
1586 tbl.cell(i + 1, 2).unwrap().set_text(pct);
1587 }
1588 }
1589
1590 // ── Section 4: Initiatives ──
1591 doc.add_paragraph("").page_break_before(true);
1592 doc.add_paragraph("Q1 2026 Initiatives").style("Heading1");
1593
1594 doc.add_paragraph("Planned Programs").style("Heading2");
1595 doc.add_numbered_list_item(
1596 "Deploy on-site battery storage at SF data center (2 MWh capacity)",
1597 0,
1598 );
1599 doc.add_numbered_list_item("Pilot immersion cooling in Dublin facility", 0);
1600 doc.add_numbered_list_item("Launch employee commute carbon offset program", 0);
1601 doc.add_numbered_list_item("Achieve ISO 14001 certification for Singapore facility", 0);
1602
1603 doc.add_paragraph("Investment Targets").style("Heading2");
1604 doc.add_paragraph("Sustainability CapEx allocation for FY2026:")
1605 .keep_with_next(true);
1606 {
1607 let mut tbl = doc
1608 .add_table(5, 2)
1609 .borders(BorderStyle::Single, 4, "2D5016");
1610 tbl.cell(0, 0).unwrap().set_text("Initiative");
1611 tbl.cell(0, 0).unwrap().shading("2D5016");
1612 tbl.cell(0, 1).unwrap().set_text("Budget");
1613 tbl.cell(0, 1).unwrap().shading("2D5016");
1614 let rows = [
1615 ("Battery Storage", "$1.2M"),
1616 ("Immersion Cooling Pilot", "$800K"),
1617 ("Solar Panel Expansion", "$2.1M"),
1618 ("Carbon Credits", "$500K"),
1619 ];
1620 for (i, (init, budget)) in rows.iter().enumerate() {
1621 tbl.cell(i + 1, 0).unwrap().set_text(init);
1622 tbl.cell(i + 1, 1).unwrap().set_text(budget);
1623 if i % 2 == 0 {
1624 tbl.cell(i + 1, 0).unwrap().shading("FFFAF0");
1625 tbl.cell(i + 1, 1).unwrap().shading("FFFAF0");
1626 }
1627 }
1628 }
1629
1630 // Image: sustainability roadmap
1631 let roadmap_img = create_sample_png(500, 100, [40, 80, 30]);
1632 doc.add_paragraph("");
1633 doc.add_picture(
1634 &roadmap_img,
1635 "roadmap.png",
1636 Length::inches(6.0),
1637 Length::inches(1.2),
1638 )
1639 .alignment(Alignment::Center);
1640 {
1641 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1642 p.add_run("Figure 2: 2026 Sustainability Roadmap")
1643 .italic(true)
1644 .size(9.0)
1645 .color("666666");
1646 }
1647
1648 doc.add_paragraph("");
1649 doc.add_paragraph(
1650 "For questions about this report, contact Walter White at sustainability@tensorbee.com.",
1651 )
1652 .shading("FFFAF0")
1653 .border_all(BorderStyle::Single, 2, "2D5016");
1654
1655 doc
1656}
1657
1658// =============================================================================
1659// 6. LETTER — Slate blue + silver scheme
1660// =============================================================================
1661fn generate_letter(_samples_dir: &Path) -> Document {
1662 let mut doc = Document::new();
1663
1664 // Colors: Slate #4A5568, Blue accent #3182CE
1665 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1666 doc.set_margins(
1667 Length::inches(1.25),
1668 Length::inches(1.25),
1669 Length::inches(1.25),
1670 Length::inches(1.25),
1671 );
1672 doc.set_title("Business Letter");
1673 doc.set_author("Walter White");
1674
1675 // Banner header using raw XML
1676 let logo_img = create_logo_png(220, 48);
1677 let banner = build_header_banner_xml(
1678 "rId1",
1679 &BannerOpts {
1680 bg_color: "4A5568",
1681 banner_width: 7772400,
1682 banner_height: 731520, // ~0.8"
1683 logo_width: 2011680,
1684 logo_height: 438912,
1685 logo_x_offset: 295125,
1686 logo_y_offset: 146304,
1687 },
1688 );
1689 doc.set_raw_header_with_images(
1690 banner,
1691 &[("rId1", &logo_img, "logo.png")],
1692 rdocx_oxml::header_footer::HdrFtrType::Default,
1693 );
1694 doc.set_margins(
1695 Length::twips(2000),
1696 Length::twips(1800),
1697 Length::twips(1440),
1698 Length::twips(1800),
1699 );
1700 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
1701
1702 // Footer with contact
1703 doc.set_footer(
1704 "Tensorbee | 123 Innovation Drive, Suite 400 | San Francisco, CA 94105 | tensorbee.com",
1705 );
1706
1707 // ── Sender Address ──
1708 doc.add_paragraph("Walter White");
1709 doc.add_paragraph("Chief Executive Officer");
1710 doc.add_paragraph("Tensorbee");
1711 doc.add_paragraph("123 Innovation Drive, Suite 400");
1712 doc.add_paragraph("San Francisco, CA 94105");
1713 doc.add_paragraph("");
1714 doc.add_paragraph("February 22, 2026");
1715
1716 doc.add_paragraph("");
1717
1718 // ── Recipient ──
1719 doc.add_paragraph("Ms. Sarah Chen");
1720 doc.add_paragraph("VP of Engineering");
1721 doc.add_paragraph("Meridian Dynamics LLC");
1722 doc.add_paragraph("456 Enterprise Boulevard");
1723 doc.add_paragraph("Austin, TX 78701");
1724
1725 doc.add_paragraph("");
1726
1727 // ── Subject ──
1728 {
1729 let mut p = doc.add_paragraph("");
1730 p.add_run("Re: Strategic Technology Partnership — Phase 2 Expansion")
1731 .bold(true);
1732 }
1733
1734 doc.add_paragraph("");
1735
1736 // ── Body ──
1737 doc.add_paragraph("Dear Ms. Chen,");
1738 doc.add_paragraph("");
1739
1740 doc.add_paragraph(
1741 "I am writing to express Tensorbee's enthusiasm for expanding our strategic technology \
1742 partnership with Meridian Dynamics. Following the successful completion of Phase 1, \
1743 which delivered a 40% improvement in your ML inference pipeline throughput, we believe \
1744 the foundation is firmly established for an ambitious Phase 2 engagement.",
1745 )
1746 .first_line_indent(Length::inches(0.5));
1747
1748 doc.add_paragraph(
1749 "During our recent executive review, your team highlighted three priority areas for \
1750 the next phase of collaboration:",
1751 )
1752 .first_line_indent(Length::inches(0.5));
1753
1754 doc.add_numbered_list_item(
1755 "Real-time anomaly detection for your financial transaction monitoring system, \
1756 targeting sub-100ms latency at 50,000 transactions per second.",
1757 0,
1758 );
1759 doc.add_numbered_list_item(
1760 "Federated learning infrastructure to enable model training across your distributed \
1761 data centers without centralizing sensitive financial data.",
1762 0,
1763 );
1764 doc.add_numbered_list_item(
1765 "MLOps automation to reduce your model deployment cycle from the current 5 days \
1766 to under 4 hours.",
1767 0,
1768 );
1769
1770 doc.add_paragraph(
1771 "Our engineering team has prepared a detailed technical proposal addressing each of \
1772 these areas. We have allocated a dedicated team of eight senior engineers, led by \
1773 our CTO, to ensure continuity with the Phase 1 team your organization has already \
1774 built a productive working relationship with.",
1775 )
1776 .first_line_indent(Length::inches(0.5));
1777
1778 doc.add_paragraph(
1779 "I would welcome the opportunity to present our Phase 2 proposal in person. My \
1780 assistant will reach out to coordinate a meeting at your convenience during the \
1781 first week of March.",
1782 )
1783 .first_line_indent(Length::inches(0.5));
1784
1785 doc.add_paragraph("");
1786
1787 doc.add_paragraph("Warm regards,");
1788 doc.add_paragraph("");
1789 doc.add_paragraph("");
1790
1791 // Signature line
1792 doc.add_paragraph("")
1793 .border_bottom(BorderStyle::Single, 4, "4A5568");
1794 {
1795 let mut p = doc.add_paragraph("");
1796 p.add_run("Walter White").bold(true).size(12.0);
1797 }
1798 {
1799 let mut p = doc.add_paragraph("");
1800 p.add_run("Chief Executive Officer, Tensorbee")
1801 .italic(true)
1802 .size(10.0)
1803 .color("666666");
1804 }
1805 {
1806 let mut p = doc.add_paragraph("");
1807 p.add_run("walter@tensorbee.com | +1 (415) 555-0199")
1808 .size(10.0)
1809 .color("888888");
1810 }
1811
1812 doc
1813}
1814
1815// =============================================================================
1816// 7. EMPLOYMENT CONTRACT — Purple + charcoal scheme
1817// =============================================================================
1818fn generate_contract(_samples_dir: &Path) -> Document {
1819 let mut doc = Document::new();
1820
1821 // Colors: Purple #5B2C6F, Plum #8E44AD, Gray #2C3E50
1822 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1823 doc.set_margins(
1824 Length::inches(1.25),
1825 Length::inches(1.0),
1826 Length::inches(1.0),
1827 Length::inches(1.0),
1828 );
1829 doc.set_title("Employment Agreement");
1830 doc.set_author("Tensorbee HR Department");
1831 doc.set_subject("Employment Contract — Walter White");
1832 doc.set_keywords("employment, contract, agreement, Tensorbee");
1833
1834 doc.set_header("TENSORBEE — Employment Agreement");
1835 doc.set_footer("Employment Agreement — Walter White — Page");
1836
1837 // ── Title Block ──
1838 doc.add_paragraph("")
1839 .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1840 {
1841 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1842 p.add_run("EMPLOYMENT AGREEMENT")
1843 .bold(true)
1844 .size(24.0)
1845 .color("5B2C6F")
1846 .font("Georgia");
1847 }
1848 doc.add_paragraph("")
1849 .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1850 doc.add_paragraph("");
1851
1852 // ── Parties ──
1853 doc.add_paragraph(
1854 "This Employment Agreement (\"Agreement\") is entered into as of February 22, 2026 \
1855 (\"Effective Date\"), by and between:",
1856 );
1857
1858 doc.add_paragraph("");
1859
1860 {
1861 let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1862 p.add_run("EMPLOYER: ").bold(true);
1863 p.add_run(
1864 "Tensorbee, Inc., a Delaware corporation with its principal place of business at \
1865 123 Innovation Drive, Suite 400, San Francisco, CA 94105 (\"Company\")",
1866 );
1867 }
1868 doc.add_paragraph("");
1869 {
1870 let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1871 p.add_run("EMPLOYEE: ").bold(true);
1872 p.add_run(
1873 "Walter White, residing at 308 Negra Arroyo Lane, Albuquerque, NM 87104 (\"Employee\")",
1874 );
1875 }
1876
1877 doc.add_paragraph("");
1878 doc.add_paragraph("The Company and Employee are collectively referred to as the \"Parties.\"");
1879 doc.add_paragraph("");
1880
1881 // ── Article 1: Position and Duties ──
1882 doc.add_paragraph("Article 1 — Position and Duties")
1883 .style("Heading1");
1884 {
1885 let mut p = doc.add_paragraph("");
1886 p.add_run("1.1 ").bold(true);
1887 p.add_run("The Company hereby employs the Employee as ");
1888 p.add_run("Chief Executive Officer (CEO)")
1889 .bold(true)
1890 .italic(true);
1891 p.add_run(", reporting directly to the Board of Directors.");
1892 }
1893 {
1894 let mut p = doc.add_paragraph("");
1895 p.add_run("1.2 ").bold(true);
1896 p.add_run("The Employee shall devote full working time, attention, and best efforts to \
1897 the performance of duties as reasonably assigned by the Board, including but not limited to:");
1898 }
1899 doc.add_bullet_list_item(
1900 "Setting and executing the Company's strategic vision and business plan",
1901 0,
1902 );
1903 doc.add_bullet_list_item(
1904 "Overseeing all operations, engineering, and commercial activities",
1905 0,
1906 );
1907 doc.add_bullet_list_item(
1908 "Representing the Company to investors, customers, and the public",
1909 0,
1910 );
1911 doc.add_bullet_list_item("Recruiting, developing, and retaining key talent", 0);
1912
1913 {
1914 let mut p = doc.add_paragraph("");
1915 p.add_run("1.3 ").bold(true);
1916 p.add_run(
1917 "The Employee's primary work location shall be the Company's San Francisco \
1918 headquarters, with reasonable travel as required by business needs.",
1919 );
1920 }
1921
1922 // ── Article 2: Compensation ──
1923 doc.add_paragraph("Article 2 — Compensation and Benefits")
1924 .style("Heading1");
1925 {
1926 let mut p = doc.add_paragraph("");
1927 p.add_run("2.1 Base Salary. ").bold(true);
1928 p.add_run("The Company shall pay the Employee an annual base salary of ");
1929 p.add_run("$375,000.00").bold(true);
1930 p.add_run(" (Three Hundred Seventy-Five Thousand Dollars), payable in accordance with the \
1931 Company's standard payroll schedule, less applicable withholdings and deductions.");
1932 }
1933 {
1934 let mut p = doc.add_paragraph("");
1935 p.add_run("2.2 Annual Bonus. ").bold(true);
1936 p.add_run("The Employee shall be eligible for an annual performance bonus of up to ");
1937 p.add_run("40%").bold(true);
1938 p.add_run(
1939 " of base salary, based on achievement of mutually agreed performance objectives.",
1940 );
1941 }
1942 {
1943 let mut p = doc.add_paragraph("");
1944 p.add_run("2.3 Equity. ").bold(true);
1945 p.add_run("Subject to Board approval, the Employee shall receive a stock option grant of ");
1946 p.add_run("500,000 shares").bold(true);
1947 p.add_run(
1948 " of the Company's common stock, vesting over four (4) years with a one-year cliff, \
1949 at an exercise price equal to the fair market value on the date of grant.",
1950 );
1951 }
1952
1953 // Compensation summary table
1954 doc.add_paragraph("");
1955 {
1956 let mut tbl = doc
1957 .add_table(5, 2)
1958 .borders(BorderStyle::Single, 4, "5B2C6F")
1959 .width_pct(70.0)
1960 .alignment(Alignment::Center);
1961 tbl.cell(0, 0).unwrap().set_text("Compensation Element");
1962 tbl.cell(0, 0).unwrap().shading("5B2C6F");
1963 tbl.cell(0, 1).unwrap().set_text("Value");
1964 tbl.cell(0, 1).unwrap().shading("5B2C6F");
1965 let items = [
1966 ("Base Salary", "$375,000/year"),
1967 ("Target Bonus", "Up to 40% ($150,000)"),
1968 ("Equity Grant", "500,000 shares (4yr vest)"),
1969 ("Total Target Comp", "$525,000 + equity"),
1970 ];
1971 for (i, (elem, val)) in items.iter().enumerate() {
1972 tbl.cell(i + 1, 0).unwrap().set_text(elem);
1973 tbl.cell(i + 1, 1).unwrap().set_text(val);
1974 if i == 3 {
1975 tbl.cell(i + 1, 0).unwrap().shading("E8DAEF");
1976 tbl.cell(i + 1, 1).unwrap().shading("E8DAEF");
1977 }
1978 }
1979 }
1980
1981 // ── Article 3: Benefits ──
1982 doc.add_paragraph("").page_break_before(true);
1983 doc.add_paragraph("Article 3 — Benefits").style("Heading1");
1984 {
1985 let mut p = doc.add_paragraph("");
1986 p.add_run("3.1 ").bold(true);
1987 p.add_run(
1988 "The Employee shall be entitled to participate in all benefit programs \
1989 generally available to senior executives, including:",
1990 );
1991 }
1992 doc.add_bullet_list_item(
1993 "Medical, dental, and vision insurance (100% premium coverage for employee and dependents)",
1994 0,
1995 );
1996 doc.add_bullet_list_item("401(k) retirement plan with 6% company match", 0);
1997 doc.add_bullet_list_item("Life insurance and long-term disability coverage", 0);
1998 doc.add_bullet_list_item("Annual professional development allowance of $10,000", 0);
1999
2000 {
2001 let mut p = doc.add_paragraph("");
2002 p.add_run("3.2 Paid Time Off. ").bold(true);
2003 p.add_run(
2004 "The Employee shall receive 25 days of paid vacation per year, plus Company holidays, \
2005 accruing on a monthly basis.",
2006 );
2007 }
2008
2009 // ── Article 4: Term and Termination ──
2010 doc.add_paragraph("Article 4 — Term and Termination")
2011 .style("Heading1");
2012 {
2013 let mut p = doc.add_paragraph("");
2014 p.add_run("4.1 At-Will Employment. ").bold(true);
2015 p.add_run(
2016 "This Agreement is for at-will employment and may be terminated by either Party \
2017 at any time, with or without cause, subject to the notice provisions herein.",
2018 );
2019 }
2020 {
2021 let mut p = doc.add_paragraph("");
2022 p.add_run("4.2 Notice Period. ").bold(true);
2023 p.add_run("Either Party shall provide ");
2024 p.add_run("ninety (90) days").bold(true);
2025 p.add_run(" written notice of termination, or pay in lieu thereof.");
2026 }
2027 {
2028 let mut p = doc.add_paragraph("");
2029 p.add_run("4.3 Severance. ").bold(true);
2030 p.add_run("In the event of termination by the Company without Cause, the Employee shall \
2031 receive (a) twelve (12) months of base salary continuation, (b) pro-rata bonus \
2032 for the year of termination, and (c) twelve (12) months of COBRA premium coverage.");
2033 }
2034
2035 // ── Article 5: Confidentiality ──
2036 doc.add_paragraph("Article 5 — Confidentiality and IP")
2037 .style("Heading1");
2038 {
2039 let mut p = doc.add_paragraph("");
2040 p.add_run("5.1 ").bold(true);
2041 p.add_run(
2042 "The Employee agrees to maintain strict confidentiality of all proprietary \
2043 information, trade secrets, and business plans of the Company during and after \
2044 employment.",
2045 );
2046 }
2047 {
2048 let mut p = doc.add_paragraph("");
2049 p.add_run("5.2 ").bold(true);
2050 p.add_run(
2051 "All intellectual property, inventions, and works of authorship created by the \
2052 Employee during the term of employment and within the scope of duties shall be \
2053 the sole and exclusive property of the Company.",
2054 );
2055 }
2056
2057 // ── Article 6: Non-Compete ──
2058 doc.add_paragraph("Article 6 — Non-Competition")
2059 .style("Heading1");
2060 {
2061 let mut p = doc.add_paragraph("");
2062 p.add_run("6.1 ").bold(true);
2063 p.add_run("For a period of twelve (12) months following termination, the Employee agrees \
2064 not to engage in any business that directly competes with the Company's core \
2065 business of AI infrastructure and ML operations services, within the United States.");
2066 }
2067
2068 // ── Article 7: General Provisions ──
2069 doc.add_paragraph("Article 7 — General Provisions")
2070 .style("Heading1");
2071 {
2072 let mut p = doc.add_paragraph("");
2073 p.add_run("7.1 Governing Law. ").bold(true);
2074 p.add_run(
2075 "This Agreement shall be governed by and construed in accordance with the laws of \
2076 the State of California.",
2077 );
2078 }
2079 {
2080 let mut p = doc.add_paragraph("");
2081 p.add_run("7.2 Entire Agreement. ").bold(true);
2082 p.add_run(
2083 "This Agreement constitutes the entire agreement between the Parties and supersedes \
2084 all prior negotiations, representations, and agreements.",
2085 );
2086 }
2087 {
2088 let mut p = doc.add_paragraph("");
2089 p.add_run("7.3 Amendment. ").bold(true);
2090 p.add_run(
2091 "This Agreement may only be amended by a written instrument signed by both Parties.",
2092 );
2093 }
2094
2095 // ── Signature Block ──
2096 doc.add_paragraph("").page_break_before(true);
2097 doc.add_paragraph("IN WITNESS WHEREOF, the Parties have executed this Employment Agreement as of the Effective Date.")
2098 .space_after(Length::pt(24.0));
2099
2100 // Signature table
2101 {
2102 let mut tbl = doc.add_table(6, 2).width_pct(100.0);
2103 tbl.cell(0, 0).unwrap().set_text("FOR THE COMPANY:");
2104 tbl.cell(0, 0).unwrap().shading("5B2C6F");
2105 tbl.cell(0, 1).unwrap().set_text("EMPLOYEE:");
2106 tbl.cell(0, 1).unwrap().shading("5B2C6F");
2107
2108 tbl.cell(1, 0).unwrap().set_text("");
2109 tbl.cell(1, 1).unwrap().set_text("");
2110 tbl.row(1).unwrap().height(Length::pt(40.0));
2111
2112 tbl.cell(2, 0)
2113 .unwrap()
2114 .set_text("Signature: ___________________________");
2115 tbl.cell(2, 1)
2116 .unwrap()
2117 .set_text("Signature: ___________________________");
2118 tbl.cell(3, 0)
2119 .unwrap()
2120 .set_text("Name: Board Representative");
2121 tbl.cell(3, 1).unwrap().set_text("Name: Walter White");
2122 tbl.cell(4, 0)
2123 .unwrap()
2124 .set_text("Title: Chair, Board of Directors");
2125 tbl.cell(4, 1)
2126 .unwrap()
2127 .set_text("Title: Chief Executive Officer");
2128 tbl.cell(5, 0).unwrap().set_text("Date: _______________");
2129 tbl.cell(5, 1).unwrap().set_text("Date: _______________");
2130 }
2131
2132 doc
2133}Sourcepub fn set_landscape(&mut self)
pub fn set_landscape(&mut self)
Set page orientation to landscape (swaps width and height if needed).
Sourcepub fn set_portrait(&mut self)
pub fn set_portrait(&mut self)
Set page orientation to portrait (swaps width and height if needed).
Sourcepub fn set_margins(
&mut self,
top: Length,
right: Length,
bottom: Length,
left: Length,
)
pub fn set_margins( &mut self, top: Length, right: Length, bottom: Length, left: Length, )
Set all page margins.
Examples found in repository?
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
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}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}97fn generate_feature_showcase(_samples_dir: &Path) -> Document {
98 let mut doc = Document::new();
99
100 // ── Page Setup & Metadata ──
101 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
102 doc.set_margins(
103 Length::inches(1.0),
104 Length::inches(1.0),
105 Length::inches(1.0),
106 Length::inches(1.0),
107 );
108 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
109 doc.set_gutter(Length::twips(0));
110 doc.set_title("rdocx Feature Showcase");
111 doc.set_author("rdocx Sample Generator");
112 doc.set_subject("Comprehensive feature demonstration");
113 doc.set_keywords("rdocx, docx, rust, sample, showcase");
114
115 // Headers & Footers with different first page
116 doc.set_different_first_page(true);
117 doc.set_first_page_header("rdocx");
118 doc.set_first_page_footer("Feature Showcase — Cover Page");
119 doc.set_header("rdocx Feature Showcase");
120 doc.set_footer("Generated by rdocx");
121
122 // ── COVER PAGE ──
123 let bg = create_sample_png(612, 792, [20, 45, 90]);
124 doc.add_background_image(&bg, "cover_bg.png");
125
126 for _ in 0..3 {
127 doc.add_paragraph("");
128 }
129 {
130 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
131 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
132 }
133 {
134 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
135 p.add_run("Complete Feature Showcase")
136 .size(28.0)
137 .color("FFFFFF")
138 .italic(true);
139 }
140 doc.add_paragraph("");
141 {
142 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
143 p.add_run("Every feature of the rdocx Rust library")
144 .size(13.0)
145 .color("B0C4DE");
146 }
147 {
148 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
149 p.add_run("demonstrated in a single document.")
150 .size(13.0)
151 .color("B0C4DE");
152 }
153
154 // ── TABLE OF CONTENTS ──
155 doc.add_paragraph("").page_break_before(true);
156 doc.insert_toc(doc.content_count(), 3);
157
158 // ── SECTION 1: TEXT FORMATTING ──
159 doc.add_paragraph("").page_break_before(true);
160 doc.add_paragraph("1. Text Formatting").style("Heading1");
161
162 // Paragraph Alignment
163 doc.add_paragraph("1.1 Paragraph Alignment")
164 .style("Heading2");
165 doc.add_paragraph("Left-aligned paragraph (default).")
166 .alignment(Alignment::Left);
167 doc.add_paragraph("Center-aligned paragraph.")
168 .alignment(Alignment::Center);
169 doc.add_paragraph("Right-aligned paragraph.")
170 .alignment(Alignment::Right);
171 doc.add_paragraph(
172 "Justified paragraph — this text is long enough to demonstrate how justified alignment \
173 distributes extra space across word gaps so lines fill the full width of the text area.",
174 )
175 .alignment(Alignment::Justify);
176
177 doc.add_paragraph("");
178
179 // Run Formatting
180 doc.add_paragraph("1.2 Run Formatting").style("Heading2");
181 {
182 let mut p = doc.add_paragraph("");
183 p.add_run("Bold").bold(true);
184 p.add_run(" | ");
185 p.add_run("Italic").italic(true);
186 p.add_run(" | ");
187 p.add_run("Bold Italic").bold(true).italic(true);
188 p.add_run(" | ");
189 p.add_run("Underline").underline(true);
190 p.add_run(" | ");
191 p.add_run("Strikethrough").strike(true);
192 p.add_run(" | ");
193 p.add_run("Double Strike").double_strike(true);
194 }
195 {
196 let mut p = doc.add_paragraph("");
197 p.add_run("Red").color("FF0000");
198 p.add_run(" | ");
199 p.add_run("Blue").color("0000FF");
200 p.add_run(" | ");
201 p.add_run("Green").color("00AA00");
202 p.add_run(" | ");
203 p.add_run("Highlighted").highlight("FFFF00");
204 }
205 {
206 let mut p = doc.add_paragraph("");
207 p.add_run("8pt").size(8.0);
208 p.add_run(" | ");
209 p.add_run("11pt").size(11.0);
210 p.add_run(" | ");
211 p.add_run("16pt").size(16.0);
212 p.add_run(" | ");
213 p.add_run("24pt").size(24.0);
214 }
215 {
216 let mut p = doc.add_paragraph("");
217 p.add_run("Arial").font("Arial");
218 p.add_run(" | ");
219 p.add_run("Times New Roman").font("Times New Roman");
220 p.add_run(" | ");
221 p.add_run("Courier New").font("Courier New");
222 }
223 {
224 let mut p = doc.add_paragraph("");
225 p.add_run("H");
226 p.add_run("2").subscript();
227 p.add_run("O (subscript) | E=mc");
228 p.add_run("2").superscript();
229 p.add_run(" (superscript)");
230 }
231 {
232 let mut p = doc.add_paragraph("");
233 p.add_run("ALL CAPS").all_caps(true);
234 p.add_run(" | ");
235 p.add_run("Small Caps").small_caps(true);
236 p.add_run(" | ");
237 p.add_run("Expanded").character_spacing(Length::twips(40));
238 p.add_run(" | ");
239 p.add_run("Hidden text (not visible)").hidden(true);
240 }
241
242 doc.add_paragraph("");
243
244 // Underline Styles
245 doc.add_paragraph("1.3 Underline Styles").style("Heading2");
246 {
247 let mut p = doc.add_paragraph("");
248 p.add_run("Single ")
249 .underline_style(rdocx::UnderlineStyle::Single);
250 p.add_run("Double ")
251 .underline_style(rdocx::UnderlineStyle::Double);
252 p.add_run("Thick ")
253 .underline_style(rdocx::UnderlineStyle::Thick);
254 p.add_run("Dotted ")
255 .underline_style(rdocx::UnderlineStyle::Dotted);
256 p.add_run("Dash ")
257 .underline_style(rdocx::UnderlineStyle::Dash);
258 p.add_run("Wave ")
259 .underline_style(rdocx::UnderlineStyle::Wave);
260 }
261
262 // ── SECTION 2: PARAGRAPH FORMATTING ──
263 doc.add_paragraph("").page_break_before(true);
264 doc.add_paragraph("2. Paragraph Formatting")
265 .style("Heading1");
266
267 doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
268 doc.add_paragraph("Paragraph with light green shading")
269 .shading("E2EFDA");
270 doc.add_paragraph("Paragraph with bottom border")
271 .border_bottom(BorderStyle::Single, 6, "2E75B6");
272 doc.add_paragraph("Paragraph with all borders (red)")
273 .border_all(BorderStyle::Single, 4, "FF0000");
274
275 doc.add_paragraph("2.2 Indentation").style("Heading2");
276 doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
277 .indent_left(Length::inches(1.0))
278 .hanging_indent(Length::inches(0.5));
279 doc.add_paragraph("0.5-inch first-line indent on this paragraph")
280 .first_line_indent(Length::inches(0.5));
281 doc.add_paragraph("Both left and right indent (0.75 inches each)")
282 .indent_left(Length::inches(0.75))
283 .indent_right(Length::inches(0.75));
284
285 doc.add_paragraph("2.3 Spacing & Line Height")
286 .style("Heading2");
287 doc.add_paragraph("Extra space before (24pt) and after (12pt)")
288 .space_before(Length::pt(24.0))
289 .space_after(Length::pt(12.0));
290 doc.add_paragraph(
291 "Double line spacing paragraph. This text should have extra vertical space between \
292 lines to demonstrate line_spacing_multiple(2.0).",
293 )
294 .line_spacing_multiple(2.0);
295 doc.add_paragraph("Exact 20pt line spacing (fixed height).")
296 .line_spacing(20.0);
297
298 doc.add_paragraph("2.4 Pagination Controls")
299 .style("Heading2");
300 doc.add_paragraph("keep_with_next — this paragraph stays with the next")
301 .keep_with_next(true);
302 doc.add_paragraph("(This paragraph was kept with the one above.)");
303 doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
304 .keep_together(true);
305 doc.add_paragraph("widow_control — prevents widow/orphan lines")
306 .widow_control(true);
307
308 // ── SECTION 3: LISTS ──
309 doc.add_paragraph("").page_break_before(true);
310 doc.add_paragraph("3. Lists").style("Heading1");
311
312 doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
313 doc.add_bullet_list_item("First item", 0);
314 doc.add_bullet_list_item("Second item", 0);
315 doc.add_bullet_list_item("Nested level 1", 1);
316 doc.add_bullet_list_item("Nested level 2", 2);
317 doc.add_bullet_list_item("Back to level 1", 1);
318 doc.add_bullet_list_item("Third item", 0);
319
320 doc.add_paragraph("");
321
322 doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
323 doc.add_numbered_list_item("First numbered item", 0);
324 doc.add_numbered_list_item("Second numbered item", 0);
325 doc.add_numbered_list_item("Sub-item A", 1);
326 doc.add_numbered_list_item("Sub-item B", 1);
327 doc.add_numbered_list_item("Third numbered item", 0);
328
329 // ── SECTION 4: TAB STOPS ──
330 doc.add_paragraph("");
331 doc.add_paragraph("4. Tab Stops").style("Heading1");
332
333 doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
334 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
335 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
336 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
337 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
338 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
339
340 doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
341 doc.add_paragraph("Item\t\tPrice")
342 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
343 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
344 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
345 doc.add_paragraph("Widget\t\t$19.99")
346 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
347 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
348 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
349 doc.add_paragraph("Gadget\t\t$249.50")
350 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
351 .add_tab_stop_with_leader(
352 TabAlignment::Right,
353 Length::inches(4.0),
354 TabLeader::Underscore,
355 )
356 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
357
358 // ── SECTION 5: TABLES ──
359 doc.add_paragraph("").page_break_before(true);
360 doc.add_paragraph("5. Tables").style("Heading1");
361
362 doc.add_paragraph("5.1 Basic Table").style("Heading2");
363 {
364 let mut tbl = doc
365 .add_table(4, 3)
366 .borders(BorderStyle::Single, 4, "000000");
367 for col in 0..3 {
368 tbl.cell(0, col).unwrap().shading("2E75B6");
369 }
370 tbl.cell(0, 0).unwrap().set_text("Name");
371 tbl.cell(0, 1).unwrap().set_text("Role");
372 tbl.cell(0, 2).unwrap().set_text("Location");
373 tbl.cell(1, 0).unwrap().set_text("Walter White");
374 tbl.cell(1, 1).unwrap().set_text("CEO");
375 tbl.cell(1, 2).unwrap().set_text("Albuquerque");
376 tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
377 tbl.cell(2, 1).unwrap().set_text("CTO");
378 tbl.cell(2, 2).unwrap().set_text("Remote");
379 tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
380 tbl.cell(3, 1).unwrap().set_text("Security");
381 tbl.cell(3, 2).unwrap().set_text("Washington");
382 }
383
384 doc.add_paragraph("");
385
386 doc.add_paragraph("5.2 Column Span & Cell Shading")
387 .style("Heading2");
388 {
389 let mut tbl = doc
390 .add_table(3, 4)
391 .borders(BorderStyle::Single, 4, "000000")
392 .width_pct(100.0);
393 tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
394 tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
395 tbl.cell(1, 0).unwrap().set_text("Region");
396 tbl.cell(1, 0).unwrap().shading("D6E4F0");
397 tbl.cell(1, 1).unwrap().set_text("Q1");
398 tbl.cell(1, 1).unwrap().shading("D6E4F0");
399 tbl.cell(1, 2).unwrap().set_text("Q2");
400 tbl.cell(1, 2).unwrap().shading("D6E4F0");
401 tbl.cell(1, 3).unwrap().set_text("Total");
402 tbl.cell(1, 3).unwrap().shading("D6E4F0");
403 tbl.cell(2, 0).unwrap().set_text("Americas");
404 tbl.cell(2, 1).unwrap().set_text("$2.4M");
405 tbl.cell(2, 2).unwrap().set_text("$2.7M");
406 tbl.cell(2, 3).unwrap().set_text("$5.1M");
407 }
408
409 doc.add_paragraph("");
410
411 doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
412 {
413 let mut tbl = doc
414 .add_table(4, 3)
415 .borders(BorderStyle::Single, 4, "000000");
416 tbl.cell(0, 0).unwrap().set_text("Category");
417 tbl.cell(0, 0).unwrap().shading("E2EFDA");
418 tbl.cell(0, 1).unwrap().set_text("Item");
419 tbl.cell(0, 1).unwrap().shading("E2EFDA");
420 tbl.cell(0, 2).unwrap().set_text("Price");
421 tbl.cell(0, 2).unwrap().shading("E2EFDA");
422 tbl.cell(1, 0).unwrap().set_text("Hardware");
423 tbl.cell(1, 0).unwrap().v_merge_restart();
424 tbl.cell(1, 1).unwrap().set_text("Laptop");
425 tbl.cell(1, 2).unwrap().set_text("$1,200");
426 tbl.cell(2, 0).unwrap().v_merge_continue();
427 tbl.cell(2, 1).unwrap().set_text("Monitor");
428 tbl.cell(2, 2).unwrap().set_text("$450");
429 tbl.cell(3, 0).unwrap().set_text("Software");
430 tbl.cell(3, 1).unwrap().set_text("IDE License");
431 tbl.cell(3, 2).unwrap().set_text("$200/yr");
432 }
433
434 doc.add_paragraph("");
435
436 doc.add_paragraph("5.4 Nested Table").style("Heading2");
437 {
438 let mut tbl = doc
439 .add_table(2, 2)
440 .borders(BorderStyle::Single, 6, "2E75B6");
441 tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
442 tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
443 tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
444 {
445 let mut cell = tbl.cell(1, 1).unwrap();
446 cell.set_text("Nested table below:");
447 let mut nested = cell
448 .add_table(2, 2)
449 .borders(BorderStyle::Single, 2, "FF6600");
450 nested.cell(0, 0).unwrap().set_text("A");
451 nested.cell(0, 1).unwrap().set_text("B");
452 nested.cell(1, 0).unwrap().set_text("C");
453 nested.cell(1, 1).unwrap().set_text("D");
454 }
455 }
456
457 doc.add_paragraph("");
458
459 doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
460 .style("Heading2");
461 {
462 let mut tbl = doc
463 .add_table(2, 3)
464 .borders(BorderStyle::Single, 4, "666666");
465 tbl.row(0).unwrap().height(Length::pt(50.0)).header();
466 tbl.cell(0, 0).unwrap().set_text("Top");
467 tbl.cell(0, 0)
468 .unwrap()
469 .vertical_alignment(VerticalAlignment::Top)
470 .shading("FFF2CC");
471 tbl.cell(0, 1).unwrap().set_text("Center");
472 tbl.cell(0, 1)
473 .unwrap()
474 .vertical_alignment(VerticalAlignment::Center)
475 .shading("D9E2F3");
476 tbl.cell(0, 2).unwrap().set_text("Bottom");
477 tbl.cell(0, 2)
478 .unwrap()
479 .vertical_alignment(VerticalAlignment::Bottom)
480 .shading("E2EFDA");
481 tbl.row(1).unwrap().cant_split();
482 tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
483 tbl.cell(1, 0).unwrap().no_wrap();
484 tbl.cell(1, 1).unwrap().set_text("Fixed width");
485 tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
486 tbl.cell(1, 2).unwrap().set_text("Normal");
487 }
488
489 // ── SECTION 6: IMAGES ──
490 doc.add_paragraph("").page_break_before(true);
491 doc.add_paragraph("6. Images").style("Heading1");
492
493 doc.add_paragraph("6.1 Inline Image").style("Heading2");
494 doc.add_paragraph("A blue gradient image below:");
495 let img = create_sample_png(200, 50, [0, 80, 200]);
496 doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
497
498 doc.add_paragraph("");
499 doc.add_paragraph("6.2 Header Image").style("Heading2");
500 let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
501 doc.set_header_image(
502 &hdr_img,
503 "header_logo.png",
504 Length::inches(2.0),
505 Length::inches(0.2),
506 );
507 doc.add_paragraph("The header now contains an inline image (check the top of this page).");
508
509 doc.add_paragraph("");
510 doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
511
512 // ── SECTION 7: CONTENT MANIPULATION ──
513 doc.add_paragraph("").page_break_before(true);
514 doc.add_paragraph("7. Content Manipulation")
515 .style("Heading1");
516
517 doc.add_paragraph("7.1 Placeholder Replacement")
518 .style("Heading2");
519 doc.add_paragraph("Customer: {{customer}}");
520 doc.add_paragraph("Date: {{date}}");
521 doc.add_paragraph("Reference: {{ref_number}}");
522 {
523 let mut tbl = doc
524 .add_table(2, 2)
525 .borders(BorderStyle::Single, 4, "000000");
526 tbl.cell(0, 0).unwrap().set_text("Project");
527 tbl.cell(0, 0).unwrap().shading("D6E4F0");
528 tbl.cell(0, 1).unwrap().set_text("{{project}}");
529 tbl.cell(1, 0).unwrap().set_text("Status");
530 tbl.cell(1, 0).unwrap().shading("D6E4F0");
531 tbl.cell(1, 1).unwrap().set_text("{{status}}");
532 }
533
534 let mut replacements = HashMap::new();
535 replacements.insert("{{customer}}", "Tensorbee Inc.");
536 replacements.insert("{{date}}", "February 22, 2026");
537 replacements.insert("{{ref_number}}", "TB-2026-001");
538 replacements.insert("{{project}}", "Infrastructure Upgrade");
539 replacements.insert("{{status}}", "In Progress");
540 let n = doc.replace_all(&replacements);
541 doc.add_paragraph(&format!("({n} placeholders replaced above)"));
542
543 doc.add_paragraph("");
544
545 doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
546 doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
547 let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
548 doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
549
550 doc.add_paragraph("");
551
552 doc.add_paragraph("7.3 Content Insertion").style("Heading2");
553 doc.add_paragraph("Section A: First content.");
554 doc.add_paragraph("Section C: Third content.");
555 if let Some(idx) = doc.find_content_index("Section C") {
556 doc.insert_paragraph(
557 idx,
558 "Section B: Inserted between A and C via find_content_index().",
559 );
560 }
561
562 // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
563 doc.add_paragraph("").section_break(SectionBreak::NextPage);
564 doc.add_paragraph("8. Section Breaks & Orientation")
565 .style("Heading1");
566 doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
567
568 {
569 let mut tbl = doc
570 .add_table(3, 7)
571 .borders(BorderStyle::Single, 4, "2E75B6");
572 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
573 for (c, h) in headers.iter().enumerate() {
574 tbl.cell(0, c).unwrap().set_text(h);
575 tbl.cell(0, c).unwrap().shading("2E75B6");
576 }
577 let data = [
578 [
579 "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
580 ],
581 [
582 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
583 ],
584 ];
585 for (r, row) in data.iter().enumerate() {
586 for (c, v) in row.iter().enumerate() {
587 tbl.cell(r + 1, c).unwrap().set_text(v);
588 }
589 }
590 }
591
592 doc.add_paragraph("")
593 .section_break(SectionBreak::NextPage)
594 .section_landscape();
595
596 // ── SECTION 9: CUSTOM STYLES ──
597 doc.add_paragraph("9. Custom Styles").style("Heading1");
598 doc.add_style(
599 StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
600 .based_on("Normal")
601 .paragraph_properties(rdocx_oxml::properties::CT_PPr {
602 shading: Some(rdocx_oxml::properties::CT_Shd {
603 val: "clear".to_string(),
604 color: None,
605 fill: Some("FFF2CC".to_string()),
606 }),
607 ..Default::default()
608 })
609 .run_properties(rdocx_oxml::properties::CT_RPr {
610 bold: Some(true),
611 color: Some("C45911".to_string()),
612 ..Default::default()
613 }),
614 );
615 doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
616 .style("CustomHighlight");
617
618 doc.add_paragraph("");
619
620 // ── SECTION 10: DOCUMENT INTELLIGENCE ──
621 doc.add_paragraph("10. Document Intelligence API")
622 .style("Heading1");
623
624 let wc = doc.word_count();
625 let hc = doc.headings().len();
626 let ic = doc.images().len();
627 let lc = doc.links().len();
628 doc.add_paragraph(&format!("Word count: {wc}"));
629 doc.add_paragraph(&format!("Heading count: {hc}"));
630 doc.add_paragraph(&format!("Image count: {ic}"));
631 doc.add_paragraph(&format!("Link count: {lc}"));
632
633 let outline = doc.document_outline();
634 doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
635
636 let issues = doc.audit_accessibility();
637 doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
638 for issue in &issues {
639 doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
640 }
641
642 // ── SECTION 11: DOCUMENT MERGING ──
643 doc.add_paragraph("");
644 doc.add_paragraph("11. Document Merging").style("Heading1");
645 {
646 let mut other = Document::new();
647 other.add_paragraph("This paragraph was merged from another document using append().");
648 doc.append(&other);
649 }
650
651 doc.add_paragraph("");
652
653 // ── SUMMARY ──
654 doc.add_paragraph("Summary of Demonstrated Features")
655 .style("Heading1");
656 let features = [
657 "Page setup: size, margins, header/footer distance, gutter",
658 "Document metadata: title, author, subject, keywords",
659 "Headers and footers: text, images, different first page",
660 "Background images (full-page behind text)",
661 "Table of Contents generation with bookmarks",
662 "Text formatting: bold, italic, underline styles, strike, color, size, font",
663 "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
664 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
665 "Pagination controls: keep-with-next, keep-together, widow control",
666 "Bullet and numbered lists with nesting",
667 "Tab stops with dot/underscore/hyphen leaders",
668 "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
669 "Row properties: header rows, exact height, cant-split",
670 "Cell properties: width, no-wrap, vertical alignment",
671 "Inline images and header images",
672 "Placeholder replacement (single and batch)",
673 "Regex-based find and replace",
674 "Content insertion at specific positions",
675 "Section breaks with mixed portrait/landscape",
676 "Custom paragraph and character styles",
677 "Document intelligence: word count, headings, outline, images, links",
678 "Accessibility audit",
679 "Document merging (append)",
680 "Export: DOCX, PDF, HTML, Markdown",
681 ];
682 for f in &features {
683 doc.add_bullet_list_item(f, 0);
684 }
685 doc.add_paragraph("");
686 doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
687 .alignment(Alignment::Center)
688 .shading("E2EFDA")
689 .border_all(BorderStyle::Single, 2, "00AA00");
690
691 doc
692}
693
694// =============================================================================
695// 2. PROPOSAL DOCUMENT — Deep navy + gold scheme
696// =============================================================================
697fn generate_proposal(_samples_dir: &Path) -> Document {
698 let mut doc = Document::new();
699
700 // Colors: Navy #1B2A4A, Gold #C5922E, Light #F4F1EB
701 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
702 doc.set_margins(
703 Length::inches(1.0),
704 Length::inches(1.0),
705 Length::inches(1.0),
706 Length::inches(1.0),
707 );
708 doc.set_title("AI Platform Modernization Proposal");
709 doc.set_author("Walter White");
710 doc.set_subject("Technology Proposal");
711 doc.set_keywords("proposal, AI, modernization, Tensorbee");
712
713 // Banner header
714 let logo_img = create_logo_png(220, 48);
715 let banner = build_header_banner_xml(
716 "rId1",
717 &BannerOpts {
718 bg_color: "1B2A4A",
719 banner_width: 7772400,
720 banner_height: 914400,
721 logo_width: 2011680,
722 logo_height: 438912,
723 logo_x_offset: 295125,
724 logo_y_offset: 237744,
725 },
726 );
727 doc.set_raw_header_with_images(
728 banner,
729 &[("rId1", &logo_img, "logo.png")],
730 rdocx_oxml::header_footer::HdrFtrType::Default,
731 );
732 doc.set_different_first_page(true);
733 let cover_banner = build_header_banner_xml(
734 "rId1",
735 &BannerOpts {
736 bg_color: "C5922E",
737 banner_width: 7772400,
738 banner_height: 914400,
739 logo_width: 2011680,
740 logo_height: 438912,
741 logo_x_offset: 295125,
742 logo_y_offset: 237744,
743 },
744 );
745 doc.set_raw_header_with_images(
746 cover_banner,
747 &[("rId1", &logo_img, "logo.png")],
748 rdocx_oxml::header_footer::HdrFtrType::First,
749 );
750 doc.set_margins(
751 Length::twips(2292),
752 Length::twips(1440),
753 Length::twips(1440),
754 Length::twips(1440),
755 );
756 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
757 doc.set_footer("Tensorbee — Confidential");
758
759 // Custom styles
760 doc.add_style(
761 StyleBuilder::paragraph("ProposalTitle", "Proposal Title")
762 .based_on("Normal")
763 .run_properties(rdocx_oxml::properties::CT_RPr {
764 bold: Some(true),
765 font_ascii: Some("Georgia".to_string()),
766 font_hansi: Some("Georgia".to_string()),
767 sz: Some(rdocx_oxml::HalfPoint(56)), // half-points → 28pt
768 color: Some("1B2A4A".to_string()),
769 ..Default::default()
770 }),
771 );
772
773 // ── Cover Page ──
774 for _ in 0..4 {
775 doc.add_paragraph("");
776 }
777 doc.add_paragraph("AI Platform Modernization")
778 .style("ProposalTitle")
779 .alignment(Alignment::Center);
780 doc.add_paragraph("")
781 .alignment(Alignment::Center)
782 .border_bottom(BorderStyle::Single, 8, "C5922E");
783 {
784 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
785 p.add_run("Prepared for Global Financial Corp.")
786 .size(14.0)
787 .color("666666")
788 .italic(true);
789 }
790 {
791 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
792 p.add_run("Prepared by: Walter White, CEO — Tensorbee")
793 .size(12.0)
794 .color("888888");
795 }
796 {
797 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
798 p.add_run("February 22, 2026").size(12.0).color("888888");
799 }
800
801 // ── TOC ──
802 doc.add_paragraph("").page_break_before(true);
803 doc.add_paragraph("Table of Contents").style("Heading1");
804 // We'll add a manual-style TOC since the insert_toc goes at a specific position
805 doc.insert_toc(doc.content_count(), 2);
806
807 // ── Executive Summary ──
808 doc.add_paragraph("").page_break_before(true);
809 doc.add_paragraph("Executive Summary").style("Heading1");
810 doc.add_paragraph(
811 "Tensorbee proposes a comprehensive modernization of Global Financial Corp's AI platform, \
812 transitioning from legacy batch-processing systems to a real-time inference architecture. \
813 This transformation will reduce model deployment time from weeks to hours, improve \
814 prediction accuracy by an estimated 23%, and deliver $4.2M in annual operational savings.",
815 )
816 .first_line_indent(Length::inches(0.3));
817 doc.add_paragraph(
818 "The project spans three phases over 18 months, with a total investment of $2.8M. \
819 Tensorbee brings deep expertise in ML infrastructure, having successfully completed \
820 similar transformations for three Fortune 500 financial institutions.",
821 )
822 .first_line_indent(Length::inches(0.3));
823
824 // Key metrics table
825 doc.add_paragraph("");
826 {
827 let mut tbl = doc
828 .add_table(5, 2)
829 .borders(BorderStyle::Single, 4, "1B2A4A")
830 .width_pct(80.0);
831 tbl.cell(0, 0).unwrap().set_text("Key Metric");
832 tbl.cell(0, 0).unwrap().shading("1B2A4A");
833 tbl.cell(0, 1).unwrap().set_text("Value");
834 tbl.cell(0, 1).unwrap().shading("1B2A4A");
835 let rows = [
836 ("Total Investment", "$2.8M"),
837 ("Annual Savings", "$4.2M"),
838 ("ROI (Year 1)", "150%"),
839 ("Timeline", "18 months"),
840 ];
841 for (i, (k, v)) in rows.iter().enumerate() {
842 tbl.cell(i + 1, 0).unwrap().set_text(k);
843 if i % 2 == 0 {
844 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
845 }
846 tbl.cell(i + 1, 1).unwrap().set_text(v);
847 if i % 2 == 0 {
848 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
849 }
850 }
851 }
852
853 // ── Problem Statement ──
854 doc.add_paragraph("").page_break_before(true);
855 doc.add_paragraph("Problem Statement").style("Heading1");
856 doc.add_paragraph(
857 "Global Financial Corp's current AI infrastructure faces three critical challenges:",
858 );
859 doc.add_numbered_list_item("Deployment Latency — New models take 3-4 weeks to deploy to production, compared to the industry benchmark of 2-3 days.", 0);
860 doc.add_numbered_list_item("Scalability Constraints — The batch processing architecture cannot handle real-time inference demands during peak trading hours.", 0);
861 doc.add_numbered_list_item("Technical Debt — Legacy Python 2.7 codebases and manual deployment scripts create reliability risks and slow iteration speed.", 0);
862
863 // ── Proposed Solution ──
864 doc.add_paragraph("").page_break_before(true);
865 doc.add_paragraph("Proposed Solution").style("Heading1");
866
867 doc.add_paragraph("Phase 1: Foundation (Months 1-6)")
868 .style("Heading2");
869 doc.add_bullet_list_item("Deploy Kubernetes-based ML serving infrastructure", 0);
870 doc.add_bullet_list_item("Implement CI/CD pipeline for model deployment", 0);
871 doc.add_bullet_list_item("Migrate top 5 production models to new platform", 0);
872
873 doc.add_paragraph("Phase 2: Expansion (Months 7-12)")
874 .style("Heading2");
875 doc.add_bullet_list_item(
876 "Real-time feature engineering pipeline (Apache Kafka + Flink)",
877 0,
878 );
879 doc.add_bullet_list_item("A/B testing framework for model variants", 0);
880 doc.add_bullet_list_item("Automated model monitoring and drift detection", 0);
881
882 doc.add_paragraph("Phase 3: Optimization (Months 13-18)")
883 .style("Heading2");
884 doc.add_bullet_list_item("GPU inference optimization (TensorRT/ONNX Runtime)", 0);
885 doc.add_bullet_list_item("Multi-region deployment for latency reduction", 0);
886 doc.add_bullet_list_item(
887 "Self-service model deployment portal for data science teams",
888 0,
889 );
890
891 // ── Budget ──
892 doc.add_paragraph("Budget Breakdown").style("Heading1");
893 {
894 let mut tbl = doc
895 .add_table(6, 3)
896 .borders(BorderStyle::Single, 4, "1B2A4A")
897 .width_pct(100.0);
898 tbl.cell(0, 0).unwrap().set_text("Category");
899 tbl.cell(0, 0).unwrap().shading("1B2A4A");
900 tbl.cell(0, 1).unwrap().set_text("Cost");
901 tbl.cell(0, 1).unwrap().shading("1B2A4A");
902 tbl.cell(0, 2).unwrap().set_text("Notes");
903 tbl.cell(0, 2).unwrap().shading("1B2A4A");
904 let items = [
905 (
906 "Infrastructure",
907 "$450,000",
908 "Cloud compute + GPU instances",
909 ),
910 ("Engineering Services", "$1,600,000", "12 FTE x 18 months"),
911 (
912 "Software Licenses",
913 "$350,000",
914 "Kafka, monitoring, CI/CD tools",
915 ),
916 (
917 "Training & Enablement",
918 "$200,000",
919 "Team training, documentation",
920 ),
921 ("Contingency (10%)", "$200,000", "Risk buffer"),
922 ];
923 for (i, (cat, cost, note)) in items.iter().enumerate() {
924 tbl.cell(i + 1, 0).unwrap().set_text(cat);
925 tbl.cell(i + 1, 1).unwrap().set_text(cost);
926 tbl.cell(i + 1, 2).unwrap().set_text(note);
927 if i % 2 == 0 {
928 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
929 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
930 tbl.cell(i + 1, 2).unwrap().shading("F4F1EB");
931 }
932 }
933 }
934
935 // ── Closing ──
936 doc.add_paragraph("");
937 doc.add_paragraph("Next Steps").style("Heading1");
938 doc.add_numbered_list_item(
939 "Schedule technical deep-dive with GFC engineering team (Week 1)",
940 0,
941 );
942 doc.add_numbered_list_item("Finalize scope and sign SOW (Week 2-3)", 0);
943 doc.add_numbered_list_item("Kick off Phase 1 with joint planning session (Week 4)", 0);
944
945 doc.add_paragraph("");
946 {
947 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
948 p.add_run("Walter White")
949 .bold(true)
950 .size(14.0)
951 .color("1B2A4A");
952 }
953 {
954 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
955 p.add_run("CEO, Tensorbee | walter@tensorbee.com")
956 .size(10.0)
957 .color("888888");
958 }
959
960 doc
961}
962
963// =============================================================================
964// 3. QUOTE / BILL OF MATERIALS — Teal + orange scheme
965// =============================================================================
966fn generate_quote(_samples_dir: &Path) -> Document {
967 let mut doc = Document::new();
968
969 // Colors: Teal #008B8B, Dark #1A3C3C, Orange #E07020, Light #F0F8F8
970 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
971 doc.set_margins(
972 Length::inches(0.75),
973 Length::inches(0.75),
974 Length::inches(0.75),
975 Length::inches(0.75),
976 );
977 doc.set_title("Quotation QT-2026-0147");
978 doc.set_author("Walter White");
979 doc.set_keywords("quote, BOM, Tensorbee");
980
981 doc.set_header("Tensorbee — Quotation");
982 doc.set_footer("QT-2026-0147 | Page");
983
984 // ── Header Block ──
985 {
986 let mut p = doc.add_paragraph("").alignment(Alignment::Left);
987 p.add_run("TENSORBEE")
988 .bold(true)
989 .size(28.0)
990 .color("008B8B")
991 .font("Helvetica");
992 }
993 doc.add_paragraph("123 Innovation Drive, Suite 400")
994 .alignment(Alignment::Left);
995 doc.add_paragraph("San Francisco, CA 94105 | +1 (415) 555-0199")
996 .alignment(Alignment::Left);
997 doc.add_paragraph("")
998 .border_bottom(BorderStyle::Single, 8, "008B8B");
999
1000 // Quote meta
1001 doc.add_paragraph("");
1002 {
1003 let mut tbl = doc.add_table(4, 4).width_pct(100.0);
1004 tbl.cell(0, 0).unwrap().set_text("QUOTATION");
1005 tbl.cell(0, 0).unwrap().shading("008B8B").grid_span(2);
1006 tbl.cell(0, 2).unwrap().set_text("DATE");
1007 tbl.cell(0, 2).unwrap().shading("008B8B");
1008 tbl.cell(0, 3).unwrap().set_text("VALID UNTIL");
1009 tbl.cell(0, 3).unwrap().shading("008B8B");
1010 tbl.cell(1, 0).unwrap().set_text("Quote #:");
1011 tbl.cell(1, 1).unwrap().set_text("QT-2026-0147");
1012 tbl.cell(1, 2).unwrap().set_text("Feb 22, 2026");
1013 tbl.cell(1, 3).unwrap().set_text("Mar 22, 2026");
1014 tbl.cell(2, 0).unwrap().set_text("Prepared by:");
1015 tbl.cell(2, 1).unwrap().set_text("Walter White");
1016 tbl.cell(2, 2).unwrap().set_text("Payment:");
1017 tbl.cell(2, 3).unwrap().set_text("Net 30");
1018 tbl.cell(3, 0).unwrap().set_text("Customer:");
1019 tbl.cell(3, 1).unwrap().set_text("Meridian Dynamics LLC");
1020 tbl.cell(3, 2).unwrap().set_text("Currency:");
1021 tbl.cell(3, 3).unwrap().set_text("USD");
1022 }
1023
1024 doc.add_paragraph("");
1025
1026 // ── Bill of Materials ──
1027 {
1028 let mut p = doc.add_paragraph("");
1029 p.add_run("Bill of Materials")
1030 .bold(true)
1031 .size(16.0)
1032 .color("1A3C3C");
1033 }
1034 doc.add_paragraph("");
1035
1036 {
1037 let mut tbl = doc
1038 .add_table(10, 6)
1039 .borders(BorderStyle::Single, 2, "008B8B")
1040 .width_pct(100.0)
1041 .layout_fixed();
1042 // Headers
1043 let hdrs = [
1044 "#",
1045 "Part Number",
1046 "Description",
1047 "Qty",
1048 "Unit Price",
1049 "Total",
1050 ];
1051 for (c, h) in hdrs.iter().enumerate() {
1052 tbl.cell(0, c).unwrap().set_text(h);
1053 tbl.cell(0, c).unwrap().shading("008B8B");
1054 }
1055
1056 let items: Vec<(&str, &str, &str, &str, &str)> = vec![
1057 (
1058 "TB-GPU-A100",
1059 "NVIDIA A100 80GB GPU",
1060 "4",
1061 "$12,500.00",
1062 "$50,000.00",
1063 ),
1064 (
1065 "TB-SRV-R750",
1066 "Dell R750xa Server Chassis",
1067 "2",
1068 "$8,200.00",
1069 "$16,400.00",
1070 ),
1071 (
1072 "TB-RAM-256G",
1073 "256GB DDR5 ECC Memory Module",
1074 "8",
1075 "$890.00",
1076 "$7,120.00",
1077 ),
1078 (
1079 "TB-SSD-3840",
1080 "3.84TB NVMe U.2 SSD",
1081 "8",
1082 "$1,150.00",
1083 "$9,200.00",
1084 ),
1085 (
1086 "TB-NET-CX7",
1087 "ConnectX-7 200GbE NIC",
1088 "4",
1089 "$1,800.00",
1090 "$7,200.00",
1091 ),
1092 (
1093 "TB-SW-48P",
1094 "48-Port 100GbE Switch",
1095 "1",
1096 "$22,000.00",
1097 "$22,000.00",
1098 ),
1099 (
1100 "TB-CAB-RACK",
1101 "42U Server Rack + PDU",
1102 "1",
1103 "$4,500.00",
1104 "$4,500.00",
1105 ),
1106 (
1107 "TB-SVC-INST",
1108 "Installation & Configuration",
1109 "1",
1110 "$8,500.00",
1111 "$8,500.00",
1112 ),
1113 (
1114 "TB-SVC-SUPP",
1115 "3-Year Premium Support",
1116 "1",
1117 "$15,000.00",
1118 "$15,000.00",
1119 ),
1120 ];
1121
1122 for (i, (pn, desc, qty, unit, total)) in items.iter().enumerate() {
1123 let row = i + 1;
1124 tbl.cell(row, 0).unwrap().set_text(&format!("{}", i + 1));
1125 tbl.cell(row, 1).unwrap().set_text(pn);
1126 tbl.cell(row, 2).unwrap().set_text(desc);
1127 tbl.cell(row, 3).unwrap().set_text(qty);
1128 tbl.cell(row, 4).unwrap().set_text(unit);
1129 tbl.cell(row, 5).unwrap().set_text(total);
1130 if i % 2 == 0 {
1131 for c in 0..6 {
1132 tbl.cell(row, c).unwrap().shading("F0F8F8");
1133 }
1134 }
1135 }
1136 }
1137
1138 doc.add_paragraph("");
1139
1140 // ── Totals ──
1141 {
1142 let mut tbl = doc
1143 .add_table(4, 2)
1144 .borders(BorderStyle::Single, 2, "008B8B")
1145 .width(Length::inches(3.5))
1146 .alignment(Alignment::Right);
1147 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1148 tbl.cell(0, 1).unwrap().set_text("$139,920.00");
1149 tbl.cell(1, 0).unwrap().set_text("Shipping & Handling");
1150 tbl.cell(1, 1).unwrap().set_text("$2,500.00");
1151 tbl.cell(2, 0).unwrap().set_text("Tax (8.625%)");
1152 tbl.cell(2, 1).unwrap().set_text("$12,068.10");
1153 tbl.cell(3, 0).unwrap().set_text("TOTAL");
1154 tbl.cell(3, 0).unwrap().shading("E07020");
1155 tbl.cell(3, 1).unwrap().set_text("$154,488.10");
1156 tbl.cell(3, 1).unwrap().shading("E07020");
1157 }
1158
1159 doc.add_paragraph("");
1160
1161 // ── Terms & Conditions ──
1162 {
1163 let mut p = doc.add_paragraph("");
1164 p.add_run("Terms & Conditions")
1165 .bold(true)
1166 .size(14.0)
1167 .color("1A3C3C");
1168 }
1169 doc.add_numbered_list_item(
1170 "This quotation is valid for 30 calendar days from the date of issue.",
1171 0,
1172 );
1173 doc.add_numbered_list_item(
1174 "All prices are in USD and exclusive of applicable taxes unless stated.",
1175 0,
1176 );
1177 doc.add_numbered_list_item("Standard lead time is 4-6 weeks from PO receipt.", 0);
1178 doc.add_numbered_list_item(
1179 "Payment terms: Net 30 from invoice date. 2% discount for payment within 10 days.",
1180 0,
1181 );
1182 doc.add_numbered_list_item(
1183 "Warranty: 3-year manufacturer warranty on all hardware components.",
1184 0,
1185 );
1186 doc.add_numbered_list_item(
1187 "Returns subject to 15% restocking fee if initiated after 14 days.",
1188 0,
1189 );
1190
1191 doc.add_paragraph("");
1192 doc.add_paragraph("")
1193 .border_bottom(BorderStyle::Single, 4, "008B8B");
1194 doc.add_paragraph("");
1195 {
1196 let mut p = doc.add_paragraph("");
1197 p.add_run("Accepted by: ").bold(true);
1198 p.add_run("___________________________ Date: ___________");
1199 }
1200 {
1201 let mut p = doc.add_paragraph("");
1202 p.add_run("Print Name: ").bold(true);
1203 p.add_run("___________________________ Title: ___________");
1204 }
1205
1206 doc
1207}
1208
1209// =============================================================================
1210// 4. INVOICE — Crimson + charcoal scheme
1211// =============================================================================
1212fn generate_invoice(_samples_dir: &Path) -> Document {
1213 let mut doc = Document::new();
1214
1215 // Colors: Crimson #B22222, Charcoal #333333, Light #FAF0F0
1216 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1217 doc.set_margins(
1218 Length::inches(0.75),
1219 Length::inches(0.75),
1220 Length::inches(0.75),
1221 Length::inches(0.75),
1222 );
1223 doc.set_title("Invoice INV-2026-0392");
1224 doc.set_author("Walter White");
1225 doc.set_keywords("invoice, Tensorbee");
1226
1227 doc.set_footer("Tensorbee — Thank you for your business!");
1228
1229 // ── Company & Invoice Header ──
1230 {
1231 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1232 // Company name left, INVOICE right
1233 {
1234 let mut cell = tbl.cell(0, 0).unwrap();
1235 let mut p = cell.add_paragraph("");
1236 p.add_run("TENSORBEE")
1237 .bold(true)
1238 .size(32.0)
1239 .color("B22222")
1240 .font("Helvetica");
1241 }
1242 {
1243 let mut cell = tbl.cell(0, 1).unwrap();
1244 let mut p = cell.add_paragraph("");
1245 p.add_run("INVOICE").bold(true).size(32.0).color("333333");
1246 }
1247 tbl.cell(1, 0)
1248 .unwrap()
1249 .set_text("123 Innovation Drive, Suite 400");
1250 tbl.cell(1, 1).unwrap().set_text("Invoice #: INV-2026-0392");
1251 tbl.cell(2, 0).unwrap().set_text("San Francisco, CA 94105");
1252 tbl.cell(2, 1).unwrap().set_text("Date: February 22, 2026");
1253 tbl.cell(3, 0).unwrap().set_text("walter@tensorbee.com");
1254 tbl.cell(3, 1).unwrap().set_text("Due Date: March 24, 2026");
1255 }
1256
1257 doc.add_paragraph("")
1258 .border_bottom(BorderStyle::Thick, 8, "B22222");
1259 doc.add_paragraph("");
1260
1261 // ── Bill To / Ship To ──
1262 {
1263 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1264 tbl.cell(0, 0).unwrap().set_text("BILL TO");
1265 tbl.cell(0, 0).unwrap().shading("B22222");
1266 tbl.cell(0, 1).unwrap().set_text("SHIP TO");
1267 tbl.cell(0, 1).unwrap().shading("B22222");
1268 tbl.cell(1, 0).unwrap().set_text("Meridian Dynamics LLC");
1269 tbl.cell(1, 1).unwrap().set_text("Meridian Dynamics LLC");
1270 tbl.cell(2, 0).unwrap().set_text("456 Enterprise Blvd");
1271 tbl.cell(2, 1).unwrap().set_text("Attn: Server Room B");
1272 tbl.cell(3, 0).unwrap().set_text("Austin, TX 78701");
1273 tbl.cell(3, 1)
1274 .unwrap()
1275 .set_text("456 Enterprise Blvd, Austin, TX 78701");
1276 }
1277
1278 doc.add_paragraph("");
1279
1280 // ── Line Items ──
1281 {
1282 let mut tbl = doc
1283 .add_table(8, 5)
1284 .borders(BorderStyle::Single, 2, "B22222")
1285 .width_pct(100.0);
1286 let hdrs = ["Description", "Qty", "Unit Price", "Tax", "Amount"];
1287 for (c, h) in hdrs.iter().enumerate() {
1288 tbl.cell(0, c).unwrap().set_text(h);
1289 tbl.cell(0, c).unwrap().shading("B22222");
1290 }
1291 let items = [
1292 (
1293 "ML Infrastructure Setup — Phase 1",
1294 "1",
1295 "$45,000.00",
1296 "$3,881.25",
1297 "$48,881.25",
1298 ),
1299 (
1300 "Data Pipeline Development (160 hrs)",
1301 "160",
1302 "$225.00",
1303 "$3,105.00",
1304 "$39,105.00",
1305 ),
1306 (
1307 "GPU Cluster Configuration",
1308 "1",
1309 "$12,000.00",
1310 "$1,035.00",
1311 "$13,035.00",
1312 ),
1313 (
1314 "API Gateway Implementation",
1315 "1",
1316 "$18,500.00",
1317 "$1,595.63",
1318 "$20,095.63",
1319 ),
1320 (
1321 "Load Testing & QA (80 hrs)",
1322 "80",
1323 "$195.00",
1324 "$1,345.50",
1325 "$16,945.50",
1326 ),
1327 (
1328 "Documentation & Training",
1329 "1",
1330 "$8,000.00",
1331 "$690.00",
1332 "$8,690.00",
1333 ),
1334 (
1335 "Project Management (3 months)",
1336 "3",
1337 "$6,500.00",
1338 "$1,679.63",
1339 "$21,179.63",
1340 ),
1341 ];
1342 for (i, (desc, qty, unit, tax, amt)) in items.iter().enumerate() {
1343 let r = i + 1;
1344 tbl.cell(r, 0).unwrap().set_text(desc);
1345 tbl.cell(r, 1).unwrap().set_text(qty);
1346 tbl.cell(r, 2).unwrap().set_text(unit);
1347 tbl.cell(r, 3).unwrap().set_text(tax);
1348 tbl.cell(r, 4).unwrap().set_text(amt);
1349 if i % 2 == 0 {
1350 for c in 0..5 {
1351 tbl.cell(r, c).unwrap().shading("FAF0F0");
1352 }
1353 }
1354 }
1355 }
1356
1357 doc.add_paragraph("");
1358
1359 // ── Totals ──
1360 {
1361 let mut tbl = doc
1362 .add_table(4, 2)
1363 .borders(BorderStyle::Single, 2, "B22222")
1364 .width(Length::inches(3.0))
1365 .alignment(Alignment::Right);
1366 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1367 tbl.cell(0, 1).unwrap().set_text("$154,600.00");
1368 tbl.cell(1, 0).unwrap().set_text("Tax (8.625%)");
1369 tbl.cell(1, 1).unwrap().set_text("$13,334.25");
1370 tbl.cell(2, 0).unwrap().set_text("Discount (5%)");
1371 tbl.cell(2, 1).unwrap().set_text("-$7,730.00");
1372 tbl.cell(3, 0).unwrap().set_text("AMOUNT DUE");
1373 tbl.cell(3, 0).unwrap().shading("B22222");
1374 tbl.cell(3, 1).unwrap().set_text("$160,204.25");
1375 tbl.cell(3, 1).unwrap().shading("B22222");
1376 }
1377
1378 doc.add_paragraph("");
1379 doc.add_paragraph("");
1380
1381 // ── Payment Details ──
1382 {
1383 let mut p = doc.add_paragraph("");
1384 p.add_run("Payment Details")
1385 .bold(true)
1386 .size(14.0)
1387 .color("333333");
1388 }
1389 doc.add_paragraph("Bank: Silicon Valley Bank")
1390 .indent_left(Length::inches(0.3));
1391 doc.add_paragraph("Account: Tensorbee Inc. — 0847-2953-1120")
1392 .indent_left(Length::inches(0.3));
1393 doc.add_paragraph("Routing: 121140399")
1394 .indent_left(Length::inches(0.3));
1395 doc.add_paragraph("Swift: SVBKUS6S")
1396 .indent_left(Length::inches(0.3));
1397
1398 doc.add_paragraph("");
1399
1400 doc.add_paragraph("Please include invoice number INV-2026-0392 in the payment reference.")
1401 .shading("FAF0F0")
1402 .border_all(BorderStyle::Single, 2, "B22222");
1403
1404 doc
1405}
1406
1407// =============================================================================
1408// 5. REPORT — Forest green + earth tones, with images & hierarchical sections
1409// =============================================================================
1410fn generate_report(_samples_dir: &Path) -> Document {
1411 let mut doc = Document::new();
1412
1413 // Colors: Forest #2D5016, Sage #6B8E23, Earth #8B7355, Cream #FFFAF0
1414 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1415 doc.set_margins(
1416 Length::inches(1.0),
1417 Length::inches(1.0),
1418 Length::inches(1.0),
1419 Length::inches(1.0),
1420 );
1421 doc.set_title("Q4 2025 Environmental Impact Report");
1422 doc.set_author("Walter White");
1423 doc.set_subject("Quarterly Environmental Report");
1424 doc.set_keywords("environment, sustainability, report, Tensorbee");
1425
1426 doc.set_different_first_page(true);
1427 doc.set_first_page_header("");
1428 doc.set_header("Tensorbee — Q4 2025 Environmental Impact Report");
1429 doc.set_footer("CONFIDENTIAL — Page");
1430
1431 // ── Cover ──
1432 let cover_bg = create_sample_png(612, 792, [20, 50, 15]);
1433 doc.add_background_image(&cover_bg, "report_cover.png");
1434
1435 for _ in 0..5 {
1436 doc.add_paragraph("");
1437 }
1438 {
1439 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1440 p.add_run("Q4 2025")
1441 .bold(true)
1442 .size(48.0)
1443 .color("FFFFFF")
1444 .font("Georgia");
1445 }
1446 {
1447 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1448 p.add_run("Environmental Impact Report")
1449 .size(24.0)
1450 .color("90EE90")
1451 .font("Georgia")
1452 .italic(true);
1453 }
1454 doc.add_paragraph("");
1455 {
1456 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1457 p.add_run("Tensorbee — Sustainability Division")
1458 .size(14.0)
1459 .color("C0C0C0");
1460 }
1461 {
1462 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1463 p.add_run("Prepared by Walter White, Chief Sustainability Officer")
1464 .size(11.0)
1465 .color("AAAAAA");
1466 }
1467
1468 // ── TOC ──
1469 doc.add_paragraph("").page_break_before(true);
1470 doc.insert_toc(doc.content_count(), 3);
1471
1472 // ── Section 1: Executive Overview ──
1473 doc.add_paragraph("").page_break_before(true);
1474 doc.add_paragraph("Executive Overview").style("Heading1");
1475 doc.add_paragraph(
1476 "This report presents Tensorbee's environmental performance for Q4 2025. Our \
1477 sustainability initiatives have yielded a 34% reduction in carbon emissions compared \
1478 to Q4 2024, exceeding our target of 25%. Key achievements include the transition to \
1479 100% renewable energy in our primary data centers and the launch of our carbon offset \
1480 marketplace.",
1481 )
1482 .first_line_indent(Length::inches(0.3));
1483
1484 // Image: performance chart
1485 let chart_img = create_chart_png(400, 200);
1486 doc.add_paragraph("");
1487 doc.add_picture(
1488 &chart_img,
1489 "performance_chart.png",
1490 Length::inches(5.0),
1491 Length::inches(2.5),
1492 )
1493 .alignment(Alignment::Center);
1494 {
1495 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1496 p.add_run("Figure 1: Quarterly Carbon Emissions (tonnes CO2e)")
1497 .italic(true)
1498 .size(9.0)
1499 .color("666666");
1500 }
1501
1502 // ── Section 2: Energy Consumption ──
1503 doc.add_paragraph("").page_break_before(true);
1504 doc.add_paragraph("Energy Consumption").style("Heading1");
1505
1506 doc.add_paragraph("Data Center Operations")
1507 .style("Heading2");
1508 doc.add_paragraph(
1509 "Our three primary data centers consumed a combined 4.2 GWh in Q4 2025, \
1510 a 12% reduction from Q3 2025 driven by improved cooling efficiency and \
1511 server consolidation.",
1512 );
1513
1514 // Data table
1515 {
1516 let mut tbl = doc
1517 .add_table(5, 4)
1518 .borders(BorderStyle::Single, 4, "2D5016")
1519 .width_pct(100.0);
1520 let hdrs = ["Data Center", "Capacity (MW)", "Usage (GWh)", "PUE"];
1521 for (c, h) in hdrs.iter().enumerate() {
1522 tbl.cell(0, c).unwrap().set_text(h);
1523 tbl.cell(0, c).unwrap().shading("2D5016");
1524 }
1525 let rows = [
1526 ("San Francisco (Primary)", "3.2", "1.8", "1.12"),
1527 ("Dublin (EU)", "2.1", "1.2", "1.18"),
1528 ("Singapore (APAC)", "1.8", "1.2", "1.24"),
1529 ("TOTAL", "7.1", "4.2", "1.17 avg"),
1530 ];
1531 for (i, (dc, cap, use_, pue)) in rows.iter().enumerate() {
1532 tbl.cell(i + 1, 0).unwrap().set_text(dc);
1533 tbl.cell(i + 1, 1).unwrap().set_text(cap);
1534 tbl.cell(i + 1, 2).unwrap().set_text(use_);
1535 tbl.cell(i + 1, 3).unwrap().set_text(pue);
1536 if i == 3 {
1537 for c in 0..4 {
1538 tbl.cell(i + 1, c).unwrap().shading("E2EFDA");
1539 }
1540 }
1541 }
1542 }
1543
1544 doc.add_paragraph("");
1545
1546 doc.add_paragraph("Renewable Energy Mix").style("Heading2");
1547 doc.add_paragraph("Breakdown of energy sources across all facilities:");
1548
1549 doc.add_bullet_list_item("Solar PV: 42% (1.76 GWh)", 0);
1550 doc.add_bullet_list_item("Wind Power Purchase Agreements: 38% (1.60 GWh)", 0);
1551 doc.add_bullet_list_item("Hydroelectric: 12% (0.50 GWh)", 0);
1552 doc.add_bullet_list_item("Grid (non-renewable): 8% (0.34 GWh)", 0);
1553
1554 // ── Section 3: Water & Waste ──
1555 doc.add_paragraph("Water & Waste Management")
1556 .style("Heading1");
1557
1558 doc.add_paragraph("Water Usage").style("Heading2");
1559 doc.add_paragraph(
1560 "Total water consumption was 12.4 million gallons, a 15% reduction from Q3. \
1561 Our closed-loop cooling systems now recycle 78% of water used in cooling operations.",
1562 );
1563
1564 doc.add_paragraph("Waste Reduction").style("Heading2");
1565 doc.add_paragraph("E-waste management results for Q4:")
1566 .keep_with_next(true);
1567 {
1568 let mut tbl = doc
1569 .add_table(4, 3)
1570 .borders(BorderStyle::Single, 2, "6B8E23")
1571 .width_pct(80.0);
1572 tbl.cell(0, 0).unwrap().set_text("Category");
1573 tbl.cell(0, 0).unwrap().shading("6B8E23");
1574 tbl.cell(0, 1).unwrap().set_text("Weight (kg)");
1575 tbl.cell(0, 1).unwrap().shading("6B8E23");
1576 tbl.cell(0, 2).unwrap().set_text("Recycled %");
1577 tbl.cell(0, 2).unwrap().shading("6B8E23");
1578 let rows = [
1579 ("Server Hardware", "2,450", "94%"),
1580 ("Networking Equipment", "820", "91%"),
1581 ("Storage Media", "340", "99%"),
1582 ];
1583 for (i, (cat, wt, pct)) in rows.iter().enumerate() {
1584 tbl.cell(i + 1, 0).unwrap().set_text(cat);
1585 tbl.cell(i + 1, 1).unwrap().set_text(wt);
1586 tbl.cell(i + 1, 2).unwrap().set_text(pct);
1587 }
1588 }
1589
1590 // ── Section 4: Initiatives ──
1591 doc.add_paragraph("").page_break_before(true);
1592 doc.add_paragraph("Q1 2026 Initiatives").style("Heading1");
1593
1594 doc.add_paragraph("Planned Programs").style("Heading2");
1595 doc.add_numbered_list_item(
1596 "Deploy on-site battery storage at SF data center (2 MWh capacity)",
1597 0,
1598 );
1599 doc.add_numbered_list_item("Pilot immersion cooling in Dublin facility", 0);
1600 doc.add_numbered_list_item("Launch employee commute carbon offset program", 0);
1601 doc.add_numbered_list_item("Achieve ISO 14001 certification for Singapore facility", 0);
1602
1603 doc.add_paragraph("Investment Targets").style("Heading2");
1604 doc.add_paragraph("Sustainability CapEx allocation for FY2026:")
1605 .keep_with_next(true);
1606 {
1607 let mut tbl = doc
1608 .add_table(5, 2)
1609 .borders(BorderStyle::Single, 4, "2D5016");
1610 tbl.cell(0, 0).unwrap().set_text("Initiative");
1611 tbl.cell(0, 0).unwrap().shading("2D5016");
1612 tbl.cell(0, 1).unwrap().set_text("Budget");
1613 tbl.cell(0, 1).unwrap().shading("2D5016");
1614 let rows = [
1615 ("Battery Storage", "$1.2M"),
1616 ("Immersion Cooling Pilot", "$800K"),
1617 ("Solar Panel Expansion", "$2.1M"),
1618 ("Carbon Credits", "$500K"),
1619 ];
1620 for (i, (init, budget)) in rows.iter().enumerate() {
1621 tbl.cell(i + 1, 0).unwrap().set_text(init);
1622 tbl.cell(i + 1, 1).unwrap().set_text(budget);
1623 if i % 2 == 0 {
1624 tbl.cell(i + 1, 0).unwrap().shading("FFFAF0");
1625 tbl.cell(i + 1, 1).unwrap().shading("FFFAF0");
1626 }
1627 }
1628 }
1629
1630 // Image: sustainability roadmap
1631 let roadmap_img = create_sample_png(500, 100, [40, 80, 30]);
1632 doc.add_paragraph("");
1633 doc.add_picture(
1634 &roadmap_img,
1635 "roadmap.png",
1636 Length::inches(6.0),
1637 Length::inches(1.2),
1638 )
1639 .alignment(Alignment::Center);
1640 {
1641 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1642 p.add_run("Figure 2: 2026 Sustainability Roadmap")
1643 .italic(true)
1644 .size(9.0)
1645 .color("666666");
1646 }
1647
1648 doc.add_paragraph("");
1649 doc.add_paragraph(
1650 "For questions about this report, contact Walter White at sustainability@tensorbee.com.",
1651 )
1652 .shading("FFFAF0")
1653 .border_all(BorderStyle::Single, 2, "2D5016");
1654
1655 doc
1656}
1657
1658// =============================================================================
1659// 6. LETTER — Slate blue + silver scheme
1660// =============================================================================
1661fn generate_letter(_samples_dir: &Path) -> Document {
1662 let mut doc = Document::new();
1663
1664 // Colors: Slate #4A5568, Blue accent #3182CE
1665 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1666 doc.set_margins(
1667 Length::inches(1.25),
1668 Length::inches(1.25),
1669 Length::inches(1.25),
1670 Length::inches(1.25),
1671 );
1672 doc.set_title("Business Letter");
1673 doc.set_author("Walter White");
1674
1675 // Banner header using raw XML
1676 let logo_img = create_logo_png(220, 48);
1677 let banner = build_header_banner_xml(
1678 "rId1",
1679 &BannerOpts {
1680 bg_color: "4A5568",
1681 banner_width: 7772400,
1682 banner_height: 731520, // ~0.8"
1683 logo_width: 2011680,
1684 logo_height: 438912,
1685 logo_x_offset: 295125,
1686 logo_y_offset: 146304,
1687 },
1688 );
1689 doc.set_raw_header_with_images(
1690 banner,
1691 &[("rId1", &logo_img, "logo.png")],
1692 rdocx_oxml::header_footer::HdrFtrType::Default,
1693 );
1694 doc.set_margins(
1695 Length::twips(2000),
1696 Length::twips(1800),
1697 Length::twips(1440),
1698 Length::twips(1800),
1699 );
1700 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
1701
1702 // Footer with contact
1703 doc.set_footer(
1704 "Tensorbee | 123 Innovation Drive, Suite 400 | San Francisco, CA 94105 | tensorbee.com",
1705 );
1706
1707 // ── Sender Address ──
1708 doc.add_paragraph("Walter White");
1709 doc.add_paragraph("Chief Executive Officer");
1710 doc.add_paragraph("Tensorbee");
1711 doc.add_paragraph("123 Innovation Drive, Suite 400");
1712 doc.add_paragraph("San Francisco, CA 94105");
1713 doc.add_paragraph("");
1714 doc.add_paragraph("February 22, 2026");
1715
1716 doc.add_paragraph("");
1717
1718 // ── Recipient ──
1719 doc.add_paragraph("Ms. Sarah Chen");
1720 doc.add_paragraph("VP of Engineering");
1721 doc.add_paragraph("Meridian Dynamics LLC");
1722 doc.add_paragraph("456 Enterprise Boulevard");
1723 doc.add_paragraph("Austin, TX 78701");
1724
1725 doc.add_paragraph("");
1726
1727 // ── Subject ──
1728 {
1729 let mut p = doc.add_paragraph("");
1730 p.add_run("Re: Strategic Technology Partnership — Phase 2 Expansion")
1731 .bold(true);
1732 }
1733
1734 doc.add_paragraph("");
1735
1736 // ── Body ──
1737 doc.add_paragraph("Dear Ms. Chen,");
1738 doc.add_paragraph("");
1739
1740 doc.add_paragraph(
1741 "I am writing to express Tensorbee's enthusiasm for expanding our strategic technology \
1742 partnership with Meridian Dynamics. Following the successful completion of Phase 1, \
1743 which delivered a 40% improvement in your ML inference pipeline throughput, we believe \
1744 the foundation is firmly established for an ambitious Phase 2 engagement.",
1745 )
1746 .first_line_indent(Length::inches(0.5));
1747
1748 doc.add_paragraph(
1749 "During our recent executive review, your team highlighted three priority areas for \
1750 the next phase of collaboration:",
1751 )
1752 .first_line_indent(Length::inches(0.5));
1753
1754 doc.add_numbered_list_item(
1755 "Real-time anomaly detection for your financial transaction monitoring system, \
1756 targeting sub-100ms latency at 50,000 transactions per second.",
1757 0,
1758 );
1759 doc.add_numbered_list_item(
1760 "Federated learning infrastructure to enable model training across your distributed \
1761 data centers without centralizing sensitive financial data.",
1762 0,
1763 );
1764 doc.add_numbered_list_item(
1765 "MLOps automation to reduce your model deployment cycle from the current 5 days \
1766 to under 4 hours.",
1767 0,
1768 );
1769
1770 doc.add_paragraph(
1771 "Our engineering team has prepared a detailed technical proposal addressing each of \
1772 these areas. We have allocated a dedicated team of eight senior engineers, led by \
1773 our CTO, to ensure continuity with the Phase 1 team your organization has already \
1774 built a productive working relationship with.",
1775 )
1776 .first_line_indent(Length::inches(0.5));
1777
1778 doc.add_paragraph(
1779 "I would welcome the opportunity to present our Phase 2 proposal in person. My \
1780 assistant will reach out to coordinate a meeting at your convenience during the \
1781 first week of March.",
1782 )
1783 .first_line_indent(Length::inches(0.5));
1784
1785 doc.add_paragraph("");
1786
1787 doc.add_paragraph("Warm regards,");
1788 doc.add_paragraph("");
1789 doc.add_paragraph("");
1790
1791 // Signature line
1792 doc.add_paragraph("")
1793 .border_bottom(BorderStyle::Single, 4, "4A5568");
1794 {
1795 let mut p = doc.add_paragraph("");
1796 p.add_run("Walter White").bold(true).size(12.0);
1797 }
1798 {
1799 let mut p = doc.add_paragraph("");
1800 p.add_run("Chief Executive Officer, Tensorbee")
1801 .italic(true)
1802 .size(10.0)
1803 .color("666666");
1804 }
1805 {
1806 let mut p = doc.add_paragraph("");
1807 p.add_run("walter@tensorbee.com | +1 (415) 555-0199")
1808 .size(10.0)
1809 .color("888888");
1810 }
1811
1812 doc
1813}
1814
1815// =============================================================================
1816// 7. EMPLOYMENT CONTRACT — Purple + charcoal scheme
1817// =============================================================================
1818fn generate_contract(_samples_dir: &Path) -> Document {
1819 let mut doc = Document::new();
1820
1821 // Colors: Purple #5B2C6F, Plum #8E44AD, Gray #2C3E50
1822 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1823 doc.set_margins(
1824 Length::inches(1.25),
1825 Length::inches(1.0),
1826 Length::inches(1.0),
1827 Length::inches(1.0),
1828 );
1829 doc.set_title("Employment Agreement");
1830 doc.set_author("Tensorbee HR Department");
1831 doc.set_subject("Employment Contract — Walter White");
1832 doc.set_keywords("employment, contract, agreement, Tensorbee");
1833
1834 doc.set_header("TENSORBEE — Employment Agreement");
1835 doc.set_footer("Employment Agreement — Walter White — Page");
1836
1837 // ── Title Block ──
1838 doc.add_paragraph("")
1839 .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1840 {
1841 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1842 p.add_run("EMPLOYMENT AGREEMENT")
1843 .bold(true)
1844 .size(24.0)
1845 .color("5B2C6F")
1846 .font("Georgia");
1847 }
1848 doc.add_paragraph("")
1849 .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1850 doc.add_paragraph("");
1851
1852 // ── Parties ──
1853 doc.add_paragraph(
1854 "This Employment Agreement (\"Agreement\") is entered into as of February 22, 2026 \
1855 (\"Effective Date\"), by and between:",
1856 );
1857
1858 doc.add_paragraph("");
1859
1860 {
1861 let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1862 p.add_run("EMPLOYER: ").bold(true);
1863 p.add_run(
1864 "Tensorbee, Inc., a Delaware corporation with its principal place of business at \
1865 123 Innovation Drive, Suite 400, San Francisco, CA 94105 (\"Company\")",
1866 );
1867 }
1868 doc.add_paragraph("");
1869 {
1870 let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1871 p.add_run("EMPLOYEE: ").bold(true);
1872 p.add_run(
1873 "Walter White, residing at 308 Negra Arroyo Lane, Albuquerque, NM 87104 (\"Employee\")",
1874 );
1875 }
1876
1877 doc.add_paragraph("");
1878 doc.add_paragraph("The Company and Employee are collectively referred to as the \"Parties.\"");
1879 doc.add_paragraph("");
1880
1881 // ── Article 1: Position and Duties ──
1882 doc.add_paragraph("Article 1 — Position and Duties")
1883 .style("Heading1");
1884 {
1885 let mut p = doc.add_paragraph("");
1886 p.add_run("1.1 ").bold(true);
1887 p.add_run("The Company hereby employs the Employee as ");
1888 p.add_run("Chief Executive Officer (CEO)")
1889 .bold(true)
1890 .italic(true);
1891 p.add_run(", reporting directly to the Board of Directors.");
1892 }
1893 {
1894 let mut p = doc.add_paragraph("");
1895 p.add_run("1.2 ").bold(true);
1896 p.add_run("The Employee shall devote full working time, attention, and best efforts to \
1897 the performance of duties as reasonably assigned by the Board, including but not limited to:");
1898 }
1899 doc.add_bullet_list_item(
1900 "Setting and executing the Company's strategic vision and business plan",
1901 0,
1902 );
1903 doc.add_bullet_list_item(
1904 "Overseeing all operations, engineering, and commercial activities",
1905 0,
1906 );
1907 doc.add_bullet_list_item(
1908 "Representing the Company to investors, customers, and the public",
1909 0,
1910 );
1911 doc.add_bullet_list_item("Recruiting, developing, and retaining key talent", 0);
1912
1913 {
1914 let mut p = doc.add_paragraph("");
1915 p.add_run("1.3 ").bold(true);
1916 p.add_run(
1917 "The Employee's primary work location shall be the Company's San Francisco \
1918 headquarters, with reasonable travel as required by business needs.",
1919 );
1920 }
1921
1922 // ── Article 2: Compensation ──
1923 doc.add_paragraph("Article 2 — Compensation and Benefits")
1924 .style("Heading1");
1925 {
1926 let mut p = doc.add_paragraph("");
1927 p.add_run("2.1 Base Salary. ").bold(true);
1928 p.add_run("The Company shall pay the Employee an annual base salary of ");
1929 p.add_run("$375,000.00").bold(true);
1930 p.add_run(" (Three Hundred Seventy-Five Thousand Dollars), payable in accordance with the \
1931 Company's standard payroll schedule, less applicable withholdings and deductions.");
1932 }
1933 {
1934 let mut p = doc.add_paragraph("");
1935 p.add_run("2.2 Annual Bonus. ").bold(true);
1936 p.add_run("The Employee shall be eligible for an annual performance bonus of up to ");
1937 p.add_run("40%").bold(true);
1938 p.add_run(
1939 " of base salary, based on achievement of mutually agreed performance objectives.",
1940 );
1941 }
1942 {
1943 let mut p = doc.add_paragraph("");
1944 p.add_run("2.3 Equity. ").bold(true);
1945 p.add_run("Subject to Board approval, the Employee shall receive a stock option grant of ");
1946 p.add_run("500,000 shares").bold(true);
1947 p.add_run(
1948 " of the Company's common stock, vesting over four (4) years with a one-year cliff, \
1949 at an exercise price equal to the fair market value on the date of grant.",
1950 );
1951 }
1952
1953 // Compensation summary table
1954 doc.add_paragraph("");
1955 {
1956 let mut tbl = doc
1957 .add_table(5, 2)
1958 .borders(BorderStyle::Single, 4, "5B2C6F")
1959 .width_pct(70.0)
1960 .alignment(Alignment::Center);
1961 tbl.cell(0, 0).unwrap().set_text("Compensation Element");
1962 tbl.cell(0, 0).unwrap().shading("5B2C6F");
1963 tbl.cell(0, 1).unwrap().set_text("Value");
1964 tbl.cell(0, 1).unwrap().shading("5B2C6F");
1965 let items = [
1966 ("Base Salary", "$375,000/year"),
1967 ("Target Bonus", "Up to 40% ($150,000)"),
1968 ("Equity Grant", "500,000 shares (4yr vest)"),
1969 ("Total Target Comp", "$525,000 + equity"),
1970 ];
1971 for (i, (elem, val)) in items.iter().enumerate() {
1972 tbl.cell(i + 1, 0).unwrap().set_text(elem);
1973 tbl.cell(i + 1, 1).unwrap().set_text(val);
1974 if i == 3 {
1975 tbl.cell(i + 1, 0).unwrap().shading("E8DAEF");
1976 tbl.cell(i + 1, 1).unwrap().shading("E8DAEF");
1977 }
1978 }
1979 }
1980
1981 // ── Article 3: Benefits ──
1982 doc.add_paragraph("").page_break_before(true);
1983 doc.add_paragraph("Article 3 — Benefits").style("Heading1");
1984 {
1985 let mut p = doc.add_paragraph("");
1986 p.add_run("3.1 ").bold(true);
1987 p.add_run(
1988 "The Employee shall be entitled to participate in all benefit programs \
1989 generally available to senior executives, including:",
1990 );
1991 }
1992 doc.add_bullet_list_item(
1993 "Medical, dental, and vision insurance (100% premium coverage for employee and dependents)",
1994 0,
1995 );
1996 doc.add_bullet_list_item("401(k) retirement plan with 6% company match", 0);
1997 doc.add_bullet_list_item("Life insurance and long-term disability coverage", 0);
1998 doc.add_bullet_list_item("Annual professional development allowance of $10,000", 0);
1999
2000 {
2001 let mut p = doc.add_paragraph("");
2002 p.add_run("3.2 Paid Time Off. ").bold(true);
2003 p.add_run(
2004 "The Employee shall receive 25 days of paid vacation per year, plus Company holidays, \
2005 accruing on a monthly basis.",
2006 );
2007 }
2008
2009 // ── Article 4: Term and Termination ──
2010 doc.add_paragraph("Article 4 — Term and Termination")
2011 .style("Heading1");
2012 {
2013 let mut p = doc.add_paragraph("");
2014 p.add_run("4.1 At-Will Employment. ").bold(true);
2015 p.add_run(
2016 "This Agreement is for at-will employment and may be terminated by either Party \
2017 at any time, with or without cause, subject to the notice provisions herein.",
2018 );
2019 }
2020 {
2021 let mut p = doc.add_paragraph("");
2022 p.add_run("4.2 Notice Period. ").bold(true);
2023 p.add_run("Either Party shall provide ");
2024 p.add_run("ninety (90) days").bold(true);
2025 p.add_run(" written notice of termination, or pay in lieu thereof.");
2026 }
2027 {
2028 let mut p = doc.add_paragraph("");
2029 p.add_run("4.3 Severance. ").bold(true);
2030 p.add_run("In the event of termination by the Company without Cause, the Employee shall \
2031 receive (a) twelve (12) months of base salary continuation, (b) pro-rata bonus \
2032 for the year of termination, and (c) twelve (12) months of COBRA premium coverage.");
2033 }
2034
2035 // ── Article 5: Confidentiality ──
2036 doc.add_paragraph("Article 5 — Confidentiality and IP")
2037 .style("Heading1");
2038 {
2039 let mut p = doc.add_paragraph("");
2040 p.add_run("5.1 ").bold(true);
2041 p.add_run(
2042 "The Employee agrees to maintain strict confidentiality of all proprietary \
2043 information, trade secrets, and business plans of the Company during and after \
2044 employment.",
2045 );
2046 }
2047 {
2048 let mut p = doc.add_paragraph("");
2049 p.add_run("5.2 ").bold(true);
2050 p.add_run(
2051 "All intellectual property, inventions, and works of authorship created by the \
2052 Employee during the term of employment and within the scope of duties shall be \
2053 the sole and exclusive property of the Company.",
2054 );
2055 }
2056
2057 // ── Article 6: Non-Compete ──
2058 doc.add_paragraph("Article 6 — Non-Competition")
2059 .style("Heading1");
2060 {
2061 let mut p = doc.add_paragraph("");
2062 p.add_run("6.1 ").bold(true);
2063 p.add_run("For a period of twelve (12) months following termination, the Employee agrees \
2064 not to engage in any business that directly competes with the Company's core \
2065 business of AI infrastructure and ML operations services, within the United States.");
2066 }
2067
2068 // ── Article 7: General Provisions ──
2069 doc.add_paragraph("Article 7 — General Provisions")
2070 .style("Heading1");
2071 {
2072 let mut p = doc.add_paragraph("");
2073 p.add_run("7.1 Governing Law. ").bold(true);
2074 p.add_run(
2075 "This Agreement shall be governed by and construed in accordance with the laws of \
2076 the State of California.",
2077 );
2078 }
2079 {
2080 let mut p = doc.add_paragraph("");
2081 p.add_run("7.2 Entire Agreement. ").bold(true);
2082 p.add_run(
2083 "This Agreement constitutes the entire agreement between the Parties and supersedes \
2084 all prior negotiations, representations, and agreements.",
2085 );
2086 }
2087 {
2088 let mut p = doc.add_paragraph("");
2089 p.add_run("7.3 Amendment. ").bold(true);
2090 p.add_run(
2091 "This Agreement may only be amended by a written instrument signed by both Parties.",
2092 );
2093 }
2094
2095 // ── Signature Block ──
2096 doc.add_paragraph("").page_break_before(true);
2097 doc.add_paragraph("IN WITNESS WHEREOF, the Parties have executed this Employment Agreement as of the Effective Date.")
2098 .space_after(Length::pt(24.0));
2099
2100 // Signature table
2101 {
2102 let mut tbl = doc.add_table(6, 2).width_pct(100.0);
2103 tbl.cell(0, 0).unwrap().set_text("FOR THE COMPANY:");
2104 tbl.cell(0, 0).unwrap().shading("5B2C6F");
2105 tbl.cell(0, 1).unwrap().set_text("EMPLOYEE:");
2106 tbl.cell(0, 1).unwrap().shading("5B2C6F");
2107
2108 tbl.cell(1, 0).unwrap().set_text("");
2109 tbl.cell(1, 1).unwrap().set_text("");
2110 tbl.row(1).unwrap().height(Length::pt(40.0));
2111
2112 tbl.cell(2, 0)
2113 .unwrap()
2114 .set_text("Signature: ___________________________");
2115 tbl.cell(2, 1)
2116 .unwrap()
2117 .set_text("Signature: ___________________________");
2118 tbl.cell(3, 0)
2119 .unwrap()
2120 .set_text("Name: Board Representative");
2121 tbl.cell(3, 1).unwrap().set_text("Name: Walter White");
2122 tbl.cell(4, 0)
2123 .unwrap()
2124 .set_text("Title: Chair, Board of Directors");
2125 tbl.cell(4, 1)
2126 .unwrap()
2127 .set_text("Title: Chief Executive Officer");
2128 tbl.cell(5, 0).unwrap().set_text("Date: _______________");
2129 tbl.cell(5, 1).unwrap().set_text("Date: _______________");
2130 }
2131
2132 doc
2133}Sourcepub fn set_columns(&mut self, num: u32, spacing: Length)
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?
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
97fn generate_feature_showcase(_samples_dir: &Path) -> Document {
98 let mut doc = Document::new();
99
100 // ── Page Setup & Metadata ──
101 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
102 doc.set_margins(
103 Length::inches(1.0),
104 Length::inches(1.0),
105 Length::inches(1.0),
106 Length::inches(1.0),
107 );
108 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
109 doc.set_gutter(Length::twips(0));
110 doc.set_title("rdocx Feature Showcase");
111 doc.set_author("rdocx Sample Generator");
112 doc.set_subject("Comprehensive feature demonstration");
113 doc.set_keywords("rdocx, docx, rust, sample, showcase");
114
115 // Headers & Footers with different first page
116 doc.set_different_first_page(true);
117 doc.set_first_page_header("rdocx");
118 doc.set_first_page_footer("Feature Showcase — Cover Page");
119 doc.set_header("rdocx Feature Showcase");
120 doc.set_footer("Generated by rdocx");
121
122 // ── COVER PAGE ──
123 let bg = create_sample_png(612, 792, [20, 45, 90]);
124 doc.add_background_image(&bg, "cover_bg.png");
125
126 for _ in 0..3 {
127 doc.add_paragraph("");
128 }
129 {
130 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
131 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
132 }
133 {
134 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
135 p.add_run("Complete Feature Showcase")
136 .size(28.0)
137 .color("FFFFFF")
138 .italic(true);
139 }
140 doc.add_paragraph("");
141 {
142 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
143 p.add_run("Every feature of the rdocx Rust library")
144 .size(13.0)
145 .color("B0C4DE");
146 }
147 {
148 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
149 p.add_run("demonstrated in a single document.")
150 .size(13.0)
151 .color("B0C4DE");
152 }
153
154 // ── TABLE OF CONTENTS ──
155 doc.add_paragraph("").page_break_before(true);
156 doc.insert_toc(doc.content_count(), 3);
157
158 // ── SECTION 1: TEXT FORMATTING ──
159 doc.add_paragraph("").page_break_before(true);
160 doc.add_paragraph("1. Text Formatting").style("Heading1");
161
162 // Paragraph Alignment
163 doc.add_paragraph("1.1 Paragraph Alignment")
164 .style("Heading2");
165 doc.add_paragraph("Left-aligned paragraph (default).")
166 .alignment(Alignment::Left);
167 doc.add_paragraph("Center-aligned paragraph.")
168 .alignment(Alignment::Center);
169 doc.add_paragraph("Right-aligned paragraph.")
170 .alignment(Alignment::Right);
171 doc.add_paragraph(
172 "Justified paragraph — this text is long enough to demonstrate how justified alignment \
173 distributes extra space across word gaps so lines fill the full width of the text area.",
174 )
175 .alignment(Alignment::Justify);
176
177 doc.add_paragraph("");
178
179 // Run Formatting
180 doc.add_paragraph("1.2 Run Formatting").style("Heading2");
181 {
182 let mut p = doc.add_paragraph("");
183 p.add_run("Bold").bold(true);
184 p.add_run(" | ");
185 p.add_run("Italic").italic(true);
186 p.add_run(" | ");
187 p.add_run("Bold Italic").bold(true).italic(true);
188 p.add_run(" | ");
189 p.add_run("Underline").underline(true);
190 p.add_run(" | ");
191 p.add_run("Strikethrough").strike(true);
192 p.add_run(" | ");
193 p.add_run("Double Strike").double_strike(true);
194 }
195 {
196 let mut p = doc.add_paragraph("");
197 p.add_run("Red").color("FF0000");
198 p.add_run(" | ");
199 p.add_run("Blue").color("0000FF");
200 p.add_run(" | ");
201 p.add_run("Green").color("00AA00");
202 p.add_run(" | ");
203 p.add_run("Highlighted").highlight("FFFF00");
204 }
205 {
206 let mut p = doc.add_paragraph("");
207 p.add_run("8pt").size(8.0);
208 p.add_run(" | ");
209 p.add_run("11pt").size(11.0);
210 p.add_run(" | ");
211 p.add_run("16pt").size(16.0);
212 p.add_run(" | ");
213 p.add_run("24pt").size(24.0);
214 }
215 {
216 let mut p = doc.add_paragraph("");
217 p.add_run("Arial").font("Arial");
218 p.add_run(" | ");
219 p.add_run("Times New Roman").font("Times New Roman");
220 p.add_run(" | ");
221 p.add_run("Courier New").font("Courier New");
222 }
223 {
224 let mut p = doc.add_paragraph("");
225 p.add_run("H");
226 p.add_run("2").subscript();
227 p.add_run("O (subscript) | E=mc");
228 p.add_run("2").superscript();
229 p.add_run(" (superscript)");
230 }
231 {
232 let mut p = doc.add_paragraph("");
233 p.add_run("ALL CAPS").all_caps(true);
234 p.add_run(" | ");
235 p.add_run("Small Caps").small_caps(true);
236 p.add_run(" | ");
237 p.add_run("Expanded").character_spacing(Length::twips(40));
238 p.add_run(" | ");
239 p.add_run("Hidden text (not visible)").hidden(true);
240 }
241
242 doc.add_paragraph("");
243
244 // Underline Styles
245 doc.add_paragraph("1.3 Underline Styles").style("Heading2");
246 {
247 let mut p = doc.add_paragraph("");
248 p.add_run("Single ")
249 .underline_style(rdocx::UnderlineStyle::Single);
250 p.add_run("Double ")
251 .underline_style(rdocx::UnderlineStyle::Double);
252 p.add_run("Thick ")
253 .underline_style(rdocx::UnderlineStyle::Thick);
254 p.add_run("Dotted ")
255 .underline_style(rdocx::UnderlineStyle::Dotted);
256 p.add_run("Dash ")
257 .underline_style(rdocx::UnderlineStyle::Dash);
258 p.add_run("Wave ")
259 .underline_style(rdocx::UnderlineStyle::Wave);
260 }
261
262 // ── SECTION 2: PARAGRAPH FORMATTING ──
263 doc.add_paragraph("").page_break_before(true);
264 doc.add_paragraph("2. Paragraph Formatting")
265 .style("Heading1");
266
267 doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
268 doc.add_paragraph("Paragraph with light green shading")
269 .shading("E2EFDA");
270 doc.add_paragraph("Paragraph with bottom border")
271 .border_bottom(BorderStyle::Single, 6, "2E75B6");
272 doc.add_paragraph("Paragraph with all borders (red)")
273 .border_all(BorderStyle::Single, 4, "FF0000");
274
275 doc.add_paragraph("2.2 Indentation").style("Heading2");
276 doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
277 .indent_left(Length::inches(1.0))
278 .hanging_indent(Length::inches(0.5));
279 doc.add_paragraph("0.5-inch first-line indent on this paragraph")
280 .first_line_indent(Length::inches(0.5));
281 doc.add_paragraph("Both left and right indent (0.75 inches each)")
282 .indent_left(Length::inches(0.75))
283 .indent_right(Length::inches(0.75));
284
285 doc.add_paragraph("2.3 Spacing & Line Height")
286 .style("Heading2");
287 doc.add_paragraph("Extra space before (24pt) and after (12pt)")
288 .space_before(Length::pt(24.0))
289 .space_after(Length::pt(12.0));
290 doc.add_paragraph(
291 "Double line spacing paragraph. This text should have extra vertical space between \
292 lines to demonstrate line_spacing_multiple(2.0).",
293 )
294 .line_spacing_multiple(2.0);
295 doc.add_paragraph("Exact 20pt line spacing (fixed height).")
296 .line_spacing(20.0);
297
298 doc.add_paragraph("2.4 Pagination Controls")
299 .style("Heading2");
300 doc.add_paragraph("keep_with_next — this paragraph stays with the next")
301 .keep_with_next(true);
302 doc.add_paragraph("(This paragraph was kept with the one above.)");
303 doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
304 .keep_together(true);
305 doc.add_paragraph("widow_control — prevents widow/orphan lines")
306 .widow_control(true);
307
308 // ── SECTION 3: LISTS ──
309 doc.add_paragraph("").page_break_before(true);
310 doc.add_paragraph("3. Lists").style("Heading1");
311
312 doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
313 doc.add_bullet_list_item("First item", 0);
314 doc.add_bullet_list_item("Second item", 0);
315 doc.add_bullet_list_item("Nested level 1", 1);
316 doc.add_bullet_list_item("Nested level 2", 2);
317 doc.add_bullet_list_item("Back to level 1", 1);
318 doc.add_bullet_list_item("Third item", 0);
319
320 doc.add_paragraph("");
321
322 doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
323 doc.add_numbered_list_item("First numbered item", 0);
324 doc.add_numbered_list_item("Second numbered item", 0);
325 doc.add_numbered_list_item("Sub-item A", 1);
326 doc.add_numbered_list_item("Sub-item B", 1);
327 doc.add_numbered_list_item("Third numbered item", 0);
328
329 // ── SECTION 4: TAB STOPS ──
330 doc.add_paragraph("");
331 doc.add_paragraph("4. Tab Stops").style("Heading1");
332
333 doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
334 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
335 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
336 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
337 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
338 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
339
340 doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
341 doc.add_paragraph("Item\t\tPrice")
342 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
343 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
344 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
345 doc.add_paragraph("Widget\t\t$19.99")
346 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
347 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
348 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
349 doc.add_paragraph("Gadget\t\t$249.50")
350 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
351 .add_tab_stop_with_leader(
352 TabAlignment::Right,
353 Length::inches(4.0),
354 TabLeader::Underscore,
355 )
356 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
357
358 // ── SECTION 5: TABLES ──
359 doc.add_paragraph("").page_break_before(true);
360 doc.add_paragraph("5. Tables").style("Heading1");
361
362 doc.add_paragraph("5.1 Basic Table").style("Heading2");
363 {
364 let mut tbl = doc
365 .add_table(4, 3)
366 .borders(BorderStyle::Single, 4, "000000");
367 for col in 0..3 {
368 tbl.cell(0, col).unwrap().shading("2E75B6");
369 }
370 tbl.cell(0, 0).unwrap().set_text("Name");
371 tbl.cell(0, 1).unwrap().set_text("Role");
372 tbl.cell(0, 2).unwrap().set_text("Location");
373 tbl.cell(1, 0).unwrap().set_text("Walter White");
374 tbl.cell(1, 1).unwrap().set_text("CEO");
375 tbl.cell(1, 2).unwrap().set_text("Albuquerque");
376 tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
377 tbl.cell(2, 1).unwrap().set_text("CTO");
378 tbl.cell(2, 2).unwrap().set_text("Remote");
379 tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
380 tbl.cell(3, 1).unwrap().set_text("Security");
381 tbl.cell(3, 2).unwrap().set_text("Washington");
382 }
383
384 doc.add_paragraph("");
385
386 doc.add_paragraph("5.2 Column Span & Cell Shading")
387 .style("Heading2");
388 {
389 let mut tbl = doc
390 .add_table(3, 4)
391 .borders(BorderStyle::Single, 4, "000000")
392 .width_pct(100.0);
393 tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
394 tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
395 tbl.cell(1, 0).unwrap().set_text("Region");
396 tbl.cell(1, 0).unwrap().shading("D6E4F0");
397 tbl.cell(1, 1).unwrap().set_text("Q1");
398 tbl.cell(1, 1).unwrap().shading("D6E4F0");
399 tbl.cell(1, 2).unwrap().set_text("Q2");
400 tbl.cell(1, 2).unwrap().shading("D6E4F0");
401 tbl.cell(1, 3).unwrap().set_text("Total");
402 tbl.cell(1, 3).unwrap().shading("D6E4F0");
403 tbl.cell(2, 0).unwrap().set_text("Americas");
404 tbl.cell(2, 1).unwrap().set_text("$2.4M");
405 tbl.cell(2, 2).unwrap().set_text("$2.7M");
406 tbl.cell(2, 3).unwrap().set_text("$5.1M");
407 }
408
409 doc.add_paragraph("");
410
411 doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
412 {
413 let mut tbl = doc
414 .add_table(4, 3)
415 .borders(BorderStyle::Single, 4, "000000");
416 tbl.cell(0, 0).unwrap().set_text("Category");
417 tbl.cell(0, 0).unwrap().shading("E2EFDA");
418 tbl.cell(0, 1).unwrap().set_text("Item");
419 tbl.cell(0, 1).unwrap().shading("E2EFDA");
420 tbl.cell(0, 2).unwrap().set_text("Price");
421 tbl.cell(0, 2).unwrap().shading("E2EFDA");
422 tbl.cell(1, 0).unwrap().set_text("Hardware");
423 tbl.cell(1, 0).unwrap().v_merge_restart();
424 tbl.cell(1, 1).unwrap().set_text("Laptop");
425 tbl.cell(1, 2).unwrap().set_text("$1,200");
426 tbl.cell(2, 0).unwrap().v_merge_continue();
427 tbl.cell(2, 1).unwrap().set_text("Monitor");
428 tbl.cell(2, 2).unwrap().set_text("$450");
429 tbl.cell(3, 0).unwrap().set_text("Software");
430 tbl.cell(3, 1).unwrap().set_text("IDE License");
431 tbl.cell(3, 2).unwrap().set_text("$200/yr");
432 }
433
434 doc.add_paragraph("");
435
436 doc.add_paragraph("5.4 Nested Table").style("Heading2");
437 {
438 let mut tbl = doc
439 .add_table(2, 2)
440 .borders(BorderStyle::Single, 6, "2E75B6");
441 tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
442 tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
443 tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
444 {
445 let mut cell = tbl.cell(1, 1).unwrap();
446 cell.set_text("Nested table below:");
447 let mut nested = cell
448 .add_table(2, 2)
449 .borders(BorderStyle::Single, 2, "FF6600");
450 nested.cell(0, 0).unwrap().set_text("A");
451 nested.cell(0, 1).unwrap().set_text("B");
452 nested.cell(1, 0).unwrap().set_text("C");
453 nested.cell(1, 1).unwrap().set_text("D");
454 }
455 }
456
457 doc.add_paragraph("");
458
459 doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
460 .style("Heading2");
461 {
462 let mut tbl = doc
463 .add_table(2, 3)
464 .borders(BorderStyle::Single, 4, "666666");
465 tbl.row(0).unwrap().height(Length::pt(50.0)).header();
466 tbl.cell(0, 0).unwrap().set_text("Top");
467 tbl.cell(0, 0)
468 .unwrap()
469 .vertical_alignment(VerticalAlignment::Top)
470 .shading("FFF2CC");
471 tbl.cell(0, 1).unwrap().set_text("Center");
472 tbl.cell(0, 1)
473 .unwrap()
474 .vertical_alignment(VerticalAlignment::Center)
475 .shading("D9E2F3");
476 tbl.cell(0, 2).unwrap().set_text("Bottom");
477 tbl.cell(0, 2)
478 .unwrap()
479 .vertical_alignment(VerticalAlignment::Bottom)
480 .shading("E2EFDA");
481 tbl.row(1).unwrap().cant_split();
482 tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
483 tbl.cell(1, 0).unwrap().no_wrap();
484 tbl.cell(1, 1).unwrap().set_text("Fixed width");
485 tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
486 tbl.cell(1, 2).unwrap().set_text("Normal");
487 }
488
489 // ── SECTION 6: IMAGES ──
490 doc.add_paragraph("").page_break_before(true);
491 doc.add_paragraph("6. Images").style("Heading1");
492
493 doc.add_paragraph("6.1 Inline Image").style("Heading2");
494 doc.add_paragraph("A blue gradient image below:");
495 let img = create_sample_png(200, 50, [0, 80, 200]);
496 doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
497
498 doc.add_paragraph("");
499 doc.add_paragraph("6.2 Header Image").style("Heading2");
500 let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
501 doc.set_header_image(
502 &hdr_img,
503 "header_logo.png",
504 Length::inches(2.0),
505 Length::inches(0.2),
506 );
507 doc.add_paragraph("The header now contains an inline image (check the top of this page).");
508
509 doc.add_paragraph("");
510 doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
511
512 // ── SECTION 7: CONTENT MANIPULATION ──
513 doc.add_paragraph("").page_break_before(true);
514 doc.add_paragraph("7. Content Manipulation")
515 .style("Heading1");
516
517 doc.add_paragraph("7.1 Placeholder Replacement")
518 .style("Heading2");
519 doc.add_paragraph("Customer: {{customer}}");
520 doc.add_paragraph("Date: {{date}}");
521 doc.add_paragraph("Reference: {{ref_number}}");
522 {
523 let mut tbl = doc
524 .add_table(2, 2)
525 .borders(BorderStyle::Single, 4, "000000");
526 tbl.cell(0, 0).unwrap().set_text("Project");
527 tbl.cell(0, 0).unwrap().shading("D6E4F0");
528 tbl.cell(0, 1).unwrap().set_text("{{project}}");
529 tbl.cell(1, 0).unwrap().set_text("Status");
530 tbl.cell(1, 0).unwrap().shading("D6E4F0");
531 tbl.cell(1, 1).unwrap().set_text("{{status}}");
532 }
533
534 let mut replacements = HashMap::new();
535 replacements.insert("{{customer}}", "Tensorbee Inc.");
536 replacements.insert("{{date}}", "February 22, 2026");
537 replacements.insert("{{ref_number}}", "TB-2026-001");
538 replacements.insert("{{project}}", "Infrastructure Upgrade");
539 replacements.insert("{{status}}", "In Progress");
540 let n = doc.replace_all(&replacements);
541 doc.add_paragraph(&format!("({n} placeholders replaced above)"));
542
543 doc.add_paragraph("");
544
545 doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
546 doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
547 let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
548 doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
549
550 doc.add_paragraph("");
551
552 doc.add_paragraph("7.3 Content Insertion").style("Heading2");
553 doc.add_paragraph("Section A: First content.");
554 doc.add_paragraph("Section C: Third content.");
555 if let Some(idx) = doc.find_content_index("Section C") {
556 doc.insert_paragraph(
557 idx,
558 "Section B: Inserted between A and C via find_content_index().",
559 );
560 }
561
562 // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
563 doc.add_paragraph("").section_break(SectionBreak::NextPage);
564 doc.add_paragraph("8. Section Breaks & Orientation")
565 .style("Heading1");
566 doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
567
568 {
569 let mut tbl = doc
570 .add_table(3, 7)
571 .borders(BorderStyle::Single, 4, "2E75B6");
572 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
573 for (c, h) in headers.iter().enumerate() {
574 tbl.cell(0, c).unwrap().set_text(h);
575 tbl.cell(0, c).unwrap().shading("2E75B6");
576 }
577 let data = [
578 [
579 "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
580 ],
581 [
582 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
583 ],
584 ];
585 for (r, row) in data.iter().enumerate() {
586 for (c, v) in row.iter().enumerate() {
587 tbl.cell(r + 1, c).unwrap().set_text(v);
588 }
589 }
590 }
591
592 doc.add_paragraph("")
593 .section_break(SectionBreak::NextPage)
594 .section_landscape();
595
596 // ── SECTION 9: CUSTOM STYLES ──
597 doc.add_paragraph("9. Custom Styles").style("Heading1");
598 doc.add_style(
599 StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
600 .based_on("Normal")
601 .paragraph_properties(rdocx_oxml::properties::CT_PPr {
602 shading: Some(rdocx_oxml::properties::CT_Shd {
603 val: "clear".to_string(),
604 color: None,
605 fill: Some("FFF2CC".to_string()),
606 }),
607 ..Default::default()
608 })
609 .run_properties(rdocx_oxml::properties::CT_RPr {
610 bold: Some(true),
611 color: Some("C45911".to_string()),
612 ..Default::default()
613 }),
614 );
615 doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
616 .style("CustomHighlight");
617
618 doc.add_paragraph("");
619
620 // ── SECTION 10: DOCUMENT INTELLIGENCE ──
621 doc.add_paragraph("10. Document Intelligence API")
622 .style("Heading1");
623
624 let wc = doc.word_count();
625 let hc = doc.headings().len();
626 let ic = doc.images().len();
627 let lc = doc.links().len();
628 doc.add_paragraph(&format!("Word count: {wc}"));
629 doc.add_paragraph(&format!("Heading count: {hc}"));
630 doc.add_paragraph(&format!("Image count: {ic}"));
631 doc.add_paragraph(&format!("Link count: {lc}"));
632
633 let outline = doc.document_outline();
634 doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
635
636 let issues = doc.audit_accessibility();
637 doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
638 for issue in &issues {
639 doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
640 }
641
642 // ── SECTION 11: DOCUMENT MERGING ──
643 doc.add_paragraph("");
644 doc.add_paragraph("11. Document Merging").style("Heading1");
645 {
646 let mut other = Document::new();
647 other.add_paragraph("This paragraph was merged from another document using append().");
648 doc.append(&other);
649 }
650
651 doc.add_paragraph("");
652
653 // ── SUMMARY ──
654 doc.add_paragraph("Summary of Demonstrated Features")
655 .style("Heading1");
656 let features = [
657 "Page setup: size, margins, header/footer distance, gutter",
658 "Document metadata: title, author, subject, keywords",
659 "Headers and footers: text, images, different first page",
660 "Background images (full-page behind text)",
661 "Table of Contents generation with bookmarks",
662 "Text formatting: bold, italic, underline styles, strike, color, size, font",
663 "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
664 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
665 "Pagination controls: keep-with-next, keep-together, widow control",
666 "Bullet and numbered lists with nesting",
667 "Tab stops with dot/underscore/hyphen leaders",
668 "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
669 "Row properties: header rows, exact height, cant-split",
670 "Cell properties: width, no-wrap, vertical alignment",
671 "Inline images and header images",
672 "Placeholder replacement (single and batch)",
673 "Regex-based find and replace",
674 "Content insertion at specific positions",
675 "Section breaks with mixed portrait/landscape",
676 "Custom paragraph and character styles",
677 "Document intelligence: word count, headings, outline, images, links",
678 "Accessibility audit",
679 "Document merging (append)",
680 "Export: DOCX, PDF, HTML, Markdown",
681 ];
682 for f in &features {
683 doc.add_bullet_list_item(f, 0);
684 }
685 doc.add_paragraph("");
686 doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
687 .alignment(Alignment::Center)
688 .shading("E2EFDA")
689 .border_all(BorderStyle::Single, 2, "00AA00");
690
691 doc
692}
693
694// =============================================================================
695// 2. PROPOSAL DOCUMENT — Deep navy + gold scheme
696// =============================================================================
697fn generate_proposal(_samples_dir: &Path) -> Document {
698 let mut doc = Document::new();
699
700 // Colors: Navy #1B2A4A, Gold #C5922E, Light #F4F1EB
701 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
702 doc.set_margins(
703 Length::inches(1.0),
704 Length::inches(1.0),
705 Length::inches(1.0),
706 Length::inches(1.0),
707 );
708 doc.set_title("AI Platform Modernization Proposal");
709 doc.set_author("Walter White");
710 doc.set_subject("Technology Proposal");
711 doc.set_keywords("proposal, AI, modernization, Tensorbee");
712
713 // Banner header
714 let logo_img = create_logo_png(220, 48);
715 let banner = build_header_banner_xml(
716 "rId1",
717 &BannerOpts {
718 bg_color: "1B2A4A",
719 banner_width: 7772400,
720 banner_height: 914400,
721 logo_width: 2011680,
722 logo_height: 438912,
723 logo_x_offset: 295125,
724 logo_y_offset: 237744,
725 },
726 );
727 doc.set_raw_header_with_images(
728 banner,
729 &[("rId1", &logo_img, "logo.png")],
730 rdocx_oxml::header_footer::HdrFtrType::Default,
731 );
732 doc.set_different_first_page(true);
733 let cover_banner = build_header_banner_xml(
734 "rId1",
735 &BannerOpts {
736 bg_color: "C5922E",
737 banner_width: 7772400,
738 banner_height: 914400,
739 logo_width: 2011680,
740 logo_height: 438912,
741 logo_x_offset: 295125,
742 logo_y_offset: 237744,
743 },
744 );
745 doc.set_raw_header_with_images(
746 cover_banner,
747 &[("rId1", &logo_img, "logo.png")],
748 rdocx_oxml::header_footer::HdrFtrType::First,
749 );
750 doc.set_margins(
751 Length::twips(2292),
752 Length::twips(1440),
753 Length::twips(1440),
754 Length::twips(1440),
755 );
756 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
757 doc.set_footer("Tensorbee — Confidential");
758
759 // Custom styles
760 doc.add_style(
761 StyleBuilder::paragraph("ProposalTitle", "Proposal Title")
762 .based_on("Normal")
763 .run_properties(rdocx_oxml::properties::CT_RPr {
764 bold: Some(true),
765 font_ascii: Some("Georgia".to_string()),
766 font_hansi: Some("Georgia".to_string()),
767 sz: Some(rdocx_oxml::HalfPoint(56)), // half-points → 28pt
768 color: Some("1B2A4A".to_string()),
769 ..Default::default()
770 }),
771 );
772
773 // ── Cover Page ──
774 for _ in 0..4 {
775 doc.add_paragraph("");
776 }
777 doc.add_paragraph("AI Platform Modernization")
778 .style("ProposalTitle")
779 .alignment(Alignment::Center);
780 doc.add_paragraph("")
781 .alignment(Alignment::Center)
782 .border_bottom(BorderStyle::Single, 8, "C5922E");
783 {
784 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
785 p.add_run("Prepared for Global Financial Corp.")
786 .size(14.0)
787 .color("666666")
788 .italic(true);
789 }
790 {
791 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
792 p.add_run("Prepared by: Walter White, CEO — Tensorbee")
793 .size(12.0)
794 .color("888888");
795 }
796 {
797 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
798 p.add_run("February 22, 2026").size(12.0).color("888888");
799 }
800
801 // ── TOC ──
802 doc.add_paragraph("").page_break_before(true);
803 doc.add_paragraph("Table of Contents").style("Heading1");
804 // We'll add a manual-style TOC since the insert_toc goes at a specific position
805 doc.insert_toc(doc.content_count(), 2);
806
807 // ── Executive Summary ──
808 doc.add_paragraph("").page_break_before(true);
809 doc.add_paragraph("Executive Summary").style("Heading1");
810 doc.add_paragraph(
811 "Tensorbee proposes a comprehensive modernization of Global Financial Corp's AI platform, \
812 transitioning from legacy batch-processing systems to a real-time inference architecture. \
813 This transformation will reduce model deployment time from weeks to hours, improve \
814 prediction accuracy by an estimated 23%, and deliver $4.2M in annual operational savings.",
815 )
816 .first_line_indent(Length::inches(0.3));
817 doc.add_paragraph(
818 "The project spans three phases over 18 months, with a total investment of $2.8M. \
819 Tensorbee brings deep expertise in ML infrastructure, having successfully completed \
820 similar transformations for three Fortune 500 financial institutions.",
821 )
822 .first_line_indent(Length::inches(0.3));
823
824 // Key metrics table
825 doc.add_paragraph("");
826 {
827 let mut tbl = doc
828 .add_table(5, 2)
829 .borders(BorderStyle::Single, 4, "1B2A4A")
830 .width_pct(80.0);
831 tbl.cell(0, 0).unwrap().set_text("Key Metric");
832 tbl.cell(0, 0).unwrap().shading("1B2A4A");
833 tbl.cell(0, 1).unwrap().set_text("Value");
834 tbl.cell(0, 1).unwrap().shading("1B2A4A");
835 let rows = [
836 ("Total Investment", "$2.8M"),
837 ("Annual Savings", "$4.2M"),
838 ("ROI (Year 1)", "150%"),
839 ("Timeline", "18 months"),
840 ];
841 for (i, (k, v)) in rows.iter().enumerate() {
842 tbl.cell(i + 1, 0).unwrap().set_text(k);
843 if i % 2 == 0 {
844 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
845 }
846 tbl.cell(i + 1, 1).unwrap().set_text(v);
847 if i % 2 == 0 {
848 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
849 }
850 }
851 }
852
853 // ── Problem Statement ──
854 doc.add_paragraph("").page_break_before(true);
855 doc.add_paragraph("Problem Statement").style("Heading1");
856 doc.add_paragraph(
857 "Global Financial Corp's current AI infrastructure faces three critical challenges:",
858 );
859 doc.add_numbered_list_item("Deployment Latency — New models take 3-4 weeks to deploy to production, compared to the industry benchmark of 2-3 days.", 0);
860 doc.add_numbered_list_item("Scalability Constraints — The batch processing architecture cannot handle real-time inference demands during peak trading hours.", 0);
861 doc.add_numbered_list_item("Technical Debt — Legacy Python 2.7 codebases and manual deployment scripts create reliability risks and slow iteration speed.", 0);
862
863 // ── Proposed Solution ──
864 doc.add_paragraph("").page_break_before(true);
865 doc.add_paragraph("Proposed Solution").style("Heading1");
866
867 doc.add_paragraph("Phase 1: Foundation (Months 1-6)")
868 .style("Heading2");
869 doc.add_bullet_list_item("Deploy Kubernetes-based ML serving infrastructure", 0);
870 doc.add_bullet_list_item("Implement CI/CD pipeline for model deployment", 0);
871 doc.add_bullet_list_item("Migrate top 5 production models to new platform", 0);
872
873 doc.add_paragraph("Phase 2: Expansion (Months 7-12)")
874 .style("Heading2");
875 doc.add_bullet_list_item(
876 "Real-time feature engineering pipeline (Apache Kafka + Flink)",
877 0,
878 );
879 doc.add_bullet_list_item("A/B testing framework for model variants", 0);
880 doc.add_bullet_list_item("Automated model monitoring and drift detection", 0);
881
882 doc.add_paragraph("Phase 3: Optimization (Months 13-18)")
883 .style("Heading2");
884 doc.add_bullet_list_item("GPU inference optimization (TensorRT/ONNX Runtime)", 0);
885 doc.add_bullet_list_item("Multi-region deployment for latency reduction", 0);
886 doc.add_bullet_list_item(
887 "Self-service model deployment portal for data science teams",
888 0,
889 );
890
891 // ── Budget ──
892 doc.add_paragraph("Budget Breakdown").style("Heading1");
893 {
894 let mut tbl = doc
895 .add_table(6, 3)
896 .borders(BorderStyle::Single, 4, "1B2A4A")
897 .width_pct(100.0);
898 tbl.cell(0, 0).unwrap().set_text("Category");
899 tbl.cell(0, 0).unwrap().shading("1B2A4A");
900 tbl.cell(0, 1).unwrap().set_text("Cost");
901 tbl.cell(0, 1).unwrap().shading("1B2A4A");
902 tbl.cell(0, 2).unwrap().set_text("Notes");
903 tbl.cell(0, 2).unwrap().shading("1B2A4A");
904 let items = [
905 (
906 "Infrastructure",
907 "$450,000",
908 "Cloud compute + GPU instances",
909 ),
910 ("Engineering Services", "$1,600,000", "12 FTE x 18 months"),
911 (
912 "Software Licenses",
913 "$350,000",
914 "Kafka, monitoring, CI/CD tools",
915 ),
916 (
917 "Training & Enablement",
918 "$200,000",
919 "Team training, documentation",
920 ),
921 ("Contingency (10%)", "$200,000", "Risk buffer"),
922 ];
923 for (i, (cat, cost, note)) in items.iter().enumerate() {
924 tbl.cell(i + 1, 0).unwrap().set_text(cat);
925 tbl.cell(i + 1, 1).unwrap().set_text(cost);
926 tbl.cell(i + 1, 2).unwrap().set_text(note);
927 if i % 2 == 0 {
928 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
929 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
930 tbl.cell(i + 1, 2).unwrap().shading("F4F1EB");
931 }
932 }
933 }
934
935 // ── Closing ──
936 doc.add_paragraph("");
937 doc.add_paragraph("Next Steps").style("Heading1");
938 doc.add_numbered_list_item(
939 "Schedule technical deep-dive with GFC engineering team (Week 1)",
940 0,
941 );
942 doc.add_numbered_list_item("Finalize scope and sign SOW (Week 2-3)", 0);
943 doc.add_numbered_list_item("Kick off Phase 1 with joint planning session (Week 4)", 0);
944
945 doc.add_paragraph("");
946 {
947 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
948 p.add_run("Walter White")
949 .bold(true)
950 .size(14.0)
951 .color("1B2A4A");
952 }
953 {
954 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
955 p.add_run("CEO, Tensorbee | walter@tensorbee.com")
956 .size(10.0)
957 .color("888888");
958 }
959
960 doc
961}
962
963// =============================================================================
964// 3. QUOTE / BILL OF MATERIALS — Teal + orange scheme
965// =============================================================================
966fn generate_quote(_samples_dir: &Path) -> Document {
967 let mut doc = Document::new();
968
969 // Colors: Teal #008B8B, Dark #1A3C3C, Orange #E07020, Light #F0F8F8
970 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
971 doc.set_margins(
972 Length::inches(0.75),
973 Length::inches(0.75),
974 Length::inches(0.75),
975 Length::inches(0.75),
976 );
977 doc.set_title("Quotation QT-2026-0147");
978 doc.set_author("Walter White");
979 doc.set_keywords("quote, BOM, Tensorbee");
980
981 doc.set_header("Tensorbee — Quotation");
982 doc.set_footer("QT-2026-0147 | Page");
983
984 // ── Header Block ──
985 {
986 let mut p = doc.add_paragraph("").alignment(Alignment::Left);
987 p.add_run("TENSORBEE")
988 .bold(true)
989 .size(28.0)
990 .color("008B8B")
991 .font("Helvetica");
992 }
993 doc.add_paragraph("123 Innovation Drive, Suite 400")
994 .alignment(Alignment::Left);
995 doc.add_paragraph("San Francisco, CA 94105 | +1 (415) 555-0199")
996 .alignment(Alignment::Left);
997 doc.add_paragraph("")
998 .border_bottom(BorderStyle::Single, 8, "008B8B");
999
1000 // Quote meta
1001 doc.add_paragraph("");
1002 {
1003 let mut tbl = doc.add_table(4, 4).width_pct(100.0);
1004 tbl.cell(0, 0).unwrap().set_text("QUOTATION");
1005 tbl.cell(0, 0).unwrap().shading("008B8B").grid_span(2);
1006 tbl.cell(0, 2).unwrap().set_text("DATE");
1007 tbl.cell(0, 2).unwrap().shading("008B8B");
1008 tbl.cell(0, 3).unwrap().set_text("VALID UNTIL");
1009 tbl.cell(0, 3).unwrap().shading("008B8B");
1010 tbl.cell(1, 0).unwrap().set_text("Quote #:");
1011 tbl.cell(1, 1).unwrap().set_text("QT-2026-0147");
1012 tbl.cell(1, 2).unwrap().set_text("Feb 22, 2026");
1013 tbl.cell(1, 3).unwrap().set_text("Mar 22, 2026");
1014 tbl.cell(2, 0).unwrap().set_text("Prepared by:");
1015 tbl.cell(2, 1).unwrap().set_text("Walter White");
1016 tbl.cell(2, 2).unwrap().set_text("Payment:");
1017 tbl.cell(2, 3).unwrap().set_text("Net 30");
1018 tbl.cell(3, 0).unwrap().set_text("Customer:");
1019 tbl.cell(3, 1).unwrap().set_text("Meridian Dynamics LLC");
1020 tbl.cell(3, 2).unwrap().set_text("Currency:");
1021 tbl.cell(3, 3).unwrap().set_text("USD");
1022 }
1023
1024 doc.add_paragraph("");
1025
1026 // ── Bill of Materials ──
1027 {
1028 let mut p = doc.add_paragraph("");
1029 p.add_run("Bill of Materials")
1030 .bold(true)
1031 .size(16.0)
1032 .color("1A3C3C");
1033 }
1034 doc.add_paragraph("");
1035
1036 {
1037 let mut tbl = doc
1038 .add_table(10, 6)
1039 .borders(BorderStyle::Single, 2, "008B8B")
1040 .width_pct(100.0)
1041 .layout_fixed();
1042 // Headers
1043 let hdrs = [
1044 "#",
1045 "Part Number",
1046 "Description",
1047 "Qty",
1048 "Unit Price",
1049 "Total",
1050 ];
1051 for (c, h) in hdrs.iter().enumerate() {
1052 tbl.cell(0, c).unwrap().set_text(h);
1053 tbl.cell(0, c).unwrap().shading("008B8B");
1054 }
1055
1056 let items: Vec<(&str, &str, &str, &str, &str)> = vec![
1057 (
1058 "TB-GPU-A100",
1059 "NVIDIA A100 80GB GPU",
1060 "4",
1061 "$12,500.00",
1062 "$50,000.00",
1063 ),
1064 (
1065 "TB-SRV-R750",
1066 "Dell R750xa Server Chassis",
1067 "2",
1068 "$8,200.00",
1069 "$16,400.00",
1070 ),
1071 (
1072 "TB-RAM-256G",
1073 "256GB DDR5 ECC Memory Module",
1074 "8",
1075 "$890.00",
1076 "$7,120.00",
1077 ),
1078 (
1079 "TB-SSD-3840",
1080 "3.84TB NVMe U.2 SSD",
1081 "8",
1082 "$1,150.00",
1083 "$9,200.00",
1084 ),
1085 (
1086 "TB-NET-CX7",
1087 "ConnectX-7 200GbE NIC",
1088 "4",
1089 "$1,800.00",
1090 "$7,200.00",
1091 ),
1092 (
1093 "TB-SW-48P",
1094 "48-Port 100GbE Switch",
1095 "1",
1096 "$22,000.00",
1097 "$22,000.00",
1098 ),
1099 (
1100 "TB-CAB-RACK",
1101 "42U Server Rack + PDU",
1102 "1",
1103 "$4,500.00",
1104 "$4,500.00",
1105 ),
1106 (
1107 "TB-SVC-INST",
1108 "Installation & Configuration",
1109 "1",
1110 "$8,500.00",
1111 "$8,500.00",
1112 ),
1113 (
1114 "TB-SVC-SUPP",
1115 "3-Year Premium Support",
1116 "1",
1117 "$15,000.00",
1118 "$15,000.00",
1119 ),
1120 ];
1121
1122 for (i, (pn, desc, qty, unit, total)) in items.iter().enumerate() {
1123 let row = i + 1;
1124 tbl.cell(row, 0).unwrap().set_text(&format!("{}", i + 1));
1125 tbl.cell(row, 1).unwrap().set_text(pn);
1126 tbl.cell(row, 2).unwrap().set_text(desc);
1127 tbl.cell(row, 3).unwrap().set_text(qty);
1128 tbl.cell(row, 4).unwrap().set_text(unit);
1129 tbl.cell(row, 5).unwrap().set_text(total);
1130 if i % 2 == 0 {
1131 for c in 0..6 {
1132 tbl.cell(row, c).unwrap().shading("F0F8F8");
1133 }
1134 }
1135 }
1136 }
1137
1138 doc.add_paragraph("");
1139
1140 // ── Totals ──
1141 {
1142 let mut tbl = doc
1143 .add_table(4, 2)
1144 .borders(BorderStyle::Single, 2, "008B8B")
1145 .width(Length::inches(3.5))
1146 .alignment(Alignment::Right);
1147 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1148 tbl.cell(0, 1).unwrap().set_text("$139,920.00");
1149 tbl.cell(1, 0).unwrap().set_text("Shipping & Handling");
1150 tbl.cell(1, 1).unwrap().set_text("$2,500.00");
1151 tbl.cell(2, 0).unwrap().set_text("Tax (8.625%)");
1152 tbl.cell(2, 1).unwrap().set_text("$12,068.10");
1153 tbl.cell(3, 0).unwrap().set_text("TOTAL");
1154 tbl.cell(3, 0).unwrap().shading("E07020");
1155 tbl.cell(3, 1).unwrap().set_text("$154,488.10");
1156 tbl.cell(3, 1).unwrap().shading("E07020");
1157 }
1158
1159 doc.add_paragraph("");
1160
1161 // ── Terms & Conditions ──
1162 {
1163 let mut p = doc.add_paragraph("");
1164 p.add_run("Terms & Conditions")
1165 .bold(true)
1166 .size(14.0)
1167 .color("1A3C3C");
1168 }
1169 doc.add_numbered_list_item(
1170 "This quotation is valid for 30 calendar days from the date of issue.",
1171 0,
1172 );
1173 doc.add_numbered_list_item(
1174 "All prices are in USD and exclusive of applicable taxes unless stated.",
1175 0,
1176 );
1177 doc.add_numbered_list_item("Standard lead time is 4-6 weeks from PO receipt.", 0);
1178 doc.add_numbered_list_item(
1179 "Payment terms: Net 30 from invoice date. 2% discount for payment within 10 days.",
1180 0,
1181 );
1182 doc.add_numbered_list_item(
1183 "Warranty: 3-year manufacturer warranty on all hardware components.",
1184 0,
1185 );
1186 doc.add_numbered_list_item(
1187 "Returns subject to 15% restocking fee if initiated after 14 days.",
1188 0,
1189 );
1190
1191 doc.add_paragraph("");
1192 doc.add_paragraph("")
1193 .border_bottom(BorderStyle::Single, 4, "008B8B");
1194 doc.add_paragraph("");
1195 {
1196 let mut p = doc.add_paragraph("");
1197 p.add_run("Accepted by: ").bold(true);
1198 p.add_run("___________________________ Date: ___________");
1199 }
1200 {
1201 let mut p = doc.add_paragraph("");
1202 p.add_run("Print Name: ").bold(true);
1203 p.add_run("___________________________ Title: ___________");
1204 }
1205
1206 doc
1207}
1208
1209// =============================================================================
1210// 4. INVOICE — Crimson + charcoal scheme
1211// =============================================================================
1212fn generate_invoice(_samples_dir: &Path) -> Document {
1213 let mut doc = Document::new();
1214
1215 // Colors: Crimson #B22222, Charcoal #333333, Light #FAF0F0
1216 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1217 doc.set_margins(
1218 Length::inches(0.75),
1219 Length::inches(0.75),
1220 Length::inches(0.75),
1221 Length::inches(0.75),
1222 );
1223 doc.set_title("Invoice INV-2026-0392");
1224 doc.set_author("Walter White");
1225 doc.set_keywords("invoice, Tensorbee");
1226
1227 doc.set_footer("Tensorbee — Thank you for your business!");
1228
1229 // ── Company & Invoice Header ──
1230 {
1231 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1232 // Company name left, INVOICE right
1233 {
1234 let mut cell = tbl.cell(0, 0).unwrap();
1235 let mut p = cell.add_paragraph("");
1236 p.add_run("TENSORBEE")
1237 .bold(true)
1238 .size(32.0)
1239 .color("B22222")
1240 .font("Helvetica");
1241 }
1242 {
1243 let mut cell = tbl.cell(0, 1).unwrap();
1244 let mut p = cell.add_paragraph("");
1245 p.add_run("INVOICE").bold(true).size(32.0).color("333333");
1246 }
1247 tbl.cell(1, 0)
1248 .unwrap()
1249 .set_text("123 Innovation Drive, Suite 400");
1250 tbl.cell(1, 1).unwrap().set_text("Invoice #: INV-2026-0392");
1251 tbl.cell(2, 0).unwrap().set_text("San Francisco, CA 94105");
1252 tbl.cell(2, 1).unwrap().set_text("Date: February 22, 2026");
1253 tbl.cell(3, 0).unwrap().set_text("walter@tensorbee.com");
1254 tbl.cell(3, 1).unwrap().set_text("Due Date: March 24, 2026");
1255 }
1256
1257 doc.add_paragraph("")
1258 .border_bottom(BorderStyle::Thick, 8, "B22222");
1259 doc.add_paragraph("");
1260
1261 // ── Bill To / Ship To ──
1262 {
1263 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1264 tbl.cell(0, 0).unwrap().set_text("BILL TO");
1265 tbl.cell(0, 0).unwrap().shading("B22222");
1266 tbl.cell(0, 1).unwrap().set_text("SHIP TO");
1267 tbl.cell(0, 1).unwrap().shading("B22222");
1268 tbl.cell(1, 0).unwrap().set_text("Meridian Dynamics LLC");
1269 tbl.cell(1, 1).unwrap().set_text("Meridian Dynamics LLC");
1270 tbl.cell(2, 0).unwrap().set_text("456 Enterprise Blvd");
1271 tbl.cell(2, 1).unwrap().set_text("Attn: Server Room B");
1272 tbl.cell(3, 0).unwrap().set_text("Austin, TX 78701");
1273 tbl.cell(3, 1)
1274 .unwrap()
1275 .set_text("456 Enterprise Blvd, Austin, TX 78701");
1276 }
1277
1278 doc.add_paragraph("");
1279
1280 // ── Line Items ──
1281 {
1282 let mut tbl = doc
1283 .add_table(8, 5)
1284 .borders(BorderStyle::Single, 2, "B22222")
1285 .width_pct(100.0);
1286 let hdrs = ["Description", "Qty", "Unit Price", "Tax", "Amount"];
1287 for (c, h) in hdrs.iter().enumerate() {
1288 tbl.cell(0, c).unwrap().set_text(h);
1289 tbl.cell(0, c).unwrap().shading("B22222");
1290 }
1291 let items = [
1292 (
1293 "ML Infrastructure Setup — Phase 1",
1294 "1",
1295 "$45,000.00",
1296 "$3,881.25",
1297 "$48,881.25",
1298 ),
1299 (
1300 "Data Pipeline Development (160 hrs)",
1301 "160",
1302 "$225.00",
1303 "$3,105.00",
1304 "$39,105.00",
1305 ),
1306 (
1307 "GPU Cluster Configuration",
1308 "1",
1309 "$12,000.00",
1310 "$1,035.00",
1311 "$13,035.00",
1312 ),
1313 (
1314 "API Gateway Implementation",
1315 "1",
1316 "$18,500.00",
1317 "$1,595.63",
1318 "$20,095.63",
1319 ),
1320 (
1321 "Load Testing & QA (80 hrs)",
1322 "80",
1323 "$195.00",
1324 "$1,345.50",
1325 "$16,945.50",
1326 ),
1327 (
1328 "Documentation & Training",
1329 "1",
1330 "$8,000.00",
1331 "$690.00",
1332 "$8,690.00",
1333 ),
1334 (
1335 "Project Management (3 months)",
1336 "3",
1337 "$6,500.00",
1338 "$1,679.63",
1339 "$21,179.63",
1340 ),
1341 ];
1342 for (i, (desc, qty, unit, tax, amt)) in items.iter().enumerate() {
1343 let r = i + 1;
1344 tbl.cell(r, 0).unwrap().set_text(desc);
1345 tbl.cell(r, 1).unwrap().set_text(qty);
1346 tbl.cell(r, 2).unwrap().set_text(unit);
1347 tbl.cell(r, 3).unwrap().set_text(tax);
1348 tbl.cell(r, 4).unwrap().set_text(amt);
1349 if i % 2 == 0 {
1350 for c in 0..5 {
1351 tbl.cell(r, c).unwrap().shading("FAF0F0");
1352 }
1353 }
1354 }
1355 }
1356
1357 doc.add_paragraph("");
1358
1359 // ── Totals ──
1360 {
1361 let mut tbl = doc
1362 .add_table(4, 2)
1363 .borders(BorderStyle::Single, 2, "B22222")
1364 .width(Length::inches(3.0))
1365 .alignment(Alignment::Right);
1366 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1367 tbl.cell(0, 1).unwrap().set_text("$154,600.00");
1368 tbl.cell(1, 0).unwrap().set_text("Tax (8.625%)");
1369 tbl.cell(1, 1).unwrap().set_text("$13,334.25");
1370 tbl.cell(2, 0).unwrap().set_text("Discount (5%)");
1371 tbl.cell(2, 1).unwrap().set_text("-$7,730.00");
1372 tbl.cell(3, 0).unwrap().set_text("AMOUNT DUE");
1373 tbl.cell(3, 0).unwrap().shading("B22222");
1374 tbl.cell(3, 1).unwrap().set_text("$160,204.25");
1375 tbl.cell(3, 1).unwrap().shading("B22222");
1376 }
1377
1378 doc.add_paragraph("");
1379 doc.add_paragraph("");
1380
1381 // ── Payment Details ──
1382 {
1383 let mut p = doc.add_paragraph("");
1384 p.add_run("Payment Details")
1385 .bold(true)
1386 .size(14.0)
1387 .color("333333");
1388 }
1389 doc.add_paragraph("Bank: Silicon Valley Bank")
1390 .indent_left(Length::inches(0.3));
1391 doc.add_paragraph("Account: Tensorbee Inc. — 0847-2953-1120")
1392 .indent_left(Length::inches(0.3));
1393 doc.add_paragraph("Routing: 121140399")
1394 .indent_left(Length::inches(0.3));
1395 doc.add_paragraph("Swift: SVBKUS6S")
1396 .indent_left(Length::inches(0.3));
1397
1398 doc.add_paragraph("");
1399
1400 doc.add_paragraph("Please include invoice number INV-2026-0392 in the payment reference.")
1401 .shading("FAF0F0")
1402 .border_all(BorderStyle::Single, 2, "B22222");
1403
1404 doc
1405}
1406
1407// =============================================================================
1408// 5. REPORT — Forest green + earth tones, with images & hierarchical sections
1409// =============================================================================
1410fn generate_report(_samples_dir: &Path) -> Document {
1411 let mut doc = Document::new();
1412
1413 // Colors: Forest #2D5016, Sage #6B8E23, Earth #8B7355, Cream #FFFAF0
1414 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1415 doc.set_margins(
1416 Length::inches(1.0),
1417 Length::inches(1.0),
1418 Length::inches(1.0),
1419 Length::inches(1.0),
1420 );
1421 doc.set_title("Q4 2025 Environmental Impact Report");
1422 doc.set_author("Walter White");
1423 doc.set_subject("Quarterly Environmental Report");
1424 doc.set_keywords("environment, sustainability, report, Tensorbee");
1425
1426 doc.set_different_first_page(true);
1427 doc.set_first_page_header("");
1428 doc.set_header("Tensorbee — Q4 2025 Environmental Impact Report");
1429 doc.set_footer("CONFIDENTIAL — Page");
1430
1431 // ── Cover ──
1432 let cover_bg = create_sample_png(612, 792, [20, 50, 15]);
1433 doc.add_background_image(&cover_bg, "report_cover.png");
1434
1435 for _ in 0..5 {
1436 doc.add_paragraph("");
1437 }
1438 {
1439 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1440 p.add_run("Q4 2025")
1441 .bold(true)
1442 .size(48.0)
1443 .color("FFFFFF")
1444 .font("Georgia");
1445 }
1446 {
1447 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1448 p.add_run("Environmental Impact Report")
1449 .size(24.0)
1450 .color("90EE90")
1451 .font("Georgia")
1452 .italic(true);
1453 }
1454 doc.add_paragraph("");
1455 {
1456 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1457 p.add_run("Tensorbee — Sustainability Division")
1458 .size(14.0)
1459 .color("C0C0C0");
1460 }
1461 {
1462 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1463 p.add_run("Prepared by Walter White, Chief Sustainability Officer")
1464 .size(11.0)
1465 .color("AAAAAA");
1466 }
1467
1468 // ── TOC ──
1469 doc.add_paragraph("").page_break_before(true);
1470 doc.insert_toc(doc.content_count(), 3);
1471
1472 // ── Section 1: Executive Overview ──
1473 doc.add_paragraph("").page_break_before(true);
1474 doc.add_paragraph("Executive Overview").style("Heading1");
1475 doc.add_paragraph(
1476 "This report presents Tensorbee's environmental performance for Q4 2025. Our \
1477 sustainability initiatives have yielded a 34% reduction in carbon emissions compared \
1478 to Q4 2024, exceeding our target of 25%. Key achievements include the transition to \
1479 100% renewable energy in our primary data centers and the launch of our carbon offset \
1480 marketplace.",
1481 )
1482 .first_line_indent(Length::inches(0.3));
1483
1484 // Image: performance chart
1485 let chart_img = create_chart_png(400, 200);
1486 doc.add_paragraph("");
1487 doc.add_picture(
1488 &chart_img,
1489 "performance_chart.png",
1490 Length::inches(5.0),
1491 Length::inches(2.5),
1492 )
1493 .alignment(Alignment::Center);
1494 {
1495 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1496 p.add_run("Figure 1: Quarterly Carbon Emissions (tonnes CO2e)")
1497 .italic(true)
1498 .size(9.0)
1499 .color("666666");
1500 }
1501
1502 // ── Section 2: Energy Consumption ──
1503 doc.add_paragraph("").page_break_before(true);
1504 doc.add_paragraph("Energy Consumption").style("Heading1");
1505
1506 doc.add_paragraph("Data Center Operations")
1507 .style("Heading2");
1508 doc.add_paragraph(
1509 "Our three primary data centers consumed a combined 4.2 GWh in Q4 2025, \
1510 a 12% reduction from Q3 2025 driven by improved cooling efficiency and \
1511 server consolidation.",
1512 );
1513
1514 // Data table
1515 {
1516 let mut tbl = doc
1517 .add_table(5, 4)
1518 .borders(BorderStyle::Single, 4, "2D5016")
1519 .width_pct(100.0);
1520 let hdrs = ["Data Center", "Capacity (MW)", "Usage (GWh)", "PUE"];
1521 for (c, h) in hdrs.iter().enumerate() {
1522 tbl.cell(0, c).unwrap().set_text(h);
1523 tbl.cell(0, c).unwrap().shading("2D5016");
1524 }
1525 let rows = [
1526 ("San Francisco (Primary)", "3.2", "1.8", "1.12"),
1527 ("Dublin (EU)", "2.1", "1.2", "1.18"),
1528 ("Singapore (APAC)", "1.8", "1.2", "1.24"),
1529 ("TOTAL", "7.1", "4.2", "1.17 avg"),
1530 ];
1531 for (i, (dc, cap, use_, pue)) in rows.iter().enumerate() {
1532 tbl.cell(i + 1, 0).unwrap().set_text(dc);
1533 tbl.cell(i + 1, 1).unwrap().set_text(cap);
1534 tbl.cell(i + 1, 2).unwrap().set_text(use_);
1535 tbl.cell(i + 1, 3).unwrap().set_text(pue);
1536 if i == 3 {
1537 for c in 0..4 {
1538 tbl.cell(i + 1, c).unwrap().shading("E2EFDA");
1539 }
1540 }
1541 }
1542 }
1543
1544 doc.add_paragraph("");
1545
1546 doc.add_paragraph("Renewable Energy Mix").style("Heading2");
1547 doc.add_paragraph("Breakdown of energy sources across all facilities:");
1548
1549 doc.add_bullet_list_item("Solar PV: 42% (1.76 GWh)", 0);
1550 doc.add_bullet_list_item("Wind Power Purchase Agreements: 38% (1.60 GWh)", 0);
1551 doc.add_bullet_list_item("Hydroelectric: 12% (0.50 GWh)", 0);
1552 doc.add_bullet_list_item("Grid (non-renewable): 8% (0.34 GWh)", 0);
1553
1554 // ── Section 3: Water & Waste ──
1555 doc.add_paragraph("Water & Waste Management")
1556 .style("Heading1");
1557
1558 doc.add_paragraph("Water Usage").style("Heading2");
1559 doc.add_paragraph(
1560 "Total water consumption was 12.4 million gallons, a 15% reduction from Q3. \
1561 Our closed-loop cooling systems now recycle 78% of water used in cooling operations.",
1562 );
1563
1564 doc.add_paragraph("Waste Reduction").style("Heading2");
1565 doc.add_paragraph("E-waste management results for Q4:")
1566 .keep_with_next(true);
1567 {
1568 let mut tbl = doc
1569 .add_table(4, 3)
1570 .borders(BorderStyle::Single, 2, "6B8E23")
1571 .width_pct(80.0);
1572 tbl.cell(0, 0).unwrap().set_text("Category");
1573 tbl.cell(0, 0).unwrap().shading("6B8E23");
1574 tbl.cell(0, 1).unwrap().set_text("Weight (kg)");
1575 tbl.cell(0, 1).unwrap().shading("6B8E23");
1576 tbl.cell(0, 2).unwrap().set_text("Recycled %");
1577 tbl.cell(0, 2).unwrap().shading("6B8E23");
1578 let rows = [
1579 ("Server Hardware", "2,450", "94%"),
1580 ("Networking Equipment", "820", "91%"),
1581 ("Storage Media", "340", "99%"),
1582 ];
1583 for (i, (cat, wt, pct)) in rows.iter().enumerate() {
1584 tbl.cell(i + 1, 0).unwrap().set_text(cat);
1585 tbl.cell(i + 1, 1).unwrap().set_text(wt);
1586 tbl.cell(i + 1, 2).unwrap().set_text(pct);
1587 }
1588 }
1589
1590 // ── Section 4: Initiatives ──
1591 doc.add_paragraph("").page_break_before(true);
1592 doc.add_paragraph("Q1 2026 Initiatives").style("Heading1");
1593
1594 doc.add_paragraph("Planned Programs").style("Heading2");
1595 doc.add_numbered_list_item(
1596 "Deploy on-site battery storage at SF data center (2 MWh capacity)",
1597 0,
1598 );
1599 doc.add_numbered_list_item("Pilot immersion cooling in Dublin facility", 0);
1600 doc.add_numbered_list_item("Launch employee commute carbon offset program", 0);
1601 doc.add_numbered_list_item("Achieve ISO 14001 certification for Singapore facility", 0);
1602
1603 doc.add_paragraph("Investment Targets").style("Heading2");
1604 doc.add_paragraph("Sustainability CapEx allocation for FY2026:")
1605 .keep_with_next(true);
1606 {
1607 let mut tbl = doc
1608 .add_table(5, 2)
1609 .borders(BorderStyle::Single, 4, "2D5016");
1610 tbl.cell(0, 0).unwrap().set_text("Initiative");
1611 tbl.cell(0, 0).unwrap().shading("2D5016");
1612 tbl.cell(0, 1).unwrap().set_text("Budget");
1613 tbl.cell(0, 1).unwrap().shading("2D5016");
1614 let rows = [
1615 ("Battery Storage", "$1.2M"),
1616 ("Immersion Cooling Pilot", "$800K"),
1617 ("Solar Panel Expansion", "$2.1M"),
1618 ("Carbon Credits", "$500K"),
1619 ];
1620 for (i, (init, budget)) in rows.iter().enumerate() {
1621 tbl.cell(i + 1, 0).unwrap().set_text(init);
1622 tbl.cell(i + 1, 1).unwrap().set_text(budget);
1623 if i % 2 == 0 {
1624 tbl.cell(i + 1, 0).unwrap().shading("FFFAF0");
1625 tbl.cell(i + 1, 1).unwrap().shading("FFFAF0");
1626 }
1627 }
1628 }
1629
1630 // Image: sustainability roadmap
1631 let roadmap_img = create_sample_png(500, 100, [40, 80, 30]);
1632 doc.add_paragraph("");
1633 doc.add_picture(
1634 &roadmap_img,
1635 "roadmap.png",
1636 Length::inches(6.0),
1637 Length::inches(1.2),
1638 )
1639 .alignment(Alignment::Center);
1640 {
1641 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1642 p.add_run("Figure 2: 2026 Sustainability Roadmap")
1643 .italic(true)
1644 .size(9.0)
1645 .color("666666");
1646 }
1647
1648 doc.add_paragraph("");
1649 doc.add_paragraph(
1650 "For questions about this report, contact Walter White at sustainability@tensorbee.com.",
1651 )
1652 .shading("FFFAF0")
1653 .border_all(BorderStyle::Single, 2, "2D5016");
1654
1655 doc
1656}
1657
1658// =============================================================================
1659// 6. LETTER — Slate blue + silver scheme
1660// =============================================================================
1661fn generate_letter(_samples_dir: &Path) -> Document {
1662 let mut doc = Document::new();
1663
1664 // Colors: Slate #4A5568, Blue accent #3182CE
1665 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1666 doc.set_margins(
1667 Length::inches(1.25),
1668 Length::inches(1.25),
1669 Length::inches(1.25),
1670 Length::inches(1.25),
1671 );
1672 doc.set_title("Business Letter");
1673 doc.set_author("Walter White");
1674
1675 // Banner header using raw XML
1676 let logo_img = create_logo_png(220, 48);
1677 let banner = build_header_banner_xml(
1678 "rId1",
1679 &BannerOpts {
1680 bg_color: "4A5568",
1681 banner_width: 7772400,
1682 banner_height: 731520, // ~0.8"
1683 logo_width: 2011680,
1684 logo_height: 438912,
1685 logo_x_offset: 295125,
1686 logo_y_offset: 146304,
1687 },
1688 );
1689 doc.set_raw_header_with_images(
1690 banner,
1691 &[("rId1", &logo_img, "logo.png")],
1692 rdocx_oxml::header_footer::HdrFtrType::Default,
1693 );
1694 doc.set_margins(
1695 Length::twips(2000),
1696 Length::twips(1800),
1697 Length::twips(1440),
1698 Length::twips(1800),
1699 );
1700 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
1701
1702 // Footer with contact
1703 doc.set_footer(
1704 "Tensorbee | 123 Innovation Drive, Suite 400 | San Francisco, CA 94105 | tensorbee.com",
1705 );
1706
1707 // ── Sender Address ──
1708 doc.add_paragraph("Walter White");
1709 doc.add_paragraph("Chief Executive Officer");
1710 doc.add_paragraph("Tensorbee");
1711 doc.add_paragraph("123 Innovation Drive, Suite 400");
1712 doc.add_paragraph("San Francisco, CA 94105");
1713 doc.add_paragraph("");
1714 doc.add_paragraph("February 22, 2026");
1715
1716 doc.add_paragraph("");
1717
1718 // ── Recipient ──
1719 doc.add_paragraph("Ms. Sarah Chen");
1720 doc.add_paragraph("VP of Engineering");
1721 doc.add_paragraph("Meridian Dynamics LLC");
1722 doc.add_paragraph("456 Enterprise Boulevard");
1723 doc.add_paragraph("Austin, TX 78701");
1724
1725 doc.add_paragraph("");
1726
1727 // ── Subject ──
1728 {
1729 let mut p = doc.add_paragraph("");
1730 p.add_run("Re: Strategic Technology Partnership — Phase 2 Expansion")
1731 .bold(true);
1732 }
1733
1734 doc.add_paragraph("");
1735
1736 // ── Body ──
1737 doc.add_paragraph("Dear Ms. Chen,");
1738 doc.add_paragraph("");
1739
1740 doc.add_paragraph(
1741 "I am writing to express Tensorbee's enthusiasm for expanding our strategic technology \
1742 partnership with Meridian Dynamics. Following the successful completion of Phase 1, \
1743 which delivered a 40% improvement in your ML inference pipeline throughput, we believe \
1744 the foundation is firmly established for an ambitious Phase 2 engagement.",
1745 )
1746 .first_line_indent(Length::inches(0.5));
1747
1748 doc.add_paragraph(
1749 "During our recent executive review, your team highlighted three priority areas for \
1750 the next phase of collaboration:",
1751 )
1752 .first_line_indent(Length::inches(0.5));
1753
1754 doc.add_numbered_list_item(
1755 "Real-time anomaly detection for your financial transaction monitoring system, \
1756 targeting sub-100ms latency at 50,000 transactions per second.",
1757 0,
1758 );
1759 doc.add_numbered_list_item(
1760 "Federated learning infrastructure to enable model training across your distributed \
1761 data centers without centralizing sensitive financial data.",
1762 0,
1763 );
1764 doc.add_numbered_list_item(
1765 "MLOps automation to reduce your model deployment cycle from the current 5 days \
1766 to under 4 hours.",
1767 0,
1768 );
1769
1770 doc.add_paragraph(
1771 "Our engineering team has prepared a detailed technical proposal addressing each of \
1772 these areas. We have allocated a dedicated team of eight senior engineers, led by \
1773 our CTO, to ensure continuity with the Phase 1 team your organization has already \
1774 built a productive working relationship with.",
1775 )
1776 .first_line_indent(Length::inches(0.5));
1777
1778 doc.add_paragraph(
1779 "I would welcome the opportunity to present our Phase 2 proposal in person. My \
1780 assistant will reach out to coordinate a meeting at your convenience during the \
1781 first week of March.",
1782 )
1783 .first_line_indent(Length::inches(0.5));
1784
1785 doc.add_paragraph("");
1786
1787 doc.add_paragraph("Warm regards,");
1788 doc.add_paragraph("");
1789 doc.add_paragraph("");
1790
1791 // Signature line
1792 doc.add_paragraph("")
1793 .border_bottom(BorderStyle::Single, 4, "4A5568");
1794 {
1795 let mut p = doc.add_paragraph("");
1796 p.add_run("Walter White").bold(true).size(12.0);
1797 }
1798 {
1799 let mut p = doc.add_paragraph("");
1800 p.add_run("Chief Executive Officer, Tensorbee")
1801 .italic(true)
1802 .size(10.0)
1803 .color("666666");
1804 }
1805 {
1806 let mut p = doc.add_paragraph("");
1807 p.add_run("walter@tensorbee.com | +1 (415) 555-0199")
1808 .size(10.0)
1809 .color("888888");
1810 }
1811
1812 doc
1813}Sourcepub fn set_gutter(&mut self, gutter: Length)
pub fn set_gutter(&mut self, gutter: Length)
Set the gutter margin.
Examples found in repository?
97fn generate_feature_showcase(_samples_dir: &Path) -> Document {
98 let mut doc = Document::new();
99
100 // ── Page Setup & Metadata ──
101 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
102 doc.set_margins(
103 Length::inches(1.0),
104 Length::inches(1.0),
105 Length::inches(1.0),
106 Length::inches(1.0),
107 );
108 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
109 doc.set_gutter(Length::twips(0));
110 doc.set_title("rdocx Feature Showcase");
111 doc.set_author("rdocx Sample Generator");
112 doc.set_subject("Comprehensive feature demonstration");
113 doc.set_keywords("rdocx, docx, rust, sample, showcase");
114
115 // Headers & Footers with different first page
116 doc.set_different_first_page(true);
117 doc.set_first_page_header("rdocx");
118 doc.set_first_page_footer("Feature Showcase — Cover Page");
119 doc.set_header("rdocx Feature Showcase");
120 doc.set_footer("Generated by rdocx");
121
122 // ── COVER PAGE ──
123 let bg = create_sample_png(612, 792, [20, 45, 90]);
124 doc.add_background_image(&bg, "cover_bg.png");
125
126 for _ in 0..3 {
127 doc.add_paragraph("");
128 }
129 {
130 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
131 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
132 }
133 {
134 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
135 p.add_run("Complete Feature Showcase")
136 .size(28.0)
137 .color("FFFFFF")
138 .italic(true);
139 }
140 doc.add_paragraph("");
141 {
142 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
143 p.add_run("Every feature of the rdocx Rust library")
144 .size(13.0)
145 .color("B0C4DE");
146 }
147 {
148 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
149 p.add_run("demonstrated in a single document.")
150 .size(13.0)
151 .color("B0C4DE");
152 }
153
154 // ── TABLE OF CONTENTS ──
155 doc.add_paragraph("").page_break_before(true);
156 doc.insert_toc(doc.content_count(), 3);
157
158 // ── SECTION 1: TEXT FORMATTING ──
159 doc.add_paragraph("").page_break_before(true);
160 doc.add_paragraph("1. Text Formatting").style("Heading1");
161
162 // Paragraph Alignment
163 doc.add_paragraph("1.1 Paragraph Alignment")
164 .style("Heading2");
165 doc.add_paragraph("Left-aligned paragraph (default).")
166 .alignment(Alignment::Left);
167 doc.add_paragraph("Center-aligned paragraph.")
168 .alignment(Alignment::Center);
169 doc.add_paragraph("Right-aligned paragraph.")
170 .alignment(Alignment::Right);
171 doc.add_paragraph(
172 "Justified paragraph — this text is long enough to demonstrate how justified alignment \
173 distributes extra space across word gaps so lines fill the full width of the text area.",
174 )
175 .alignment(Alignment::Justify);
176
177 doc.add_paragraph("");
178
179 // Run Formatting
180 doc.add_paragraph("1.2 Run Formatting").style("Heading2");
181 {
182 let mut p = doc.add_paragraph("");
183 p.add_run("Bold").bold(true);
184 p.add_run(" | ");
185 p.add_run("Italic").italic(true);
186 p.add_run(" | ");
187 p.add_run("Bold Italic").bold(true).italic(true);
188 p.add_run(" | ");
189 p.add_run("Underline").underline(true);
190 p.add_run(" | ");
191 p.add_run("Strikethrough").strike(true);
192 p.add_run(" | ");
193 p.add_run("Double Strike").double_strike(true);
194 }
195 {
196 let mut p = doc.add_paragraph("");
197 p.add_run("Red").color("FF0000");
198 p.add_run(" | ");
199 p.add_run("Blue").color("0000FF");
200 p.add_run(" | ");
201 p.add_run("Green").color("00AA00");
202 p.add_run(" | ");
203 p.add_run("Highlighted").highlight("FFFF00");
204 }
205 {
206 let mut p = doc.add_paragraph("");
207 p.add_run("8pt").size(8.0);
208 p.add_run(" | ");
209 p.add_run("11pt").size(11.0);
210 p.add_run(" | ");
211 p.add_run("16pt").size(16.0);
212 p.add_run(" | ");
213 p.add_run("24pt").size(24.0);
214 }
215 {
216 let mut p = doc.add_paragraph("");
217 p.add_run("Arial").font("Arial");
218 p.add_run(" | ");
219 p.add_run("Times New Roman").font("Times New Roman");
220 p.add_run(" | ");
221 p.add_run("Courier New").font("Courier New");
222 }
223 {
224 let mut p = doc.add_paragraph("");
225 p.add_run("H");
226 p.add_run("2").subscript();
227 p.add_run("O (subscript) | E=mc");
228 p.add_run("2").superscript();
229 p.add_run(" (superscript)");
230 }
231 {
232 let mut p = doc.add_paragraph("");
233 p.add_run("ALL CAPS").all_caps(true);
234 p.add_run(" | ");
235 p.add_run("Small Caps").small_caps(true);
236 p.add_run(" | ");
237 p.add_run("Expanded").character_spacing(Length::twips(40));
238 p.add_run(" | ");
239 p.add_run("Hidden text (not visible)").hidden(true);
240 }
241
242 doc.add_paragraph("");
243
244 // Underline Styles
245 doc.add_paragraph("1.3 Underline Styles").style("Heading2");
246 {
247 let mut p = doc.add_paragraph("");
248 p.add_run("Single ")
249 .underline_style(rdocx::UnderlineStyle::Single);
250 p.add_run("Double ")
251 .underline_style(rdocx::UnderlineStyle::Double);
252 p.add_run("Thick ")
253 .underline_style(rdocx::UnderlineStyle::Thick);
254 p.add_run("Dotted ")
255 .underline_style(rdocx::UnderlineStyle::Dotted);
256 p.add_run("Dash ")
257 .underline_style(rdocx::UnderlineStyle::Dash);
258 p.add_run("Wave ")
259 .underline_style(rdocx::UnderlineStyle::Wave);
260 }
261
262 // ── SECTION 2: PARAGRAPH FORMATTING ──
263 doc.add_paragraph("").page_break_before(true);
264 doc.add_paragraph("2. Paragraph Formatting")
265 .style("Heading1");
266
267 doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
268 doc.add_paragraph("Paragraph with light green shading")
269 .shading("E2EFDA");
270 doc.add_paragraph("Paragraph with bottom border")
271 .border_bottom(BorderStyle::Single, 6, "2E75B6");
272 doc.add_paragraph("Paragraph with all borders (red)")
273 .border_all(BorderStyle::Single, 4, "FF0000");
274
275 doc.add_paragraph("2.2 Indentation").style("Heading2");
276 doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
277 .indent_left(Length::inches(1.0))
278 .hanging_indent(Length::inches(0.5));
279 doc.add_paragraph("0.5-inch first-line indent on this paragraph")
280 .first_line_indent(Length::inches(0.5));
281 doc.add_paragraph("Both left and right indent (0.75 inches each)")
282 .indent_left(Length::inches(0.75))
283 .indent_right(Length::inches(0.75));
284
285 doc.add_paragraph("2.3 Spacing & Line Height")
286 .style("Heading2");
287 doc.add_paragraph("Extra space before (24pt) and after (12pt)")
288 .space_before(Length::pt(24.0))
289 .space_after(Length::pt(12.0));
290 doc.add_paragraph(
291 "Double line spacing paragraph. This text should have extra vertical space between \
292 lines to demonstrate line_spacing_multiple(2.0).",
293 )
294 .line_spacing_multiple(2.0);
295 doc.add_paragraph("Exact 20pt line spacing (fixed height).")
296 .line_spacing(20.0);
297
298 doc.add_paragraph("2.4 Pagination Controls")
299 .style("Heading2");
300 doc.add_paragraph("keep_with_next — this paragraph stays with the next")
301 .keep_with_next(true);
302 doc.add_paragraph("(This paragraph was kept with the one above.)");
303 doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
304 .keep_together(true);
305 doc.add_paragraph("widow_control — prevents widow/orphan lines")
306 .widow_control(true);
307
308 // ── SECTION 3: LISTS ──
309 doc.add_paragraph("").page_break_before(true);
310 doc.add_paragraph("3. Lists").style("Heading1");
311
312 doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
313 doc.add_bullet_list_item("First item", 0);
314 doc.add_bullet_list_item("Second item", 0);
315 doc.add_bullet_list_item("Nested level 1", 1);
316 doc.add_bullet_list_item("Nested level 2", 2);
317 doc.add_bullet_list_item("Back to level 1", 1);
318 doc.add_bullet_list_item("Third item", 0);
319
320 doc.add_paragraph("");
321
322 doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
323 doc.add_numbered_list_item("First numbered item", 0);
324 doc.add_numbered_list_item("Second numbered item", 0);
325 doc.add_numbered_list_item("Sub-item A", 1);
326 doc.add_numbered_list_item("Sub-item B", 1);
327 doc.add_numbered_list_item("Third numbered item", 0);
328
329 // ── SECTION 4: TAB STOPS ──
330 doc.add_paragraph("");
331 doc.add_paragraph("4. Tab Stops").style("Heading1");
332
333 doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
334 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
335 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
336 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
337 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
338 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
339
340 doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
341 doc.add_paragraph("Item\t\tPrice")
342 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
343 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
344 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
345 doc.add_paragraph("Widget\t\t$19.99")
346 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
347 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
348 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
349 doc.add_paragraph("Gadget\t\t$249.50")
350 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
351 .add_tab_stop_with_leader(
352 TabAlignment::Right,
353 Length::inches(4.0),
354 TabLeader::Underscore,
355 )
356 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
357
358 // ── SECTION 5: TABLES ──
359 doc.add_paragraph("").page_break_before(true);
360 doc.add_paragraph("5. Tables").style("Heading1");
361
362 doc.add_paragraph("5.1 Basic Table").style("Heading2");
363 {
364 let mut tbl = doc
365 .add_table(4, 3)
366 .borders(BorderStyle::Single, 4, "000000");
367 for col in 0..3 {
368 tbl.cell(0, col).unwrap().shading("2E75B6");
369 }
370 tbl.cell(0, 0).unwrap().set_text("Name");
371 tbl.cell(0, 1).unwrap().set_text("Role");
372 tbl.cell(0, 2).unwrap().set_text("Location");
373 tbl.cell(1, 0).unwrap().set_text("Walter White");
374 tbl.cell(1, 1).unwrap().set_text("CEO");
375 tbl.cell(1, 2).unwrap().set_text("Albuquerque");
376 tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
377 tbl.cell(2, 1).unwrap().set_text("CTO");
378 tbl.cell(2, 2).unwrap().set_text("Remote");
379 tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
380 tbl.cell(3, 1).unwrap().set_text("Security");
381 tbl.cell(3, 2).unwrap().set_text("Washington");
382 }
383
384 doc.add_paragraph("");
385
386 doc.add_paragraph("5.2 Column Span & Cell Shading")
387 .style("Heading2");
388 {
389 let mut tbl = doc
390 .add_table(3, 4)
391 .borders(BorderStyle::Single, 4, "000000")
392 .width_pct(100.0);
393 tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
394 tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
395 tbl.cell(1, 0).unwrap().set_text("Region");
396 tbl.cell(1, 0).unwrap().shading("D6E4F0");
397 tbl.cell(1, 1).unwrap().set_text("Q1");
398 tbl.cell(1, 1).unwrap().shading("D6E4F0");
399 tbl.cell(1, 2).unwrap().set_text("Q2");
400 tbl.cell(1, 2).unwrap().shading("D6E4F0");
401 tbl.cell(1, 3).unwrap().set_text("Total");
402 tbl.cell(1, 3).unwrap().shading("D6E4F0");
403 tbl.cell(2, 0).unwrap().set_text("Americas");
404 tbl.cell(2, 1).unwrap().set_text("$2.4M");
405 tbl.cell(2, 2).unwrap().set_text("$2.7M");
406 tbl.cell(2, 3).unwrap().set_text("$5.1M");
407 }
408
409 doc.add_paragraph("");
410
411 doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
412 {
413 let mut tbl = doc
414 .add_table(4, 3)
415 .borders(BorderStyle::Single, 4, "000000");
416 tbl.cell(0, 0).unwrap().set_text("Category");
417 tbl.cell(0, 0).unwrap().shading("E2EFDA");
418 tbl.cell(0, 1).unwrap().set_text("Item");
419 tbl.cell(0, 1).unwrap().shading("E2EFDA");
420 tbl.cell(0, 2).unwrap().set_text("Price");
421 tbl.cell(0, 2).unwrap().shading("E2EFDA");
422 tbl.cell(1, 0).unwrap().set_text("Hardware");
423 tbl.cell(1, 0).unwrap().v_merge_restart();
424 tbl.cell(1, 1).unwrap().set_text("Laptop");
425 tbl.cell(1, 2).unwrap().set_text("$1,200");
426 tbl.cell(2, 0).unwrap().v_merge_continue();
427 tbl.cell(2, 1).unwrap().set_text("Monitor");
428 tbl.cell(2, 2).unwrap().set_text("$450");
429 tbl.cell(3, 0).unwrap().set_text("Software");
430 tbl.cell(3, 1).unwrap().set_text("IDE License");
431 tbl.cell(3, 2).unwrap().set_text("$200/yr");
432 }
433
434 doc.add_paragraph("");
435
436 doc.add_paragraph("5.4 Nested Table").style("Heading2");
437 {
438 let mut tbl = doc
439 .add_table(2, 2)
440 .borders(BorderStyle::Single, 6, "2E75B6");
441 tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
442 tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
443 tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
444 {
445 let mut cell = tbl.cell(1, 1).unwrap();
446 cell.set_text("Nested table below:");
447 let mut nested = cell
448 .add_table(2, 2)
449 .borders(BorderStyle::Single, 2, "FF6600");
450 nested.cell(0, 0).unwrap().set_text("A");
451 nested.cell(0, 1).unwrap().set_text("B");
452 nested.cell(1, 0).unwrap().set_text("C");
453 nested.cell(1, 1).unwrap().set_text("D");
454 }
455 }
456
457 doc.add_paragraph("");
458
459 doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
460 .style("Heading2");
461 {
462 let mut tbl = doc
463 .add_table(2, 3)
464 .borders(BorderStyle::Single, 4, "666666");
465 tbl.row(0).unwrap().height(Length::pt(50.0)).header();
466 tbl.cell(0, 0).unwrap().set_text("Top");
467 tbl.cell(0, 0)
468 .unwrap()
469 .vertical_alignment(VerticalAlignment::Top)
470 .shading("FFF2CC");
471 tbl.cell(0, 1).unwrap().set_text("Center");
472 tbl.cell(0, 1)
473 .unwrap()
474 .vertical_alignment(VerticalAlignment::Center)
475 .shading("D9E2F3");
476 tbl.cell(0, 2).unwrap().set_text("Bottom");
477 tbl.cell(0, 2)
478 .unwrap()
479 .vertical_alignment(VerticalAlignment::Bottom)
480 .shading("E2EFDA");
481 tbl.row(1).unwrap().cant_split();
482 tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
483 tbl.cell(1, 0).unwrap().no_wrap();
484 tbl.cell(1, 1).unwrap().set_text("Fixed width");
485 tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
486 tbl.cell(1, 2).unwrap().set_text("Normal");
487 }
488
489 // ── SECTION 6: IMAGES ──
490 doc.add_paragraph("").page_break_before(true);
491 doc.add_paragraph("6. Images").style("Heading1");
492
493 doc.add_paragraph("6.1 Inline Image").style("Heading2");
494 doc.add_paragraph("A blue gradient image below:");
495 let img = create_sample_png(200, 50, [0, 80, 200]);
496 doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
497
498 doc.add_paragraph("");
499 doc.add_paragraph("6.2 Header Image").style("Heading2");
500 let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
501 doc.set_header_image(
502 &hdr_img,
503 "header_logo.png",
504 Length::inches(2.0),
505 Length::inches(0.2),
506 );
507 doc.add_paragraph("The header now contains an inline image (check the top of this page).");
508
509 doc.add_paragraph("");
510 doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
511
512 // ── SECTION 7: CONTENT MANIPULATION ──
513 doc.add_paragraph("").page_break_before(true);
514 doc.add_paragraph("7. Content Manipulation")
515 .style("Heading1");
516
517 doc.add_paragraph("7.1 Placeholder Replacement")
518 .style("Heading2");
519 doc.add_paragraph("Customer: {{customer}}");
520 doc.add_paragraph("Date: {{date}}");
521 doc.add_paragraph("Reference: {{ref_number}}");
522 {
523 let mut tbl = doc
524 .add_table(2, 2)
525 .borders(BorderStyle::Single, 4, "000000");
526 tbl.cell(0, 0).unwrap().set_text("Project");
527 tbl.cell(0, 0).unwrap().shading("D6E4F0");
528 tbl.cell(0, 1).unwrap().set_text("{{project}}");
529 tbl.cell(1, 0).unwrap().set_text("Status");
530 tbl.cell(1, 0).unwrap().shading("D6E4F0");
531 tbl.cell(1, 1).unwrap().set_text("{{status}}");
532 }
533
534 let mut replacements = HashMap::new();
535 replacements.insert("{{customer}}", "Tensorbee Inc.");
536 replacements.insert("{{date}}", "February 22, 2026");
537 replacements.insert("{{ref_number}}", "TB-2026-001");
538 replacements.insert("{{project}}", "Infrastructure Upgrade");
539 replacements.insert("{{status}}", "In Progress");
540 let n = doc.replace_all(&replacements);
541 doc.add_paragraph(&format!("({n} placeholders replaced above)"));
542
543 doc.add_paragraph("");
544
545 doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
546 doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
547 let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
548 doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
549
550 doc.add_paragraph("");
551
552 doc.add_paragraph("7.3 Content Insertion").style("Heading2");
553 doc.add_paragraph("Section A: First content.");
554 doc.add_paragraph("Section C: Third content.");
555 if let Some(idx) = doc.find_content_index("Section C") {
556 doc.insert_paragraph(
557 idx,
558 "Section B: Inserted between A and C via find_content_index().",
559 );
560 }
561
562 // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
563 doc.add_paragraph("").section_break(SectionBreak::NextPage);
564 doc.add_paragraph("8. Section Breaks & Orientation")
565 .style("Heading1");
566 doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
567
568 {
569 let mut tbl = doc
570 .add_table(3, 7)
571 .borders(BorderStyle::Single, 4, "2E75B6");
572 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
573 for (c, h) in headers.iter().enumerate() {
574 tbl.cell(0, c).unwrap().set_text(h);
575 tbl.cell(0, c).unwrap().shading("2E75B6");
576 }
577 let data = [
578 [
579 "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
580 ],
581 [
582 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
583 ],
584 ];
585 for (r, row) in data.iter().enumerate() {
586 for (c, v) in row.iter().enumerate() {
587 tbl.cell(r + 1, c).unwrap().set_text(v);
588 }
589 }
590 }
591
592 doc.add_paragraph("")
593 .section_break(SectionBreak::NextPage)
594 .section_landscape();
595
596 // ── SECTION 9: CUSTOM STYLES ──
597 doc.add_paragraph("9. Custom Styles").style("Heading1");
598 doc.add_style(
599 StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
600 .based_on("Normal")
601 .paragraph_properties(rdocx_oxml::properties::CT_PPr {
602 shading: Some(rdocx_oxml::properties::CT_Shd {
603 val: "clear".to_string(),
604 color: None,
605 fill: Some("FFF2CC".to_string()),
606 }),
607 ..Default::default()
608 })
609 .run_properties(rdocx_oxml::properties::CT_RPr {
610 bold: Some(true),
611 color: Some("C45911".to_string()),
612 ..Default::default()
613 }),
614 );
615 doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
616 .style("CustomHighlight");
617
618 doc.add_paragraph("");
619
620 // ── SECTION 10: DOCUMENT INTELLIGENCE ──
621 doc.add_paragraph("10. Document Intelligence API")
622 .style("Heading1");
623
624 let wc = doc.word_count();
625 let hc = doc.headings().len();
626 let ic = doc.images().len();
627 let lc = doc.links().len();
628 doc.add_paragraph(&format!("Word count: {wc}"));
629 doc.add_paragraph(&format!("Heading count: {hc}"));
630 doc.add_paragraph(&format!("Image count: {ic}"));
631 doc.add_paragraph(&format!("Link count: {lc}"));
632
633 let outline = doc.document_outline();
634 doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
635
636 let issues = doc.audit_accessibility();
637 doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
638 for issue in &issues {
639 doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
640 }
641
642 // ── SECTION 11: DOCUMENT MERGING ──
643 doc.add_paragraph("");
644 doc.add_paragraph("11. Document Merging").style("Heading1");
645 {
646 let mut other = Document::new();
647 other.add_paragraph("This paragraph was merged from another document using append().");
648 doc.append(&other);
649 }
650
651 doc.add_paragraph("");
652
653 // ── SUMMARY ──
654 doc.add_paragraph("Summary of Demonstrated Features")
655 .style("Heading1");
656 let features = [
657 "Page setup: size, margins, header/footer distance, gutter",
658 "Document metadata: title, author, subject, keywords",
659 "Headers and footers: text, images, different first page",
660 "Background images (full-page behind text)",
661 "Table of Contents generation with bookmarks",
662 "Text formatting: bold, italic, underline styles, strike, color, size, font",
663 "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
664 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
665 "Pagination controls: keep-with-next, keep-together, widow control",
666 "Bullet and numbered lists with nesting",
667 "Tab stops with dot/underscore/hyphen leaders",
668 "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
669 "Row properties: header rows, exact height, cant-split",
670 "Cell properties: width, no-wrap, vertical alignment",
671 "Inline images and header images",
672 "Placeholder replacement (single and batch)",
673 "Regex-based find and replace",
674 "Content insertion at specific positions",
675 "Section breaks with mixed portrait/landscape",
676 "Custom paragraph and character styles",
677 "Document intelligence: word count, headings, outline, images, links",
678 "Accessibility audit",
679 "Document merging (append)",
680 "Export: DOCX, PDF, HTML, Markdown",
681 ];
682 for f in &features {
683 doc.add_bullet_list_item(f, 0);
684 }
685 doc.add_paragraph("");
686 doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
687 .alignment(Alignment::Center)
688 .shading("E2EFDA")
689 .border_all(BorderStyle::Single, 2, "00AA00");
690
691 doc
692}Sourcepub fn set_different_first_page(&mut self, val: bool)
pub fn set_different_first_page(&mut self, val: bool)
Enable or disable different first page header/footer.
Examples found in repository?
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
97fn generate_feature_showcase(_samples_dir: &Path) -> Document {
98 let mut doc = Document::new();
99
100 // ── Page Setup & Metadata ──
101 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
102 doc.set_margins(
103 Length::inches(1.0),
104 Length::inches(1.0),
105 Length::inches(1.0),
106 Length::inches(1.0),
107 );
108 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
109 doc.set_gutter(Length::twips(0));
110 doc.set_title("rdocx Feature Showcase");
111 doc.set_author("rdocx Sample Generator");
112 doc.set_subject("Comprehensive feature demonstration");
113 doc.set_keywords("rdocx, docx, rust, sample, showcase");
114
115 // Headers & Footers with different first page
116 doc.set_different_first_page(true);
117 doc.set_first_page_header("rdocx");
118 doc.set_first_page_footer("Feature Showcase — Cover Page");
119 doc.set_header("rdocx Feature Showcase");
120 doc.set_footer("Generated by rdocx");
121
122 // ── COVER PAGE ──
123 let bg = create_sample_png(612, 792, [20, 45, 90]);
124 doc.add_background_image(&bg, "cover_bg.png");
125
126 for _ in 0..3 {
127 doc.add_paragraph("");
128 }
129 {
130 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
131 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
132 }
133 {
134 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
135 p.add_run("Complete Feature Showcase")
136 .size(28.0)
137 .color("FFFFFF")
138 .italic(true);
139 }
140 doc.add_paragraph("");
141 {
142 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
143 p.add_run("Every feature of the rdocx Rust library")
144 .size(13.0)
145 .color("B0C4DE");
146 }
147 {
148 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
149 p.add_run("demonstrated in a single document.")
150 .size(13.0)
151 .color("B0C4DE");
152 }
153
154 // ── TABLE OF CONTENTS ──
155 doc.add_paragraph("").page_break_before(true);
156 doc.insert_toc(doc.content_count(), 3);
157
158 // ── SECTION 1: TEXT FORMATTING ──
159 doc.add_paragraph("").page_break_before(true);
160 doc.add_paragraph("1. Text Formatting").style("Heading1");
161
162 // Paragraph Alignment
163 doc.add_paragraph("1.1 Paragraph Alignment")
164 .style("Heading2");
165 doc.add_paragraph("Left-aligned paragraph (default).")
166 .alignment(Alignment::Left);
167 doc.add_paragraph("Center-aligned paragraph.")
168 .alignment(Alignment::Center);
169 doc.add_paragraph("Right-aligned paragraph.")
170 .alignment(Alignment::Right);
171 doc.add_paragraph(
172 "Justified paragraph — this text is long enough to demonstrate how justified alignment \
173 distributes extra space across word gaps so lines fill the full width of the text area.",
174 )
175 .alignment(Alignment::Justify);
176
177 doc.add_paragraph("");
178
179 // Run Formatting
180 doc.add_paragraph("1.2 Run Formatting").style("Heading2");
181 {
182 let mut p = doc.add_paragraph("");
183 p.add_run("Bold").bold(true);
184 p.add_run(" | ");
185 p.add_run("Italic").italic(true);
186 p.add_run(" | ");
187 p.add_run("Bold Italic").bold(true).italic(true);
188 p.add_run(" | ");
189 p.add_run("Underline").underline(true);
190 p.add_run(" | ");
191 p.add_run("Strikethrough").strike(true);
192 p.add_run(" | ");
193 p.add_run("Double Strike").double_strike(true);
194 }
195 {
196 let mut p = doc.add_paragraph("");
197 p.add_run("Red").color("FF0000");
198 p.add_run(" | ");
199 p.add_run("Blue").color("0000FF");
200 p.add_run(" | ");
201 p.add_run("Green").color("00AA00");
202 p.add_run(" | ");
203 p.add_run("Highlighted").highlight("FFFF00");
204 }
205 {
206 let mut p = doc.add_paragraph("");
207 p.add_run("8pt").size(8.0);
208 p.add_run(" | ");
209 p.add_run("11pt").size(11.0);
210 p.add_run(" | ");
211 p.add_run("16pt").size(16.0);
212 p.add_run(" | ");
213 p.add_run("24pt").size(24.0);
214 }
215 {
216 let mut p = doc.add_paragraph("");
217 p.add_run("Arial").font("Arial");
218 p.add_run(" | ");
219 p.add_run("Times New Roman").font("Times New Roman");
220 p.add_run(" | ");
221 p.add_run("Courier New").font("Courier New");
222 }
223 {
224 let mut p = doc.add_paragraph("");
225 p.add_run("H");
226 p.add_run("2").subscript();
227 p.add_run("O (subscript) | E=mc");
228 p.add_run("2").superscript();
229 p.add_run(" (superscript)");
230 }
231 {
232 let mut p = doc.add_paragraph("");
233 p.add_run("ALL CAPS").all_caps(true);
234 p.add_run(" | ");
235 p.add_run("Small Caps").small_caps(true);
236 p.add_run(" | ");
237 p.add_run("Expanded").character_spacing(Length::twips(40));
238 p.add_run(" | ");
239 p.add_run("Hidden text (not visible)").hidden(true);
240 }
241
242 doc.add_paragraph("");
243
244 // Underline Styles
245 doc.add_paragraph("1.3 Underline Styles").style("Heading2");
246 {
247 let mut p = doc.add_paragraph("");
248 p.add_run("Single ")
249 .underline_style(rdocx::UnderlineStyle::Single);
250 p.add_run("Double ")
251 .underline_style(rdocx::UnderlineStyle::Double);
252 p.add_run("Thick ")
253 .underline_style(rdocx::UnderlineStyle::Thick);
254 p.add_run("Dotted ")
255 .underline_style(rdocx::UnderlineStyle::Dotted);
256 p.add_run("Dash ")
257 .underline_style(rdocx::UnderlineStyle::Dash);
258 p.add_run("Wave ")
259 .underline_style(rdocx::UnderlineStyle::Wave);
260 }
261
262 // ── SECTION 2: PARAGRAPH FORMATTING ──
263 doc.add_paragraph("").page_break_before(true);
264 doc.add_paragraph("2. Paragraph Formatting")
265 .style("Heading1");
266
267 doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
268 doc.add_paragraph("Paragraph with light green shading")
269 .shading("E2EFDA");
270 doc.add_paragraph("Paragraph with bottom border")
271 .border_bottom(BorderStyle::Single, 6, "2E75B6");
272 doc.add_paragraph("Paragraph with all borders (red)")
273 .border_all(BorderStyle::Single, 4, "FF0000");
274
275 doc.add_paragraph("2.2 Indentation").style("Heading2");
276 doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
277 .indent_left(Length::inches(1.0))
278 .hanging_indent(Length::inches(0.5));
279 doc.add_paragraph("0.5-inch first-line indent on this paragraph")
280 .first_line_indent(Length::inches(0.5));
281 doc.add_paragraph("Both left and right indent (0.75 inches each)")
282 .indent_left(Length::inches(0.75))
283 .indent_right(Length::inches(0.75));
284
285 doc.add_paragraph("2.3 Spacing & Line Height")
286 .style("Heading2");
287 doc.add_paragraph("Extra space before (24pt) and after (12pt)")
288 .space_before(Length::pt(24.0))
289 .space_after(Length::pt(12.0));
290 doc.add_paragraph(
291 "Double line spacing paragraph. This text should have extra vertical space between \
292 lines to demonstrate line_spacing_multiple(2.0).",
293 )
294 .line_spacing_multiple(2.0);
295 doc.add_paragraph("Exact 20pt line spacing (fixed height).")
296 .line_spacing(20.0);
297
298 doc.add_paragraph("2.4 Pagination Controls")
299 .style("Heading2");
300 doc.add_paragraph("keep_with_next — this paragraph stays with the next")
301 .keep_with_next(true);
302 doc.add_paragraph("(This paragraph was kept with the one above.)");
303 doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
304 .keep_together(true);
305 doc.add_paragraph("widow_control — prevents widow/orphan lines")
306 .widow_control(true);
307
308 // ── SECTION 3: LISTS ──
309 doc.add_paragraph("").page_break_before(true);
310 doc.add_paragraph("3. Lists").style("Heading1");
311
312 doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
313 doc.add_bullet_list_item("First item", 0);
314 doc.add_bullet_list_item("Second item", 0);
315 doc.add_bullet_list_item("Nested level 1", 1);
316 doc.add_bullet_list_item("Nested level 2", 2);
317 doc.add_bullet_list_item("Back to level 1", 1);
318 doc.add_bullet_list_item("Third item", 0);
319
320 doc.add_paragraph("");
321
322 doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
323 doc.add_numbered_list_item("First numbered item", 0);
324 doc.add_numbered_list_item("Second numbered item", 0);
325 doc.add_numbered_list_item("Sub-item A", 1);
326 doc.add_numbered_list_item("Sub-item B", 1);
327 doc.add_numbered_list_item("Third numbered item", 0);
328
329 // ── SECTION 4: TAB STOPS ──
330 doc.add_paragraph("");
331 doc.add_paragraph("4. Tab Stops").style("Heading1");
332
333 doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
334 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
335 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
336 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
337 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
338 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
339
340 doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
341 doc.add_paragraph("Item\t\tPrice")
342 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
343 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
344 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
345 doc.add_paragraph("Widget\t\t$19.99")
346 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
347 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
348 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
349 doc.add_paragraph("Gadget\t\t$249.50")
350 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
351 .add_tab_stop_with_leader(
352 TabAlignment::Right,
353 Length::inches(4.0),
354 TabLeader::Underscore,
355 )
356 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
357
358 // ── SECTION 5: TABLES ──
359 doc.add_paragraph("").page_break_before(true);
360 doc.add_paragraph("5. Tables").style("Heading1");
361
362 doc.add_paragraph("5.1 Basic Table").style("Heading2");
363 {
364 let mut tbl = doc
365 .add_table(4, 3)
366 .borders(BorderStyle::Single, 4, "000000");
367 for col in 0..3 {
368 tbl.cell(0, col).unwrap().shading("2E75B6");
369 }
370 tbl.cell(0, 0).unwrap().set_text("Name");
371 tbl.cell(0, 1).unwrap().set_text("Role");
372 tbl.cell(0, 2).unwrap().set_text("Location");
373 tbl.cell(1, 0).unwrap().set_text("Walter White");
374 tbl.cell(1, 1).unwrap().set_text("CEO");
375 tbl.cell(1, 2).unwrap().set_text("Albuquerque");
376 tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
377 tbl.cell(2, 1).unwrap().set_text("CTO");
378 tbl.cell(2, 2).unwrap().set_text("Remote");
379 tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
380 tbl.cell(3, 1).unwrap().set_text("Security");
381 tbl.cell(3, 2).unwrap().set_text("Washington");
382 }
383
384 doc.add_paragraph("");
385
386 doc.add_paragraph("5.2 Column Span & Cell Shading")
387 .style("Heading2");
388 {
389 let mut tbl = doc
390 .add_table(3, 4)
391 .borders(BorderStyle::Single, 4, "000000")
392 .width_pct(100.0);
393 tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
394 tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
395 tbl.cell(1, 0).unwrap().set_text("Region");
396 tbl.cell(1, 0).unwrap().shading("D6E4F0");
397 tbl.cell(1, 1).unwrap().set_text("Q1");
398 tbl.cell(1, 1).unwrap().shading("D6E4F0");
399 tbl.cell(1, 2).unwrap().set_text("Q2");
400 tbl.cell(1, 2).unwrap().shading("D6E4F0");
401 tbl.cell(1, 3).unwrap().set_text("Total");
402 tbl.cell(1, 3).unwrap().shading("D6E4F0");
403 tbl.cell(2, 0).unwrap().set_text("Americas");
404 tbl.cell(2, 1).unwrap().set_text("$2.4M");
405 tbl.cell(2, 2).unwrap().set_text("$2.7M");
406 tbl.cell(2, 3).unwrap().set_text("$5.1M");
407 }
408
409 doc.add_paragraph("");
410
411 doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
412 {
413 let mut tbl = doc
414 .add_table(4, 3)
415 .borders(BorderStyle::Single, 4, "000000");
416 tbl.cell(0, 0).unwrap().set_text("Category");
417 tbl.cell(0, 0).unwrap().shading("E2EFDA");
418 tbl.cell(0, 1).unwrap().set_text("Item");
419 tbl.cell(0, 1).unwrap().shading("E2EFDA");
420 tbl.cell(0, 2).unwrap().set_text("Price");
421 tbl.cell(0, 2).unwrap().shading("E2EFDA");
422 tbl.cell(1, 0).unwrap().set_text("Hardware");
423 tbl.cell(1, 0).unwrap().v_merge_restart();
424 tbl.cell(1, 1).unwrap().set_text("Laptop");
425 tbl.cell(1, 2).unwrap().set_text("$1,200");
426 tbl.cell(2, 0).unwrap().v_merge_continue();
427 tbl.cell(2, 1).unwrap().set_text("Monitor");
428 tbl.cell(2, 2).unwrap().set_text("$450");
429 tbl.cell(3, 0).unwrap().set_text("Software");
430 tbl.cell(3, 1).unwrap().set_text("IDE License");
431 tbl.cell(3, 2).unwrap().set_text("$200/yr");
432 }
433
434 doc.add_paragraph("");
435
436 doc.add_paragraph("5.4 Nested Table").style("Heading2");
437 {
438 let mut tbl = doc
439 .add_table(2, 2)
440 .borders(BorderStyle::Single, 6, "2E75B6");
441 tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
442 tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
443 tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
444 {
445 let mut cell = tbl.cell(1, 1).unwrap();
446 cell.set_text("Nested table below:");
447 let mut nested = cell
448 .add_table(2, 2)
449 .borders(BorderStyle::Single, 2, "FF6600");
450 nested.cell(0, 0).unwrap().set_text("A");
451 nested.cell(0, 1).unwrap().set_text("B");
452 nested.cell(1, 0).unwrap().set_text("C");
453 nested.cell(1, 1).unwrap().set_text("D");
454 }
455 }
456
457 doc.add_paragraph("");
458
459 doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
460 .style("Heading2");
461 {
462 let mut tbl = doc
463 .add_table(2, 3)
464 .borders(BorderStyle::Single, 4, "666666");
465 tbl.row(0).unwrap().height(Length::pt(50.0)).header();
466 tbl.cell(0, 0).unwrap().set_text("Top");
467 tbl.cell(0, 0)
468 .unwrap()
469 .vertical_alignment(VerticalAlignment::Top)
470 .shading("FFF2CC");
471 tbl.cell(0, 1).unwrap().set_text("Center");
472 tbl.cell(0, 1)
473 .unwrap()
474 .vertical_alignment(VerticalAlignment::Center)
475 .shading("D9E2F3");
476 tbl.cell(0, 2).unwrap().set_text("Bottom");
477 tbl.cell(0, 2)
478 .unwrap()
479 .vertical_alignment(VerticalAlignment::Bottom)
480 .shading("E2EFDA");
481 tbl.row(1).unwrap().cant_split();
482 tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
483 tbl.cell(1, 0).unwrap().no_wrap();
484 tbl.cell(1, 1).unwrap().set_text("Fixed width");
485 tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
486 tbl.cell(1, 2).unwrap().set_text("Normal");
487 }
488
489 // ── SECTION 6: IMAGES ──
490 doc.add_paragraph("").page_break_before(true);
491 doc.add_paragraph("6. Images").style("Heading1");
492
493 doc.add_paragraph("6.1 Inline Image").style("Heading2");
494 doc.add_paragraph("A blue gradient image below:");
495 let img = create_sample_png(200, 50, [0, 80, 200]);
496 doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
497
498 doc.add_paragraph("");
499 doc.add_paragraph("6.2 Header Image").style("Heading2");
500 let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
501 doc.set_header_image(
502 &hdr_img,
503 "header_logo.png",
504 Length::inches(2.0),
505 Length::inches(0.2),
506 );
507 doc.add_paragraph("The header now contains an inline image (check the top of this page).");
508
509 doc.add_paragraph("");
510 doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
511
512 // ── SECTION 7: CONTENT MANIPULATION ──
513 doc.add_paragraph("").page_break_before(true);
514 doc.add_paragraph("7. Content Manipulation")
515 .style("Heading1");
516
517 doc.add_paragraph("7.1 Placeholder Replacement")
518 .style("Heading2");
519 doc.add_paragraph("Customer: {{customer}}");
520 doc.add_paragraph("Date: {{date}}");
521 doc.add_paragraph("Reference: {{ref_number}}");
522 {
523 let mut tbl = doc
524 .add_table(2, 2)
525 .borders(BorderStyle::Single, 4, "000000");
526 tbl.cell(0, 0).unwrap().set_text("Project");
527 tbl.cell(0, 0).unwrap().shading("D6E4F0");
528 tbl.cell(0, 1).unwrap().set_text("{{project}}");
529 tbl.cell(1, 0).unwrap().set_text("Status");
530 tbl.cell(1, 0).unwrap().shading("D6E4F0");
531 tbl.cell(1, 1).unwrap().set_text("{{status}}");
532 }
533
534 let mut replacements = HashMap::new();
535 replacements.insert("{{customer}}", "Tensorbee Inc.");
536 replacements.insert("{{date}}", "February 22, 2026");
537 replacements.insert("{{ref_number}}", "TB-2026-001");
538 replacements.insert("{{project}}", "Infrastructure Upgrade");
539 replacements.insert("{{status}}", "In Progress");
540 let n = doc.replace_all(&replacements);
541 doc.add_paragraph(&format!("({n} placeholders replaced above)"));
542
543 doc.add_paragraph("");
544
545 doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
546 doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
547 let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
548 doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
549
550 doc.add_paragraph("");
551
552 doc.add_paragraph("7.3 Content Insertion").style("Heading2");
553 doc.add_paragraph("Section A: First content.");
554 doc.add_paragraph("Section C: Third content.");
555 if let Some(idx) = doc.find_content_index("Section C") {
556 doc.insert_paragraph(
557 idx,
558 "Section B: Inserted between A and C via find_content_index().",
559 );
560 }
561
562 // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
563 doc.add_paragraph("").section_break(SectionBreak::NextPage);
564 doc.add_paragraph("8. Section Breaks & Orientation")
565 .style("Heading1");
566 doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
567
568 {
569 let mut tbl = doc
570 .add_table(3, 7)
571 .borders(BorderStyle::Single, 4, "2E75B6");
572 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
573 for (c, h) in headers.iter().enumerate() {
574 tbl.cell(0, c).unwrap().set_text(h);
575 tbl.cell(0, c).unwrap().shading("2E75B6");
576 }
577 let data = [
578 [
579 "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
580 ],
581 [
582 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
583 ],
584 ];
585 for (r, row) in data.iter().enumerate() {
586 for (c, v) in row.iter().enumerate() {
587 tbl.cell(r + 1, c).unwrap().set_text(v);
588 }
589 }
590 }
591
592 doc.add_paragraph("")
593 .section_break(SectionBreak::NextPage)
594 .section_landscape();
595
596 // ── SECTION 9: CUSTOM STYLES ──
597 doc.add_paragraph("9. Custom Styles").style("Heading1");
598 doc.add_style(
599 StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
600 .based_on("Normal")
601 .paragraph_properties(rdocx_oxml::properties::CT_PPr {
602 shading: Some(rdocx_oxml::properties::CT_Shd {
603 val: "clear".to_string(),
604 color: None,
605 fill: Some("FFF2CC".to_string()),
606 }),
607 ..Default::default()
608 })
609 .run_properties(rdocx_oxml::properties::CT_RPr {
610 bold: Some(true),
611 color: Some("C45911".to_string()),
612 ..Default::default()
613 }),
614 );
615 doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
616 .style("CustomHighlight");
617
618 doc.add_paragraph("");
619
620 // ── SECTION 10: DOCUMENT INTELLIGENCE ──
621 doc.add_paragraph("10. Document Intelligence API")
622 .style("Heading1");
623
624 let wc = doc.word_count();
625 let hc = doc.headings().len();
626 let ic = doc.images().len();
627 let lc = doc.links().len();
628 doc.add_paragraph(&format!("Word count: {wc}"));
629 doc.add_paragraph(&format!("Heading count: {hc}"));
630 doc.add_paragraph(&format!("Image count: {ic}"));
631 doc.add_paragraph(&format!("Link count: {lc}"));
632
633 let outline = doc.document_outline();
634 doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
635
636 let issues = doc.audit_accessibility();
637 doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
638 for issue in &issues {
639 doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
640 }
641
642 // ── SECTION 11: DOCUMENT MERGING ──
643 doc.add_paragraph("");
644 doc.add_paragraph("11. Document Merging").style("Heading1");
645 {
646 let mut other = Document::new();
647 other.add_paragraph("This paragraph was merged from another document using append().");
648 doc.append(&other);
649 }
650
651 doc.add_paragraph("");
652
653 // ── SUMMARY ──
654 doc.add_paragraph("Summary of Demonstrated Features")
655 .style("Heading1");
656 let features = [
657 "Page setup: size, margins, header/footer distance, gutter",
658 "Document metadata: title, author, subject, keywords",
659 "Headers and footers: text, images, different first page",
660 "Background images (full-page behind text)",
661 "Table of Contents generation with bookmarks",
662 "Text formatting: bold, italic, underline styles, strike, color, size, font",
663 "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
664 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
665 "Pagination controls: keep-with-next, keep-together, widow control",
666 "Bullet and numbered lists with nesting",
667 "Tab stops with dot/underscore/hyphen leaders",
668 "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
669 "Row properties: header rows, exact height, cant-split",
670 "Cell properties: width, no-wrap, vertical alignment",
671 "Inline images and header images",
672 "Placeholder replacement (single and batch)",
673 "Regex-based find and replace",
674 "Content insertion at specific positions",
675 "Section breaks with mixed portrait/landscape",
676 "Custom paragraph and character styles",
677 "Document intelligence: word count, headings, outline, images, links",
678 "Accessibility audit",
679 "Document merging (append)",
680 "Export: DOCX, PDF, HTML, Markdown",
681 ];
682 for f in &features {
683 doc.add_bullet_list_item(f, 0);
684 }
685 doc.add_paragraph("");
686 doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
687 .alignment(Alignment::Center)
688 .shading("E2EFDA")
689 .border_all(BorderStyle::Single, 2, "00AA00");
690
691 doc
692}
693
694// =============================================================================
695// 2. PROPOSAL DOCUMENT — Deep navy + gold scheme
696// =============================================================================
697fn generate_proposal(_samples_dir: &Path) -> Document {
698 let mut doc = Document::new();
699
700 // Colors: Navy #1B2A4A, Gold #C5922E, Light #F4F1EB
701 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
702 doc.set_margins(
703 Length::inches(1.0),
704 Length::inches(1.0),
705 Length::inches(1.0),
706 Length::inches(1.0),
707 );
708 doc.set_title("AI Platform Modernization Proposal");
709 doc.set_author("Walter White");
710 doc.set_subject("Technology Proposal");
711 doc.set_keywords("proposal, AI, modernization, Tensorbee");
712
713 // Banner header
714 let logo_img = create_logo_png(220, 48);
715 let banner = build_header_banner_xml(
716 "rId1",
717 &BannerOpts {
718 bg_color: "1B2A4A",
719 banner_width: 7772400,
720 banner_height: 914400,
721 logo_width: 2011680,
722 logo_height: 438912,
723 logo_x_offset: 295125,
724 logo_y_offset: 237744,
725 },
726 );
727 doc.set_raw_header_with_images(
728 banner,
729 &[("rId1", &logo_img, "logo.png")],
730 rdocx_oxml::header_footer::HdrFtrType::Default,
731 );
732 doc.set_different_first_page(true);
733 let cover_banner = build_header_banner_xml(
734 "rId1",
735 &BannerOpts {
736 bg_color: "C5922E",
737 banner_width: 7772400,
738 banner_height: 914400,
739 logo_width: 2011680,
740 logo_height: 438912,
741 logo_x_offset: 295125,
742 logo_y_offset: 237744,
743 },
744 );
745 doc.set_raw_header_with_images(
746 cover_banner,
747 &[("rId1", &logo_img, "logo.png")],
748 rdocx_oxml::header_footer::HdrFtrType::First,
749 );
750 doc.set_margins(
751 Length::twips(2292),
752 Length::twips(1440),
753 Length::twips(1440),
754 Length::twips(1440),
755 );
756 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
757 doc.set_footer("Tensorbee — Confidential");
758
759 // Custom styles
760 doc.add_style(
761 StyleBuilder::paragraph("ProposalTitle", "Proposal Title")
762 .based_on("Normal")
763 .run_properties(rdocx_oxml::properties::CT_RPr {
764 bold: Some(true),
765 font_ascii: Some("Georgia".to_string()),
766 font_hansi: Some("Georgia".to_string()),
767 sz: Some(rdocx_oxml::HalfPoint(56)), // half-points → 28pt
768 color: Some("1B2A4A".to_string()),
769 ..Default::default()
770 }),
771 );
772
773 // ── Cover Page ──
774 for _ in 0..4 {
775 doc.add_paragraph("");
776 }
777 doc.add_paragraph("AI Platform Modernization")
778 .style("ProposalTitle")
779 .alignment(Alignment::Center);
780 doc.add_paragraph("")
781 .alignment(Alignment::Center)
782 .border_bottom(BorderStyle::Single, 8, "C5922E");
783 {
784 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
785 p.add_run("Prepared for Global Financial Corp.")
786 .size(14.0)
787 .color("666666")
788 .italic(true);
789 }
790 {
791 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
792 p.add_run("Prepared by: Walter White, CEO — Tensorbee")
793 .size(12.0)
794 .color("888888");
795 }
796 {
797 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
798 p.add_run("February 22, 2026").size(12.0).color("888888");
799 }
800
801 // ── TOC ──
802 doc.add_paragraph("").page_break_before(true);
803 doc.add_paragraph("Table of Contents").style("Heading1");
804 // We'll add a manual-style TOC since the insert_toc goes at a specific position
805 doc.insert_toc(doc.content_count(), 2);
806
807 // ── Executive Summary ──
808 doc.add_paragraph("").page_break_before(true);
809 doc.add_paragraph("Executive Summary").style("Heading1");
810 doc.add_paragraph(
811 "Tensorbee proposes a comprehensive modernization of Global Financial Corp's AI platform, \
812 transitioning from legacy batch-processing systems to a real-time inference architecture. \
813 This transformation will reduce model deployment time from weeks to hours, improve \
814 prediction accuracy by an estimated 23%, and deliver $4.2M in annual operational savings.",
815 )
816 .first_line_indent(Length::inches(0.3));
817 doc.add_paragraph(
818 "The project spans three phases over 18 months, with a total investment of $2.8M. \
819 Tensorbee brings deep expertise in ML infrastructure, having successfully completed \
820 similar transformations for three Fortune 500 financial institutions.",
821 )
822 .first_line_indent(Length::inches(0.3));
823
824 // Key metrics table
825 doc.add_paragraph("");
826 {
827 let mut tbl = doc
828 .add_table(5, 2)
829 .borders(BorderStyle::Single, 4, "1B2A4A")
830 .width_pct(80.0);
831 tbl.cell(0, 0).unwrap().set_text("Key Metric");
832 tbl.cell(0, 0).unwrap().shading("1B2A4A");
833 tbl.cell(0, 1).unwrap().set_text("Value");
834 tbl.cell(0, 1).unwrap().shading("1B2A4A");
835 let rows = [
836 ("Total Investment", "$2.8M"),
837 ("Annual Savings", "$4.2M"),
838 ("ROI (Year 1)", "150%"),
839 ("Timeline", "18 months"),
840 ];
841 for (i, (k, v)) in rows.iter().enumerate() {
842 tbl.cell(i + 1, 0).unwrap().set_text(k);
843 if i % 2 == 0 {
844 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
845 }
846 tbl.cell(i + 1, 1).unwrap().set_text(v);
847 if i % 2 == 0 {
848 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
849 }
850 }
851 }
852
853 // ── Problem Statement ──
854 doc.add_paragraph("").page_break_before(true);
855 doc.add_paragraph("Problem Statement").style("Heading1");
856 doc.add_paragraph(
857 "Global Financial Corp's current AI infrastructure faces three critical challenges:",
858 );
859 doc.add_numbered_list_item("Deployment Latency — New models take 3-4 weeks to deploy to production, compared to the industry benchmark of 2-3 days.", 0);
860 doc.add_numbered_list_item("Scalability Constraints — The batch processing architecture cannot handle real-time inference demands during peak trading hours.", 0);
861 doc.add_numbered_list_item("Technical Debt — Legacy Python 2.7 codebases and manual deployment scripts create reliability risks and slow iteration speed.", 0);
862
863 // ── Proposed Solution ──
864 doc.add_paragraph("").page_break_before(true);
865 doc.add_paragraph("Proposed Solution").style("Heading1");
866
867 doc.add_paragraph("Phase 1: Foundation (Months 1-6)")
868 .style("Heading2");
869 doc.add_bullet_list_item("Deploy Kubernetes-based ML serving infrastructure", 0);
870 doc.add_bullet_list_item("Implement CI/CD pipeline for model deployment", 0);
871 doc.add_bullet_list_item("Migrate top 5 production models to new platform", 0);
872
873 doc.add_paragraph("Phase 2: Expansion (Months 7-12)")
874 .style("Heading2");
875 doc.add_bullet_list_item(
876 "Real-time feature engineering pipeline (Apache Kafka + Flink)",
877 0,
878 );
879 doc.add_bullet_list_item("A/B testing framework for model variants", 0);
880 doc.add_bullet_list_item("Automated model monitoring and drift detection", 0);
881
882 doc.add_paragraph("Phase 3: Optimization (Months 13-18)")
883 .style("Heading2");
884 doc.add_bullet_list_item("GPU inference optimization (TensorRT/ONNX Runtime)", 0);
885 doc.add_bullet_list_item("Multi-region deployment for latency reduction", 0);
886 doc.add_bullet_list_item(
887 "Self-service model deployment portal for data science teams",
888 0,
889 );
890
891 // ── Budget ──
892 doc.add_paragraph("Budget Breakdown").style("Heading1");
893 {
894 let mut tbl = doc
895 .add_table(6, 3)
896 .borders(BorderStyle::Single, 4, "1B2A4A")
897 .width_pct(100.0);
898 tbl.cell(0, 0).unwrap().set_text("Category");
899 tbl.cell(0, 0).unwrap().shading("1B2A4A");
900 tbl.cell(0, 1).unwrap().set_text("Cost");
901 tbl.cell(0, 1).unwrap().shading("1B2A4A");
902 tbl.cell(0, 2).unwrap().set_text("Notes");
903 tbl.cell(0, 2).unwrap().shading("1B2A4A");
904 let items = [
905 (
906 "Infrastructure",
907 "$450,000",
908 "Cloud compute + GPU instances",
909 ),
910 ("Engineering Services", "$1,600,000", "12 FTE x 18 months"),
911 (
912 "Software Licenses",
913 "$350,000",
914 "Kafka, monitoring, CI/CD tools",
915 ),
916 (
917 "Training & Enablement",
918 "$200,000",
919 "Team training, documentation",
920 ),
921 ("Contingency (10%)", "$200,000", "Risk buffer"),
922 ];
923 for (i, (cat, cost, note)) in items.iter().enumerate() {
924 tbl.cell(i + 1, 0).unwrap().set_text(cat);
925 tbl.cell(i + 1, 1).unwrap().set_text(cost);
926 tbl.cell(i + 1, 2).unwrap().set_text(note);
927 if i % 2 == 0 {
928 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
929 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
930 tbl.cell(i + 1, 2).unwrap().shading("F4F1EB");
931 }
932 }
933 }
934
935 // ── Closing ──
936 doc.add_paragraph("");
937 doc.add_paragraph("Next Steps").style("Heading1");
938 doc.add_numbered_list_item(
939 "Schedule technical deep-dive with GFC engineering team (Week 1)",
940 0,
941 );
942 doc.add_numbered_list_item("Finalize scope and sign SOW (Week 2-3)", 0);
943 doc.add_numbered_list_item("Kick off Phase 1 with joint planning session (Week 4)", 0);
944
945 doc.add_paragraph("");
946 {
947 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
948 p.add_run("Walter White")
949 .bold(true)
950 .size(14.0)
951 .color("1B2A4A");
952 }
953 {
954 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
955 p.add_run("CEO, Tensorbee | walter@tensorbee.com")
956 .size(10.0)
957 .color("888888");
958 }
959
960 doc
961}
962
963// =============================================================================
964// 3. QUOTE / BILL OF MATERIALS — Teal + orange scheme
965// =============================================================================
966fn generate_quote(_samples_dir: &Path) -> Document {
967 let mut doc = Document::new();
968
969 // Colors: Teal #008B8B, Dark #1A3C3C, Orange #E07020, Light #F0F8F8
970 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
971 doc.set_margins(
972 Length::inches(0.75),
973 Length::inches(0.75),
974 Length::inches(0.75),
975 Length::inches(0.75),
976 );
977 doc.set_title("Quotation QT-2026-0147");
978 doc.set_author("Walter White");
979 doc.set_keywords("quote, BOM, Tensorbee");
980
981 doc.set_header("Tensorbee — Quotation");
982 doc.set_footer("QT-2026-0147 | Page");
983
984 // ── Header Block ──
985 {
986 let mut p = doc.add_paragraph("").alignment(Alignment::Left);
987 p.add_run("TENSORBEE")
988 .bold(true)
989 .size(28.0)
990 .color("008B8B")
991 .font("Helvetica");
992 }
993 doc.add_paragraph("123 Innovation Drive, Suite 400")
994 .alignment(Alignment::Left);
995 doc.add_paragraph("San Francisco, CA 94105 | +1 (415) 555-0199")
996 .alignment(Alignment::Left);
997 doc.add_paragraph("")
998 .border_bottom(BorderStyle::Single, 8, "008B8B");
999
1000 // Quote meta
1001 doc.add_paragraph("");
1002 {
1003 let mut tbl = doc.add_table(4, 4).width_pct(100.0);
1004 tbl.cell(0, 0).unwrap().set_text("QUOTATION");
1005 tbl.cell(0, 0).unwrap().shading("008B8B").grid_span(2);
1006 tbl.cell(0, 2).unwrap().set_text("DATE");
1007 tbl.cell(0, 2).unwrap().shading("008B8B");
1008 tbl.cell(0, 3).unwrap().set_text("VALID UNTIL");
1009 tbl.cell(0, 3).unwrap().shading("008B8B");
1010 tbl.cell(1, 0).unwrap().set_text("Quote #:");
1011 tbl.cell(1, 1).unwrap().set_text("QT-2026-0147");
1012 tbl.cell(1, 2).unwrap().set_text("Feb 22, 2026");
1013 tbl.cell(1, 3).unwrap().set_text("Mar 22, 2026");
1014 tbl.cell(2, 0).unwrap().set_text("Prepared by:");
1015 tbl.cell(2, 1).unwrap().set_text("Walter White");
1016 tbl.cell(2, 2).unwrap().set_text("Payment:");
1017 tbl.cell(2, 3).unwrap().set_text("Net 30");
1018 tbl.cell(3, 0).unwrap().set_text("Customer:");
1019 tbl.cell(3, 1).unwrap().set_text("Meridian Dynamics LLC");
1020 tbl.cell(3, 2).unwrap().set_text("Currency:");
1021 tbl.cell(3, 3).unwrap().set_text("USD");
1022 }
1023
1024 doc.add_paragraph("");
1025
1026 // ── Bill of Materials ──
1027 {
1028 let mut p = doc.add_paragraph("");
1029 p.add_run("Bill of Materials")
1030 .bold(true)
1031 .size(16.0)
1032 .color("1A3C3C");
1033 }
1034 doc.add_paragraph("");
1035
1036 {
1037 let mut tbl = doc
1038 .add_table(10, 6)
1039 .borders(BorderStyle::Single, 2, "008B8B")
1040 .width_pct(100.0)
1041 .layout_fixed();
1042 // Headers
1043 let hdrs = [
1044 "#",
1045 "Part Number",
1046 "Description",
1047 "Qty",
1048 "Unit Price",
1049 "Total",
1050 ];
1051 for (c, h) in hdrs.iter().enumerate() {
1052 tbl.cell(0, c).unwrap().set_text(h);
1053 tbl.cell(0, c).unwrap().shading("008B8B");
1054 }
1055
1056 let items: Vec<(&str, &str, &str, &str, &str)> = vec![
1057 (
1058 "TB-GPU-A100",
1059 "NVIDIA A100 80GB GPU",
1060 "4",
1061 "$12,500.00",
1062 "$50,000.00",
1063 ),
1064 (
1065 "TB-SRV-R750",
1066 "Dell R750xa Server Chassis",
1067 "2",
1068 "$8,200.00",
1069 "$16,400.00",
1070 ),
1071 (
1072 "TB-RAM-256G",
1073 "256GB DDR5 ECC Memory Module",
1074 "8",
1075 "$890.00",
1076 "$7,120.00",
1077 ),
1078 (
1079 "TB-SSD-3840",
1080 "3.84TB NVMe U.2 SSD",
1081 "8",
1082 "$1,150.00",
1083 "$9,200.00",
1084 ),
1085 (
1086 "TB-NET-CX7",
1087 "ConnectX-7 200GbE NIC",
1088 "4",
1089 "$1,800.00",
1090 "$7,200.00",
1091 ),
1092 (
1093 "TB-SW-48P",
1094 "48-Port 100GbE Switch",
1095 "1",
1096 "$22,000.00",
1097 "$22,000.00",
1098 ),
1099 (
1100 "TB-CAB-RACK",
1101 "42U Server Rack + PDU",
1102 "1",
1103 "$4,500.00",
1104 "$4,500.00",
1105 ),
1106 (
1107 "TB-SVC-INST",
1108 "Installation & Configuration",
1109 "1",
1110 "$8,500.00",
1111 "$8,500.00",
1112 ),
1113 (
1114 "TB-SVC-SUPP",
1115 "3-Year Premium Support",
1116 "1",
1117 "$15,000.00",
1118 "$15,000.00",
1119 ),
1120 ];
1121
1122 for (i, (pn, desc, qty, unit, total)) in items.iter().enumerate() {
1123 let row = i + 1;
1124 tbl.cell(row, 0).unwrap().set_text(&format!("{}", i + 1));
1125 tbl.cell(row, 1).unwrap().set_text(pn);
1126 tbl.cell(row, 2).unwrap().set_text(desc);
1127 tbl.cell(row, 3).unwrap().set_text(qty);
1128 tbl.cell(row, 4).unwrap().set_text(unit);
1129 tbl.cell(row, 5).unwrap().set_text(total);
1130 if i % 2 == 0 {
1131 for c in 0..6 {
1132 tbl.cell(row, c).unwrap().shading("F0F8F8");
1133 }
1134 }
1135 }
1136 }
1137
1138 doc.add_paragraph("");
1139
1140 // ── Totals ──
1141 {
1142 let mut tbl = doc
1143 .add_table(4, 2)
1144 .borders(BorderStyle::Single, 2, "008B8B")
1145 .width(Length::inches(3.5))
1146 .alignment(Alignment::Right);
1147 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1148 tbl.cell(0, 1).unwrap().set_text("$139,920.00");
1149 tbl.cell(1, 0).unwrap().set_text("Shipping & Handling");
1150 tbl.cell(1, 1).unwrap().set_text("$2,500.00");
1151 tbl.cell(2, 0).unwrap().set_text("Tax (8.625%)");
1152 tbl.cell(2, 1).unwrap().set_text("$12,068.10");
1153 tbl.cell(3, 0).unwrap().set_text("TOTAL");
1154 tbl.cell(3, 0).unwrap().shading("E07020");
1155 tbl.cell(3, 1).unwrap().set_text("$154,488.10");
1156 tbl.cell(3, 1).unwrap().shading("E07020");
1157 }
1158
1159 doc.add_paragraph("");
1160
1161 // ── Terms & Conditions ──
1162 {
1163 let mut p = doc.add_paragraph("");
1164 p.add_run("Terms & Conditions")
1165 .bold(true)
1166 .size(14.0)
1167 .color("1A3C3C");
1168 }
1169 doc.add_numbered_list_item(
1170 "This quotation is valid for 30 calendar days from the date of issue.",
1171 0,
1172 );
1173 doc.add_numbered_list_item(
1174 "All prices are in USD and exclusive of applicable taxes unless stated.",
1175 0,
1176 );
1177 doc.add_numbered_list_item("Standard lead time is 4-6 weeks from PO receipt.", 0);
1178 doc.add_numbered_list_item(
1179 "Payment terms: Net 30 from invoice date. 2% discount for payment within 10 days.",
1180 0,
1181 );
1182 doc.add_numbered_list_item(
1183 "Warranty: 3-year manufacturer warranty on all hardware components.",
1184 0,
1185 );
1186 doc.add_numbered_list_item(
1187 "Returns subject to 15% restocking fee if initiated after 14 days.",
1188 0,
1189 );
1190
1191 doc.add_paragraph("");
1192 doc.add_paragraph("")
1193 .border_bottom(BorderStyle::Single, 4, "008B8B");
1194 doc.add_paragraph("");
1195 {
1196 let mut p = doc.add_paragraph("");
1197 p.add_run("Accepted by: ").bold(true);
1198 p.add_run("___________________________ Date: ___________");
1199 }
1200 {
1201 let mut p = doc.add_paragraph("");
1202 p.add_run("Print Name: ").bold(true);
1203 p.add_run("___________________________ Title: ___________");
1204 }
1205
1206 doc
1207}
1208
1209// =============================================================================
1210// 4. INVOICE — Crimson + charcoal scheme
1211// =============================================================================
1212fn generate_invoice(_samples_dir: &Path) -> Document {
1213 let mut doc = Document::new();
1214
1215 // Colors: Crimson #B22222, Charcoal #333333, Light #FAF0F0
1216 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1217 doc.set_margins(
1218 Length::inches(0.75),
1219 Length::inches(0.75),
1220 Length::inches(0.75),
1221 Length::inches(0.75),
1222 );
1223 doc.set_title("Invoice INV-2026-0392");
1224 doc.set_author("Walter White");
1225 doc.set_keywords("invoice, Tensorbee");
1226
1227 doc.set_footer("Tensorbee — Thank you for your business!");
1228
1229 // ── Company & Invoice Header ──
1230 {
1231 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1232 // Company name left, INVOICE right
1233 {
1234 let mut cell = tbl.cell(0, 0).unwrap();
1235 let mut p = cell.add_paragraph("");
1236 p.add_run("TENSORBEE")
1237 .bold(true)
1238 .size(32.0)
1239 .color("B22222")
1240 .font("Helvetica");
1241 }
1242 {
1243 let mut cell = tbl.cell(0, 1).unwrap();
1244 let mut p = cell.add_paragraph("");
1245 p.add_run("INVOICE").bold(true).size(32.0).color("333333");
1246 }
1247 tbl.cell(1, 0)
1248 .unwrap()
1249 .set_text("123 Innovation Drive, Suite 400");
1250 tbl.cell(1, 1).unwrap().set_text("Invoice #: INV-2026-0392");
1251 tbl.cell(2, 0).unwrap().set_text("San Francisco, CA 94105");
1252 tbl.cell(2, 1).unwrap().set_text("Date: February 22, 2026");
1253 tbl.cell(3, 0).unwrap().set_text("walter@tensorbee.com");
1254 tbl.cell(3, 1).unwrap().set_text("Due Date: March 24, 2026");
1255 }
1256
1257 doc.add_paragraph("")
1258 .border_bottom(BorderStyle::Thick, 8, "B22222");
1259 doc.add_paragraph("");
1260
1261 // ── Bill To / Ship To ──
1262 {
1263 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1264 tbl.cell(0, 0).unwrap().set_text("BILL TO");
1265 tbl.cell(0, 0).unwrap().shading("B22222");
1266 tbl.cell(0, 1).unwrap().set_text("SHIP TO");
1267 tbl.cell(0, 1).unwrap().shading("B22222");
1268 tbl.cell(1, 0).unwrap().set_text("Meridian Dynamics LLC");
1269 tbl.cell(1, 1).unwrap().set_text("Meridian Dynamics LLC");
1270 tbl.cell(2, 0).unwrap().set_text("456 Enterprise Blvd");
1271 tbl.cell(2, 1).unwrap().set_text("Attn: Server Room B");
1272 tbl.cell(3, 0).unwrap().set_text("Austin, TX 78701");
1273 tbl.cell(3, 1)
1274 .unwrap()
1275 .set_text("456 Enterprise Blvd, Austin, TX 78701");
1276 }
1277
1278 doc.add_paragraph("");
1279
1280 // ── Line Items ──
1281 {
1282 let mut tbl = doc
1283 .add_table(8, 5)
1284 .borders(BorderStyle::Single, 2, "B22222")
1285 .width_pct(100.0);
1286 let hdrs = ["Description", "Qty", "Unit Price", "Tax", "Amount"];
1287 for (c, h) in hdrs.iter().enumerate() {
1288 tbl.cell(0, c).unwrap().set_text(h);
1289 tbl.cell(0, c).unwrap().shading("B22222");
1290 }
1291 let items = [
1292 (
1293 "ML Infrastructure Setup — Phase 1",
1294 "1",
1295 "$45,000.00",
1296 "$3,881.25",
1297 "$48,881.25",
1298 ),
1299 (
1300 "Data Pipeline Development (160 hrs)",
1301 "160",
1302 "$225.00",
1303 "$3,105.00",
1304 "$39,105.00",
1305 ),
1306 (
1307 "GPU Cluster Configuration",
1308 "1",
1309 "$12,000.00",
1310 "$1,035.00",
1311 "$13,035.00",
1312 ),
1313 (
1314 "API Gateway Implementation",
1315 "1",
1316 "$18,500.00",
1317 "$1,595.63",
1318 "$20,095.63",
1319 ),
1320 (
1321 "Load Testing & QA (80 hrs)",
1322 "80",
1323 "$195.00",
1324 "$1,345.50",
1325 "$16,945.50",
1326 ),
1327 (
1328 "Documentation & Training",
1329 "1",
1330 "$8,000.00",
1331 "$690.00",
1332 "$8,690.00",
1333 ),
1334 (
1335 "Project Management (3 months)",
1336 "3",
1337 "$6,500.00",
1338 "$1,679.63",
1339 "$21,179.63",
1340 ),
1341 ];
1342 for (i, (desc, qty, unit, tax, amt)) in items.iter().enumerate() {
1343 let r = i + 1;
1344 tbl.cell(r, 0).unwrap().set_text(desc);
1345 tbl.cell(r, 1).unwrap().set_text(qty);
1346 tbl.cell(r, 2).unwrap().set_text(unit);
1347 tbl.cell(r, 3).unwrap().set_text(tax);
1348 tbl.cell(r, 4).unwrap().set_text(amt);
1349 if i % 2 == 0 {
1350 for c in 0..5 {
1351 tbl.cell(r, c).unwrap().shading("FAF0F0");
1352 }
1353 }
1354 }
1355 }
1356
1357 doc.add_paragraph("");
1358
1359 // ── Totals ──
1360 {
1361 let mut tbl = doc
1362 .add_table(4, 2)
1363 .borders(BorderStyle::Single, 2, "B22222")
1364 .width(Length::inches(3.0))
1365 .alignment(Alignment::Right);
1366 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1367 tbl.cell(0, 1).unwrap().set_text("$154,600.00");
1368 tbl.cell(1, 0).unwrap().set_text("Tax (8.625%)");
1369 tbl.cell(1, 1).unwrap().set_text("$13,334.25");
1370 tbl.cell(2, 0).unwrap().set_text("Discount (5%)");
1371 tbl.cell(2, 1).unwrap().set_text("-$7,730.00");
1372 tbl.cell(3, 0).unwrap().set_text("AMOUNT DUE");
1373 tbl.cell(3, 0).unwrap().shading("B22222");
1374 tbl.cell(3, 1).unwrap().set_text("$160,204.25");
1375 tbl.cell(3, 1).unwrap().shading("B22222");
1376 }
1377
1378 doc.add_paragraph("");
1379 doc.add_paragraph("");
1380
1381 // ── Payment Details ──
1382 {
1383 let mut p = doc.add_paragraph("");
1384 p.add_run("Payment Details")
1385 .bold(true)
1386 .size(14.0)
1387 .color("333333");
1388 }
1389 doc.add_paragraph("Bank: Silicon Valley Bank")
1390 .indent_left(Length::inches(0.3));
1391 doc.add_paragraph("Account: Tensorbee Inc. — 0847-2953-1120")
1392 .indent_left(Length::inches(0.3));
1393 doc.add_paragraph("Routing: 121140399")
1394 .indent_left(Length::inches(0.3));
1395 doc.add_paragraph("Swift: SVBKUS6S")
1396 .indent_left(Length::inches(0.3));
1397
1398 doc.add_paragraph("");
1399
1400 doc.add_paragraph("Please include invoice number INV-2026-0392 in the payment reference.")
1401 .shading("FAF0F0")
1402 .border_all(BorderStyle::Single, 2, "B22222");
1403
1404 doc
1405}
1406
1407// =============================================================================
1408// 5. REPORT — Forest green + earth tones, with images & hierarchical sections
1409// =============================================================================
1410fn generate_report(_samples_dir: &Path) -> Document {
1411 let mut doc = Document::new();
1412
1413 // Colors: Forest #2D5016, Sage #6B8E23, Earth #8B7355, Cream #FFFAF0
1414 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1415 doc.set_margins(
1416 Length::inches(1.0),
1417 Length::inches(1.0),
1418 Length::inches(1.0),
1419 Length::inches(1.0),
1420 );
1421 doc.set_title("Q4 2025 Environmental Impact Report");
1422 doc.set_author("Walter White");
1423 doc.set_subject("Quarterly Environmental Report");
1424 doc.set_keywords("environment, sustainability, report, Tensorbee");
1425
1426 doc.set_different_first_page(true);
1427 doc.set_first_page_header("");
1428 doc.set_header("Tensorbee — Q4 2025 Environmental Impact Report");
1429 doc.set_footer("CONFIDENTIAL — Page");
1430
1431 // ── Cover ──
1432 let cover_bg = create_sample_png(612, 792, [20, 50, 15]);
1433 doc.add_background_image(&cover_bg, "report_cover.png");
1434
1435 for _ in 0..5 {
1436 doc.add_paragraph("");
1437 }
1438 {
1439 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1440 p.add_run("Q4 2025")
1441 .bold(true)
1442 .size(48.0)
1443 .color("FFFFFF")
1444 .font("Georgia");
1445 }
1446 {
1447 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1448 p.add_run("Environmental Impact Report")
1449 .size(24.0)
1450 .color("90EE90")
1451 .font("Georgia")
1452 .italic(true);
1453 }
1454 doc.add_paragraph("");
1455 {
1456 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1457 p.add_run("Tensorbee — Sustainability Division")
1458 .size(14.0)
1459 .color("C0C0C0");
1460 }
1461 {
1462 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1463 p.add_run("Prepared by Walter White, Chief Sustainability Officer")
1464 .size(11.0)
1465 .color("AAAAAA");
1466 }
1467
1468 // ── TOC ──
1469 doc.add_paragraph("").page_break_before(true);
1470 doc.insert_toc(doc.content_count(), 3);
1471
1472 // ── Section 1: Executive Overview ──
1473 doc.add_paragraph("").page_break_before(true);
1474 doc.add_paragraph("Executive Overview").style("Heading1");
1475 doc.add_paragraph(
1476 "This report presents Tensorbee's environmental performance for Q4 2025. Our \
1477 sustainability initiatives have yielded a 34% reduction in carbon emissions compared \
1478 to Q4 2024, exceeding our target of 25%. Key achievements include the transition to \
1479 100% renewable energy in our primary data centers and the launch of our carbon offset \
1480 marketplace.",
1481 )
1482 .first_line_indent(Length::inches(0.3));
1483
1484 // Image: performance chart
1485 let chart_img = create_chart_png(400, 200);
1486 doc.add_paragraph("");
1487 doc.add_picture(
1488 &chart_img,
1489 "performance_chart.png",
1490 Length::inches(5.0),
1491 Length::inches(2.5),
1492 )
1493 .alignment(Alignment::Center);
1494 {
1495 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1496 p.add_run("Figure 1: Quarterly Carbon Emissions (tonnes CO2e)")
1497 .italic(true)
1498 .size(9.0)
1499 .color("666666");
1500 }
1501
1502 // ── Section 2: Energy Consumption ──
1503 doc.add_paragraph("").page_break_before(true);
1504 doc.add_paragraph("Energy Consumption").style("Heading1");
1505
1506 doc.add_paragraph("Data Center Operations")
1507 .style("Heading2");
1508 doc.add_paragraph(
1509 "Our three primary data centers consumed a combined 4.2 GWh in Q4 2025, \
1510 a 12% reduction from Q3 2025 driven by improved cooling efficiency and \
1511 server consolidation.",
1512 );
1513
1514 // Data table
1515 {
1516 let mut tbl = doc
1517 .add_table(5, 4)
1518 .borders(BorderStyle::Single, 4, "2D5016")
1519 .width_pct(100.0);
1520 let hdrs = ["Data Center", "Capacity (MW)", "Usage (GWh)", "PUE"];
1521 for (c, h) in hdrs.iter().enumerate() {
1522 tbl.cell(0, c).unwrap().set_text(h);
1523 tbl.cell(0, c).unwrap().shading("2D5016");
1524 }
1525 let rows = [
1526 ("San Francisco (Primary)", "3.2", "1.8", "1.12"),
1527 ("Dublin (EU)", "2.1", "1.2", "1.18"),
1528 ("Singapore (APAC)", "1.8", "1.2", "1.24"),
1529 ("TOTAL", "7.1", "4.2", "1.17 avg"),
1530 ];
1531 for (i, (dc, cap, use_, pue)) in rows.iter().enumerate() {
1532 tbl.cell(i + 1, 0).unwrap().set_text(dc);
1533 tbl.cell(i + 1, 1).unwrap().set_text(cap);
1534 tbl.cell(i + 1, 2).unwrap().set_text(use_);
1535 tbl.cell(i + 1, 3).unwrap().set_text(pue);
1536 if i == 3 {
1537 for c in 0..4 {
1538 tbl.cell(i + 1, c).unwrap().shading("E2EFDA");
1539 }
1540 }
1541 }
1542 }
1543
1544 doc.add_paragraph("");
1545
1546 doc.add_paragraph("Renewable Energy Mix").style("Heading2");
1547 doc.add_paragraph("Breakdown of energy sources across all facilities:");
1548
1549 doc.add_bullet_list_item("Solar PV: 42% (1.76 GWh)", 0);
1550 doc.add_bullet_list_item("Wind Power Purchase Agreements: 38% (1.60 GWh)", 0);
1551 doc.add_bullet_list_item("Hydroelectric: 12% (0.50 GWh)", 0);
1552 doc.add_bullet_list_item("Grid (non-renewable): 8% (0.34 GWh)", 0);
1553
1554 // ── Section 3: Water & Waste ──
1555 doc.add_paragraph("Water & Waste Management")
1556 .style("Heading1");
1557
1558 doc.add_paragraph("Water Usage").style("Heading2");
1559 doc.add_paragraph(
1560 "Total water consumption was 12.4 million gallons, a 15% reduction from Q3. \
1561 Our closed-loop cooling systems now recycle 78% of water used in cooling operations.",
1562 );
1563
1564 doc.add_paragraph("Waste Reduction").style("Heading2");
1565 doc.add_paragraph("E-waste management results for Q4:")
1566 .keep_with_next(true);
1567 {
1568 let mut tbl = doc
1569 .add_table(4, 3)
1570 .borders(BorderStyle::Single, 2, "6B8E23")
1571 .width_pct(80.0);
1572 tbl.cell(0, 0).unwrap().set_text("Category");
1573 tbl.cell(0, 0).unwrap().shading("6B8E23");
1574 tbl.cell(0, 1).unwrap().set_text("Weight (kg)");
1575 tbl.cell(0, 1).unwrap().shading("6B8E23");
1576 tbl.cell(0, 2).unwrap().set_text("Recycled %");
1577 tbl.cell(0, 2).unwrap().shading("6B8E23");
1578 let rows = [
1579 ("Server Hardware", "2,450", "94%"),
1580 ("Networking Equipment", "820", "91%"),
1581 ("Storage Media", "340", "99%"),
1582 ];
1583 for (i, (cat, wt, pct)) in rows.iter().enumerate() {
1584 tbl.cell(i + 1, 0).unwrap().set_text(cat);
1585 tbl.cell(i + 1, 1).unwrap().set_text(wt);
1586 tbl.cell(i + 1, 2).unwrap().set_text(pct);
1587 }
1588 }
1589
1590 // ── Section 4: Initiatives ──
1591 doc.add_paragraph("").page_break_before(true);
1592 doc.add_paragraph("Q1 2026 Initiatives").style("Heading1");
1593
1594 doc.add_paragraph("Planned Programs").style("Heading2");
1595 doc.add_numbered_list_item(
1596 "Deploy on-site battery storage at SF data center (2 MWh capacity)",
1597 0,
1598 );
1599 doc.add_numbered_list_item("Pilot immersion cooling in Dublin facility", 0);
1600 doc.add_numbered_list_item("Launch employee commute carbon offset program", 0);
1601 doc.add_numbered_list_item("Achieve ISO 14001 certification for Singapore facility", 0);
1602
1603 doc.add_paragraph("Investment Targets").style("Heading2");
1604 doc.add_paragraph("Sustainability CapEx allocation for FY2026:")
1605 .keep_with_next(true);
1606 {
1607 let mut tbl = doc
1608 .add_table(5, 2)
1609 .borders(BorderStyle::Single, 4, "2D5016");
1610 tbl.cell(0, 0).unwrap().set_text("Initiative");
1611 tbl.cell(0, 0).unwrap().shading("2D5016");
1612 tbl.cell(0, 1).unwrap().set_text("Budget");
1613 tbl.cell(0, 1).unwrap().shading("2D5016");
1614 let rows = [
1615 ("Battery Storage", "$1.2M"),
1616 ("Immersion Cooling Pilot", "$800K"),
1617 ("Solar Panel Expansion", "$2.1M"),
1618 ("Carbon Credits", "$500K"),
1619 ];
1620 for (i, (init, budget)) in rows.iter().enumerate() {
1621 tbl.cell(i + 1, 0).unwrap().set_text(init);
1622 tbl.cell(i + 1, 1).unwrap().set_text(budget);
1623 if i % 2 == 0 {
1624 tbl.cell(i + 1, 0).unwrap().shading("FFFAF0");
1625 tbl.cell(i + 1, 1).unwrap().shading("FFFAF0");
1626 }
1627 }
1628 }
1629
1630 // Image: sustainability roadmap
1631 let roadmap_img = create_sample_png(500, 100, [40, 80, 30]);
1632 doc.add_paragraph("");
1633 doc.add_picture(
1634 &roadmap_img,
1635 "roadmap.png",
1636 Length::inches(6.0),
1637 Length::inches(1.2),
1638 )
1639 .alignment(Alignment::Center);
1640 {
1641 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1642 p.add_run("Figure 2: 2026 Sustainability Roadmap")
1643 .italic(true)
1644 .size(9.0)
1645 .color("666666");
1646 }
1647
1648 doc.add_paragraph("");
1649 doc.add_paragraph(
1650 "For questions about this report, contact Walter White at sustainability@tensorbee.com.",
1651 )
1652 .shading("FFFAF0")
1653 .border_all(BorderStyle::Single, 2, "2D5016");
1654
1655 doc
1656}Sourcepub fn set_title(&mut self, title: &str)
pub fn set_title(&mut self, title: &str)
Set the document title.
Examples found in repository?
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
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}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}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}97fn generate_feature_showcase(_samples_dir: &Path) -> Document {
98 let mut doc = Document::new();
99
100 // ── Page Setup & Metadata ──
101 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
102 doc.set_margins(
103 Length::inches(1.0),
104 Length::inches(1.0),
105 Length::inches(1.0),
106 Length::inches(1.0),
107 );
108 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
109 doc.set_gutter(Length::twips(0));
110 doc.set_title("rdocx Feature Showcase");
111 doc.set_author("rdocx Sample Generator");
112 doc.set_subject("Comprehensive feature demonstration");
113 doc.set_keywords("rdocx, docx, rust, sample, showcase");
114
115 // Headers & Footers with different first page
116 doc.set_different_first_page(true);
117 doc.set_first_page_header("rdocx");
118 doc.set_first_page_footer("Feature Showcase — Cover Page");
119 doc.set_header("rdocx Feature Showcase");
120 doc.set_footer("Generated by rdocx");
121
122 // ── COVER PAGE ──
123 let bg = create_sample_png(612, 792, [20, 45, 90]);
124 doc.add_background_image(&bg, "cover_bg.png");
125
126 for _ in 0..3 {
127 doc.add_paragraph("");
128 }
129 {
130 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
131 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
132 }
133 {
134 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
135 p.add_run("Complete Feature Showcase")
136 .size(28.0)
137 .color("FFFFFF")
138 .italic(true);
139 }
140 doc.add_paragraph("");
141 {
142 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
143 p.add_run("Every feature of the rdocx Rust library")
144 .size(13.0)
145 .color("B0C4DE");
146 }
147 {
148 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
149 p.add_run("demonstrated in a single document.")
150 .size(13.0)
151 .color("B0C4DE");
152 }
153
154 // ── TABLE OF CONTENTS ──
155 doc.add_paragraph("").page_break_before(true);
156 doc.insert_toc(doc.content_count(), 3);
157
158 // ── SECTION 1: TEXT FORMATTING ──
159 doc.add_paragraph("").page_break_before(true);
160 doc.add_paragraph("1. Text Formatting").style("Heading1");
161
162 // Paragraph Alignment
163 doc.add_paragraph("1.1 Paragraph Alignment")
164 .style("Heading2");
165 doc.add_paragraph("Left-aligned paragraph (default).")
166 .alignment(Alignment::Left);
167 doc.add_paragraph("Center-aligned paragraph.")
168 .alignment(Alignment::Center);
169 doc.add_paragraph("Right-aligned paragraph.")
170 .alignment(Alignment::Right);
171 doc.add_paragraph(
172 "Justified paragraph — this text is long enough to demonstrate how justified alignment \
173 distributes extra space across word gaps so lines fill the full width of the text area.",
174 )
175 .alignment(Alignment::Justify);
176
177 doc.add_paragraph("");
178
179 // Run Formatting
180 doc.add_paragraph("1.2 Run Formatting").style("Heading2");
181 {
182 let mut p = doc.add_paragraph("");
183 p.add_run("Bold").bold(true);
184 p.add_run(" | ");
185 p.add_run("Italic").italic(true);
186 p.add_run(" | ");
187 p.add_run("Bold Italic").bold(true).italic(true);
188 p.add_run(" | ");
189 p.add_run("Underline").underline(true);
190 p.add_run(" | ");
191 p.add_run("Strikethrough").strike(true);
192 p.add_run(" | ");
193 p.add_run("Double Strike").double_strike(true);
194 }
195 {
196 let mut p = doc.add_paragraph("");
197 p.add_run("Red").color("FF0000");
198 p.add_run(" | ");
199 p.add_run("Blue").color("0000FF");
200 p.add_run(" | ");
201 p.add_run("Green").color("00AA00");
202 p.add_run(" | ");
203 p.add_run("Highlighted").highlight("FFFF00");
204 }
205 {
206 let mut p = doc.add_paragraph("");
207 p.add_run("8pt").size(8.0);
208 p.add_run(" | ");
209 p.add_run("11pt").size(11.0);
210 p.add_run(" | ");
211 p.add_run("16pt").size(16.0);
212 p.add_run(" | ");
213 p.add_run("24pt").size(24.0);
214 }
215 {
216 let mut p = doc.add_paragraph("");
217 p.add_run("Arial").font("Arial");
218 p.add_run(" | ");
219 p.add_run("Times New Roman").font("Times New Roman");
220 p.add_run(" | ");
221 p.add_run("Courier New").font("Courier New");
222 }
223 {
224 let mut p = doc.add_paragraph("");
225 p.add_run("H");
226 p.add_run("2").subscript();
227 p.add_run("O (subscript) | E=mc");
228 p.add_run("2").superscript();
229 p.add_run(" (superscript)");
230 }
231 {
232 let mut p = doc.add_paragraph("");
233 p.add_run("ALL CAPS").all_caps(true);
234 p.add_run(" | ");
235 p.add_run("Small Caps").small_caps(true);
236 p.add_run(" | ");
237 p.add_run("Expanded").character_spacing(Length::twips(40));
238 p.add_run(" | ");
239 p.add_run("Hidden text (not visible)").hidden(true);
240 }
241
242 doc.add_paragraph("");
243
244 // Underline Styles
245 doc.add_paragraph("1.3 Underline Styles").style("Heading2");
246 {
247 let mut p = doc.add_paragraph("");
248 p.add_run("Single ")
249 .underline_style(rdocx::UnderlineStyle::Single);
250 p.add_run("Double ")
251 .underline_style(rdocx::UnderlineStyle::Double);
252 p.add_run("Thick ")
253 .underline_style(rdocx::UnderlineStyle::Thick);
254 p.add_run("Dotted ")
255 .underline_style(rdocx::UnderlineStyle::Dotted);
256 p.add_run("Dash ")
257 .underline_style(rdocx::UnderlineStyle::Dash);
258 p.add_run("Wave ")
259 .underline_style(rdocx::UnderlineStyle::Wave);
260 }
261
262 // ── SECTION 2: PARAGRAPH FORMATTING ──
263 doc.add_paragraph("").page_break_before(true);
264 doc.add_paragraph("2. Paragraph Formatting")
265 .style("Heading1");
266
267 doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
268 doc.add_paragraph("Paragraph with light green shading")
269 .shading("E2EFDA");
270 doc.add_paragraph("Paragraph with bottom border")
271 .border_bottom(BorderStyle::Single, 6, "2E75B6");
272 doc.add_paragraph("Paragraph with all borders (red)")
273 .border_all(BorderStyle::Single, 4, "FF0000");
274
275 doc.add_paragraph("2.2 Indentation").style("Heading2");
276 doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
277 .indent_left(Length::inches(1.0))
278 .hanging_indent(Length::inches(0.5));
279 doc.add_paragraph("0.5-inch first-line indent on this paragraph")
280 .first_line_indent(Length::inches(0.5));
281 doc.add_paragraph("Both left and right indent (0.75 inches each)")
282 .indent_left(Length::inches(0.75))
283 .indent_right(Length::inches(0.75));
284
285 doc.add_paragraph("2.3 Spacing & Line Height")
286 .style("Heading2");
287 doc.add_paragraph("Extra space before (24pt) and after (12pt)")
288 .space_before(Length::pt(24.0))
289 .space_after(Length::pt(12.0));
290 doc.add_paragraph(
291 "Double line spacing paragraph. This text should have extra vertical space between \
292 lines to demonstrate line_spacing_multiple(2.0).",
293 )
294 .line_spacing_multiple(2.0);
295 doc.add_paragraph("Exact 20pt line spacing (fixed height).")
296 .line_spacing(20.0);
297
298 doc.add_paragraph("2.4 Pagination Controls")
299 .style("Heading2");
300 doc.add_paragraph("keep_with_next — this paragraph stays with the next")
301 .keep_with_next(true);
302 doc.add_paragraph("(This paragraph was kept with the one above.)");
303 doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
304 .keep_together(true);
305 doc.add_paragraph("widow_control — prevents widow/orphan lines")
306 .widow_control(true);
307
308 // ── SECTION 3: LISTS ──
309 doc.add_paragraph("").page_break_before(true);
310 doc.add_paragraph("3. Lists").style("Heading1");
311
312 doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
313 doc.add_bullet_list_item("First item", 0);
314 doc.add_bullet_list_item("Second item", 0);
315 doc.add_bullet_list_item("Nested level 1", 1);
316 doc.add_bullet_list_item("Nested level 2", 2);
317 doc.add_bullet_list_item("Back to level 1", 1);
318 doc.add_bullet_list_item("Third item", 0);
319
320 doc.add_paragraph("");
321
322 doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
323 doc.add_numbered_list_item("First numbered item", 0);
324 doc.add_numbered_list_item("Second numbered item", 0);
325 doc.add_numbered_list_item("Sub-item A", 1);
326 doc.add_numbered_list_item("Sub-item B", 1);
327 doc.add_numbered_list_item("Third numbered item", 0);
328
329 // ── SECTION 4: TAB STOPS ──
330 doc.add_paragraph("");
331 doc.add_paragraph("4. Tab Stops").style("Heading1");
332
333 doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
334 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
335 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
336 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
337 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
338 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
339
340 doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
341 doc.add_paragraph("Item\t\tPrice")
342 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
343 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
344 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
345 doc.add_paragraph("Widget\t\t$19.99")
346 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
347 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
348 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
349 doc.add_paragraph("Gadget\t\t$249.50")
350 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
351 .add_tab_stop_with_leader(
352 TabAlignment::Right,
353 Length::inches(4.0),
354 TabLeader::Underscore,
355 )
356 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
357
358 // ── SECTION 5: TABLES ──
359 doc.add_paragraph("").page_break_before(true);
360 doc.add_paragraph("5. Tables").style("Heading1");
361
362 doc.add_paragraph("5.1 Basic Table").style("Heading2");
363 {
364 let mut tbl = doc
365 .add_table(4, 3)
366 .borders(BorderStyle::Single, 4, "000000");
367 for col in 0..3 {
368 tbl.cell(0, col).unwrap().shading("2E75B6");
369 }
370 tbl.cell(0, 0).unwrap().set_text("Name");
371 tbl.cell(0, 1).unwrap().set_text("Role");
372 tbl.cell(0, 2).unwrap().set_text("Location");
373 tbl.cell(1, 0).unwrap().set_text("Walter White");
374 tbl.cell(1, 1).unwrap().set_text("CEO");
375 tbl.cell(1, 2).unwrap().set_text("Albuquerque");
376 tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
377 tbl.cell(2, 1).unwrap().set_text("CTO");
378 tbl.cell(2, 2).unwrap().set_text("Remote");
379 tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
380 tbl.cell(3, 1).unwrap().set_text("Security");
381 tbl.cell(3, 2).unwrap().set_text("Washington");
382 }
383
384 doc.add_paragraph("");
385
386 doc.add_paragraph("5.2 Column Span & Cell Shading")
387 .style("Heading2");
388 {
389 let mut tbl = doc
390 .add_table(3, 4)
391 .borders(BorderStyle::Single, 4, "000000")
392 .width_pct(100.0);
393 tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
394 tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
395 tbl.cell(1, 0).unwrap().set_text("Region");
396 tbl.cell(1, 0).unwrap().shading("D6E4F0");
397 tbl.cell(1, 1).unwrap().set_text("Q1");
398 tbl.cell(1, 1).unwrap().shading("D6E4F0");
399 tbl.cell(1, 2).unwrap().set_text("Q2");
400 tbl.cell(1, 2).unwrap().shading("D6E4F0");
401 tbl.cell(1, 3).unwrap().set_text("Total");
402 tbl.cell(1, 3).unwrap().shading("D6E4F0");
403 tbl.cell(2, 0).unwrap().set_text("Americas");
404 tbl.cell(2, 1).unwrap().set_text("$2.4M");
405 tbl.cell(2, 2).unwrap().set_text("$2.7M");
406 tbl.cell(2, 3).unwrap().set_text("$5.1M");
407 }
408
409 doc.add_paragraph("");
410
411 doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
412 {
413 let mut tbl = doc
414 .add_table(4, 3)
415 .borders(BorderStyle::Single, 4, "000000");
416 tbl.cell(0, 0).unwrap().set_text("Category");
417 tbl.cell(0, 0).unwrap().shading("E2EFDA");
418 tbl.cell(0, 1).unwrap().set_text("Item");
419 tbl.cell(0, 1).unwrap().shading("E2EFDA");
420 tbl.cell(0, 2).unwrap().set_text("Price");
421 tbl.cell(0, 2).unwrap().shading("E2EFDA");
422 tbl.cell(1, 0).unwrap().set_text("Hardware");
423 tbl.cell(1, 0).unwrap().v_merge_restart();
424 tbl.cell(1, 1).unwrap().set_text("Laptop");
425 tbl.cell(1, 2).unwrap().set_text("$1,200");
426 tbl.cell(2, 0).unwrap().v_merge_continue();
427 tbl.cell(2, 1).unwrap().set_text("Monitor");
428 tbl.cell(2, 2).unwrap().set_text("$450");
429 tbl.cell(3, 0).unwrap().set_text("Software");
430 tbl.cell(3, 1).unwrap().set_text("IDE License");
431 tbl.cell(3, 2).unwrap().set_text("$200/yr");
432 }
433
434 doc.add_paragraph("");
435
436 doc.add_paragraph("5.4 Nested Table").style("Heading2");
437 {
438 let mut tbl = doc
439 .add_table(2, 2)
440 .borders(BorderStyle::Single, 6, "2E75B6");
441 tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
442 tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
443 tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
444 {
445 let mut cell = tbl.cell(1, 1).unwrap();
446 cell.set_text("Nested table below:");
447 let mut nested = cell
448 .add_table(2, 2)
449 .borders(BorderStyle::Single, 2, "FF6600");
450 nested.cell(0, 0).unwrap().set_text("A");
451 nested.cell(0, 1).unwrap().set_text("B");
452 nested.cell(1, 0).unwrap().set_text("C");
453 nested.cell(1, 1).unwrap().set_text("D");
454 }
455 }
456
457 doc.add_paragraph("");
458
459 doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
460 .style("Heading2");
461 {
462 let mut tbl = doc
463 .add_table(2, 3)
464 .borders(BorderStyle::Single, 4, "666666");
465 tbl.row(0).unwrap().height(Length::pt(50.0)).header();
466 tbl.cell(0, 0).unwrap().set_text("Top");
467 tbl.cell(0, 0)
468 .unwrap()
469 .vertical_alignment(VerticalAlignment::Top)
470 .shading("FFF2CC");
471 tbl.cell(0, 1).unwrap().set_text("Center");
472 tbl.cell(0, 1)
473 .unwrap()
474 .vertical_alignment(VerticalAlignment::Center)
475 .shading("D9E2F3");
476 tbl.cell(0, 2).unwrap().set_text("Bottom");
477 tbl.cell(0, 2)
478 .unwrap()
479 .vertical_alignment(VerticalAlignment::Bottom)
480 .shading("E2EFDA");
481 tbl.row(1).unwrap().cant_split();
482 tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
483 tbl.cell(1, 0).unwrap().no_wrap();
484 tbl.cell(1, 1).unwrap().set_text("Fixed width");
485 tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
486 tbl.cell(1, 2).unwrap().set_text("Normal");
487 }
488
489 // ── SECTION 6: IMAGES ──
490 doc.add_paragraph("").page_break_before(true);
491 doc.add_paragraph("6. Images").style("Heading1");
492
493 doc.add_paragraph("6.1 Inline Image").style("Heading2");
494 doc.add_paragraph("A blue gradient image below:");
495 let img = create_sample_png(200, 50, [0, 80, 200]);
496 doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
497
498 doc.add_paragraph("");
499 doc.add_paragraph("6.2 Header Image").style("Heading2");
500 let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
501 doc.set_header_image(
502 &hdr_img,
503 "header_logo.png",
504 Length::inches(2.0),
505 Length::inches(0.2),
506 );
507 doc.add_paragraph("The header now contains an inline image (check the top of this page).");
508
509 doc.add_paragraph("");
510 doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
511
512 // ── SECTION 7: CONTENT MANIPULATION ──
513 doc.add_paragraph("").page_break_before(true);
514 doc.add_paragraph("7. Content Manipulation")
515 .style("Heading1");
516
517 doc.add_paragraph("7.1 Placeholder Replacement")
518 .style("Heading2");
519 doc.add_paragraph("Customer: {{customer}}");
520 doc.add_paragraph("Date: {{date}}");
521 doc.add_paragraph("Reference: {{ref_number}}");
522 {
523 let mut tbl = doc
524 .add_table(2, 2)
525 .borders(BorderStyle::Single, 4, "000000");
526 tbl.cell(0, 0).unwrap().set_text("Project");
527 tbl.cell(0, 0).unwrap().shading("D6E4F0");
528 tbl.cell(0, 1).unwrap().set_text("{{project}}");
529 tbl.cell(1, 0).unwrap().set_text("Status");
530 tbl.cell(1, 0).unwrap().shading("D6E4F0");
531 tbl.cell(1, 1).unwrap().set_text("{{status}}");
532 }
533
534 let mut replacements = HashMap::new();
535 replacements.insert("{{customer}}", "Tensorbee Inc.");
536 replacements.insert("{{date}}", "February 22, 2026");
537 replacements.insert("{{ref_number}}", "TB-2026-001");
538 replacements.insert("{{project}}", "Infrastructure Upgrade");
539 replacements.insert("{{status}}", "In Progress");
540 let n = doc.replace_all(&replacements);
541 doc.add_paragraph(&format!("({n} placeholders replaced above)"));
542
543 doc.add_paragraph("");
544
545 doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
546 doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
547 let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
548 doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
549
550 doc.add_paragraph("");
551
552 doc.add_paragraph("7.3 Content Insertion").style("Heading2");
553 doc.add_paragraph("Section A: First content.");
554 doc.add_paragraph("Section C: Third content.");
555 if let Some(idx) = doc.find_content_index("Section C") {
556 doc.insert_paragraph(
557 idx,
558 "Section B: Inserted between A and C via find_content_index().",
559 );
560 }
561
562 // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
563 doc.add_paragraph("").section_break(SectionBreak::NextPage);
564 doc.add_paragraph("8. Section Breaks & Orientation")
565 .style("Heading1");
566 doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
567
568 {
569 let mut tbl = doc
570 .add_table(3, 7)
571 .borders(BorderStyle::Single, 4, "2E75B6");
572 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
573 for (c, h) in headers.iter().enumerate() {
574 tbl.cell(0, c).unwrap().set_text(h);
575 tbl.cell(0, c).unwrap().shading("2E75B6");
576 }
577 let data = [
578 [
579 "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
580 ],
581 [
582 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
583 ],
584 ];
585 for (r, row) in data.iter().enumerate() {
586 for (c, v) in row.iter().enumerate() {
587 tbl.cell(r + 1, c).unwrap().set_text(v);
588 }
589 }
590 }
591
592 doc.add_paragraph("")
593 .section_break(SectionBreak::NextPage)
594 .section_landscape();
595
596 // ── SECTION 9: CUSTOM STYLES ──
597 doc.add_paragraph("9. Custom Styles").style("Heading1");
598 doc.add_style(
599 StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
600 .based_on("Normal")
601 .paragraph_properties(rdocx_oxml::properties::CT_PPr {
602 shading: Some(rdocx_oxml::properties::CT_Shd {
603 val: "clear".to_string(),
604 color: None,
605 fill: Some("FFF2CC".to_string()),
606 }),
607 ..Default::default()
608 })
609 .run_properties(rdocx_oxml::properties::CT_RPr {
610 bold: Some(true),
611 color: Some("C45911".to_string()),
612 ..Default::default()
613 }),
614 );
615 doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
616 .style("CustomHighlight");
617
618 doc.add_paragraph("");
619
620 // ── SECTION 10: DOCUMENT INTELLIGENCE ──
621 doc.add_paragraph("10. Document Intelligence API")
622 .style("Heading1");
623
624 let wc = doc.word_count();
625 let hc = doc.headings().len();
626 let ic = doc.images().len();
627 let lc = doc.links().len();
628 doc.add_paragraph(&format!("Word count: {wc}"));
629 doc.add_paragraph(&format!("Heading count: {hc}"));
630 doc.add_paragraph(&format!("Image count: {ic}"));
631 doc.add_paragraph(&format!("Link count: {lc}"));
632
633 let outline = doc.document_outline();
634 doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
635
636 let issues = doc.audit_accessibility();
637 doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
638 for issue in &issues {
639 doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
640 }
641
642 // ── SECTION 11: DOCUMENT MERGING ──
643 doc.add_paragraph("");
644 doc.add_paragraph("11. Document Merging").style("Heading1");
645 {
646 let mut other = Document::new();
647 other.add_paragraph("This paragraph was merged from another document using append().");
648 doc.append(&other);
649 }
650
651 doc.add_paragraph("");
652
653 // ── SUMMARY ──
654 doc.add_paragraph("Summary of Demonstrated Features")
655 .style("Heading1");
656 let features = [
657 "Page setup: size, margins, header/footer distance, gutter",
658 "Document metadata: title, author, subject, keywords",
659 "Headers and footers: text, images, different first page",
660 "Background images (full-page behind text)",
661 "Table of Contents generation with bookmarks",
662 "Text formatting: bold, italic, underline styles, strike, color, size, font",
663 "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
664 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
665 "Pagination controls: keep-with-next, keep-together, widow control",
666 "Bullet and numbered lists with nesting",
667 "Tab stops with dot/underscore/hyphen leaders",
668 "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
669 "Row properties: header rows, exact height, cant-split",
670 "Cell properties: width, no-wrap, vertical alignment",
671 "Inline images and header images",
672 "Placeholder replacement (single and batch)",
673 "Regex-based find and replace",
674 "Content insertion at specific positions",
675 "Section breaks with mixed portrait/landscape",
676 "Custom paragraph and character styles",
677 "Document intelligence: word count, headings, outline, images, links",
678 "Accessibility audit",
679 "Document merging (append)",
680 "Export: DOCX, PDF, HTML, Markdown",
681 ];
682 for f in &features {
683 doc.add_bullet_list_item(f, 0);
684 }
685 doc.add_paragraph("");
686 doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
687 .alignment(Alignment::Center)
688 .shading("E2EFDA")
689 .border_all(BorderStyle::Single, 2, "00AA00");
690
691 doc
692}
693
694// =============================================================================
695// 2. PROPOSAL DOCUMENT — Deep navy + gold scheme
696// =============================================================================
697fn generate_proposal(_samples_dir: &Path) -> Document {
698 let mut doc = Document::new();
699
700 // Colors: Navy #1B2A4A, Gold #C5922E, Light #F4F1EB
701 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
702 doc.set_margins(
703 Length::inches(1.0),
704 Length::inches(1.0),
705 Length::inches(1.0),
706 Length::inches(1.0),
707 );
708 doc.set_title("AI Platform Modernization Proposal");
709 doc.set_author("Walter White");
710 doc.set_subject("Technology Proposal");
711 doc.set_keywords("proposal, AI, modernization, Tensorbee");
712
713 // Banner header
714 let logo_img = create_logo_png(220, 48);
715 let banner = build_header_banner_xml(
716 "rId1",
717 &BannerOpts {
718 bg_color: "1B2A4A",
719 banner_width: 7772400,
720 banner_height: 914400,
721 logo_width: 2011680,
722 logo_height: 438912,
723 logo_x_offset: 295125,
724 logo_y_offset: 237744,
725 },
726 );
727 doc.set_raw_header_with_images(
728 banner,
729 &[("rId1", &logo_img, "logo.png")],
730 rdocx_oxml::header_footer::HdrFtrType::Default,
731 );
732 doc.set_different_first_page(true);
733 let cover_banner = build_header_banner_xml(
734 "rId1",
735 &BannerOpts {
736 bg_color: "C5922E",
737 banner_width: 7772400,
738 banner_height: 914400,
739 logo_width: 2011680,
740 logo_height: 438912,
741 logo_x_offset: 295125,
742 logo_y_offset: 237744,
743 },
744 );
745 doc.set_raw_header_with_images(
746 cover_banner,
747 &[("rId1", &logo_img, "logo.png")],
748 rdocx_oxml::header_footer::HdrFtrType::First,
749 );
750 doc.set_margins(
751 Length::twips(2292),
752 Length::twips(1440),
753 Length::twips(1440),
754 Length::twips(1440),
755 );
756 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
757 doc.set_footer("Tensorbee — Confidential");
758
759 // Custom styles
760 doc.add_style(
761 StyleBuilder::paragraph("ProposalTitle", "Proposal Title")
762 .based_on("Normal")
763 .run_properties(rdocx_oxml::properties::CT_RPr {
764 bold: Some(true),
765 font_ascii: Some("Georgia".to_string()),
766 font_hansi: Some("Georgia".to_string()),
767 sz: Some(rdocx_oxml::HalfPoint(56)), // half-points → 28pt
768 color: Some("1B2A4A".to_string()),
769 ..Default::default()
770 }),
771 );
772
773 // ── Cover Page ──
774 for _ in 0..4 {
775 doc.add_paragraph("");
776 }
777 doc.add_paragraph("AI Platform Modernization")
778 .style("ProposalTitle")
779 .alignment(Alignment::Center);
780 doc.add_paragraph("")
781 .alignment(Alignment::Center)
782 .border_bottom(BorderStyle::Single, 8, "C5922E");
783 {
784 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
785 p.add_run("Prepared for Global Financial Corp.")
786 .size(14.0)
787 .color("666666")
788 .italic(true);
789 }
790 {
791 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
792 p.add_run("Prepared by: Walter White, CEO — Tensorbee")
793 .size(12.0)
794 .color("888888");
795 }
796 {
797 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
798 p.add_run("February 22, 2026").size(12.0).color("888888");
799 }
800
801 // ── TOC ──
802 doc.add_paragraph("").page_break_before(true);
803 doc.add_paragraph("Table of Contents").style("Heading1");
804 // We'll add a manual-style TOC since the insert_toc goes at a specific position
805 doc.insert_toc(doc.content_count(), 2);
806
807 // ── Executive Summary ──
808 doc.add_paragraph("").page_break_before(true);
809 doc.add_paragraph("Executive Summary").style("Heading1");
810 doc.add_paragraph(
811 "Tensorbee proposes a comprehensive modernization of Global Financial Corp's AI platform, \
812 transitioning from legacy batch-processing systems to a real-time inference architecture. \
813 This transformation will reduce model deployment time from weeks to hours, improve \
814 prediction accuracy by an estimated 23%, and deliver $4.2M in annual operational savings.",
815 )
816 .first_line_indent(Length::inches(0.3));
817 doc.add_paragraph(
818 "The project spans three phases over 18 months, with a total investment of $2.8M. \
819 Tensorbee brings deep expertise in ML infrastructure, having successfully completed \
820 similar transformations for three Fortune 500 financial institutions.",
821 )
822 .first_line_indent(Length::inches(0.3));
823
824 // Key metrics table
825 doc.add_paragraph("");
826 {
827 let mut tbl = doc
828 .add_table(5, 2)
829 .borders(BorderStyle::Single, 4, "1B2A4A")
830 .width_pct(80.0);
831 tbl.cell(0, 0).unwrap().set_text("Key Metric");
832 tbl.cell(0, 0).unwrap().shading("1B2A4A");
833 tbl.cell(0, 1).unwrap().set_text("Value");
834 tbl.cell(0, 1).unwrap().shading("1B2A4A");
835 let rows = [
836 ("Total Investment", "$2.8M"),
837 ("Annual Savings", "$4.2M"),
838 ("ROI (Year 1)", "150%"),
839 ("Timeline", "18 months"),
840 ];
841 for (i, (k, v)) in rows.iter().enumerate() {
842 tbl.cell(i + 1, 0).unwrap().set_text(k);
843 if i % 2 == 0 {
844 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
845 }
846 tbl.cell(i + 1, 1).unwrap().set_text(v);
847 if i % 2 == 0 {
848 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
849 }
850 }
851 }
852
853 // ── Problem Statement ──
854 doc.add_paragraph("").page_break_before(true);
855 doc.add_paragraph("Problem Statement").style("Heading1");
856 doc.add_paragraph(
857 "Global Financial Corp's current AI infrastructure faces three critical challenges:",
858 );
859 doc.add_numbered_list_item("Deployment Latency — New models take 3-4 weeks to deploy to production, compared to the industry benchmark of 2-3 days.", 0);
860 doc.add_numbered_list_item("Scalability Constraints — The batch processing architecture cannot handle real-time inference demands during peak trading hours.", 0);
861 doc.add_numbered_list_item("Technical Debt — Legacy Python 2.7 codebases and manual deployment scripts create reliability risks and slow iteration speed.", 0);
862
863 // ── Proposed Solution ──
864 doc.add_paragraph("").page_break_before(true);
865 doc.add_paragraph("Proposed Solution").style("Heading1");
866
867 doc.add_paragraph("Phase 1: Foundation (Months 1-6)")
868 .style("Heading2");
869 doc.add_bullet_list_item("Deploy Kubernetes-based ML serving infrastructure", 0);
870 doc.add_bullet_list_item("Implement CI/CD pipeline for model deployment", 0);
871 doc.add_bullet_list_item("Migrate top 5 production models to new platform", 0);
872
873 doc.add_paragraph("Phase 2: Expansion (Months 7-12)")
874 .style("Heading2");
875 doc.add_bullet_list_item(
876 "Real-time feature engineering pipeline (Apache Kafka + Flink)",
877 0,
878 );
879 doc.add_bullet_list_item("A/B testing framework for model variants", 0);
880 doc.add_bullet_list_item("Automated model monitoring and drift detection", 0);
881
882 doc.add_paragraph("Phase 3: Optimization (Months 13-18)")
883 .style("Heading2");
884 doc.add_bullet_list_item("GPU inference optimization (TensorRT/ONNX Runtime)", 0);
885 doc.add_bullet_list_item("Multi-region deployment for latency reduction", 0);
886 doc.add_bullet_list_item(
887 "Self-service model deployment portal for data science teams",
888 0,
889 );
890
891 // ── Budget ──
892 doc.add_paragraph("Budget Breakdown").style("Heading1");
893 {
894 let mut tbl = doc
895 .add_table(6, 3)
896 .borders(BorderStyle::Single, 4, "1B2A4A")
897 .width_pct(100.0);
898 tbl.cell(0, 0).unwrap().set_text("Category");
899 tbl.cell(0, 0).unwrap().shading("1B2A4A");
900 tbl.cell(0, 1).unwrap().set_text("Cost");
901 tbl.cell(0, 1).unwrap().shading("1B2A4A");
902 tbl.cell(0, 2).unwrap().set_text("Notes");
903 tbl.cell(0, 2).unwrap().shading("1B2A4A");
904 let items = [
905 (
906 "Infrastructure",
907 "$450,000",
908 "Cloud compute + GPU instances",
909 ),
910 ("Engineering Services", "$1,600,000", "12 FTE x 18 months"),
911 (
912 "Software Licenses",
913 "$350,000",
914 "Kafka, monitoring, CI/CD tools",
915 ),
916 (
917 "Training & Enablement",
918 "$200,000",
919 "Team training, documentation",
920 ),
921 ("Contingency (10%)", "$200,000", "Risk buffer"),
922 ];
923 for (i, (cat, cost, note)) in items.iter().enumerate() {
924 tbl.cell(i + 1, 0).unwrap().set_text(cat);
925 tbl.cell(i + 1, 1).unwrap().set_text(cost);
926 tbl.cell(i + 1, 2).unwrap().set_text(note);
927 if i % 2 == 0 {
928 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
929 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
930 tbl.cell(i + 1, 2).unwrap().shading("F4F1EB");
931 }
932 }
933 }
934
935 // ── Closing ──
936 doc.add_paragraph("");
937 doc.add_paragraph("Next Steps").style("Heading1");
938 doc.add_numbered_list_item(
939 "Schedule technical deep-dive with GFC engineering team (Week 1)",
940 0,
941 );
942 doc.add_numbered_list_item("Finalize scope and sign SOW (Week 2-3)", 0);
943 doc.add_numbered_list_item("Kick off Phase 1 with joint planning session (Week 4)", 0);
944
945 doc.add_paragraph("");
946 {
947 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
948 p.add_run("Walter White")
949 .bold(true)
950 .size(14.0)
951 .color("1B2A4A");
952 }
953 {
954 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
955 p.add_run("CEO, Tensorbee | walter@tensorbee.com")
956 .size(10.0)
957 .color("888888");
958 }
959
960 doc
961}
962
963// =============================================================================
964// 3. QUOTE / BILL OF MATERIALS — Teal + orange scheme
965// =============================================================================
966fn generate_quote(_samples_dir: &Path) -> Document {
967 let mut doc = Document::new();
968
969 // Colors: Teal #008B8B, Dark #1A3C3C, Orange #E07020, Light #F0F8F8
970 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
971 doc.set_margins(
972 Length::inches(0.75),
973 Length::inches(0.75),
974 Length::inches(0.75),
975 Length::inches(0.75),
976 );
977 doc.set_title("Quotation QT-2026-0147");
978 doc.set_author("Walter White");
979 doc.set_keywords("quote, BOM, Tensorbee");
980
981 doc.set_header("Tensorbee — Quotation");
982 doc.set_footer("QT-2026-0147 | Page");
983
984 // ── Header Block ──
985 {
986 let mut p = doc.add_paragraph("").alignment(Alignment::Left);
987 p.add_run("TENSORBEE")
988 .bold(true)
989 .size(28.0)
990 .color("008B8B")
991 .font("Helvetica");
992 }
993 doc.add_paragraph("123 Innovation Drive, Suite 400")
994 .alignment(Alignment::Left);
995 doc.add_paragraph("San Francisco, CA 94105 | +1 (415) 555-0199")
996 .alignment(Alignment::Left);
997 doc.add_paragraph("")
998 .border_bottom(BorderStyle::Single, 8, "008B8B");
999
1000 // Quote meta
1001 doc.add_paragraph("");
1002 {
1003 let mut tbl = doc.add_table(4, 4).width_pct(100.0);
1004 tbl.cell(0, 0).unwrap().set_text("QUOTATION");
1005 tbl.cell(0, 0).unwrap().shading("008B8B").grid_span(2);
1006 tbl.cell(0, 2).unwrap().set_text("DATE");
1007 tbl.cell(0, 2).unwrap().shading("008B8B");
1008 tbl.cell(0, 3).unwrap().set_text("VALID UNTIL");
1009 tbl.cell(0, 3).unwrap().shading("008B8B");
1010 tbl.cell(1, 0).unwrap().set_text("Quote #:");
1011 tbl.cell(1, 1).unwrap().set_text("QT-2026-0147");
1012 tbl.cell(1, 2).unwrap().set_text("Feb 22, 2026");
1013 tbl.cell(1, 3).unwrap().set_text("Mar 22, 2026");
1014 tbl.cell(2, 0).unwrap().set_text("Prepared by:");
1015 tbl.cell(2, 1).unwrap().set_text("Walter White");
1016 tbl.cell(2, 2).unwrap().set_text("Payment:");
1017 tbl.cell(2, 3).unwrap().set_text("Net 30");
1018 tbl.cell(3, 0).unwrap().set_text("Customer:");
1019 tbl.cell(3, 1).unwrap().set_text("Meridian Dynamics LLC");
1020 tbl.cell(3, 2).unwrap().set_text("Currency:");
1021 tbl.cell(3, 3).unwrap().set_text("USD");
1022 }
1023
1024 doc.add_paragraph("");
1025
1026 // ── Bill of Materials ──
1027 {
1028 let mut p = doc.add_paragraph("");
1029 p.add_run("Bill of Materials")
1030 .bold(true)
1031 .size(16.0)
1032 .color("1A3C3C");
1033 }
1034 doc.add_paragraph("");
1035
1036 {
1037 let mut tbl = doc
1038 .add_table(10, 6)
1039 .borders(BorderStyle::Single, 2, "008B8B")
1040 .width_pct(100.0)
1041 .layout_fixed();
1042 // Headers
1043 let hdrs = [
1044 "#",
1045 "Part Number",
1046 "Description",
1047 "Qty",
1048 "Unit Price",
1049 "Total",
1050 ];
1051 for (c, h) in hdrs.iter().enumerate() {
1052 tbl.cell(0, c).unwrap().set_text(h);
1053 tbl.cell(0, c).unwrap().shading("008B8B");
1054 }
1055
1056 let items: Vec<(&str, &str, &str, &str, &str)> = vec![
1057 (
1058 "TB-GPU-A100",
1059 "NVIDIA A100 80GB GPU",
1060 "4",
1061 "$12,500.00",
1062 "$50,000.00",
1063 ),
1064 (
1065 "TB-SRV-R750",
1066 "Dell R750xa Server Chassis",
1067 "2",
1068 "$8,200.00",
1069 "$16,400.00",
1070 ),
1071 (
1072 "TB-RAM-256G",
1073 "256GB DDR5 ECC Memory Module",
1074 "8",
1075 "$890.00",
1076 "$7,120.00",
1077 ),
1078 (
1079 "TB-SSD-3840",
1080 "3.84TB NVMe U.2 SSD",
1081 "8",
1082 "$1,150.00",
1083 "$9,200.00",
1084 ),
1085 (
1086 "TB-NET-CX7",
1087 "ConnectX-7 200GbE NIC",
1088 "4",
1089 "$1,800.00",
1090 "$7,200.00",
1091 ),
1092 (
1093 "TB-SW-48P",
1094 "48-Port 100GbE Switch",
1095 "1",
1096 "$22,000.00",
1097 "$22,000.00",
1098 ),
1099 (
1100 "TB-CAB-RACK",
1101 "42U Server Rack + PDU",
1102 "1",
1103 "$4,500.00",
1104 "$4,500.00",
1105 ),
1106 (
1107 "TB-SVC-INST",
1108 "Installation & Configuration",
1109 "1",
1110 "$8,500.00",
1111 "$8,500.00",
1112 ),
1113 (
1114 "TB-SVC-SUPP",
1115 "3-Year Premium Support",
1116 "1",
1117 "$15,000.00",
1118 "$15,000.00",
1119 ),
1120 ];
1121
1122 for (i, (pn, desc, qty, unit, total)) in items.iter().enumerate() {
1123 let row = i + 1;
1124 tbl.cell(row, 0).unwrap().set_text(&format!("{}", i + 1));
1125 tbl.cell(row, 1).unwrap().set_text(pn);
1126 tbl.cell(row, 2).unwrap().set_text(desc);
1127 tbl.cell(row, 3).unwrap().set_text(qty);
1128 tbl.cell(row, 4).unwrap().set_text(unit);
1129 tbl.cell(row, 5).unwrap().set_text(total);
1130 if i % 2 == 0 {
1131 for c in 0..6 {
1132 tbl.cell(row, c).unwrap().shading("F0F8F8");
1133 }
1134 }
1135 }
1136 }
1137
1138 doc.add_paragraph("");
1139
1140 // ── Totals ──
1141 {
1142 let mut tbl = doc
1143 .add_table(4, 2)
1144 .borders(BorderStyle::Single, 2, "008B8B")
1145 .width(Length::inches(3.5))
1146 .alignment(Alignment::Right);
1147 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1148 tbl.cell(0, 1).unwrap().set_text("$139,920.00");
1149 tbl.cell(1, 0).unwrap().set_text("Shipping & Handling");
1150 tbl.cell(1, 1).unwrap().set_text("$2,500.00");
1151 tbl.cell(2, 0).unwrap().set_text("Tax (8.625%)");
1152 tbl.cell(2, 1).unwrap().set_text("$12,068.10");
1153 tbl.cell(3, 0).unwrap().set_text("TOTAL");
1154 tbl.cell(3, 0).unwrap().shading("E07020");
1155 tbl.cell(3, 1).unwrap().set_text("$154,488.10");
1156 tbl.cell(3, 1).unwrap().shading("E07020");
1157 }
1158
1159 doc.add_paragraph("");
1160
1161 // ── Terms & Conditions ──
1162 {
1163 let mut p = doc.add_paragraph("");
1164 p.add_run("Terms & Conditions")
1165 .bold(true)
1166 .size(14.0)
1167 .color("1A3C3C");
1168 }
1169 doc.add_numbered_list_item(
1170 "This quotation is valid for 30 calendar days from the date of issue.",
1171 0,
1172 );
1173 doc.add_numbered_list_item(
1174 "All prices are in USD and exclusive of applicable taxes unless stated.",
1175 0,
1176 );
1177 doc.add_numbered_list_item("Standard lead time is 4-6 weeks from PO receipt.", 0);
1178 doc.add_numbered_list_item(
1179 "Payment terms: Net 30 from invoice date. 2% discount for payment within 10 days.",
1180 0,
1181 );
1182 doc.add_numbered_list_item(
1183 "Warranty: 3-year manufacturer warranty on all hardware components.",
1184 0,
1185 );
1186 doc.add_numbered_list_item(
1187 "Returns subject to 15% restocking fee if initiated after 14 days.",
1188 0,
1189 );
1190
1191 doc.add_paragraph("");
1192 doc.add_paragraph("")
1193 .border_bottom(BorderStyle::Single, 4, "008B8B");
1194 doc.add_paragraph("");
1195 {
1196 let mut p = doc.add_paragraph("");
1197 p.add_run("Accepted by: ").bold(true);
1198 p.add_run("___________________________ Date: ___________");
1199 }
1200 {
1201 let mut p = doc.add_paragraph("");
1202 p.add_run("Print Name: ").bold(true);
1203 p.add_run("___________________________ Title: ___________");
1204 }
1205
1206 doc
1207}
1208
1209// =============================================================================
1210// 4. INVOICE — Crimson + charcoal scheme
1211// =============================================================================
1212fn generate_invoice(_samples_dir: &Path) -> Document {
1213 let mut doc = Document::new();
1214
1215 // Colors: Crimson #B22222, Charcoal #333333, Light #FAF0F0
1216 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1217 doc.set_margins(
1218 Length::inches(0.75),
1219 Length::inches(0.75),
1220 Length::inches(0.75),
1221 Length::inches(0.75),
1222 );
1223 doc.set_title("Invoice INV-2026-0392");
1224 doc.set_author("Walter White");
1225 doc.set_keywords("invoice, Tensorbee");
1226
1227 doc.set_footer("Tensorbee — Thank you for your business!");
1228
1229 // ── Company & Invoice Header ──
1230 {
1231 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1232 // Company name left, INVOICE right
1233 {
1234 let mut cell = tbl.cell(0, 0).unwrap();
1235 let mut p = cell.add_paragraph("");
1236 p.add_run("TENSORBEE")
1237 .bold(true)
1238 .size(32.0)
1239 .color("B22222")
1240 .font("Helvetica");
1241 }
1242 {
1243 let mut cell = tbl.cell(0, 1).unwrap();
1244 let mut p = cell.add_paragraph("");
1245 p.add_run("INVOICE").bold(true).size(32.0).color("333333");
1246 }
1247 tbl.cell(1, 0)
1248 .unwrap()
1249 .set_text("123 Innovation Drive, Suite 400");
1250 tbl.cell(1, 1).unwrap().set_text("Invoice #: INV-2026-0392");
1251 tbl.cell(2, 0).unwrap().set_text("San Francisco, CA 94105");
1252 tbl.cell(2, 1).unwrap().set_text("Date: February 22, 2026");
1253 tbl.cell(3, 0).unwrap().set_text("walter@tensorbee.com");
1254 tbl.cell(3, 1).unwrap().set_text("Due Date: March 24, 2026");
1255 }
1256
1257 doc.add_paragraph("")
1258 .border_bottom(BorderStyle::Thick, 8, "B22222");
1259 doc.add_paragraph("");
1260
1261 // ── Bill To / Ship To ──
1262 {
1263 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1264 tbl.cell(0, 0).unwrap().set_text("BILL TO");
1265 tbl.cell(0, 0).unwrap().shading("B22222");
1266 tbl.cell(0, 1).unwrap().set_text("SHIP TO");
1267 tbl.cell(0, 1).unwrap().shading("B22222");
1268 tbl.cell(1, 0).unwrap().set_text("Meridian Dynamics LLC");
1269 tbl.cell(1, 1).unwrap().set_text("Meridian Dynamics LLC");
1270 tbl.cell(2, 0).unwrap().set_text("456 Enterprise Blvd");
1271 tbl.cell(2, 1).unwrap().set_text("Attn: Server Room B");
1272 tbl.cell(3, 0).unwrap().set_text("Austin, TX 78701");
1273 tbl.cell(3, 1)
1274 .unwrap()
1275 .set_text("456 Enterprise Blvd, Austin, TX 78701");
1276 }
1277
1278 doc.add_paragraph("");
1279
1280 // ── Line Items ──
1281 {
1282 let mut tbl = doc
1283 .add_table(8, 5)
1284 .borders(BorderStyle::Single, 2, "B22222")
1285 .width_pct(100.0);
1286 let hdrs = ["Description", "Qty", "Unit Price", "Tax", "Amount"];
1287 for (c, h) in hdrs.iter().enumerate() {
1288 tbl.cell(0, c).unwrap().set_text(h);
1289 tbl.cell(0, c).unwrap().shading("B22222");
1290 }
1291 let items = [
1292 (
1293 "ML Infrastructure Setup — Phase 1",
1294 "1",
1295 "$45,000.00",
1296 "$3,881.25",
1297 "$48,881.25",
1298 ),
1299 (
1300 "Data Pipeline Development (160 hrs)",
1301 "160",
1302 "$225.00",
1303 "$3,105.00",
1304 "$39,105.00",
1305 ),
1306 (
1307 "GPU Cluster Configuration",
1308 "1",
1309 "$12,000.00",
1310 "$1,035.00",
1311 "$13,035.00",
1312 ),
1313 (
1314 "API Gateway Implementation",
1315 "1",
1316 "$18,500.00",
1317 "$1,595.63",
1318 "$20,095.63",
1319 ),
1320 (
1321 "Load Testing & QA (80 hrs)",
1322 "80",
1323 "$195.00",
1324 "$1,345.50",
1325 "$16,945.50",
1326 ),
1327 (
1328 "Documentation & Training",
1329 "1",
1330 "$8,000.00",
1331 "$690.00",
1332 "$8,690.00",
1333 ),
1334 (
1335 "Project Management (3 months)",
1336 "3",
1337 "$6,500.00",
1338 "$1,679.63",
1339 "$21,179.63",
1340 ),
1341 ];
1342 for (i, (desc, qty, unit, tax, amt)) in items.iter().enumerate() {
1343 let r = i + 1;
1344 tbl.cell(r, 0).unwrap().set_text(desc);
1345 tbl.cell(r, 1).unwrap().set_text(qty);
1346 tbl.cell(r, 2).unwrap().set_text(unit);
1347 tbl.cell(r, 3).unwrap().set_text(tax);
1348 tbl.cell(r, 4).unwrap().set_text(amt);
1349 if i % 2 == 0 {
1350 for c in 0..5 {
1351 tbl.cell(r, c).unwrap().shading("FAF0F0");
1352 }
1353 }
1354 }
1355 }
1356
1357 doc.add_paragraph("");
1358
1359 // ── Totals ──
1360 {
1361 let mut tbl = doc
1362 .add_table(4, 2)
1363 .borders(BorderStyle::Single, 2, "B22222")
1364 .width(Length::inches(3.0))
1365 .alignment(Alignment::Right);
1366 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1367 tbl.cell(0, 1).unwrap().set_text("$154,600.00");
1368 tbl.cell(1, 0).unwrap().set_text("Tax (8.625%)");
1369 tbl.cell(1, 1).unwrap().set_text("$13,334.25");
1370 tbl.cell(2, 0).unwrap().set_text("Discount (5%)");
1371 tbl.cell(2, 1).unwrap().set_text("-$7,730.00");
1372 tbl.cell(3, 0).unwrap().set_text("AMOUNT DUE");
1373 tbl.cell(3, 0).unwrap().shading("B22222");
1374 tbl.cell(3, 1).unwrap().set_text("$160,204.25");
1375 tbl.cell(3, 1).unwrap().shading("B22222");
1376 }
1377
1378 doc.add_paragraph("");
1379 doc.add_paragraph("");
1380
1381 // ── Payment Details ──
1382 {
1383 let mut p = doc.add_paragraph("");
1384 p.add_run("Payment Details")
1385 .bold(true)
1386 .size(14.0)
1387 .color("333333");
1388 }
1389 doc.add_paragraph("Bank: Silicon Valley Bank")
1390 .indent_left(Length::inches(0.3));
1391 doc.add_paragraph("Account: Tensorbee Inc. — 0847-2953-1120")
1392 .indent_left(Length::inches(0.3));
1393 doc.add_paragraph("Routing: 121140399")
1394 .indent_left(Length::inches(0.3));
1395 doc.add_paragraph("Swift: SVBKUS6S")
1396 .indent_left(Length::inches(0.3));
1397
1398 doc.add_paragraph("");
1399
1400 doc.add_paragraph("Please include invoice number INV-2026-0392 in the payment reference.")
1401 .shading("FAF0F0")
1402 .border_all(BorderStyle::Single, 2, "B22222");
1403
1404 doc
1405}
1406
1407// =============================================================================
1408// 5. REPORT — Forest green + earth tones, with images & hierarchical sections
1409// =============================================================================
1410fn generate_report(_samples_dir: &Path) -> Document {
1411 let mut doc = Document::new();
1412
1413 // Colors: Forest #2D5016, Sage #6B8E23, Earth #8B7355, Cream #FFFAF0
1414 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1415 doc.set_margins(
1416 Length::inches(1.0),
1417 Length::inches(1.0),
1418 Length::inches(1.0),
1419 Length::inches(1.0),
1420 );
1421 doc.set_title("Q4 2025 Environmental Impact Report");
1422 doc.set_author("Walter White");
1423 doc.set_subject("Quarterly Environmental Report");
1424 doc.set_keywords("environment, sustainability, report, Tensorbee");
1425
1426 doc.set_different_first_page(true);
1427 doc.set_first_page_header("");
1428 doc.set_header("Tensorbee — Q4 2025 Environmental Impact Report");
1429 doc.set_footer("CONFIDENTIAL — Page");
1430
1431 // ── Cover ──
1432 let cover_bg = create_sample_png(612, 792, [20, 50, 15]);
1433 doc.add_background_image(&cover_bg, "report_cover.png");
1434
1435 for _ in 0..5 {
1436 doc.add_paragraph("");
1437 }
1438 {
1439 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1440 p.add_run("Q4 2025")
1441 .bold(true)
1442 .size(48.0)
1443 .color("FFFFFF")
1444 .font("Georgia");
1445 }
1446 {
1447 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1448 p.add_run("Environmental Impact Report")
1449 .size(24.0)
1450 .color("90EE90")
1451 .font("Georgia")
1452 .italic(true);
1453 }
1454 doc.add_paragraph("");
1455 {
1456 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1457 p.add_run("Tensorbee — Sustainability Division")
1458 .size(14.0)
1459 .color("C0C0C0");
1460 }
1461 {
1462 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1463 p.add_run("Prepared by Walter White, Chief Sustainability Officer")
1464 .size(11.0)
1465 .color("AAAAAA");
1466 }
1467
1468 // ── TOC ──
1469 doc.add_paragraph("").page_break_before(true);
1470 doc.insert_toc(doc.content_count(), 3);
1471
1472 // ── Section 1: Executive Overview ──
1473 doc.add_paragraph("").page_break_before(true);
1474 doc.add_paragraph("Executive Overview").style("Heading1");
1475 doc.add_paragraph(
1476 "This report presents Tensorbee's environmental performance for Q4 2025. Our \
1477 sustainability initiatives have yielded a 34% reduction in carbon emissions compared \
1478 to Q4 2024, exceeding our target of 25%. Key achievements include the transition to \
1479 100% renewable energy in our primary data centers and the launch of our carbon offset \
1480 marketplace.",
1481 )
1482 .first_line_indent(Length::inches(0.3));
1483
1484 // Image: performance chart
1485 let chart_img = create_chart_png(400, 200);
1486 doc.add_paragraph("");
1487 doc.add_picture(
1488 &chart_img,
1489 "performance_chart.png",
1490 Length::inches(5.0),
1491 Length::inches(2.5),
1492 )
1493 .alignment(Alignment::Center);
1494 {
1495 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1496 p.add_run("Figure 1: Quarterly Carbon Emissions (tonnes CO2e)")
1497 .italic(true)
1498 .size(9.0)
1499 .color("666666");
1500 }
1501
1502 // ── Section 2: Energy Consumption ──
1503 doc.add_paragraph("").page_break_before(true);
1504 doc.add_paragraph("Energy Consumption").style("Heading1");
1505
1506 doc.add_paragraph("Data Center Operations")
1507 .style("Heading2");
1508 doc.add_paragraph(
1509 "Our three primary data centers consumed a combined 4.2 GWh in Q4 2025, \
1510 a 12% reduction from Q3 2025 driven by improved cooling efficiency and \
1511 server consolidation.",
1512 );
1513
1514 // Data table
1515 {
1516 let mut tbl = doc
1517 .add_table(5, 4)
1518 .borders(BorderStyle::Single, 4, "2D5016")
1519 .width_pct(100.0);
1520 let hdrs = ["Data Center", "Capacity (MW)", "Usage (GWh)", "PUE"];
1521 for (c, h) in hdrs.iter().enumerate() {
1522 tbl.cell(0, c).unwrap().set_text(h);
1523 tbl.cell(0, c).unwrap().shading("2D5016");
1524 }
1525 let rows = [
1526 ("San Francisco (Primary)", "3.2", "1.8", "1.12"),
1527 ("Dublin (EU)", "2.1", "1.2", "1.18"),
1528 ("Singapore (APAC)", "1.8", "1.2", "1.24"),
1529 ("TOTAL", "7.1", "4.2", "1.17 avg"),
1530 ];
1531 for (i, (dc, cap, use_, pue)) in rows.iter().enumerate() {
1532 tbl.cell(i + 1, 0).unwrap().set_text(dc);
1533 tbl.cell(i + 1, 1).unwrap().set_text(cap);
1534 tbl.cell(i + 1, 2).unwrap().set_text(use_);
1535 tbl.cell(i + 1, 3).unwrap().set_text(pue);
1536 if i == 3 {
1537 for c in 0..4 {
1538 tbl.cell(i + 1, c).unwrap().shading("E2EFDA");
1539 }
1540 }
1541 }
1542 }
1543
1544 doc.add_paragraph("");
1545
1546 doc.add_paragraph("Renewable Energy Mix").style("Heading2");
1547 doc.add_paragraph("Breakdown of energy sources across all facilities:");
1548
1549 doc.add_bullet_list_item("Solar PV: 42% (1.76 GWh)", 0);
1550 doc.add_bullet_list_item("Wind Power Purchase Agreements: 38% (1.60 GWh)", 0);
1551 doc.add_bullet_list_item("Hydroelectric: 12% (0.50 GWh)", 0);
1552 doc.add_bullet_list_item("Grid (non-renewable): 8% (0.34 GWh)", 0);
1553
1554 // ── Section 3: Water & Waste ──
1555 doc.add_paragraph("Water & Waste Management")
1556 .style("Heading1");
1557
1558 doc.add_paragraph("Water Usage").style("Heading2");
1559 doc.add_paragraph(
1560 "Total water consumption was 12.4 million gallons, a 15% reduction from Q3. \
1561 Our closed-loop cooling systems now recycle 78% of water used in cooling operations.",
1562 );
1563
1564 doc.add_paragraph("Waste Reduction").style("Heading2");
1565 doc.add_paragraph("E-waste management results for Q4:")
1566 .keep_with_next(true);
1567 {
1568 let mut tbl = doc
1569 .add_table(4, 3)
1570 .borders(BorderStyle::Single, 2, "6B8E23")
1571 .width_pct(80.0);
1572 tbl.cell(0, 0).unwrap().set_text("Category");
1573 tbl.cell(0, 0).unwrap().shading("6B8E23");
1574 tbl.cell(0, 1).unwrap().set_text("Weight (kg)");
1575 tbl.cell(0, 1).unwrap().shading("6B8E23");
1576 tbl.cell(0, 2).unwrap().set_text("Recycled %");
1577 tbl.cell(0, 2).unwrap().shading("6B8E23");
1578 let rows = [
1579 ("Server Hardware", "2,450", "94%"),
1580 ("Networking Equipment", "820", "91%"),
1581 ("Storage Media", "340", "99%"),
1582 ];
1583 for (i, (cat, wt, pct)) in rows.iter().enumerate() {
1584 tbl.cell(i + 1, 0).unwrap().set_text(cat);
1585 tbl.cell(i + 1, 1).unwrap().set_text(wt);
1586 tbl.cell(i + 1, 2).unwrap().set_text(pct);
1587 }
1588 }
1589
1590 // ── Section 4: Initiatives ──
1591 doc.add_paragraph("").page_break_before(true);
1592 doc.add_paragraph("Q1 2026 Initiatives").style("Heading1");
1593
1594 doc.add_paragraph("Planned Programs").style("Heading2");
1595 doc.add_numbered_list_item(
1596 "Deploy on-site battery storage at SF data center (2 MWh capacity)",
1597 0,
1598 );
1599 doc.add_numbered_list_item("Pilot immersion cooling in Dublin facility", 0);
1600 doc.add_numbered_list_item("Launch employee commute carbon offset program", 0);
1601 doc.add_numbered_list_item("Achieve ISO 14001 certification for Singapore facility", 0);
1602
1603 doc.add_paragraph("Investment Targets").style("Heading2");
1604 doc.add_paragraph("Sustainability CapEx allocation for FY2026:")
1605 .keep_with_next(true);
1606 {
1607 let mut tbl = doc
1608 .add_table(5, 2)
1609 .borders(BorderStyle::Single, 4, "2D5016");
1610 tbl.cell(0, 0).unwrap().set_text("Initiative");
1611 tbl.cell(0, 0).unwrap().shading("2D5016");
1612 tbl.cell(0, 1).unwrap().set_text("Budget");
1613 tbl.cell(0, 1).unwrap().shading("2D5016");
1614 let rows = [
1615 ("Battery Storage", "$1.2M"),
1616 ("Immersion Cooling Pilot", "$800K"),
1617 ("Solar Panel Expansion", "$2.1M"),
1618 ("Carbon Credits", "$500K"),
1619 ];
1620 for (i, (init, budget)) in rows.iter().enumerate() {
1621 tbl.cell(i + 1, 0).unwrap().set_text(init);
1622 tbl.cell(i + 1, 1).unwrap().set_text(budget);
1623 if i % 2 == 0 {
1624 tbl.cell(i + 1, 0).unwrap().shading("FFFAF0");
1625 tbl.cell(i + 1, 1).unwrap().shading("FFFAF0");
1626 }
1627 }
1628 }
1629
1630 // Image: sustainability roadmap
1631 let roadmap_img = create_sample_png(500, 100, [40, 80, 30]);
1632 doc.add_paragraph("");
1633 doc.add_picture(
1634 &roadmap_img,
1635 "roadmap.png",
1636 Length::inches(6.0),
1637 Length::inches(1.2),
1638 )
1639 .alignment(Alignment::Center);
1640 {
1641 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1642 p.add_run("Figure 2: 2026 Sustainability Roadmap")
1643 .italic(true)
1644 .size(9.0)
1645 .color("666666");
1646 }
1647
1648 doc.add_paragraph("");
1649 doc.add_paragraph(
1650 "For questions about this report, contact Walter White at sustainability@tensorbee.com.",
1651 )
1652 .shading("FFFAF0")
1653 .border_all(BorderStyle::Single, 2, "2D5016");
1654
1655 doc
1656}
1657
1658// =============================================================================
1659// 6. LETTER — Slate blue + silver scheme
1660// =============================================================================
1661fn generate_letter(_samples_dir: &Path) -> Document {
1662 let mut doc = Document::new();
1663
1664 // Colors: Slate #4A5568, Blue accent #3182CE
1665 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1666 doc.set_margins(
1667 Length::inches(1.25),
1668 Length::inches(1.25),
1669 Length::inches(1.25),
1670 Length::inches(1.25),
1671 );
1672 doc.set_title("Business Letter");
1673 doc.set_author("Walter White");
1674
1675 // Banner header using raw XML
1676 let logo_img = create_logo_png(220, 48);
1677 let banner = build_header_banner_xml(
1678 "rId1",
1679 &BannerOpts {
1680 bg_color: "4A5568",
1681 banner_width: 7772400,
1682 banner_height: 731520, // ~0.8"
1683 logo_width: 2011680,
1684 logo_height: 438912,
1685 logo_x_offset: 295125,
1686 logo_y_offset: 146304,
1687 },
1688 );
1689 doc.set_raw_header_with_images(
1690 banner,
1691 &[("rId1", &logo_img, "logo.png")],
1692 rdocx_oxml::header_footer::HdrFtrType::Default,
1693 );
1694 doc.set_margins(
1695 Length::twips(2000),
1696 Length::twips(1800),
1697 Length::twips(1440),
1698 Length::twips(1800),
1699 );
1700 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
1701
1702 // Footer with contact
1703 doc.set_footer(
1704 "Tensorbee | 123 Innovation Drive, Suite 400 | San Francisco, CA 94105 | tensorbee.com",
1705 );
1706
1707 // ── Sender Address ──
1708 doc.add_paragraph("Walter White");
1709 doc.add_paragraph("Chief Executive Officer");
1710 doc.add_paragraph("Tensorbee");
1711 doc.add_paragraph("123 Innovation Drive, Suite 400");
1712 doc.add_paragraph("San Francisco, CA 94105");
1713 doc.add_paragraph("");
1714 doc.add_paragraph("February 22, 2026");
1715
1716 doc.add_paragraph("");
1717
1718 // ── Recipient ──
1719 doc.add_paragraph("Ms. Sarah Chen");
1720 doc.add_paragraph("VP of Engineering");
1721 doc.add_paragraph("Meridian Dynamics LLC");
1722 doc.add_paragraph("456 Enterprise Boulevard");
1723 doc.add_paragraph("Austin, TX 78701");
1724
1725 doc.add_paragraph("");
1726
1727 // ── Subject ──
1728 {
1729 let mut p = doc.add_paragraph("");
1730 p.add_run("Re: Strategic Technology Partnership — Phase 2 Expansion")
1731 .bold(true);
1732 }
1733
1734 doc.add_paragraph("");
1735
1736 // ── Body ──
1737 doc.add_paragraph("Dear Ms. Chen,");
1738 doc.add_paragraph("");
1739
1740 doc.add_paragraph(
1741 "I am writing to express Tensorbee's enthusiasm for expanding our strategic technology \
1742 partnership with Meridian Dynamics. Following the successful completion of Phase 1, \
1743 which delivered a 40% improvement in your ML inference pipeline throughput, we believe \
1744 the foundation is firmly established for an ambitious Phase 2 engagement.",
1745 )
1746 .first_line_indent(Length::inches(0.5));
1747
1748 doc.add_paragraph(
1749 "During our recent executive review, your team highlighted three priority areas for \
1750 the next phase of collaboration:",
1751 )
1752 .first_line_indent(Length::inches(0.5));
1753
1754 doc.add_numbered_list_item(
1755 "Real-time anomaly detection for your financial transaction monitoring system, \
1756 targeting sub-100ms latency at 50,000 transactions per second.",
1757 0,
1758 );
1759 doc.add_numbered_list_item(
1760 "Federated learning infrastructure to enable model training across your distributed \
1761 data centers without centralizing sensitive financial data.",
1762 0,
1763 );
1764 doc.add_numbered_list_item(
1765 "MLOps automation to reduce your model deployment cycle from the current 5 days \
1766 to under 4 hours.",
1767 0,
1768 );
1769
1770 doc.add_paragraph(
1771 "Our engineering team has prepared a detailed technical proposal addressing each of \
1772 these areas. We have allocated a dedicated team of eight senior engineers, led by \
1773 our CTO, to ensure continuity with the Phase 1 team your organization has already \
1774 built a productive working relationship with.",
1775 )
1776 .first_line_indent(Length::inches(0.5));
1777
1778 doc.add_paragraph(
1779 "I would welcome the opportunity to present our Phase 2 proposal in person. My \
1780 assistant will reach out to coordinate a meeting at your convenience during the \
1781 first week of March.",
1782 )
1783 .first_line_indent(Length::inches(0.5));
1784
1785 doc.add_paragraph("");
1786
1787 doc.add_paragraph("Warm regards,");
1788 doc.add_paragraph("");
1789 doc.add_paragraph("");
1790
1791 // Signature line
1792 doc.add_paragraph("")
1793 .border_bottom(BorderStyle::Single, 4, "4A5568");
1794 {
1795 let mut p = doc.add_paragraph("");
1796 p.add_run("Walter White").bold(true).size(12.0);
1797 }
1798 {
1799 let mut p = doc.add_paragraph("");
1800 p.add_run("Chief Executive Officer, Tensorbee")
1801 .italic(true)
1802 .size(10.0)
1803 .color("666666");
1804 }
1805 {
1806 let mut p = doc.add_paragraph("");
1807 p.add_run("walter@tensorbee.com | +1 (415) 555-0199")
1808 .size(10.0)
1809 .color("888888");
1810 }
1811
1812 doc
1813}
1814
1815// =============================================================================
1816// 7. EMPLOYMENT CONTRACT — Purple + charcoal scheme
1817// =============================================================================
1818fn generate_contract(_samples_dir: &Path) -> Document {
1819 let mut doc = Document::new();
1820
1821 // Colors: Purple #5B2C6F, Plum #8E44AD, Gray #2C3E50
1822 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1823 doc.set_margins(
1824 Length::inches(1.25),
1825 Length::inches(1.0),
1826 Length::inches(1.0),
1827 Length::inches(1.0),
1828 );
1829 doc.set_title("Employment Agreement");
1830 doc.set_author("Tensorbee HR Department");
1831 doc.set_subject("Employment Contract — Walter White");
1832 doc.set_keywords("employment, contract, agreement, Tensorbee");
1833
1834 doc.set_header("TENSORBEE — Employment Agreement");
1835 doc.set_footer("Employment Agreement — Walter White — Page");
1836
1837 // ── Title Block ──
1838 doc.add_paragraph("")
1839 .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1840 {
1841 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1842 p.add_run("EMPLOYMENT AGREEMENT")
1843 .bold(true)
1844 .size(24.0)
1845 .color("5B2C6F")
1846 .font("Georgia");
1847 }
1848 doc.add_paragraph("")
1849 .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1850 doc.add_paragraph("");
1851
1852 // ── Parties ──
1853 doc.add_paragraph(
1854 "This Employment Agreement (\"Agreement\") is entered into as of February 22, 2026 \
1855 (\"Effective Date\"), by and between:",
1856 );
1857
1858 doc.add_paragraph("");
1859
1860 {
1861 let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1862 p.add_run("EMPLOYER: ").bold(true);
1863 p.add_run(
1864 "Tensorbee, Inc., a Delaware corporation with its principal place of business at \
1865 123 Innovation Drive, Suite 400, San Francisco, CA 94105 (\"Company\")",
1866 );
1867 }
1868 doc.add_paragraph("");
1869 {
1870 let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1871 p.add_run("EMPLOYEE: ").bold(true);
1872 p.add_run(
1873 "Walter White, residing at 308 Negra Arroyo Lane, Albuquerque, NM 87104 (\"Employee\")",
1874 );
1875 }
1876
1877 doc.add_paragraph("");
1878 doc.add_paragraph("The Company and Employee are collectively referred to as the \"Parties.\"");
1879 doc.add_paragraph("");
1880
1881 // ── Article 1: Position and Duties ──
1882 doc.add_paragraph("Article 1 — Position and Duties")
1883 .style("Heading1");
1884 {
1885 let mut p = doc.add_paragraph("");
1886 p.add_run("1.1 ").bold(true);
1887 p.add_run("The Company hereby employs the Employee as ");
1888 p.add_run("Chief Executive Officer (CEO)")
1889 .bold(true)
1890 .italic(true);
1891 p.add_run(", reporting directly to the Board of Directors.");
1892 }
1893 {
1894 let mut p = doc.add_paragraph("");
1895 p.add_run("1.2 ").bold(true);
1896 p.add_run("The Employee shall devote full working time, attention, and best efforts to \
1897 the performance of duties as reasonably assigned by the Board, including but not limited to:");
1898 }
1899 doc.add_bullet_list_item(
1900 "Setting and executing the Company's strategic vision and business plan",
1901 0,
1902 );
1903 doc.add_bullet_list_item(
1904 "Overseeing all operations, engineering, and commercial activities",
1905 0,
1906 );
1907 doc.add_bullet_list_item(
1908 "Representing the Company to investors, customers, and the public",
1909 0,
1910 );
1911 doc.add_bullet_list_item("Recruiting, developing, and retaining key talent", 0);
1912
1913 {
1914 let mut p = doc.add_paragraph("");
1915 p.add_run("1.3 ").bold(true);
1916 p.add_run(
1917 "The Employee's primary work location shall be the Company's San Francisco \
1918 headquarters, with reasonable travel as required by business needs.",
1919 );
1920 }
1921
1922 // ── Article 2: Compensation ──
1923 doc.add_paragraph("Article 2 — Compensation and Benefits")
1924 .style("Heading1");
1925 {
1926 let mut p = doc.add_paragraph("");
1927 p.add_run("2.1 Base Salary. ").bold(true);
1928 p.add_run("The Company shall pay the Employee an annual base salary of ");
1929 p.add_run("$375,000.00").bold(true);
1930 p.add_run(" (Three Hundred Seventy-Five Thousand Dollars), payable in accordance with the \
1931 Company's standard payroll schedule, less applicable withholdings and deductions.");
1932 }
1933 {
1934 let mut p = doc.add_paragraph("");
1935 p.add_run("2.2 Annual Bonus. ").bold(true);
1936 p.add_run("The Employee shall be eligible for an annual performance bonus of up to ");
1937 p.add_run("40%").bold(true);
1938 p.add_run(
1939 " of base salary, based on achievement of mutually agreed performance objectives.",
1940 );
1941 }
1942 {
1943 let mut p = doc.add_paragraph("");
1944 p.add_run("2.3 Equity. ").bold(true);
1945 p.add_run("Subject to Board approval, the Employee shall receive a stock option grant of ");
1946 p.add_run("500,000 shares").bold(true);
1947 p.add_run(
1948 " of the Company's common stock, vesting over four (4) years with a one-year cliff, \
1949 at an exercise price equal to the fair market value on the date of grant.",
1950 );
1951 }
1952
1953 // Compensation summary table
1954 doc.add_paragraph("");
1955 {
1956 let mut tbl = doc
1957 .add_table(5, 2)
1958 .borders(BorderStyle::Single, 4, "5B2C6F")
1959 .width_pct(70.0)
1960 .alignment(Alignment::Center);
1961 tbl.cell(0, 0).unwrap().set_text("Compensation Element");
1962 tbl.cell(0, 0).unwrap().shading("5B2C6F");
1963 tbl.cell(0, 1).unwrap().set_text("Value");
1964 tbl.cell(0, 1).unwrap().shading("5B2C6F");
1965 let items = [
1966 ("Base Salary", "$375,000/year"),
1967 ("Target Bonus", "Up to 40% ($150,000)"),
1968 ("Equity Grant", "500,000 shares (4yr vest)"),
1969 ("Total Target Comp", "$525,000 + equity"),
1970 ];
1971 for (i, (elem, val)) in items.iter().enumerate() {
1972 tbl.cell(i + 1, 0).unwrap().set_text(elem);
1973 tbl.cell(i + 1, 1).unwrap().set_text(val);
1974 if i == 3 {
1975 tbl.cell(i + 1, 0).unwrap().shading("E8DAEF");
1976 tbl.cell(i + 1, 1).unwrap().shading("E8DAEF");
1977 }
1978 }
1979 }
1980
1981 // ── Article 3: Benefits ──
1982 doc.add_paragraph("").page_break_before(true);
1983 doc.add_paragraph("Article 3 — Benefits").style("Heading1");
1984 {
1985 let mut p = doc.add_paragraph("");
1986 p.add_run("3.1 ").bold(true);
1987 p.add_run(
1988 "The Employee shall be entitled to participate in all benefit programs \
1989 generally available to senior executives, including:",
1990 );
1991 }
1992 doc.add_bullet_list_item(
1993 "Medical, dental, and vision insurance (100% premium coverage for employee and dependents)",
1994 0,
1995 );
1996 doc.add_bullet_list_item("401(k) retirement plan with 6% company match", 0);
1997 doc.add_bullet_list_item("Life insurance and long-term disability coverage", 0);
1998 doc.add_bullet_list_item("Annual professional development allowance of $10,000", 0);
1999
2000 {
2001 let mut p = doc.add_paragraph("");
2002 p.add_run("3.2 Paid Time Off. ").bold(true);
2003 p.add_run(
2004 "The Employee shall receive 25 days of paid vacation per year, plus Company holidays, \
2005 accruing on a monthly basis.",
2006 );
2007 }
2008
2009 // ── Article 4: Term and Termination ──
2010 doc.add_paragraph("Article 4 — Term and Termination")
2011 .style("Heading1");
2012 {
2013 let mut p = doc.add_paragraph("");
2014 p.add_run("4.1 At-Will Employment. ").bold(true);
2015 p.add_run(
2016 "This Agreement is for at-will employment and may be terminated by either Party \
2017 at any time, with or without cause, subject to the notice provisions herein.",
2018 );
2019 }
2020 {
2021 let mut p = doc.add_paragraph("");
2022 p.add_run("4.2 Notice Period. ").bold(true);
2023 p.add_run("Either Party shall provide ");
2024 p.add_run("ninety (90) days").bold(true);
2025 p.add_run(" written notice of termination, or pay in lieu thereof.");
2026 }
2027 {
2028 let mut p = doc.add_paragraph("");
2029 p.add_run("4.3 Severance. ").bold(true);
2030 p.add_run("In the event of termination by the Company without Cause, the Employee shall \
2031 receive (a) twelve (12) months of base salary continuation, (b) pro-rata bonus \
2032 for the year of termination, and (c) twelve (12) months of COBRA premium coverage.");
2033 }
2034
2035 // ── Article 5: Confidentiality ──
2036 doc.add_paragraph("Article 5 — Confidentiality and IP")
2037 .style("Heading1");
2038 {
2039 let mut p = doc.add_paragraph("");
2040 p.add_run("5.1 ").bold(true);
2041 p.add_run(
2042 "The Employee agrees to maintain strict confidentiality of all proprietary \
2043 information, trade secrets, and business plans of the Company during and after \
2044 employment.",
2045 );
2046 }
2047 {
2048 let mut p = doc.add_paragraph("");
2049 p.add_run("5.2 ").bold(true);
2050 p.add_run(
2051 "All intellectual property, inventions, and works of authorship created by the \
2052 Employee during the term of employment and within the scope of duties shall be \
2053 the sole and exclusive property of the Company.",
2054 );
2055 }
2056
2057 // ── Article 6: Non-Compete ──
2058 doc.add_paragraph("Article 6 — Non-Competition")
2059 .style("Heading1");
2060 {
2061 let mut p = doc.add_paragraph("");
2062 p.add_run("6.1 ").bold(true);
2063 p.add_run("For a period of twelve (12) months following termination, the Employee agrees \
2064 not to engage in any business that directly competes with the Company's core \
2065 business of AI infrastructure and ML operations services, within the United States.");
2066 }
2067
2068 // ── Article 7: General Provisions ──
2069 doc.add_paragraph("Article 7 — General Provisions")
2070 .style("Heading1");
2071 {
2072 let mut p = doc.add_paragraph("");
2073 p.add_run("7.1 Governing Law. ").bold(true);
2074 p.add_run(
2075 "This Agreement shall be governed by and construed in accordance with the laws of \
2076 the State of California.",
2077 );
2078 }
2079 {
2080 let mut p = doc.add_paragraph("");
2081 p.add_run("7.2 Entire Agreement. ").bold(true);
2082 p.add_run(
2083 "This Agreement constitutes the entire agreement between the Parties and supersedes \
2084 all prior negotiations, representations, and agreements.",
2085 );
2086 }
2087 {
2088 let mut p = doc.add_paragraph("");
2089 p.add_run("7.3 Amendment. ").bold(true);
2090 p.add_run(
2091 "This Agreement may only be amended by a written instrument signed by both Parties.",
2092 );
2093 }
2094
2095 // ── Signature Block ──
2096 doc.add_paragraph("").page_break_before(true);
2097 doc.add_paragraph("IN WITNESS WHEREOF, the Parties have executed this Employment Agreement as of the Effective Date.")
2098 .space_after(Length::pt(24.0));
2099
2100 // Signature table
2101 {
2102 let mut tbl = doc.add_table(6, 2).width_pct(100.0);
2103 tbl.cell(0, 0).unwrap().set_text("FOR THE COMPANY:");
2104 tbl.cell(0, 0).unwrap().shading("5B2C6F");
2105 tbl.cell(0, 1).unwrap().set_text("EMPLOYEE:");
2106 tbl.cell(0, 1).unwrap().shading("5B2C6F");
2107
2108 tbl.cell(1, 0).unwrap().set_text("");
2109 tbl.cell(1, 1).unwrap().set_text("");
2110 tbl.row(1).unwrap().height(Length::pt(40.0));
2111
2112 tbl.cell(2, 0)
2113 .unwrap()
2114 .set_text("Signature: ___________________________");
2115 tbl.cell(2, 1)
2116 .unwrap()
2117 .set_text("Signature: ___________________________");
2118 tbl.cell(3, 0)
2119 .unwrap()
2120 .set_text("Name: Board Representative");
2121 tbl.cell(3, 1).unwrap().set_text("Name: Walter White");
2122 tbl.cell(4, 0)
2123 .unwrap()
2124 .set_text("Title: Chair, Board of Directors");
2125 tbl.cell(4, 1)
2126 .unwrap()
2127 .set_text("Title: Chief Executive Officer");
2128 tbl.cell(5, 0).unwrap().set_text("Date: _______________");
2129 tbl.cell(5, 1).unwrap().set_text("Date: _______________");
2130 }
2131
2132 doc
2133}Get the document author/creator.
Set the document author/creator.
Examples found in repository?
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
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}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}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}97fn generate_feature_showcase(_samples_dir: &Path) -> Document {
98 let mut doc = Document::new();
99
100 // ── Page Setup & Metadata ──
101 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
102 doc.set_margins(
103 Length::inches(1.0),
104 Length::inches(1.0),
105 Length::inches(1.0),
106 Length::inches(1.0),
107 );
108 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
109 doc.set_gutter(Length::twips(0));
110 doc.set_title("rdocx Feature Showcase");
111 doc.set_author("rdocx Sample Generator");
112 doc.set_subject("Comprehensive feature demonstration");
113 doc.set_keywords("rdocx, docx, rust, sample, showcase");
114
115 // Headers & Footers with different first page
116 doc.set_different_first_page(true);
117 doc.set_first_page_header("rdocx");
118 doc.set_first_page_footer("Feature Showcase — Cover Page");
119 doc.set_header("rdocx Feature Showcase");
120 doc.set_footer("Generated by rdocx");
121
122 // ── COVER PAGE ──
123 let bg = create_sample_png(612, 792, [20, 45, 90]);
124 doc.add_background_image(&bg, "cover_bg.png");
125
126 for _ in 0..3 {
127 doc.add_paragraph("");
128 }
129 {
130 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
131 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
132 }
133 {
134 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
135 p.add_run("Complete Feature Showcase")
136 .size(28.0)
137 .color("FFFFFF")
138 .italic(true);
139 }
140 doc.add_paragraph("");
141 {
142 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
143 p.add_run("Every feature of the rdocx Rust library")
144 .size(13.0)
145 .color("B0C4DE");
146 }
147 {
148 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
149 p.add_run("demonstrated in a single document.")
150 .size(13.0)
151 .color("B0C4DE");
152 }
153
154 // ── TABLE OF CONTENTS ──
155 doc.add_paragraph("").page_break_before(true);
156 doc.insert_toc(doc.content_count(), 3);
157
158 // ── SECTION 1: TEXT FORMATTING ──
159 doc.add_paragraph("").page_break_before(true);
160 doc.add_paragraph("1. Text Formatting").style("Heading1");
161
162 // Paragraph Alignment
163 doc.add_paragraph("1.1 Paragraph Alignment")
164 .style("Heading2");
165 doc.add_paragraph("Left-aligned paragraph (default).")
166 .alignment(Alignment::Left);
167 doc.add_paragraph("Center-aligned paragraph.")
168 .alignment(Alignment::Center);
169 doc.add_paragraph("Right-aligned paragraph.")
170 .alignment(Alignment::Right);
171 doc.add_paragraph(
172 "Justified paragraph — this text is long enough to demonstrate how justified alignment \
173 distributes extra space across word gaps so lines fill the full width of the text area.",
174 )
175 .alignment(Alignment::Justify);
176
177 doc.add_paragraph("");
178
179 // Run Formatting
180 doc.add_paragraph("1.2 Run Formatting").style("Heading2");
181 {
182 let mut p = doc.add_paragraph("");
183 p.add_run("Bold").bold(true);
184 p.add_run(" | ");
185 p.add_run("Italic").italic(true);
186 p.add_run(" | ");
187 p.add_run("Bold Italic").bold(true).italic(true);
188 p.add_run(" | ");
189 p.add_run("Underline").underline(true);
190 p.add_run(" | ");
191 p.add_run("Strikethrough").strike(true);
192 p.add_run(" | ");
193 p.add_run("Double Strike").double_strike(true);
194 }
195 {
196 let mut p = doc.add_paragraph("");
197 p.add_run("Red").color("FF0000");
198 p.add_run(" | ");
199 p.add_run("Blue").color("0000FF");
200 p.add_run(" | ");
201 p.add_run("Green").color("00AA00");
202 p.add_run(" | ");
203 p.add_run("Highlighted").highlight("FFFF00");
204 }
205 {
206 let mut p = doc.add_paragraph("");
207 p.add_run("8pt").size(8.0);
208 p.add_run(" | ");
209 p.add_run("11pt").size(11.0);
210 p.add_run(" | ");
211 p.add_run("16pt").size(16.0);
212 p.add_run(" | ");
213 p.add_run("24pt").size(24.0);
214 }
215 {
216 let mut p = doc.add_paragraph("");
217 p.add_run("Arial").font("Arial");
218 p.add_run(" | ");
219 p.add_run("Times New Roman").font("Times New Roman");
220 p.add_run(" | ");
221 p.add_run("Courier New").font("Courier New");
222 }
223 {
224 let mut p = doc.add_paragraph("");
225 p.add_run("H");
226 p.add_run("2").subscript();
227 p.add_run("O (subscript) | E=mc");
228 p.add_run("2").superscript();
229 p.add_run(" (superscript)");
230 }
231 {
232 let mut p = doc.add_paragraph("");
233 p.add_run("ALL CAPS").all_caps(true);
234 p.add_run(" | ");
235 p.add_run("Small Caps").small_caps(true);
236 p.add_run(" | ");
237 p.add_run("Expanded").character_spacing(Length::twips(40));
238 p.add_run(" | ");
239 p.add_run("Hidden text (not visible)").hidden(true);
240 }
241
242 doc.add_paragraph("");
243
244 // Underline Styles
245 doc.add_paragraph("1.3 Underline Styles").style("Heading2");
246 {
247 let mut p = doc.add_paragraph("");
248 p.add_run("Single ")
249 .underline_style(rdocx::UnderlineStyle::Single);
250 p.add_run("Double ")
251 .underline_style(rdocx::UnderlineStyle::Double);
252 p.add_run("Thick ")
253 .underline_style(rdocx::UnderlineStyle::Thick);
254 p.add_run("Dotted ")
255 .underline_style(rdocx::UnderlineStyle::Dotted);
256 p.add_run("Dash ")
257 .underline_style(rdocx::UnderlineStyle::Dash);
258 p.add_run("Wave ")
259 .underline_style(rdocx::UnderlineStyle::Wave);
260 }
261
262 // ── SECTION 2: PARAGRAPH FORMATTING ──
263 doc.add_paragraph("").page_break_before(true);
264 doc.add_paragraph("2. Paragraph Formatting")
265 .style("Heading1");
266
267 doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
268 doc.add_paragraph("Paragraph with light green shading")
269 .shading("E2EFDA");
270 doc.add_paragraph("Paragraph with bottom border")
271 .border_bottom(BorderStyle::Single, 6, "2E75B6");
272 doc.add_paragraph("Paragraph with all borders (red)")
273 .border_all(BorderStyle::Single, 4, "FF0000");
274
275 doc.add_paragraph("2.2 Indentation").style("Heading2");
276 doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
277 .indent_left(Length::inches(1.0))
278 .hanging_indent(Length::inches(0.5));
279 doc.add_paragraph("0.5-inch first-line indent on this paragraph")
280 .first_line_indent(Length::inches(0.5));
281 doc.add_paragraph("Both left and right indent (0.75 inches each)")
282 .indent_left(Length::inches(0.75))
283 .indent_right(Length::inches(0.75));
284
285 doc.add_paragraph("2.3 Spacing & Line Height")
286 .style("Heading2");
287 doc.add_paragraph("Extra space before (24pt) and after (12pt)")
288 .space_before(Length::pt(24.0))
289 .space_after(Length::pt(12.0));
290 doc.add_paragraph(
291 "Double line spacing paragraph. This text should have extra vertical space between \
292 lines to demonstrate line_spacing_multiple(2.0).",
293 )
294 .line_spacing_multiple(2.0);
295 doc.add_paragraph("Exact 20pt line spacing (fixed height).")
296 .line_spacing(20.0);
297
298 doc.add_paragraph("2.4 Pagination Controls")
299 .style("Heading2");
300 doc.add_paragraph("keep_with_next — this paragraph stays with the next")
301 .keep_with_next(true);
302 doc.add_paragraph("(This paragraph was kept with the one above.)");
303 doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
304 .keep_together(true);
305 doc.add_paragraph("widow_control — prevents widow/orphan lines")
306 .widow_control(true);
307
308 // ── SECTION 3: LISTS ──
309 doc.add_paragraph("").page_break_before(true);
310 doc.add_paragraph("3. Lists").style("Heading1");
311
312 doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
313 doc.add_bullet_list_item("First item", 0);
314 doc.add_bullet_list_item("Second item", 0);
315 doc.add_bullet_list_item("Nested level 1", 1);
316 doc.add_bullet_list_item("Nested level 2", 2);
317 doc.add_bullet_list_item("Back to level 1", 1);
318 doc.add_bullet_list_item("Third item", 0);
319
320 doc.add_paragraph("");
321
322 doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
323 doc.add_numbered_list_item("First numbered item", 0);
324 doc.add_numbered_list_item("Second numbered item", 0);
325 doc.add_numbered_list_item("Sub-item A", 1);
326 doc.add_numbered_list_item("Sub-item B", 1);
327 doc.add_numbered_list_item("Third numbered item", 0);
328
329 // ── SECTION 4: TAB STOPS ──
330 doc.add_paragraph("");
331 doc.add_paragraph("4. Tab Stops").style("Heading1");
332
333 doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
334 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
335 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
336 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
337 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
338 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
339
340 doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
341 doc.add_paragraph("Item\t\tPrice")
342 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
343 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
344 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
345 doc.add_paragraph("Widget\t\t$19.99")
346 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
347 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
348 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
349 doc.add_paragraph("Gadget\t\t$249.50")
350 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
351 .add_tab_stop_with_leader(
352 TabAlignment::Right,
353 Length::inches(4.0),
354 TabLeader::Underscore,
355 )
356 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
357
358 // ── SECTION 5: TABLES ──
359 doc.add_paragraph("").page_break_before(true);
360 doc.add_paragraph("5. Tables").style("Heading1");
361
362 doc.add_paragraph("5.1 Basic Table").style("Heading2");
363 {
364 let mut tbl = doc
365 .add_table(4, 3)
366 .borders(BorderStyle::Single, 4, "000000");
367 for col in 0..3 {
368 tbl.cell(0, col).unwrap().shading("2E75B6");
369 }
370 tbl.cell(0, 0).unwrap().set_text("Name");
371 tbl.cell(0, 1).unwrap().set_text("Role");
372 tbl.cell(0, 2).unwrap().set_text("Location");
373 tbl.cell(1, 0).unwrap().set_text("Walter White");
374 tbl.cell(1, 1).unwrap().set_text("CEO");
375 tbl.cell(1, 2).unwrap().set_text("Albuquerque");
376 tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
377 tbl.cell(2, 1).unwrap().set_text("CTO");
378 tbl.cell(2, 2).unwrap().set_text("Remote");
379 tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
380 tbl.cell(3, 1).unwrap().set_text("Security");
381 tbl.cell(3, 2).unwrap().set_text("Washington");
382 }
383
384 doc.add_paragraph("");
385
386 doc.add_paragraph("5.2 Column Span & Cell Shading")
387 .style("Heading2");
388 {
389 let mut tbl = doc
390 .add_table(3, 4)
391 .borders(BorderStyle::Single, 4, "000000")
392 .width_pct(100.0);
393 tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
394 tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
395 tbl.cell(1, 0).unwrap().set_text("Region");
396 tbl.cell(1, 0).unwrap().shading("D6E4F0");
397 tbl.cell(1, 1).unwrap().set_text("Q1");
398 tbl.cell(1, 1).unwrap().shading("D6E4F0");
399 tbl.cell(1, 2).unwrap().set_text("Q2");
400 tbl.cell(1, 2).unwrap().shading("D6E4F0");
401 tbl.cell(1, 3).unwrap().set_text("Total");
402 tbl.cell(1, 3).unwrap().shading("D6E4F0");
403 tbl.cell(2, 0).unwrap().set_text("Americas");
404 tbl.cell(2, 1).unwrap().set_text("$2.4M");
405 tbl.cell(2, 2).unwrap().set_text("$2.7M");
406 tbl.cell(2, 3).unwrap().set_text("$5.1M");
407 }
408
409 doc.add_paragraph("");
410
411 doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
412 {
413 let mut tbl = doc
414 .add_table(4, 3)
415 .borders(BorderStyle::Single, 4, "000000");
416 tbl.cell(0, 0).unwrap().set_text("Category");
417 tbl.cell(0, 0).unwrap().shading("E2EFDA");
418 tbl.cell(0, 1).unwrap().set_text("Item");
419 tbl.cell(0, 1).unwrap().shading("E2EFDA");
420 tbl.cell(0, 2).unwrap().set_text("Price");
421 tbl.cell(0, 2).unwrap().shading("E2EFDA");
422 tbl.cell(1, 0).unwrap().set_text("Hardware");
423 tbl.cell(1, 0).unwrap().v_merge_restart();
424 tbl.cell(1, 1).unwrap().set_text("Laptop");
425 tbl.cell(1, 2).unwrap().set_text("$1,200");
426 tbl.cell(2, 0).unwrap().v_merge_continue();
427 tbl.cell(2, 1).unwrap().set_text("Monitor");
428 tbl.cell(2, 2).unwrap().set_text("$450");
429 tbl.cell(3, 0).unwrap().set_text("Software");
430 tbl.cell(3, 1).unwrap().set_text("IDE License");
431 tbl.cell(3, 2).unwrap().set_text("$200/yr");
432 }
433
434 doc.add_paragraph("");
435
436 doc.add_paragraph("5.4 Nested Table").style("Heading2");
437 {
438 let mut tbl = doc
439 .add_table(2, 2)
440 .borders(BorderStyle::Single, 6, "2E75B6");
441 tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
442 tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
443 tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
444 {
445 let mut cell = tbl.cell(1, 1).unwrap();
446 cell.set_text("Nested table below:");
447 let mut nested = cell
448 .add_table(2, 2)
449 .borders(BorderStyle::Single, 2, "FF6600");
450 nested.cell(0, 0).unwrap().set_text("A");
451 nested.cell(0, 1).unwrap().set_text("B");
452 nested.cell(1, 0).unwrap().set_text("C");
453 nested.cell(1, 1).unwrap().set_text("D");
454 }
455 }
456
457 doc.add_paragraph("");
458
459 doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
460 .style("Heading2");
461 {
462 let mut tbl = doc
463 .add_table(2, 3)
464 .borders(BorderStyle::Single, 4, "666666");
465 tbl.row(0).unwrap().height(Length::pt(50.0)).header();
466 tbl.cell(0, 0).unwrap().set_text("Top");
467 tbl.cell(0, 0)
468 .unwrap()
469 .vertical_alignment(VerticalAlignment::Top)
470 .shading("FFF2CC");
471 tbl.cell(0, 1).unwrap().set_text("Center");
472 tbl.cell(0, 1)
473 .unwrap()
474 .vertical_alignment(VerticalAlignment::Center)
475 .shading("D9E2F3");
476 tbl.cell(0, 2).unwrap().set_text("Bottom");
477 tbl.cell(0, 2)
478 .unwrap()
479 .vertical_alignment(VerticalAlignment::Bottom)
480 .shading("E2EFDA");
481 tbl.row(1).unwrap().cant_split();
482 tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
483 tbl.cell(1, 0).unwrap().no_wrap();
484 tbl.cell(1, 1).unwrap().set_text("Fixed width");
485 tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
486 tbl.cell(1, 2).unwrap().set_text("Normal");
487 }
488
489 // ── SECTION 6: IMAGES ──
490 doc.add_paragraph("").page_break_before(true);
491 doc.add_paragraph("6. Images").style("Heading1");
492
493 doc.add_paragraph("6.1 Inline Image").style("Heading2");
494 doc.add_paragraph("A blue gradient image below:");
495 let img = create_sample_png(200, 50, [0, 80, 200]);
496 doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
497
498 doc.add_paragraph("");
499 doc.add_paragraph("6.2 Header Image").style("Heading2");
500 let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
501 doc.set_header_image(
502 &hdr_img,
503 "header_logo.png",
504 Length::inches(2.0),
505 Length::inches(0.2),
506 );
507 doc.add_paragraph("The header now contains an inline image (check the top of this page).");
508
509 doc.add_paragraph("");
510 doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
511
512 // ── SECTION 7: CONTENT MANIPULATION ──
513 doc.add_paragraph("").page_break_before(true);
514 doc.add_paragraph("7. Content Manipulation")
515 .style("Heading1");
516
517 doc.add_paragraph("7.1 Placeholder Replacement")
518 .style("Heading2");
519 doc.add_paragraph("Customer: {{customer}}");
520 doc.add_paragraph("Date: {{date}}");
521 doc.add_paragraph("Reference: {{ref_number}}");
522 {
523 let mut tbl = doc
524 .add_table(2, 2)
525 .borders(BorderStyle::Single, 4, "000000");
526 tbl.cell(0, 0).unwrap().set_text("Project");
527 tbl.cell(0, 0).unwrap().shading("D6E4F0");
528 tbl.cell(0, 1).unwrap().set_text("{{project}}");
529 tbl.cell(1, 0).unwrap().set_text("Status");
530 tbl.cell(1, 0).unwrap().shading("D6E4F0");
531 tbl.cell(1, 1).unwrap().set_text("{{status}}");
532 }
533
534 let mut replacements = HashMap::new();
535 replacements.insert("{{customer}}", "Tensorbee Inc.");
536 replacements.insert("{{date}}", "February 22, 2026");
537 replacements.insert("{{ref_number}}", "TB-2026-001");
538 replacements.insert("{{project}}", "Infrastructure Upgrade");
539 replacements.insert("{{status}}", "In Progress");
540 let n = doc.replace_all(&replacements);
541 doc.add_paragraph(&format!("({n} placeholders replaced above)"));
542
543 doc.add_paragraph("");
544
545 doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
546 doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
547 let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
548 doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
549
550 doc.add_paragraph("");
551
552 doc.add_paragraph("7.3 Content Insertion").style("Heading2");
553 doc.add_paragraph("Section A: First content.");
554 doc.add_paragraph("Section C: Third content.");
555 if let Some(idx) = doc.find_content_index("Section C") {
556 doc.insert_paragraph(
557 idx,
558 "Section B: Inserted between A and C via find_content_index().",
559 );
560 }
561
562 // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
563 doc.add_paragraph("").section_break(SectionBreak::NextPage);
564 doc.add_paragraph("8. Section Breaks & Orientation")
565 .style("Heading1");
566 doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
567
568 {
569 let mut tbl = doc
570 .add_table(3, 7)
571 .borders(BorderStyle::Single, 4, "2E75B6");
572 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
573 for (c, h) in headers.iter().enumerate() {
574 tbl.cell(0, c).unwrap().set_text(h);
575 tbl.cell(0, c).unwrap().shading("2E75B6");
576 }
577 let data = [
578 [
579 "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
580 ],
581 [
582 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
583 ],
584 ];
585 for (r, row) in data.iter().enumerate() {
586 for (c, v) in row.iter().enumerate() {
587 tbl.cell(r + 1, c).unwrap().set_text(v);
588 }
589 }
590 }
591
592 doc.add_paragraph("")
593 .section_break(SectionBreak::NextPage)
594 .section_landscape();
595
596 // ── SECTION 9: CUSTOM STYLES ──
597 doc.add_paragraph("9. Custom Styles").style("Heading1");
598 doc.add_style(
599 StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
600 .based_on("Normal")
601 .paragraph_properties(rdocx_oxml::properties::CT_PPr {
602 shading: Some(rdocx_oxml::properties::CT_Shd {
603 val: "clear".to_string(),
604 color: None,
605 fill: Some("FFF2CC".to_string()),
606 }),
607 ..Default::default()
608 })
609 .run_properties(rdocx_oxml::properties::CT_RPr {
610 bold: Some(true),
611 color: Some("C45911".to_string()),
612 ..Default::default()
613 }),
614 );
615 doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
616 .style("CustomHighlight");
617
618 doc.add_paragraph("");
619
620 // ── SECTION 10: DOCUMENT INTELLIGENCE ──
621 doc.add_paragraph("10. Document Intelligence API")
622 .style("Heading1");
623
624 let wc = doc.word_count();
625 let hc = doc.headings().len();
626 let ic = doc.images().len();
627 let lc = doc.links().len();
628 doc.add_paragraph(&format!("Word count: {wc}"));
629 doc.add_paragraph(&format!("Heading count: {hc}"));
630 doc.add_paragraph(&format!("Image count: {ic}"));
631 doc.add_paragraph(&format!("Link count: {lc}"));
632
633 let outline = doc.document_outline();
634 doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
635
636 let issues = doc.audit_accessibility();
637 doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
638 for issue in &issues {
639 doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
640 }
641
642 // ── SECTION 11: DOCUMENT MERGING ──
643 doc.add_paragraph("");
644 doc.add_paragraph("11. Document Merging").style("Heading1");
645 {
646 let mut other = Document::new();
647 other.add_paragraph("This paragraph was merged from another document using append().");
648 doc.append(&other);
649 }
650
651 doc.add_paragraph("");
652
653 // ── SUMMARY ──
654 doc.add_paragraph("Summary of Demonstrated Features")
655 .style("Heading1");
656 let features = [
657 "Page setup: size, margins, header/footer distance, gutter",
658 "Document metadata: title, author, subject, keywords",
659 "Headers and footers: text, images, different first page",
660 "Background images (full-page behind text)",
661 "Table of Contents generation with bookmarks",
662 "Text formatting: bold, italic, underline styles, strike, color, size, font",
663 "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
664 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
665 "Pagination controls: keep-with-next, keep-together, widow control",
666 "Bullet and numbered lists with nesting",
667 "Tab stops with dot/underscore/hyphen leaders",
668 "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
669 "Row properties: header rows, exact height, cant-split",
670 "Cell properties: width, no-wrap, vertical alignment",
671 "Inline images and header images",
672 "Placeholder replacement (single and batch)",
673 "Regex-based find and replace",
674 "Content insertion at specific positions",
675 "Section breaks with mixed portrait/landscape",
676 "Custom paragraph and character styles",
677 "Document intelligence: word count, headings, outline, images, links",
678 "Accessibility audit",
679 "Document merging (append)",
680 "Export: DOCX, PDF, HTML, Markdown",
681 ];
682 for f in &features {
683 doc.add_bullet_list_item(f, 0);
684 }
685 doc.add_paragraph("");
686 doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
687 .alignment(Alignment::Center)
688 .shading("E2EFDA")
689 .border_all(BorderStyle::Single, 2, "00AA00");
690
691 doc
692}
693
694// =============================================================================
695// 2. PROPOSAL DOCUMENT — Deep navy + gold scheme
696// =============================================================================
697fn generate_proposal(_samples_dir: &Path) -> Document {
698 let mut doc = Document::new();
699
700 // Colors: Navy #1B2A4A, Gold #C5922E, Light #F4F1EB
701 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
702 doc.set_margins(
703 Length::inches(1.0),
704 Length::inches(1.0),
705 Length::inches(1.0),
706 Length::inches(1.0),
707 );
708 doc.set_title("AI Platform Modernization Proposal");
709 doc.set_author("Walter White");
710 doc.set_subject("Technology Proposal");
711 doc.set_keywords("proposal, AI, modernization, Tensorbee");
712
713 // Banner header
714 let logo_img = create_logo_png(220, 48);
715 let banner = build_header_banner_xml(
716 "rId1",
717 &BannerOpts {
718 bg_color: "1B2A4A",
719 banner_width: 7772400,
720 banner_height: 914400,
721 logo_width: 2011680,
722 logo_height: 438912,
723 logo_x_offset: 295125,
724 logo_y_offset: 237744,
725 },
726 );
727 doc.set_raw_header_with_images(
728 banner,
729 &[("rId1", &logo_img, "logo.png")],
730 rdocx_oxml::header_footer::HdrFtrType::Default,
731 );
732 doc.set_different_first_page(true);
733 let cover_banner = build_header_banner_xml(
734 "rId1",
735 &BannerOpts {
736 bg_color: "C5922E",
737 banner_width: 7772400,
738 banner_height: 914400,
739 logo_width: 2011680,
740 logo_height: 438912,
741 logo_x_offset: 295125,
742 logo_y_offset: 237744,
743 },
744 );
745 doc.set_raw_header_with_images(
746 cover_banner,
747 &[("rId1", &logo_img, "logo.png")],
748 rdocx_oxml::header_footer::HdrFtrType::First,
749 );
750 doc.set_margins(
751 Length::twips(2292),
752 Length::twips(1440),
753 Length::twips(1440),
754 Length::twips(1440),
755 );
756 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
757 doc.set_footer("Tensorbee — Confidential");
758
759 // Custom styles
760 doc.add_style(
761 StyleBuilder::paragraph("ProposalTitle", "Proposal Title")
762 .based_on("Normal")
763 .run_properties(rdocx_oxml::properties::CT_RPr {
764 bold: Some(true),
765 font_ascii: Some("Georgia".to_string()),
766 font_hansi: Some("Georgia".to_string()),
767 sz: Some(rdocx_oxml::HalfPoint(56)), // half-points → 28pt
768 color: Some("1B2A4A".to_string()),
769 ..Default::default()
770 }),
771 );
772
773 // ── Cover Page ──
774 for _ in 0..4 {
775 doc.add_paragraph("");
776 }
777 doc.add_paragraph("AI Platform Modernization")
778 .style("ProposalTitle")
779 .alignment(Alignment::Center);
780 doc.add_paragraph("")
781 .alignment(Alignment::Center)
782 .border_bottom(BorderStyle::Single, 8, "C5922E");
783 {
784 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
785 p.add_run("Prepared for Global Financial Corp.")
786 .size(14.0)
787 .color("666666")
788 .italic(true);
789 }
790 {
791 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
792 p.add_run("Prepared by: Walter White, CEO — Tensorbee")
793 .size(12.0)
794 .color("888888");
795 }
796 {
797 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
798 p.add_run("February 22, 2026").size(12.0).color("888888");
799 }
800
801 // ── TOC ──
802 doc.add_paragraph("").page_break_before(true);
803 doc.add_paragraph("Table of Contents").style("Heading1");
804 // We'll add a manual-style TOC since the insert_toc goes at a specific position
805 doc.insert_toc(doc.content_count(), 2);
806
807 // ── Executive Summary ──
808 doc.add_paragraph("").page_break_before(true);
809 doc.add_paragraph("Executive Summary").style("Heading1");
810 doc.add_paragraph(
811 "Tensorbee proposes a comprehensive modernization of Global Financial Corp's AI platform, \
812 transitioning from legacy batch-processing systems to a real-time inference architecture. \
813 This transformation will reduce model deployment time from weeks to hours, improve \
814 prediction accuracy by an estimated 23%, and deliver $4.2M in annual operational savings.",
815 )
816 .first_line_indent(Length::inches(0.3));
817 doc.add_paragraph(
818 "The project spans three phases over 18 months, with a total investment of $2.8M. \
819 Tensorbee brings deep expertise in ML infrastructure, having successfully completed \
820 similar transformations for three Fortune 500 financial institutions.",
821 )
822 .first_line_indent(Length::inches(0.3));
823
824 // Key metrics table
825 doc.add_paragraph("");
826 {
827 let mut tbl = doc
828 .add_table(5, 2)
829 .borders(BorderStyle::Single, 4, "1B2A4A")
830 .width_pct(80.0);
831 tbl.cell(0, 0).unwrap().set_text("Key Metric");
832 tbl.cell(0, 0).unwrap().shading("1B2A4A");
833 tbl.cell(0, 1).unwrap().set_text("Value");
834 tbl.cell(0, 1).unwrap().shading("1B2A4A");
835 let rows = [
836 ("Total Investment", "$2.8M"),
837 ("Annual Savings", "$4.2M"),
838 ("ROI (Year 1)", "150%"),
839 ("Timeline", "18 months"),
840 ];
841 for (i, (k, v)) in rows.iter().enumerate() {
842 tbl.cell(i + 1, 0).unwrap().set_text(k);
843 if i % 2 == 0 {
844 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
845 }
846 tbl.cell(i + 1, 1).unwrap().set_text(v);
847 if i % 2 == 0 {
848 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
849 }
850 }
851 }
852
853 // ── Problem Statement ──
854 doc.add_paragraph("").page_break_before(true);
855 doc.add_paragraph("Problem Statement").style("Heading1");
856 doc.add_paragraph(
857 "Global Financial Corp's current AI infrastructure faces three critical challenges:",
858 );
859 doc.add_numbered_list_item("Deployment Latency — New models take 3-4 weeks to deploy to production, compared to the industry benchmark of 2-3 days.", 0);
860 doc.add_numbered_list_item("Scalability Constraints — The batch processing architecture cannot handle real-time inference demands during peak trading hours.", 0);
861 doc.add_numbered_list_item("Technical Debt — Legacy Python 2.7 codebases and manual deployment scripts create reliability risks and slow iteration speed.", 0);
862
863 // ── Proposed Solution ──
864 doc.add_paragraph("").page_break_before(true);
865 doc.add_paragraph("Proposed Solution").style("Heading1");
866
867 doc.add_paragraph("Phase 1: Foundation (Months 1-6)")
868 .style("Heading2");
869 doc.add_bullet_list_item("Deploy Kubernetes-based ML serving infrastructure", 0);
870 doc.add_bullet_list_item("Implement CI/CD pipeline for model deployment", 0);
871 doc.add_bullet_list_item("Migrate top 5 production models to new platform", 0);
872
873 doc.add_paragraph("Phase 2: Expansion (Months 7-12)")
874 .style("Heading2");
875 doc.add_bullet_list_item(
876 "Real-time feature engineering pipeline (Apache Kafka + Flink)",
877 0,
878 );
879 doc.add_bullet_list_item("A/B testing framework for model variants", 0);
880 doc.add_bullet_list_item("Automated model monitoring and drift detection", 0);
881
882 doc.add_paragraph("Phase 3: Optimization (Months 13-18)")
883 .style("Heading2");
884 doc.add_bullet_list_item("GPU inference optimization (TensorRT/ONNX Runtime)", 0);
885 doc.add_bullet_list_item("Multi-region deployment for latency reduction", 0);
886 doc.add_bullet_list_item(
887 "Self-service model deployment portal for data science teams",
888 0,
889 );
890
891 // ── Budget ──
892 doc.add_paragraph("Budget Breakdown").style("Heading1");
893 {
894 let mut tbl = doc
895 .add_table(6, 3)
896 .borders(BorderStyle::Single, 4, "1B2A4A")
897 .width_pct(100.0);
898 tbl.cell(0, 0).unwrap().set_text("Category");
899 tbl.cell(0, 0).unwrap().shading("1B2A4A");
900 tbl.cell(0, 1).unwrap().set_text("Cost");
901 tbl.cell(0, 1).unwrap().shading("1B2A4A");
902 tbl.cell(0, 2).unwrap().set_text("Notes");
903 tbl.cell(0, 2).unwrap().shading("1B2A4A");
904 let items = [
905 (
906 "Infrastructure",
907 "$450,000",
908 "Cloud compute + GPU instances",
909 ),
910 ("Engineering Services", "$1,600,000", "12 FTE x 18 months"),
911 (
912 "Software Licenses",
913 "$350,000",
914 "Kafka, monitoring, CI/CD tools",
915 ),
916 (
917 "Training & Enablement",
918 "$200,000",
919 "Team training, documentation",
920 ),
921 ("Contingency (10%)", "$200,000", "Risk buffer"),
922 ];
923 for (i, (cat, cost, note)) in items.iter().enumerate() {
924 tbl.cell(i + 1, 0).unwrap().set_text(cat);
925 tbl.cell(i + 1, 1).unwrap().set_text(cost);
926 tbl.cell(i + 1, 2).unwrap().set_text(note);
927 if i % 2 == 0 {
928 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
929 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
930 tbl.cell(i + 1, 2).unwrap().shading("F4F1EB");
931 }
932 }
933 }
934
935 // ── Closing ──
936 doc.add_paragraph("");
937 doc.add_paragraph("Next Steps").style("Heading1");
938 doc.add_numbered_list_item(
939 "Schedule technical deep-dive with GFC engineering team (Week 1)",
940 0,
941 );
942 doc.add_numbered_list_item("Finalize scope and sign SOW (Week 2-3)", 0);
943 doc.add_numbered_list_item("Kick off Phase 1 with joint planning session (Week 4)", 0);
944
945 doc.add_paragraph("");
946 {
947 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
948 p.add_run("Walter White")
949 .bold(true)
950 .size(14.0)
951 .color("1B2A4A");
952 }
953 {
954 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
955 p.add_run("CEO, Tensorbee | walter@tensorbee.com")
956 .size(10.0)
957 .color("888888");
958 }
959
960 doc
961}
962
963// =============================================================================
964// 3. QUOTE / BILL OF MATERIALS — Teal + orange scheme
965// =============================================================================
966fn generate_quote(_samples_dir: &Path) -> Document {
967 let mut doc = Document::new();
968
969 // Colors: Teal #008B8B, Dark #1A3C3C, Orange #E07020, Light #F0F8F8
970 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
971 doc.set_margins(
972 Length::inches(0.75),
973 Length::inches(0.75),
974 Length::inches(0.75),
975 Length::inches(0.75),
976 );
977 doc.set_title("Quotation QT-2026-0147");
978 doc.set_author("Walter White");
979 doc.set_keywords("quote, BOM, Tensorbee");
980
981 doc.set_header("Tensorbee — Quotation");
982 doc.set_footer("QT-2026-0147 | Page");
983
984 // ── Header Block ──
985 {
986 let mut p = doc.add_paragraph("").alignment(Alignment::Left);
987 p.add_run("TENSORBEE")
988 .bold(true)
989 .size(28.0)
990 .color("008B8B")
991 .font("Helvetica");
992 }
993 doc.add_paragraph("123 Innovation Drive, Suite 400")
994 .alignment(Alignment::Left);
995 doc.add_paragraph("San Francisco, CA 94105 | +1 (415) 555-0199")
996 .alignment(Alignment::Left);
997 doc.add_paragraph("")
998 .border_bottom(BorderStyle::Single, 8, "008B8B");
999
1000 // Quote meta
1001 doc.add_paragraph("");
1002 {
1003 let mut tbl = doc.add_table(4, 4).width_pct(100.0);
1004 tbl.cell(0, 0).unwrap().set_text("QUOTATION");
1005 tbl.cell(0, 0).unwrap().shading("008B8B").grid_span(2);
1006 tbl.cell(0, 2).unwrap().set_text("DATE");
1007 tbl.cell(0, 2).unwrap().shading("008B8B");
1008 tbl.cell(0, 3).unwrap().set_text("VALID UNTIL");
1009 tbl.cell(0, 3).unwrap().shading("008B8B");
1010 tbl.cell(1, 0).unwrap().set_text("Quote #:");
1011 tbl.cell(1, 1).unwrap().set_text("QT-2026-0147");
1012 tbl.cell(1, 2).unwrap().set_text("Feb 22, 2026");
1013 tbl.cell(1, 3).unwrap().set_text("Mar 22, 2026");
1014 tbl.cell(2, 0).unwrap().set_text("Prepared by:");
1015 tbl.cell(2, 1).unwrap().set_text("Walter White");
1016 tbl.cell(2, 2).unwrap().set_text("Payment:");
1017 tbl.cell(2, 3).unwrap().set_text("Net 30");
1018 tbl.cell(3, 0).unwrap().set_text("Customer:");
1019 tbl.cell(3, 1).unwrap().set_text("Meridian Dynamics LLC");
1020 tbl.cell(3, 2).unwrap().set_text("Currency:");
1021 tbl.cell(3, 3).unwrap().set_text("USD");
1022 }
1023
1024 doc.add_paragraph("");
1025
1026 // ── Bill of Materials ──
1027 {
1028 let mut p = doc.add_paragraph("");
1029 p.add_run("Bill of Materials")
1030 .bold(true)
1031 .size(16.0)
1032 .color("1A3C3C");
1033 }
1034 doc.add_paragraph("");
1035
1036 {
1037 let mut tbl = doc
1038 .add_table(10, 6)
1039 .borders(BorderStyle::Single, 2, "008B8B")
1040 .width_pct(100.0)
1041 .layout_fixed();
1042 // Headers
1043 let hdrs = [
1044 "#",
1045 "Part Number",
1046 "Description",
1047 "Qty",
1048 "Unit Price",
1049 "Total",
1050 ];
1051 for (c, h) in hdrs.iter().enumerate() {
1052 tbl.cell(0, c).unwrap().set_text(h);
1053 tbl.cell(0, c).unwrap().shading("008B8B");
1054 }
1055
1056 let items: Vec<(&str, &str, &str, &str, &str)> = vec![
1057 (
1058 "TB-GPU-A100",
1059 "NVIDIA A100 80GB GPU",
1060 "4",
1061 "$12,500.00",
1062 "$50,000.00",
1063 ),
1064 (
1065 "TB-SRV-R750",
1066 "Dell R750xa Server Chassis",
1067 "2",
1068 "$8,200.00",
1069 "$16,400.00",
1070 ),
1071 (
1072 "TB-RAM-256G",
1073 "256GB DDR5 ECC Memory Module",
1074 "8",
1075 "$890.00",
1076 "$7,120.00",
1077 ),
1078 (
1079 "TB-SSD-3840",
1080 "3.84TB NVMe U.2 SSD",
1081 "8",
1082 "$1,150.00",
1083 "$9,200.00",
1084 ),
1085 (
1086 "TB-NET-CX7",
1087 "ConnectX-7 200GbE NIC",
1088 "4",
1089 "$1,800.00",
1090 "$7,200.00",
1091 ),
1092 (
1093 "TB-SW-48P",
1094 "48-Port 100GbE Switch",
1095 "1",
1096 "$22,000.00",
1097 "$22,000.00",
1098 ),
1099 (
1100 "TB-CAB-RACK",
1101 "42U Server Rack + PDU",
1102 "1",
1103 "$4,500.00",
1104 "$4,500.00",
1105 ),
1106 (
1107 "TB-SVC-INST",
1108 "Installation & Configuration",
1109 "1",
1110 "$8,500.00",
1111 "$8,500.00",
1112 ),
1113 (
1114 "TB-SVC-SUPP",
1115 "3-Year Premium Support",
1116 "1",
1117 "$15,000.00",
1118 "$15,000.00",
1119 ),
1120 ];
1121
1122 for (i, (pn, desc, qty, unit, total)) in items.iter().enumerate() {
1123 let row = i + 1;
1124 tbl.cell(row, 0).unwrap().set_text(&format!("{}", i + 1));
1125 tbl.cell(row, 1).unwrap().set_text(pn);
1126 tbl.cell(row, 2).unwrap().set_text(desc);
1127 tbl.cell(row, 3).unwrap().set_text(qty);
1128 tbl.cell(row, 4).unwrap().set_text(unit);
1129 tbl.cell(row, 5).unwrap().set_text(total);
1130 if i % 2 == 0 {
1131 for c in 0..6 {
1132 tbl.cell(row, c).unwrap().shading("F0F8F8");
1133 }
1134 }
1135 }
1136 }
1137
1138 doc.add_paragraph("");
1139
1140 // ── Totals ──
1141 {
1142 let mut tbl = doc
1143 .add_table(4, 2)
1144 .borders(BorderStyle::Single, 2, "008B8B")
1145 .width(Length::inches(3.5))
1146 .alignment(Alignment::Right);
1147 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1148 tbl.cell(0, 1).unwrap().set_text("$139,920.00");
1149 tbl.cell(1, 0).unwrap().set_text("Shipping & Handling");
1150 tbl.cell(1, 1).unwrap().set_text("$2,500.00");
1151 tbl.cell(2, 0).unwrap().set_text("Tax (8.625%)");
1152 tbl.cell(2, 1).unwrap().set_text("$12,068.10");
1153 tbl.cell(3, 0).unwrap().set_text("TOTAL");
1154 tbl.cell(3, 0).unwrap().shading("E07020");
1155 tbl.cell(3, 1).unwrap().set_text("$154,488.10");
1156 tbl.cell(3, 1).unwrap().shading("E07020");
1157 }
1158
1159 doc.add_paragraph("");
1160
1161 // ── Terms & Conditions ──
1162 {
1163 let mut p = doc.add_paragraph("");
1164 p.add_run("Terms & Conditions")
1165 .bold(true)
1166 .size(14.0)
1167 .color("1A3C3C");
1168 }
1169 doc.add_numbered_list_item(
1170 "This quotation is valid for 30 calendar days from the date of issue.",
1171 0,
1172 );
1173 doc.add_numbered_list_item(
1174 "All prices are in USD and exclusive of applicable taxes unless stated.",
1175 0,
1176 );
1177 doc.add_numbered_list_item("Standard lead time is 4-6 weeks from PO receipt.", 0);
1178 doc.add_numbered_list_item(
1179 "Payment terms: Net 30 from invoice date. 2% discount for payment within 10 days.",
1180 0,
1181 );
1182 doc.add_numbered_list_item(
1183 "Warranty: 3-year manufacturer warranty on all hardware components.",
1184 0,
1185 );
1186 doc.add_numbered_list_item(
1187 "Returns subject to 15% restocking fee if initiated after 14 days.",
1188 0,
1189 );
1190
1191 doc.add_paragraph("");
1192 doc.add_paragraph("")
1193 .border_bottom(BorderStyle::Single, 4, "008B8B");
1194 doc.add_paragraph("");
1195 {
1196 let mut p = doc.add_paragraph("");
1197 p.add_run("Accepted by: ").bold(true);
1198 p.add_run("___________________________ Date: ___________");
1199 }
1200 {
1201 let mut p = doc.add_paragraph("");
1202 p.add_run("Print Name: ").bold(true);
1203 p.add_run("___________________________ Title: ___________");
1204 }
1205
1206 doc
1207}
1208
1209// =============================================================================
1210// 4. INVOICE — Crimson + charcoal scheme
1211// =============================================================================
1212fn generate_invoice(_samples_dir: &Path) -> Document {
1213 let mut doc = Document::new();
1214
1215 // Colors: Crimson #B22222, Charcoal #333333, Light #FAF0F0
1216 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1217 doc.set_margins(
1218 Length::inches(0.75),
1219 Length::inches(0.75),
1220 Length::inches(0.75),
1221 Length::inches(0.75),
1222 );
1223 doc.set_title("Invoice INV-2026-0392");
1224 doc.set_author("Walter White");
1225 doc.set_keywords("invoice, Tensorbee");
1226
1227 doc.set_footer("Tensorbee — Thank you for your business!");
1228
1229 // ── Company & Invoice Header ──
1230 {
1231 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1232 // Company name left, INVOICE right
1233 {
1234 let mut cell = tbl.cell(0, 0).unwrap();
1235 let mut p = cell.add_paragraph("");
1236 p.add_run("TENSORBEE")
1237 .bold(true)
1238 .size(32.0)
1239 .color("B22222")
1240 .font("Helvetica");
1241 }
1242 {
1243 let mut cell = tbl.cell(0, 1).unwrap();
1244 let mut p = cell.add_paragraph("");
1245 p.add_run("INVOICE").bold(true).size(32.0).color("333333");
1246 }
1247 tbl.cell(1, 0)
1248 .unwrap()
1249 .set_text("123 Innovation Drive, Suite 400");
1250 tbl.cell(1, 1).unwrap().set_text("Invoice #: INV-2026-0392");
1251 tbl.cell(2, 0).unwrap().set_text("San Francisco, CA 94105");
1252 tbl.cell(2, 1).unwrap().set_text("Date: February 22, 2026");
1253 tbl.cell(3, 0).unwrap().set_text("walter@tensorbee.com");
1254 tbl.cell(3, 1).unwrap().set_text("Due Date: March 24, 2026");
1255 }
1256
1257 doc.add_paragraph("")
1258 .border_bottom(BorderStyle::Thick, 8, "B22222");
1259 doc.add_paragraph("");
1260
1261 // ── Bill To / Ship To ──
1262 {
1263 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1264 tbl.cell(0, 0).unwrap().set_text("BILL TO");
1265 tbl.cell(0, 0).unwrap().shading("B22222");
1266 tbl.cell(0, 1).unwrap().set_text("SHIP TO");
1267 tbl.cell(0, 1).unwrap().shading("B22222");
1268 tbl.cell(1, 0).unwrap().set_text("Meridian Dynamics LLC");
1269 tbl.cell(1, 1).unwrap().set_text("Meridian Dynamics LLC");
1270 tbl.cell(2, 0).unwrap().set_text("456 Enterprise Blvd");
1271 tbl.cell(2, 1).unwrap().set_text("Attn: Server Room B");
1272 tbl.cell(3, 0).unwrap().set_text("Austin, TX 78701");
1273 tbl.cell(3, 1)
1274 .unwrap()
1275 .set_text("456 Enterprise Blvd, Austin, TX 78701");
1276 }
1277
1278 doc.add_paragraph("");
1279
1280 // ── Line Items ──
1281 {
1282 let mut tbl = doc
1283 .add_table(8, 5)
1284 .borders(BorderStyle::Single, 2, "B22222")
1285 .width_pct(100.0);
1286 let hdrs = ["Description", "Qty", "Unit Price", "Tax", "Amount"];
1287 for (c, h) in hdrs.iter().enumerate() {
1288 tbl.cell(0, c).unwrap().set_text(h);
1289 tbl.cell(0, c).unwrap().shading("B22222");
1290 }
1291 let items = [
1292 (
1293 "ML Infrastructure Setup — Phase 1",
1294 "1",
1295 "$45,000.00",
1296 "$3,881.25",
1297 "$48,881.25",
1298 ),
1299 (
1300 "Data Pipeline Development (160 hrs)",
1301 "160",
1302 "$225.00",
1303 "$3,105.00",
1304 "$39,105.00",
1305 ),
1306 (
1307 "GPU Cluster Configuration",
1308 "1",
1309 "$12,000.00",
1310 "$1,035.00",
1311 "$13,035.00",
1312 ),
1313 (
1314 "API Gateway Implementation",
1315 "1",
1316 "$18,500.00",
1317 "$1,595.63",
1318 "$20,095.63",
1319 ),
1320 (
1321 "Load Testing & QA (80 hrs)",
1322 "80",
1323 "$195.00",
1324 "$1,345.50",
1325 "$16,945.50",
1326 ),
1327 (
1328 "Documentation & Training",
1329 "1",
1330 "$8,000.00",
1331 "$690.00",
1332 "$8,690.00",
1333 ),
1334 (
1335 "Project Management (3 months)",
1336 "3",
1337 "$6,500.00",
1338 "$1,679.63",
1339 "$21,179.63",
1340 ),
1341 ];
1342 for (i, (desc, qty, unit, tax, amt)) in items.iter().enumerate() {
1343 let r = i + 1;
1344 tbl.cell(r, 0).unwrap().set_text(desc);
1345 tbl.cell(r, 1).unwrap().set_text(qty);
1346 tbl.cell(r, 2).unwrap().set_text(unit);
1347 tbl.cell(r, 3).unwrap().set_text(tax);
1348 tbl.cell(r, 4).unwrap().set_text(amt);
1349 if i % 2 == 0 {
1350 for c in 0..5 {
1351 tbl.cell(r, c).unwrap().shading("FAF0F0");
1352 }
1353 }
1354 }
1355 }
1356
1357 doc.add_paragraph("");
1358
1359 // ── Totals ──
1360 {
1361 let mut tbl = doc
1362 .add_table(4, 2)
1363 .borders(BorderStyle::Single, 2, "B22222")
1364 .width(Length::inches(3.0))
1365 .alignment(Alignment::Right);
1366 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1367 tbl.cell(0, 1).unwrap().set_text("$154,600.00");
1368 tbl.cell(1, 0).unwrap().set_text("Tax (8.625%)");
1369 tbl.cell(1, 1).unwrap().set_text("$13,334.25");
1370 tbl.cell(2, 0).unwrap().set_text("Discount (5%)");
1371 tbl.cell(2, 1).unwrap().set_text("-$7,730.00");
1372 tbl.cell(3, 0).unwrap().set_text("AMOUNT DUE");
1373 tbl.cell(3, 0).unwrap().shading("B22222");
1374 tbl.cell(3, 1).unwrap().set_text("$160,204.25");
1375 tbl.cell(3, 1).unwrap().shading("B22222");
1376 }
1377
1378 doc.add_paragraph("");
1379 doc.add_paragraph("");
1380
1381 // ── Payment Details ──
1382 {
1383 let mut p = doc.add_paragraph("");
1384 p.add_run("Payment Details")
1385 .bold(true)
1386 .size(14.0)
1387 .color("333333");
1388 }
1389 doc.add_paragraph("Bank: Silicon Valley Bank")
1390 .indent_left(Length::inches(0.3));
1391 doc.add_paragraph("Account: Tensorbee Inc. — 0847-2953-1120")
1392 .indent_left(Length::inches(0.3));
1393 doc.add_paragraph("Routing: 121140399")
1394 .indent_left(Length::inches(0.3));
1395 doc.add_paragraph("Swift: SVBKUS6S")
1396 .indent_left(Length::inches(0.3));
1397
1398 doc.add_paragraph("");
1399
1400 doc.add_paragraph("Please include invoice number INV-2026-0392 in the payment reference.")
1401 .shading("FAF0F0")
1402 .border_all(BorderStyle::Single, 2, "B22222");
1403
1404 doc
1405}
1406
1407// =============================================================================
1408// 5. REPORT — Forest green + earth tones, with images & hierarchical sections
1409// =============================================================================
1410fn generate_report(_samples_dir: &Path) -> Document {
1411 let mut doc = Document::new();
1412
1413 // Colors: Forest #2D5016, Sage #6B8E23, Earth #8B7355, Cream #FFFAF0
1414 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1415 doc.set_margins(
1416 Length::inches(1.0),
1417 Length::inches(1.0),
1418 Length::inches(1.0),
1419 Length::inches(1.0),
1420 );
1421 doc.set_title("Q4 2025 Environmental Impact Report");
1422 doc.set_author("Walter White");
1423 doc.set_subject("Quarterly Environmental Report");
1424 doc.set_keywords("environment, sustainability, report, Tensorbee");
1425
1426 doc.set_different_first_page(true);
1427 doc.set_first_page_header("");
1428 doc.set_header("Tensorbee — Q4 2025 Environmental Impact Report");
1429 doc.set_footer("CONFIDENTIAL — Page");
1430
1431 // ── Cover ──
1432 let cover_bg = create_sample_png(612, 792, [20, 50, 15]);
1433 doc.add_background_image(&cover_bg, "report_cover.png");
1434
1435 for _ in 0..5 {
1436 doc.add_paragraph("");
1437 }
1438 {
1439 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1440 p.add_run("Q4 2025")
1441 .bold(true)
1442 .size(48.0)
1443 .color("FFFFFF")
1444 .font("Georgia");
1445 }
1446 {
1447 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1448 p.add_run("Environmental Impact Report")
1449 .size(24.0)
1450 .color("90EE90")
1451 .font("Georgia")
1452 .italic(true);
1453 }
1454 doc.add_paragraph("");
1455 {
1456 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1457 p.add_run("Tensorbee — Sustainability Division")
1458 .size(14.0)
1459 .color("C0C0C0");
1460 }
1461 {
1462 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1463 p.add_run("Prepared by Walter White, Chief Sustainability Officer")
1464 .size(11.0)
1465 .color("AAAAAA");
1466 }
1467
1468 // ── TOC ──
1469 doc.add_paragraph("").page_break_before(true);
1470 doc.insert_toc(doc.content_count(), 3);
1471
1472 // ── Section 1: Executive Overview ──
1473 doc.add_paragraph("").page_break_before(true);
1474 doc.add_paragraph("Executive Overview").style("Heading1");
1475 doc.add_paragraph(
1476 "This report presents Tensorbee's environmental performance for Q4 2025. Our \
1477 sustainability initiatives have yielded a 34% reduction in carbon emissions compared \
1478 to Q4 2024, exceeding our target of 25%. Key achievements include the transition to \
1479 100% renewable energy in our primary data centers and the launch of our carbon offset \
1480 marketplace.",
1481 )
1482 .first_line_indent(Length::inches(0.3));
1483
1484 // Image: performance chart
1485 let chart_img = create_chart_png(400, 200);
1486 doc.add_paragraph("");
1487 doc.add_picture(
1488 &chart_img,
1489 "performance_chart.png",
1490 Length::inches(5.0),
1491 Length::inches(2.5),
1492 )
1493 .alignment(Alignment::Center);
1494 {
1495 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1496 p.add_run("Figure 1: Quarterly Carbon Emissions (tonnes CO2e)")
1497 .italic(true)
1498 .size(9.0)
1499 .color("666666");
1500 }
1501
1502 // ── Section 2: Energy Consumption ──
1503 doc.add_paragraph("").page_break_before(true);
1504 doc.add_paragraph("Energy Consumption").style("Heading1");
1505
1506 doc.add_paragraph("Data Center Operations")
1507 .style("Heading2");
1508 doc.add_paragraph(
1509 "Our three primary data centers consumed a combined 4.2 GWh in Q4 2025, \
1510 a 12% reduction from Q3 2025 driven by improved cooling efficiency and \
1511 server consolidation.",
1512 );
1513
1514 // Data table
1515 {
1516 let mut tbl = doc
1517 .add_table(5, 4)
1518 .borders(BorderStyle::Single, 4, "2D5016")
1519 .width_pct(100.0);
1520 let hdrs = ["Data Center", "Capacity (MW)", "Usage (GWh)", "PUE"];
1521 for (c, h) in hdrs.iter().enumerate() {
1522 tbl.cell(0, c).unwrap().set_text(h);
1523 tbl.cell(0, c).unwrap().shading("2D5016");
1524 }
1525 let rows = [
1526 ("San Francisco (Primary)", "3.2", "1.8", "1.12"),
1527 ("Dublin (EU)", "2.1", "1.2", "1.18"),
1528 ("Singapore (APAC)", "1.8", "1.2", "1.24"),
1529 ("TOTAL", "7.1", "4.2", "1.17 avg"),
1530 ];
1531 for (i, (dc, cap, use_, pue)) in rows.iter().enumerate() {
1532 tbl.cell(i + 1, 0).unwrap().set_text(dc);
1533 tbl.cell(i + 1, 1).unwrap().set_text(cap);
1534 tbl.cell(i + 1, 2).unwrap().set_text(use_);
1535 tbl.cell(i + 1, 3).unwrap().set_text(pue);
1536 if i == 3 {
1537 for c in 0..4 {
1538 tbl.cell(i + 1, c).unwrap().shading("E2EFDA");
1539 }
1540 }
1541 }
1542 }
1543
1544 doc.add_paragraph("");
1545
1546 doc.add_paragraph("Renewable Energy Mix").style("Heading2");
1547 doc.add_paragraph("Breakdown of energy sources across all facilities:");
1548
1549 doc.add_bullet_list_item("Solar PV: 42% (1.76 GWh)", 0);
1550 doc.add_bullet_list_item("Wind Power Purchase Agreements: 38% (1.60 GWh)", 0);
1551 doc.add_bullet_list_item("Hydroelectric: 12% (0.50 GWh)", 0);
1552 doc.add_bullet_list_item("Grid (non-renewable): 8% (0.34 GWh)", 0);
1553
1554 // ── Section 3: Water & Waste ──
1555 doc.add_paragraph("Water & Waste Management")
1556 .style("Heading1");
1557
1558 doc.add_paragraph("Water Usage").style("Heading2");
1559 doc.add_paragraph(
1560 "Total water consumption was 12.4 million gallons, a 15% reduction from Q3. \
1561 Our closed-loop cooling systems now recycle 78% of water used in cooling operations.",
1562 );
1563
1564 doc.add_paragraph("Waste Reduction").style("Heading2");
1565 doc.add_paragraph("E-waste management results for Q4:")
1566 .keep_with_next(true);
1567 {
1568 let mut tbl = doc
1569 .add_table(4, 3)
1570 .borders(BorderStyle::Single, 2, "6B8E23")
1571 .width_pct(80.0);
1572 tbl.cell(0, 0).unwrap().set_text("Category");
1573 tbl.cell(0, 0).unwrap().shading("6B8E23");
1574 tbl.cell(0, 1).unwrap().set_text("Weight (kg)");
1575 tbl.cell(0, 1).unwrap().shading("6B8E23");
1576 tbl.cell(0, 2).unwrap().set_text("Recycled %");
1577 tbl.cell(0, 2).unwrap().shading("6B8E23");
1578 let rows = [
1579 ("Server Hardware", "2,450", "94%"),
1580 ("Networking Equipment", "820", "91%"),
1581 ("Storage Media", "340", "99%"),
1582 ];
1583 for (i, (cat, wt, pct)) in rows.iter().enumerate() {
1584 tbl.cell(i + 1, 0).unwrap().set_text(cat);
1585 tbl.cell(i + 1, 1).unwrap().set_text(wt);
1586 tbl.cell(i + 1, 2).unwrap().set_text(pct);
1587 }
1588 }
1589
1590 // ── Section 4: Initiatives ──
1591 doc.add_paragraph("").page_break_before(true);
1592 doc.add_paragraph("Q1 2026 Initiatives").style("Heading1");
1593
1594 doc.add_paragraph("Planned Programs").style("Heading2");
1595 doc.add_numbered_list_item(
1596 "Deploy on-site battery storage at SF data center (2 MWh capacity)",
1597 0,
1598 );
1599 doc.add_numbered_list_item("Pilot immersion cooling in Dublin facility", 0);
1600 doc.add_numbered_list_item("Launch employee commute carbon offset program", 0);
1601 doc.add_numbered_list_item("Achieve ISO 14001 certification for Singapore facility", 0);
1602
1603 doc.add_paragraph("Investment Targets").style("Heading2");
1604 doc.add_paragraph("Sustainability CapEx allocation for FY2026:")
1605 .keep_with_next(true);
1606 {
1607 let mut tbl = doc
1608 .add_table(5, 2)
1609 .borders(BorderStyle::Single, 4, "2D5016");
1610 tbl.cell(0, 0).unwrap().set_text("Initiative");
1611 tbl.cell(0, 0).unwrap().shading("2D5016");
1612 tbl.cell(0, 1).unwrap().set_text("Budget");
1613 tbl.cell(0, 1).unwrap().shading("2D5016");
1614 let rows = [
1615 ("Battery Storage", "$1.2M"),
1616 ("Immersion Cooling Pilot", "$800K"),
1617 ("Solar Panel Expansion", "$2.1M"),
1618 ("Carbon Credits", "$500K"),
1619 ];
1620 for (i, (init, budget)) in rows.iter().enumerate() {
1621 tbl.cell(i + 1, 0).unwrap().set_text(init);
1622 tbl.cell(i + 1, 1).unwrap().set_text(budget);
1623 if i % 2 == 0 {
1624 tbl.cell(i + 1, 0).unwrap().shading("FFFAF0");
1625 tbl.cell(i + 1, 1).unwrap().shading("FFFAF0");
1626 }
1627 }
1628 }
1629
1630 // Image: sustainability roadmap
1631 let roadmap_img = create_sample_png(500, 100, [40, 80, 30]);
1632 doc.add_paragraph("");
1633 doc.add_picture(
1634 &roadmap_img,
1635 "roadmap.png",
1636 Length::inches(6.0),
1637 Length::inches(1.2),
1638 )
1639 .alignment(Alignment::Center);
1640 {
1641 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1642 p.add_run("Figure 2: 2026 Sustainability Roadmap")
1643 .italic(true)
1644 .size(9.0)
1645 .color("666666");
1646 }
1647
1648 doc.add_paragraph("");
1649 doc.add_paragraph(
1650 "For questions about this report, contact Walter White at sustainability@tensorbee.com.",
1651 )
1652 .shading("FFFAF0")
1653 .border_all(BorderStyle::Single, 2, "2D5016");
1654
1655 doc
1656}
1657
1658// =============================================================================
1659// 6. LETTER — Slate blue + silver scheme
1660// =============================================================================
1661fn generate_letter(_samples_dir: &Path) -> Document {
1662 let mut doc = Document::new();
1663
1664 // Colors: Slate #4A5568, Blue accent #3182CE
1665 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1666 doc.set_margins(
1667 Length::inches(1.25),
1668 Length::inches(1.25),
1669 Length::inches(1.25),
1670 Length::inches(1.25),
1671 );
1672 doc.set_title("Business Letter");
1673 doc.set_author("Walter White");
1674
1675 // Banner header using raw XML
1676 let logo_img = create_logo_png(220, 48);
1677 let banner = build_header_banner_xml(
1678 "rId1",
1679 &BannerOpts {
1680 bg_color: "4A5568",
1681 banner_width: 7772400,
1682 banner_height: 731520, // ~0.8"
1683 logo_width: 2011680,
1684 logo_height: 438912,
1685 logo_x_offset: 295125,
1686 logo_y_offset: 146304,
1687 },
1688 );
1689 doc.set_raw_header_with_images(
1690 banner,
1691 &[("rId1", &logo_img, "logo.png")],
1692 rdocx_oxml::header_footer::HdrFtrType::Default,
1693 );
1694 doc.set_margins(
1695 Length::twips(2000),
1696 Length::twips(1800),
1697 Length::twips(1440),
1698 Length::twips(1800),
1699 );
1700 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
1701
1702 // Footer with contact
1703 doc.set_footer(
1704 "Tensorbee | 123 Innovation Drive, Suite 400 | San Francisco, CA 94105 | tensorbee.com",
1705 );
1706
1707 // ── Sender Address ──
1708 doc.add_paragraph("Walter White");
1709 doc.add_paragraph("Chief Executive Officer");
1710 doc.add_paragraph("Tensorbee");
1711 doc.add_paragraph("123 Innovation Drive, Suite 400");
1712 doc.add_paragraph("San Francisco, CA 94105");
1713 doc.add_paragraph("");
1714 doc.add_paragraph("February 22, 2026");
1715
1716 doc.add_paragraph("");
1717
1718 // ── Recipient ──
1719 doc.add_paragraph("Ms. Sarah Chen");
1720 doc.add_paragraph("VP of Engineering");
1721 doc.add_paragraph("Meridian Dynamics LLC");
1722 doc.add_paragraph("456 Enterprise Boulevard");
1723 doc.add_paragraph("Austin, TX 78701");
1724
1725 doc.add_paragraph("");
1726
1727 // ── Subject ──
1728 {
1729 let mut p = doc.add_paragraph("");
1730 p.add_run("Re: Strategic Technology Partnership — Phase 2 Expansion")
1731 .bold(true);
1732 }
1733
1734 doc.add_paragraph("");
1735
1736 // ── Body ──
1737 doc.add_paragraph("Dear Ms. Chen,");
1738 doc.add_paragraph("");
1739
1740 doc.add_paragraph(
1741 "I am writing to express Tensorbee's enthusiasm for expanding our strategic technology \
1742 partnership with Meridian Dynamics. Following the successful completion of Phase 1, \
1743 which delivered a 40% improvement in your ML inference pipeline throughput, we believe \
1744 the foundation is firmly established for an ambitious Phase 2 engagement.",
1745 )
1746 .first_line_indent(Length::inches(0.5));
1747
1748 doc.add_paragraph(
1749 "During our recent executive review, your team highlighted three priority areas for \
1750 the next phase of collaboration:",
1751 )
1752 .first_line_indent(Length::inches(0.5));
1753
1754 doc.add_numbered_list_item(
1755 "Real-time anomaly detection for your financial transaction monitoring system, \
1756 targeting sub-100ms latency at 50,000 transactions per second.",
1757 0,
1758 );
1759 doc.add_numbered_list_item(
1760 "Federated learning infrastructure to enable model training across your distributed \
1761 data centers without centralizing sensitive financial data.",
1762 0,
1763 );
1764 doc.add_numbered_list_item(
1765 "MLOps automation to reduce your model deployment cycle from the current 5 days \
1766 to under 4 hours.",
1767 0,
1768 );
1769
1770 doc.add_paragraph(
1771 "Our engineering team has prepared a detailed technical proposal addressing each of \
1772 these areas. We have allocated a dedicated team of eight senior engineers, led by \
1773 our CTO, to ensure continuity with the Phase 1 team your organization has already \
1774 built a productive working relationship with.",
1775 )
1776 .first_line_indent(Length::inches(0.5));
1777
1778 doc.add_paragraph(
1779 "I would welcome the opportunity to present our Phase 2 proposal in person. My \
1780 assistant will reach out to coordinate a meeting at your convenience during the \
1781 first week of March.",
1782 )
1783 .first_line_indent(Length::inches(0.5));
1784
1785 doc.add_paragraph("");
1786
1787 doc.add_paragraph("Warm regards,");
1788 doc.add_paragraph("");
1789 doc.add_paragraph("");
1790
1791 // Signature line
1792 doc.add_paragraph("")
1793 .border_bottom(BorderStyle::Single, 4, "4A5568");
1794 {
1795 let mut p = doc.add_paragraph("");
1796 p.add_run("Walter White").bold(true).size(12.0);
1797 }
1798 {
1799 let mut p = doc.add_paragraph("");
1800 p.add_run("Chief Executive Officer, Tensorbee")
1801 .italic(true)
1802 .size(10.0)
1803 .color("666666");
1804 }
1805 {
1806 let mut p = doc.add_paragraph("");
1807 p.add_run("walter@tensorbee.com | +1 (415) 555-0199")
1808 .size(10.0)
1809 .color("888888");
1810 }
1811
1812 doc
1813}
1814
1815// =============================================================================
1816// 7. EMPLOYMENT CONTRACT — Purple + charcoal scheme
1817// =============================================================================
1818fn generate_contract(_samples_dir: &Path) -> Document {
1819 let mut doc = Document::new();
1820
1821 // Colors: Purple #5B2C6F, Plum #8E44AD, Gray #2C3E50
1822 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1823 doc.set_margins(
1824 Length::inches(1.25),
1825 Length::inches(1.0),
1826 Length::inches(1.0),
1827 Length::inches(1.0),
1828 );
1829 doc.set_title("Employment Agreement");
1830 doc.set_author("Tensorbee HR Department");
1831 doc.set_subject("Employment Contract — Walter White");
1832 doc.set_keywords("employment, contract, agreement, Tensorbee");
1833
1834 doc.set_header("TENSORBEE — Employment Agreement");
1835 doc.set_footer("Employment Agreement — Walter White — Page");
1836
1837 // ── Title Block ──
1838 doc.add_paragraph("")
1839 .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1840 {
1841 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1842 p.add_run("EMPLOYMENT AGREEMENT")
1843 .bold(true)
1844 .size(24.0)
1845 .color("5B2C6F")
1846 .font("Georgia");
1847 }
1848 doc.add_paragraph("")
1849 .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1850 doc.add_paragraph("");
1851
1852 // ── Parties ──
1853 doc.add_paragraph(
1854 "This Employment Agreement (\"Agreement\") is entered into as of February 22, 2026 \
1855 (\"Effective Date\"), by and between:",
1856 );
1857
1858 doc.add_paragraph("");
1859
1860 {
1861 let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1862 p.add_run("EMPLOYER: ").bold(true);
1863 p.add_run(
1864 "Tensorbee, Inc., a Delaware corporation with its principal place of business at \
1865 123 Innovation Drive, Suite 400, San Francisco, CA 94105 (\"Company\")",
1866 );
1867 }
1868 doc.add_paragraph("");
1869 {
1870 let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1871 p.add_run("EMPLOYEE: ").bold(true);
1872 p.add_run(
1873 "Walter White, residing at 308 Negra Arroyo Lane, Albuquerque, NM 87104 (\"Employee\")",
1874 );
1875 }
1876
1877 doc.add_paragraph("");
1878 doc.add_paragraph("The Company and Employee are collectively referred to as the \"Parties.\"");
1879 doc.add_paragraph("");
1880
1881 // ── Article 1: Position and Duties ──
1882 doc.add_paragraph("Article 1 — Position and Duties")
1883 .style("Heading1");
1884 {
1885 let mut p = doc.add_paragraph("");
1886 p.add_run("1.1 ").bold(true);
1887 p.add_run("The Company hereby employs the Employee as ");
1888 p.add_run("Chief Executive Officer (CEO)")
1889 .bold(true)
1890 .italic(true);
1891 p.add_run(", reporting directly to the Board of Directors.");
1892 }
1893 {
1894 let mut p = doc.add_paragraph("");
1895 p.add_run("1.2 ").bold(true);
1896 p.add_run("The Employee shall devote full working time, attention, and best efforts to \
1897 the performance of duties as reasonably assigned by the Board, including but not limited to:");
1898 }
1899 doc.add_bullet_list_item(
1900 "Setting and executing the Company's strategic vision and business plan",
1901 0,
1902 );
1903 doc.add_bullet_list_item(
1904 "Overseeing all operations, engineering, and commercial activities",
1905 0,
1906 );
1907 doc.add_bullet_list_item(
1908 "Representing the Company to investors, customers, and the public",
1909 0,
1910 );
1911 doc.add_bullet_list_item("Recruiting, developing, and retaining key talent", 0);
1912
1913 {
1914 let mut p = doc.add_paragraph("");
1915 p.add_run("1.3 ").bold(true);
1916 p.add_run(
1917 "The Employee's primary work location shall be the Company's San Francisco \
1918 headquarters, with reasonable travel as required by business needs.",
1919 );
1920 }
1921
1922 // ── Article 2: Compensation ──
1923 doc.add_paragraph("Article 2 — Compensation and Benefits")
1924 .style("Heading1");
1925 {
1926 let mut p = doc.add_paragraph("");
1927 p.add_run("2.1 Base Salary. ").bold(true);
1928 p.add_run("The Company shall pay the Employee an annual base salary of ");
1929 p.add_run("$375,000.00").bold(true);
1930 p.add_run(" (Three Hundred Seventy-Five Thousand Dollars), payable in accordance with the \
1931 Company's standard payroll schedule, less applicable withholdings and deductions.");
1932 }
1933 {
1934 let mut p = doc.add_paragraph("");
1935 p.add_run("2.2 Annual Bonus. ").bold(true);
1936 p.add_run("The Employee shall be eligible for an annual performance bonus of up to ");
1937 p.add_run("40%").bold(true);
1938 p.add_run(
1939 " of base salary, based on achievement of mutually agreed performance objectives.",
1940 );
1941 }
1942 {
1943 let mut p = doc.add_paragraph("");
1944 p.add_run("2.3 Equity. ").bold(true);
1945 p.add_run("Subject to Board approval, the Employee shall receive a stock option grant of ");
1946 p.add_run("500,000 shares").bold(true);
1947 p.add_run(
1948 " of the Company's common stock, vesting over four (4) years with a one-year cliff, \
1949 at an exercise price equal to the fair market value on the date of grant.",
1950 );
1951 }
1952
1953 // Compensation summary table
1954 doc.add_paragraph("");
1955 {
1956 let mut tbl = doc
1957 .add_table(5, 2)
1958 .borders(BorderStyle::Single, 4, "5B2C6F")
1959 .width_pct(70.0)
1960 .alignment(Alignment::Center);
1961 tbl.cell(0, 0).unwrap().set_text("Compensation Element");
1962 tbl.cell(0, 0).unwrap().shading("5B2C6F");
1963 tbl.cell(0, 1).unwrap().set_text("Value");
1964 tbl.cell(0, 1).unwrap().shading("5B2C6F");
1965 let items = [
1966 ("Base Salary", "$375,000/year"),
1967 ("Target Bonus", "Up to 40% ($150,000)"),
1968 ("Equity Grant", "500,000 shares (4yr vest)"),
1969 ("Total Target Comp", "$525,000 + equity"),
1970 ];
1971 for (i, (elem, val)) in items.iter().enumerate() {
1972 tbl.cell(i + 1, 0).unwrap().set_text(elem);
1973 tbl.cell(i + 1, 1).unwrap().set_text(val);
1974 if i == 3 {
1975 tbl.cell(i + 1, 0).unwrap().shading("E8DAEF");
1976 tbl.cell(i + 1, 1).unwrap().shading("E8DAEF");
1977 }
1978 }
1979 }
1980
1981 // ── Article 3: Benefits ──
1982 doc.add_paragraph("").page_break_before(true);
1983 doc.add_paragraph("Article 3 — Benefits").style("Heading1");
1984 {
1985 let mut p = doc.add_paragraph("");
1986 p.add_run("3.1 ").bold(true);
1987 p.add_run(
1988 "The Employee shall be entitled to participate in all benefit programs \
1989 generally available to senior executives, including:",
1990 );
1991 }
1992 doc.add_bullet_list_item(
1993 "Medical, dental, and vision insurance (100% premium coverage for employee and dependents)",
1994 0,
1995 );
1996 doc.add_bullet_list_item("401(k) retirement plan with 6% company match", 0);
1997 doc.add_bullet_list_item("Life insurance and long-term disability coverage", 0);
1998 doc.add_bullet_list_item("Annual professional development allowance of $10,000", 0);
1999
2000 {
2001 let mut p = doc.add_paragraph("");
2002 p.add_run("3.2 Paid Time Off. ").bold(true);
2003 p.add_run(
2004 "The Employee shall receive 25 days of paid vacation per year, plus Company holidays, \
2005 accruing on a monthly basis.",
2006 );
2007 }
2008
2009 // ── Article 4: Term and Termination ──
2010 doc.add_paragraph("Article 4 — Term and Termination")
2011 .style("Heading1");
2012 {
2013 let mut p = doc.add_paragraph("");
2014 p.add_run("4.1 At-Will Employment. ").bold(true);
2015 p.add_run(
2016 "This Agreement is for at-will employment and may be terminated by either Party \
2017 at any time, with or without cause, subject to the notice provisions herein.",
2018 );
2019 }
2020 {
2021 let mut p = doc.add_paragraph("");
2022 p.add_run("4.2 Notice Period. ").bold(true);
2023 p.add_run("Either Party shall provide ");
2024 p.add_run("ninety (90) days").bold(true);
2025 p.add_run(" written notice of termination, or pay in lieu thereof.");
2026 }
2027 {
2028 let mut p = doc.add_paragraph("");
2029 p.add_run("4.3 Severance. ").bold(true);
2030 p.add_run("In the event of termination by the Company without Cause, the Employee shall \
2031 receive (a) twelve (12) months of base salary continuation, (b) pro-rata bonus \
2032 for the year of termination, and (c) twelve (12) months of COBRA premium coverage.");
2033 }
2034
2035 // ── Article 5: Confidentiality ──
2036 doc.add_paragraph("Article 5 — Confidentiality and IP")
2037 .style("Heading1");
2038 {
2039 let mut p = doc.add_paragraph("");
2040 p.add_run("5.1 ").bold(true);
2041 p.add_run(
2042 "The Employee agrees to maintain strict confidentiality of all proprietary \
2043 information, trade secrets, and business plans of the Company during and after \
2044 employment.",
2045 );
2046 }
2047 {
2048 let mut p = doc.add_paragraph("");
2049 p.add_run("5.2 ").bold(true);
2050 p.add_run(
2051 "All intellectual property, inventions, and works of authorship created by the \
2052 Employee during the term of employment and within the scope of duties shall be \
2053 the sole and exclusive property of the Company.",
2054 );
2055 }
2056
2057 // ── Article 6: Non-Compete ──
2058 doc.add_paragraph("Article 6 — Non-Competition")
2059 .style("Heading1");
2060 {
2061 let mut p = doc.add_paragraph("");
2062 p.add_run("6.1 ").bold(true);
2063 p.add_run("For a period of twelve (12) months following termination, the Employee agrees \
2064 not to engage in any business that directly competes with the Company's core \
2065 business of AI infrastructure and ML operations services, within the United States.");
2066 }
2067
2068 // ── Article 7: General Provisions ──
2069 doc.add_paragraph("Article 7 — General Provisions")
2070 .style("Heading1");
2071 {
2072 let mut p = doc.add_paragraph("");
2073 p.add_run("7.1 Governing Law. ").bold(true);
2074 p.add_run(
2075 "This Agreement shall be governed by and construed in accordance with the laws of \
2076 the State of California.",
2077 );
2078 }
2079 {
2080 let mut p = doc.add_paragraph("");
2081 p.add_run("7.2 Entire Agreement. ").bold(true);
2082 p.add_run(
2083 "This Agreement constitutes the entire agreement between the Parties and supersedes \
2084 all prior negotiations, representations, and agreements.",
2085 );
2086 }
2087 {
2088 let mut p = doc.add_paragraph("");
2089 p.add_run("7.3 Amendment. ").bold(true);
2090 p.add_run(
2091 "This Agreement may only be amended by a written instrument signed by both Parties.",
2092 );
2093 }
2094
2095 // ── Signature Block ──
2096 doc.add_paragraph("").page_break_before(true);
2097 doc.add_paragraph("IN WITNESS WHEREOF, the Parties have executed this Employment Agreement as of the Effective Date.")
2098 .space_after(Length::pt(24.0));
2099
2100 // Signature table
2101 {
2102 let mut tbl = doc.add_table(6, 2).width_pct(100.0);
2103 tbl.cell(0, 0).unwrap().set_text("FOR THE COMPANY:");
2104 tbl.cell(0, 0).unwrap().shading("5B2C6F");
2105 tbl.cell(0, 1).unwrap().set_text("EMPLOYEE:");
2106 tbl.cell(0, 1).unwrap().shading("5B2C6F");
2107
2108 tbl.cell(1, 0).unwrap().set_text("");
2109 tbl.cell(1, 1).unwrap().set_text("");
2110 tbl.row(1).unwrap().height(Length::pt(40.0));
2111
2112 tbl.cell(2, 0)
2113 .unwrap()
2114 .set_text("Signature: ___________________________");
2115 tbl.cell(2, 1)
2116 .unwrap()
2117 .set_text("Signature: ___________________________");
2118 tbl.cell(3, 0)
2119 .unwrap()
2120 .set_text("Name: Board Representative");
2121 tbl.cell(3, 1).unwrap().set_text("Name: Walter White");
2122 tbl.cell(4, 0)
2123 .unwrap()
2124 .set_text("Title: Chair, Board of Directors");
2125 tbl.cell(4, 1)
2126 .unwrap()
2127 .set_text("Title: Chief Executive Officer");
2128 tbl.cell(5, 0).unwrap().set_text("Date: _______________");
2129 tbl.cell(5, 1).unwrap().set_text("Date: _______________");
2130 }
2131
2132 doc
2133}Sourcepub fn set_subject(&mut self, subject: &str)
pub fn set_subject(&mut self, subject: &str)
Set the document subject.
Examples found in repository?
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
97fn generate_feature_showcase(_samples_dir: &Path) -> Document {
98 let mut doc = Document::new();
99
100 // ── Page Setup & Metadata ──
101 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
102 doc.set_margins(
103 Length::inches(1.0),
104 Length::inches(1.0),
105 Length::inches(1.0),
106 Length::inches(1.0),
107 );
108 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
109 doc.set_gutter(Length::twips(0));
110 doc.set_title("rdocx Feature Showcase");
111 doc.set_author("rdocx Sample Generator");
112 doc.set_subject("Comprehensive feature demonstration");
113 doc.set_keywords("rdocx, docx, rust, sample, showcase");
114
115 // Headers & Footers with different first page
116 doc.set_different_first_page(true);
117 doc.set_first_page_header("rdocx");
118 doc.set_first_page_footer("Feature Showcase — Cover Page");
119 doc.set_header("rdocx Feature Showcase");
120 doc.set_footer("Generated by rdocx");
121
122 // ── COVER PAGE ──
123 let bg = create_sample_png(612, 792, [20, 45, 90]);
124 doc.add_background_image(&bg, "cover_bg.png");
125
126 for _ in 0..3 {
127 doc.add_paragraph("");
128 }
129 {
130 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
131 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
132 }
133 {
134 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
135 p.add_run("Complete Feature Showcase")
136 .size(28.0)
137 .color("FFFFFF")
138 .italic(true);
139 }
140 doc.add_paragraph("");
141 {
142 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
143 p.add_run("Every feature of the rdocx Rust library")
144 .size(13.0)
145 .color("B0C4DE");
146 }
147 {
148 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
149 p.add_run("demonstrated in a single document.")
150 .size(13.0)
151 .color("B0C4DE");
152 }
153
154 // ── TABLE OF CONTENTS ──
155 doc.add_paragraph("").page_break_before(true);
156 doc.insert_toc(doc.content_count(), 3);
157
158 // ── SECTION 1: TEXT FORMATTING ──
159 doc.add_paragraph("").page_break_before(true);
160 doc.add_paragraph("1. Text Formatting").style("Heading1");
161
162 // Paragraph Alignment
163 doc.add_paragraph("1.1 Paragraph Alignment")
164 .style("Heading2");
165 doc.add_paragraph("Left-aligned paragraph (default).")
166 .alignment(Alignment::Left);
167 doc.add_paragraph("Center-aligned paragraph.")
168 .alignment(Alignment::Center);
169 doc.add_paragraph("Right-aligned paragraph.")
170 .alignment(Alignment::Right);
171 doc.add_paragraph(
172 "Justified paragraph — this text is long enough to demonstrate how justified alignment \
173 distributes extra space across word gaps so lines fill the full width of the text area.",
174 )
175 .alignment(Alignment::Justify);
176
177 doc.add_paragraph("");
178
179 // Run Formatting
180 doc.add_paragraph("1.2 Run Formatting").style("Heading2");
181 {
182 let mut p = doc.add_paragraph("");
183 p.add_run("Bold").bold(true);
184 p.add_run(" | ");
185 p.add_run("Italic").italic(true);
186 p.add_run(" | ");
187 p.add_run("Bold Italic").bold(true).italic(true);
188 p.add_run(" | ");
189 p.add_run("Underline").underline(true);
190 p.add_run(" | ");
191 p.add_run("Strikethrough").strike(true);
192 p.add_run(" | ");
193 p.add_run("Double Strike").double_strike(true);
194 }
195 {
196 let mut p = doc.add_paragraph("");
197 p.add_run("Red").color("FF0000");
198 p.add_run(" | ");
199 p.add_run("Blue").color("0000FF");
200 p.add_run(" | ");
201 p.add_run("Green").color("00AA00");
202 p.add_run(" | ");
203 p.add_run("Highlighted").highlight("FFFF00");
204 }
205 {
206 let mut p = doc.add_paragraph("");
207 p.add_run("8pt").size(8.0);
208 p.add_run(" | ");
209 p.add_run("11pt").size(11.0);
210 p.add_run(" | ");
211 p.add_run("16pt").size(16.0);
212 p.add_run(" | ");
213 p.add_run("24pt").size(24.0);
214 }
215 {
216 let mut p = doc.add_paragraph("");
217 p.add_run("Arial").font("Arial");
218 p.add_run(" | ");
219 p.add_run("Times New Roman").font("Times New Roman");
220 p.add_run(" | ");
221 p.add_run("Courier New").font("Courier New");
222 }
223 {
224 let mut p = doc.add_paragraph("");
225 p.add_run("H");
226 p.add_run("2").subscript();
227 p.add_run("O (subscript) | E=mc");
228 p.add_run("2").superscript();
229 p.add_run(" (superscript)");
230 }
231 {
232 let mut p = doc.add_paragraph("");
233 p.add_run("ALL CAPS").all_caps(true);
234 p.add_run(" | ");
235 p.add_run("Small Caps").small_caps(true);
236 p.add_run(" | ");
237 p.add_run("Expanded").character_spacing(Length::twips(40));
238 p.add_run(" | ");
239 p.add_run("Hidden text (not visible)").hidden(true);
240 }
241
242 doc.add_paragraph("");
243
244 // Underline Styles
245 doc.add_paragraph("1.3 Underline Styles").style("Heading2");
246 {
247 let mut p = doc.add_paragraph("");
248 p.add_run("Single ")
249 .underline_style(rdocx::UnderlineStyle::Single);
250 p.add_run("Double ")
251 .underline_style(rdocx::UnderlineStyle::Double);
252 p.add_run("Thick ")
253 .underline_style(rdocx::UnderlineStyle::Thick);
254 p.add_run("Dotted ")
255 .underline_style(rdocx::UnderlineStyle::Dotted);
256 p.add_run("Dash ")
257 .underline_style(rdocx::UnderlineStyle::Dash);
258 p.add_run("Wave ")
259 .underline_style(rdocx::UnderlineStyle::Wave);
260 }
261
262 // ── SECTION 2: PARAGRAPH FORMATTING ──
263 doc.add_paragraph("").page_break_before(true);
264 doc.add_paragraph("2. Paragraph Formatting")
265 .style("Heading1");
266
267 doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
268 doc.add_paragraph("Paragraph with light green shading")
269 .shading("E2EFDA");
270 doc.add_paragraph("Paragraph with bottom border")
271 .border_bottom(BorderStyle::Single, 6, "2E75B6");
272 doc.add_paragraph("Paragraph with all borders (red)")
273 .border_all(BorderStyle::Single, 4, "FF0000");
274
275 doc.add_paragraph("2.2 Indentation").style("Heading2");
276 doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
277 .indent_left(Length::inches(1.0))
278 .hanging_indent(Length::inches(0.5));
279 doc.add_paragraph("0.5-inch first-line indent on this paragraph")
280 .first_line_indent(Length::inches(0.5));
281 doc.add_paragraph("Both left and right indent (0.75 inches each)")
282 .indent_left(Length::inches(0.75))
283 .indent_right(Length::inches(0.75));
284
285 doc.add_paragraph("2.3 Spacing & Line Height")
286 .style("Heading2");
287 doc.add_paragraph("Extra space before (24pt) and after (12pt)")
288 .space_before(Length::pt(24.0))
289 .space_after(Length::pt(12.0));
290 doc.add_paragraph(
291 "Double line spacing paragraph. This text should have extra vertical space between \
292 lines to demonstrate line_spacing_multiple(2.0).",
293 )
294 .line_spacing_multiple(2.0);
295 doc.add_paragraph("Exact 20pt line spacing (fixed height).")
296 .line_spacing(20.0);
297
298 doc.add_paragraph("2.4 Pagination Controls")
299 .style("Heading2");
300 doc.add_paragraph("keep_with_next — this paragraph stays with the next")
301 .keep_with_next(true);
302 doc.add_paragraph("(This paragraph was kept with the one above.)");
303 doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
304 .keep_together(true);
305 doc.add_paragraph("widow_control — prevents widow/orphan lines")
306 .widow_control(true);
307
308 // ── SECTION 3: LISTS ──
309 doc.add_paragraph("").page_break_before(true);
310 doc.add_paragraph("3. Lists").style("Heading1");
311
312 doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
313 doc.add_bullet_list_item("First item", 0);
314 doc.add_bullet_list_item("Second item", 0);
315 doc.add_bullet_list_item("Nested level 1", 1);
316 doc.add_bullet_list_item("Nested level 2", 2);
317 doc.add_bullet_list_item("Back to level 1", 1);
318 doc.add_bullet_list_item("Third item", 0);
319
320 doc.add_paragraph("");
321
322 doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
323 doc.add_numbered_list_item("First numbered item", 0);
324 doc.add_numbered_list_item("Second numbered item", 0);
325 doc.add_numbered_list_item("Sub-item A", 1);
326 doc.add_numbered_list_item("Sub-item B", 1);
327 doc.add_numbered_list_item("Third numbered item", 0);
328
329 // ── SECTION 4: TAB STOPS ──
330 doc.add_paragraph("");
331 doc.add_paragraph("4. Tab Stops").style("Heading1");
332
333 doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
334 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
335 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
336 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
337 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
338 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
339
340 doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
341 doc.add_paragraph("Item\t\tPrice")
342 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
343 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
344 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
345 doc.add_paragraph("Widget\t\t$19.99")
346 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
347 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
348 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
349 doc.add_paragraph("Gadget\t\t$249.50")
350 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
351 .add_tab_stop_with_leader(
352 TabAlignment::Right,
353 Length::inches(4.0),
354 TabLeader::Underscore,
355 )
356 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
357
358 // ── SECTION 5: TABLES ──
359 doc.add_paragraph("").page_break_before(true);
360 doc.add_paragraph("5. Tables").style("Heading1");
361
362 doc.add_paragraph("5.1 Basic Table").style("Heading2");
363 {
364 let mut tbl = doc
365 .add_table(4, 3)
366 .borders(BorderStyle::Single, 4, "000000");
367 for col in 0..3 {
368 tbl.cell(0, col).unwrap().shading("2E75B6");
369 }
370 tbl.cell(0, 0).unwrap().set_text("Name");
371 tbl.cell(0, 1).unwrap().set_text("Role");
372 tbl.cell(0, 2).unwrap().set_text("Location");
373 tbl.cell(1, 0).unwrap().set_text("Walter White");
374 tbl.cell(1, 1).unwrap().set_text("CEO");
375 tbl.cell(1, 2).unwrap().set_text("Albuquerque");
376 tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
377 tbl.cell(2, 1).unwrap().set_text("CTO");
378 tbl.cell(2, 2).unwrap().set_text("Remote");
379 tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
380 tbl.cell(3, 1).unwrap().set_text("Security");
381 tbl.cell(3, 2).unwrap().set_text("Washington");
382 }
383
384 doc.add_paragraph("");
385
386 doc.add_paragraph("5.2 Column Span & Cell Shading")
387 .style("Heading2");
388 {
389 let mut tbl = doc
390 .add_table(3, 4)
391 .borders(BorderStyle::Single, 4, "000000")
392 .width_pct(100.0);
393 tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
394 tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
395 tbl.cell(1, 0).unwrap().set_text("Region");
396 tbl.cell(1, 0).unwrap().shading("D6E4F0");
397 tbl.cell(1, 1).unwrap().set_text("Q1");
398 tbl.cell(1, 1).unwrap().shading("D6E4F0");
399 tbl.cell(1, 2).unwrap().set_text("Q2");
400 tbl.cell(1, 2).unwrap().shading("D6E4F0");
401 tbl.cell(1, 3).unwrap().set_text("Total");
402 tbl.cell(1, 3).unwrap().shading("D6E4F0");
403 tbl.cell(2, 0).unwrap().set_text("Americas");
404 tbl.cell(2, 1).unwrap().set_text("$2.4M");
405 tbl.cell(2, 2).unwrap().set_text("$2.7M");
406 tbl.cell(2, 3).unwrap().set_text("$5.1M");
407 }
408
409 doc.add_paragraph("");
410
411 doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
412 {
413 let mut tbl = doc
414 .add_table(4, 3)
415 .borders(BorderStyle::Single, 4, "000000");
416 tbl.cell(0, 0).unwrap().set_text("Category");
417 tbl.cell(0, 0).unwrap().shading("E2EFDA");
418 tbl.cell(0, 1).unwrap().set_text("Item");
419 tbl.cell(0, 1).unwrap().shading("E2EFDA");
420 tbl.cell(0, 2).unwrap().set_text("Price");
421 tbl.cell(0, 2).unwrap().shading("E2EFDA");
422 tbl.cell(1, 0).unwrap().set_text("Hardware");
423 tbl.cell(1, 0).unwrap().v_merge_restart();
424 tbl.cell(1, 1).unwrap().set_text("Laptop");
425 tbl.cell(1, 2).unwrap().set_text("$1,200");
426 tbl.cell(2, 0).unwrap().v_merge_continue();
427 tbl.cell(2, 1).unwrap().set_text("Monitor");
428 tbl.cell(2, 2).unwrap().set_text("$450");
429 tbl.cell(3, 0).unwrap().set_text("Software");
430 tbl.cell(3, 1).unwrap().set_text("IDE License");
431 tbl.cell(3, 2).unwrap().set_text("$200/yr");
432 }
433
434 doc.add_paragraph("");
435
436 doc.add_paragraph("5.4 Nested Table").style("Heading2");
437 {
438 let mut tbl = doc
439 .add_table(2, 2)
440 .borders(BorderStyle::Single, 6, "2E75B6");
441 tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
442 tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
443 tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
444 {
445 let mut cell = tbl.cell(1, 1).unwrap();
446 cell.set_text("Nested table below:");
447 let mut nested = cell
448 .add_table(2, 2)
449 .borders(BorderStyle::Single, 2, "FF6600");
450 nested.cell(0, 0).unwrap().set_text("A");
451 nested.cell(0, 1).unwrap().set_text("B");
452 nested.cell(1, 0).unwrap().set_text("C");
453 nested.cell(1, 1).unwrap().set_text("D");
454 }
455 }
456
457 doc.add_paragraph("");
458
459 doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
460 .style("Heading2");
461 {
462 let mut tbl = doc
463 .add_table(2, 3)
464 .borders(BorderStyle::Single, 4, "666666");
465 tbl.row(0).unwrap().height(Length::pt(50.0)).header();
466 tbl.cell(0, 0).unwrap().set_text("Top");
467 tbl.cell(0, 0)
468 .unwrap()
469 .vertical_alignment(VerticalAlignment::Top)
470 .shading("FFF2CC");
471 tbl.cell(0, 1).unwrap().set_text("Center");
472 tbl.cell(0, 1)
473 .unwrap()
474 .vertical_alignment(VerticalAlignment::Center)
475 .shading("D9E2F3");
476 tbl.cell(0, 2).unwrap().set_text("Bottom");
477 tbl.cell(0, 2)
478 .unwrap()
479 .vertical_alignment(VerticalAlignment::Bottom)
480 .shading("E2EFDA");
481 tbl.row(1).unwrap().cant_split();
482 tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
483 tbl.cell(1, 0).unwrap().no_wrap();
484 tbl.cell(1, 1).unwrap().set_text("Fixed width");
485 tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
486 tbl.cell(1, 2).unwrap().set_text("Normal");
487 }
488
489 // ── SECTION 6: IMAGES ──
490 doc.add_paragraph("").page_break_before(true);
491 doc.add_paragraph("6. Images").style("Heading1");
492
493 doc.add_paragraph("6.1 Inline Image").style("Heading2");
494 doc.add_paragraph("A blue gradient image below:");
495 let img = create_sample_png(200, 50, [0, 80, 200]);
496 doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
497
498 doc.add_paragraph("");
499 doc.add_paragraph("6.2 Header Image").style("Heading2");
500 let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
501 doc.set_header_image(
502 &hdr_img,
503 "header_logo.png",
504 Length::inches(2.0),
505 Length::inches(0.2),
506 );
507 doc.add_paragraph("The header now contains an inline image (check the top of this page).");
508
509 doc.add_paragraph("");
510 doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
511
512 // ── SECTION 7: CONTENT MANIPULATION ──
513 doc.add_paragraph("").page_break_before(true);
514 doc.add_paragraph("7. Content Manipulation")
515 .style("Heading1");
516
517 doc.add_paragraph("7.1 Placeholder Replacement")
518 .style("Heading2");
519 doc.add_paragraph("Customer: {{customer}}");
520 doc.add_paragraph("Date: {{date}}");
521 doc.add_paragraph("Reference: {{ref_number}}");
522 {
523 let mut tbl = doc
524 .add_table(2, 2)
525 .borders(BorderStyle::Single, 4, "000000");
526 tbl.cell(0, 0).unwrap().set_text("Project");
527 tbl.cell(0, 0).unwrap().shading("D6E4F0");
528 tbl.cell(0, 1).unwrap().set_text("{{project}}");
529 tbl.cell(1, 0).unwrap().set_text("Status");
530 tbl.cell(1, 0).unwrap().shading("D6E4F0");
531 tbl.cell(1, 1).unwrap().set_text("{{status}}");
532 }
533
534 let mut replacements = HashMap::new();
535 replacements.insert("{{customer}}", "Tensorbee Inc.");
536 replacements.insert("{{date}}", "February 22, 2026");
537 replacements.insert("{{ref_number}}", "TB-2026-001");
538 replacements.insert("{{project}}", "Infrastructure Upgrade");
539 replacements.insert("{{status}}", "In Progress");
540 let n = doc.replace_all(&replacements);
541 doc.add_paragraph(&format!("({n} placeholders replaced above)"));
542
543 doc.add_paragraph("");
544
545 doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
546 doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
547 let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
548 doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
549
550 doc.add_paragraph("");
551
552 doc.add_paragraph("7.3 Content Insertion").style("Heading2");
553 doc.add_paragraph("Section A: First content.");
554 doc.add_paragraph("Section C: Third content.");
555 if let Some(idx) = doc.find_content_index("Section C") {
556 doc.insert_paragraph(
557 idx,
558 "Section B: Inserted between A and C via find_content_index().",
559 );
560 }
561
562 // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
563 doc.add_paragraph("").section_break(SectionBreak::NextPage);
564 doc.add_paragraph("8. Section Breaks & Orientation")
565 .style("Heading1");
566 doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
567
568 {
569 let mut tbl = doc
570 .add_table(3, 7)
571 .borders(BorderStyle::Single, 4, "2E75B6");
572 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
573 for (c, h) in headers.iter().enumerate() {
574 tbl.cell(0, c).unwrap().set_text(h);
575 tbl.cell(0, c).unwrap().shading("2E75B6");
576 }
577 let data = [
578 [
579 "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
580 ],
581 [
582 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
583 ],
584 ];
585 for (r, row) in data.iter().enumerate() {
586 for (c, v) in row.iter().enumerate() {
587 tbl.cell(r + 1, c).unwrap().set_text(v);
588 }
589 }
590 }
591
592 doc.add_paragraph("")
593 .section_break(SectionBreak::NextPage)
594 .section_landscape();
595
596 // ── SECTION 9: CUSTOM STYLES ──
597 doc.add_paragraph("9. Custom Styles").style("Heading1");
598 doc.add_style(
599 StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
600 .based_on("Normal")
601 .paragraph_properties(rdocx_oxml::properties::CT_PPr {
602 shading: Some(rdocx_oxml::properties::CT_Shd {
603 val: "clear".to_string(),
604 color: None,
605 fill: Some("FFF2CC".to_string()),
606 }),
607 ..Default::default()
608 })
609 .run_properties(rdocx_oxml::properties::CT_RPr {
610 bold: Some(true),
611 color: Some("C45911".to_string()),
612 ..Default::default()
613 }),
614 );
615 doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
616 .style("CustomHighlight");
617
618 doc.add_paragraph("");
619
620 // ── SECTION 10: DOCUMENT INTELLIGENCE ──
621 doc.add_paragraph("10. Document Intelligence API")
622 .style("Heading1");
623
624 let wc = doc.word_count();
625 let hc = doc.headings().len();
626 let ic = doc.images().len();
627 let lc = doc.links().len();
628 doc.add_paragraph(&format!("Word count: {wc}"));
629 doc.add_paragraph(&format!("Heading count: {hc}"));
630 doc.add_paragraph(&format!("Image count: {ic}"));
631 doc.add_paragraph(&format!("Link count: {lc}"));
632
633 let outline = doc.document_outline();
634 doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
635
636 let issues = doc.audit_accessibility();
637 doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
638 for issue in &issues {
639 doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
640 }
641
642 // ── SECTION 11: DOCUMENT MERGING ──
643 doc.add_paragraph("");
644 doc.add_paragraph("11. Document Merging").style("Heading1");
645 {
646 let mut other = Document::new();
647 other.add_paragraph("This paragraph was merged from another document using append().");
648 doc.append(&other);
649 }
650
651 doc.add_paragraph("");
652
653 // ── SUMMARY ──
654 doc.add_paragraph("Summary of Demonstrated Features")
655 .style("Heading1");
656 let features = [
657 "Page setup: size, margins, header/footer distance, gutter",
658 "Document metadata: title, author, subject, keywords",
659 "Headers and footers: text, images, different first page",
660 "Background images (full-page behind text)",
661 "Table of Contents generation with bookmarks",
662 "Text formatting: bold, italic, underline styles, strike, color, size, font",
663 "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
664 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
665 "Pagination controls: keep-with-next, keep-together, widow control",
666 "Bullet and numbered lists with nesting",
667 "Tab stops with dot/underscore/hyphen leaders",
668 "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
669 "Row properties: header rows, exact height, cant-split",
670 "Cell properties: width, no-wrap, vertical alignment",
671 "Inline images and header images",
672 "Placeholder replacement (single and batch)",
673 "Regex-based find and replace",
674 "Content insertion at specific positions",
675 "Section breaks with mixed portrait/landscape",
676 "Custom paragraph and character styles",
677 "Document intelligence: word count, headings, outline, images, links",
678 "Accessibility audit",
679 "Document merging (append)",
680 "Export: DOCX, PDF, HTML, Markdown",
681 ];
682 for f in &features {
683 doc.add_bullet_list_item(f, 0);
684 }
685 doc.add_paragraph("");
686 doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
687 .alignment(Alignment::Center)
688 .shading("E2EFDA")
689 .border_all(BorderStyle::Single, 2, "00AA00");
690
691 doc
692}
693
694// =============================================================================
695// 2. PROPOSAL DOCUMENT — Deep navy + gold scheme
696// =============================================================================
697fn generate_proposal(_samples_dir: &Path) -> Document {
698 let mut doc = Document::new();
699
700 // Colors: Navy #1B2A4A, Gold #C5922E, Light #F4F1EB
701 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
702 doc.set_margins(
703 Length::inches(1.0),
704 Length::inches(1.0),
705 Length::inches(1.0),
706 Length::inches(1.0),
707 );
708 doc.set_title("AI Platform Modernization Proposal");
709 doc.set_author("Walter White");
710 doc.set_subject("Technology Proposal");
711 doc.set_keywords("proposal, AI, modernization, Tensorbee");
712
713 // Banner header
714 let logo_img = create_logo_png(220, 48);
715 let banner = build_header_banner_xml(
716 "rId1",
717 &BannerOpts {
718 bg_color: "1B2A4A",
719 banner_width: 7772400,
720 banner_height: 914400,
721 logo_width: 2011680,
722 logo_height: 438912,
723 logo_x_offset: 295125,
724 logo_y_offset: 237744,
725 },
726 );
727 doc.set_raw_header_with_images(
728 banner,
729 &[("rId1", &logo_img, "logo.png")],
730 rdocx_oxml::header_footer::HdrFtrType::Default,
731 );
732 doc.set_different_first_page(true);
733 let cover_banner = build_header_banner_xml(
734 "rId1",
735 &BannerOpts {
736 bg_color: "C5922E",
737 banner_width: 7772400,
738 banner_height: 914400,
739 logo_width: 2011680,
740 logo_height: 438912,
741 logo_x_offset: 295125,
742 logo_y_offset: 237744,
743 },
744 );
745 doc.set_raw_header_with_images(
746 cover_banner,
747 &[("rId1", &logo_img, "logo.png")],
748 rdocx_oxml::header_footer::HdrFtrType::First,
749 );
750 doc.set_margins(
751 Length::twips(2292),
752 Length::twips(1440),
753 Length::twips(1440),
754 Length::twips(1440),
755 );
756 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
757 doc.set_footer("Tensorbee — Confidential");
758
759 // Custom styles
760 doc.add_style(
761 StyleBuilder::paragraph("ProposalTitle", "Proposal Title")
762 .based_on("Normal")
763 .run_properties(rdocx_oxml::properties::CT_RPr {
764 bold: Some(true),
765 font_ascii: Some("Georgia".to_string()),
766 font_hansi: Some("Georgia".to_string()),
767 sz: Some(rdocx_oxml::HalfPoint(56)), // half-points → 28pt
768 color: Some("1B2A4A".to_string()),
769 ..Default::default()
770 }),
771 );
772
773 // ── Cover Page ──
774 for _ in 0..4 {
775 doc.add_paragraph("");
776 }
777 doc.add_paragraph("AI Platform Modernization")
778 .style("ProposalTitle")
779 .alignment(Alignment::Center);
780 doc.add_paragraph("")
781 .alignment(Alignment::Center)
782 .border_bottom(BorderStyle::Single, 8, "C5922E");
783 {
784 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
785 p.add_run("Prepared for Global Financial Corp.")
786 .size(14.0)
787 .color("666666")
788 .italic(true);
789 }
790 {
791 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
792 p.add_run("Prepared by: Walter White, CEO — Tensorbee")
793 .size(12.0)
794 .color("888888");
795 }
796 {
797 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
798 p.add_run("February 22, 2026").size(12.0).color("888888");
799 }
800
801 // ── TOC ──
802 doc.add_paragraph("").page_break_before(true);
803 doc.add_paragraph("Table of Contents").style("Heading1");
804 // We'll add a manual-style TOC since the insert_toc goes at a specific position
805 doc.insert_toc(doc.content_count(), 2);
806
807 // ── Executive Summary ──
808 doc.add_paragraph("").page_break_before(true);
809 doc.add_paragraph("Executive Summary").style("Heading1");
810 doc.add_paragraph(
811 "Tensorbee proposes a comprehensive modernization of Global Financial Corp's AI platform, \
812 transitioning from legacy batch-processing systems to a real-time inference architecture. \
813 This transformation will reduce model deployment time from weeks to hours, improve \
814 prediction accuracy by an estimated 23%, and deliver $4.2M in annual operational savings.",
815 )
816 .first_line_indent(Length::inches(0.3));
817 doc.add_paragraph(
818 "The project spans three phases over 18 months, with a total investment of $2.8M. \
819 Tensorbee brings deep expertise in ML infrastructure, having successfully completed \
820 similar transformations for three Fortune 500 financial institutions.",
821 )
822 .first_line_indent(Length::inches(0.3));
823
824 // Key metrics table
825 doc.add_paragraph("");
826 {
827 let mut tbl = doc
828 .add_table(5, 2)
829 .borders(BorderStyle::Single, 4, "1B2A4A")
830 .width_pct(80.0);
831 tbl.cell(0, 0).unwrap().set_text("Key Metric");
832 tbl.cell(0, 0).unwrap().shading("1B2A4A");
833 tbl.cell(0, 1).unwrap().set_text("Value");
834 tbl.cell(0, 1).unwrap().shading("1B2A4A");
835 let rows = [
836 ("Total Investment", "$2.8M"),
837 ("Annual Savings", "$4.2M"),
838 ("ROI (Year 1)", "150%"),
839 ("Timeline", "18 months"),
840 ];
841 for (i, (k, v)) in rows.iter().enumerate() {
842 tbl.cell(i + 1, 0).unwrap().set_text(k);
843 if i % 2 == 0 {
844 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
845 }
846 tbl.cell(i + 1, 1).unwrap().set_text(v);
847 if i % 2 == 0 {
848 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
849 }
850 }
851 }
852
853 // ── Problem Statement ──
854 doc.add_paragraph("").page_break_before(true);
855 doc.add_paragraph("Problem Statement").style("Heading1");
856 doc.add_paragraph(
857 "Global Financial Corp's current AI infrastructure faces three critical challenges:",
858 );
859 doc.add_numbered_list_item("Deployment Latency — New models take 3-4 weeks to deploy to production, compared to the industry benchmark of 2-3 days.", 0);
860 doc.add_numbered_list_item("Scalability Constraints — The batch processing architecture cannot handle real-time inference demands during peak trading hours.", 0);
861 doc.add_numbered_list_item("Technical Debt — Legacy Python 2.7 codebases and manual deployment scripts create reliability risks and slow iteration speed.", 0);
862
863 // ── Proposed Solution ──
864 doc.add_paragraph("").page_break_before(true);
865 doc.add_paragraph("Proposed Solution").style("Heading1");
866
867 doc.add_paragraph("Phase 1: Foundation (Months 1-6)")
868 .style("Heading2");
869 doc.add_bullet_list_item("Deploy Kubernetes-based ML serving infrastructure", 0);
870 doc.add_bullet_list_item("Implement CI/CD pipeline for model deployment", 0);
871 doc.add_bullet_list_item("Migrate top 5 production models to new platform", 0);
872
873 doc.add_paragraph("Phase 2: Expansion (Months 7-12)")
874 .style("Heading2");
875 doc.add_bullet_list_item(
876 "Real-time feature engineering pipeline (Apache Kafka + Flink)",
877 0,
878 );
879 doc.add_bullet_list_item("A/B testing framework for model variants", 0);
880 doc.add_bullet_list_item("Automated model monitoring and drift detection", 0);
881
882 doc.add_paragraph("Phase 3: Optimization (Months 13-18)")
883 .style("Heading2");
884 doc.add_bullet_list_item("GPU inference optimization (TensorRT/ONNX Runtime)", 0);
885 doc.add_bullet_list_item("Multi-region deployment for latency reduction", 0);
886 doc.add_bullet_list_item(
887 "Self-service model deployment portal for data science teams",
888 0,
889 );
890
891 // ── Budget ──
892 doc.add_paragraph("Budget Breakdown").style("Heading1");
893 {
894 let mut tbl = doc
895 .add_table(6, 3)
896 .borders(BorderStyle::Single, 4, "1B2A4A")
897 .width_pct(100.0);
898 tbl.cell(0, 0).unwrap().set_text("Category");
899 tbl.cell(0, 0).unwrap().shading("1B2A4A");
900 tbl.cell(0, 1).unwrap().set_text("Cost");
901 tbl.cell(0, 1).unwrap().shading("1B2A4A");
902 tbl.cell(0, 2).unwrap().set_text("Notes");
903 tbl.cell(0, 2).unwrap().shading("1B2A4A");
904 let items = [
905 (
906 "Infrastructure",
907 "$450,000",
908 "Cloud compute + GPU instances",
909 ),
910 ("Engineering Services", "$1,600,000", "12 FTE x 18 months"),
911 (
912 "Software Licenses",
913 "$350,000",
914 "Kafka, monitoring, CI/CD tools",
915 ),
916 (
917 "Training & Enablement",
918 "$200,000",
919 "Team training, documentation",
920 ),
921 ("Contingency (10%)", "$200,000", "Risk buffer"),
922 ];
923 for (i, (cat, cost, note)) in items.iter().enumerate() {
924 tbl.cell(i + 1, 0).unwrap().set_text(cat);
925 tbl.cell(i + 1, 1).unwrap().set_text(cost);
926 tbl.cell(i + 1, 2).unwrap().set_text(note);
927 if i % 2 == 0 {
928 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
929 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
930 tbl.cell(i + 1, 2).unwrap().shading("F4F1EB");
931 }
932 }
933 }
934
935 // ── Closing ──
936 doc.add_paragraph("");
937 doc.add_paragraph("Next Steps").style("Heading1");
938 doc.add_numbered_list_item(
939 "Schedule technical deep-dive with GFC engineering team (Week 1)",
940 0,
941 );
942 doc.add_numbered_list_item("Finalize scope and sign SOW (Week 2-3)", 0);
943 doc.add_numbered_list_item("Kick off Phase 1 with joint planning session (Week 4)", 0);
944
945 doc.add_paragraph("");
946 {
947 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
948 p.add_run("Walter White")
949 .bold(true)
950 .size(14.0)
951 .color("1B2A4A");
952 }
953 {
954 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
955 p.add_run("CEO, Tensorbee | walter@tensorbee.com")
956 .size(10.0)
957 .color("888888");
958 }
959
960 doc
961}
962
963// =============================================================================
964// 3. QUOTE / BILL OF MATERIALS — Teal + orange scheme
965// =============================================================================
966fn generate_quote(_samples_dir: &Path) -> Document {
967 let mut doc = Document::new();
968
969 // Colors: Teal #008B8B, Dark #1A3C3C, Orange #E07020, Light #F0F8F8
970 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
971 doc.set_margins(
972 Length::inches(0.75),
973 Length::inches(0.75),
974 Length::inches(0.75),
975 Length::inches(0.75),
976 );
977 doc.set_title("Quotation QT-2026-0147");
978 doc.set_author("Walter White");
979 doc.set_keywords("quote, BOM, Tensorbee");
980
981 doc.set_header("Tensorbee — Quotation");
982 doc.set_footer("QT-2026-0147 | Page");
983
984 // ── Header Block ──
985 {
986 let mut p = doc.add_paragraph("").alignment(Alignment::Left);
987 p.add_run("TENSORBEE")
988 .bold(true)
989 .size(28.0)
990 .color("008B8B")
991 .font("Helvetica");
992 }
993 doc.add_paragraph("123 Innovation Drive, Suite 400")
994 .alignment(Alignment::Left);
995 doc.add_paragraph("San Francisco, CA 94105 | +1 (415) 555-0199")
996 .alignment(Alignment::Left);
997 doc.add_paragraph("")
998 .border_bottom(BorderStyle::Single, 8, "008B8B");
999
1000 // Quote meta
1001 doc.add_paragraph("");
1002 {
1003 let mut tbl = doc.add_table(4, 4).width_pct(100.0);
1004 tbl.cell(0, 0).unwrap().set_text("QUOTATION");
1005 tbl.cell(0, 0).unwrap().shading("008B8B").grid_span(2);
1006 tbl.cell(0, 2).unwrap().set_text("DATE");
1007 tbl.cell(0, 2).unwrap().shading("008B8B");
1008 tbl.cell(0, 3).unwrap().set_text("VALID UNTIL");
1009 tbl.cell(0, 3).unwrap().shading("008B8B");
1010 tbl.cell(1, 0).unwrap().set_text("Quote #:");
1011 tbl.cell(1, 1).unwrap().set_text("QT-2026-0147");
1012 tbl.cell(1, 2).unwrap().set_text("Feb 22, 2026");
1013 tbl.cell(1, 3).unwrap().set_text("Mar 22, 2026");
1014 tbl.cell(2, 0).unwrap().set_text("Prepared by:");
1015 tbl.cell(2, 1).unwrap().set_text("Walter White");
1016 tbl.cell(2, 2).unwrap().set_text("Payment:");
1017 tbl.cell(2, 3).unwrap().set_text("Net 30");
1018 tbl.cell(3, 0).unwrap().set_text("Customer:");
1019 tbl.cell(3, 1).unwrap().set_text("Meridian Dynamics LLC");
1020 tbl.cell(3, 2).unwrap().set_text("Currency:");
1021 tbl.cell(3, 3).unwrap().set_text("USD");
1022 }
1023
1024 doc.add_paragraph("");
1025
1026 // ── Bill of Materials ──
1027 {
1028 let mut p = doc.add_paragraph("");
1029 p.add_run("Bill of Materials")
1030 .bold(true)
1031 .size(16.0)
1032 .color("1A3C3C");
1033 }
1034 doc.add_paragraph("");
1035
1036 {
1037 let mut tbl = doc
1038 .add_table(10, 6)
1039 .borders(BorderStyle::Single, 2, "008B8B")
1040 .width_pct(100.0)
1041 .layout_fixed();
1042 // Headers
1043 let hdrs = [
1044 "#",
1045 "Part Number",
1046 "Description",
1047 "Qty",
1048 "Unit Price",
1049 "Total",
1050 ];
1051 for (c, h) in hdrs.iter().enumerate() {
1052 tbl.cell(0, c).unwrap().set_text(h);
1053 tbl.cell(0, c).unwrap().shading("008B8B");
1054 }
1055
1056 let items: Vec<(&str, &str, &str, &str, &str)> = vec![
1057 (
1058 "TB-GPU-A100",
1059 "NVIDIA A100 80GB GPU",
1060 "4",
1061 "$12,500.00",
1062 "$50,000.00",
1063 ),
1064 (
1065 "TB-SRV-R750",
1066 "Dell R750xa Server Chassis",
1067 "2",
1068 "$8,200.00",
1069 "$16,400.00",
1070 ),
1071 (
1072 "TB-RAM-256G",
1073 "256GB DDR5 ECC Memory Module",
1074 "8",
1075 "$890.00",
1076 "$7,120.00",
1077 ),
1078 (
1079 "TB-SSD-3840",
1080 "3.84TB NVMe U.2 SSD",
1081 "8",
1082 "$1,150.00",
1083 "$9,200.00",
1084 ),
1085 (
1086 "TB-NET-CX7",
1087 "ConnectX-7 200GbE NIC",
1088 "4",
1089 "$1,800.00",
1090 "$7,200.00",
1091 ),
1092 (
1093 "TB-SW-48P",
1094 "48-Port 100GbE Switch",
1095 "1",
1096 "$22,000.00",
1097 "$22,000.00",
1098 ),
1099 (
1100 "TB-CAB-RACK",
1101 "42U Server Rack + PDU",
1102 "1",
1103 "$4,500.00",
1104 "$4,500.00",
1105 ),
1106 (
1107 "TB-SVC-INST",
1108 "Installation & Configuration",
1109 "1",
1110 "$8,500.00",
1111 "$8,500.00",
1112 ),
1113 (
1114 "TB-SVC-SUPP",
1115 "3-Year Premium Support",
1116 "1",
1117 "$15,000.00",
1118 "$15,000.00",
1119 ),
1120 ];
1121
1122 for (i, (pn, desc, qty, unit, total)) in items.iter().enumerate() {
1123 let row = i + 1;
1124 tbl.cell(row, 0).unwrap().set_text(&format!("{}", i + 1));
1125 tbl.cell(row, 1).unwrap().set_text(pn);
1126 tbl.cell(row, 2).unwrap().set_text(desc);
1127 tbl.cell(row, 3).unwrap().set_text(qty);
1128 tbl.cell(row, 4).unwrap().set_text(unit);
1129 tbl.cell(row, 5).unwrap().set_text(total);
1130 if i % 2 == 0 {
1131 for c in 0..6 {
1132 tbl.cell(row, c).unwrap().shading("F0F8F8");
1133 }
1134 }
1135 }
1136 }
1137
1138 doc.add_paragraph("");
1139
1140 // ── Totals ──
1141 {
1142 let mut tbl = doc
1143 .add_table(4, 2)
1144 .borders(BorderStyle::Single, 2, "008B8B")
1145 .width(Length::inches(3.5))
1146 .alignment(Alignment::Right);
1147 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1148 tbl.cell(0, 1).unwrap().set_text("$139,920.00");
1149 tbl.cell(1, 0).unwrap().set_text("Shipping & Handling");
1150 tbl.cell(1, 1).unwrap().set_text("$2,500.00");
1151 tbl.cell(2, 0).unwrap().set_text("Tax (8.625%)");
1152 tbl.cell(2, 1).unwrap().set_text("$12,068.10");
1153 tbl.cell(3, 0).unwrap().set_text("TOTAL");
1154 tbl.cell(3, 0).unwrap().shading("E07020");
1155 tbl.cell(3, 1).unwrap().set_text("$154,488.10");
1156 tbl.cell(3, 1).unwrap().shading("E07020");
1157 }
1158
1159 doc.add_paragraph("");
1160
1161 // ── Terms & Conditions ──
1162 {
1163 let mut p = doc.add_paragraph("");
1164 p.add_run("Terms & Conditions")
1165 .bold(true)
1166 .size(14.0)
1167 .color("1A3C3C");
1168 }
1169 doc.add_numbered_list_item(
1170 "This quotation is valid for 30 calendar days from the date of issue.",
1171 0,
1172 );
1173 doc.add_numbered_list_item(
1174 "All prices are in USD and exclusive of applicable taxes unless stated.",
1175 0,
1176 );
1177 doc.add_numbered_list_item("Standard lead time is 4-6 weeks from PO receipt.", 0);
1178 doc.add_numbered_list_item(
1179 "Payment terms: Net 30 from invoice date. 2% discount for payment within 10 days.",
1180 0,
1181 );
1182 doc.add_numbered_list_item(
1183 "Warranty: 3-year manufacturer warranty on all hardware components.",
1184 0,
1185 );
1186 doc.add_numbered_list_item(
1187 "Returns subject to 15% restocking fee if initiated after 14 days.",
1188 0,
1189 );
1190
1191 doc.add_paragraph("");
1192 doc.add_paragraph("")
1193 .border_bottom(BorderStyle::Single, 4, "008B8B");
1194 doc.add_paragraph("");
1195 {
1196 let mut p = doc.add_paragraph("");
1197 p.add_run("Accepted by: ").bold(true);
1198 p.add_run("___________________________ Date: ___________");
1199 }
1200 {
1201 let mut p = doc.add_paragraph("");
1202 p.add_run("Print Name: ").bold(true);
1203 p.add_run("___________________________ Title: ___________");
1204 }
1205
1206 doc
1207}
1208
1209// =============================================================================
1210// 4. INVOICE — Crimson + charcoal scheme
1211// =============================================================================
1212fn generate_invoice(_samples_dir: &Path) -> Document {
1213 let mut doc = Document::new();
1214
1215 // Colors: Crimson #B22222, Charcoal #333333, Light #FAF0F0
1216 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1217 doc.set_margins(
1218 Length::inches(0.75),
1219 Length::inches(0.75),
1220 Length::inches(0.75),
1221 Length::inches(0.75),
1222 );
1223 doc.set_title("Invoice INV-2026-0392");
1224 doc.set_author("Walter White");
1225 doc.set_keywords("invoice, Tensorbee");
1226
1227 doc.set_footer("Tensorbee — Thank you for your business!");
1228
1229 // ── Company & Invoice Header ──
1230 {
1231 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1232 // Company name left, INVOICE right
1233 {
1234 let mut cell = tbl.cell(0, 0).unwrap();
1235 let mut p = cell.add_paragraph("");
1236 p.add_run("TENSORBEE")
1237 .bold(true)
1238 .size(32.0)
1239 .color("B22222")
1240 .font("Helvetica");
1241 }
1242 {
1243 let mut cell = tbl.cell(0, 1).unwrap();
1244 let mut p = cell.add_paragraph("");
1245 p.add_run("INVOICE").bold(true).size(32.0).color("333333");
1246 }
1247 tbl.cell(1, 0)
1248 .unwrap()
1249 .set_text("123 Innovation Drive, Suite 400");
1250 tbl.cell(1, 1).unwrap().set_text("Invoice #: INV-2026-0392");
1251 tbl.cell(2, 0).unwrap().set_text("San Francisco, CA 94105");
1252 tbl.cell(2, 1).unwrap().set_text("Date: February 22, 2026");
1253 tbl.cell(3, 0).unwrap().set_text("walter@tensorbee.com");
1254 tbl.cell(3, 1).unwrap().set_text("Due Date: March 24, 2026");
1255 }
1256
1257 doc.add_paragraph("")
1258 .border_bottom(BorderStyle::Thick, 8, "B22222");
1259 doc.add_paragraph("");
1260
1261 // ── Bill To / Ship To ──
1262 {
1263 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1264 tbl.cell(0, 0).unwrap().set_text("BILL TO");
1265 tbl.cell(0, 0).unwrap().shading("B22222");
1266 tbl.cell(0, 1).unwrap().set_text("SHIP TO");
1267 tbl.cell(0, 1).unwrap().shading("B22222");
1268 tbl.cell(1, 0).unwrap().set_text("Meridian Dynamics LLC");
1269 tbl.cell(1, 1).unwrap().set_text("Meridian Dynamics LLC");
1270 tbl.cell(2, 0).unwrap().set_text("456 Enterprise Blvd");
1271 tbl.cell(2, 1).unwrap().set_text("Attn: Server Room B");
1272 tbl.cell(3, 0).unwrap().set_text("Austin, TX 78701");
1273 tbl.cell(3, 1)
1274 .unwrap()
1275 .set_text("456 Enterprise Blvd, Austin, TX 78701");
1276 }
1277
1278 doc.add_paragraph("");
1279
1280 // ── Line Items ──
1281 {
1282 let mut tbl = doc
1283 .add_table(8, 5)
1284 .borders(BorderStyle::Single, 2, "B22222")
1285 .width_pct(100.0);
1286 let hdrs = ["Description", "Qty", "Unit Price", "Tax", "Amount"];
1287 for (c, h) in hdrs.iter().enumerate() {
1288 tbl.cell(0, c).unwrap().set_text(h);
1289 tbl.cell(0, c).unwrap().shading("B22222");
1290 }
1291 let items = [
1292 (
1293 "ML Infrastructure Setup — Phase 1",
1294 "1",
1295 "$45,000.00",
1296 "$3,881.25",
1297 "$48,881.25",
1298 ),
1299 (
1300 "Data Pipeline Development (160 hrs)",
1301 "160",
1302 "$225.00",
1303 "$3,105.00",
1304 "$39,105.00",
1305 ),
1306 (
1307 "GPU Cluster Configuration",
1308 "1",
1309 "$12,000.00",
1310 "$1,035.00",
1311 "$13,035.00",
1312 ),
1313 (
1314 "API Gateway Implementation",
1315 "1",
1316 "$18,500.00",
1317 "$1,595.63",
1318 "$20,095.63",
1319 ),
1320 (
1321 "Load Testing & QA (80 hrs)",
1322 "80",
1323 "$195.00",
1324 "$1,345.50",
1325 "$16,945.50",
1326 ),
1327 (
1328 "Documentation & Training",
1329 "1",
1330 "$8,000.00",
1331 "$690.00",
1332 "$8,690.00",
1333 ),
1334 (
1335 "Project Management (3 months)",
1336 "3",
1337 "$6,500.00",
1338 "$1,679.63",
1339 "$21,179.63",
1340 ),
1341 ];
1342 for (i, (desc, qty, unit, tax, amt)) in items.iter().enumerate() {
1343 let r = i + 1;
1344 tbl.cell(r, 0).unwrap().set_text(desc);
1345 tbl.cell(r, 1).unwrap().set_text(qty);
1346 tbl.cell(r, 2).unwrap().set_text(unit);
1347 tbl.cell(r, 3).unwrap().set_text(tax);
1348 tbl.cell(r, 4).unwrap().set_text(amt);
1349 if i % 2 == 0 {
1350 for c in 0..5 {
1351 tbl.cell(r, c).unwrap().shading("FAF0F0");
1352 }
1353 }
1354 }
1355 }
1356
1357 doc.add_paragraph("");
1358
1359 // ── Totals ──
1360 {
1361 let mut tbl = doc
1362 .add_table(4, 2)
1363 .borders(BorderStyle::Single, 2, "B22222")
1364 .width(Length::inches(3.0))
1365 .alignment(Alignment::Right);
1366 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1367 tbl.cell(0, 1).unwrap().set_text("$154,600.00");
1368 tbl.cell(1, 0).unwrap().set_text("Tax (8.625%)");
1369 tbl.cell(1, 1).unwrap().set_text("$13,334.25");
1370 tbl.cell(2, 0).unwrap().set_text("Discount (5%)");
1371 tbl.cell(2, 1).unwrap().set_text("-$7,730.00");
1372 tbl.cell(3, 0).unwrap().set_text("AMOUNT DUE");
1373 tbl.cell(3, 0).unwrap().shading("B22222");
1374 tbl.cell(3, 1).unwrap().set_text("$160,204.25");
1375 tbl.cell(3, 1).unwrap().shading("B22222");
1376 }
1377
1378 doc.add_paragraph("");
1379 doc.add_paragraph("");
1380
1381 // ── Payment Details ──
1382 {
1383 let mut p = doc.add_paragraph("");
1384 p.add_run("Payment Details")
1385 .bold(true)
1386 .size(14.0)
1387 .color("333333");
1388 }
1389 doc.add_paragraph("Bank: Silicon Valley Bank")
1390 .indent_left(Length::inches(0.3));
1391 doc.add_paragraph("Account: Tensorbee Inc. — 0847-2953-1120")
1392 .indent_left(Length::inches(0.3));
1393 doc.add_paragraph("Routing: 121140399")
1394 .indent_left(Length::inches(0.3));
1395 doc.add_paragraph("Swift: SVBKUS6S")
1396 .indent_left(Length::inches(0.3));
1397
1398 doc.add_paragraph("");
1399
1400 doc.add_paragraph("Please include invoice number INV-2026-0392 in the payment reference.")
1401 .shading("FAF0F0")
1402 .border_all(BorderStyle::Single, 2, "B22222");
1403
1404 doc
1405}
1406
1407// =============================================================================
1408// 5. REPORT — Forest green + earth tones, with images & hierarchical sections
1409// =============================================================================
1410fn generate_report(_samples_dir: &Path) -> Document {
1411 let mut doc = Document::new();
1412
1413 // Colors: Forest #2D5016, Sage #6B8E23, Earth #8B7355, Cream #FFFAF0
1414 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1415 doc.set_margins(
1416 Length::inches(1.0),
1417 Length::inches(1.0),
1418 Length::inches(1.0),
1419 Length::inches(1.0),
1420 );
1421 doc.set_title("Q4 2025 Environmental Impact Report");
1422 doc.set_author("Walter White");
1423 doc.set_subject("Quarterly Environmental Report");
1424 doc.set_keywords("environment, sustainability, report, Tensorbee");
1425
1426 doc.set_different_first_page(true);
1427 doc.set_first_page_header("");
1428 doc.set_header("Tensorbee — Q4 2025 Environmental Impact Report");
1429 doc.set_footer("CONFIDENTIAL — Page");
1430
1431 // ── Cover ──
1432 let cover_bg = create_sample_png(612, 792, [20, 50, 15]);
1433 doc.add_background_image(&cover_bg, "report_cover.png");
1434
1435 for _ in 0..5 {
1436 doc.add_paragraph("");
1437 }
1438 {
1439 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1440 p.add_run("Q4 2025")
1441 .bold(true)
1442 .size(48.0)
1443 .color("FFFFFF")
1444 .font("Georgia");
1445 }
1446 {
1447 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1448 p.add_run("Environmental Impact Report")
1449 .size(24.0)
1450 .color("90EE90")
1451 .font("Georgia")
1452 .italic(true);
1453 }
1454 doc.add_paragraph("");
1455 {
1456 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1457 p.add_run("Tensorbee — Sustainability Division")
1458 .size(14.0)
1459 .color("C0C0C0");
1460 }
1461 {
1462 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1463 p.add_run("Prepared by Walter White, Chief Sustainability Officer")
1464 .size(11.0)
1465 .color("AAAAAA");
1466 }
1467
1468 // ── TOC ──
1469 doc.add_paragraph("").page_break_before(true);
1470 doc.insert_toc(doc.content_count(), 3);
1471
1472 // ── Section 1: Executive Overview ──
1473 doc.add_paragraph("").page_break_before(true);
1474 doc.add_paragraph("Executive Overview").style("Heading1");
1475 doc.add_paragraph(
1476 "This report presents Tensorbee's environmental performance for Q4 2025. Our \
1477 sustainability initiatives have yielded a 34% reduction in carbon emissions compared \
1478 to Q4 2024, exceeding our target of 25%. Key achievements include the transition to \
1479 100% renewable energy in our primary data centers and the launch of our carbon offset \
1480 marketplace.",
1481 )
1482 .first_line_indent(Length::inches(0.3));
1483
1484 // Image: performance chart
1485 let chart_img = create_chart_png(400, 200);
1486 doc.add_paragraph("");
1487 doc.add_picture(
1488 &chart_img,
1489 "performance_chart.png",
1490 Length::inches(5.0),
1491 Length::inches(2.5),
1492 )
1493 .alignment(Alignment::Center);
1494 {
1495 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1496 p.add_run("Figure 1: Quarterly Carbon Emissions (tonnes CO2e)")
1497 .italic(true)
1498 .size(9.0)
1499 .color("666666");
1500 }
1501
1502 // ── Section 2: Energy Consumption ──
1503 doc.add_paragraph("").page_break_before(true);
1504 doc.add_paragraph("Energy Consumption").style("Heading1");
1505
1506 doc.add_paragraph("Data Center Operations")
1507 .style("Heading2");
1508 doc.add_paragraph(
1509 "Our three primary data centers consumed a combined 4.2 GWh in Q4 2025, \
1510 a 12% reduction from Q3 2025 driven by improved cooling efficiency and \
1511 server consolidation.",
1512 );
1513
1514 // Data table
1515 {
1516 let mut tbl = doc
1517 .add_table(5, 4)
1518 .borders(BorderStyle::Single, 4, "2D5016")
1519 .width_pct(100.0);
1520 let hdrs = ["Data Center", "Capacity (MW)", "Usage (GWh)", "PUE"];
1521 for (c, h) in hdrs.iter().enumerate() {
1522 tbl.cell(0, c).unwrap().set_text(h);
1523 tbl.cell(0, c).unwrap().shading("2D5016");
1524 }
1525 let rows = [
1526 ("San Francisco (Primary)", "3.2", "1.8", "1.12"),
1527 ("Dublin (EU)", "2.1", "1.2", "1.18"),
1528 ("Singapore (APAC)", "1.8", "1.2", "1.24"),
1529 ("TOTAL", "7.1", "4.2", "1.17 avg"),
1530 ];
1531 for (i, (dc, cap, use_, pue)) in rows.iter().enumerate() {
1532 tbl.cell(i + 1, 0).unwrap().set_text(dc);
1533 tbl.cell(i + 1, 1).unwrap().set_text(cap);
1534 tbl.cell(i + 1, 2).unwrap().set_text(use_);
1535 tbl.cell(i + 1, 3).unwrap().set_text(pue);
1536 if i == 3 {
1537 for c in 0..4 {
1538 tbl.cell(i + 1, c).unwrap().shading("E2EFDA");
1539 }
1540 }
1541 }
1542 }
1543
1544 doc.add_paragraph("");
1545
1546 doc.add_paragraph("Renewable Energy Mix").style("Heading2");
1547 doc.add_paragraph("Breakdown of energy sources across all facilities:");
1548
1549 doc.add_bullet_list_item("Solar PV: 42% (1.76 GWh)", 0);
1550 doc.add_bullet_list_item("Wind Power Purchase Agreements: 38% (1.60 GWh)", 0);
1551 doc.add_bullet_list_item("Hydroelectric: 12% (0.50 GWh)", 0);
1552 doc.add_bullet_list_item("Grid (non-renewable): 8% (0.34 GWh)", 0);
1553
1554 // ── Section 3: Water & Waste ──
1555 doc.add_paragraph("Water & Waste Management")
1556 .style("Heading1");
1557
1558 doc.add_paragraph("Water Usage").style("Heading2");
1559 doc.add_paragraph(
1560 "Total water consumption was 12.4 million gallons, a 15% reduction from Q3. \
1561 Our closed-loop cooling systems now recycle 78% of water used in cooling operations.",
1562 );
1563
1564 doc.add_paragraph("Waste Reduction").style("Heading2");
1565 doc.add_paragraph("E-waste management results for Q4:")
1566 .keep_with_next(true);
1567 {
1568 let mut tbl = doc
1569 .add_table(4, 3)
1570 .borders(BorderStyle::Single, 2, "6B8E23")
1571 .width_pct(80.0);
1572 tbl.cell(0, 0).unwrap().set_text("Category");
1573 tbl.cell(0, 0).unwrap().shading("6B8E23");
1574 tbl.cell(0, 1).unwrap().set_text("Weight (kg)");
1575 tbl.cell(0, 1).unwrap().shading("6B8E23");
1576 tbl.cell(0, 2).unwrap().set_text("Recycled %");
1577 tbl.cell(0, 2).unwrap().shading("6B8E23");
1578 let rows = [
1579 ("Server Hardware", "2,450", "94%"),
1580 ("Networking Equipment", "820", "91%"),
1581 ("Storage Media", "340", "99%"),
1582 ];
1583 for (i, (cat, wt, pct)) in rows.iter().enumerate() {
1584 tbl.cell(i + 1, 0).unwrap().set_text(cat);
1585 tbl.cell(i + 1, 1).unwrap().set_text(wt);
1586 tbl.cell(i + 1, 2).unwrap().set_text(pct);
1587 }
1588 }
1589
1590 // ── Section 4: Initiatives ──
1591 doc.add_paragraph("").page_break_before(true);
1592 doc.add_paragraph("Q1 2026 Initiatives").style("Heading1");
1593
1594 doc.add_paragraph("Planned Programs").style("Heading2");
1595 doc.add_numbered_list_item(
1596 "Deploy on-site battery storage at SF data center (2 MWh capacity)",
1597 0,
1598 );
1599 doc.add_numbered_list_item("Pilot immersion cooling in Dublin facility", 0);
1600 doc.add_numbered_list_item("Launch employee commute carbon offset program", 0);
1601 doc.add_numbered_list_item("Achieve ISO 14001 certification for Singapore facility", 0);
1602
1603 doc.add_paragraph("Investment Targets").style("Heading2");
1604 doc.add_paragraph("Sustainability CapEx allocation for FY2026:")
1605 .keep_with_next(true);
1606 {
1607 let mut tbl = doc
1608 .add_table(5, 2)
1609 .borders(BorderStyle::Single, 4, "2D5016");
1610 tbl.cell(0, 0).unwrap().set_text("Initiative");
1611 tbl.cell(0, 0).unwrap().shading("2D5016");
1612 tbl.cell(0, 1).unwrap().set_text("Budget");
1613 tbl.cell(0, 1).unwrap().shading("2D5016");
1614 let rows = [
1615 ("Battery Storage", "$1.2M"),
1616 ("Immersion Cooling Pilot", "$800K"),
1617 ("Solar Panel Expansion", "$2.1M"),
1618 ("Carbon Credits", "$500K"),
1619 ];
1620 for (i, (init, budget)) in rows.iter().enumerate() {
1621 tbl.cell(i + 1, 0).unwrap().set_text(init);
1622 tbl.cell(i + 1, 1).unwrap().set_text(budget);
1623 if i % 2 == 0 {
1624 tbl.cell(i + 1, 0).unwrap().shading("FFFAF0");
1625 tbl.cell(i + 1, 1).unwrap().shading("FFFAF0");
1626 }
1627 }
1628 }
1629
1630 // Image: sustainability roadmap
1631 let roadmap_img = create_sample_png(500, 100, [40, 80, 30]);
1632 doc.add_paragraph("");
1633 doc.add_picture(
1634 &roadmap_img,
1635 "roadmap.png",
1636 Length::inches(6.0),
1637 Length::inches(1.2),
1638 )
1639 .alignment(Alignment::Center);
1640 {
1641 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1642 p.add_run("Figure 2: 2026 Sustainability Roadmap")
1643 .italic(true)
1644 .size(9.0)
1645 .color("666666");
1646 }
1647
1648 doc.add_paragraph("");
1649 doc.add_paragraph(
1650 "For questions about this report, contact Walter White at sustainability@tensorbee.com.",
1651 )
1652 .shading("FFFAF0")
1653 .border_all(BorderStyle::Single, 2, "2D5016");
1654
1655 doc
1656}
1657
1658// =============================================================================
1659// 6. LETTER — Slate blue + silver scheme
1660// =============================================================================
1661fn generate_letter(_samples_dir: &Path) -> Document {
1662 let mut doc = Document::new();
1663
1664 // Colors: Slate #4A5568, Blue accent #3182CE
1665 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1666 doc.set_margins(
1667 Length::inches(1.25),
1668 Length::inches(1.25),
1669 Length::inches(1.25),
1670 Length::inches(1.25),
1671 );
1672 doc.set_title("Business Letter");
1673 doc.set_author("Walter White");
1674
1675 // Banner header using raw XML
1676 let logo_img = create_logo_png(220, 48);
1677 let banner = build_header_banner_xml(
1678 "rId1",
1679 &BannerOpts {
1680 bg_color: "4A5568",
1681 banner_width: 7772400,
1682 banner_height: 731520, // ~0.8"
1683 logo_width: 2011680,
1684 logo_height: 438912,
1685 logo_x_offset: 295125,
1686 logo_y_offset: 146304,
1687 },
1688 );
1689 doc.set_raw_header_with_images(
1690 banner,
1691 &[("rId1", &logo_img, "logo.png")],
1692 rdocx_oxml::header_footer::HdrFtrType::Default,
1693 );
1694 doc.set_margins(
1695 Length::twips(2000),
1696 Length::twips(1800),
1697 Length::twips(1440),
1698 Length::twips(1800),
1699 );
1700 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
1701
1702 // Footer with contact
1703 doc.set_footer(
1704 "Tensorbee | 123 Innovation Drive, Suite 400 | San Francisco, CA 94105 | tensorbee.com",
1705 );
1706
1707 // ── Sender Address ──
1708 doc.add_paragraph("Walter White");
1709 doc.add_paragraph("Chief Executive Officer");
1710 doc.add_paragraph("Tensorbee");
1711 doc.add_paragraph("123 Innovation Drive, Suite 400");
1712 doc.add_paragraph("San Francisco, CA 94105");
1713 doc.add_paragraph("");
1714 doc.add_paragraph("February 22, 2026");
1715
1716 doc.add_paragraph("");
1717
1718 // ── Recipient ──
1719 doc.add_paragraph("Ms. Sarah Chen");
1720 doc.add_paragraph("VP of Engineering");
1721 doc.add_paragraph("Meridian Dynamics LLC");
1722 doc.add_paragraph("456 Enterprise Boulevard");
1723 doc.add_paragraph("Austin, TX 78701");
1724
1725 doc.add_paragraph("");
1726
1727 // ── Subject ──
1728 {
1729 let mut p = doc.add_paragraph("");
1730 p.add_run("Re: Strategic Technology Partnership — Phase 2 Expansion")
1731 .bold(true);
1732 }
1733
1734 doc.add_paragraph("");
1735
1736 // ── Body ──
1737 doc.add_paragraph("Dear Ms. Chen,");
1738 doc.add_paragraph("");
1739
1740 doc.add_paragraph(
1741 "I am writing to express Tensorbee's enthusiasm for expanding our strategic technology \
1742 partnership with Meridian Dynamics. Following the successful completion of Phase 1, \
1743 which delivered a 40% improvement in your ML inference pipeline throughput, we believe \
1744 the foundation is firmly established for an ambitious Phase 2 engagement.",
1745 )
1746 .first_line_indent(Length::inches(0.5));
1747
1748 doc.add_paragraph(
1749 "During our recent executive review, your team highlighted three priority areas for \
1750 the next phase of collaboration:",
1751 )
1752 .first_line_indent(Length::inches(0.5));
1753
1754 doc.add_numbered_list_item(
1755 "Real-time anomaly detection for your financial transaction monitoring system, \
1756 targeting sub-100ms latency at 50,000 transactions per second.",
1757 0,
1758 );
1759 doc.add_numbered_list_item(
1760 "Federated learning infrastructure to enable model training across your distributed \
1761 data centers without centralizing sensitive financial data.",
1762 0,
1763 );
1764 doc.add_numbered_list_item(
1765 "MLOps automation to reduce your model deployment cycle from the current 5 days \
1766 to under 4 hours.",
1767 0,
1768 );
1769
1770 doc.add_paragraph(
1771 "Our engineering team has prepared a detailed technical proposal addressing each of \
1772 these areas. We have allocated a dedicated team of eight senior engineers, led by \
1773 our CTO, to ensure continuity with the Phase 1 team your organization has already \
1774 built a productive working relationship with.",
1775 )
1776 .first_line_indent(Length::inches(0.5));
1777
1778 doc.add_paragraph(
1779 "I would welcome the opportunity to present our Phase 2 proposal in person. My \
1780 assistant will reach out to coordinate a meeting at your convenience during the \
1781 first week of March.",
1782 )
1783 .first_line_indent(Length::inches(0.5));
1784
1785 doc.add_paragraph("");
1786
1787 doc.add_paragraph("Warm regards,");
1788 doc.add_paragraph("");
1789 doc.add_paragraph("");
1790
1791 // Signature line
1792 doc.add_paragraph("")
1793 .border_bottom(BorderStyle::Single, 4, "4A5568");
1794 {
1795 let mut p = doc.add_paragraph("");
1796 p.add_run("Walter White").bold(true).size(12.0);
1797 }
1798 {
1799 let mut p = doc.add_paragraph("");
1800 p.add_run("Chief Executive Officer, Tensorbee")
1801 .italic(true)
1802 .size(10.0)
1803 .color("666666");
1804 }
1805 {
1806 let mut p = doc.add_paragraph("");
1807 p.add_run("walter@tensorbee.com | +1 (415) 555-0199")
1808 .size(10.0)
1809 .color("888888");
1810 }
1811
1812 doc
1813}
1814
1815// =============================================================================
1816// 7. EMPLOYMENT CONTRACT — Purple + charcoal scheme
1817// =============================================================================
1818fn generate_contract(_samples_dir: &Path) -> Document {
1819 let mut doc = Document::new();
1820
1821 // Colors: Purple #5B2C6F, Plum #8E44AD, Gray #2C3E50
1822 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1823 doc.set_margins(
1824 Length::inches(1.25),
1825 Length::inches(1.0),
1826 Length::inches(1.0),
1827 Length::inches(1.0),
1828 );
1829 doc.set_title("Employment Agreement");
1830 doc.set_author("Tensorbee HR Department");
1831 doc.set_subject("Employment Contract — Walter White");
1832 doc.set_keywords("employment, contract, agreement, Tensorbee");
1833
1834 doc.set_header("TENSORBEE — Employment Agreement");
1835 doc.set_footer("Employment Agreement — Walter White — Page");
1836
1837 // ── Title Block ──
1838 doc.add_paragraph("")
1839 .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1840 {
1841 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1842 p.add_run("EMPLOYMENT AGREEMENT")
1843 .bold(true)
1844 .size(24.0)
1845 .color("5B2C6F")
1846 .font("Georgia");
1847 }
1848 doc.add_paragraph("")
1849 .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1850 doc.add_paragraph("");
1851
1852 // ── Parties ──
1853 doc.add_paragraph(
1854 "This Employment Agreement (\"Agreement\") is entered into as of February 22, 2026 \
1855 (\"Effective Date\"), by and between:",
1856 );
1857
1858 doc.add_paragraph("");
1859
1860 {
1861 let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1862 p.add_run("EMPLOYER: ").bold(true);
1863 p.add_run(
1864 "Tensorbee, Inc., a Delaware corporation with its principal place of business at \
1865 123 Innovation Drive, Suite 400, San Francisco, CA 94105 (\"Company\")",
1866 );
1867 }
1868 doc.add_paragraph("");
1869 {
1870 let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1871 p.add_run("EMPLOYEE: ").bold(true);
1872 p.add_run(
1873 "Walter White, residing at 308 Negra Arroyo Lane, Albuquerque, NM 87104 (\"Employee\")",
1874 );
1875 }
1876
1877 doc.add_paragraph("");
1878 doc.add_paragraph("The Company and Employee are collectively referred to as the \"Parties.\"");
1879 doc.add_paragraph("");
1880
1881 // ── Article 1: Position and Duties ──
1882 doc.add_paragraph("Article 1 — Position and Duties")
1883 .style("Heading1");
1884 {
1885 let mut p = doc.add_paragraph("");
1886 p.add_run("1.1 ").bold(true);
1887 p.add_run("The Company hereby employs the Employee as ");
1888 p.add_run("Chief Executive Officer (CEO)")
1889 .bold(true)
1890 .italic(true);
1891 p.add_run(", reporting directly to the Board of Directors.");
1892 }
1893 {
1894 let mut p = doc.add_paragraph("");
1895 p.add_run("1.2 ").bold(true);
1896 p.add_run("The Employee shall devote full working time, attention, and best efforts to \
1897 the performance of duties as reasonably assigned by the Board, including but not limited to:");
1898 }
1899 doc.add_bullet_list_item(
1900 "Setting and executing the Company's strategic vision and business plan",
1901 0,
1902 );
1903 doc.add_bullet_list_item(
1904 "Overseeing all operations, engineering, and commercial activities",
1905 0,
1906 );
1907 doc.add_bullet_list_item(
1908 "Representing the Company to investors, customers, and the public",
1909 0,
1910 );
1911 doc.add_bullet_list_item("Recruiting, developing, and retaining key talent", 0);
1912
1913 {
1914 let mut p = doc.add_paragraph("");
1915 p.add_run("1.3 ").bold(true);
1916 p.add_run(
1917 "The Employee's primary work location shall be the Company's San Francisco \
1918 headquarters, with reasonable travel as required by business needs.",
1919 );
1920 }
1921
1922 // ── Article 2: Compensation ──
1923 doc.add_paragraph("Article 2 — Compensation and Benefits")
1924 .style("Heading1");
1925 {
1926 let mut p = doc.add_paragraph("");
1927 p.add_run("2.1 Base Salary. ").bold(true);
1928 p.add_run("The Company shall pay the Employee an annual base salary of ");
1929 p.add_run("$375,000.00").bold(true);
1930 p.add_run(" (Three Hundred Seventy-Five Thousand Dollars), payable in accordance with the \
1931 Company's standard payroll schedule, less applicable withholdings and deductions.");
1932 }
1933 {
1934 let mut p = doc.add_paragraph("");
1935 p.add_run("2.2 Annual Bonus. ").bold(true);
1936 p.add_run("The Employee shall be eligible for an annual performance bonus of up to ");
1937 p.add_run("40%").bold(true);
1938 p.add_run(
1939 " of base salary, based on achievement of mutually agreed performance objectives.",
1940 );
1941 }
1942 {
1943 let mut p = doc.add_paragraph("");
1944 p.add_run("2.3 Equity. ").bold(true);
1945 p.add_run("Subject to Board approval, the Employee shall receive a stock option grant of ");
1946 p.add_run("500,000 shares").bold(true);
1947 p.add_run(
1948 " of the Company's common stock, vesting over four (4) years with a one-year cliff, \
1949 at an exercise price equal to the fair market value on the date of grant.",
1950 );
1951 }
1952
1953 // Compensation summary table
1954 doc.add_paragraph("");
1955 {
1956 let mut tbl = doc
1957 .add_table(5, 2)
1958 .borders(BorderStyle::Single, 4, "5B2C6F")
1959 .width_pct(70.0)
1960 .alignment(Alignment::Center);
1961 tbl.cell(0, 0).unwrap().set_text("Compensation Element");
1962 tbl.cell(0, 0).unwrap().shading("5B2C6F");
1963 tbl.cell(0, 1).unwrap().set_text("Value");
1964 tbl.cell(0, 1).unwrap().shading("5B2C6F");
1965 let items = [
1966 ("Base Salary", "$375,000/year"),
1967 ("Target Bonus", "Up to 40% ($150,000)"),
1968 ("Equity Grant", "500,000 shares (4yr vest)"),
1969 ("Total Target Comp", "$525,000 + equity"),
1970 ];
1971 for (i, (elem, val)) in items.iter().enumerate() {
1972 tbl.cell(i + 1, 0).unwrap().set_text(elem);
1973 tbl.cell(i + 1, 1).unwrap().set_text(val);
1974 if i == 3 {
1975 tbl.cell(i + 1, 0).unwrap().shading("E8DAEF");
1976 tbl.cell(i + 1, 1).unwrap().shading("E8DAEF");
1977 }
1978 }
1979 }
1980
1981 // ── Article 3: Benefits ──
1982 doc.add_paragraph("").page_break_before(true);
1983 doc.add_paragraph("Article 3 — Benefits").style("Heading1");
1984 {
1985 let mut p = doc.add_paragraph("");
1986 p.add_run("3.1 ").bold(true);
1987 p.add_run(
1988 "The Employee shall be entitled to participate in all benefit programs \
1989 generally available to senior executives, including:",
1990 );
1991 }
1992 doc.add_bullet_list_item(
1993 "Medical, dental, and vision insurance (100% premium coverage for employee and dependents)",
1994 0,
1995 );
1996 doc.add_bullet_list_item("401(k) retirement plan with 6% company match", 0);
1997 doc.add_bullet_list_item("Life insurance and long-term disability coverage", 0);
1998 doc.add_bullet_list_item("Annual professional development allowance of $10,000", 0);
1999
2000 {
2001 let mut p = doc.add_paragraph("");
2002 p.add_run("3.2 Paid Time Off. ").bold(true);
2003 p.add_run(
2004 "The Employee shall receive 25 days of paid vacation per year, plus Company holidays, \
2005 accruing on a monthly basis.",
2006 );
2007 }
2008
2009 // ── Article 4: Term and Termination ──
2010 doc.add_paragraph("Article 4 — Term and Termination")
2011 .style("Heading1");
2012 {
2013 let mut p = doc.add_paragraph("");
2014 p.add_run("4.1 At-Will Employment. ").bold(true);
2015 p.add_run(
2016 "This Agreement is for at-will employment and may be terminated by either Party \
2017 at any time, with or without cause, subject to the notice provisions herein.",
2018 );
2019 }
2020 {
2021 let mut p = doc.add_paragraph("");
2022 p.add_run("4.2 Notice Period. ").bold(true);
2023 p.add_run("Either Party shall provide ");
2024 p.add_run("ninety (90) days").bold(true);
2025 p.add_run(" written notice of termination, or pay in lieu thereof.");
2026 }
2027 {
2028 let mut p = doc.add_paragraph("");
2029 p.add_run("4.3 Severance. ").bold(true);
2030 p.add_run("In the event of termination by the Company without Cause, the Employee shall \
2031 receive (a) twelve (12) months of base salary continuation, (b) pro-rata bonus \
2032 for the year of termination, and (c) twelve (12) months of COBRA premium coverage.");
2033 }
2034
2035 // ── Article 5: Confidentiality ──
2036 doc.add_paragraph("Article 5 — Confidentiality and IP")
2037 .style("Heading1");
2038 {
2039 let mut p = doc.add_paragraph("");
2040 p.add_run("5.1 ").bold(true);
2041 p.add_run(
2042 "The Employee agrees to maintain strict confidentiality of all proprietary \
2043 information, trade secrets, and business plans of the Company during and after \
2044 employment.",
2045 );
2046 }
2047 {
2048 let mut p = doc.add_paragraph("");
2049 p.add_run("5.2 ").bold(true);
2050 p.add_run(
2051 "All intellectual property, inventions, and works of authorship created by the \
2052 Employee during the term of employment and within the scope of duties shall be \
2053 the sole and exclusive property of the Company.",
2054 );
2055 }
2056
2057 // ── Article 6: Non-Compete ──
2058 doc.add_paragraph("Article 6 — Non-Competition")
2059 .style("Heading1");
2060 {
2061 let mut p = doc.add_paragraph("");
2062 p.add_run("6.1 ").bold(true);
2063 p.add_run("For a period of twelve (12) months following termination, the Employee agrees \
2064 not to engage in any business that directly competes with the Company's core \
2065 business of AI infrastructure and ML operations services, within the United States.");
2066 }
2067
2068 // ── Article 7: General Provisions ──
2069 doc.add_paragraph("Article 7 — General Provisions")
2070 .style("Heading1");
2071 {
2072 let mut p = doc.add_paragraph("");
2073 p.add_run("7.1 Governing Law. ").bold(true);
2074 p.add_run(
2075 "This Agreement shall be governed by and construed in accordance with the laws of \
2076 the State of California.",
2077 );
2078 }
2079 {
2080 let mut p = doc.add_paragraph("");
2081 p.add_run("7.2 Entire Agreement. ").bold(true);
2082 p.add_run(
2083 "This Agreement constitutes the entire agreement between the Parties and supersedes \
2084 all prior negotiations, representations, and agreements.",
2085 );
2086 }
2087 {
2088 let mut p = doc.add_paragraph("");
2089 p.add_run("7.3 Amendment. ").bold(true);
2090 p.add_run(
2091 "This Agreement may only be amended by a written instrument signed by both Parties.",
2092 );
2093 }
2094
2095 // ── Signature Block ──
2096 doc.add_paragraph("").page_break_before(true);
2097 doc.add_paragraph("IN WITNESS WHEREOF, the Parties have executed this Employment Agreement as of the Effective Date.")
2098 .space_after(Length::pt(24.0));
2099
2100 // Signature table
2101 {
2102 let mut tbl = doc.add_table(6, 2).width_pct(100.0);
2103 tbl.cell(0, 0).unwrap().set_text("FOR THE COMPANY:");
2104 tbl.cell(0, 0).unwrap().shading("5B2C6F");
2105 tbl.cell(0, 1).unwrap().set_text("EMPLOYEE:");
2106 tbl.cell(0, 1).unwrap().shading("5B2C6F");
2107
2108 tbl.cell(1, 0).unwrap().set_text("");
2109 tbl.cell(1, 1).unwrap().set_text("");
2110 tbl.row(1).unwrap().height(Length::pt(40.0));
2111
2112 tbl.cell(2, 0)
2113 .unwrap()
2114 .set_text("Signature: ___________________________");
2115 tbl.cell(2, 1)
2116 .unwrap()
2117 .set_text("Signature: ___________________________");
2118 tbl.cell(3, 0)
2119 .unwrap()
2120 .set_text("Name: Board Representative");
2121 tbl.cell(3, 1).unwrap().set_text("Name: Walter White");
2122 tbl.cell(4, 0)
2123 .unwrap()
2124 .set_text("Title: Chair, Board of Directors");
2125 tbl.cell(4, 1)
2126 .unwrap()
2127 .set_text("Title: Chief Executive Officer");
2128 tbl.cell(5, 0).unwrap().set_text("Date: _______________");
2129 tbl.cell(5, 1).unwrap().set_text("Date: _______________");
2130 }
2131
2132 doc
2133}Sourcepub fn set_keywords(&mut self, keywords: &str)
pub fn set_keywords(&mut self, keywords: &str)
Set the document keywords.
Examples found in repository?
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
97fn generate_feature_showcase(_samples_dir: &Path) -> Document {
98 let mut doc = Document::new();
99
100 // ── Page Setup & Metadata ──
101 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
102 doc.set_margins(
103 Length::inches(1.0),
104 Length::inches(1.0),
105 Length::inches(1.0),
106 Length::inches(1.0),
107 );
108 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
109 doc.set_gutter(Length::twips(0));
110 doc.set_title("rdocx Feature Showcase");
111 doc.set_author("rdocx Sample Generator");
112 doc.set_subject("Comprehensive feature demonstration");
113 doc.set_keywords("rdocx, docx, rust, sample, showcase");
114
115 // Headers & Footers with different first page
116 doc.set_different_first_page(true);
117 doc.set_first_page_header("rdocx");
118 doc.set_first_page_footer("Feature Showcase — Cover Page");
119 doc.set_header("rdocx Feature Showcase");
120 doc.set_footer("Generated by rdocx");
121
122 // ── COVER PAGE ──
123 let bg = create_sample_png(612, 792, [20, 45, 90]);
124 doc.add_background_image(&bg, "cover_bg.png");
125
126 for _ in 0..3 {
127 doc.add_paragraph("");
128 }
129 {
130 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
131 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
132 }
133 {
134 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
135 p.add_run("Complete Feature Showcase")
136 .size(28.0)
137 .color("FFFFFF")
138 .italic(true);
139 }
140 doc.add_paragraph("");
141 {
142 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
143 p.add_run("Every feature of the rdocx Rust library")
144 .size(13.0)
145 .color("B0C4DE");
146 }
147 {
148 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
149 p.add_run("demonstrated in a single document.")
150 .size(13.0)
151 .color("B0C4DE");
152 }
153
154 // ── TABLE OF CONTENTS ──
155 doc.add_paragraph("").page_break_before(true);
156 doc.insert_toc(doc.content_count(), 3);
157
158 // ── SECTION 1: TEXT FORMATTING ──
159 doc.add_paragraph("").page_break_before(true);
160 doc.add_paragraph("1. Text Formatting").style("Heading1");
161
162 // Paragraph Alignment
163 doc.add_paragraph("1.1 Paragraph Alignment")
164 .style("Heading2");
165 doc.add_paragraph("Left-aligned paragraph (default).")
166 .alignment(Alignment::Left);
167 doc.add_paragraph("Center-aligned paragraph.")
168 .alignment(Alignment::Center);
169 doc.add_paragraph("Right-aligned paragraph.")
170 .alignment(Alignment::Right);
171 doc.add_paragraph(
172 "Justified paragraph — this text is long enough to demonstrate how justified alignment \
173 distributes extra space across word gaps so lines fill the full width of the text area.",
174 )
175 .alignment(Alignment::Justify);
176
177 doc.add_paragraph("");
178
179 // Run Formatting
180 doc.add_paragraph("1.2 Run Formatting").style("Heading2");
181 {
182 let mut p = doc.add_paragraph("");
183 p.add_run("Bold").bold(true);
184 p.add_run(" | ");
185 p.add_run("Italic").italic(true);
186 p.add_run(" | ");
187 p.add_run("Bold Italic").bold(true).italic(true);
188 p.add_run(" | ");
189 p.add_run("Underline").underline(true);
190 p.add_run(" | ");
191 p.add_run("Strikethrough").strike(true);
192 p.add_run(" | ");
193 p.add_run("Double Strike").double_strike(true);
194 }
195 {
196 let mut p = doc.add_paragraph("");
197 p.add_run("Red").color("FF0000");
198 p.add_run(" | ");
199 p.add_run("Blue").color("0000FF");
200 p.add_run(" | ");
201 p.add_run("Green").color("00AA00");
202 p.add_run(" | ");
203 p.add_run("Highlighted").highlight("FFFF00");
204 }
205 {
206 let mut p = doc.add_paragraph("");
207 p.add_run("8pt").size(8.0);
208 p.add_run(" | ");
209 p.add_run("11pt").size(11.0);
210 p.add_run(" | ");
211 p.add_run("16pt").size(16.0);
212 p.add_run(" | ");
213 p.add_run("24pt").size(24.0);
214 }
215 {
216 let mut p = doc.add_paragraph("");
217 p.add_run("Arial").font("Arial");
218 p.add_run(" | ");
219 p.add_run("Times New Roman").font("Times New Roman");
220 p.add_run(" | ");
221 p.add_run("Courier New").font("Courier New");
222 }
223 {
224 let mut p = doc.add_paragraph("");
225 p.add_run("H");
226 p.add_run("2").subscript();
227 p.add_run("O (subscript) | E=mc");
228 p.add_run("2").superscript();
229 p.add_run(" (superscript)");
230 }
231 {
232 let mut p = doc.add_paragraph("");
233 p.add_run("ALL CAPS").all_caps(true);
234 p.add_run(" | ");
235 p.add_run("Small Caps").small_caps(true);
236 p.add_run(" | ");
237 p.add_run("Expanded").character_spacing(Length::twips(40));
238 p.add_run(" | ");
239 p.add_run("Hidden text (not visible)").hidden(true);
240 }
241
242 doc.add_paragraph("");
243
244 // Underline Styles
245 doc.add_paragraph("1.3 Underline Styles").style("Heading2");
246 {
247 let mut p = doc.add_paragraph("");
248 p.add_run("Single ")
249 .underline_style(rdocx::UnderlineStyle::Single);
250 p.add_run("Double ")
251 .underline_style(rdocx::UnderlineStyle::Double);
252 p.add_run("Thick ")
253 .underline_style(rdocx::UnderlineStyle::Thick);
254 p.add_run("Dotted ")
255 .underline_style(rdocx::UnderlineStyle::Dotted);
256 p.add_run("Dash ")
257 .underline_style(rdocx::UnderlineStyle::Dash);
258 p.add_run("Wave ")
259 .underline_style(rdocx::UnderlineStyle::Wave);
260 }
261
262 // ── SECTION 2: PARAGRAPH FORMATTING ──
263 doc.add_paragraph("").page_break_before(true);
264 doc.add_paragraph("2. Paragraph Formatting")
265 .style("Heading1");
266
267 doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
268 doc.add_paragraph("Paragraph with light green shading")
269 .shading("E2EFDA");
270 doc.add_paragraph("Paragraph with bottom border")
271 .border_bottom(BorderStyle::Single, 6, "2E75B6");
272 doc.add_paragraph("Paragraph with all borders (red)")
273 .border_all(BorderStyle::Single, 4, "FF0000");
274
275 doc.add_paragraph("2.2 Indentation").style("Heading2");
276 doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
277 .indent_left(Length::inches(1.0))
278 .hanging_indent(Length::inches(0.5));
279 doc.add_paragraph("0.5-inch first-line indent on this paragraph")
280 .first_line_indent(Length::inches(0.5));
281 doc.add_paragraph("Both left and right indent (0.75 inches each)")
282 .indent_left(Length::inches(0.75))
283 .indent_right(Length::inches(0.75));
284
285 doc.add_paragraph("2.3 Spacing & Line Height")
286 .style("Heading2");
287 doc.add_paragraph("Extra space before (24pt) and after (12pt)")
288 .space_before(Length::pt(24.0))
289 .space_after(Length::pt(12.0));
290 doc.add_paragraph(
291 "Double line spacing paragraph. This text should have extra vertical space between \
292 lines to demonstrate line_spacing_multiple(2.0).",
293 )
294 .line_spacing_multiple(2.0);
295 doc.add_paragraph("Exact 20pt line spacing (fixed height).")
296 .line_spacing(20.0);
297
298 doc.add_paragraph("2.4 Pagination Controls")
299 .style("Heading2");
300 doc.add_paragraph("keep_with_next — this paragraph stays with the next")
301 .keep_with_next(true);
302 doc.add_paragraph("(This paragraph was kept with the one above.)");
303 doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
304 .keep_together(true);
305 doc.add_paragraph("widow_control — prevents widow/orphan lines")
306 .widow_control(true);
307
308 // ── SECTION 3: LISTS ──
309 doc.add_paragraph("").page_break_before(true);
310 doc.add_paragraph("3. Lists").style("Heading1");
311
312 doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
313 doc.add_bullet_list_item("First item", 0);
314 doc.add_bullet_list_item("Second item", 0);
315 doc.add_bullet_list_item("Nested level 1", 1);
316 doc.add_bullet_list_item("Nested level 2", 2);
317 doc.add_bullet_list_item("Back to level 1", 1);
318 doc.add_bullet_list_item("Third item", 0);
319
320 doc.add_paragraph("");
321
322 doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
323 doc.add_numbered_list_item("First numbered item", 0);
324 doc.add_numbered_list_item("Second numbered item", 0);
325 doc.add_numbered_list_item("Sub-item A", 1);
326 doc.add_numbered_list_item("Sub-item B", 1);
327 doc.add_numbered_list_item("Third numbered item", 0);
328
329 // ── SECTION 4: TAB STOPS ──
330 doc.add_paragraph("");
331 doc.add_paragraph("4. Tab Stops").style("Heading1");
332
333 doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
334 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
335 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
336 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
337 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
338 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
339
340 doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
341 doc.add_paragraph("Item\t\tPrice")
342 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
343 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
344 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
345 doc.add_paragraph("Widget\t\t$19.99")
346 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
347 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
348 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
349 doc.add_paragraph("Gadget\t\t$249.50")
350 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
351 .add_tab_stop_with_leader(
352 TabAlignment::Right,
353 Length::inches(4.0),
354 TabLeader::Underscore,
355 )
356 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
357
358 // ── SECTION 5: TABLES ──
359 doc.add_paragraph("").page_break_before(true);
360 doc.add_paragraph("5. Tables").style("Heading1");
361
362 doc.add_paragraph("5.1 Basic Table").style("Heading2");
363 {
364 let mut tbl = doc
365 .add_table(4, 3)
366 .borders(BorderStyle::Single, 4, "000000");
367 for col in 0..3 {
368 tbl.cell(0, col).unwrap().shading("2E75B6");
369 }
370 tbl.cell(0, 0).unwrap().set_text("Name");
371 tbl.cell(0, 1).unwrap().set_text("Role");
372 tbl.cell(0, 2).unwrap().set_text("Location");
373 tbl.cell(1, 0).unwrap().set_text("Walter White");
374 tbl.cell(1, 1).unwrap().set_text("CEO");
375 tbl.cell(1, 2).unwrap().set_text("Albuquerque");
376 tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
377 tbl.cell(2, 1).unwrap().set_text("CTO");
378 tbl.cell(2, 2).unwrap().set_text("Remote");
379 tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
380 tbl.cell(3, 1).unwrap().set_text("Security");
381 tbl.cell(3, 2).unwrap().set_text("Washington");
382 }
383
384 doc.add_paragraph("");
385
386 doc.add_paragraph("5.2 Column Span & Cell Shading")
387 .style("Heading2");
388 {
389 let mut tbl = doc
390 .add_table(3, 4)
391 .borders(BorderStyle::Single, 4, "000000")
392 .width_pct(100.0);
393 tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
394 tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
395 tbl.cell(1, 0).unwrap().set_text("Region");
396 tbl.cell(1, 0).unwrap().shading("D6E4F0");
397 tbl.cell(1, 1).unwrap().set_text("Q1");
398 tbl.cell(1, 1).unwrap().shading("D6E4F0");
399 tbl.cell(1, 2).unwrap().set_text("Q2");
400 tbl.cell(1, 2).unwrap().shading("D6E4F0");
401 tbl.cell(1, 3).unwrap().set_text("Total");
402 tbl.cell(1, 3).unwrap().shading("D6E4F0");
403 tbl.cell(2, 0).unwrap().set_text("Americas");
404 tbl.cell(2, 1).unwrap().set_text("$2.4M");
405 tbl.cell(2, 2).unwrap().set_text("$2.7M");
406 tbl.cell(2, 3).unwrap().set_text("$5.1M");
407 }
408
409 doc.add_paragraph("");
410
411 doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
412 {
413 let mut tbl = doc
414 .add_table(4, 3)
415 .borders(BorderStyle::Single, 4, "000000");
416 tbl.cell(0, 0).unwrap().set_text("Category");
417 tbl.cell(0, 0).unwrap().shading("E2EFDA");
418 tbl.cell(0, 1).unwrap().set_text("Item");
419 tbl.cell(0, 1).unwrap().shading("E2EFDA");
420 tbl.cell(0, 2).unwrap().set_text("Price");
421 tbl.cell(0, 2).unwrap().shading("E2EFDA");
422 tbl.cell(1, 0).unwrap().set_text("Hardware");
423 tbl.cell(1, 0).unwrap().v_merge_restart();
424 tbl.cell(1, 1).unwrap().set_text("Laptop");
425 tbl.cell(1, 2).unwrap().set_text("$1,200");
426 tbl.cell(2, 0).unwrap().v_merge_continue();
427 tbl.cell(2, 1).unwrap().set_text("Monitor");
428 tbl.cell(2, 2).unwrap().set_text("$450");
429 tbl.cell(3, 0).unwrap().set_text("Software");
430 tbl.cell(3, 1).unwrap().set_text("IDE License");
431 tbl.cell(3, 2).unwrap().set_text("$200/yr");
432 }
433
434 doc.add_paragraph("");
435
436 doc.add_paragraph("5.4 Nested Table").style("Heading2");
437 {
438 let mut tbl = doc
439 .add_table(2, 2)
440 .borders(BorderStyle::Single, 6, "2E75B6");
441 tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
442 tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
443 tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
444 {
445 let mut cell = tbl.cell(1, 1).unwrap();
446 cell.set_text("Nested table below:");
447 let mut nested = cell
448 .add_table(2, 2)
449 .borders(BorderStyle::Single, 2, "FF6600");
450 nested.cell(0, 0).unwrap().set_text("A");
451 nested.cell(0, 1).unwrap().set_text("B");
452 nested.cell(1, 0).unwrap().set_text("C");
453 nested.cell(1, 1).unwrap().set_text("D");
454 }
455 }
456
457 doc.add_paragraph("");
458
459 doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
460 .style("Heading2");
461 {
462 let mut tbl = doc
463 .add_table(2, 3)
464 .borders(BorderStyle::Single, 4, "666666");
465 tbl.row(0).unwrap().height(Length::pt(50.0)).header();
466 tbl.cell(0, 0).unwrap().set_text("Top");
467 tbl.cell(0, 0)
468 .unwrap()
469 .vertical_alignment(VerticalAlignment::Top)
470 .shading("FFF2CC");
471 tbl.cell(0, 1).unwrap().set_text("Center");
472 tbl.cell(0, 1)
473 .unwrap()
474 .vertical_alignment(VerticalAlignment::Center)
475 .shading("D9E2F3");
476 tbl.cell(0, 2).unwrap().set_text("Bottom");
477 tbl.cell(0, 2)
478 .unwrap()
479 .vertical_alignment(VerticalAlignment::Bottom)
480 .shading("E2EFDA");
481 tbl.row(1).unwrap().cant_split();
482 tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
483 tbl.cell(1, 0).unwrap().no_wrap();
484 tbl.cell(1, 1).unwrap().set_text("Fixed width");
485 tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
486 tbl.cell(1, 2).unwrap().set_text("Normal");
487 }
488
489 // ── SECTION 6: IMAGES ──
490 doc.add_paragraph("").page_break_before(true);
491 doc.add_paragraph("6. Images").style("Heading1");
492
493 doc.add_paragraph("6.1 Inline Image").style("Heading2");
494 doc.add_paragraph("A blue gradient image below:");
495 let img = create_sample_png(200, 50, [0, 80, 200]);
496 doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
497
498 doc.add_paragraph("");
499 doc.add_paragraph("6.2 Header Image").style("Heading2");
500 let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
501 doc.set_header_image(
502 &hdr_img,
503 "header_logo.png",
504 Length::inches(2.0),
505 Length::inches(0.2),
506 );
507 doc.add_paragraph("The header now contains an inline image (check the top of this page).");
508
509 doc.add_paragraph("");
510 doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
511
512 // ── SECTION 7: CONTENT MANIPULATION ──
513 doc.add_paragraph("").page_break_before(true);
514 doc.add_paragraph("7. Content Manipulation")
515 .style("Heading1");
516
517 doc.add_paragraph("7.1 Placeholder Replacement")
518 .style("Heading2");
519 doc.add_paragraph("Customer: {{customer}}");
520 doc.add_paragraph("Date: {{date}}");
521 doc.add_paragraph("Reference: {{ref_number}}");
522 {
523 let mut tbl = doc
524 .add_table(2, 2)
525 .borders(BorderStyle::Single, 4, "000000");
526 tbl.cell(0, 0).unwrap().set_text("Project");
527 tbl.cell(0, 0).unwrap().shading("D6E4F0");
528 tbl.cell(0, 1).unwrap().set_text("{{project}}");
529 tbl.cell(1, 0).unwrap().set_text("Status");
530 tbl.cell(1, 0).unwrap().shading("D6E4F0");
531 tbl.cell(1, 1).unwrap().set_text("{{status}}");
532 }
533
534 let mut replacements = HashMap::new();
535 replacements.insert("{{customer}}", "Tensorbee Inc.");
536 replacements.insert("{{date}}", "February 22, 2026");
537 replacements.insert("{{ref_number}}", "TB-2026-001");
538 replacements.insert("{{project}}", "Infrastructure Upgrade");
539 replacements.insert("{{status}}", "In Progress");
540 let n = doc.replace_all(&replacements);
541 doc.add_paragraph(&format!("({n} placeholders replaced above)"));
542
543 doc.add_paragraph("");
544
545 doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
546 doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
547 let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
548 doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
549
550 doc.add_paragraph("");
551
552 doc.add_paragraph("7.3 Content Insertion").style("Heading2");
553 doc.add_paragraph("Section A: First content.");
554 doc.add_paragraph("Section C: Third content.");
555 if let Some(idx) = doc.find_content_index("Section C") {
556 doc.insert_paragraph(
557 idx,
558 "Section B: Inserted between A and C via find_content_index().",
559 );
560 }
561
562 // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
563 doc.add_paragraph("").section_break(SectionBreak::NextPage);
564 doc.add_paragraph("8. Section Breaks & Orientation")
565 .style("Heading1");
566 doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
567
568 {
569 let mut tbl = doc
570 .add_table(3, 7)
571 .borders(BorderStyle::Single, 4, "2E75B6");
572 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
573 for (c, h) in headers.iter().enumerate() {
574 tbl.cell(0, c).unwrap().set_text(h);
575 tbl.cell(0, c).unwrap().shading("2E75B6");
576 }
577 let data = [
578 [
579 "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
580 ],
581 [
582 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
583 ],
584 ];
585 for (r, row) in data.iter().enumerate() {
586 for (c, v) in row.iter().enumerate() {
587 tbl.cell(r + 1, c).unwrap().set_text(v);
588 }
589 }
590 }
591
592 doc.add_paragraph("")
593 .section_break(SectionBreak::NextPage)
594 .section_landscape();
595
596 // ── SECTION 9: CUSTOM STYLES ──
597 doc.add_paragraph("9. Custom Styles").style("Heading1");
598 doc.add_style(
599 StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
600 .based_on("Normal")
601 .paragraph_properties(rdocx_oxml::properties::CT_PPr {
602 shading: Some(rdocx_oxml::properties::CT_Shd {
603 val: "clear".to_string(),
604 color: None,
605 fill: Some("FFF2CC".to_string()),
606 }),
607 ..Default::default()
608 })
609 .run_properties(rdocx_oxml::properties::CT_RPr {
610 bold: Some(true),
611 color: Some("C45911".to_string()),
612 ..Default::default()
613 }),
614 );
615 doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
616 .style("CustomHighlight");
617
618 doc.add_paragraph("");
619
620 // ── SECTION 10: DOCUMENT INTELLIGENCE ──
621 doc.add_paragraph("10. Document Intelligence API")
622 .style("Heading1");
623
624 let wc = doc.word_count();
625 let hc = doc.headings().len();
626 let ic = doc.images().len();
627 let lc = doc.links().len();
628 doc.add_paragraph(&format!("Word count: {wc}"));
629 doc.add_paragraph(&format!("Heading count: {hc}"));
630 doc.add_paragraph(&format!("Image count: {ic}"));
631 doc.add_paragraph(&format!("Link count: {lc}"));
632
633 let outline = doc.document_outline();
634 doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
635
636 let issues = doc.audit_accessibility();
637 doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
638 for issue in &issues {
639 doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
640 }
641
642 // ── SECTION 11: DOCUMENT MERGING ──
643 doc.add_paragraph("");
644 doc.add_paragraph("11. Document Merging").style("Heading1");
645 {
646 let mut other = Document::new();
647 other.add_paragraph("This paragraph was merged from another document using append().");
648 doc.append(&other);
649 }
650
651 doc.add_paragraph("");
652
653 // ── SUMMARY ──
654 doc.add_paragraph("Summary of Demonstrated Features")
655 .style("Heading1");
656 let features = [
657 "Page setup: size, margins, header/footer distance, gutter",
658 "Document metadata: title, author, subject, keywords",
659 "Headers and footers: text, images, different first page",
660 "Background images (full-page behind text)",
661 "Table of Contents generation with bookmarks",
662 "Text formatting: bold, italic, underline styles, strike, color, size, font",
663 "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
664 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
665 "Pagination controls: keep-with-next, keep-together, widow control",
666 "Bullet and numbered lists with nesting",
667 "Tab stops with dot/underscore/hyphen leaders",
668 "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
669 "Row properties: header rows, exact height, cant-split",
670 "Cell properties: width, no-wrap, vertical alignment",
671 "Inline images and header images",
672 "Placeholder replacement (single and batch)",
673 "Regex-based find and replace",
674 "Content insertion at specific positions",
675 "Section breaks with mixed portrait/landscape",
676 "Custom paragraph and character styles",
677 "Document intelligence: word count, headings, outline, images, links",
678 "Accessibility audit",
679 "Document merging (append)",
680 "Export: DOCX, PDF, HTML, Markdown",
681 ];
682 for f in &features {
683 doc.add_bullet_list_item(f, 0);
684 }
685 doc.add_paragraph("");
686 doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
687 .alignment(Alignment::Center)
688 .shading("E2EFDA")
689 .border_all(BorderStyle::Single, 2, "00AA00");
690
691 doc
692}
693
694// =============================================================================
695// 2. PROPOSAL DOCUMENT — Deep navy + gold scheme
696// =============================================================================
697fn generate_proposal(_samples_dir: &Path) -> Document {
698 let mut doc = Document::new();
699
700 // Colors: Navy #1B2A4A, Gold #C5922E, Light #F4F1EB
701 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
702 doc.set_margins(
703 Length::inches(1.0),
704 Length::inches(1.0),
705 Length::inches(1.0),
706 Length::inches(1.0),
707 );
708 doc.set_title("AI Platform Modernization Proposal");
709 doc.set_author("Walter White");
710 doc.set_subject("Technology Proposal");
711 doc.set_keywords("proposal, AI, modernization, Tensorbee");
712
713 // Banner header
714 let logo_img = create_logo_png(220, 48);
715 let banner = build_header_banner_xml(
716 "rId1",
717 &BannerOpts {
718 bg_color: "1B2A4A",
719 banner_width: 7772400,
720 banner_height: 914400,
721 logo_width: 2011680,
722 logo_height: 438912,
723 logo_x_offset: 295125,
724 logo_y_offset: 237744,
725 },
726 );
727 doc.set_raw_header_with_images(
728 banner,
729 &[("rId1", &logo_img, "logo.png")],
730 rdocx_oxml::header_footer::HdrFtrType::Default,
731 );
732 doc.set_different_first_page(true);
733 let cover_banner = build_header_banner_xml(
734 "rId1",
735 &BannerOpts {
736 bg_color: "C5922E",
737 banner_width: 7772400,
738 banner_height: 914400,
739 logo_width: 2011680,
740 logo_height: 438912,
741 logo_x_offset: 295125,
742 logo_y_offset: 237744,
743 },
744 );
745 doc.set_raw_header_with_images(
746 cover_banner,
747 &[("rId1", &logo_img, "logo.png")],
748 rdocx_oxml::header_footer::HdrFtrType::First,
749 );
750 doc.set_margins(
751 Length::twips(2292),
752 Length::twips(1440),
753 Length::twips(1440),
754 Length::twips(1440),
755 );
756 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
757 doc.set_footer("Tensorbee — Confidential");
758
759 // Custom styles
760 doc.add_style(
761 StyleBuilder::paragraph("ProposalTitle", "Proposal Title")
762 .based_on("Normal")
763 .run_properties(rdocx_oxml::properties::CT_RPr {
764 bold: Some(true),
765 font_ascii: Some("Georgia".to_string()),
766 font_hansi: Some("Georgia".to_string()),
767 sz: Some(rdocx_oxml::HalfPoint(56)), // half-points → 28pt
768 color: Some("1B2A4A".to_string()),
769 ..Default::default()
770 }),
771 );
772
773 // ── Cover Page ──
774 for _ in 0..4 {
775 doc.add_paragraph("");
776 }
777 doc.add_paragraph("AI Platform Modernization")
778 .style("ProposalTitle")
779 .alignment(Alignment::Center);
780 doc.add_paragraph("")
781 .alignment(Alignment::Center)
782 .border_bottom(BorderStyle::Single, 8, "C5922E");
783 {
784 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
785 p.add_run("Prepared for Global Financial Corp.")
786 .size(14.0)
787 .color("666666")
788 .italic(true);
789 }
790 {
791 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
792 p.add_run("Prepared by: Walter White, CEO — Tensorbee")
793 .size(12.0)
794 .color("888888");
795 }
796 {
797 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
798 p.add_run("February 22, 2026").size(12.0).color("888888");
799 }
800
801 // ── TOC ──
802 doc.add_paragraph("").page_break_before(true);
803 doc.add_paragraph("Table of Contents").style("Heading1");
804 // We'll add a manual-style TOC since the insert_toc goes at a specific position
805 doc.insert_toc(doc.content_count(), 2);
806
807 // ── Executive Summary ──
808 doc.add_paragraph("").page_break_before(true);
809 doc.add_paragraph("Executive Summary").style("Heading1");
810 doc.add_paragraph(
811 "Tensorbee proposes a comprehensive modernization of Global Financial Corp's AI platform, \
812 transitioning from legacy batch-processing systems to a real-time inference architecture. \
813 This transformation will reduce model deployment time from weeks to hours, improve \
814 prediction accuracy by an estimated 23%, and deliver $4.2M in annual operational savings.",
815 )
816 .first_line_indent(Length::inches(0.3));
817 doc.add_paragraph(
818 "The project spans three phases over 18 months, with a total investment of $2.8M. \
819 Tensorbee brings deep expertise in ML infrastructure, having successfully completed \
820 similar transformations for three Fortune 500 financial institutions.",
821 )
822 .first_line_indent(Length::inches(0.3));
823
824 // Key metrics table
825 doc.add_paragraph("");
826 {
827 let mut tbl = doc
828 .add_table(5, 2)
829 .borders(BorderStyle::Single, 4, "1B2A4A")
830 .width_pct(80.0);
831 tbl.cell(0, 0).unwrap().set_text("Key Metric");
832 tbl.cell(0, 0).unwrap().shading("1B2A4A");
833 tbl.cell(0, 1).unwrap().set_text("Value");
834 tbl.cell(0, 1).unwrap().shading("1B2A4A");
835 let rows = [
836 ("Total Investment", "$2.8M"),
837 ("Annual Savings", "$4.2M"),
838 ("ROI (Year 1)", "150%"),
839 ("Timeline", "18 months"),
840 ];
841 for (i, (k, v)) in rows.iter().enumerate() {
842 tbl.cell(i + 1, 0).unwrap().set_text(k);
843 if i % 2 == 0 {
844 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
845 }
846 tbl.cell(i + 1, 1).unwrap().set_text(v);
847 if i % 2 == 0 {
848 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
849 }
850 }
851 }
852
853 // ── Problem Statement ──
854 doc.add_paragraph("").page_break_before(true);
855 doc.add_paragraph("Problem Statement").style("Heading1");
856 doc.add_paragraph(
857 "Global Financial Corp's current AI infrastructure faces three critical challenges:",
858 );
859 doc.add_numbered_list_item("Deployment Latency — New models take 3-4 weeks to deploy to production, compared to the industry benchmark of 2-3 days.", 0);
860 doc.add_numbered_list_item("Scalability Constraints — The batch processing architecture cannot handle real-time inference demands during peak trading hours.", 0);
861 doc.add_numbered_list_item("Technical Debt — Legacy Python 2.7 codebases and manual deployment scripts create reliability risks and slow iteration speed.", 0);
862
863 // ── Proposed Solution ──
864 doc.add_paragraph("").page_break_before(true);
865 doc.add_paragraph("Proposed Solution").style("Heading1");
866
867 doc.add_paragraph("Phase 1: Foundation (Months 1-6)")
868 .style("Heading2");
869 doc.add_bullet_list_item("Deploy Kubernetes-based ML serving infrastructure", 0);
870 doc.add_bullet_list_item("Implement CI/CD pipeline for model deployment", 0);
871 doc.add_bullet_list_item("Migrate top 5 production models to new platform", 0);
872
873 doc.add_paragraph("Phase 2: Expansion (Months 7-12)")
874 .style("Heading2");
875 doc.add_bullet_list_item(
876 "Real-time feature engineering pipeline (Apache Kafka + Flink)",
877 0,
878 );
879 doc.add_bullet_list_item("A/B testing framework for model variants", 0);
880 doc.add_bullet_list_item("Automated model monitoring and drift detection", 0);
881
882 doc.add_paragraph("Phase 3: Optimization (Months 13-18)")
883 .style("Heading2");
884 doc.add_bullet_list_item("GPU inference optimization (TensorRT/ONNX Runtime)", 0);
885 doc.add_bullet_list_item("Multi-region deployment for latency reduction", 0);
886 doc.add_bullet_list_item(
887 "Self-service model deployment portal for data science teams",
888 0,
889 );
890
891 // ── Budget ──
892 doc.add_paragraph("Budget Breakdown").style("Heading1");
893 {
894 let mut tbl = doc
895 .add_table(6, 3)
896 .borders(BorderStyle::Single, 4, "1B2A4A")
897 .width_pct(100.0);
898 tbl.cell(0, 0).unwrap().set_text("Category");
899 tbl.cell(0, 0).unwrap().shading("1B2A4A");
900 tbl.cell(0, 1).unwrap().set_text("Cost");
901 tbl.cell(0, 1).unwrap().shading("1B2A4A");
902 tbl.cell(0, 2).unwrap().set_text("Notes");
903 tbl.cell(0, 2).unwrap().shading("1B2A4A");
904 let items = [
905 (
906 "Infrastructure",
907 "$450,000",
908 "Cloud compute + GPU instances",
909 ),
910 ("Engineering Services", "$1,600,000", "12 FTE x 18 months"),
911 (
912 "Software Licenses",
913 "$350,000",
914 "Kafka, monitoring, CI/CD tools",
915 ),
916 (
917 "Training & Enablement",
918 "$200,000",
919 "Team training, documentation",
920 ),
921 ("Contingency (10%)", "$200,000", "Risk buffer"),
922 ];
923 for (i, (cat, cost, note)) in items.iter().enumerate() {
924 tbl.cell(i + 1, 0).unwrap().set_text(cat);
925 tbl.cell(i + 1, 1).unwrap().set_text(cost);
926 tbl.cell(i + 1, 2).unwrap().set_text(note);
927 if i % 2 == 0 {
928 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
929 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
930 tbl.cell(i + 1, 2).unwrap().shading("F4F1EB");
931 }
932 }
933 }
934
935 // ── Closing ──
936 doc.add_paragraph("");
937 doc.add_paragraph("Next Steps").style("Heading1");
938 doc.add_numbered_list_item(
939 "Schedule technical deep-dive with GFC engineering team (Week 1)",
940 0,
941 );
942 doc.add_numbered_list_item("Finalize scope and sign SOW (Week 2-3)", 0);
943 doc.add_numbered_list_item("Kick off Phase 1 with joint planning session (Week 4)", 0);
944
945 doc.add_paragraph("");
946 {
947 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
948 p.add_run("Walter White")
949 .bold(true)
950 .size(14.0)
951 .color("1B2A4A");
952 }
953 {
954 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
955 p.add_run("CEO, Tensorbee | walter@tensorbee.com")
956 .size(10.0)
957 .color("888888");
958 }
959
960 doc
961}
962
963// =============================================================================
964// 3. QUOTE / BILL OF MATERIALS — Teal + orange scheme
965// =============================================================================
966fn generate_quote(_samples_dir: &Path) -> Document {
967 let mut doc = Document::new();
968
969 // Colors: Teal #008B8B, Dark #1A3C3C, Orange #E07020, Light #F0F8F8
970 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
971 doc.set_margins(
972 Length::inches(0.75),
973 Length::inches(0.75),
974 Length::inches(0.75),
975 Length::inches(0.75),
976 );
977 doc.set_title("Quotation QT-2026-0147");
978 doc.set_author("Walter White");
979 doc.set_keywords("quote, BOM, Tensorbee");
980
981 doc.set_header("Tensorbee — Quotation");
982 doc.set_footer("QT-2026-0147 | Page");
983
984 // ── Header Block ──
985 {
986 let mut p = doc.add_paragraph("").alignment(Alignment::Left);
987 p.add_run("TENSORBEE")
988 .bold(true)
989 .size(28.0)
990 .color("008B8B")
991 .font("Helvetica");
992 }
993 doc.add_paragraph("123 Innovation Drive, Suite 400")
994 .alignment(Alignment::Left);
995 doc.add_paragraph("San Francisco, CA 94105 | +1 (415) 555-0199")
996 .alignment(Alignment::Left);
997 doc.add_paragraph("")
998 .border_bottom(BorderStyle::Single, 8, "008B8B");
999
1000 // Quote meta
1001 doc.add_paragraph("");
1002 {
1003 let mut tbl = doc.add_table(4, 4).width_pct(100.0);
1004 tbl.cell(0, 0).unwrap().set_text("QUOTATION");
1005 tbl.cell(0, 0).unwrap().shading("008B8B").grid_span(2);
1006 tbl.cell(0, 2).unwrap().set_text("DATE");
1007 tbl.cell(0, 2).unwrap().shading("008B8B");
1008 tbl.cell(0, 3).unwrap().set_text("VALID UNTIL");
1009 tbl.cell(0, 3).unwrap().shading("008B8B");
1010 tbl.cell(1, 0).unwrap().set_text("Quote #:");
1011 tbl.cell(1, 1).unwrap().set_text("QT-2026-0147");
1012 tbl.cell(1, 2).unwrap().set_text("Feb 22, 2026");
1013 tbl.cell(1, 3).unwrap().set_text("Mar 22, 2026");
1014 tbl.cell(2, 0).unwrap().set_text("Prepared by:");
1015 tbl.cell(2, 1).unwrap().set_text("Walter White");
1016 tbl.cell(2, 2).unwrap().set_text("Payment:");
1017 tbl.cell(2, 3).unwrap().set_text("Net 30");
1018 tbl.cell(3, 0).unwrap().set_text("Customer:");
1019 tbl.cell(3, 1).unwrap().set_text("Meridian Dynamics LLC");
1020 tbl.cell(3, 2).unwrap().set_text("Currency:");
1021 tbl.cell(3, 3).unwrap().set_text("USD");
1022 }
1023
1024 doc.add_paragraph("");
1025
1026 // ── Bill of Materials ──
1027 {
1028 let mut p = doc.add_paragraph("");
1029 p.add_run("Bill of Materials")
1030 .bold(true)
1031 .size(16.0)
1032 .color("1A3C3C");
1033 }
1034 doc.add_paragraph("");
1035
1036 {
1037 let mut tbl = doc
1038 .add_table(10, 6)
1039 .borders(BorderStyle::Single, 2, "008B8B")
1040 .width_pct(100.0)
1041 .layout_fixed();
1042 // Headers
1043 let hdrs = [
1044 "#",
1045 "Part Number",
1046 "Description",
1047 "Qty",
1048 "Unit Price",
1049 "Total",
1050 ];
1051 for (c, h) in hdrs.iter().enumerate() {
1052 tbl.cell(0, c).unwrap().set_text(h);
1053 tbl.cell(0, c).unwrap().shading("008B8B");
1054 }
1055
1056 let items: Vec<(&str, &str, &str, &str, &str)> = vec![
1057 (
1058 "TB-GPU-A100",
1059 "NVIDIA A100 80GB GPU",
1060 "4",
1061 "$12,500.00",
1062 "$50,000.00",
1063 ),
1064 (
1065 "TB-SRV-R750",
1066 "Dell R750xa Server Chassis",
1067 "2",
1068 "$8,200.00",
1069 "$16,400.00",
1070 ),
1071 (
1072 "TB-RAM-256G",
1073 "256GB DDR5 ECC Memory Module",
1074 "8",
1075 "$890.00",
1076 "$7,120.00",
1077 ),
1078 (
1079 "TB-SSD-3840",
1080 "3.84TB NVMe U.2 SSD",
1081 "8",
1082 "$1,150.00",
1083 "$9,200.00",
1084 ),
1085 (
1086 "TB-NET-CX7",
1087 "ConnectX-7 200GbE NIC",
1088 "4",
1089 "$1,800.00",
1090 "$7,200.00",
1091 ),
1092 (
1093 "TB-SW-48P",
1094 "48-Port 100GbE Switch",
1095 "1",
1096 "$22,000.00",
1097 "$22,000.00",
1098 ),
1099 (
1100 "TB-CAB-RACK",
1101 "42U Server Rack + PDU",
1102 "1",
1103 "$4,500.00",
1104 "$4,500.00",
1105 ),
1106 (
1107 "TB-SVC-INST",
1108 "Installation & Configuration",
1109 "1",
1110 "$8,500.00",
1111 "$8,500.00",
1112 ),
1113 (
1114 "TB-SVC-SUPP",
1115 "3-Year Premium Support",
1116 "1",
1117 "$15,000.00",
1118 "$15,000.00",
1119 ),
1120 ];
1121
1122 for (i, (pn, desc, qty, unit, total)) in items.iter().enumerate() {
1123 let row = i + 1;
1124 tbl.cell(row, 0).unwrap().set_text(&format!("{}", i + 1));
1125 tbl.cell(row, 1).unwrap().set_text(pn);
1126 tbl.cell(row, 2).unwrap().set_text(desc);
1127 tbl.cell(row, 3).unwrap().set_text(qty);
1128 tbl.cell(row, 4).unwrap().set_text(unit);
1129 tbl.cell(row, 5).unwrap().set_text(total);
1130 if i % 2 == 0 {
1131 for c in 0..6 {
1132 tbl.cell(row, c).unwrap().shading("F0F8F8");
1133 }
1134 }
1135 }
1136 }
1137
1138 doc.add_paragraph("");
1139
1140 // ── Totals ──
1141 {
1142 let mut tbl = doc
1143 .add_table(4, 2)
1144 .borders(BorderStyle::Single, 2, "008B8B")
1145 .width(Length::inches(3.5))
1146 .alignment(Alignment::Right);
1147 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1148 tbl.cell(0, 1).unwrap().set_text("$139,920.00");
1149 tbl.cell(1, 0).unwrap().set_text("Shipping & Handling");
1150 tbl.cell(1, 1).unwrap().set_text("$2,500.00");
1151 tbl.cell(2, 0).unwrap().set_text("Tax (8.625%)");
1152 tbl.cell(2, 1).unwrap().set_text("$12,068.10");
1153 tbl.cell(3, 0).unwrap().set_text("TOTAL");
1154 tbl.cell(3, 0).unwrap().shading("E07020");
1155 tbl.cell(3, 1).unwrap().set_text("$154,488.10");
1156 tbl.cell(3, 1).unwrap().shading("E07020");
1157 }
1158
1159 doc.add_paragraph("");
1160
1161 // ── Terms & Conditions ──
1162 {
1163 let mut p = doc.add_paragraph("");
1164 p.add_run("Terms & Conditions")
1165 .bold(true)
1166 .size(14.0)
1167 .color("1A3C3C");
1168 }
1169 doc.add_numbered_list_item(
1170 "This quotation is valid for 30 calendar days from the date of issue.",
1171 0,
1172 );
1173 doc.add_numbered_list_item(
1174 "All prices are in USD and exclusive of applicable taxes unless stated.",
1175 0,
1176 );
1177 doc.add_numbered_list_item("Standard lead time is 4-6 weeks from PO receipt.", 0);
1178 doc.add_numbered_list_item(
1179 "Payment terms: Net 30 from invoice date. 2% discount for payment within 10 days.",
1180 0,
1181 );
1182 doc.add_numbered_list_item(
1183 "Warranty: 3-year manufacturer warranty on all hardware components.",
1184 0,
1185 );
1186 doc.add_numbered_list_item(
1187 "Returns subject to 15% restocking fee if initiated after 14 days.",
1188 0,
1189 );
1190
1191 doc.add_paragraph("");
1192 doc.add_paragraph("")
1193 .border_bottom(BorderStyle::Single, 4, "008B8B");
1194 doc.add_paragraph("");
1195 {
1196 let mut p = doc.add_paragraph("");
1197 p.add_run("Accepted by: ").bold(true);
1198 p.add_run("___________________________ Date: ___________");
1199 }
1200 {
1201 let mut p = doc.add_paragraph("");
1202 p.add_run("Print Name: ").bold(true);
1203 p.add_run("___________________________ Title: ___________");
1204 }
1205
1206 doc
1207}
1208
1209// =============================================================================
1210// 4. INVOICE — Crimson + charcoal scheme
1211// =============================================================================
1212fn generate_invoice(_samples_dir: &Path) -> Document {
1213 let mut doc = Document::new();
1214
1215 // Colors: Crimson #B22222, Charcoal #333333, Light #FAF0F0
1216 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1217 doc.set_margins(
1218 Length::inches(0.75),
1219 Length::inches(0.75),
1220 Length::inches(0.75),
1221 Length::inches(0.75),
1222 );
1223 doc.set_title("Invoice INV-2026-0392");
1224 doc.set_author("Walter White");
1225 doc.set_keywords("invoice, Tensorbee");
1226
1227 doc.set_footer("Tensorbee — Thank you for your business!");
1228
1229 // ── Company & Invoice Header ──
1230 {
1231 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1232 // Company name left, INVOICE right
1233 {
1234 let mut cell = tbl.cell(0, 0).unwrap();
1235 let mut p = cell.add_paragraph("");
1236 p.add_run("TENSORBEE")
1237 .bold(true)
1238 .size(32.0)
1239 .color("B22222")
1240 .font("Helvetica");
1241 }
1242 {
1243 let mut cell = tbl.cell(0, 1).unwrap();
1244 let mut p = cell.add_paragraph("");
1245 p.add_run("INVOICE").bold(true).size(32.0).color("333333");
1246 }
1247 tbl.cell(1, 0)
1248 .unwrap()
1249 .set_text("123 Innovation Drive, Suite 400");
1250 tbl.cell(1, 1).unwrap().set_text("Invoice #: INV-2026-0392");
1251 tbl.cell(2, 0).unwrap().set_text("San Francisco, CA 94105");
1252 tbl.cell(2, 1).unwrap().set_text("Date: February 22, 2026");
1253 tbl.cell(3, 0).unwrap().set_text("walter@tensorbee.com");
1254 tbl.cell(3, 1).unwrap().set_text("Due Date: March 24, 2026");
1255 }
1256
1257 doc.add_paragraph("")
1258 .border_bottom(BorderStyle::Thick, 8, "B22222");
1259 doc.add_paragraph("");
1260
1261 // ── Bill To / Ship To ──
1262 {
1263 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1264 tbl.cell(0, 0).unwrap().set_text("BILL TO");
1265 tbl.cell(0, 0).unwrap().shading("B22222");
1266 tbl.cell(0, 1).unwrap().set_text("SHIP TO");
1267 tbl.cell(0, 1).unwrap().shading("B22222");
1268 tbl.cell(1, 0).unwrap().set_text("Meridian Dynamics LLC");
1269 tbl.cell(1, 1).unwrap().set_text("Meridian Dynamics LLC");
1270 tbl.cell(2, 0).unwrap().set_text("456 Enterprise Blvd");
1271 tbl.cell(2, 1).unwrap().set_text("Attn: Server Room B");
1272 tbl.cell(3, 0).unwrap().set_text("Austin, TX 78701");
1273 tbl.cell(3, 1)
1274 .unwrap()
1275 .set_text("456 Enterprise Blvd, Austin, TX 78701");
1276 }
1277
1278 doc.add_paragraph("");
1279
1280 // ── Line Items ──
1281 {
1282 let mut tbl = doc
1283 .add_table(8, 5)
1284 .borders(BorderStyle::Single, 2, "B22222")
1285 .width_pct(100.0);
1286 let hdrs = ["Description", "Qty", "Unit Price", "Tax", "Amount"];
1287 for (c, h) in hdrs.iter().enumerate() {
1288 tbl.cell(0, c).unwrap().set_text(h);
1289 tbl.cell(0, c).unwrap().shading("B22222");
1290 }
1291 let items = [
1292 (
1293 "ML Infrastructure Setup — Phase 1",
1294 "1",
1295 "$45,000.00",
1296 "$3,881.25",
1297 "$48,881.25",
1298 ),
1299 (
1300 "Data Pipeline Development (160 hrs)",
1301 "160",
1302 "$225.00",
1303 "$3,105.00",
1304 "$39,105.00",
1305 ),
1306 (
1307 "GPU Cluster Configuration",
1308 "1",
1309 "$12,000.00",
1310 "$1,035.00",
1311 "$13,035.00",
1312 ),
1313 (
1314 "API Gateway Implementation",
1315 "1",
1316 "$18,500.00",
1317 "$1,595.63",
1318 "$20,095.63",
1319 ),
1320 (
1321 "Load Testing & QA (80 hrs)",
1322 "80",
1323 "$195.00",
1324 "$1,345.50",
1325 "$16,945.50",
1326 ),
1327 (
1328 "Documentation & Training",
1329 "1",
1330 "$8,000.00",
1331 "$690.00",
1332 "$8,690.00",
1333 ),
1334 (
1335 "Project Management (3 months)",
1336 "3",
1337 "$6,500.00",
1338 "$1,679.63",
1339 "$21,179.63",
1340 ),
1341 ];
1342 for (i, (desc, qty, unit, tax, amt)) in items.iter().enumerate() {
1343 let r = i + 1;
1344 tbl.cell(r, 0).unwrap().set_text(desc);
1345 tbl.cell(r, 1).unwrap().set_text(qty);
1346 tbl.cell(r, 2).unwrap().set_text(unit);
1347 tbl.cell(r, 3).unwrap().set_text(tax);
1348 tbl.cell(r, 4).unwrap().set_text(amt);
1349 if i % 2 == 0 {
1350 for c in 0..5 {
1351 tbl.cell(r, c).unwrap().shading("FAF0F0");
1352 }
1353 }
1354 }
1355 }
1356
1357 doc.add_paragraph("");
1358
1359 // ── Totals ──
1360 {
1361 let mut tbl = doc
1362 .add_table(4, 2)
1363 .borders(BorderStyle::Single, 2, "B22222")
1364 .width(Length::inches(3.0))
1365 .alignment(Alignment::Right);
1366 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1367 tbl.cell(0, 1).unwrap().set_text("$154,600.00");
1368 tbl.cell(1, 0).unwrap().set_text("Tax (8.625%)");
1369 tbl.cell(1, 1).unwrap().set_text("$13,334.25");
1370 tbl.cell(2, 0).unwrap().set_text("Discount (5%)");
1371 tbl.cell(2, 1).unwrap().set_text("-$7,730.00");
1372 tbl.cell(3, 0).unwrap().set_text("AMOUNT DUE");
1373 tbl.cell(3, 0).unwrap().shading("B22222");
1374 tbl.cell(3, 1).unwrap().set_text("$160,204.25");
1375 tbl.cell(3, 1).unwrap().shading("B22222");
1376 }
1377
1378 doc.add_paragraph("");
1379 doc.add_paragraph("");
1380
1381 // ── Payment Details ──
1382 {
1383 let mut p = doc.add_paragraph("");
1384 p.add_run("Payment Details")
1385 .bold(true)
1386 .size(14.0)
1387 .color("333333");
1388 }
1389 doc.add_paragraph("Bank: Silicon Valley Bank")
1390 .indent_left(Length::inches(0.3));
1391 doc.add_paragraph("Account: Tensorbee Inc. — 0847-2953-1120")
1392 .indent_left(Length::inches(0.3));
1393 doc.add_paragraph("Routing: 121140399")
1394 .indent_left(Length::inches(0.3));
1395 doc.add_paragraph("Swift: SVBKUS6S")
1396 .indent_left(Length::inches(0.3));
1397
1398 doc.add_paragraph("");
1399
1400 doc.add_paragraph("Please include invoice number INV-2026-0392 in the payment reference.")
1401 .shading("FAF0F0")
1402 .border_all(BorderStyle::Single, 2, "B22222");
1403
1404 doc
1405}
1406
1407// =============================================================================
1408// 5. REPORT — Forest green + earth tones, with images & hierarchical sections
1409// =============================================================================
1410fn generate_report(_samples_dir: &Path) -> Document {
1411 let mut doc = Document::new();
1412
1413 // Colors: Forest #2D5016, Sage #6B8E23, Earth #8B7355, Cream #FFFAF0
1414 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1415 doc.set_margins(
1416 Length::inches(1.0),
1417 Length::inches(1.0),
1418 Length::inches(1.0),
1419 Length::inches(1.0),
1420 );
1421 doc.set_title("Q4 2025 Environmental Impact Report");
1422 doc.set_author("Walter White");
1423 doc.set_subject("Quarterly Environmental Report");
1424 doc.set_keywords("environment, sustainability, report, Tensorbee");
1425
1426 doc.set_different_first_page(true);
1427 doc.set_first_page_header("");
1428 doc.set_header("Tensorbee — Q4 2025 Environmental Impact Report");
1429 doc.set_footer("CONFIDENTIAL — Page");
1430
1431 // ── Cover ──
1432 let cover_bg = create_sample_png(612, 792, [20, 50, 15]);
1433 doc.add_background_image(&cover_bg, "report_cover.png");
1434
1435 for _ in 0..5 {
1436 doc.add_paragraph("");
1437 }
1438 {
1439 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1440 p.add_run("Q4 2025")
1441 .bold(true)
1442 .size(48.0)
1443 .color("FFFFFF")
1444 .font("Georgia");
1445 }
1446 {
1447 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1448 p.add_run("Environmental Impact Report")
1449 .size(24.0)
1450 .color("90EE90")
1451 .font("Georgia")
1452 .italic(true);
1453 }
1454 doc.add_paragraph("");
1455 {
1456 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1457 p.add_run("Tensorbee — Sustainability Division")
1458 .size(14.0)
1459 .color("C0C0C0");
1460 }
1461 {
1462 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1463 p.add_run("Prepared by Walter White, Chief Sustainability Officer")
1464 .size(11.0)
1465 .color("AAAAAA");
1466 }
1467
1468 // ── TOC ──
1469 doc.add_paragraph("").page_break_before(true);
1470 doc.insert_toc(doc.content_count(), 3);
1471
1472 // ── Section 1: Executive Overview ──
1473 doc.add_paragraph("").page_break_before(true);
1474 doc.add_paragraph("Executive Overview").style("Heading1");
1475 doc.add_paragraph(
1476 "This report presents Tensorbee's environmental performance for Q4 2025. Our \
1477 sustainability initiatives have yielded a 34% reduction in carbon emissions compared \
1478 to Q4 2024, exceeding our target of 25%. Key achievements include the transition to \
1479 100% renewable energy in our primary data centers and the launch of our carbon offset \
1480 marketplace.",
1481 )
1482 .first_line_indent(Length::inches(0.3));
1483
1484 // Image: performance chart
1485 let chart_img = create_chart_png(400, 200);
1486 doc.add_paragraph("");
1487 doc.add_picture(
1488 &chart_img,
1489 "performance_chart.png",
1490 Length::inches(5.0),
1491 Length::inches(2.5),
1492 )
1493 .alignment(Alignment::Center);
1494 {
1495 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1496 p.add_run("Figure 1: Quarterly Carbon Emissions (tonnes CO2e)")
1497 .italic(true)
1498 .size(9.0)
1499 .color("666666");
1500 }
1501
1502 // ── Section 2: Energy Consumption ──
1503 doc.add_paragraph("").page_break_before(true);
1504 doc.add_paragraph("Energy Consumption").style("Heading1");
1505
1506 doc.add_paragraph("Data Center Operations")
1507 .style("Heading2");
1508 doc.add_paragraph(
1509 "Our three primary data centers consumed a combined 4.2 GWh in Q4 2025, \
1510 a 12% reduction from Q3 2025 driven by improved cooling efficiency and \
1511 server consolidation.",
1512 );
1513
1514 // Data table
1515 {
1516 let mut tbl = doc
1517 .add_table(5, 4)
1518 .borders(BorderStyle::Single, 4, "2D5016")
1519 .width_pct(100.0);
1520 let hdrs = ["Data Center", "Capacity (MW)", "Usage (GWh)", "PUE"];
1521 for (c, h) in hdrs.iter().enumerate() {
1522 tbl.cell(0, c).unwrap().set_text(h);
1523 tbl.cell(0, c).unwrap().shading("2D5016");
1524 }
1525 let rows = [
1526 ("San Francisco (Primary)", "3.2", "1.8", "1.12"),
1527 ("Dublin (EU)", "2.1", "1.2", "1.18"),
1528 ("Singapore (APAC)", "1.8", "1.2", "1.24"),
1529 ("TOTAL", "7.1", "4.2", "1.17 avg"),
1530 ];
1531 for (i, (dc, cap, use_, pue)) in rows.iter().enumerate() {
1532 tbl.cell(i + 1, 0).unwrap().set_text(dc);
1533 tbl.cell(i + 1, 1).unwrap().set_text(cap);
1534 tbl.cell(i + 1, 2).unwrap().set_text(use_);
1535 tbl.cell(i + 1, 3).unwrap().set_text(pue);
1536 if i == 3 {
1537 for c in 0..4 {
1538 tbl.cell(i + 1, c).unwrap().shading("E2EFDA");
1539 }
1540 }
1541 }
1542 }
1543
1544 doc.add_paragraph("");
1545
1546 doc.add_paragraph("Renewable Energy Mix").style("Heading2");
1547 doc.add_paragraph("Breakdown of energy sources across all facilities:");
1548
1549 doc.add_bullet_list_item("Solar PV: 42% (1.76 GWh)", 0);
1550 doc.add_bullet_list_item("Wind Power Purchase Agreements: 38% (1.60 GWh)", 0);
1551 doc.add_bullet_list_item("Hydroelectric: 12% (0.50 GWh)", 0);
1552 doc.add_bullet_list_item("Grid (non-renewable): 8% (0.34 GWh)", 0);
1553
1554 // ── Section 3: Water & Waste ──
1555 doc.add_paragraph("Water & Waste Management")
1556 .style("Heading1");
1557
1558 doc.add_paragraph("Water Usage").style("Heading2");
1559 doc.add_paragraph(
1560 "Total water consumption was 12.4 million gallons, a 15% reduction from Q3. \
1561 Our closed-loop cooling systems now recycle 78% of water used in cooling operations.",
1562 );
1563
1564 doc.add_paragraph("Waste Reduction").style("Heading2");
1565 doc.add_paragraph("E-waste management results for Q4:")
1566 .keep_with_next(true);
1567 {
1568 let mut tbl = doc
1569 .add_table(4, 3)
1570 .borders(BorderStyle::Single, 2, "6B8E23")
1571 .width_pct(80.0);
1572 tbl.cell(0, 0).unwrap().set_text("Category");
1573 tbl.cell(0, 0).unwrap().shading("6B8E23");
1574 tbl.cell(0, 1).unwrap().set_text("Weight (kg)");
1575 tbl.cell(0, 1).unwrap().shading("6B8E23");
1576 tbl.cell(0, 2).unwrap().set_text("Recycled %");
1577 tbl.cell(0, 2).unwrap().shading("6B8E23");
1578 let rows = [
1579 ("Server Hardware", "2,450", "94%"),
1580 ("Networking Equipment", "820", "91%"),
1581 ("Storage Media", "340", "99%"),
1582 ];
1583 for (i, (cat, wt, pct)) in rows.iter().enumerate() {
1584 tbl.cell(i + 1, 0).unwrap().set_text(cat);
1585 tbl.cell(i + 1, 1).unwrap().set_text(wt);
1586 tbl.cell(i + 1, 2).unwrap().set_text(pct);
1587 }
1588 }
1589
1590 // ── Section 4: Initiatives ──
1591 doc.add_paragraph("").page_break_before(true);
1592 doc.add_paragraph("Q1 2026 Initiatives").style("Heading1");
1593
1594 doc.add_paragraph("Planned Programs").style("Heading2");
1595 doc.add_numbered_list_item(
1596 "Deploy on-site battery storage at SF data center (2 MWh capacity)",
1597 0,
1598 );
1599 doc.add_numbered_list_item("Pilot immersion cooling in Dublin facility", 0);
1600 doc.add_numbered_list_item("Launch employee commute carbon offset program", 0);
1601 doc.add_numbered_list_item("Achieve ISO 14001 certification for Singapore facility", 0);
1602
1603 doc.add_paragraph("Investment Targets").style("Heading2");
1604 doc.add_paragraph("Sustainability CapEx allocation for FY2026:")
1605 .keep_with_next(true);
1606 {
1607 let mut tbl = doc
1608 .add_table(5, 2)
1609 .borders(BorderStyle::Single, 4, "2D5016");
1610 tbl.cell(0, 0).unwrap().set_text("Initiative");
1611 tbl.cell(0, 0).unwrap().shading("2D5016");
1612 tbl.cell(0, 1).unwrap().set_text("Budget");
1613 tbl.cell(0, 1).unwrap().shading("2D5016");
1614 let rows = [
1615 ("Battery Storage", "$1.2M"),
1616 ("Immersion Cooling Pilot", "$800K"),
1617 ("Solar Panel Expansion", "$2.1M"),
1618 ("Carbon Credits", "$500K"),
1619 ];
1620 for (i, (init, budget)) in rows.iter().enumerate() {
1621 tbl.cell(i + 1, 0).unwrap().set_text(init);
1622 tbl.cell(i + 1, 1).unwrap().set_text(budget);
1623 if i % 2 == 0 {
1624 tbl.cell(i + 1, 0).unwrap().shading("FFFAF0");
1625 tbl.cell(i + 1, 1).unwrap().shading("FFFAF0");
1626 }
1627 }
1628 }
1629
1630 // Image: sustainability roadmap
1631 let roadmap_img = create_sample_png(500, 100, [40, 80, 30]);
1632 doc.add_paragraph("");
1633 doc.add_picture(
1634 &roadmap_img,
1635 "roadmap.png",
1636 Length::inches(6.0),
1637 Length::inches(1.2),
1638 )
1639 .alignment(Alignment::Center);
1640 {
1641 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1642 p.add_run("Figure 2: 2026 Sustainability Roadmap")
1643 .italic(true)
1644 .size(9.0)
1645 .color("666666");
1646 }
1647
1648 doc.add_paragraph("");
1649 doc.add_paragraph(
1650 "For questions about this report, contact Walter White at sustainability@tensorbee.com.",
1651 )
1652 .shading("FFFAF0")
1653 .border_all(BorderStyle::Single, 2, "2D5016");
1654
1655 doc
1656}
1657
1658// =============================================================================
1659// 6. LETTER — Slate blue + silver scheme
1660// =============================================================================
1661fn generate_letter(_samples_dir: &Path) -> Document {
1662 let mut doc = Document::new();
1663
1664 // Colors: Slate #4A5568, Blue accent #3182CE
1665 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1666 doc.set_margins(
1667 Length::inches(1.25),
1668 Length::inches(1.25),
1669 Length::inches(1.25),
1670 Length::inches(1.25),
1671 );
1672 doc.set_title("Business Letter");
1673 doc.set_author("Walter White");
1674
1675 // Banner header using raw XML
1676 let logo_img = create_logo_png(220, 48);
1677 let banner = build_header_banner_xml(
1678 "rId1",
1679 &BannerOpts {
1680 bg_color: "4A5568",
1681 banner_width: 7772400,
1682 banner_height: 731520, // ~0.8"
1683 logo_width: 2011680,
1684 logo_height: 438912,
1685 logo_x_offset: 295125,
1686 logo_y_offset: 146304,
1687 },
1688 );
1689 doc.set_raw_header_with_images(
1690 banner,
1691 &[("rId1", &logo_img, "logo.png")],
1692 rdocx_oxml::header_footer::HdrFtrType::Default,
1693 );
1694 doc.set_margins(
1695 Length::twips(2000),
1696 Length::twips(1800),
1697 Length::twips(1440),
1698 Length::twips(1800),
1699 );
1700 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
1701
1702 // Footer with contact
1703 doc.set_footer(
1704 "Tensorbee | 123 Innovation Drive, Suite 400 | San Francisco, CA 94105 | tensorbee.com",
1705 );
1706
1707 // ── Sender Address ──
1708 doc.add_paragraph("Walter White");
1709 doc.add_paragraph("Chief Executive Officer");
1710 doc.add_paragraph("Tensorbee");
1711 doc.add_paragraph("123 Innovation Drive, Suite 400");
1712 doc.add_paragraph("San Francisco, CA 94105");
1713 doc.add_paragraph("");
1714 doc.add_paragraph("February 22, 2026");
1715
1716 doc.add_paragraph("");
1717
1718 // ── Recipient ──
1719 doc.add_paragraph("Ms. Sarah Chen");
1720 doc.add_paragraph("VP of Engineering");
1721 doc.add_paragraph("Meridian Dynamics LLC");
1722 doc.add_paragraph("456 Enterprise Boulevard");
1723 doc.add_paragraph("Austin, TX 78701");
1724
1725 doc.add_paragraph("");
1726
1727 // ── Subject ──
1728 {
1729 let mut p = doc.add_paragraph("");
1730 p.add_run("Re: Strategic Technology Partnership — Phase 2 Expansion")
1731 .bold(true);
1732 }
1733
1734 doc.add_paragraph("");
1735
1736 // ── Body ──
1737 doc.add_paragraph("Dear Ms. Chen,");
1738 doc.add_paragraph("");
1739
1740 doc.add_paragraph(
1741 "I am writing to express Tensorbee's enthusiasm for expanding our strategic technology \
1742 partnership with Meridian Dynamics. Following the successful completion of Phase 1, \
1743 which delivered a 40% improvement in your ML inference pipeline throughput, we believe \
1744 the foundation is firmly established for an ambitious Phase 2 engagement.",
1745 )
1746 .first_line_indent(Length::inches(0.5));
1747
1748 doc.add_paragraph(
1749 "During our recent executive review, your team highlighted three priority areas for \
1750 the next phase of collaboration:",
1751 )
1752 .first_line_indent(Length::inches(0.5));
1753
1754 doc.add_numbered_list_item(
1755 "Real-time anomaly detection for your financial transaction monitoring system, \
1756 targeting sub-100ms latency at 50,000 transactions per second.",
1757 0,
1758 );
1759 doc.add_numbered_list_item(
1760 "Federated learning infrastructure to enable model training across your distributed \
1761 data centers without centralizing sensitive financial data.",
1762 0,
1763 );
1764 doc.add_numbered_list_item(
1765 "MLOps automation to reduce your model deployment cycle from the current 5 days \
1766 to under 4 hours.",
1767 0,
1768 );
1769
1770 doc.add_paragraph(
1771 "Our engineering team has prepared a detailed technical proposal addressing each of \
1772 these areas. We have allocated a dedicated team of eight senior engineers, led by \
1773 our CTO, to ensure continuity with the Phase 1 team your organization has already \
1774 built a productive working relationship with.",
1775 )
1776 .first_line_indent(Length::inches(0.5));
1777
1778 doc.add_paragraph(
1779 "I would welcome the opportunity to present our Phase 2 proposal in person. My \
1780 assistant will reach out to coordinate a meeting at your convenience during the \
1781 first week of March.",
1782 )
1783 .first_line_indent(Length::inches(0.5));
1784
1785 doc.add_paragraph("");
1786
1787 doc.add_paragraph("Warm regards,");
1788 doc.add_paragraph("");
1789 doc.add_paragraph("");
1790
1791 // Signature line
1792 doc.add_paragraph("")
1793 .border_bottom(BorderStyle::Single, 4, "4A5568");
1794 {
1795 let mut p = doc.add_paragraph("");
1796 p.add_run("Walter White").bold(true).size(12.0);
1797 }
1798 {
1799 let mut p = doc.add_paragraph("");
1800 p.add_run("Chief Executive Officer, Tensorbee")
1801 .italic(true)
1802 .size(10.0)
1803 .color("666666");
1804 }
1805 {
1806 let mut p = doc.add_paragraph("");
1807 p.add_run("walter@tensorbee.com | +1 (415) 555-0199")
1808 .size(10.0)
1809 .color("888888");
1810 }
1811
1812 doc
1813}
1814
1815// =============================================================================
1816// 7. EMPLOYMENT CONTRACT — Purple + charcoal scheme
1817// =============================================================================
1818fn generate_contract(_samples_dir: &Path) -> Document {
1819 let mut doc = Document::new();
1820
1821 // Colors: Purple #5B2C6F, Plum #8E44AD, Gray #2C3E50
1822 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1823 doc.set_margins(
1824 Length::inches(1.25),
1825 Length::inches(1.0),
1826 Length::inches(1.0),
1827 Length::inches(1.0),
1828 );
1829 doc.set_title("Employment Agreement");
1830 doc.set_author("Tensorbee HR Department");
1831 doc.set_subject("Employment Contract — Walter White");
1832 doc.set_keywords("employment, contract, agreement, Tensorbee");
1833
1834 doc.set_header("TENSORBEE — Employment Agreement");
1835 doc.set_footer("Employment Agreement — Walter White — Page");
1836
1837 // ── Title Block ──
1838 doc.add_paragraph("")
1839 .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1840 {
1841 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1842 p.add_run("EMPLOYMENT AGREEMENT")
1843 .bold(true)
1844 .size(24.0)
1845 .color("5B2C6F")
1846 .font("Georgia");
1847 }
1848 doc.add_paragraph("")
1849 .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1850 doc.add_paragraph("");
1851
1852 // ── Parties ──
1853 doc.add_paragraph(
1854 "This Employment Agreement (\"Agreement\") is entered into as of February 22, 2026 \
1855 (\"Effective Date\"), by and between:",
1856 );
1857
1858 doc.add_paragraph("");
1859
1860 {
1861 let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1862 p.add_run("EMPLOYER: ").bold(true);
1863 p.add_run(
1864 "Tensorbee, Inc., a Delaware corporation with its principal place of business at \
1865 123 Innovation Drive, Suite 400, San Francisco, CA 94105 (\"Company\")",
1866 );
1867 }
1868 doc.add_paragraph("");
1869 {
1870 let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1871 p.add_run("EMPLOYEE: ").bold(true);
1872 p.add_run(
1873 "Walter White, residing at 308 Negra Arroyo Lane, Albuquerque, NM 87104 (\"Employee\")",
1874 );
1875 }
1876
1877 doc.add_paragraph("");
1878 doc.add_paragraph("The Company and Employee are collectively referred to as the \"Parties.\"");
1879 doc.add_paragraph("");
1880
1881 // ── Article 1: Position and Duties ──
1882 doc.add_paragraph("Article 1 — Position and Duties")
1883 .style("Heading1");
1884 {
1885 let mut p = doc.add_paragraph("");
1886 p.add_run("1.1 ").bold(true);
1887 p.add_run("The Company hereby employs the Employee as ");
1888 p.add_run("Chief Executive Officer (CEO)")
1889 .bold(true)
1890 .italic(true);
1891 p.add_run(", reporting directly to the Board of Directors.");
1892 }
1893 {
1894 let mut p = doc.add_paragraph("");
1895 p.add_run("1.2 ").bold(true);
1896 p.add_run("The Employee shall devote full working time, attention, and best efforts to \
1897 the performance of duties as reasonably assigned by the Board, including but not limited to:");
1898 }
1899 doc.add_bullet_list_item(
1900 "Setting and executing the Company's strategic vision and business plan",
1901 0,
1902 );
1903 doc.add_bullet_list_item(
1904 "Overseeing all operations, engineering, and commercial activities",
1905 0,
1906 );
1907 doc.add_bullet_list_item(
1908 "Representing the Company to investors, customers, and the public",
1909 0,
1910 );
1911 doc.add_bullet_list_item("Recruiting, developing, and retaining key talent", 0);
1912
1913 {
1914 let mut p = doc.add_paragraph("");
1915 p.add_run("1.3 ").bold(true);
1916 p.add_run(
1917 "The Employee's primary work location shall be the Company's San Francisco \
1918 headquarters, with reasonable travel as required by business needs.",
1919 );
1920 }
1921
1922 // ── Article 2: Compensation ──
1923 doc.add_paragraph("Article 2 — Compensation and Benefits")
1924 .style("Heading1");
1925 {
1926 let mut p = doc.add_paragraph("");
1927 p.add_run("2.1 Base Salary. ").bold(true);
1928 p.add_run("The Company shall pay the Employee an annual base salary of ");
1929 p.add_run("$375,000.00").bold(true);
1930 p.add_run(" (Three Hundred Seventy-Five Thousand Dollars), payable in accordance with the \
1931 Company's standard payroll schedule, less applicable withholdings and deductions.");
1932 }
1933 {
1934 let mut p = doc.add_paragraph("");
1935 p.add_run("2.2 Annual Bonus. ").bold(true);
1936 p.add_run("The Employee shall be eligible for an annual performance bonus of up to ");
1937 p.add_run("40%").bold(true);
1938 p.add_run(
1939 " of base salary, based on achievement of mutually agreed performance objectives.",
1940 );
1941 }
1942 {
1943 let mut p = doc.add_paragraph("");
1944 p.add_run("2.3 Equity. ").bold(true);
1945 p.add_run("Subject to Board approval, the Employee shall receive a stock option grant of ");
1946 p.add_run("500,000 shares").bold(true);
1947 p.add_run(
1948 " of the Company's common stock, vesting over four (4) years with a one-year cliff, \
1949 at an exercise price equal to the fair market value on the date of grant.",
1950 );
1951 }
1952
1953 // Compensation summary table
1954 doc.add_paragraph("");
1955 {
1956 let mut tbl = doc
1957 .add_table(5, 2)
1958 .borders(BorderStyle::Single, 4, "5B2C6F")
1959 .width_pct(70.0)
1960 .alignment(Alignment::Center);
1961 tbl.cell(0, 0).unwrap().set_text("Compensation Element");
1962 tbl.cell(0, 0).unwrap().shading("5B2C6F");
1963 tbl.cell(0, 1).unwrap().set_text("Value");
1964 tbl.cell(0, 1).unwrap().shading("5B2C6F");
1965 let items = [
1966 ("Base Salary", "$375,000/year"),
1967 ("Target Bonus", "Up to 40% ($150,000)"),
1968 ("Equity Grant", "500,000 shares (4yr vest)"),
1969 ("Total Target Comp", "$525,000 + equity"),
1970 ];
1971 for (i, (elem, val)) in items.iter().enumerate() {
1972 tbl.cell(i + 1, 0).unwrap().set_text(elem);
1973 tbl.cell(i + 1, 1).unwrap().set_text(val);
1974 if i == 3 {
1975 tbl.cell(i + 1, 0).unwrap().shading("E8DAEF");
1976 tbl.cell(i + 1, 1).unwrap().shading("E8DAEF");
1977 }
1978 }
1979 }
1980
1981 // ── Article 3: Benefits ──
1982 doc.add_paragraph("").page_break_before(true);
1983 doc.add_paragraph("Article 3 — Benefits").style("Heading1");
1984 {
1985 let mut p = doc.add_paragraph("");
1986 p.add_run("3.1 ").bold(true);
1987 p.add_run(
1988 "The Employee shall be entitled to participate in all benefit programs \
1989 generally available to senior executives, including:",
1990 );
1991 }
1992 doc.add_bullet_list_item(
1993 "Medical, dental, and vision insurance (100% premium coverage for employee and dependents)",
1994 0,
1995 );
1996 doc.add_bullet_list_item("401(k) retirement plan with 6% company match", 0);
1997 doc.add_bullet_list_item("Life insurance and long-term disability coverage", 0);
1998 doc.add_bullet_list_item("Annual professional development allowance of $10,000", 0);
1999
2000 {
2001 let mut p = doc.add_paragraph("");
2002 p.add_run("3.2 Paid Time Off. ").bold(true);
2003 p.add_run(
2004 "The Employee shall receive 25 days of paid vacation per year, plus Company holidays, \
2005 accruing on a monthly basis.",
2006 );
2007 }
2008
2009 // ── Article 4: Term and Termination ──
2010 doc.add_paragraph("Article 4 — Term and Termination")
2011 .style("Heading1");
2012 {
2013 let mut p = doc.add_paragraph("");
2014 p.add_run("4.1 At-Will Employment. ").bold(true);
2015 p.add_run(
2016 "This Agreement is for at-will employment and may be terminated by either Party \
2017 at any time, with or without cause, subject to the notice provisions herein.",
2018 );
2019 }
2020 {
2021 let mut p = doc.add_paragraph("");
2022 p.add_run("4.2 Notice Period. ").bold(true);
2023 p.add_run("Either Party shall provide ");
2024 p.add_run("ninety (90) days").bold(true);
2025 p.add_run(" written notice of termination, or pay in lieu thereof.");
2026 }
2027 {
2028 let mut p = doc.add_paragraph("");
2029 p.add_run("4.3 Severance. ").bold(true);
2030 p.add_run("In the event of termination by the Company without Cause, the Employee shall \
2031 receive (a) twelve (12) months of base salary continuation, (b) pro-rata bonus \
2032 for the year of termination, and (c) twelve (12) months of COBRA premium coverage.");
2033 }
2034
2035 // ── Article 5: Confidentiality ──
2036 doc.add_paragraph("Article 5 — Confidentiality and IP")
2037 .style("Heading1");
2038 {
2039 let mut p = doc.add_paragraph("");
2040 p.add_run("5.1 ").bold(true);
2041 p.add_run(
2042 "The Employee agrees to maintain strict confidentiality of all proprietary \
2043 information, trade secrets, and business plans of the Company during and after \
2044 employment.",
2045 );
2046 }
2047 {
2048 let mut p = doc.add_paragraph("");
2049 p.add_run("5.2 ").bold(true);
2050 p.add_run(
2051 "All intellectual property, inventions, and works of authorship created by the \
2052 Employee during the term of employment and within the scope of duties shall be \
2053 the sole and exclusive property of the Company.",
2054 );
2055 }
2056
2057 // ── Article 6: Non-Compete ──
2058 doc.add_paragraph("Article 6 — Non-Competition")
2059 .style("Heading1");
2060 {
2061 let mut p = doc.add_paragraph("");
2062 p.add_run("6.1 ").bold(true);
2063 p.add_run("For a period of twelve (12) months following termination, the Employee agrees \
2064 not to engage in any business that directly competes with the Company's core \
2065 business of AI infrastructure and ML operations services, within the United States.");
2066 }
2067
2068 // ── Article 7: General Provisions ──
2069 doc.add_paragraph("Article 7 — General Provisions")
2070 .style("Heading1");
2071 {
2072 let mut p = doc.add_paragraph("");
2073 p.add_run("7.1 Governing Law. ").bold(true);
2074 p.add_run(
2075 "This Agreement shall be governed by and construed in accordance with the laws of \
2076 the State of California.",
2077 );
2078 }
2079 {
2080 let mut p = doc.add_paragraph("");
2081 p.add_run("7.2 Entire Agreement. ").bold(true);
2082 p.add_run(
2083 "This Agreement constitutes the entire agreement between the Parties and supersedes \
2084 all prior negotiations, representations, and agreements.",
2085 );
2086 }
2087 {
2088 let mut p = doc.add_paragraph("");
2089 p.add_run("7.3 Amendment. ").bold(true);
2090 p.add_run(
2091 "This Agreement may only be amended by a written instrument signed by both Parties.",
2092 );
2093 }
2094
2095 // ── Signature Block ──
2096 doc.add_paragraph("").page_break_before(true);
2097 doc.add_paragraph("IN WITNESS WHEREOF, the Parties have executed this Employment Agreement as of the Effective Date.")
2098 .space_after(Length::pt(24.0));
2099
2100 // Signature table
2101 {
2102 let mut tbl = doc.add_table(6, 2).width_pct(100.0);
2103 tbl.cell(0, 0).unwrap().set_text("FOR THE COMPANY:");
2104 tbl.cell(0, 0).unwrap().shading("5B2C6F");
2105 tbl.cell(0, 1).unwrap().set_text("EMPLOYEE:");
2106 tbl.cell(0, 1).unwrap().shading("5B2C6F");
2107
2108 tbl.cell(1, 0).unwrap().set_text("");
2109 tbl.cell(1, 1).unwrap().set_text("");
2110 tbl.row(1).unwrap().height(Length::pt(40.0));
2111
2112 tbl.cell(2, 0)
2113 .unwrap()
2114 .set_text("Signature: ___________________________");
2115 tbl.cell(2, 1)
2116 .unwrap()
2117 .set_text("Signature: ___________________________");
2118 tbl.cell(3, 0)
2119 .unwrap()
2120 .set_text("Name: Board Representative");
2121 tbl.cell(3, 1).unwrap().set_text("Name: Walter White");
2122 tbl.cell(4, 0)
2123 .unwrap()
2124 .set_text("Title: Chair, Board of Directors");
2125 tbl.cell(4, 1)
2126 .unwrap()
2127 .set_text("Title: Chief Executive Officer");
2128 tbl.cell(5, 0).unwrap().set_text("Date: _______________");
2129 tbl.cell(5, 1).unwrap().set_text("Date: _______________");
2130 }
2131
2132 doc
2133}Sourcepub fn append(&mut self, other: &Document)
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?
97fn generate_feature_showcase(_samples_dir: &Path) -> Document {
98 let mut doc = Document::new();
99
100 // ── Page Setup & Metadata ──
101 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
102 doc.set_margins(
103 Length::inches(1.0),
104 Length::inches(1.0),
105 Length::inches(1.0),
106 Length::inches(1.0),
107 );
108 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
109 doc.set_gutter(Length::twips(0));
110 doc.set_title("rdocx Feature Showcase");
111 doc.set_author("rdocx Sample Generator");
112 doc.set_subject("Comprehensive feature demonstration");
113 doc.set_keywords("rdocx, docx, rust, sample, showcase");
114
115 // Headers & Footers with different first page
116 doc.set_different_first_page(true);
117 doc.set_first_page_header("rdocx");
118 doc.set_first_page_footer("Feature Showcase — Cover Page");
119 doc.set_header("rdocx Feature Showcase");
120 doc.set_footer("Generated by rdocx");
121
122 // ── COVER PAGE ──
123 let bg = create_sample_png(612, 792, [20, 45, 90]);
124 doc.add_background_image(&bg, "cover_bg.png");
125
126 for _ in 0..3 {
127 doc.add_paragraph("");
128 }
129 {
130 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
131 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
132 }
133 {
134 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
135 p.add_run("Complete Feature Showcase")
136 .size(28.0)
137 .color("FFFFFF")
138 .italic(true);
139 }
140 doc.add_paragraph("");
141 {
142 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
143 p.add_run("Every feature of the rdocx Rust library")
144 .size(13.0)
145 .color("B0C4DE");
146 }
147 {
148 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
149 p.add_run("demonstrated in a single document.")
150 .size(13.0)
151 .color("B0C4DE");
152 }
153
154 // ── TABLE OF CONTENTS ──
155 doc.add_paragraph("").page_break_before(true);
156 doc.insert_toc(doc.content_count(), 3);
157
158 // ── SECTION 1: TEXT FORMATTING ──
159 doc.add_paragraph("").page_break_before(true);
160 doc.add_paragraph("1. Text Formatting").style("Heading1");
161
162 // Paragraph Alignment
163 doc.add_paragraph("1.1 Paragraph Alignment")
164 .style("Heading2");
165 doc.add_paragraph("Left-aligned paragraph (default).")
166 .alignment(Alignment::Left);
167 doc.add_paragraph("Center-aligned paragraph.")
168 .alignment(Alignment::Center);
169 doc.add_paragraph("Right-aligned paragraph.")
170 .alignment(Alignment::Right);
171 doc.add_paragraph(
172 "Justified paragraph — this text is long enough to demonstrate how justified alignment \
173 distributes extra space across word gaps so lines fill the full width of the text area.",
174 )
175 .alignment(Alignment::Justify);
176
177 doc.add_paragraph("");
178
179 // Run Formatting
180 doc.add_paragraph("1.2 Run Formatting").style("Heading2");
181 {
182 let mut p = doc.add_paragraph("");
183 p.add_run("Bold").bold(true);
184 p.add_run(" | ");
185 p.add_run("Italic").italic(true);
186 p.add_run(" | ");
187 p.add_run("Bold Italic").bold(true).italic(true);
188 p.add_run(" | ");
189 p.add_run("Underline").underline(true);
190 p.add_run(" | ");
191 p.add_run("Strikethrough").strike(true);
192 p.add_run(" | ");
193 p.add_run("Double Strike").double_strike(true);
194 }
195 {
196 let mut p = doc.add_paragraph("");
197 p.add_run("Red").color("FF0000");
198 p.add_run(" | ");
199 p.add_run("Blue").color("0000FF");
200 p.add_run(" | ");
201 p.add_run("Green").color("00AA00");
202 p.add_run(" | ");
203 p.add_run("Highlighted").highlight("FFFF00");
204 }
205 {
206 let mut p = doc.add_paragraph("");
207 p.add_run("8pt").size(8.0);
208 p.add_run(" | ");
209 p.add_run("11pt").size(11.0);
210 p.add_run(" | ");
211 p.add_run("16pt").size(16.0);
212 p.add_run(" | ");
213 p.add_run("24pt").size(24.0);
214 }
215 {
216 let mut p = doc.add_paragraph("");
217 p.add_run("Arial").font("Arial");
218 p.add_run(" | ");
219 p.add_run("Times New Roman").font("Times New Roman");
220 p.add_run(" | ");
221 p.add_run("Courier New").font("Courier New");
222 }
223 {
224 let mut p = doc.add_paragraph("");
225 p.add_run("H");
226 p.add_run("2").subscript();
227 p.add_run("O (subscript) | E=mc");
228 p.add_run("2").superscript();
229 p.add_run(" (superscript)");
230 }
231 {
232 let mut p = doc.add_paragraph("");
233 p.add_run("ALL CAPS").all_caps(true);
234 p.add_run(" | ");
235 p.add_run("Small Caps").small_caps(true);
236 p.add_run(" | ");
237 p.add_run("Expanded").character_spacing(Length::twips(40));
238 p.add_run(" | ");
239 p.add_run("Hidden text (not visible)").hidden(true);
240 }
241
242 doc.add_paragraph("");
243
244 // Underline Styles
245 doc.add_paragraph("1.3 Underline Styles").style("Heading2");
246 {
247 let mut p = doc.add_paragraph("");
248 p.add_run("Single ")
249 .underline_style(rdocx::UnderlineStyle::Single);
250 p.add_run("Double ")
251 .underline_style(rdocx::UnderlineStyle::Double);
252 p.add_run("Thick ")
253 .underline_style(rdocx::UnderlineStyle::Thick);
254 p.add_run("Dotted ")
255 .underline_style(rdocx::UnderlineStyle::Dotted);
256 p.add_run("Dash ")
257 .underline_style(rdocx::UnderlineStyle::Dash);
258 p.add_run("Wave ")
259 .underline_style(rdocx::UnderlineStyle::Wave);
260 }
261
262 // ── SECTION 2: PARAGRAPH FORMATTING ──
263 doc.add_paragraph("").page_break_before(true);
264 doc.add_paragraph("2. Paragraph Formatting")
265 .style("Heading1");
266
267 doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
268 doc.add_paragraph("Paragraph with light green shading")
269 .shading("E2EFDA");
270 doc.add_paragraph("Paragraph with bottom border")
271 .border_bottom(BorderStyle::Single, 6, "2E75B6");
272 doc.add_paragraph("Paragraph with all borders (red)")
273 .border_all(BorderStyle::Single, 4, "FF0000");
274
275 doc.add_paragraph("2.2 Indentation").style("Heading2");
276 doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
277 .indent_left(Length::inches(1.0))
278 .hanging_indent(Length::inches(0.5));
279 doc.add_paragraph("0.5-inch first-line indent on this paragraph")
280 .first_line_indent(Length::inches(0.5));
281 doc.add_paragraph("Both left and right indent (0.75 inches each)")
282 .indent_left(Length::inches(0.75))
283 .indent_right(Length::inches(0.75));
284
285 doc.add_paragraph("2.3 Spacing & Line Height")
286 .style("Heading2");
287 doc.add_paragraph("Extra space before (24pt) and after (12pt)")
288 .space_before(Length::pt(24.0))
289 .space_after(Length::pt(12.0));
290 doc.add_paragraph(
291 "Double line spacing paragraph. This text should have extra vertical space between \
292 lines to demonstrate line_spacing_multiple(2.0).",
293 )
294 .line_spacing_multiple(2.0);
295 doc.add_paragraph("Exact 20pt line spacing (fixed height).")
296 .line_spacing(20.0);
297
298 doc.add_paragraph("2.4 Pagination Controls")
299 .style("Heading2");
300 doc.add_paragraph("keep_with_next — this paragraph stays with the next")
301 .keep_with_next(true);
302 doc.add_paragraph("(This paragraph was kept with the one above.)");
303 doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
304 .keep_together(true);
305 doc.add_paragraph("widow_control — prevents widow/orphan lines")
306 .widow_control(true);
307
308 // ── SECTION 3: LISTS ──
309 doc.add_paragraph("").page_break_before(true);
310 doc.add_paragraph("3. Lists").style("Heading1");
311
312 doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
313 doc.add_bullet_list_item("First item", 0);
314 doc.add_bullet_list_item("Second item", 0);
315 doc.add_bullet_list_item("Nested level 1", 1);
316 doc.add_bullet_list_item("Nested level 2", 2);
317 doc.add_bullet_list_item("Back to level 1", 1);
318 doc.add_bullet_list_item("Third item", 0);
319
320 doc.add_paragraph("");
321
322 doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
323 doc.add_numbered_list_item("First numbered item", 0);
324 doc.add_numbered_list_item("Second numbered item", 0);
325 doc.add_numbered_list_item("Sub-item A", 1);
326 doc.add_numbered_list_item("Sub-item B", 1);
327 doc.add_numbered_list_item("Third numbered item", 0);
328
329 // ── SECTION 4: TAB STOPS ──
330 doc.add_paragraph("");
331 doc.add_paragraph("4. Tab Stops").style("Heading1");
332
333 doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
334 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
335 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
336 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
337 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
338 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
339
340 doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
341 doc.add_paragraph("Item\t\tPrice")
342 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
343 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
344 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
345 doc.add_paragraph("Widget\t\t$19.99")
346 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
347 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
348 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
349 doc.add_paragraph("Gadget\t\t$249.50")
350 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
351 .add_tab_stop_with_leader(
352 TabAlignment::Right,
353 Length::inches(4.0),
354 TabLeader::Underscore,
355 )
356 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
357
358 // ── SECTION 5: TABLES ──
359 doc.add_paragraph("").page_break_before(true);
360 doc.add_paragraph("5. Tables").style("Heading1");
361
362 doc.add_paragraph("5.1 Basic Table").style("Heading2");
363 {
364 let mut tbl = doc
365 .add_table(4, 3)
366 .borders(BorderStyle::Single, 4, "000000");
367 for col in 0..3 {
368 tbl.cell(0, col).unwrap().shading("2E75B6");
369 }
370 tbl.cell(0, 0).unwrap().set_text("Name");
371 tbl.cell(0, 1).unwrap().set_text("Role");
372 tbl.cell(0, 2).unwrap().set_text("Location");
373 tbl.cell(1, 0).unwrap().set_text("Walter White");
374 tbl.cell(1, 1).unwrap().set_text("CEO");
375 tbl.cell(1, 2).unwrap().set_text("Albuquerque");
376 tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
377 tbl.cell(2, 1).unwrap().set_text("CTO");
378 tbl.cell(2, 2).unwrap().set_text("Remote");
379 tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
380 tbl.cell(3, 1).unwrap().set_text("Security");
381 tbl.cell(3, 2).unwrap().set_text("Washington");
382 }
383
384 doc.add_paragraph("");
385
386 doc.add_paragraph("5.2 Column Span & Cell Shading")
387 .style("Heading2");
388 {
389 let mut tbl = doc
390 .add_table(3, 4)
391 .borders(BorderStyle::Single, 4, "000000")
392 .width_pct(100.0);
393 tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
394 tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
395 tbl.cell(1, 0).unwrap().set_text("Region");
396 tbl.cell(1, 0).unwrap().shading("D6E4F0");
397 tbl.cell(1, 1).unwrap().set_text("Q1");
398 tbl.cell(1, 1).unwrap().shading("D6E4F0");
399 tbl.cell(1, 2).unwrap().set_text("Q2");
400 tbl.cell(1, 2).unwrap().shading("D6E4F0");
401 tbl.cell(1, 3).unwrap().set_text("Total");
402 tbl.cell(1, 3).unwrap().shading("D6E4F0");
403 tbl.cell(2, 0).unwrap().set_text("Americas");
404 tbl.cell(2, 1).unwrap().set_text("$2.4M");
405 tbl.cell(2, 2).unwrap().set_text("$2.7M");
406 tbl.cell(2, 3).unwrap().set_text("$5.1M");
407 }
408
409 doc.add_paragraph("");
410
411 doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
412 {
413 let mut tbl = doc
414 .add_table(4, 3)
415 .borders(BorderStyle::Single, 4, "000000");
416 tbl.cell(0, 0).unwrap().set_text("Category");
417 tbl.cell(0, 0).unwrap().shading("E2EFDA");
418 tbl.cell(0, 1).unwrap().set_text("Item");
419 tbl.cell(0, 1).unwrap().shading("E2EFDA");
420 tbl.cell(0, 2).unwrap().set_text("Price");
421 tbl.cell(0, 2).unwrap().shading("E2EFDA");
422 tbl.cell(1, 0).unwrap().set_text("Hardware");
423 tbl.cell(1, 0).unwrap().v_merge_restart();
424 tbl.cell(1, 1).unwrap().set_text("Laptop");
425 tbl.cell(1, 2).unwrap().set_text("$1,200");
426 tbl.cell(2, 0).unwrap().v_merge_continue();
427 tbl.cell(2, 1).unwrap().set_text("Monitor");
428 tbl.cell(2, 2).unwrap().set_text("$450");
429 tbl.cell(3, 0).unwrap().set_text("Software");
430 tbl.cell(3, 1).unwrap().set_text("IDE License");
431 tbl.cell(3, 2).unwrap().set_text("$200/yr");
432 }
433
434 doc.add_paragraph("");
435
436 doc.add_paragraph("5.4 Nested Table").style("Heading2");
437 {
438 let mut tbl = doc
439 .add_table(2, 2)
440 .borders(BorderStyle::Single, 6, "2E75B6");
441 tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
442 tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
443 tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
444 {
445 let mut cell = tbl.cell(1, 1).unwrap();
446 cell.set_text("Nested table below:");
447 let mut nested = cell
448 .add_table(2, 2)
449 .borders(BorderStyle::Single, 2, "FF6600");
450 nested.cell(0, 0).unwrap().set_text("A");
451 nested.cell(0, 1).unwrap().set_text("B");
452 nested.cell(1, 0).unwrap().set_text("C");
453 nested.cell(1, 1).unwrap().set_text("D");
454 }
455 }
456
457 doc.add_paragraph("");
458
459 doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
460 .style("Heading2");
461 {
462 let mut tbl = doc
463 .add_table(2, 3)
464 .borders(BorderStyle::Single, 4, "666666");
465 tbl.row(0).unwrap().height(Length::pt(50.0)).header();
466 tbl.cell(0, 0).unwrap().set_text("Top");
467 tbl.cell(0, 0)
468 .unwrap()
469 .vertical_alignment(VerticalAlignment::Top)
470 .shading("FFF2CC");
471 tbl.cell(0, 1).unwrap().set_text("Center");
472 tbl.cell(0, 1)
473 .unwrap()
474 .vertical_alignment(VerticalAlignment::Center)
475 .shading("D9E2F3");
476 tbl.cell(0, 2).unwrap().set_text("Bottom");
477 tbl.cell(0, 2)
478 .unwrap()
479 .vertical_alignment(VerticalAlignment::Bottom)
480 .shading("E2EFDA");
481 tbl.row(1).unwrap().cant_split();
482 tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
483 tbl.cell(1, 0).unwrap().no_wrap();
484 tbl.cell(1, 1).unwrap().set_text("Fixed width");
485 tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
486 tbl.cell(1, 2).unwrap().set_text("Normal");
487 }
488
489 // ── SECTION 6: IMAGES ──
490 doc.add_paragraph("").page_break_before(true);
491 doc.add_paragraph("6. Images").style("Heading1");
492
493 doc.add_paragraph("6.1 Inline Image").style("Heading2");
494 doc.add_paragraph("A blue gradient image below:");
495 let img = create_sample_png(200, 50, [0, 80, 200]);
496 doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
497
498 doc.add_paragraph("");
499 doc.add_paragraph("6.2 Header Image").style("Heading2");
500 let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
501 doc.set_header_image(
502 &hdr_img,
503 "header_logo.png",
504 Length::inches(2.0),
505 Length::inches(0.2),
506 );
507 doc.add_paragraph("The header now contains an inline image (check the top of this page).");
508
509 doc.add_paragraph("");
510 doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
511
512 // ── SECTION 7: CONTENT MANIPULATION ──
513 doc.add_paragraph("").page_break_before(true);
514 doc.add_paragraph("7. Content Manipulation")
515 .style("Heading1");
516
517 doc.add_paragraph("7.1 Placeholder Replacement")
518 .style("Heading2");
519 doc.add_paragraph("Customer: {{customer}}");
520 doc.add_paragraph("Date: {{date}}");
521 doc.add_paragraph("Reference: {{ref_number}}");
522 {
523 let mut tbl = doc
524 .add_table(2, 2)
525 .borders(BorderStyle::Single, 4, "000000");
526 tbl.cell(0, 0).unwrap().set_text("Project");
527 tbl.cell(0, 0).unwrap().shading("D6E4F0");
528 tbl.cell(0, 1).unwrap().set_text("{{project}}");
529 tbl.cell(1, 0).unwrap().set_text("Status");
530 tbl.cell(1, 0).unwrap().shading("D6E4F0");
531 tbl.cell(1, 1).unwrap().set_text("{{status}}");
532 }
533
534 let mut replacements = HashMap::new();
535 replacements.insert("{{customer}}", "Tensorbee Inc.");
536 replacements.insert("{{date}}", "February 22, 2026");
537 replacements.insert("{{ref_number}}", "TB-2026-001");
538 replacements.insert("{{project}}", "Infrastructure Upgrade");
539 replacements.insert("{{status}}", "In Progress");
540 let n = doc.replace_all(&replacements);
541 doc.add_paragraph(&format!("({n} placeholders replaced above)"));
542
543 doc.add_paragraph("");
544
545 doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
546 doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
547 let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
548 doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
549
550 doc.add_paragraph("");
551
552 doc.add_paragraph("7.3 Content Insertion").style("Heading2");
553 doc.add_paragraph("Section A: First content.");
554 doc.add_paragraph("Section C: Third content.");
555 if let Some(idx) = doc.find_content_index("Section C") {
556 doc.insert_paragraph(
557 idx,
558 "Section B: Inserted between A and C via find_content_index().",
559 );
560 }
561
562 // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
563 doc.add_paragraph("").section_break(SectionBreak::NextPage);
564 doc.add_paragraph("8. Section Breaks & Orientation")
565 .style("Heading1");
566 doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
567
568 {
569 let mut tbl = doc
570 .add_table(3, 7)
571 .borders(BorderStyle::Single, 4, "2E75B6");
572 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
573 for (c, h) in headers.iter().enumerate() {
574 tbl.cell(0, c).unwrap().set_text(h);
575 tbl.cell(0, c).unwrap().shading("2E75B6");
576 }
577 let data = [
578 [
579 "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
580 ],
581 [
582 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
583 ],
584 ];
585 for (r, row) in data.iter().enumerate() {
586 for (c, v) in row.iter().enumerate() {
587 tbl.cell(r + 1, c).unwrap().set_text(v);
588 }
589 }
590 }
591
592 doc.add_paragraph("")
593 .section_break(SectionBreak::NextPage)
594 .section_landscape();
595
596 // ── SECTION 9: CUSTOM STYLES ──
597 doc.add_paragraph("9. Custom Styles").style("Heading1");
598 doc.add_style(
599 StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
600 .based_on("Normal")
601 .paragraph_properties(rdocx_oxml::properties::CT_PPr {
602 shading: Some(rdocx_oxml::properties::CT_Shd {
603 val: "clear".to_string(),
604 color: None,
605 fill: Some("FFF2CC".to_string()),
606 }),
607 ..Default::default()
608 })
609 .run_properties(rdocx_oxml::properties::CT_RPr {
610 bold: Some(true),
611 color: Some("C45911".to_string()),
612 ..Default::default()
613 }),
614 );
615 doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
616 .style("CustomHighlight");
617
618 doc.add_paragraph("");
619
620 // ── SECTION 10: DOCUMENT INTELLIGENCE ──
621 doc.add_paragraph("10. Document Intelligence API")
622 .style("Heading1");
623
624 let wc = doc.word_count();
625 let hc = doc.headings().len();
626 let ic = doc.images().len();
627 let lc = doc.links().len();
628 doc.add_paragraph(&format!("Word count: {wc}"));
629 doc.add_paragraph(&format!("Heading count: {hc}"));
630 doc.add_paragraph(&format!("Image count: {ic}"));
631 doc.add_paragraph(&format!("Link count: {lc}"));
632
633 let outline = doc.document_outline();
634 doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
635
636 let issues = doc.audit_accessibility();
637 doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
638 for issue in &issues {
639 doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
640 }
641
642 // ── SECTION 11: DOCUMENT MERGING ──
643 doc.add_paragraph("");
644 doc.add_paragraph("11. Document Merging").style("Heading1");
645 {
646 let mut other = Document::new();
647 other.add_paragraph("This paragraph was merged from another document using append().");
648 doc.append(&other);
649 }
650
651 doc.add_paragraph("");
652
653 // ── SUMMARY ──
654 doc.add_paragraph("Summary of Demonstrated Features")
655 .style("Heading1");
656 let features = [
657 "Page setup: size, margins, header/footer distance, gutter",
658 "Document metadata: title, author, subject, keywords",
659 "Headers and footers: text, images, different first page",
660 "Background images (full-page behind text)",
661 "Table of Contents generation with bookmarks",
662 "Text formatting: bold, italic, underline styles, strike, color, size, font",
663 "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
664 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
665 "Pagination controls: keep-with-next, keep-together, widow control",
666 "Bullet and numbered lists with nesting",
667 "Tab stops with dot/underscore/hyphen leaders",
668 "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
669 "Row properties: header rows, exact height, cant-split",
670 "Cell properties: width, no-wrap, vertical alignment",
671 "Inline images and header images",
672 "Placeholder replacement (single and batch)",
673 "Regex-based find and replace",
674 "Content insertion at specific positions",
675 "Section breaks with mixed portrait/landscape",
676 "Custom paragraph and character styles",
677 "Document intelligence: word count, headings, outline, images, links",
678 "Accessibility audit",
679 "Document merging (append)",
680 "Export: DOCX, PDF, HTML, Markdown",
681 ];
682 for f in &features {
683 doc.add_bullet_list_item(f, 0);
684 }
685 doc.add_paragraph("");
686 doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
687 .alignment(Alignment::Center)
688 .shading("E2EFDA")
689 .border_all(BorderStyle::Single, 2, "00AA00");
690
691 doc
692}Sourcepub fn append_with_break(&mut self, other: &Document, break_type: SectionBreak)
pub fn append_with_break(&mut self, other: &Document, break_type: SectionBreak)
Append the content of another document with a section break.
Sourcepub fn insert_document(&mut self, index: usize, other: &Document)
pub fn insert_document(&mut self, index: usize, other: &Document)
Insert the content of another document at a specified body index.
An index past the end is clamped to the end rather than panicking.
Sourcepub fn insert_toc(&mut self, index: usize, max_level: u32)
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 TOCmax_level- Maximum heading level to include (1-9, typically 3)
Examples found in repository?
97fn generate_feature_showcase(_samples_dir: &Path) -> Document {
98 let mut doc = Document::new();
99
100 // ── Page Setup & Metadata ──
101 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
102 doc.set_margins(
103 Length::inches(1.0),
104 Length::inches(1.0),
105 Length::inches(1.0),
106 Length::inches(1.0),
107 );
108 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
109 doc.set_gutter(Length::twips(0));
110 doc.set_title("rdocx Feature Showcase");
111 doc.set_author("rdocx Sample Generator");
112 doc.set_subject("Comprehensive feature demonstration");
113 doc.set_keywords("rdocx, docx, rust, sample, showcase");
114
115 // Headers & Footers with different first page
116 doc.set_different_first_page(true);
117 doc.set_first_page_header("rdocx");
118 doc.set_first_page_footer("Feature Showcase — Cover Page");
119 doc.set_header("rdocx Feature Showcase");
120 doc.set_footer("Generated by rdocx");
121
122 // ── COVER PAGE ──
123 let bg = create_sample_png(612, 792, [20, 45, 90]);
124 doc.add_background_image(&bg, "cover_bg.png");
125
126 for _ in 0..3 {
127 doc.add_paragraph("");
128 }
129 {
130 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
131 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
132 }
133 {
134 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
135 p.add_run("Complete Feature Showcase")
136 .size(28.0)
137 .color("FFFFFF")
138 .italic(true);
139 }
140 doc.add_paragraph("");
141 {
142 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
143 p.add_run("Every feature of the rdocx Rust library")
144 .size(13.0)
145 .color("B0C4DE");
146 }
147 {
148 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
149 p.add_run("demonstrated in a single document.")
150 .size(13.0)
151 .color("B0C4DE");
152 }
153
154 // ── TABLE OF CONTENTS ──
155 doc.add_paragraph("").page_break_before(true);
156 doc.insert_toc(doc.content_count(), 3);
157
158 // ── SECTION 1: TEXT FORMATTING ──
159 doc.add_paragraph("").page_break_before(true);
160 doc.add_paragraph("1. Text Formatting").style("Heading1");
161
162 // Paragraph Alignment
163 doc.add_paragraph("1.1 Paragraph Alignment")
164 .style("Heading2");
165 doc.add_paragraph("Left-aligned paragraph (default).")
166 .alignment(Alignment::Left);
167 doc.add_paragraph("Center-aligned paragraph.")
168 .alignment(Alignment::Center);
169 doc.add_paragraph("Right-aligned paragraph.")
170 .alignment(Alignment::Right);
171 doc.add_paragraph(
172 "Justified paragraph — this text is long enough to demonstrate how justified alignment \
173 distributes extra space across word gaps so lines fill the full width of the text area.",
174 )
175 .alignment(Alignment::Justify);
176
177 doc.add_paragraph("");
178
179 // Run Formatting
180 doc.add_paragraph("1.2 Run Formatting").style("Heading2");
181 {
182 let mut p = doc.add_paragraph("");
183 p.add_run("Bold").bold(true);
184 p.add_run(" | ");
185 p.add_run("Italic").italic(true);
186 p.add_run(" | ");
187 p.add_run("Bold Italic").bold(true).italic(true);
188 p.add_run(" | ");
189 p.add_run("Underline").underline(true);
190 p.add_run(" | ");
191 p.add_run("Strikethrough").strike(true);
192 p.add_run(" | ");
193 p.add_run("Double Strike").double_strike(true);
194 }
195 {
196 let mut p = doc.add_paragraph("");
197 p.add_run("Red").color("FF0000");
198 p.add_run(" | ");
199 p.add_run("Blue").color("0000FF");
200 p.add_run(" | ");
201 p.add_run("Green").color("00AA00");
202 p.add_run(" | ");
203 p.add_run("Highlighted").highlight("FFFF00");
204 }
205 {
206 let mut p = doc.add_paragraph("");
207 p.add_run("8pt").size(8.0);
208 p.add_run(" | ");
209 p.add_run("11pt").size(11.0);
210 p.add_run(" | ");
211 p.add_run("16pt").size(16.0);
212 p.add_run(" | ");
213 p.add_run("24pt").size(24.0);
214 }
215 {
216 let mut p = doc.add_paragraph("");
217 p.add_run("Arial").font("Arial");
218 p.add_run(" | ");
219 p.add_run("Times New Roman").font("Times New Roman");
220 p.add_run(" | ");
221 p.add_run("Courier New").font("Courier New");
222 }
223 {
224 let mut p = doc.add_paragraph("");
225 p.add_run("H");
226 p.add_run("2").subscript();
227 p.add_run("O (subscript) | E=mc");
228 p.add_run("2").superscript();
229 p.add_run(" (superscript)");
230 }
231 {
232 let mut p = doc.add_paragraph("");
233 p.add_run("ALL CAPS").all_caps(true);
234 p.add_run(" | ");
235 p.add_run("Small Caps").small_caps(true);
236 p.add_run(" | ");
237 p.add_run("Expanded").character_spacing(Length::twips(40));
238 p.add_run(" | ");
239 p.add_run("Hidden text (not visible)").hidden(true);
240 }
241
242 doc.add_paragraph("");
243
244 // Underline Styles
245 doc.add_paragraph("1.3 Underline Styles").style("Heading2");
246 {
247 let mut p = doc.add_paragraph("");
248 p.add_run("Single ")
249 .underline_style(rdocx::UnderlineStyle::Single);
250 p.add_run("Double ")
251 .underline_style(rdocx::UnderlineStyle::Double);
252 p.add_run("Thick ")
253 .underline_style(rdocx::UnderlineStyle::Thick);
254 p.add_run("Dotted ")
255 .underline_style(rdocx::UnderlineStyle::Dotted);
256 p.add_run("Dash ")
257 .underline_style(rdocx::UnderlineStyle::Dash);
258 p.add_run("Wave ")
259 .underline_style(rdocx::UnderlineStyle::Wave);
260 }
261
262 // ── SECTION 2: PARAGRAPH FORMATTING ──
263 doc.add_paragraph("").page_break_before(true);
264 doc.add_paragraph("2. Paragraph Formatting")
265 .style("Heading1");
266
267 doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
268 doc.add_paragraph("Paragraph with light green shading")
269 .shading("E2EFDA");
270 doc.add_paragraph("Paragraph with bottom border")
271 .border_bottom(BorderStyle::Single, 6, "2E75B6");
272 doc.add_paragraph("Paragraph with all borders (red)")
273 .border_all(BorderStyle::Single, 4, "FF0000");
274
275 doc.add_paragraph("2.2 Indentation").style("Heading2");
276 doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
277 .indent_left(Length::inches(1.0))
278 .hanging_indent(Length::inches(0.5));
279 doc.add_paragraph("0.5-inch first-line indent on this paragraph")
280 .first_line_indent(Length::inches(0.5));
281 doc.add_paragraph("Both left and right indent (0.75 inches each)")
282 .indent_left(Length::inches(0.75))
283 .indent_right(Length::inches(0.75));
284
285 doc.add_paragraph("2.3 Spacing & Line Height")
286 .style("Heading2");
287 doc.add_paragraph("Extra space before (24pt) and after (12pt)")
288 .space_before(Length::pt(24.0))
289 .space_after(Length::pt(12.0));
290 doc.add_paragraph(
291 "Double line spacing paragraph. This text should have extra vertical space between \
292 lines to demonstrate line_spacing_multiple(2.0).",
293 )
294 .line_spacing_multiple(2.0);
295 doc.add_paragraph("Exact 20pt line spacing (fixed height).")
296 .line_spacing(20.0);
297
298 doc.add_paragraph("2.4 Pagination Controls")
299 .style("Heading2");
300 doc.add_paragraph("keep_with_next — this paragraph stays with the next")
301 .keep_with_next(true);
302 doc.add_paragraph("(This paragraph was kept with the one above.)");
303 doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
304 .keep_together(true);
305 doc.add_paragraph("widow_control — prevents widow/orphan lines")
306 .widow_control(true);
307
308 // ── SECTION 3: LISTS ──
309 doc.add_paragraph("").page_break_before(true);
310 doc.add_paragraph("3. Lists").style("Heading1");
311
312 doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
313 doc.add_bullet_list_item("First item", 0);
314 doc.add_bullet_list_item("Second item", 0);
315 doc.add_bullet_list_item("Nested level 1", 1);
316 doc.add_bullet_list_item("Nested level 2", 2);
317 doc.add_bullet_list_item("Back to level 1", 1);
318 doc.add_bullet_list_item("Third item", 0);
319
320 doc.add_paragraph("");
321
322 doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
323 doc.add_numbered_list_item("First numbered item", 0);
324 doc.add_numbered_list_item("Second numbered item", 0);
325 doc.add_numbered_list_item("Sub-item A", 1);
326 doc.add_numbered_list_item("Sub-item B", 1);
327 doc.add_numbered_list_item("Third numbered item", 0);
328
329 // ── SECTION 4: TAB STOPS ──
330 doc.add_paragraph("");
331 doc.add_paragraph("4. Tab Stops").style("Heading1");
332
333 doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
334 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
335 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
336 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
337 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
338 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
339
340 doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
341 doc.add_paragraph("Item\t\tPrice")
342 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
343 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
344 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
345 doc.add_paragraph("Widget\t\t$19.99")
346 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
347 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
348 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
349 doc.add_paragraph("Gadget\t\t$249.50")
350 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
351 .add_tab_stop_with_leader(
352 TabAlignment::Right,
353 Length::inches(4.0),
354 TabLeader::Underscore,
355 )
356 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
357
358 // ── SECTION 5: TABLES ──
359 doc.add_paragraph("").page_break_before(true);
360 doc.add_paragraph("5. Tables").style("Heading1");
361
362 doc.add_paragraph("5.1 Basic Table").style("Heading2");
363 {
364 let mut tbl = doc
365 .add_table(4, 3)
366 .borders(BorderStyle::Single, 4, "000000");
367 for col in 0..3 {
368 tbl.cell(0, col).unwrap().shading("2E75B6");
369 }
370 tbl.cell(0, 0).unwrap().set_text("Name");
371 tbl.cell(0, 1).unwrap().set_text("Role");
372 tbl.cell(0, 2).unwrap().set_text("Location");
373 tbl.cell(1, 0).unwrap().set_text("Walter White");
374 tbl.cell(1, 1).unwrap().set_text("CEO");
375 tbl.cell(1, 2).unwrap().set_text("Albuquerque");
376 tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
377 tbl.cell(2, 1).unwrap().set_text("CTO");
378 tbl.cell(2, 2).unwrap().set_text("Remote");
379 tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
380 tbl.cell(3, 1).unwrap().set_text("Security");
381 tbl.cell(3, 2).unwrap().set_text("Washington");
382 }
383
384 doc.add_paragraph("");
385
386 doc.add_paragraph("5.2 Column Span & Cell Shading")
387 .style("Heading2");
388 {
389 let mut tbl = doc
390 .add_table(3, 4)
391 .borders(BorderStyle::Single, 4, "000000")
392 .width_pct(100.0);
393 tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
394 tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
395 tbl.cell(1, 0).unwrap().set_text("Region");
396 tbl.cell(1, 0).unwrap().shading("D6E4F0");
397 tbl.cell(1, 1).unwrap().set_text("Q1");
398 tbl.cell(1, 1).unwrap().shading("D6E4F0");
399 tbl.cell(1, 2).unwrap().set_text("Q2");
400 tbl.cell(1, 2).unwrap().shading("D6E4F0");
401 tbl.cell(1, 3).unwrap().set_text("Total");
402 tbl.cell(1, 3).unwrap().shading("D6E4F0");
403 tbl.cell(2, 0).unwrap().set_text("Americas");
404 tbl.cell(2, 1).unwrap().set_text("$2.4M");
405 tbl.cell(2, 2).unwrap().set_text("$2.7M");
406 tbl.cell(2, 3).unwrap().set_text("$5.1M");
407 }
408
409 doc.add_paragraph("");
410
411 doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
412 {
413 let mut tbl = doc
414 .add_table(4, 3)
415 .borders(BorderStyle::Single, 4, "000000");
416 tbl.cell(0, 0).unwrap().set_text("Category");
417 tbl.cell(0, 0).unwrap().shading("E2EFDA");
418 tbl.cell(0, 1).unwrap().set_text("Item");
419 tbl.cell(0, 1).unwrap().shading("E2EFDA");
420 tbl.cell(0, 2).unwrap().set_text("Price");
421 tbl.cell(0, 2).unwrap().shading("E2EFDA");
422 tbl.cell(1, 0).unwrap().set_text("Hardware");
423 tbl.cell(1, 0).unwrap().v_merge_restart();
424 tbl.cell(1, 1).unwrap().set_text("Laptop");
425 tbl.cell(1, 2).unwrap().set_text("$1,200");
426 tbl.cell(2, 0).unwrap().v_merge_continue();
427 tbl.cell(2, 1).unwrap().set_text("Monitor");
428 tbl.cell(2, 2).unwrap().set_text("$450");
429 tbl.cell(3, 0).unwrap().set_text("Software");
430 tbl.cell(3, 1).unwrap().set_text("IDE License");
431 tbl.cell(3, 2).unwrap().set_text("$200/yr");
432 }
433
434 doc.add_paragraph("");
435
436 doc.add_paragraph("5.4 Nested Table").style("Heading2");
437 {
438 let mut tbl = doc
439 .add_table(2, 2)
440 .borders(BorderStyle::Single, 6, "2E75B6");
441 tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
442 tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
443 tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
444 {
445 let mut cell = tbl.cell(1, 1).unwrap();
446 cell.set_text("Nested table below:");
447 let mut nested = cell
448 .add_table(2, 2)
449 .borders(BorderStyle::Single, 2, "FF6600");
450 nested.cell(0, 0).unwrap().set_text("A");
451 nested.cell(0, 1).unwrap().set_text("B");
452 nested.cell(1, 0).unwrap().set_text("C");
453 nested.cell(1, 1).unwrap().set_text("D");
454 }
455 }
456
457 doc.add_paragraph("");
458
459 doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
460 .style("Heading2");
461 {
462 let mut tbl = doc
463 .add_table(2, 3)
464 .borders(BorderStyle::Single, 4, "666666");
465 tbl.row(0).unwrap().height(Length::pt(50.0)).header();
466 tbl.cell(0, 0).unwrap().set_text("Top");
467 tbl.cell(0, 0)
468 .unwrap()
469 .vertical_alignment(VerticalAlignment::Top)
470 .shading("FFF2CC");
471 tbl.cell(0, 1).unwrap().set_text("Center");
472 tbl.cell(0, 1)
473 .unwrap()
474 .vertical_alignment(VerticalAlignment::Center)
475 .shading("D9E2F3");
476 tbl.cell(0, 2).unwrap().set_text("Bottom");
477 tbl.cell(0, 2)
478 .unwrap()
479 .vertical_alignment(VerticalAlignment::Bottom)
480 .shading("E2EFDA");
481 tbl.row(1).unwrap().cant_split();
482 tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
483 tbl.cell(1, 0).unwrap().no_wrap();
484 tbl.cell(1, 1).unwrap().set_text("Fixed width");
485 tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
486 tbl.cell(1, 2).unwrap().set_text("Normal");
487 }
488
489 // ── SECTION 6: IMAGES ──
490 doc.add_paragraph("").page_break_before(true);
491 doc.add_paragraph("6. Images").style("Heading1");
492
493 doc.add_paragraph("6.1 Inline Image").style("Heading2");
494 doc.add_paragraph("A blue gradient image below:");
495 let img = create_sample_png(200, 50, [0, 80, 200]);
496 doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
497
498 doc.add_paragraph("");
499 doc.add_paragraph("6.2 Header Image").style("Heading2");
500 let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
501 doc.set_header_image(
502 &hdr_img,
503 "header_logo.png",
504 Length::inches(2.0),
505 Length::inches(0.2),
506 );
507 doc.add_paragraph("The header now contains an inline image (check the top of this page).");
508
509 doc.add_paragraph("");
510 doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
511
512 // ── SECTION 7: CONTENT MANIPULATION ──
513 doc.add_paragraph("").page_break_before(true);
514 doc.add_paragraph("7. Content Manipulation")
515 .style("Heading1");
516
517 doc.add_paragraph("7.1 Placeholder Replacement")
518 .style("Heading2");
519 doc.add_paragraph("Customer: {{customer}}");
520 doc.add_paragraph("Date: {{date}}");
521 doc.add_paragraph("Reference: {{ref_number}}");
522 {
523 let mut tbl = doc
524 .add_table(2, 2)
525 .borders(BorderStyle::Single, 4, "000000");
526 tbl.cell(0, 0).unwrap().set_text("Project");
527 tbl.cell(0, 0).unwrap().shading("D6E4F0");
528 tbl.cell(0, 1).unwrap().set_text("{{project}}");
529 tbl.cell(1, 0).unwrap().set_text("Status");
530 tbl.cell(1, 0).unwrap().shading("D6E4F0");
531 tbl.cell(1, 1).unwrap().set_text("{{status}}");
532 }
533
534 let mut replacements = HashMap::new();
535 replacements.insert("{{customer}}", "Tensorbee Inc.");
536 replacements.insert("{{date}}", "February 22, 2026");
537 replacements.insert("{{ref_number}}", "TB-2026-001");
538 replacements.insert("{{project}}", "Infrastructure Upgrade");
539 replacements.insert("{{status}}", "In Progress");
540 let n = doc.replace_all(&replacements);
541 doc.add_paragraph(&format!("({n} placeholders replaced above)"));
542
543 doc.add_paragraph("");
544
545 doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
546 doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
547 let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
548 doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
549
550 doc.add_paragraph("");
551
552 doc.add_paragraph("7.3 Content Insertion").style("Heading2");
553 doc.add_paragraph("Section A: First content.");
554 doc.add_paragraph("Section C: Third content.");
555 if let Some(idx) = doc.find_content_index("Section C") {
556 doc.insert_paragraph(
557 idx,
558 "Section B: Inserted between A and C via find_content_index().",
559 );
560 }
561
562 // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
563 doc.add_paragraph("").section_break(SectionBreak::NextPage);
564 doc.add_paragraph("8. Section Breaks & Orientation")
565 .style("Heading1");
566 doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
567
568 {
569 let mut tbl = doc
570 .add_table(3, 7)
571 .borders(BorderStyle::Single, 4, "2E75B6");
572 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
573 for (c, h) in headers.iter().enumerate() {
574 tbl.cell(0, c).unwrap().set_text(h);
575 tbl.cell(0, c).unwrap().shading("2E75B6");
576 }
577 let data = [
578 [
579 "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
580 ],
581 [
582 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
583 ],
584 ];
585 for (r, row) in data.iter().enumerate() {
586 for (c, v) in row.iter().enumerate() {
587 tbl.cell(r + 1, c).unwrap().set_text(v);
588 }
589 }
590 }
591
592 doc.add_paragraph("")
593 .section_break(SectionBreak::NextPage)
594 .section_landscape();
595
596 // ── SECTION 9: CUSTOM STYLES ──
597 doc.add_paragraph("9. Custom Styles").style("Heading1");
598 doc.add_style(
599 StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
600 .based_on("Normal")
601 .paragraph_properties(rdocx_oxml::properties::CT_PPr {
602 shading: Some(rdocx_oxml::properties::CT_Shd {
603 val: "clear".to_string(),
604 color: None,
605 fill: Some("FFF2CC".to_string()),
606 }),
607 ..Default::default()
608 })
609 .run_properties(rdocx_oxml::properties::CT_RPr {
610 bold: Some(true),
611 color: Some("C45911".to_string()),
612 ..Default::default()
613 }),
614 );
615 doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
616 .style("CustomHighlight");
617
618 doc.add_paragraph("");
619
620 // ── SECTION 10: DOCUMENT INTELLIGENCE ──
621 doc.add_paragraph("10. Document Intelligence API")
622 .style("Heading1");
623
624 let wc = doc.word_count();
625 let hc = doc.headings().len();
626 let ic = doc.images().len();
627 let lc = doc.links().len();
628 doc.add_paragraph(&format!("Word count: {wc}"));
629 doc.add_paragraph(&format!("Heading count: {hc}"));
630 doc.add_paragraph(&format!("Image count: {ic}"));
631 doc.add_paragraph(&format!("Link count: {lc}"));
632
633 let outline = doc.document_outline();
634 doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
635
636 let issues = doc.audit_accessibility();
637 doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
638 for issue in &issues {
639 doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
640 }
641
642 // ── SECTION 11: DOCUMENT MERGING ──
643 doc.add_paragraph("");
644 doc.add_paragraph("11. Document Merging").style("Heading1");
645 {
646 let mut other = Document::new();
647 other.add_paragraph("This paragraph was merged from another document using append().");
648 doc.append(&other);
649 }
650
651 doc.add_paragraph("");
652
653 // ── SUMMARY ──
654 doc.add_paragraph("Summary of Demonstrated Features")
655 .style("Heading1");
656 let features = [
657 "Page setup: size, margins, header/footer distance, gutter",
658 "Document metadata: title, author, subject, keywords",
659 "Headers and footers: text, images, different first page",
660 "Background images (full-page behind text)",
661 "Table of Contents generation with bookmarks",
662 "Text formatting: bold, italic, underline styles, strike, color, size, font",
663 "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
664 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
665 "Pagination controls: keep-with-next, keep-together, widow control",
666 "Bullet and numbered lists with nesting",
667 "Tab stops with dot/underscore/hyphen leaders",
668 "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
669 "Row properties: header rows, exact height, cant-split",
670 "Cell properties: width, no-wrap, vertical alignment",
671 "Inline images and header images",
672 "Placeholder replacement (single and batch)",
673 "Regex-based find and replace",
674 "Content insertion at specific positions",
675 "Section breaks with mixed portrait/landscape",
676 "Custom paragraph and character styles",
677 "Document intelligence: word count, headings, outline, images, links",
678 "Accessibility audit",
679 "Document merging (append)",
680 "Export: DOCX, PDF, HTML, Markdown",
681 ];
682 for f in &features {
683 doc.add_bullet_list_item(f, 0);
684 }
685 doc.add_paragraph("");
686 doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
687 .alignment(Alignment::Center)
688 .shading("E2EFDA")
689 .border_all(BorderStyle::Single, 2, "00AA00");
690
691 doc
692}
693
694// =============================================================================
695// 2. PROPOSAL DOCUMENT — Deep navy + gold scheme
696// =============================================================================
697fn generate_proposal(_samples_dir: &Path) -> Document {
698 let mut doc = Document::new();
699
700 // Colors: Navy #1B2A4A, Gold #C5922E, Light #F4F1EB
701 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
702 doc.set_margins(
703 Length::inches(1.0),
704 Length::inches(1.0),
705 Length::inches(1.0),
706 Length::inches(1.0),
707 );
708 doc.set_title("AI Platform Modernization Proposal");
709 doc.set_author("Walter White");
710 doc.set_subject("Technology Proposal");
711 doc.set_keywords("proposal, AI, modernization, Tensorbee");
712
713 // Banner header
714 let logo_img = create_logo_png(220, 48);
715 let banner = build_header_banner_xml(
716 "rId1",
717 &BannerOpts {
718 bg_color: "1B2A4A",
719 banner_width: 7772400,
720 banner_height: 914400,
721 logo_width: 2011680,
722 logo_height: 438912,
723 logo_x_offset: 295125,
724 logo_y_offset: 237744,
725 },
726 );
727 doc.set_raw_header_with_images(
728 banner,
729 &[("rId1", &logo_img, "logo.png")],
730 rdocx_oxml::header_footer::HdrFtrType::Default,
731 );
732 doc.set_different_first_page(true);
733 let cover_banner = build_header_banner_xml(
734 "rId1",
735 &BannerOpts {
736 bg_color: "C5922E",
737 banner_width: 7772400,
738 banner_height: 914400,
739 logo_width: 2011680,
740 logo_height: 438912,
741 logo_x_offset: 295125,
742 logo_y_offset: 237744,
743 },
744 );
745 doc.set_raw_header_with_images(
746 cover_banner,
747 &[("rId1", &logo_img, "logo.png")],
748 rdocx_oxml::header_footer::HdrFtrType::First,
749 );
750 doc.set_margins(
751 Length::twips(2292),
752 Length::twips(1440),
753 Length::twips(1440),
754 Length::twips(1440),
755 );
756 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
757 doc.set_footer("Tensorbee — Confidential");
758
759 // Custom styles
760 doc.add_style(
761 StyleBuilder::paragraph("ProposalTitle", "Proposal Title")
762 .based_on("Normal")
763 .run_properties(rdocx_oxml::properties::CT_RPr {
764 bold: Some(true),
765 font_ascii: Some("Georgia".to_string()),
766 font_hansi: Some("Georgia".to_string()),
767 sz: Some(rdocx_oxml::HalfPoint(56)), // half-points → 28pt
768 color: Some("1B2A4A".to_string()),
769 ..Default::default()
770 }),
771 );
772
773 // ── Cover Page ──
774 for _ in 0..4 {
775 doc.add_paragraph("");
776 }
777 doc.add_paragraph("AI Platform Modernization")
778 .style("ProposalTitle")
779 .alignment(Alignment::Center);
780 doc.add_paragraph("")
781 .alignment(Alignment::Center)
782 .border_bottom(BorderStyle::Single, 8, "C5922E");
783 {
784 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
785 p.add_run("Prepared for Global Financial Corp.")
786 .size(14.0)
787 .color("666666")
788 .italic(true);
789 }
790 {
791 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
792 p.add_run("Prepared by: Walter White, CEO — Tensorbee")
793 .size(12.0)
794 .color("888888");
795 }
796 {
797 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
798 p.add_run("February 22, 2026").size(12.0).color("888888");
799 }
800
801 // ── TOC ──
802 doc.add_paragraph("").page_break_before(true);
803 doc.add_paragraph("Table of Contents").style("Heading1");
804 // We'll add a manual-style TOC since the insert_toc goes at a specific position
805 doc.insert_toc(doc.content_count(), 2);
806
807 // ── Executive Summary ──
808 doc.add_paragraph("").page_break_before(true);
809 doc.add_paragraph("Executive Summary").style("Heading1");
810 doc.add_paragraph(
811 "Tensorbee proposes a comprehensive modernization of Global Financial Corp's AI platform, \
812 transitioning from legacy batch-processing systems to a real-time inference architecture. \
813 This transformation will reduce model deployment time from weeks to hours, improve \
814 prediction accuracy by an estimated 23%, and deliver $4.2M in annual operational savings.",
815 )
816 .first_line_indent(Length::inches(0.3));
817 doc.add_paragraph(
818 "The project spans three phases over 18 months, with a total investment of $2.8M. \
819 Tensorbee brings deep expertise in ML infrastructure, having successfully completed \
820 similar transformations for three Fortune 500 financial institutions.",
821 )
822 .first_line_indent(Length::inches(0.3));
823
824 // Key metrics table
825 doc.add_paragraph("");
826 {
827 let mut tbl = doc
828 .add_table(5, 2)
829 .borders(BorderStyle::Single, 4, "1B2A4A")
830 .width_pct(80.0);
831 tbl.cell(0, 0).unwrap().set_text("Key Metric");
832 tbl.cell(0, 0).unwrap().shading("1B2A4A");
833 tbl.cell(0, 1).unwrap().set_text("Value");
834 tbl.cell(0, 1).unwrap().shading("1B2A4A");
835 let rows = [
836 ("Total Investment", "$2.8M"),
837 ("Annual Savings", "$4.2M"),
838 ("ROI (Year 1)", "150%"),
839 ("Timeline", "18 months"),
840 ];
841 for (i, (k, v)) in rows.iter().enumerate() {
842 tbl.cell(i + 1, 0).unwrap().set_text(k);
843 if i % 2 == 0 {
844 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
845 }
846 tbl.cell(i + 1, 1).unwrap().set_text(v);
847 if i % 2 == 0 {
848 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
849 }
850 }
851 }
852
853 // ── Problem Statement ──
854 doc.add_paragraph("").page_break_before(true);
855 doc.add_paragraph("Problem Statement").style("Heading1");
856 doc.add_paragraph(
857 "Global Financial Corp's current AI infrastructure faces three critical challenges:",
858 );
859 doc.add_numbered_list_item("Deployment Latency — New models take 3-4 weeks to deploy to production, compared to the industry benchmark of 2-3 days.", 0);
860 doc.add_numbered_list_item("Scalability Constraints — The batch processing architecture cannot handle real-time inference demands during peak trading hours.", 0);
861 doc.add_numbered_list_item("Technical Debt — Legacy Python 2.7 codebases and manual deployment scripts create reliability risks and slow iteration speed.", 0);
862
863 // ── Proposed Solution ──
864 doc.add_paragraph("").page_break_before(true);
865 doc.add_paragraph("Proposed Solution").style("Heading1");
866
867 doc.add_paragraph("Phase 1: Foundation (Months 1-6)")
868 .style("Heading2");
869 doc.add_bullet_list_item("Deploy Kubernetes-based ML serving infrastructure", 0);
870 doc.add_bullet_list_item("Implement CI/CD pipeline for model deployment", 0);
871 doc.add_bullet_list_item("Migrate top 5 production models to new platform", 0);
872
873 doc.add_paragraph("Phase 2: Expansion (Months 7-12)")
874 .style("Heading2");
875 doc.add_bullet_list_item(
876 "Real-time feature engineering pipeline (Apache Kafka + Flink)",
877 0,
878 );
879 doc.add_bullet_list_item("A/B testing framework for model variants", 0);
880 doc.add_bullet_list_item("Automated model monitoring and drift detection", 0);
881
882 doc.add_paragraph("Phase 3: Optimization (Months 13-18)")
883 .style("Heading2");
884 doc.add_bullet_list_item("GPU inference optimization (TensorRT/ONNX Runtime)", 0);
885 doc.add_bullet_list_item("Multi-region deployment for latency reduction", 0);
886 doc.add_bullet_list_item(
887 "Self-service model deployment portal for data science teams",
888 0,
889 );
890
891 // ── Budget ──
892 doc.add_paragraph("Budget Breakdown").style("Heading1");
893 {
894 let mut tbl = doc
895 .add_table(6, 3)
896 .borders(BorderStyle::Single, 4, "1B2A4A")
897 .width_pct(100.0);
898 tbl.cell(0, 0).unwrap().set_text("Category");
899 tbl.cell(0, 0).unwrap().shading("1B2A4A");
900 tbl.cell(0, 1).unwrap().set_text("Cost");
901 tbl.cell(0, 1).unwrap().shading("1B2A4A");
902 tbl.cell(0, 2).unwrap().set_text("Notes");
903 tbl.cell(0, 2).unwrap().shading("1B2A4A");
904 let items = [
905 (
906 "Infrastructure",
907 "$450,000",
908 "Cloud compute + GPU instances",
909 ),
910 ("Engineering Services", "$1,600,000", "12 FTE x 18 months"),
911 (
912 "Software Licenses",
913 "$350,000",
914 "Kafka, monitoring, CI/CD tools",
915 ),
916 (
917 "Training & Enablement",
918 "$200,000",
919 "Team training, documentation",
920 ),
921 ("Contingency (10%)", "$200,000", "Risk buffer"),
922 ];
923 for (i, (cat, cost, note)) in items.iter().enumerate() {
924 tbl.cell(i + 1, 0).unwrap().set_text(cat);
925 tbl.cell(i + 1, 1).unwrap().set_text(cost);
926 tbl.cell(i + 1, 2).unwrap().set_text(note);
927 if i % 2 == 0 {
928 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
929 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
930 tbl.cell(i + 1, 2).unwrap().shading("F4F1EB");
931 }
932 }
933 }
934
935 // ── Closing ──
936 doc.add_paragraph("");
937 doc.add_paragraph("Next Steps").style("Heading1");
938 doc.add_numbered_list_item(
939 "Schedule technical deep-dive with GFC engineering team (Week 1)",
940 0,
941 );
942 doc.add_numbered_list_item("Finalize scope and sign SOW (Week 2-3)", 0);
943 doc.add_numbered_list_item("Kick off Phase 1 with joint planning session (Week 4)", 0);
944
945 doc.add_paragraph("");
946 {
947 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
948 p.add_run("Walter White")
949 .bold(true)
950 .size(14.0)
951 .color("1B2A4A");
952 }
953 {
954 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
955 p.add_run("CEO, Tensorbee | walter@tensorbee.com")
956 .size(10.0)
957 .color("888888");
958 }
959
960 doc
961}
962
963// =============================================================================
964// 3. QUOTE / BILL OF MATERIALS — Teal + orange scheme
965// =============================================================================
966fn generate_quote(_samples_dir: &Path) -> Document {
967 let mut doc = Document::new();
968
969 // Colors: Teal #008B8B, Dark #1A3C3C, Orange #E07020, Light #F0F8F8
970 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
971 doc.set_margins(
972 Length::inches(0.75),
973 Length::inches(0.75),
974 Length::inches(0.75),
975 Length::inches(0.75),
976 );
977 doc.set_title("Quotation QT-2026-0147");
978 doc.set_author("Walter White");
979 doc.set_keywords("quote, BOM, Tensorbee");
980
981 doc.set_header("Tensorbee — Quotation");
982 doc.set_footer("QT-2026-0147 | Page");
983
984 // ── Header Block ──
985 {
986 let mut p = doc.add_paragraph("").alignment(Alignment::Left);
987 p.add_run("TENSORBEE")
988 .bold(true)
989 .size(28.0)
990 .color("008B8B")
991 .font("Helvetica");
992 }
993 doc.add_paragraph("123 Innovation Drive, Suite 400")
994 .alignment(Alignment::Left);
995 doc.add_paragraph("San Francisco, CA 94105 | +1 (415) 555-0199")
996 .alignment(Alignment::Left);
997 doc.add_paragraph("")
998 .border_bottom(BorderStyle::Single, 8, "008B8B");
999
1000 // Quote meta
1001 doc.add_paragraph("");
1002 {
1003 let mut tbl = doc.add_table(4, 4).width_pct(100.0);
1004 tbl.cell(0, 0).unwrap().set_text("QUOTATION");
1005 tbl.cell(0, 0).unwrap().shading("008B8B").grid_span(2);
1006 tbl.cell(0, 2).unwrap().set_text("DATE");
1007 tbl.cell(0, 2).unwrap().shading("008B8B");
1008 tbl.cell(0, 3).unwrap().set_text("VALID UNTIL");
1009 tbl.cell(0, 3).unwrap().shading("008B8B");
1010 tbl.cell(1, 0).unwrap().set_text("Quote #:");
1011 tbl.cell(1, 1).unwrap().set_text("QT-2026-0147");
1012 tbl.cell(1, 2).unwrap().set_text("Feb 22, 2026");
1013 tbl.cell(1, 3).unwrap().set_text("Mar 22, 2026");
1014 tbl.cell(2, 0).unwrap().set_text("Prepared by:");
1015 tbl.cell(2, 1).unwrap().set_text("Walter White");
1016 tbl.cell(2, 2).unwrap().set_text("Payment:");
1017 tbl.cell(2, 3).unwrap().set_text("Net 30");
1018 tbl.cell(3, 0).unwrap().set_text("Customer:");
1019 tbl.cell(3, 1).unwrap().set_text("Meridian Dynamics LLC");
1020 tbl.cell(3, 2).unwrap().set_text("Currency:");
1021 tbl.cell(3, 3).unwrap().set_text("USD");
1022 }
1023
1024 doc.add_paragraph("");
1025
1026 // ── Bill of Materials ──
1027 {
1028 let mut p = doc.add_paragraph("");
1029 p.add_run("Bill of Materials")
1030 .bold(true)
1031 .size(16.0)
1032 .color("1A3C3C");
1033 }
1034 doc.add_paragraph("");
1035
1036 {
1037 let mut tbl = doc
1038 .add_table(10, 6)
1039 .borders(BorderStyle::Single, 2, "008B8B")
1040 .width_pct(100.0)
1041 .layout_fixed();
1042 // Headers
1043 let hdrs = [
1044 "#",
1045 "Part Number",
1046 "Description",
1047 "Qty",
1048 "Unit Price",
1049 "Total",
1050 ];
1051 for (c, h) in hdrs.iter().enumerate() {
1052 tbl.cell(0, c).unwrap().set_text(h);
1053 tbl.cell(0, c).unwrap().shading("008B8B");
1054 }
1055
1056 let items: Vec<(&str, &str, &str, &str, &str)> = vec![
1057 (
1058 "TB-GPU-A100",
1059 "NVIDIA A100 80GB GPU",
1060 "4",
1061 "$12,500.00",
1062 "$50,000.00",
1063 ),
1064 (
1065 "TB-SRV-R750",
1066 "Dell R750xa Server Chassis",
1067 "2",
1068 "$8,200.00",
1069 "$16,400.00",
1070 ),
1071 (
1072 "TB-RAM-256G",
1073 "256GB DDR5 ECC Memory Module",
1074 "8",
1075 "$890.00",
1076 "$7,120.00",
1077 ),
1078 (
1079 "TB-SSD-3840",
1080 "3.84TB NVMe U.2 SSD",
1081 "8",
1082 "$1,150.00",
1083 "$9,200.00",
1084 ),
1085 (
1086 "TB-NET-CX7",
1087 "ConnectX-7 200GbE NIC",
1088 "4",
1089 "$1,800.00",
1090 "$7,200.00",
1091 ),
1092 (
1093 "TB-SW-48P",
1094 "48-Port 100GbE Switch",
1095 "1",
1096 "$22,000.00",
1097 "$22,000.00",
1098 ),
1099 (
1100 "TB-CAB-RACK",
1101 "42U Server Rack + PDU",
1102 "1",
1103 "$4,500.00",
1104 "$4,500.00",
1105 ),
1106 (
1107 "TB-SVC-INST",
1108 "Installation & Configuration",
1109 "1",
1110 "$8,500.00",
1111 "$8,500.00",
1112 ),
1113 (
1114 "TB-SVC-SUPP",
1115 "3-Year Premium Support",
1116 "1",
1117 "$15,000.00",
1118 "$15,000.00",
1119 ),
1120 ];
1121
1122 for (i, (pn, desc, qty, unit, total)) in items.iter().enumerate() {
1123 let row = i + 1;
1124 tbl.cell(row, 0).unwrap().set_text(&format!("{}", i + 1));
1125 tbl.cell(row, 1).unwrap().set_text(pn);
1126 tbl.cell(row, 2).unwrap().set_text(desc);
1127 tbl.cell(row, 3).unwrap().set_text(qty);
1128 tbl.cell(row, 4).unwrap().set_text(unit);
1129 tbl.cell(row, 5).unwrap().set_text(total);
1130 if i % 2 == 0 {
1131 for c in 0..6 {
1132 tbl.cell(row, c).unwrap().shading("F0F8F8");
1133 }
1134 }
1135 }
1136 }
1137
1138 doc.add_paragraph("");
1139
1140 // ── Totals ──
1141 {
1142 let mut tbl = doc
1143 .add_table(4, 2)
1144 .borders(BorderStyle::Single, 2, "008B8B")
1145 .width(Length::inches(3.5))
1146 .alignment(Alignment::Right);
1147 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1148 tbl.cell(0, 1).unwrap().set_text("$139,920.00");
1149 tbl.cell(1, 0).unwrap().set_text("Shipping & Handling");
1150 tbl.cell(1, 1).unwrap().set_text("$2,500.00");
1151 tbl.cell(2, 0).unwrap().set_text("Tax (8.625%)");
1152 tbl.cell(2, 1).unwrap().set_text("$12,068.10");
1153 tbl.cell(3, 0).unwrap().set_text("TOTAL");
1154 tbl.cell(3, 0).unwrap().shading("E07020");
1155 tbl.cell(3, 1).unwrap().set_text("$154,488.10");
1156 tbl.cell(3, 1).unwrap().shading("E07020");
1157 }
1158
1159 doc.add_paragraph("");
1160
1161 // ── Terms & Conditions ──
1162 {
1163 let mut p = doc.add_paragraph("");
1164 p.add_run("Terms & Conditions")
1165 .bold(true)
1166 .size(14.0)
1167 .color("1A3C3C");
1168 }
1169 doc.add_numbered_list_item(
1170 "This quotation is valid for 30 calendar days from the date of issue.",
1171 0,
1172 );
1173 doc.add_numbered_list_item(
1174 "All prices are in USD and exclusive of applicable taxes unless stated.",
1175 0,
1176 );
1177 doc.add_numbered_list_item("Standard lead time is 4-6 weeks from PO receipt.", 0);
1178 doc.add_numbered_list_item(
1179 "Payment terms: Net 30 from invoice date. 2% discount for payment within 10 days.",
1180 0,
1181 );
1182 doc.add_numbered_list_item(
1183 "Warranty: 3-year manufacturer warranty on all hardware components.",
1184 0,
1185 );
1186 doc.add_numbered_list_item(
1187 "Returns subject to 15% restocking fee if initiated after 14 days.",
1188 0,
1189 );
1190
1191 doc.add_paragraph("");
1192 doc.add_paragraph("")
1193 .border_bottom(BorderStyle::Single, 4, "008B8B");
1194 doc.add_paragraph("");
1195 {
1196 let mut p = doc.add_paragraph("");
1197 p.add_run("Accepted by: ").bold(true);
1198 p.add_run("___________________________ Date: ___________");
1199 }
1200 {
1201 let mut p = doc.add_paragraph("");
1202 p.add_run("Print Name: ").bold(true);
1203 p.add_run("___________________________ Title: ___________");
1204 }
1205
1206 doc
1207}
1208
1209// =============================================================================
1210// 4. INVOICE — Crimson + charcoal scheme
1211// =============================================================================
1212fn generate_invoice(_samples_dir: &Path) -> Document {
1213 let mut doc = Document::new();
1214
1215 // Colors: Crimson #B22222, Charcoal #333333, Light #FAF0F0
1216 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1217 doc.set_margins(
1218 Length::inches(0.75),
1219 Length::inches(0.75),
1220 Length::inches(0.75),
1221 Length::inches(0.75),
1222 );
1223 doc.set_title("Invoice INV-2026-0392");
1224 doc.set_author("Walter White");
1225 doc.set_keywords("invoice, Tensorbee");
1226
1227 doc.set_footer("Tensorbee — Thank you for your business!");
1228
1229 // ── Company & Invoice Header ──
1230 {
1231 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1232 // Company name left, INVOICE right
1233 {
1234 let mut cell = tbl.cell(0, 0).unwrap();
1235 let mut p = cell.add_paragraph("");
1236 p.add_run("TENSORBEE")
1237 .bold(true)
1238 .size(32.0)
1239 .color("B22222")
1240 .font("Helvetica");
1241 }
1242 {
1243 let mut cell = tbl.cell(0, 1).unwrap();
1244 let mut p = cell.add_paragraph("");
1245 p.add_run("INVOICE").bold(true).size(32.0).color("333333");
1246 }
1247 tbl.cell(1, 0)
1248 .unwrap()
1249 .set_text("123 Innovation Drive, Suite 400");
1250 tbl.cell(1, 1).unwrap().set_text("Invoice #: INV-2026-0392");
1251 tbl.cell(2, 0).unwrap().set_text("San Francisco, CA 94105");
1252 tbl.cell(2, 1).unwrap().set_text("Date: February 22, 2026");
1253 tbl.cell(3, 0).unwrap().set_text("walter@tensorbee.com");
1254 tbl.cell(3, 1).unwrap().set_text("Due Date: March 24, 2026");
1255 }
1256
1257 doc.add_paragraph("")
1258 .border_bottom(BorderStyle::Thick, 8, "B22222");
1259 doc.add_paragraph("");
1260
1261 // ── Bill To / Ship To ──
1262 {
1263 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1264 tbl.cell(0, 0).unwrap().set_text("BILL TO");
1265 tbl.cell(0, 0).unwrap().shading("B22222");
1266 tbl.cell(0, 1).unwrap().set_text("SHIP TO");
1267 tbl.cell(0, 1).unwrap().shading("B22222");
1268 tbl.cell(1, 0).unwrap().set_text("Meridian Dynamics LLC");
1269 tbl.cell(1, 1).unwrap().set_text("Meridian Dynamics LLC");
1270 tbl.cell(2, 0).unwrap().set_text("456 Enterprise Blvd");
1271 tbl.cell(2, 1).unwrap().set_text("Attn: Server Room B");
1272 tbl.cell(3, 0).unwrap().set_text("Austin, TX 78701");
1273 tbl.cell(3, 1)
1274 .unwrap()
1275 .set_text("456 Enterprise Blvd, Austin, TX 78701");
1276 }
1277
1278 doc.add_paragraph("");
1279
1280 // ── Line Items ──
1281 {
1282 let mut tbl = doc
1283 .add_table(8, 5)
1284 .borders(BorderStyle::Single, 2, "B22222")
1285 .width_pct(100.0);
1286 let hdrs = ["Description", "Qty", "Unit Price", "Tax", "Amount"];
1287 for (c, h) in hdrs.iter().enumerate() {
1288 tbl.cell(0, c).unwrap().set_text(h);
1289 tbl.cell(0, c).unwrap().shading("B22222");
1290 }
1291 let items = [
1292 (
1293 "ML Infrastructure Setup — Phase 1",
1294 "1",
1295 "$45,000.00",
1296 "$3,881.25",
1297 "$48,881.25",
1298 ),
1299 (
1300 "Data Pipeline Development (160 hrs)",
1301 "160",
1302 "$225.00",
1303 "$3,105.00",
1304 "$39,105.00",
1305 ),
1306 (
1307 "GPU Cluster Configuration",
1308 "1",
1309 "$12,000.00",
1310 "$1,035.00",
1311 "$13,035.00",
1312 ),
1313 (
1314 "API Gateway Implementation",
1315 "1",
1316 "$18,500.00",
1317 "$1,595.63",
1318 "$20,095.63",
1319 ),
1320 (
1321 "Load Testing & QA (80 hrs)",
1322 "80",
1323 "$195.00",
1324 "$1,345.50",
1325 "$16,945.50",
1326 ),
1327 (
1328 "Documentation & Training",
1329 "1",
1330 "$8,000.00",
1331 "$690.00",
1332 "$8,690.00",
1333 ),
1334 (
1335 "Project Management (3 months)",
1336 "3",
1337 "$6,500.00",
1338 "$1,679.63",
1339 "$21,179.63",
1340 ),
1341 ];
1342 for (i, (desc, qty, unit, tax, amt)) in items.iter().enumerate() {
1343 let r = i + 1;
1344 tbl.cell(r, 0).unwrap().set_text(desc);
1345 tbl.cell(r, 1).unwrap().set_text(qty);
1346 tbl.cell(r, 2).unwrap().set_text(unit);
1347 tbl.cell(r, 3).unwrap().set_text(tax);
1348 tbl.cell(r, 4).unwrap().set_text(amt);
1349 if i % 2 == 0 {
1350 for c in 0..5 {
1351 tbl.cell(r, c).unwrap().shading("FAF0F0");
1352 }
1353 }
1354 }
1355 }
1356
1357 doc.add_paragraph("");
1358
1359 // ── Totals ──
1360 {
1361 let mut tbl = doc
1362 .add_table(4, 2)
1363 .borders(BorderStyle::Single, 2, "B22222")
1364 .width(Length::inches(3.0))
1365 .alignment(Alignment::Right);
1366 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1367 tbl.cell(0, 1).unwrap().set_text("$154,600.00");
1368 tbl.cell(1, 0).unwrap().set_text("Tax (8.625%)");
1369 tbl.cell(1, 1).unwrap().set_text("$13,334.25");
1370 tbl.cell(2, 0).unwrap().set_text("Discount (5%)");
1371 tbl.cell(2, 1).unwrap().set_text("-$7,730.00");
1372 tbl.cell(3, 0).unwrap().set_text("AMOUNT DUE");
1373 tbl.cell(3, 0).unwrap().shading("B22222");
1374 tbl.cell(3, 1).unwrap().set_text("$160,204.25");
1375 tbl.cell(3, 1).unwrap().shading("B22222");
1376 }
1377
1378 doc.add_paragraph("");
1379 doc.add_paragraph("");
1380
1381 // ── Payment Details ──
1382 {
1383 let mut p = doc.add_paragraph("");
1384 p.add_run("Payment Details")
1385 .bold(true)
1386 .size(14.0)
1387 .color("333333");
1388 }
1389 doc.add_paragraph("Bank: Silicon Valley Bank")
1390 .indent_left(Length::inches(0.3));
1391 doc.add_paragraph("Account: Tensorbee Inc. — 0847-2953-1120")
1392 .indent_left(Length::inches(0.3));
1393 doc.add_paragraph("Routing: 121140399")
1394 .indent_left(Length::inches(0.3));
1395 doc.add_paragraph("Swift: SVBKUS6S")
1396 .indent_left(Length::inches(0.3));
1397
1398 doc.add_paragraph("");
1399
1400 doc.add_paragraph("Please include invoice number INV-2026-0392 in the payment reference.")
1401 .shading("FAF0F0")
1402 .border_all(BorderStyle::Single, 2, "B22222");
1403
1404 doc
1405}
1406
1407// =============================================================================
1408// 5. REPORT — Forest green + earth tones, with images & hierarchical sections
1409// =============================================================================
1410fn generate_report(_samples_dir: &Path) -> Document {
1411 let mut doc = Document::new();
1412
1413 // Colors: Forest #2D5016, Sage #6B8E23, Earth #8B7355, Cream #FFFAF0
1414 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1415 doc.set_margins(
1416 Length::inches(1.0),
1417 Length::inches(1.0),
1418 Length::inches(1.0),
1419 Length::inches(1.0),
1420 );
1421 doc.set_title("Q4 2025 Environmental Impact Report");
1422 doc.set_author("Walter White");
1423 doc.set_subject("Quarterly Environmental Report");
1424 doc.set_keywords("environment, sustainability, report, Tensorbee");
1425
1426 doc.set_different_first_page(true);
1427 doc.set_first_page_header("");
1428 doc.set_header("Tensorbee — Q4 2025 Environmental Impact Report");
1429 doc.set_footer("CONFIDENTIAL — Page");
1430
1431 // ── Cover ──
1432 let cover_bg = create_sample_png(612, 792, [20, 50, 15]);
1433 doc.add_background_image(&cover_bg, "report_cover.png");
1434
1435 for _ in 0..5 {
1436 doc.add_paragraph("");
1437 }
1438 {
1439 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1440 p.add_run("Q4 2025")
1441 .bold(true)
1442 .size(48.0)
1443 .color("FFFFFF")
1444 .font("Georgia");
1445 }
1446 {
1447 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1448 p.add_run("Environmental Impact Report")
1449 .size(24.0)
1450 .color("90EE90")
1451 .font("Georgia")
1452 .italic(true);
1453 }
1454 doc.add_paragraph("");
1455 {
1456 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1457 p.add_run("Tensorbee — Sustainability Division")
1458 .size(14.0)
1459 .color("C0C0C0");
1460 }
1461 {
1462 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1463 p.add_run("Prepared by Walter White, Chief Sustainability Officer")
1464 .size(11.0)
1465 .color("AAAAAA");
1466 }
1467
1468 // ── TOC ──
1469 doc.add_paragraph("").page_break_before(true);
1470 doc.insert_toc(doc.content_count(), 3);
1471
1472 // ── Section 1: Executive Overview ──
1473 doc.add_paragraph("").page_break_before(true);
1474 doc.add_paragraph("Executive Overview").style("Heading1");
1475 doc.add_paragraph(
1476 "This report presents Tensorbee's environmental performance for Q4 2025. Our \
1477 sustainability initiatives have yielded a 34% reduction in carbon emissions compared \
1478 to Q4 2024, exceeding our target of 25%. Key achievements include the transition to \
1479 100% renewable energy in our primary data centers and the launch of our carbon offset \
1480 marketplace.",
1481 )
1482 .first_line_indent(Length::inches(0.3));
1483
1484 // Image: performance chart
1485 let chart_img = create_chart_png(400, 200);
1486 doc.add_paragraph("");
1487 doc.add_picture(
1488 &chart_img,
1489 "performance_chart.png",
1490 Length::inches(5.0),
1491 Length::inches(2.5),
1492 )
1493 .alignment(Alignment::Center);
1494 {
1495 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1496 p.add_run("Figure 1: Quarterly Carbon Emissions (tonnes CO2e)")
1497 .italic(true)
1498 .size(9.0)
1499 .color("666666");
1500 }
1501
1502 // ── Section 2: Energy Consumption ──
1503 doc.add_paragraph("").page_break_before(true);
1504 doc.add_paragraph("Energy Consumption").style("Heading1");
1505
1506 doc.add_paragraph("Data Center Operations")
1507 .style("Heading2");
1508 doc.add_paragraph(
1509 "Our three primary data centers consumed a combined 4.2 GWh in Q4 2025, \
1510 a 12% reduction from Q3 2025 driven by improved cooling efficiency and \
1511 server consolidation.",
1512 );
1513
1514 // Data table
1515 {
1516 let mut tbl = doc
1517 .add_table(5, 4)
1518 .borders(BorderStyle::Single, 4, "2D5016")
1519 .width_pct(100.0);
1520 let hdrs = ["Data Center", "Capacity (MW)", "Usage (GWh)", "PUE"];
1521 for (c, h) in hdrs.iter().enumerate() {
1522 tbl.cell(0, c).unwrap().set_text(h);
1523 tbl.cell(0, c).unwrap().shading("2D5016");
1524 }
1525 let rows = [
1526 ("San Francisco (Primary)", "3.2", "1.8", "1.12"),
1527 ("Dublin (EU)", "2.1", "1.2", "1.18"),
1528 ("Singapore (APAC)", "1.8", "1.2", "1.24"),
1529 ("TOTAL", "7.1", "4.2", "1.17 avg"),
1530 ];
1531 for (i, (dc, cap, use_, pue)) in rows.iter().enumerate() {
1532 tbl.cell(i + 1, 0).unwrap().set_text(dc);
1533 tbl.cell(i + 1, 1).unwrap().set_text(cap);
1534 tbl.cell(i + 1, 2).unwrap().set_text(use_);
1535 tbl.cell(i + 1, 3).unwrap().set_text(pue);
1536 if i == 3 {
1537 for c in 0..4 {
1538 tbl.cell(i + 1, c).unwrap().shading("E2EFDA");
1539 }
1540 }
1541 }
1542 }
1543
1544 doc.add_paragraph("");
1545
1546 doc.add_paragraph("Renewable Energy Mix").style("Heading2");
1547 doc.add_paragraph("Breakdown of energy sources across all facilities:");
1548
1549 doc.add_bullet_list_item("Solar PV: 42% (1.76 GWh)", 0);
1550 doc.add_bullet_list_item("Wind Power Purchase Agreements: 38% (1.60 GWh)", 0);
1551 doc.add_bullet_list_item("Hydroelectric: 12% (0.50 GWh)", 0);
1552 doc.add_bullet_list_item("Grid (non-renewable): 8% (0.34 GWh)", 0);
1553
1554 // ── Section 3: Water & Waste ──
1555 doc.add_paragraph("Water & Waste Management")
1556 .style("Heading1");
1557
1558 doc.add_paragraph("Water Usage").style("Heading2");
1559 doc.add_paragraph(
1560 "Total water consumption was 12.4 million gallons, a 15% reduction from Q3. \
1561 Our closed-loop cooling systems now recycle 78% of water used in cooling operations.",
1562 );
1563
1564 doc.add_paragraph("Waste Reduction").style("Heading2");
1565 doc.add_paragraph("E-waste management results for Q4:")
1566 .keep_with_next(true);
1567 {
1568 let mut tbl = doc
1569 .add_table(4, 3)
1570 .borders(BorderStyle::Single, 2, "6B8E23")
1571 .width_pct(80.0);
1572 tbl.cell(0, 0).unwrap().set_text("Category");
1573 tbl.cell(0, 0).unwrap().shading("6B8E23");
1574 tbl.cell(0, 1).unwrap().set_text("Weight (kg)");
1575 tbl.cell(0, 1).unwrap().shading("6B8E23");
1576 tbl.cell(0, 2).unwrap().set_text("Recycled %");
1577 tbl.cell(0, 2).unwrap().shading("6B8E23");
1578 let rows = [
1579 ("Server Hardware", "2,450", "94%"),
1580 ("Networking Equipment", "820", "91%"),
1581 ("Storage Media", "340", "99%"),
1582 ];
1583 for (i, (cat, wt, pct)) in rows.iter().enumerate() {
1584 tbl.cell(i + 1, 0).unwrap().set_text(cat);
1585 tbl.cell(i + 1, 1).unwrap().set_text(wt);
1586 tbl.cell(i + 1, 2).unwrap().set_text(pct);
1587 }
1588 }
1589
1590 // ── Section 4: Initiatives ──
1591 doc.add_paragraph("").page_break_before(true);
1592 doc.add_paragraph("Q1 2026 Initiatives").style("Heading1");
1593
1594 doc.add_paragraph("Planned Programs").style("Heading2");
1595 doc.add_numbered_list_item(
1596 "Deploy on-site battery storage at SF data center (2 MWh capacity)",
1597 0,
1598 );
1599 doc.add_numbered_list_item("Pilot immersion cooling in Dublin facility", 0);
1600 doc.add_numbered_list_item("Launch employee commute carbon offset program", 0);
1601 doc.add_numbered_list_item("Achieve ISO 14001 certification for Singapore facility", 0);
1602
1603 doc.add_paragraph("Investment Targets").style("Heading2");
1604 doc.add_paragraph("Sustainability CapEx allocation for FY2026:")
1605 .keep_with_next(true);
1606 {
1607 let mut tbl = doc
1608 .add_table(5, 2)
1609 .borders(BorderStyle::Single, 4, "2D5016");
1610 tbl.cell(0, 0).unwrap().set_text("Initiative");
1611 tbl.cell(0, 0).unwrap().shading("2D5016");
1612 tbl.cell(0, 1).unwrap().set_text("Budget");
1613 tbl.cell(0, 1).unwrap().shading("2D5016");
1614 let rows = [
1615 ("Battery Storage", "$1.2M"),
1616 ("Immersion Cooling Pilot", "$800K"),
1617 ("Solar Panel Expansion", "$2.1M"),
1618 ("Carbon Credits", "$500K"),
1619 ];
1620 for (i, (init, budget)) in rows.iter().enumerate() {
1621 tbl.cell(i + 1, 0).unwrap().set_text(init);
1622 tbl.cell(i + 1, 1).unwrap().set_text(budget);
1623 if i % 2 == 0 {
1624 tbl.cell(i + 1, 0).unwrap().shading("FFFAF0");
1625 tbl.cell(i + 1, 1).unwrap().shading("FFFAF0");
1626 }
1627 }
1628 }
1629
1630 // Image: sustainability roadmap
1631 let roadmap_img = create_sample_png(500, 100, [40, 80, 30]);
1632 doc.add_paragraph("");
1633 doc.add_picture(
1634 &roadmap_img,
1635 "roadmap.png",
1636 Length::inches(6.0),
1637 Length::inches(1.2),
1638 )
1639 .alignment(Alignment::Center);
1640 {
1641 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1642 p.add_run("Figure 2: 2026 Sustainability Roadmap")
1643 .italic(true)
1644 .size(9.0)
1645 .color("666666");
1646 }
1647
1648 doc.add_paragraph("");
1649 doc.add_paragraph(
1650 "For questions about this report, contact Walter White at sustainability@tensorbee.com.",
1651 )
1652 .shading("FFFAF0")
1653 .border_all(BorderStyle::Single, 2, "2D5016");
1654
1655 doc
1656}Sourcepub fn replace_text(&mut self, placeholder: &str, replacement: &str) -> usize
pub fn replace_text(&mut self, placeholder: &str, replacement: &str) -> usize
Replace all occurrences of placeholder with replacement throughout the document.
Searches body paragraphs, tables (including nested), headers, footers, text boxes and chart labels. Handles placeholders split across multiple runs. Returns the total number of replacements made.
A replacement that contains placeholder is substituted once, not
repeatedly.
Sourcepub fn replace_all(&mut self, replacements: &HashMap<&str, &str>) -> usize
pub fn replace_all(&mut self, replacements: &HashMap<&str, &str>) -> usize
Replace multiple placeholders at once. Returns total replacements.
Cheaper than calling Self::replace_text per entry: the document is
serialised and re-parsed once for the whole batch rather than once per
placeholder.
Examples found in repository?
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
97fn generate_feature_showcase(_samples_dir: &Path) -> Document {
98 let mut doc = Document::new();
99
100 // ── Page Setup & Metadata ──
101 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
102 doc.set_margins(
103 Length::inches(1.0),
104 Length::inches(1.0),
105 Length::inches(1.0),
106 Length::inches(1.0),
107 );
108 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
109 doc.set_gutter(Length::twips(0));
110 doc.set_title("rdocx Feature Showcase");
111 doc.set_author("rdocx Sample Generator");
112 doc.set_subject("Comprehensive feature demonstration");
113 doc.set_keywords("rdocx, docx, rust, sample, showcase");
114
115 // Headers & Footers with different first page
116 doc.set_different_first_page(true);
117 doc.set_first_page_header("rdocx");
118 doc.set_first_page_footer("Feature Showcase — Cover Page");
119 doc.set_header("rdocx Feature Showcase");
120 doc.set_footer("Generated by rdocx");
121
122 // ── COVER PAGE ──
123 let bg = create_sample_png(612, 792, [20, 45, 90]);
124 doc.add_background_image(&bg, "cover_bg.png");
125
126 for _ in 0..3 {
127 doc.add_paragraph("");
128 }
129 {
130 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
131 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
132 }
133 {
134 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
135 p.add_run("Complete Feature Showcase")
136 .size(28.0)
137 .color("FFFFFF")
138 .italic(true);
139 }
140 doc.add_paragraph("");
141 {
142 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
143 p.add_run("Every feature of the rdocx Rust library")
144 .size(13.0)
145 .color("B0C4DE");
146 }
147 {
148 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
149 p.add_run("demonstrated in a single document.")
150 .size(13.0)
151 .color("B0C4DE");
152 }
153
154 // ── TABLE OF CONTENTS ──
155 doc.add_paragraph("").page_break_before(true);
156 doc.insert_toc(doc.content_count(), 3);
157
158 // ── SECTION 1: TEXT FORMATTING ──
159 doc.add_paragraph("").page_break_before(true);
160 doc.add_paragraph("1. Text Formatting").style("Heading1");
161
162 // Paragraph Alignment
163 doc.add_paragraph("1.1 Paragraph Alignment")
164 .style("Heading2");
165 doc.add_paragraph("Left-aligned paragraph (default).")
166 .alignment(Alignment::Left);
167 doc.add_paragraph("Center-aligned paragraph.")
168 .alignment(Alignment::Center);
169 doc.add_paragraph("Right-aligned paragraph.")
170 .alignment(Alignment::Right);
171 doc.add_paragraph(
172 "Justified paragraph — this text is long enough to demonstrate how justified alignment \
173 distributes extra space across word gaps so lines fill the full width of the text area.",
174 )
175 .alignment(Alignment::Justify);
176
177 doc.add_paragraph("");
178
179 // Run Formatting
180 doc.add_paragraph("1.2 Run Formatting").style("Heading2");
181 {
182 let mut p = doc.add_paragraph("");
183 p.add_run("Bold").bold(true);
184 p.add_run(" | ");
185 p.add_run("Italic").italic(true);
186 p.add_run(" | ");
187 p.add_run("Bold Italic").bold(true).italic(true);
188 p.add_run(" | ");
189 p.add_run("Underline").underline(true);
190 p.add_run(" | ");
191 p.add_run("Strikethrough").strike(true);
192 p.add_run(" | ");
193 p.add_run("Double Strike").double_strike(true);
194 }
195 {
196 let mut p = doc.add_paragraph("");
197 p.add_run("Red").color("FF0000");
198 p.add_run(" | ");
199 p.add_run("Blue").color("0000FF");
200 p.add_run(" | ");
201 p.add_run("Green").color("00AA00");
202 p.add_run(" | ");
203 p.add_run("Highlighted").highlight("FFFF00");
204 }
205 {
206 let mut p = doc.add_paragraph("");
207 p.add_run("8pt").size(8.0);
208 p.add_run(" | ");
209 p.add_run("11pt").size(11.0);
210 p.add_run(" | ");
211 p.add_run("16pt").size(16.0);
212 p.add_run(" | ");
213 p.add_run("24pt").size(24.0);
214 }
215 {
216 let mut p = doc.add_paragraph("");
217 p.add_run("Arial").font("Arial");
218 p.add_run(" | ");
219 p.add_run("Times New Roman").font("Times New Roman");
220 p.add_run(" | ");
221 p.add_run("Courier New").font("Courier New");
222 }
223 {
224 let mut p = doc.add_paragraph("");
225 p.add_run("H");
226 p.add_run("2").subscript();
227 p.add_run("O (subscript) | E=mc");
228 p.add_run("2").superscript();
229 p.add_run(" (superscript)");
230 }
231 {
232 let mut p = doc.add_paragraph("");
233 p.add_run("ALL CAPS").all_caps(true);
234 p.add_run(" | ");
235 p.add_run("Small Caps").small_caps(true);
236 p.add_run(" | ");
237 p.add_run("Expanded").character_spacing(Length::twips(40));
238 p.add_run(" | ");
239 p.add_run("Hidden text (not visible)").hidden(true);
240 }
241
242 doc.add_paragraph("");
243
244 // Underline Styles
245 doc.add_paragraph("1.3 Underline Styles").style("Heading2");
246 {
247 let mut p = doc.add_paragraph("");
248 p.add_run("Single ")
249 .underline_style(rdocx::UnderlineStyle::Single);
250 p.add_run("Double ")
251 .underline_style(rdocx::UnderlineStyle::Double);
252 p.add_run("Thick ")
253 .underline_style(rdocx::UnderlineStyle::Thick);
254 p.add_run("Dotted ")
255 .underline_style(rdocx::UnderlineStyle::Dotted);
256 p.add_run("Dash ")
257 .underline_style(rdocx::UnderlineStyle::Dash);
258 p.add_run("Wave ")
259 .underline_style(rdocx::UnderlineStyle::Wave);
260 }
261
262 // ── SECTION 2: PARAGRAPH FORMATTING ──
263 doc.add_paragraph("").page_break_before(true);
264 doc.add_paragraph("2. Paragraph Formatting")
265 .style("Heading1");
266
267 doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
268 doc.add_paragraph("Paragraph with light green shading")
269 .shading("E2EFDA");
270 doc.add_paragraph("Paragraph with bottom border")
271 .border_bottom(BorderStyle::Single, 6, "2E75B6");
272 doc.add_paragraph("Paragraph with all borders (red)")
273 .border_all(BorderStyle::Single, 4, "FF0000");
274
275 doc.add_paragraph("2.2 Indentation").style("Heading2");
276 doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
277 .indent_left(Length::inches(1.0))
278 .hanging_indent(Length::inches(0.5));
279 doc.add_paragraph("0.5-inch first-line indent on this paragraph")
280 .first_line_indent(Length::inches(0.5));
281 doc.add_paragraph("Both left and right indent (0.75 inches each)")
282 .indent_left(Length::inches(0.75))
283 .indent_right(Length::inches(0.75));
284
285 doc.add_paragraph("2.3 Spacing & Line Height")
286 .style("Heading2");
287 doc.add_paragraph("Extra space before (24pt) and after (12pt)")
288 .space_before(Length::pt(24.0))
289 .space_after(Length::pt(12.0));
290 doc.add_paragraph(
291 "Double line spacing paragraph. This text should have extra vertical space between \
292 lines to demonstrate line_spacing_multiple(2.0).",
293 )
294 .line_spacing_multiple(2.0);
295 doc.add_paragraph("Exact 20pt line spacing (fixed height).")
296 .line_spacing(20.0);
297
298 doc.add_paragraph("2.4 Pagination Controls")
299 .style("Heading2");
300 doc.add_paragraph("keep_with_next — this paragraph stays with the next")
301 .keep_with_next(true);
302 doc.add_paragraph("(This paragraph was kept with the one above.)");
303 doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
304 .keep_together(true);
305 doc.add_paragraph("widow_control — prevents widow/orphan lines")
306 .widow_control(true);
307
308 // ── SECTION 3: LISTS ──
309 doc.add_paragraph("").page_break_before(true);
310 doc.add_paragraph("3. Lists").style("Heading1");
311
312 doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
313 doc.add_bullet_list_item("First item", 0);
314 doc.add_bullet_list_item("Second item", 0);
315 doc.add_bullet_list_item("Nested level 1", 1);
316 doc.add_bullet_list_item("Nested level 2", 2);
317 doc.add_bullet_list_item("Back to level 1", 1);
318 doc.add_bullet_list_item("Third item", 0);
319
320 doc.add_paragraph("");
321
322 doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
323 doc.add_numbered_list_item("First numbered item", 0);
324 doc.add_numbered_list_item("Second numbered item", 0);
325 doc.add_numbered_list_item("Sub-item A", 1);
326 doc.add_numbered_list_item("Sub-item B", 1);
327 doc.add_numbered_list_item("Third numbered item", 0);
328
329 // ── SECTION 4: TAB STOPS ──
330 doc.add_paragraph("");
331 doc.add_paragraph("4. Tab Stops").style("Heading1");
332
333 doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
334 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
335 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
336 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
337 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
338 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
339
340 doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
341 doc.add_paragraph("Item\t\tPrice")
342 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
343 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
344 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
345 doc.add_paragraph("Widget\t\t$19.99")
346 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
347 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
348 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
349 doc.add_paragraph("Gadget\t\t$249.50")
350 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
351 .add_tab_stop_with_leader(
352 TabAlignment::Right,
353 Length::inches(4.0),
354 TabLeader::Underscore,
355 )
356 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
357
358 // ── SECTION 5: TABLES ──
359 doc.add_paragraph("").page_break_before(true);
360 doc.add_paragraph("5. Tables").style("Heading1");
361
362 doc.add_paragraph("5.1 Basic Table").style("Heading2");
363 {
364 let mut tbl = doc
365 .add_table(4, 3)
366 .borders(BorderStyle::Single, 4, "000000");
367 for col in 0..3 {
368 tbl.cell(0, col).unwrap().shading("2E75B6");
369 }
370 tbl.cell(0, 0).unwrap().set_text("Name");
371 tbl.cell(0, 1).unwrap().set_text("Role");
372 tbl.cell(0, 2).unwrap().set_text("Location");
373 tbl.cell(1, 0).unwrap().set_text("Walter White");
374 tbl.cell(1, 1).unwrap().set_text("CEO");
375 tbl.cell(1, 2).unwrap().set_text("Albuquerque");
376 tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
377 tbl.cell(2, 1).unwrap().set_text("CTO");
378 tbl.cell(2, 2).unwrap().set_text("Remote");
379 tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
380 tbl.cell(3, 1).unwrap().set_text("Security");
381 tbl.cell(3, 2).unwrap().set_text("Washington");
382 }
383
384 doc.add_paragraph("");
385
386 doc.add_paragraph("5.2 Column Span & Cell Shading")
387 .style("Heading2");
388 {
389 let mut tbl = doc
390 .add_table(3, 4)
391 .borders(BorderStyle::Single, 4, "000000")
392 .width_pct(100.0);
393 tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
394 tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
395 tbl.cell(1, 0).unwrap().set_text("Region");
396 tbl.cell(1, 0).unwrap().shading("D6E4F0");
397 tbl.cell(1, 1).unwrap().set_text("Q1");
398 tbl.cell(1, 1).unwrap().shading("D6E4F0");
399 tbl.cell(1, 2).unwrap().set_text("Q2");
400 tbl.cell(1, 2).unwrap().shading("D6E4F0");
401 tbl.cell(1, 3).unwrap().set_text("Total");
402 tbl.cell(1, 3).unwrap().shading("D6E4F0");
403 tbl.cell(2, 0).unwrap().set_text("Americas");
404 tbl.cell(2, 1).unwrap().set_text("$2.4M");
405 tbl.cell(2, 2).unwrap().set_text("$2.7M");
406 tbl.cell(2, 3).unwrap().set_text("$5.1M");
407 }
408
409 doc.add_paragraph("");
410
411 doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
412 {
413 let mut tbl = doc
414 .add_table(4, 3)
415 .borders(BorderStyle::Single, 4, "000000");
416 tbl.cell(0, 0).unwrap().set_text("Category");
417 tbl.cell(0, 0).unwrap().shading("E2EFDA");
418 tbl.cell(0, 1).unwrap().set_text("Item");
419 tbl.cell(0, 1).unwrap().shading("E2EFDA");
420 tbl.cell(0, 2).unwrap().set_text("Price");
421 tbl.cell(0, 2).unwrap().shading("E2EFDA");
422 tbl.cell(1, 0).unwrap().set_text("Hardware");
423 tbl.cell(1, 0).unwrap().v_merge_restart();
424 tbl.cell(1, 1).unwrap().set_text("Laptop");
425 tbl.cell(1, 2).unwrap().set_text("$1,200");
426 tbl.cell(2, 0).unwrap().v_merge_continue();
427 tbl.cell(2, 1).unwrap().set_text("Monitor");
428 tbl.cell(2, 2).unwrap().set_text("$450");
429 tbl.cell(3, 0).unwrap().set_text("Software");
430 tbl.cell(3, 1).unwrap().set_text("IDE License");
431 tbl.cell(3, 2).unwrap().set_text("$200/yr");
432 }
433
434 doc.add_paragraph("");
435
436 doc.add_paragraph("5.4 Nested Table").style("Heading2");
437 {
438 let mut tbl = doc
439 .add_table(2, 2)
440 .borders(BorderStyle::Single, 6, "2E75B6");
441 tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
442 tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
443 tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
444 {
445 let mut cell = tbl.cell(1, 1).unwrap();
446 cell.set_text("Nested table below:");
447 let mut nested = cell
448 .add_table(2, 2)
449 .borders(BorderStyle::Single, 2, "FF6600");
450 nested.cell(0, 0).unwrap().set_text("A");
451 nested.cell(0, 1).unwrap().set_text("B");
452 nested.cell(1, 0).unwrap().set_text("C");
453 nested.cell(1, 1).unwrap().set_text("D");
454 }
455 }
456
457 doc.add_paragraph("");
458
459 doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
460 .style("Heading2");
461 {
462 let mut tbl = doc
463 .add_table(2, 3)
464 .borders(BorderStyle::Single, 4, "666666");
465 tbl.row(0).unwrap().height(Length::pt(50.0)).header();
466 tbl.cell(0, 0).unwrap().set_text("Top");
467 tbl.cell(0, 0)
468 .unwrap()
469 .vertical_alignment(VerticalAlignment::Top)
470 .shading("FFF2CC");
471 tbl.cell(0, 1).unwrap().set_text("Center");
472 tbl.cell(0, 1)
473 .unwrap()
474 .vertical_alignment(VerticalAlignment::Center)
475 .shading("D9E2F3");
476 tbl.cell(0, 2).unwrap().set_text("Bottom");
477 tbl.cell(0, 2)
478 .unwrap()
479 .vertical_alignment(VerticalAlignment::Bottom)
480 .shading("E2EFDA");
481 tbl.row(1).unwrap().cant_split();
482 tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
483 tbl.cell(1, 0).unwrap().no_wrap();
484 tbl.cell(1, 1).unwrap().set_text("Fixed width");
485 tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
486 tbl.cell(1, 2).unwrap().set_text("Normal");
487 }
488
489 // ── SECTION 6: IMAGES ──
490 doc.add_paragraph("").page_break_before(true);
491 doc.add_paragraph("6. Images").style("Heading1");
492
493 doc.add_paragraph("6.1 Inline Image").style("Heading2");
494 doc.add_paragraph("A blue gradient image below:");
495 let img = create_sample_png(200, 50, [0, 80, 200]);
496 doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
497
498 doc.add_paragraph("");
499 doc.add_paragraph("6.2 Header Image").style("Heading2");
500 let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
501 doc.set_header_image(
502 &hdr_img,
503 "header_logo.png",
504 Length::inches(2.0),
505 Length::inches(0.2),
506 );
507 doc.add_paragraph("The header now contains an inline image (check the top of this page).");
508
509 doc.add_paragraph("");
510 doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
511
512 // ── SECTION 7: CONTENT MANIPULATION ──
513 doc.add_paragraph("").page_break_before(true);
514 doc.add_paragraph("7. Content Manipulation")
515 .style("Heading1");
516
517 doc.add_paragraph("7.1 Placeholder Replacement")
518 .style("Heading2");
519 doc.add_paragraph("Customer: {{customer}}");
520 doc.add_paragraph("Date: {{date}}");
521 doc.add_paragraph("Reference: {{ref_number}}");
522 {
523 let mut tbl = doc
524 .add_table(2, 2)
525 .borders(BorderStyle::Single, 4, "000000");
526 tbl.cell(0, 0).unwrap().set_text("Project");
527 tbl.cell(0, 0).unwrap().shading("D6E4F0");
528 tbl.cell(0, 1).unwrap().set_text("{{project}}");
529 tbl.cell(1, 0).unwrap().set_text("Status");
530 tbl.cell(1, 0).unwrap().shading("D6E4F0");
531 tbl.cell(1, 1).unwrap().set_text("{{status}}");
532 }
533
534 let mut replacements = HashMap::new();
535 replacements.insert("{{customer}}", "Tensorbee Inc.");
536 replacements.insert("{{date}}", "February 22, 2026");
537 replacements.insert("{{ref_number}}", "TB-2026-001");
538 replacements.insert("{{project}}", "Infrastructure Upgrade");
539 replacements.insert("{{status}}", "In Progress");
540 let n = doc.replace_all(&replacements);
541 doc.add_paragraph(&format!("({n} placeholders replaced above)"));
542
543 doc.add_paragraph("");
544
545 doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
546 doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
547 let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
548 doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
549
550 doc.add_paragraph("");
551
552 doc.add_paragraph("7.3 Content Insertion").style("Heading2");
553 doc.add_paragraph("Section A: First content.");
554 doc.add_paragraph("Section C: Third content.");
555 if let Some(idx) = doc.find_content_index("Section C") {
556 doc.insert_paragraph(
557 idx,
558 "Section B: Inserted between A and C via find_content_index().",
559 );
560 }
561
562 // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
563 doc.add_paragraph("").section_break(SectionBreak::NextPage);
564 doc.add_paragraph("8. Section Breaks & Orientation")
565 .style("Heading1");
566 doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
567
568 {
569 let mut tbl = doc
570 .add_table(3, 7)
571 .borders(BorderStyle::Single, 4, "2E75B6");
572 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
573 for (c, h) in headers.iter().enumerate() {
574 tbl.cell(0, c).unwrap().set_text(h);
575 tbl.cell(0, c).unwrap().shading("2E75B6");
576 }
577 let data = [
578 [
579 "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
580 ],
581 [
582 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
583 ],
584 ];
585 for (r, row) in data.iter().enumerate() {
586 for (c, v) in row.iter().enumerate() {
587 tbl.cell(r + 1, c).unwrap().set_text(v);
588 }
589 }
590 }
591
592 doc.add_paragraph("")
593 .section_break(SectionBreak::NextPage)
594 .section_landscape();
595
596 // ── SECTION 9: CUSTOM STYLES ──
597 doc.add_paragraph("9. Custom Styles").style("Heading1");
598 doc.add_style(
599 StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
600 .based_on("Normal")
601 .paragraph_properties(rdocx_oxml::properties::CT_PPr {
602 shading: Some(rdocx_oxml::properties::CT_Shd {
603 val: "clear".to_string(),
604 color: None,
605 fill: Some("FFF2CC".to_string()),
606 }),
607 ..Default::default()
608 })
609 .run_properties(rdocx_oxml::properties::CT_RPr {
610 bold: Some(true),
611 color: Some("C45911".to_string()),
612 ..Default::default()
613 }),
614 );
615 doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
616 .style("CustomHighlight");
617
618 doc.add_paragraph("");
619
620 // ── SECTION 10: DOCUMENT INTELLIGENCE ──
621 doc.add_paragraph("10. Document Intelligence API")
622 .style("Heading1");
623
624 let wc = doc.word_count();
625 let hc = doc.headings().len();
626 let ic = doc.images().len();
627 let lc = doc.links().len();
628 doc.add_paragraph(&format!("Word count: {wc}"));
629 doc.add_paragraph(&format!("Heading count: {hc}"));
630 doc.add_paragraph(&format!("Image count: {ic}"));
631 doc.add_paragraph(&format!("Link count: {lc}"));
632
633 let outline = doc.document_outline();
634 doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
635
636 let issues = doc.audit_accessibility();
637 doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
638 for issue in &issues {
639 doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
640 }
641
642 // ── SECTION 11: DOCUMENT MERGING ──
643 doc.add_paragraph("");
644 doc.add_paragraph("11. Document Merging").style("Heading1");
645 {
646 let mut other = Document::new();
647 other.add_paragraph("This paragraph was merged from another document using append().");
648 doc.append(&other);
649 }
650
651 doc.add_paragraph("");
652
653 // ── SUMMARY ──
654 doc.add_paragraph("Summary of Demonstrated Features")
655 .style("Heading1");
656 let features = [
657 "Page setup: size, margins, header/footer distance, gutter",
658 "Document metadata: title, author, subject, keywords",
659 "Headers and footers: text, images, different first page",
660 "Background images (full-page behind text)",
661 "Table of Contents generation with bookmarks",
662 "Text formatting: bold, italic, underline styles, strike, color, size, font",
663 "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
664 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
665 "Pagination controls: keep-with-next, keep-together, widow control",
666 "Bullet and numbered lists with nesting",
667 "Tab stops with dot/underscore/hyphen leaders",
668 "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
669 "Row properties: header rows, exact height, cant-split",
670 "Cell properties: width, no-wrap, vertical alignment",
671 "Inline images and header images",
672 "Placeholder replacement (single and batch)",
673 "Regex-based find and replace",
674 "Content insertion at specific positions",
675 "Section breaks with mixed portrait/landscape",
676 "Custom paragraph and character styles",
677 "Document intelligence: word count, headings, outline, images, links",
678 "Accessibility audit",
679 "Document merging (append)",
680 "Export: DOCX, PDF, HTML, Markdown",
681 ];
682 for f in &features {
683 doc.add_bullet_list_item(f, 0);
684 }
685 doc.add_paragraph("");
686 doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
687 .alignment(Alignment::Center)
688 .shading("E2EFDA")
689 .border_all(BorderStyle::Single, 2, "00AA00");
690
691 doc
692}Sourcepub fn replace_regex(
&mut self,
pattern: &str,
replacement: &str,
) -> Result<usize>
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?
97fn generate_feature_showcase(_samples_dir: &Path) -> Document {
98 let mut doc = Document::new();
99
100 // ── Page Setup & Metadata ──
101 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
102 doc.set_margins(
103 Length::inches(1.0),
104 Length::inches(1.0),
105 Length::inches(1.0),
106 Length::inches(1.0),
107 );
108 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
109 doc.set_gutter(Length::twips(0));
110 doc.set_title("rdocx Feature Showcase");
111 doc.set_author("rdocx Sample Generator");
112 doc.set_subject("Comprehensive feature demonstration");
113 doc.set_keywords("rdocx, docx, rust, sample, showcase");
114
115 // Headers & Footers with different first page
116 doc.set_different_first_page(true);
117 doc.set_first_page_header("rdocx");
118 doc.set_first_page_footer("Feature Showcase — Cover Page");
119 doc.set_header("rdocx Feature Showcase");
120 doc.set_footer("Generated by rdocx");
121
122 // ── COVER PAGE ──
123 let bg = create_sample_png(612, 792, [20, 45, 90]);
124 doc.add_background_image(&bg, "cover_bg.png");
125
126 for _ in 0..3 {
127 doc.add_paragraph("");
128 }
129 {
130 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
131 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
132 }
133 {
134 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
135 p.add_run("Complete Feature Showcase")
136 .size(28.0)
137 .color("FFFFFF")
138 .italic(true);
139 }
140 doc.add_paragraph("");
141 {
142 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
143 p.add_run("Every feature of the rdocx Rust library")
144 .size(13.0)
145 .color("B0C4DE");
146 }
147 {
148 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
149 p.add_run("demonstrated in a single document.")
150 .size(13.0)
151 .color("B0C4DE");
152 }
153
154 // ── TABLE OF CONTENTS ──
155 doc.add_paragraph("").page_break_before(true);
156 doc.insert_toc(doc.content_count(), 3);
157
158 // ── SECTION 1: TEXT FORMATTING ──
159 doc.add_paragraph("").page_break_before(true);
160 doc.add_paragraph("1. Text Formatting").style("Heading1");
161
162 // Paragraph Alignment
163 doc.add_paragraph("1.1 Paragraph Alignment")
164 .style("Heading2");
165 doc.add_paragraph("Left-aligned paragraph (default).")
166 .alignment(Alignment::Left);
167 doc.add_paragraph("Center-aligned paragraph.")
168 .alignment(Alignment::Center);
169 doc.add_paragraph("Right-aligned paragraph.")
170 .alignment(Alignment::Right);
171 doc.add_paragraph(
172 "Justified paragraph — this text is long enough to demonstrate how justified alignment \
173 distributes extra space across word gaps so lines fill the full width of the text area.",
174 )
175 .alignment(Alignment::Justify);
176
177 doc.add_paragraph("");
178
179 // Run Formatting
180 doc.add_paragraph("1.2 Run Formatting").style("Heading2");
181 {
182 let mut p = doc.add_paragraph("");
183 p.add_run("Bold").bold(true);
184 p.add_run(" | ");
185 p.add_run("Italic").italic(true);
186 p.add_run(" | ");
187 p.add_run("Bold Italic").bold(true).italic(true);
188 p.add_run(" | ");
189 p.add_run("Underline").underline(true);
190 p.add_run(" | ");
191 p.add_run("Strikethrough").strike(true);
192 p.add_run(" | ");
193 p.add_run("Double Strike").double_strike(true);
194 }
195 {
196 let mut p = doc.add_paragraph("");
197 p.add_run("Red").color("FF0000");
198 p.add_run(" | ");
199 p.add_run("Blue").color("0000FF");
200 p.add_run(" | ");
201 p.add_run("Green").color("00AA00");
202 p.add_run(" | ");
203 p.add_run("Highlighted").highlight("FFFF00");
204 }
205 {
206 let mut p = doc.add_paragraph("");
207 p.add_run("8pt").size(8.0);
208 p.add_run(" | ");
209 p.add_run("11pt").size(11.0);
210 p.add_run(" | ");
211 p.add_run("16pt").size(16.0);
212 p.add_run(" | ");
213 p.add_run("24pt").size(24.0);
214 }
215 {
216 let mut p = doc.add_paragraph("");
217 p.add_run("Arial").font("Arial");
218 p.add_run(" | ");
219 p.add_run("Times New Roman").font("Times New Roman");
220 p.add_run(" | ");
221 p.add_run("Courier New").font("Courier New");
222 }
223 {
224 let mut p = doc.add_paragraph("");
225 p.add_run("H");
226 p.add_run("2").subscript();
227 p.add_run("O (subscript) | E=mc");
228 p.add_run("2").superscript();
229 p.add_run(" (superscript)");
230 }
231 {
232 let mut p = doc.add_paragraph("");
233 p.add_run("ALL CAPS").all_caps(true);
234 p.add_run(" | ");
235 p.add_run("Small Caps").small_caps(true);
236 p.add_run(" | ");
237 p.add_run("Expanded").character_spacing(Length::twips(40));
238 p.add_run(" | ");
239 p.add_run("Hidden text (not visible)").hidden(true);
240 }
241
242 doc.add_paragraph("");
243
244 // Underline Styles
245 doc.add_paragraph("1.3 Underline Styles").style("Heading2");
246 {
247 let mut p = doc.add_paragraph("");
248 p.add_run("Single ")
249 .underline_style(rdocx::UnderlineStyle::Single);
250 p.add_run("Double ")
251 .underline_style(rdocx::UnderlineStyle::Double);
252 p.add_run("Thick ")
253 .underline_style(rdocx::UnderlineStyle::Thick);
254 p.add_run("Dotted ")
255 .underline_style(rdocx::UnderlineStyle::Dotted);
256 p.add_run("Dash ")
257 .underline_style(rdocx::UnderlineStyle::Dash);
258 p.add_run("Wave ")
259 .underline_style(rdocx::UnderlineStyle::Wave);
260 }
261
262 // ── SECTION 2: PARAGRAPH FORMATTING ──
263 doc.add_paragraph("").page_break_before(true);
264 doc.add_paragraph("2. Paragraph Formatting")
265 .style("Heading1");
266
267 doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
268 doc.add_paragraph("Paragraph with light green shading")
269 .shading("E2EFDA");
270 doc.add_paragraph("Paragraph with bottom border")
271 .border_bottom(BorderStyle::Single, 6, "2E75B6");
272 doc.add_paragraph("Paragraph with all borders (red)")
273 .border_all(BorderStyle::Single, 4, "FF0000");
274
275 doc.add_paragraph("2.2 Indentation").style("Heading2");
276 doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
277 .indent_left(Length::inches(1.0))
278 .hanging_indent(Length::inches(0.5));
279 doc.add_paragraph("0.5-inch first-line indent on this paragraph")
280 .first_line_indent(Length::inches(0.5));
281 doc.add_paragraph("Both left and right indent (0.75 inches each)")
282 .indent_left(Length::inches(0.75))
283 .indent_right(Length::inches(0.75));
284
285 doc.add_paragraph("2.3 Spacing & Line Height")
286 .style("Heading2");
287 doc.add_paragraph("Extra space before (24pt) and after (12pt)")
288 .space_before(Length::pt(24.0))
289 .space_after(Length::pt(12.0));
290 doc.add_paragraph(
291 "Double line spacing paragraph. This text should have extra vertical space between \
292 lines to demonstrate line_spacing_multiple(2.0).",
293 )
294 .line_spacing_multiple(2.0);
295 doc.add_paragraph("Exact 20pt line spacing (fixed height).")
296 .line_spacing(20.0);
297
298 doc.add_paragraph("2.4 Pagination Controls")
299 .style("Heading2");
300 doc.add_paragraph("keep_with_next — this paragraph stays with the next")
301 .keep_with_next(true);
302 doc.add_paragraph("(This paragraph was kept with the one above.)");
303 doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
304 .keep_together(true);
305 doc.add_paragraph("widow_control — prevents widow/orphan lines")
306 .widow_control(true);
307
308 // ── SECTION 3: LISTS ──
309 doc.add_paragraph("").page_break_before(true);
310 doc.add_paragraph("3. Lists").style("Heading1");
311
312 doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
313 doc.add_bullet_list_item("First item", 0);
314 doc.add_bullet_list_item("Second item", 0);
315 doc.add_bullet_list_item("Nested level 1", 1);
316 doc.add_bullet_list_item("Nested level 2", 2);
317 doc.add_bullet_list_item("Back to level 1", 1);
318 doc.add_bullet_list_item("Third item", 0);
319
320 doc.add_paragraph("");
321
322 doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
323 doc.add_numbered_list_item("First numbered item", 0);
324 doc.add_numbered_list_item("Second numbered item", 0);
325 doc.add_numbered_list_item("Sub-item A", 1);
326 doc.add_numbered_list_item("Sub-item B", 1);
327 doc.add_numbered_list_item("Third numbered item", 0);
328
329 // ── SECTION 4: TAB STOPS ──
330 doc.add_paragraph("");
331 doc.add_paragraph("4. Tab Stops").style("Heading1");
332
333 doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
334 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
335 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
336 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
337 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
338 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
339
340 doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
341 doc.add_paragraph("Item\t\tPrice")
342 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
343 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
344 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
345 doc.add_paragraph("Widget\t\t$19.99")
346 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
347 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
348 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
349 doc.add_paragraph("Gadget\t\t$249.50")
350 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
351 .add_tab_stop_with_leader(
352 TabAlignment::Right,
353 Length::inches(4.0),
354 TabLeader::Underscore,
355 )
356 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
357
358 // ── SECTION 5: TABLES ──
359 doc.add_paragraph("").page_break_before(true);
360 doc.add_paragraph("5. Tables").style("Heading1");
361
362 doc.add_paragraph("5.1 Basic Table").style("Heading2");
363 {
364 let mut tbl = doc
365 .add_table(4, 3)
366 .borders(BorderStyle::Single, 4, "000000");
367 for col in 0..3 {
368 tbl.cell(0, col).unwrap().shading("2E75B6");
369 }
370 tbl.cell(0, 0).unwrap().set_text("Name");
371 tbl.cell(0, 1).unwrap().set_text("Role");
372 tbl.cell(0, 2).unwrap().set_text("Location");
373 tbl.cell(1, 0).unwrap().set_text("Walter White");
374 tbl.cell(1, 1).unwrap().set_text("CEO");
375 tbl.cell(1, 2).unwrap().set_text("Albuquerque");
376 tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
377 tbl.cell(2, 1).unwrap().set_text("CTO");
378 tbl.cell(2, 2).unwrap().set_text("Remote");
379 tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
380 tbl.cell(3, 1).unwrap().set_text("Security");
381 tbl.cell(3, 2).unwrap().set_text("Washington");
382 }
383
384 doc.add_paragraph("");
385
386 doc.add_paragraph("5.2 Column Span & Cell Shading")
387 .style("Heading2");
388 {
389 let mut tbl = doc
390 .add_table(3, 4)
391 .borders(BorderStyle::Single, 4, "000000")
392 .width_pct(100.0);
393 tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
394 tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
395 tbl.cell(1, 0).unwrap().set_text("Region");
396 tbl.cell(1, 0).unwrap().shading("D6E4F0");
397 tbl.cell(1, 1).unwrap().set_text("Q1");
398 tbl.cell(1, 1).unwrap().shading("D6E4F0");
399 tbl.cell(1, 2).unwrap().set_text("Q2");
400 tbl.cell(1, 2).unwrap().shading("D6E4F0");
401 tbl.cell(1, 3).unwrap().set_text("Total");
402 tbl.cell(1, 3).unwrap().shading("D6E4F0");
403 tbl.cell(2, 0).unwrap().set_text("Americas");
404 tbl.cell(2, 1).unwrap().set_text("$2.4M");
405 tbl.cell(2, 2).unwrap().set_text("$2.7M");
406 tbl.cell(2, 3).unwrap().set_text("$5.1M");
407 }
408
409 doc.add_paragraph("");
410
411 doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
412 {
413 let mut tbl = doc
414 .add_table(4, 3)
415 .borders(BorderStyle::Single, 4, "000000");
416 tbl.cell(0, 0).unwrap().set_text("Category");
417 tbl.cell(0, 0).unwrap().shading("E2EFDA");
418 tbl.cell(0, 1).unwrap().set_text("Item");
419 tbl.cell(0, 1).unwrap().shading("E2EFDA");
420 tbl.cell(0, 2).unwrap().set_text("Price");
421 tbl.cell(0, 2).unwrap().shading("E2EFDA");
422 tbl.cell(1, 0).unwrap().set_text("Hardware");
423 tbl.cell(1, 0).unwrap().v_merge_restart();
424 tbl.cell(1, 1).unwrap().set_text("Laptop");
425 tbl.cell(1, 2).unwrap().set_text("$1,200");
426 tbl.cell(2, 0).unwrap().v_merge_continue();
427 tbl.cell(2, 1).unwrap().set_text("Monitor");
428 tbl.cell(2, 2).unwrap().set_text("$450");
429 tbl.cell(3, 0).unwrap().set_text("Software");
430 tbl.cell(3, 1).unwrap().set_text("IDE License");
431 tbl.cell(3, 2).unwrap().set_text("$200/yr");
432 }
433
434 doc.add_paragraph("");
435
436 doc.add_paragraph("5.4 Nested Table").style("Heading2");
437 {
438 let mut tbl = doc
439 .add_table(2, 2)
440 .borders(BorderStyle::Single, 6, "2E75B6");
441 tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
442 tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
443 tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
444 {
445 let mut cell = tbl.cell(1, 1).unwrap();
446 cell.set_text("Nested table below:");
447 let mut nested = cell
448 .add_table(2, 2)
449 .borders(BorderStyle::Single, 2, "FF6600");
450 nested.cell(0, 0).unwrap().set_text("A");
451 nested.cell(0, 1).unwrap().set_text("B");
452 nested.cell(1, 0).unwrap().set_text("C");
453 nested.cell(1, 1).unwrap().set_text("D");
454 }
455 }
456
457 doc.add_paragraph("");
458
459 doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
460 .style("Heading2");
461 {
462 let mut tbl = doc
463 .add_table(2, 3)
464 .borders(BorderStyle::Single, 4, "666666");
465 tbl.row(0).unwrap().height(Length::pt(50.0)).header();
466 tbl.cell(0, 0).unwrap().set_text("Top");
467 tbl.cell(0, 0)
468 .unwrap()
469 .vertical_alignment(VerticalAlignment::Top)
470 .shading("FFF2CC");
471 tbl.cell(0, 1).unwrap().set_text("Center");
472 tbl.cell(0, 1)
473 .unwrap()
474 .vertical_alignment(VerticalAlignment::Center)
475 .shading("D9E2F3");
476 tbl.cell(0, 2).unwrap().set_text("Bottom");
477 tbl.cell(0, 2)
478 .unwrap()
479 .vertical_alignment(VerticalAlignment::Bottom)
480 .shading("E2EFDA");
481 tbl.row(1).unwrap().cant_split();
482 tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
483 tbl.cell(1, 0).unwrap().no_wrap();
484 tbl.cell(1, 1).unwrap().set_text("Fixed width");
485 tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
486 tbl.cell(1, 2).unwrap().set_text("Normal");
487 }
488
489 // ── SECTION 6: IMAGES ──
490 doc.add_paragraph("").page_break_before(true);
491 doc.add_paragraph("6. Images").style("Heading1");
492
493 doc.add_paragraph("6.1 Inline Image").style("Heading2");
494 doc.add_paragraph("A blue gradient image below:");
495 let img = create_sample_png(200, 50, [0, 80, 200]);
496 doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
497
498 doc.add_paragraph("");
499 doc.add_paragraph("6.2 Header Image").style("Heading2");
500 let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
501 doc.set_header_image(
502 &hdr_img,
503 "header_logo.png",
504 Length::inches(2.0),
505 Length::inches(0.2),
506 );
507 doc.add_paragraph("The header now contains an inline image (check the top of this page).");
508
509 doc.add_paragraph("");
510 doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
511
512 // ── SECTION 7: CONTENT MANIPULATION ──
513 doc.add_paragraph("").page_break_before(true);
514 doc.add_paragraph("7. Content Manipulation")
515 .style("Heading1");
516
517 doc.add_paragraph("7.1 Placeholder Replacement")
518 .style("Heading2");
519 doc.add_paragraph("Customer: {{customer}}");
520 doc.add_paragraph("Date: {{date}}");
521 doc.add_paragraph("Reference: {{ref_number}}");
522 {
523 let mut tbl = doc
524 .add_table(2, 2)
525 .borders(BorderStyle::Single, 4, "000000");
526 tbl.cell(0, 0).unwrap().set_text("Project");
527 tbl.cell(0, 0).unwrap().shading("D6E4F0");
528 tbl.cell(0, 1).unwrap().set_text("{{project}}");
529 tbl.cell(1, 0).unwrap().set_text("Status");
530 tbl.cell(1, 0).unwrap().shading("D6E4F0");
531 tbl.cell(1, 1).unwrap().set_text("{{status}}");
532 }
533
534 let mut replacements = HashMap::new();
535 replacements.insert("{{customer}}", "Tensorbee Inc.");
536 replacements.insert("{{date}}", "February 22, 2026");
537 replacements.insert("{{ref_number}}", "TB-2026-001");
538 replacements.insert("{{project}}", "Infrastructure Upgrade");
539 replacements.insert("{{status}}", "In Progress");
540 let n = doc.replace_all(&replacements);
541 doc.add_paragraph(&format!("({n} placeholders replaced above)"));
542
543 doc.add_paragraph("");
544
545 doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
546 doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
547 let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
548 doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
549
550 doc.add_paragraph("");
551
552 doc.add_paragraph("7.3 Content Insertion").style("Heading2");
553 doc.add_paragraph("Section A: First content.");
554 doc.add_paragraph("Section C: Third content.");
555 if let Some(idx) = doc.find_content_index("Section C") {
556 doc.insert_paragraph(
557 idx,
558 "Section B: Inserted between A and C via find_content_index().",
559 );
560 }
561
562 // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
563 doc.add_paragraph("").section_break(SectionBreak::NextPage);
564 doc.add_paragraph("8. Section Breaks & Orientation")
565 .style("Heading1");
566 doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
567
568 {
569 let mut tbl = doc
570 .add_table(3, 7)
571 .borders(BorderStyle::Single, 4, "2E75B6");
572 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
573 for (c, h) in headers.iter().enumerate() {
574 tbl.cell(0, c).unwrap().set_text(h);
575 tbl.cell(0, c).unwrap().shading("2E75B6");
576 }
577 let data = [
578 [
579 "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
580 ],
581 [
582 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
583 ],
584 ];
585 for (r, row) in data.iter().enumerate() {
586 for (c, v) in row.iter().enumerate() {
587 tbl.cell(r + 1, c).unwrap().set_text(v);
588 }
589 }
590 }
591
592 doc.add_paragraph("")
593 .section_break(SectionBreak::NextPage)
594 .section_landscape();
595
596 // ── SECTION 9: CUSTOM STYLES ──
597 doc.add_paragraph("9. Custom Styles").style("Heading1");
598 doc.add_style(
599 StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
600 .based_on("Normal")
601 .paragraph_properties(rdocx_oxml::properties::CT_PPr {
602 shading: Some(rdocx_oxml::properties::CT_Shd {
603 val: "clear".to_string(),
604 color: None,
605 fill: Some("FFF2CC".to_string()),
606 }),
607 ..Default::default()
608 })
609 .run_properties(rdocx_oxml::properties::CT_RPr {
610 bold: Some(true),
611 color: Some("C45911".to_string()),
612 ..Default::default()
613 }),
614 );
615 doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
616 .style("CustomHighlight");
617
618 doc.add_paragraph("");
619
620 // ── SECTION 10: DOCUMENT INTELLIGENCE ──
621 doc.add_paragraph("10. Document Intelligence API")
622 .style("Heading1");
623
624 let wc = doc.word_count();
625 let hc = doc.headings().len();
626 let ic = doc.images().len();
627 let lc = doc.links().len();
628 doc.add_paragraph(&format!("Word count: {wc}"));
629 doc.add_paragraph(&format!("Heading count: {hc}"));
630 doc.add_paragraph(&format!("Image count: {ic}"));
631 doc.add_paragraph(&format!("Link count: {lc}"));
632
633 let outline = doc.document_outline();
634 doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
635
636 let issues = doc.audit_accessibility();
637 doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
638 for issue in &issues {
639 doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
640 }
641
642 // ── SECTION 11: DOCUMENT MERGING ──
643 doc.add_paragraph("");
644 doc.add_paragraph("11. Document Merging").style("Heading1");
645 {
646 let mut other = Document::new();
647 other.add_paragraph("This paragraph was merged from another document using append().");
648 doc.append(&other);
649 }
650
651 doc.add_paragraph("");
652
653 // ── SUMMARY ──
654 doc.add_paragraph("Summary of Demonstrated Features")
655 .style("Heading1");
656 let features = [
657 "Page setup: size, margins, header/footer distance, gutter",
658 "Document metadata: title, author, subject, keywords",
659 "Headers and footers: text, images, different first page",
660 "Background images (full-page behind text)",
661 "Table of Contents generation with bookmarks",
662 "Text formatting: bold, italic, underline styles, strike, color, size, font",
663 "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
664 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
665 "Pagination controls: keep-with-next, keep-together, widow control",
666 "Bullet and numbered lists with nesting",
667 "Tab stops with dot/underscore/hyphen leaders",
668 "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
669 "Row properties: header rows, exact height, cant-split",
670 "Cell properties: width, no-wrap, vertical alignment",
671 "Inline images and header images",
672 "Placeholder replacement (single and batch)",
673 "Regex-based find and replace",
674 "Content insertion at specific positions",
675 "Section breaks with mixed portrait/landscape",
676 "Custom paragraph and character styles",
677 "Document intelligence: word count, headings, outline, images, links",
678 "Accessibility audit",
679 "Document merging (append)",
680 "Export: DOCX, PDF, HTML, Markdown",
681 ];
682 for f in &features {
683 doc.add_bullet_list_item(f, 0);
684 }
685 doc.add_paragraph("");
686 doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
687 .alignment(Alignment::Center)
688 .shading("E2EFDA")
689 .border_all(BorderStyle::Single, 2, "00AA00");
690
691 doc
692}Sourcepub fn replace_all_regex(
&mut self,
patterns: &[(String, String)],
) -> Result<usize>
pub fn replace_all_regex( &mut self, patterns: &[(String, String)], ) -> Result<usize>
Replace multiple regex patterns at once. Returns total replacements.
Sourcepub fn to_pdf(&self) -> Result<Vec<u8>>
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:
- Fonts embedded in the DOCX file (word/fonts/)
- System fonts when the default
system-fontsfeature is enabled - Always-available bundled metric-compatible fonts
Examples found in repository?
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}Sourcepub fn to_pdf_deterministic(&self) -> Result<Vec<u8>>
pub fn to_pdf_deterministic(&self) -> Result<Vec<u8>>
Render the document to PDF bytes using bundled fonts without system font discovery.
The deterministic layout is cached independently from the normal-font layout and is suitable for reproducible render baselines.
Examples found in repository?
61fn export_all(dir: &Path, name: &str, mut doc: Document) {
62 let docx_path = dir.join(format!("{name}.docx"));
63 doc.save(&docx_path).unwrap();
64 println!(" {name}.docx");
65
66 let png = doc
67 .render_page_to_png_deterministic(0, 150.0)
68 .unwrap()
69 .expect("every generated sample should have a first page");
70 let png_path = dir.join(format!("{name}.png"));
71 std::fs::write(&png_path, &png).unwrap();
72 println!(" {name}.png ({} bytes)", png.len());
73
74 match doc.to_pdf_deterministic() {
75 Ok(pdf) => {
76 let pdf_path = dir.join(format!("{name}.pdf"));
77 std::fs::write(&pdf_path, &pdf).unwrap();
78 println!(" {name}.pdf ({} bytes)", pdf.len());
79 }
80 Err(e) => println!(" {name}.pdf — skipped: {e}"),
81 }
82
83 let html = doc.to_html();
84 let html_path = dir.join(format!("{name}.html"));
85 std::fs::write(&html_path, &html).unwrap();
86 println!(" {name}.html ({} bytes)", html.len());
87
88 let md = doc.to_markdown();
89 let md_path = dir.join(format!("{name}.md"));
90 std::fs::write(&md_path, &md).unwrap();
91 println!(" {name}.md ({} bytes)", md.len());
92}Sourcepub fn to_pdf_with_fonts(&self, font_files: &[(&str, &[u8])]) -> Result<Vec<u8>>
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:
- User-provided fonts (this parameter)
- Fonts embedded in the DOCX file (word/fonts/)
- System fonts when the default
system-fontsfeature is enabled - Always-available bundled metric-compatible fonts
Sourcepub fn to_html(&self) -> String
pub fn to_html(&self) -> String
Convert the document to a complete HTML document string.
Examples found in repository?
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
61fn export_all(dir: &Path, name: &str, mut doc: Document) {
62 let docx_path = dir.join(format!("{name}.docx"));
63 doc.save(&docx_path).unwrap();
64 println!(" {name}.docx");
65
66 let png = doc
67 .render_page_to_png_deterministic(0, 150.0)
68 .unwrap()
69 .expect("every generated sample should have a first page");
70 let png_path = dir.join(format!("{name}.png"));
71 std::fs::write(&png_path, &png).unwrap();
72 println!(" {name}.png ({} bytes)", png.len());
73
74 match doc.to_pdf_deterministic() {
75 Ok(pdf) => {
76 let pdf_path = dir.join(format!("{name}.pdf"));
77 std::fs::write(&pdf_path, &pdf).unwrap();
78 println!(" {name}.pdf ({} bytes)", pdf.len());
79 }
80 Err(e) => println!(" {name}.pdf — skipped: {e}"),
81 }
82
83 let html = doc.to_html();
84 let html_path = dir.join(format!("{name}.html"));
85 std::fs::write(&html_path, &html).unwrap();
86 println!(" {name}.html ({} bytes)", html.len());
87
88 let md = doc.to_markdown();
89 let md_path = dir.join(format!("{name}.md"));
90 std::fs::write(&md_path, &md).unwrap();
91 println!(" {name}.md ({} bytes)", md.len());
92}Sourcepub fn to_html_fragment(&self) -> String
pub fn to_html_fragment(&self) -> String
Convert the document to an HTML fragment (body content only, no <html> wrapper).
Sourcepub fn to_markdown(&self) -> String
pub fn to_markdown(&self) -> String
Convert the document to Markdown.
Examples found in repository?
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
61fn export_all(dir: &Path, name: &str, mut doc: Document) {
62 let docx_path = dir.join(format!("{name}.docx"));
63 doc.save(&docx_path).unwrap();
64 println!(" {name}.docx");
65
66 let png = doc
67 .render_page_to_png_deterministic(0, 150.0)
68 .unwrap()
69 .expect("every generated sample should have a first page");
70 let png_path = dir.join(format!("{name}.png"));
71 std::fs::write(&png_path, &png).unwrap();
72 println!(" {name}.png ({} bytes)", png.len());
73
74 match doc.to_pdf_deterministic() {
75 Ok(pdf) => {
76 let pdf_path = dir.join(format!("{name}.pdf"));
77 std::fs::write(&pdf_path, &pdf).unwrap();
78 println!(" {name}.pdf ({} bytes)", pdf.len());
79 }
80 Err(e) => println!(" {name}.pdf — skipped: {e}"),
81 }
82
83 let html = doc.to_html();
84 let html_path = dir.join(format!("{name}.html"));
85 std::fs::write(&html_path, &html).unwrap();
86 println!(" {name}.html ({} bytes)", html.len());
87
88 let md = doc.to_markdown();
89 let md_path = dir.join(format!("{name}.md"));
90 std::fs::write(&md_path, &md).unwrap();
91 println!(" {name}.md ({} bytes)", md.len());
92}Sourcepub fn render_page_to_png(
&self,
page_index: usize,
dpi: f64,
) -> Result<Option<Vec<u8>>>
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 indexdpi- Resolution (72 = 1:1, 150 = standard, 300 = high quality)
Sourcepub fn render_page_to_png_deterministic(
&self,
page_index: usize,
dpi: f64,
) -> Result<Option<Vec<u8>>>
pub fn render_page_to_png_deterministic( &self, page_index: usize, dpi: f64, ) -> Result<Option<Vec<u8>>>
Render a single page to PNG using bundled fonts without system font discovery.
§Arguments
page_index- 0-based page indexdpi- Resolution (72 = 1:1, 150 = standard, 300 = high quality)
Examples found in repository?
61fn export_all(dir: &Path, name: &str, mut doc: Document) {
62 let docx_path = dir.join(format!("{name}.docx"));
63 doc.save(&docx_path).unwrap();
64 println!(" {name}.docx");
65
66 let png = doc
67 .render_page_to_png_deterministic(0, 150.0)
68 .unwrap()
69 .expect("every generated sample should have a first page");
70 let png_path = dir.join(format!("{name}.png"));
71 std::fs::write(&png_path, &png).unwrap();
72 println!(" {name}.png ({} bytes)", png.len());
73
74 match doc.to_pdf_deterministic() {
75 Ok(pdf) => {
76 let pdf_path = dir.join(format!("{name}.pdf"));
77 std::fs::write(&pdf_path, &pdf).unwrap();
78 println!(" {name}.pdf ({} bytes)", pdf.len());
79 }
80 Err(e) => println!(" {name}.pdf — skipped: {e}"),
81 }
82
83 let html = doc.to_html();
84 let html_path = dir.join(format!("{name}.html"));
85 std::fs::write(&html_path, &html).unwrap();
86 println!(" {name}.html ({} bytes)", html.len());
87
88 let md = doc.to_markdown();
89 let md_path = dir.join(format!("{name}.md"));
90 std::fs::write(&md_path, &md).unwrap();
91 println!(" {name}.md ({} bytes)", md.len());
92}Sourcepub fn render_all_pages(&self, dpi: f64) -> Result<Vec<Vec<u8>>>
pub fn render_all_pages(&self, dpi: f64) -> Result<Vec<Vec<u8>>>
Render all pages of the document to PNG bytes.
Sourcepub fn layout_page(&self, page_index: usize) -> Result<Option<PageFrame>>
pub fn layout_page(&self, page_index: usize) -> Result<Option<PageFrame>>
Return a cloned positioned page from the cached normal-font layout.
page_index is zero-based. An index beyond the document returns None.
Sourcepub fn load_fonts_from_dir<P: AsRef<Path>>(dir: P) -> Vec<FontFile>
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.
Sourcepub fn headings(&self) -> Vec<(u32, String)>
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?
97fn generate_feature_showcase(_samples_dir: &Path) -> Document {
98 let mut doc = Document::new();
99
100 // ── Page Setup & Metadata ──
101 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
102 doc.set_margins(
103 Length::inches(1.0),
104 Length::inches(1.0),
105 Length::inches(1.0),
106 Length::inches(1.0),
107 );
108 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
109 doc.set_gutter(Length::twips(0));
110 doc.set_title("rdocx Feature Showcase");
111 doc.set_author("rdocx Sample Generator");
112 doc.set_subject("Comprehensive feature demonstration");
113 doc.set_keywords("rdocx, docx, rust, sample, showcase");
114
115 // Headers & Footers with different first page
116 doc.set_different_first_page(true);
117 doc.set_first_page_header("rdocx");
118 doc.set_first_page_footer("Feature Showcase — Cover Page");
119 doc.set_header("rdocx Feature Showcase");
120 doc.set_footer("Generated by rdocx");
121
122 // ── COVER PAGE ──
123 let bg = create_sample_png(612, 792, [20, 45, 90]);
124 doc.add_background_image(&bg, "cover_bg.png");
125
126 for _ in 0..3 {
127 doc.add_paragraph("");
128 }
129 {
130 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
131 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
132 }
133 {
134 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
135 p.add_run("Complete Feature Showcase")
136 .size(28.0)
137 .color("FFFFFF")
138 .italic(true);
139 }
140 doc.add_paragraph("");
141 {
142 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
143 p.add_run("Every feature of the rdocx Rust library")
144 .size(13.0)
145 .color("B0C4DE");
146 }
147 {
148 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
149 p.add_run("demonstrated in a single document.")
150 .size(13.0)
151 .color("B0C4DE");
152 }
153
154 // ── TABLE OF CONTENTS ──
155 doc.add_paragraph("").page_break_before(true);
156 doc.insert_toc(doc.content_count(), 3);
157
158 // ── SECTION 1: TEXT FORMATTING ──
159 doc.add_paragraph("").page_break_before(true);
160 doc.add_paragraph("1. Text Formatting").style("Heading1");
161
162 // Paragraph Alignment
163 doc.add_paragraph("1.1 Paragraph Alignment")
164 .style("Heading2");
165 doc.add_paragraph("Left-aligned paragraph (default).")
166 .alignment(Alignment::Left);
167 doc.add_paragraph("Center-aligned paragraph.")
168 .alignment(Alignment::Center);
169 doc.add_paragraph("Right-aligned paragraph.")
170 .alignment(Alignment::Right);
171 doc.add_paragraph(
172 "Justified paragraph — this text is long enough to demonstrate how justified alignment \
173 distributes extra space across word gaps so lines fill the full width of the text area.",
174 )
175 .alignment(Alignment::Justify);
176
177 doc.add_paragraph("");
178
179 // Run Formatting
180 doc.add_paragraph("1.2 Run Formatting").style("Heading2");
181 {
182 let mut p = doc.add_paragraph("");
183 p.add_run("Bold").bold(true);
184 p.add_run(" | ");
185 p.add_run("Italic").italic(true);
186 p.add_run(" | ");
187 p.add_run("Bold Italic").bold(true).italic(true);
188 p.add_run(" | ");
189 p.add_run("Underline").underline(true);
190 p.add_run(" | ");
191 p.add_run("Strikethrough").strike(true);
192 p.add_run(" | ");
193 p.add_run("Double Strike").double_strike(true);
194 }
195 {
196 let mut p = doc.add_paragraph("");
197 p.add_run("Red").color("FF0000");
198 p.add_run(" | ");
199 p.add_run("Blue").color("0000FF");
200 p.add_run(" | ");
201 p.add_run("Green").color("00AA00");
202 p.add_run(" | ");
203 p.add_run("Highlighted").highlight("FFFF00");
204 }
205 {
206 let mut p = doc.add_paragraph("");
207 p.add_run("8pt").size(8.0);
208 p.add_run(" | ");
209 p.add_run("11pt").size(11.0);
210 p.add_run(" | ");
211 p.add_run("16pt").size(16.0);
212 p.add_run(" | ");
213 p.add_run("24pt").size(24.0);
214 }
215 {
216 let mut p = doc.add_paragraph("");
217 p.add_run("Arial").font("Arial");
218 p.add_run(" | ");
219 p.add_run("Times New Roman").font("Times New Roman");
220 p.add_run(" | ");
221 p.add_run("Courier New").font("Courier New");
222 }
223 {
224 let mut p = doc.add_paragraph("");
225 p.add_run("H");
226 p.add_run("2").subscript();
227 p.add_run("O (subscript) | E=mc");
228 p.add_run("2").superscript();
229 p.add_run(" (superscript)");
230 }
231 {
232 let mut p = doc.add_paragraph("");
233 p.add_run("ALL CAPS").all_caps(true);
234 p.add_run(" | ");
235 p.add_run("Small Caps").small_caps(true);
236 p.add_run(" | ");
237 p.add_run("Expanded").character_spacing(Length::twips(40));
238 p.add_run(" | ");
239 p.add_run("Hidden text (not visible)").hidden(true);
240 }
241
242 doc.add_paragraph("");
243
244 // Underline Styles
245 doc.add_paragraph("1.3 Underline Styles").style("Heading2");
246 {
247 let mut p = doc.add_paragraph("");
248 p.add_run("Single ")
249 .underline_style(rdocx::UnderlineStyle::Single);
250 p.add_run("Double ")
251 .underline_style(rdocx::UnderlineStyle::Double);
252 p.add_run("Thick ")
253 .underline_style(rdocx::UnderlineStyle::Thick);
254 p.add_run("Dotted ")
255 .underline_style(rdocx::UnderlineStyle::Dotted);
256 p.add_run("Dash ")
257 .underline_style(rdocx::UnderlineStyle::Dash);
258 p.add_run("Wave ")
259 .underline_style(rdocx::UnderlineStyle::Wave);
260 }
261
262 // ── SECTION 2: PARAGRAPH FORMATTING ──
263 doc.add_paragraph("").page_break_before(true);
264 doc.add_paragraph("2. Paragraph Formatting")
265 .style("Heading1");
266
267 doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
268 doc.add_paragraph("Paragraph with light green shading")
269 .shading("E2EFDA");
270 doc.add_paragraph("Paragraph with bottom border")
271 .border_bottom(BorderStyle::Single, 6, "2E75B6");
272 doc.add_paragraph("Paragraph with all borders (red)")
273 .border_all(BorderStyle::Single, 4, "FF0000");
274
275 doc.add_paragraph("2.2 Indentation").style("Heading2");
276 doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
277 .indent_left(Length::inches(1.0))
278 .hanging_indent(Length::inches(0.5));
279 doc.add_paragraph("0.5-inch first-line indent on this paragraph")
280 .first_line_indent(Length::inches(0.5));
281 doc.add_paragraph("Both left and right indent (0.75 inches each)")
282 .indent_left(Length::inches(0.75))
283 .indent_right(Length::inches(0.75));
284
285 doc.add_paragraph("2.3 Spacing & Line Height")
286 .style("Heading2");
287 doc.add_paragraph("Extra space before (24pt) and after (12pt)")
288 .space_before(Length::pt(24.0))
289 .space_after(Length::pt(12.0));
290 doc.add_paragraph(
291 "Double line spacing paragraph. This text should have extra vertical space between \
292 lines to demonstrate line_spacing_multiple(2.0).",
293 )
294 .line_spacing_multiple(2.0);
295 doc.add_paragraph("Exact 20pt line spacing (fixed height).")
296 .line_spacing(20.0);
297
298 doc.add_paragraph("2.4 Pagination Controls")
299 .style("Heading2");
300 doc.add_paragraph("keep_with_next — this paragraph stays with the next")
301 .keep_with_next(true);
302 doc.add_paragraph("(This paragraph was kept with the one above.)");
303 doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
304 .keep_together(true);
305 doc.add_paragraph("widow_control — prevents widow/orphan lines")
306 .widow_control(true);
307
308 // ── SECTION 3: LISTS ──
309 doc.add_paragraph("").page_break_before(true);
310 doc.add_paragraph("3. Lists").style("Heading1");
311
312 doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
313 doc.add_bullet_list_item("First item", 0);
314 doc.add_bullet_list_item("Second item", 0);
315 doc.add_bullet_list_item("Nested level 1", 1);
316 doc.add_bullet_list_item("Nested level 2", 2);
317 doc.add_bullet_list_item("Back to level 1", 1);
318 doc.add_bullet_list_item("Third item", 0);
319
320 doc.add_paragraph("");
321
322 doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
323 doc.add_numbered_list_item("First numbered item", 0);
324 doc.add_numbered_list_item("Second numbered item", 0);
325 doc.add_numbered_list_item("Sub-item A", 1);
326 doc.add_numbered_list_item("Sub-item B", 1);
327 doc.add_numbered_list_item("Third numbered item", 0);
328
329 // ── SECTION 4: TAB STOPS ──
330 doc.add_paragraph("");
331 doc.add_paragraph("4. Tab Stops").style("Heading1");
332
333 doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
334 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
335 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
336 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
337 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
338 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
339
340 doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
341 doc.add_paragraph("Item\t\tPrice")
342 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
343 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
344 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
345 doc.add_paragraph("Widget\t\t$19.99")
346 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
347 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
348 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
349 doc.add_paragraph("Gadget\t\t$249.50")
350 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
351 .add_tab_stop_with_leader(
352 TabAlignment::Right,
353 Length::inches(4.0),
354 TabLeader::Underscore,
355 )
356 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
357
358 // ── SECTION 5: TABLES ──
359 doc.add_paragraph("").page_break_before(true);
360 doc.add_paragraph("5. Tables").style("Heading1");
361
362 doc.add_paragraph("5.1 Basic Table").style("Heading2");
363 {
364 let mut tbl = doc
365 .add_table(4, 3)
366 .borders(BorderStyle::Single, 4, "000000");
367 for col in 0..3 {
368 tbl.cell(0, col).unwrap().shading("2E75B6");
369 }
370 tbl.cell(0, 0).unwrap().set_text("Name");
371 tbl.cell(0, 1).unwrap().set_text("Role");
372 tbl.cell(0, 2).unwrap().set_text("Location");
373 tbl.cell(1, 0).unwrap().set_text("Walter White");
374 tbl.cell(1, 1).unwrap().set_text("CEO");
375 tbl.cell(1, 2).unwrap().set_text("Albuquerque");
376 tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
377 tbl.cell(2, 1).unwrap().set_text("CTO");
378 tbl.cell(2, 2).unwrap().set_text("Remote");
379 tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
380 tbl.cell(3, 1).unwrap().set_text("Security");
381 tbl.cell(3, 2).unwrap().set_text("Washington");
382 }
383
384 doc.add_paragraph("");
385
386 doc.add_paragraph("5.2 Column Span & Cell Shading")
387 .style("Heading2");
388 {
389 let mut tbl = doc
390 .add_table(3, 4)
391 .borders(BorderStyle::Single, 4, "000000")
392 .width_pct(100.0);
393 tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
394 tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
395 tbl.cell(1, 0).unwrap().set_text("Region");
396 tbl.cell(1, 0).unwrap().shading("D6E4F0");
397 tbl.cell(1, 1).unwrap().set_text("Q1");
398 tbl.cell(1, 1).unwrap().shading("D6E4F0");
399 tbl.cell(1, 2).unwrap().set_text("Q2");
400 tbl.cell(1, 2).unwrap().shading("D6E4F0");
401 tbl.cell(1, 3).unwrap().set_text("Total");
402 tbl.cell(1, 3).unwrap().shading("D6E4F0");
403 tbl.cell(2, 0).unwrap().set_text("Americas");
404 tbl.cell(2, 1).unwrap().set_text("$2.4M");
405 tbl.cell(2, 2).unwrap().set_text("$2.7M");
406 tbl.cell(2, 3).unwrap().set_text("$5.1M");
407 }
408
409 doc.add_paragraph("");
410
411 doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
412 {
413 let mut tbl = doc
414 .add_table(4, 3)
415 .borders(BorderStyle::Single, 4, "000000");
416 tbl.cell(0, 0).unwrap().set_text("Category");
417 tbl.cell(0, 0).unwrap().shading("E2EFDA");
418 tbl.cell(0, 1).unwrap().set_text("Item");
419 tbl.cell(0, 1).unwrap().shading("E2EFDA");
420 tbl.cell(0, 2).unwrap().set_text("Price");
421 tbl.cell(0, 2).unwrap().shading("E2EFDA");
422 tbl.cell(1, 0).unwrap().set_text("Hardware");
423 tbl.cell(1, 0).unwrap().v_merge_restart();
424 tbl.cell(1, 1).unwrap().set_text("Laptop");
425 tbl.cell(1, 2).unwrap().set_text("$1,200");
426 tbl.cell(2, 0).unwrap().v_merge_continue();
427 tbl.cell(2, 1).unwrap().set_text("Monitor");
428 tbl.cell(2, 2).unwrap().set_text("$450");
429 tbl.cell(3, 0).unwrap().set_text("Software");
430 tbl.cell(3, 1).unwrap().set_text("IDE License");
431 tbl.cell(3, 2).unwrap().set_text("$200/yr");
432 }
433
434 doc.add_paragraph("");
435
436 doc.add_paragraph("5.4 Nested Table").style("Heading2");
437 {
438 let mut tbl = doc
439 .add_table(2, 2)
440 .borders(BorderStyle::Single, 6, "2E75B6");
441 tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
442 tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
443 tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
444 {
445 let mut cell = tbl.cell(1, 1).unwrap();
446 cell.set_text("Nested table below:");
447 let mut nested = cell
448 .add_table(2, 2)
449 .borders(BorderStyle::Single, 2, "FF6600");
450 nested.cell(0, 0).unwrap().set_text("A");
451 nested.cell(0, 1).unwrap().set_text("B");
452 nested.cell(1, 0).unwrap().set_text("C");
453 nested.cell(1, 1).unwrap().set_text("D");
454 }
455 }
456
457 doc.add_paragraph("");
458
459 doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
460 .style("Heading2");
461 {
462 let mut tbl = doc
463 .add_table(2, 3)
464 .borders(BorderStyle::Single, 4, "666666");
465 tbl.row(0).unwrap().height(Length::pt(50.0)).header();
466 tbl.cell(0, 0).unwrap().set_text("Top");
467 tbl.cell(0, 0)
468 .unwrap()
469 .vertical_alignment(VerticalAlignment::Top)
470 .shading("FFF2CC");
471 tbl.cell(0, 1).unwrap().set_text("Center");
472 tbl.cell(0, 1)
473 .unwrap()
474 .vertical_alignment(VerticalAlignment::Center)
475 .shading("D9E2F3");
476 tbl.cell(0, 2).unwrap().set_text("Bottom");
477 tbl.cell(0, 2)
478 .unwrap()
479 .vertical_alignment(VerticalAlignment::Bottom)
480 .shading("E2EFDA");
481 tbl.row(1).unwrap().cant_split();
482 tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
483 tbl.cell(1, 0).unwrap().no_wrap();
484 tbl.cell(1, 1).unwrap().set_text("Fixed width");
485 tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
486 tbl.cell(1, 2).unwrap().set_text("Normal");
487 }
488
489 // ── SECTION 6: IMAGES ──
490 doc.add_paragraph("").page_break_before(true);
491 doc.add_paragraph("6. Images").style("Heading1");
492
493 doc.add_paragraph("6.1 Inline Image").style("Heading2");
494 doc.add_paragraph("A blue gradient image below:");
495 let img = create_sample_png(200, 50, [0, 80, 200]);
496 doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
497
498 doc.add_paragraph("");
499 doc.add_paragraph("6.2 Header Image").style("Heading2");
500 let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
501 doc.set_header_image(
502 &hdr_img,
503 "header_logo.png",
504 Length::inches(2.0),
505 Length::inches(0.2),
506 );
507 doc.add_paragraph("The header now contains an inline image (check the top of this page).");
508
509 doc.add_paragraph("");
510 doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
511
512 // ── SECTION 7: CONTENT MANIPULATION ──
513 doc.add_paragraph("").page_break_before(true);
514 doc.add_paragraph("7. Content Manipulation")
515 .style("Heading1");
516
517 doc.add_paragraph("7.1 Placeholder Replacement")
518 .style("Heading2");
519 doc.add_paragraph("Customer: {{customer}}");
520 doc.add_paragraph("Date: {{date}}");
521 doc.add_paragraph("Reference: {{ref_number}}");
522 {
523 let mut tbl = doc
524 .add_table(2, 2)
525 .borders(BorderStyle::Single, 4, "000000");
526 tbl.cell(0, 0).unwrap().set_text("Project");
527 tbl.cell(0, 0).unwrap().shading("D6E4F0");
528 tbl.cell(0, 1).unwrap().set_text("{{project}}");
529 tbl.cell(1, 0).unwrap().set_text("Status");
530 tbl.cell(1, 0).unwrap().shading("D6E4F0");
531 tbl.cell(1, 1).unwrap().set_text("{{status}}");
532 }
533
534 let mut replacements = HashMap::new();
535 replacements.insert("{{customer}}", "Tensorbee Inc.");
536 replacements.insert("{{date}}", "February 22, 2026");
537 replacements.insert("{{ref_number}}", "TB-2026-001");
538 replacements.insert("{{project}}", "Infrastructure Upgrade");
539 replacements.insert("{{status}}", "In Progress");
540 let n = doc.replace_all(&replacements);
541 doc.add_paragraph(&format!("({n} placeholders replaced above)"));
542
543 doc.add_paragraph("");
544
545 doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
546 doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
547 let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
548 doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
549
550 doc.add_paragraph("");
551
552 doc.add_paragraph("7.3 Content Insertion").style("Heading2");
553 doc.add_paragraph("Section A: First content.");
554 doc.add_paragraph("Section C: Third content.");
555 if let Some(idx) = doc.find_content_index("Section C") {
556 doc.insert_paragraph(
557 idx,
558 "Section B: Inserted between A and C via find_content_index().",
559 );
560 }
561
562 // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
563 doc.add_paragraph("").section_break(SectionBreak::NextPage);
564 doc.add_paragraph("8. Section Breaks & Orientation")
565 .style("Heading1");
566 doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
567
568 {
569 let mut tbl = doc
570 .add_table(3, 7)
571 .borders(BorderStyle::Single, 4, "2E75B6");
572 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
573 for (c, h) in headers.iter().enumerate() {
574 tbl.cell(0, c).unwrap().set_text(h);
575 tbl.cell(0, c).unwrap().shading("2E75B6");
576 }
577 let data = [
578 [
579 "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
580 ],
581 [
582 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
583 ],
584 ];
585 for (r, row) in data.iter().enumerate() {
586 for (c, v) in row.iter().enumerate() {
587 tbl.cell(r + 1, c).unwrap().set_text(v);
588 }
589 }
590 }
591
592 doc.add_paragraph("")
593 .section_break(SectionBreak::NextPage)
594 .section_landscape();
595
596 // ── SECTION 9: CUSTOM STYLES ──
597 doc.add_paragraph("9. Custom Styles").style("Heading1");
598 doc.add_style(
599 StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
600 .based_on("Normal")
601 .paragraph_properties(rdocx_oxml::properties::CT_PPr {
602 shading: Some(rdocx_oxml::properties::CT_Shd {
603 val: "clear".to_string(),
604 color: None,
605 fill: Some("FFF2CC".to_string()),
606 }),
607 ..Default::default()
608 })
609 .run_properties(rdocx_oxml::properties::CT_RPr {
610 bold: Some(true),
611 color: Some("C45911".to_string()),
612 ..Default::default()
613 }),
614 );
615 doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
616 .style("CustomHighlight");
617
618 doc.add_paragraph("");
619
620 // ── SECTION 10: DOCUMENT INTELLIGENCE ──
621 doc.add_paragraph("10. Document Intelligence API")
622 .style("Heading1");
623
624 let wc = doc.word_count();
625 let hc = doc.headings().len();
626 let ic = doc.images().len();
627 let lc = doc.links().len();
628 doc.add_paragraph(&format!("Word count: {wc}"));
629 doc.add_paragraph(&format!("Heading count: {hc}"));
630 doc.add_paragraph(&format!("Image count: {ic}"));
631 doc.add_paragraph(&format!("Link count: {lc}"));
632
633 let outline = doc.document_outline();
634 doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
635
636 let issues = doc.audit_accessibility();
637 doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
638 for issue in &issues {
639 doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
640 }
641
642 // ── SECTION 11: DOCUMENT MERGING ──
643 doc.add_paragraph("");
644 doc.add_paragraph("11. Document Merging").style("Heading1");
645 {
646 let mut other = Document::new();
647 other.add_paragraph("This paragraph was merged from another document using append().");
648 doc.append(&other);
649 }
650
651 doc.add_paragraph("");
652
653 // ── SUMMARY ──
654 doc.add_paragraph("Summary of Demonstrated Features")
655 .style("Heading1");
656 let features = [
657 "Page setup: size, margins, header/footer distance, gutter",
658 "Document metadata: title, author, subject, keywords",
659 "Headers and footers: text, images, different first page",
660 "Background images (full-page behind text)",
661 "Table of Contents generation with bookmarks",
662 "Text formatting: bold, italic, underline styles, strike, color, size, font",
663 "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
664 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
665 "Pagination controls: keep-with-next, keep-together, widow control",
666 "Bullet and numbered lists with nesting",
667 "Tab stops with dot/underscore/hyphen leaders",
668 "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
669 "Row properties: header rows, exact height, cant-split",
670 "Cell properties: width, no-wrap, vertical alignment",
671 "Inline images and header images",
672 "Placeholder replacement (single and batch)",
673 "Regex-based find and replace",
674 "Content insertion at specific positions",
675 "Section breaks with mixed portrait/landscape",
676 "Custom paragraph and character styles",
677 "Document intelligence: word count, headings, outline, images, links",
678 "Accessibility audit",
679 "Document merging (append)",
680 "Export: DOCX, PDF, HTML, Markdown",
681 ];
682 for f in &features {
683 doc.add_bullet_list_item(f, 0);
684 }
685 doc.add_paragraph("");
686 doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
687 .alignment(Alignment::Center)
688 .shading("E2EFDA")
689 .border_all(BorderStyle::Single, 2, "00AA00");
690
691 doc
692}Sourcepub fn document_outline(&self) -> Vec<OutlineNode>
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?
97fn generate_feature_showcase(_samples_dir: &Path) -> Document {
98 let mut doc = Document::new();
99
100 // ── Page Setup & Metadata ──
101 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
102 doc.set_margins(
103 Length::inches(1.0),
104 Length::inches(1.0),
105 Length::inches(1.0),
106 Length::inches(1.0),
107 );
108 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
109 doc.set_gutter(Length::twips(0));
110 doc.set_title("rdocx Feature Showcase");
111 doc.set_author("rdocx Sample Generator");
112 doc.set_subject("Comprehensive feature demonstration");
113 doc.set_keywords("rdocx, docx, rust, sample, showcase");
114
115 // Headers & Footers with different first page
116 doc.set_different_first_page(true);
117 doc.set_first_page_header("rdocx");
118 doc.set_first_page_footer("Feature Showcase — Cover Page");
119 doc.set_header("rdocx Feature Showcase");
120 doc.set_footer("Generated by rdocx");
121
122 // ── COVER PAGE ──
123 let bg = create_sample_png(612, 792, [20, 45, 90]);
124 doc.add_background_image(&bg, "cover_bg.png");
125
126 for _ in 0..3 {
127 doc.add_paragraph("");
128 }
129 {
130 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
131 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
132 }
133 {
134 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
135 p.add_run("Complete Feature Showcase")
136 .size(28.0)
137 .color("FFFFFF")
138 .italic(true);
139 }
140 doc.add_paragraph("");
141 {
142 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
143 p.add_run("Every feature of the rdocx Rust library")
144 .size(13.0)
145 .color("B0C4DE");
146 }
147 {
148 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
149 p.add_run("demonstrated in a single document.")
150 .size(13.0)
151 .color("B0C4DE");
152 }
153
154 // ── TABLE OF CONTENTS ──
155 doc.add_paragraph("").page_break_before(true);
156 doc.insert_toc(doc.content_count(), 3);
157
158 // ── SECTION 1: TEXT FORMATTING ──
159 doc.add_paragraph("").page_break_before(true);
160 doc.add_paragraph("1. Text Formatting").style("Heading1");
161
162 // Paragraph Alignment
163 doc.add_paragraph("1.1 Paragraph Alignment")
164 .style("Heading2");
165 doc.add_paragraph("Left-aligned paragraph (default).")
166 .alignment(Alignment::Left);
167 doc.add_paragraph("Center-aligned paragraph.")
168 .alignment(Alignment::Center);
169 doc.add_paragraph("Right-aligned paragraph.")
170 .alignment(Alignment::Right);
171 doc.add_paragraph(
172 "Justified paragraph — this text is long enough to demonstrate how justified alignment \
173 distributes extra space across word gaps so lines fill the full width of the text area.",
174 )
175 .alignment(Alignment::Justify);
176
177 doc.add_paragraph("");
178
179 // Run Formatting
180 doc.add_paragraph("1.2 Run Formatting").style("Heading2");
181 {
182 let mut p = doc.add_paragraph("");
183 p.add_run("Bold").bold(true);
184 p.add_run(" | ");
185 p.add_run("Italic").italic(true);
186 p.add_run(" | ");
187 p.add_run("Bold Italic").bold(true).italic(true);
188 p.add_run(" | ");
189 p.add_run("Underline").underline(true);
190 p.add_run(" | ");
191 p.add_run("Strikethrough").strike(true);
192 p.add_run(" | ");
193 p.add_run("Double Strike").double_strike(true);
194 }
195 {
196 let mut p = doc.add_paragraph("");
197 p.add_run("Red").color("FF0000");
198 p.add_run(" | ");
199 p.add_run("Blue").color("0000FF");
200 p.add_run(" | ");
201 p.add_run("Green").color("00AA00");
202 p.add_run(" | ");
203 p.add_run("Highlighted").highlight("FFFF00");
204 }
205 {
206 let mut p = doc.add_paragraph("");
207 p.add_run("8pt").size(8.0);
208 p.add_run(" | ");
209 p.add_run("11pt").size(11.0);
210 p.add_run(" | ");
211 p.add_run("16pt").size(16.0);
212 p.add_run(" | ");
213 p.add_run("24pt").size(24.0);
214 }
215 {
216 let mut p = doc.add_paragraph("");
217 p.add_run("Arial").font("Arial");
218 p.add_run(" | ");
219 p.add_run("Times New Roman").font("Times New Roman");
220 p.add_run(" | ");
221 p.add_run("Courier New").font("Courier New");
222 }
223 {
224 let mut p = doc.add_paragraph("");
225 p.add_run("H");
226 p.add_run("2").subscript();
227 p.add_run("O (subscript) | E=mc");
228 p.add_run("2").superscript();
229 p.add_run(" (superscript)");
230 }
231 {
232 let mut p = doc.add_paragraph("");
233 p.add_run("ALL CAPS").all_caps(true);
234 p.add_run(" | ");
235 p.add_run("Small Caps").small_caps(true);
236 p.add_run(" | ");
237 p.add_run("Expanded").character_spacing(Length::twips(40));
238 p.add_run(" | ");
239 p.add_run("Hidden text (not visible)").hidden(true);
240 }
241
242 doc.add_paragraph("");
243
244 // Underline Styles
245 doc.add_paragraph("1.3 Underline Styles").style("Heading2");
246 {
247 let mut p = doc.add_paragraph("");
248 p.add_run("Single ")
249 .underline_style(rdocx::UnderlineStyle::Single);
250 p.add_run("Double ")
251 .underline_style(rdocx::UnderlineStyle::Double);
252 p.add_run("Thick ")
253 .underline_style(rdocx::UnderlineStyle::Thick);
254 p.add_run("Dotted ")
255 .underline_style(rdocx::UnderlineStyle::Dotted);
256 p.add_run("Dash ")
257 .underline_style(rdocx::UnderlineStyle::Dash);
258 p.add_run("Wave ")
259 .underline_style(rdocx::UnderlineStyle::Wave);
260 }
261
262 // ── SECTION 2: PARAGRAPH FORMATTING ──
263 doc.add_paragraph("").page_break_before(true);
264 doc.add_paragraph("2. Paragraph Formatting")
265 .style("Heading1");
266
267 doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
268 doc.add_paragraph("Paragraph with light green shading")
269 .shading("E2EFDA");
270 doc.add_paragraph("Paragraph with bottom border")
271 .border_bottom(BorderStyle::Single, 6, "2E75B6");
272 doc.add_paragraph("Paragraph with all borders (red)")
273 .border_all(BorderStyle::Single, 4, "FF0000");
274
275 doc.add_paragraph("2.2 Indentation").style("Heading2");
276 doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
277 .indent_left(Length::inches(1.0))
278 .hanging_indent(Length::inches(0.5));
279 doc.add_paragraph("0.5-inch first-line indent on this paragraph")
280 .first_line_indent(Length::inches(0.5));
281 doc.add_paragraph("Both left and right indent (0.75 inches each)")
282 .indent_left(Length::inches(0.75))
283 .indent_right(Length::inches(0.75));
284
285 doc.add_paragraph("2.3 Spacing & Line Height")
286 .style("Heading2");
287 doc.add_paragraph("Extra space before (24pt) and after (12pt)")
288 .space_before(Length::pt(24.0))
289 .space_after(Length::pt(12.0));
290 doc.add_paragraph(
291 "Double line spacing paragraph. This text should have extra vertical space between \
292 lines to demonstrate line_spacing_multiple(2.0).",
293 )
294 .line_spacing_multiple(2.0);
295 doc.add_paragraph("Exact 20pt line spacing (fixed height).")
296 .line_spacing(20.0);
297
298 doc.add_paragraph("2.4 Pagination Controls")
299 .style("Heading2");
300 doc.add_paragraph("keep_with_next — this paragraph stays with the next")
301 .keep_with_next(true);
302 doc.add_paragraph("(This paragraph was kept with the one above.)");
303 doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
304 .keep_together(true);
305 doc.add_paragraph("widow_control — prevents widow/orphan lines")
306 .widow_control(true);
307
308 // ── SECTION 3: LISTS ──
309 doc.add_paragraph("").page_break_before(true);
310 doc.add_paragraph("3. Lists").style("Heading1");
311
312 doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
313 doc.add_bullet_list_item("First item", 0);
314 doc.add_bullet_list_item("Second item", 0);
315 doc.add_bullet_list_item("Nested level 1", 1);
316 doc.add_bullet_list_item("Nested level 2", 2);
317 doc.add_bullet_list_item("Back to level 1", 1);
318 doc.add_bullet_list_item("Third item", 0);
319
320 doc.add_paragraph("");
321
322 doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
323 doc.add_numbered_list_item("First numbered item", 0);
324 doc.add_numbered_list_item("Second numbered item", 0);
325 doc.add_numbered_list_item("Sub-item A", 1);
326 doc.add_numbered_list_item("Sub-item B", 1);
327 doc.add_numbered_list_item("Third numbered item", 0);
328
329 // ── SECTION 4: TAB STOPS ──
330 doc.add_paragraph("");
331 doc.add_paragraph("4. Tab Stops").style("Heading1");
332
333 doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
334 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
335 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
336 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
337 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
338 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
339
340 doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
341 doc.add_paragraph("Item\t\tPrice")
342 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
343 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
344 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
345 doc.add_paragraph("Widget\t\t$19.99")
346 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
347 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
348 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
349 doc.add_paragraph("Gadget\t\t$249.50")
350 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
351 .add_tab_stop_with_leader(
352 TabAlignment::Right,
353 Length::inches(4.0),
354 TabLeader::Underscore,
355 )
356 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
357
358 // ── SECTION 5: TABLES ──
359 doc.add_paragraph("").page_break_before(true);
360 doc.add_paragraph("5. Tables").style("Heading1");
361
362 doc.add_paragraph("5.1 Basic Table").style("Heading2");
363 {
364 let mut tbl = doc
365 .add_table(4, 3)
366 .borders(BorderStyle::Single, 4, "000000");
367 for col in 0..3 {
368 tbl.cell(0, col).unwrap().shading("2E75B6");
369 }
370 tbl.cell(0, 0).unwrap().set_text("Name");
371 tbl.cell(0, 1).unwrap().set_text("Role");
372 tbl.cell(0, 2).unwrap().set_text("Location");
373 tbl.cell(1, 0).unwrap().set_text("Walter White");
374 tbl.cell(1, 1).unwrap().set_text("CEO");
375 tbl.cell(1, 2).unwrap().set_text("Albuquerque");
376 tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
377 tbl.cell(2, 1).unwrap().set_text("CTO");
378 tbl.cell(2, 2).unwrap().set_text("Remote");
379 tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
380 tbl.cell(3, 1).unwrap().set_text("Security");
381 tbl.cell(3, 2).unwrap().set_text("Washington");
382 }
383
384 doc.add_paragraph("");
385
386 doc.add_paragraph("5.2 Column Span & Cell Shading")
387 .style("Heading2");
388 {
389 let mut tbl = doc
390 .add_table(3, 4)
391 .borders(BorderStyle::Single, 4, "000000")
392 .width_pct(100.0);
393 tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
394 tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
395 tbl.cell(1, 0).unwrap().set_text("Region");
396 tbl.cell(1, 0).unwrap().shading("D6E4F0");
397 tbl.cell(1, 1).unwrap().set_text("Q1");
398 tbl.cell(1, 1).unwrap().shading("D6E4F0");
399 tbl.cell(1, 2).unwrap().set_text("Q2");
400 tbl.cell(1, 2).unwrap().shading("D6E4F0");
401 tbl.cell(1, 3).unwrap().set_text("Total");
402 tbl.cell(1, 3).unwrap().shading("D6E4F0");
403 tbl.cell(2, 0).unwrap().set_text("Americas");
404 tbl.cell(2, 1).unwrap().set_text("$2.4M");
405 tbl.cell(2, 2).unwrap().set_text("$2.7M");
406 tbl.cell(2, 3).unwrap().set_text("$5.1M");
407 }
408
409 doc.add_paragraph("");
410
411 doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
412 {
413 let mut tbl = doc
414 .add_table(4, 3)
415 .borders(BorderStyle::Single, 4, "000000");
416 tbl.cell(0, 0).unwrap().set_text("Category");
417 tbl.cell(0, 0).unwrap().shading("E2EFDA");
418 tbl.cell(0, 1).unwrap().set_text("Item");
419 tbl.cell(0, 1).unwrap().shading("E2EFDA");
420 tbl.cell(0, 2).unwrap().set_text("Price");
421 tbl.cell(0, 2).unwrap().shading("E2EFDA");
422 tbl.cell(1, 0).unwrap().set_text("Hardware");
423 tbl.cell(1, 0).unwrap().v_merge_restart();
424 tbl.cell(1, 1).unwrap().set_text("Laptop");
425 tbl.cell(1, 2).unwrap().set_text("$1,200");
426 tbl.cell(2, 0).unwrap().v_merge_continue();
427 tbl.cell(2, 1).unwrap().set_text("Monitor");
428 tbl.cell(2, 2).unwrap().set_text("$450");
429 tbl.cell(3, 0).unwrap().set_text("Software");
430 tbl.cell(3, 1).unwrap().set_text("IDE License");
431 tbl.cell(3, 2).unwrap().set_text("$200/yr");
432 }
433
434 doc.add_paragraph("");
435
436 doc.add_paragraph("5.4 Nested Table").style("Heading2");
437 {
438 let mut tbl = doc
439 .add_table(2, 2)
440 .borders(BorderStyle::Single, 6, "2E75B6");
441 tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
442 tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
443 tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
444 {
445 let mut cell = tbl.cell(1, 1).unwrap();
446 cell.set_text("Nested table below:");
447 let mut nested = cell
448 .add_table(2, 2)
449 .borders(BorderStyle::Single, 2, "FF6600");
450 nested.cell(0, 0).unwrap().set_text("A");
451 nested.cell(0, 1).unwrap().set_text("B");
452 nested.cell(1, 0).unwrap().set_text("C");
453 nested.cell(1, 1).unwrap().set_text("D");
454 }
455 }
456
457 doc.add_paragraph("");
458
459 doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
460 .style("Heading2");
461 {
462 let mut tbl = doc
463 .add_table(2, 3)
464 .borders(BorderStyle::Single, 4, "666666");
465 tbl.row(0).unwrap().height(Length::pt(50.0)).header();
466 tbl.cell(0, 0).unwrap().set_text("Top");
467 tbl.cell(0, 0)
468 .unwrap()
469 .vertical_alignment(VerticalAlignment::Top)
470 .shading("FFF2CC");
471 tbl.cell(0, 1).unwrap().set_text("Center");
472 tbl.cell(0, 1)
473 .unwrap()
474 .vertical_alignment(VerticalAlignment::Center)
475 .shading("D9E2F3");
476 tbl.cell(0, 2).unwrap().set_text("Bottom");
477 tbl.cell(0, 2)
478 .unwrap()
479 .vertical_alignment(VerticalAlignment::Bottom)
480 .shading("E2EFDA");
481 tbl.row(1).unwrap().cant_split();
482 tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
483 tbl.cell(1, 0).unwrap().no_wrap();
484 tbl.cell(1, 1).unwrap().set_text("Fixed width");
485 tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
486 tbl.cell(1, 2).unwrap().set_text("Normal");
487 }
488
489 // ── SECTION 6: IMAGES ──
490 doc.add_paragraph("").page_break_before(true);
491 doc.add_paragraph("6. Images").style("Heading1");
492
493 doc.add_paragraph("6.1 Inline Image").style("Heading2");
494 doc.add_paragraph("A blue gradient image below:");
495 let img = create_sample_png(200, 50, [0, 80, 200]);
496 doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
497
498 doc.add_paragraph("");
499 doc.add_paragraph("6.2 Header Image").style("Heading2");
500 let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
501 doc.set_header_image(
502 &hdr_img,
503 "header_logo.png",
504 Length::inches(2.0),
505 Length::inches(0.2),
506 );
507 doc.add_paragraph("The header now contains an inline image (check the top of this page).");
508
509 doc.add_paragraph("");
510 doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
511
512 // ── SECTION 7: CONTENT MANIPULATION ──
513 doc.add_paragraph("").page_break_before(true);
514 doc.add_paragraph("7. Content Manipulation")
515 .style("Heading1");
516
517 doc.add_paragraph("7.1 Placeholder Replacement")
518 .style("Heading2");
519 doc.add_paragraph("Customer: {{customer}}");
520 doc.add_paragraph("Date: {{date}}");
521 doc.add_paragraph("Reference: {{ref_number}}");
522 {
523 let mut tbl = doc
524 .add_table(2, 2)
525 .borders(BorderStyle::Single, 4, "000000");
526 tbl.cell(0, 0).unwrap().set_text("Project");
527 tbl.cell(0, 0).unwrap().shading("D6E4F0");
528 tbl.cell(0, 1).unwrap().set_text("{{project}}");
529 tbl.cell(1, 0).unwrap().set_text("Status");
530 tbl.cell(1, 0).unwrap().shading("D6E4F0");
531 tbl.cell(1, 1).unwrap().set_text("{{status}}");
532 }
533
534 let mut replacements = HashMap::new();
535 replacements.insert("{{customer}}", "Tensorbee Inc.");
536 replacements.insert("{{date}}", "February 22, 2026");
537 replacements.insert("{{ref_number}}", "TB-2026-001");
538 replacements.insert("{{project}}", "Infrastructure Upgrade");
539 replacements.insert("{{status}}", "In Progress");
540 let n = doc.replace_all(&replacements);
541 doc.add_paragraph(&format!("({n} placeholders replaced above)"));
542
543 doc.add_paragraph("");
544
545 doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
546 doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
547 let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
548 doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
549
550 doc.add_paragraph("");
551
552 doc.add_paragraph("7.3 Content Insertion").style("Heading2");
553 doc.add_paragraph("Section A: First content.");
554 doc.add_paragraph("Section C: Third content.");
555 if let Some(idx) = doc.find_content_index("Section C") {
556 doc.insert_paragraph(
557 idx,
558 "Section B: Inserted between A and C via find_content_index().",
559 );
560 }
561
562 // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
563 doc.add_paragraph("").section_break(SectionBreak::NextPage);
564 doc.add_paragraph("8. Section Breaks & Orientation")
565 .style("Heading1");
566 doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
567
568 {
569 let mut tbl = doc
570 .add_table(3, 7)
571 .borders(BorderStyle::Single, 4, "2E75B6");
572 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
573 for (c, h) in headers.iter().enumerate() {
574 tbl.cell(0, c).unwrap().set_text(h);
575 tbl.cell(0, c).unwrap().shading("2E75B6");
576 }
577 let data = [
578 [
579 "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
580 ],
581 [
582 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
583 ],
584 ];
585 for (r, row) in data.iter().enumerate() {
586 for (c, v) in row.iter().enumerate() {
587 tbl.cell(r + 1, c).unwrap().set_text(v);
588 }
589 }
590 }
591
592 doc.add_paragraph("")
593 .section_break(SectionBreak::NextPage)
594 .section_landscape();
595
596 // ── SECTION 9: CUSTOM STYLES ──
597 doc.add_paragraph("9. Custom Styles").style("Heading1");
598 doc.add_style(
599 StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
600 .based_on("Normal")
601 .paragraph_properties(rdocx_oxml::properties::CT_PPr {
602 shading: Some(rdocx_oxml::properties::CT_Shd {
603 val: "clear".to_string(),
604 color: None,
605 fill: Some("FFF2CC".to_string()),
606 }),
607 ..Default::default()
608 })
609 .run_properties(rdocx_oxml::properties::CT_RPr {
610 bold: Some(true),
611 color: Some("C45911".to_string()),
612 ..Default::default()
613 }),
614 );
615 doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
616 .style("CustomHighlight");
617
618 doc.add_paragraph("");
619
620 // ── SECTION 10: DOCUMENT INTELLIGENCE ──
621 doc.add_paragraph("10. Document Intelligence API")
622 .style("Heading1");
623
624 let wc = doc.word_count();
625 let hc = doc.headings().len();
626 let ic = doc.images().len();
627 let lc = doc.links().len();
628 doc.add_paragraph(&format!("Word count: {wc}"));
629 doc.add_paragraph(&format!("Heading count: {hc}"));
630 doc.add_paragraph(&format!("Image count: {ic}"));
631 doc.add_paragraph(&format!("Link count: {lc}"));
632
633 let outline = doc.document_outline();
634 doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
635
636 let issues = doc.audit_accessibility();
637 doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
638 for issue in &issues {
639 doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
640 }
641
642 // ── SECTION 11: DOCUMENT MERGING ──
643 doc.add_paragraph("");
644 doc.add_paragraph("11. Document Merging").style("Heading1");
645 {
646 let mut other = Document::new();
647 other.add_paragraph("This paragraph was merged from another document using append().");
648 doc.append(&other);
649 }
650
651 doc.add_paragraph("");
652
653 // ── SUMMARY ──
654 doc.add_paragraph("Summary of Demonstrated Features")
655 .style("Heading1");
656 let features = [
657 "Page setup: size, margins, header/footer distance, gutter",
658 "Document metadata: title, author, subject, keywords",
659 "Headers and footers: text, images, different first page",
660 "Background images (full-page behind text)",
661 "Table of Contents generation with bookmarks",
662 "Text formatting: bold, italic, underline styles, strike, color, size, font",
663 "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
664 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
665 "Pagination controls: keep-with-next, keep-together, widow control",
666 "Bullet and numbered lists with nesting",
667 "Tab stops with dot/underscore/hyphen leaders",
668 "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
669 "Row properties: header rows, exact height, cant-split",
670 "Cell properties: width, no-wrap, vertical alignment",
671 "Inline images and header images",
672 "Placeholder replacement (single and batch)",
673 "Regex-based find and replace",
674 "Content insertion at specific positions",
675 "Section breaks with mixed portrait/landscape",
676 "Custom paragraph and character styles",
677 "Document intelligence: word count, headings, outline, images, links",
678 "Accessibility audit",
679 "Document merging (append)",
680 "Export: DOCX, PDF, HTML, Markdown",
681 ];
682 for f in &features {
683 doc.add_bullet_list_item(f, 0);
684 }
685 doc.add_paragraph("");
686 doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
687 .alignment(Alignment::Center)
688 .shading("E2EFDA")
689 .border_all(BorderStyle::Single, 2, "00AA00");
690
691 doc
692}Sourcepub fn images(&self) -> Vec<ImageInfo>
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?
97fn generate_feature_showcase(_samples_dir: &Path) -> Document {
98 let mut doc = Document::new();
99
100 // ── Page Setup & Metadata ──
101 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
102 doc.set_margins(
103 Length::inches(1.0),
104 Length::inches(1.0),
105 Length::inches(1.0),
106 Length::inches(1.0),
107 );
108 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
109 doc.set_gutter(Length::twips(0));
110 doc.set_title("rdocx Feature Showcase");
111 doc.set_author("rdocx Sample Generator");
112 doc.set_subject("Comprehensive feature demonstration");
113 doc.set_keywords("rdocx, docx, rust, sample, showcase");
114
115 // Headers & Footers with different first page
116 doc.set_different_first_page(true);
117 doc.set_first_page_header("rdocx");
118 doc.set_first_page_footer("Feature Showcase — Cover Page");
119 doc.set_header("rdocx Feature Showcase");
120 doc.set_footer("Generated by rdocx");
121
122 // ── COVER PAGE ──
123 let bg = create_sample_png(612, 792, [20, 45, 90]);
124 doc.add_background_image(&bg, "cover_bg.png");
125
126 for _ in 0..3 {
127 doc.add_paragraph("");
128 }
129 {
130 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
131 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
132 }
133 {
134 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
135 p.add_run("Complete Feature Showcase")
136 .size(28.0)
137 .color("FFFFFF")
138 .italic(true);
139 }
140 doc.add_paragraph("");
141 {
142 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
143 p.add_run("Every feature of the rdocx Rust library")
144 .size(13.0)
145 .color("B0C4DE");
146 }
147 {
148 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
149 p.add_run("demonstrated in a single document.")
150 .size(13.0)
151 .color("B0C4DE");
152 }
153
154 // ── TABLE OF CONTENTS ──
155 doc.add_paragraph("").page_break_before(true);
156 doc.insert_toc(doc.content_count(), 3);
157
158 // ── SECTION 1: TEXT FORMATTING ──
159 doc.add_paragraph("").page_break_before(true);
160 doc.add_paragraph("1. Text Formatting").style("Heading1");
161
162 // Paragraph Alignment
163 doc.add_paragraph("1.1 Paragraph Alignment")
164 .style("Heading2");
165 doc.add_paragraph("Left-aligned paragraph (default).")
166 .alignment(Alignment::Left);
167 doc.add_paragraph("Center-aligned paragraph.")
168 .alignment(Alignment::Center);
169 doc.add_paragraph("Right-aligned paragraph.")
170 .alignment(Alignment::Right);
171 doc.add_paragraph(
172 "Justified paragraph — this text is long enough to demonstrate how justified alignment \
173 distributes extra space across word gaps so lines fill the full width of the text area.",
174 )
175 .alignment(Alignment::Justify);
176
177 doc.add_paragraph("");
178
179 // Run Formatting
180 doc.add_paragraph("1.2 Run Formatting").style("Heading2");
181 {
182 let mut p = doc.add_paragraph("");
183 p.add_run("Bold").bold(true);
184 p.add_run(" | ");
185 p.add_run("Italic").italic(true);
186 p.add_run(" | ");
187 p.add_run("Bold Italic").bold(true).italic(true);
188 p.add_run(" | ");
189 p.add_run("Underline").underline(true);
190 p.add_run(" | ");
191 p.add_run("Strikethrough").strike(true);
192 p.add_run(" | ");
193 p.add_run("Double Strike").double_strike(true);
194 }
195 {
196 let mut p = doc.add_paragraph("");
197 p.add_run("Red").color("FF0000");
198 p.add_run(" | ");
199 p.add_run("Blue").color("0000FF");
200 p.add_run(" | ");
201 p.add_run("Green").color("00AA00");
202 p.add_run(" | ");
203 p.add_run("Highlighted").highlight("FFFF00");
204 }
205 {
206 let mut p = doc.add_paragraph("");
207 p.add_run("8pt").size(8.0);
208 p.add_run(" | ");
209 p.add_run("11pt").size(11.0);
210 p.add_run(" | ");
211 p.add_run("16pt").size(16.0);
212 p.add_run(" | ");
213 p.add_run("24pt").size(24.0);
214 }
215 {
216 let mut p = doc.add_paragraph("");
217 p.add_run("Arial").font("Arial");
218 p.add_run(" | ");
219 p.add_run("Times New Roman").font("Times New Roman");
220 p.add_run(" | ");
221 p.add_run("Courier New").font("Courier New");
222 }
223 {
224 let mut p = doc.add_paragraph("");
225 p.add_run("H");
226 p.add_run("2").subscript();
227 p.add_run("O (subscript) | E=mc");
228 p.add_run("2").superscript();
229 p.add_run(" (superscript)");
230 }
231 {
232 let mut p = doc.add_paragraph("");
233 p.add_run("ALL CAPS").all_caps(true);
234 p.add_run(" | ");
235 p.add_run("Small Caps").small_caps(true);
236 p.add_run(" | ");
237 p.add_run("Expanded").character_spacing(Length::twips(40));
238 p.add_run(" | ");
239 p.add_run("Hidden text (not visible)").hidden(true);
240 }
241
242 doc.add_paragraph("");
243
244 // Underline Styles
245 doc.add_paragraph("1.3 Underline Styles").style("Heading2");
246 {
247 let mut p = doc.add_paragraph("");
248 p.add_run("Single ")
249 .underline_style(rdocx::UnderlineStyle::Single);
250 p.add_run("Double ")
251 .underline_style(rdocx::UnderlineStyle::Double);
252 p.add_run("Thick ")
253 .underline_style(rdocx::UnderlineStyle::Thick);
254 p.add_run("Dotted ")
255 .underline_style(rdocx::UnderlineStyle::Dotted);
256 p.add_run("Dash ")
257 .underline_style(rdocx::UnderlineStyle::Dash);
258 p.add_run("Wave ")
259 .underline_style(rdocx::UnderlineStyle::Wave);
260 }
261
262 // ── SECTION 2: PARAGRAPH FORMATTING ──
263 doc.add_paragraph("").page_break_before(true);
264 doc.add_paragraph("2. Paragraph Formatting")
265 .style("Heading1");
266
267 doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
268 doc.add_paragraph("Paragraph with light green shading")
269 .shading("E2EFDA");
270 doc.add_paragraph("Paragraph with bottom border")
271 .border_bottom(BorderStyle::Single, 6, "2E75B6");
272 doc.add_paragraph("Paragraph with all borders (red)")
273 .border_all(BorderStyle::Single, 4, "FF0000");
274
275 doc.add_paragraph("2.2 Indentation").style("Heading2");
276 doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
277 .indent_left(Length::inches(1.0))
278 .hanging_indent(Length::inches(0.5));
279 doc.add_paragraph("0.5-inch first-line indent on this paragraph")
280 .first_line_indent(Length::inches(0.5));
281 doc.add_paragraph("Both left and right indent (0.75 inches each)")
282 .indent_left(Length::inches(0.75))
283 .indent_right(Length::inches(0.75));
284
285 doc.add_paragraph("2.3 Spacing & Line Height")
286 .style("Heading2");
287 doc.add_paragraph("Extra space before (24pt) and after (12pt)")
288 .space_before(Length::pt(24.0))
289 .space_after(Length::pt(12.0));
290 doc.add_paragraph(
291 "Double line spacing paragraph. This text should have extra vertical space between \
292 lines to demonstrate line_spacing_multiple(2.0).",
293 )
294 .line_spacing_multiple(2.0);
295 doc.add_paragraph("Exact 20pt line spacing (fixed height).")
296 .line_spacing(20.0);
297
298 doc.add_paragraph("2.4 Pagination Controls")
299 .style("Heading2");
300 doc.add_paragraph("keep_with_next — this paragraph stays with the next")
301 .keep_with_next(true);
302 doc.add_paragraph("(This paragraph was kept with the one above.)");
303 doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
304 .keep_together(true);
305 doc.add_paragraph("widow_control — prevents widow/orphan lines")
306 .widow_control(true);
307
308 // ── SECTION 3: LISTS ──
309 doc.add_paragraph("").page_break_before(true);
310 doc.add_paragraph("3. Lists").style("Heading1");
311
312 doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
313 doc.add_bullet_list_item("First item", 0);
314 doc.add_bullet_list_item("Second item", 0);
315 doc.add_bullet_list_item("Nested level 1", 1);
316 doc.add_bullet_list_item("Nested level 2", 2);
317 doc.add_bullet_list_item("Back to level 1", 1);
318 doc.add_bullet_list_item("Third item", 0);
319
320 doc.add_paragraph("");
321
322 doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
323 doc.add_numbered_list_item("First numbered item", 0);
324 doc.add_numbered_list_item("Second numbered item", 0);
325 doc.add_numbered_list_item("Sub-item A", 1);
326 doc.add_numbered_list_item("Sub-item B", 1);
327 doc.add_numbered_list_item("Third numbered item", 0);
328
329 // ── SECTION 4: TAB STOPS ──
330 doc.add_paragraph("");
331 doc.add_paragraph("4. Tab Stops").style("Heading1");
332
333 doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
334 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
335 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
336 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
337 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
338 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
339
340 doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
341 doc.add_paragraph("Item\t\tPrice")
342 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
343 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
344 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
345 doc.add_paragraph("Widget\t\t$19.99")
346 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
347 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
348 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
349 doc.add_paragraph("Gadget\t\t$249.50")
350 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
351 .add_tab_stop_with_leader(
352 TabAlignment::Right,
353 Length::inches(4.0),
354 TabLeader::Underscore,
355 )
356 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
357
358 // ── SECTION 5: TABLES ──
359 doc.add_paragraph("").page_break_before(true);
360 doc.add_paragraph("5. Tables").style("Heading1");
361
362 doc.add_paragraph("5.1 Basic Table").style("Heading2");
363 {
364 let mut tbl = doc
365 .add_table(4, 3)
366 .borders(BorderStyle::Single, 4, "000000");
367 for col in 0..3 {
368 tbl.cell(0, col).unwrap().shading("2E75B6");
369 }
370 tbl.cell(0, 0).unwrap().set_text("Name");
371 tbl.cell(0, 1).unwrap().set_text("Role");
372 tbl.cell(0, 2).unwrap().set_text("Location");
373 tbl.cell(1, 0).unwrap().set_text("Walter White");
374 tbl.cell(1, 1).unwrap().set_text("CEO");
375 tbl.cell(1, 2).unwrap().set_text("Albuquerque");
376 tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
377 tbl.cell(2, 1).unwrap().set_text("CTO");
378 tbl.cell(2, 2).unwrap().set_text("Remote");
379 tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
380 tbl.cell(3, 1).unwrap().set_text("Security");
381 tbl.cell(3, 2).unwrap().set_text("Washington");
382 }
383
384 doc.add_paragraph("");
385
386 doc.add_paragraph("5.2 Column Span & Cell Shading")
387 .style("Heading2");
388 {
389 let mut tbl = doc
390 .add_table(3, 4)
391 .borders(BorderStyle::Single, 4, "000000")
392 .width_pct(100.0);
393 tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
394 tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
395 tbl.cell(1, 0).unwrap().set_text("Region");
396 tbl.cell(1, 0).unwrap().shading("D6E4F0");
397 tbl.cell(1, 1).unwrap().set_text("Q1");
398 tbl.cell(1, 1).unwrap().shading("D6E4F0");
399 tbl.cell(1, 2).unwrap().set_text("Q2");
400 tbl.cell(1, 2).unwrap().shading("D6E4F0");
401 tbl.cell(1, 3).unwrap().set_text("Total");
402 tbl.cell(1, 3).unwrap().shading("D6E4F0");
403 tbl.cell(2, 0).unwrap().set_text("Americas");
404 tbl.cell(2, 1).unwrap().set_text("$2.4M");
405 tbl.cell(2, 2).unwrap().set_text("$2.7M");
406 tbl.cell(2, 3).unwrap().set_text("$5.1M");
407 }
408
409 doc.add_paragraph("");
410
411 doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
412 {
413 let mut tbl = doc
414 .add_table(4, 3)
415 .borders(BorderStyle::Single, 4, "000000");
416 tbl.cell(0, 0).unwrap().set_text("Category");
417 tbl.cell(0, 0).unwrap().shading("E2EFDA");
418 tbl.cell(0, 1).unwrap().set_text("Item");
419 tbl.cell(0, 1).unwrap().shading("E2EFDA");
420 tbl.cell(0, 2).unwrap().set_text("Price");
421 tbl.cell(0, 2).unwrap().shading("E2EFDA");
422 tbl.cell(1, 0).unwrap().set_text("Hardware");
423 tbl.cell(1, 0).unwrap().v_merge_restart();
424 tbl.cell(1, 1).unwrap().set_text("Laptop");
425 tbl.cell(1, 2).unwrap().set_text("$1,200");
426 tbl.cell(2, 0).unwrap().v_merge_continue();
427 tbl.cell(2, 1).unwrap().set_text("Monitor");
428 tbl.cell(2, 2).unwrap().set_text("$450");
429 tbl.cell(3, 0).unwrap().set_text("Software");
430 tbl.cell(3, 1).unwrap().set_text("IDE License");
431 tbl.cell(3, 2).unwrap().set_text("$200/yr");
432 }
433
434 doc.add_paragraph("");
435
436 doc.add_paragraph("5.4 Nested Table").style("Heading2");
437 {
438 let mut tbl = doc
439 .add_table(2, 2)
440 .borders(BorderStyle::Single, 6, "2E75B6");
441 tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
442 tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
443 tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
444 {
445 let mut cell = tbl.cell(1, 1).unwrap();
446 cell.set_text("Nested table below:");
447 let mut nested = cell
448 .add_table(2, 2)
449 .borders(BorderStyle::Single, 2, "FF6600");
450 nested.cell(0, 0).unwrap().set_text("A");
451 nested.cell(0, 1).unwrap().set_text("B");
452 nested.cell(1, 0).unwrap().set_text("C");
453 nested.cell(1, 1).unwrap().set_text("D");
454 }
455 }
456
457 doc.add_paragraph("");
458
459 doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
460 .style("Heading2");
461 {
462 let mut tbl = doc
463 .add_table(2, 3)
464 .borders(BorderStyle::Single, 4, "666666");
465 tbl.row(0).unwrap().height(Length::pt(50.0)).header();
466 tbl.cell(0, 0).unwrap().set_text("Top");
467 tbl.cell(0, 0)
468 .unwrap()
469 .vertical_alignment(VerticalAlignment::Top)
470 .shading("FFF2CC");
471 tbl.cell(0, 1).unwrap().set_text("Center");
472 tbl.cell(0, 1)
473 .unwrap()
474 .vertical_alignment(VerticalAlignment::Center)
475 .shading("D9E2F3");
476 tbl.cell(0, 2).unwrap().set_text("Bottom");
477 tbl.cell(0, 2)
478 .unwrap()
479 .vertical_alignment(VerticalAlignment::Bottom)
480 .shading("E2EFDA");
481 tbl.row(1).unwrap().cant_split();
482 tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
483 tbl.cell(1, 0).unwrap().no_wrap();
484 tbl.cell(1, 1).unwrap().set_text("Fixed width");
485 tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
486 tbl.cell(1, 2).unwrap().set_text("Normal");
487 }
488
489 // ── SECTION 6: IMAGES ──
490 doc.add_paragraph("").page_break_before(true);
491 doc.add_paragraph("6. Images").style("Heading1");
492
493 doc.add_paragraph("6.1 Inline Image").style("Heading2");
494 doc.add_paragraph("A blue gradient image below:");
495 let img = create_sample_png(200, 50, [0, 80, 200]);
496 doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
497
498 doc.add_paragraph("");
499 doc.add_paragraph("6.2 Header Image").style("Heading2");
500 let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
501 doc.set_header_image(
502 &hdr_img,
503 "header_logo.png",
504 Length::inches(2.0),
505 Length::inches(0.2),
506 );
507 doc.add_paragraph("The header now contains an inline image (check the top of this page).");
508
509 doc.add_paragraph("");
510 doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
511
512 // ── SECTION 7: CONTENT MANIPULATION ──
513 doc.add_paragraph("").page_break_before(true);
514 doc.add_paragraph("7. Content Manipulation")
515 .style("Heading1");
516
517 doc.add_paragraph("7.1 Placeholder Replacement")
518 .style("Heading2");
519 doc.add_paragraph("Customer: {{customer}}");
520 doc.add_paragraph("Date: {{date}}");
521 doc.add_paragraph("Reference: {{ref_number}}");
522 {
523 let mut tbl = doc
524 .add_table(2, 2)
525 .borders(BorderStyle::Single, 4, "000000");
526 tbl.cell(0, 0).unwrap().set_text("Project");
527 tbl.cell(0, 0).unwrap().shading("D6E4F0");
528 tbl.cell(0, 1).unwrap().set_text("{{project}}");
529 tbl.cell(1, 0).unwrap().set_text("Status");
530 tbl.cell(1, 0).unwrap().shading("D6E4F0");
531 tbl.cell(1, 1).unwrap().set_text("{{status}}");
532 }
533
534 let mut replacements = HashMap::new();
535 replacements.insert("{{customer}}", "Tensorbee Inc.");
536 replacements.insert("{{date}}", "February 22, 2026");
537 replacements.insert("{{ref_number}}", "TB-2026-001");
538 replacements.insert("{{project}}", "Infrastructure Upgrade");
539 replacements.insert("{{status}}", "In Progress");
540 let n = doc.replace_all(&replacements);
541 doc.add_paragraph(&format!("({n} placeholders replaced above)"));
542
543 doc.add_paragraph("");
544
545 doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
546 doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
547 let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
548 doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
549
550 doc.add_paragraph("");
551
552 doc.add_paragraph("7.3 Content Insertion").style("Heading2");
553 doc.add_paragraph("Section A: First content.");
554 doc.add_paragraph("Section C: Third content.");
555 if let Some(idx) = doc.find_content_index("Section C") {
556 doc.insert_paragraph(
557 idx,
558 "Section B: Inserted between A and C via find_content_index().",
559 );
560 }
561
562 // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
563 doc.add_paragraph("").section_break(SectionBreak::NextPage);
564 doc.add_paragraph("8. Section Breaks & Orientation")
565 .style("Heading1");
566 doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
567
568 {
569 let mut tbl = doc
570 .add_table(3, 7)
571 .borders(BorderStyle::Single, 4, "2E75B6");
572 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
573 for (c, h) in headers.iter().enumerate() {
574 tbl.cell(0, c).unwrap().set_text(h);
575 tbl.cell(0, c).unwrap().shading("2E75B6");
576 }
577 let data = [
578 [
579 "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
580 ],
581 [
582 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
583 ],
584 ];
585 for (r, row) in data.iter().enumerate() {
586 for (c, v) in row.iter().enumerate() {
587 tbl.cell(r + 1, c).unwrap().set_text(v);
588 }
589 }
590 }
591
592 doc.add_paragraph("")
593 .section_break(SectionBreak::NextPage)
594 .section_landscape();
595
596 // ── SECTION 9: CUSTOM STYLES ──
597 doc.add_paragraph("9. Custom Styles").style("Heading1");
598 doc.add_style(
599 StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
600 .based_on("Normal")
601 .paragraph_properties(rdocx_oxml::properties::CT_PPr {
602 shading: Some(rdocx_oxml::properties::CT_Shd {
603 val: "clear".to_string(),
604 color: None,
605 fill: Some("FFF2CC".to_string()),
606 }),
607 ..Default::default()
608 })
609 .run_properties(rdocx_oxml::properties::CT_RPr {
610 bold: Some(true),
611 color: Some("C45911".to_string()),
612 ..Default::default()
613 }),
614 );
615 doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
616 .style("CustomHighlight");
617
618 doc.add_paragraph("");
619
620 // ── SECTION 10: DOCUMENT INTELLIGENCE ──
621 doc.add_paragraph("10. Document Intelligence API")
622 .style("Heading1");
623
624 let wc = doc.word_count();
625 let hc = doc.headings().len();
626 let ic = doc.images().len();
627 let lc = doc.links().len();
628 doc.add_paragraph(&format!("Word count: {wc}"));
629 doc.add_paragraph(&format!("Heading count: {hc}"));
630 doc.add_paragraph(&format!("Image count: {ic}"));
631 doc.add_paragraph(&format!("Link count: {lc}"));
632
633 let outline = doc.document_outline();
634 doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
635
636 let issues = doc.audit_accessibility();
637 doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
638 for issue in &issues {
639 doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
640 }
641
642 // ── SECTION 11: DOCUMENT MERGING ──
643 doc.add_paragraph("");
644 doc.add_paragraph("11. Document Merging").style("Heading1");
645 {
646 let mut other = Document::new();
647 other.add_paragraph("This paragraph was merged from another document using append().");
648 doc.append(&other);
649 }
650
651 doc.add_paragraph("");
652
653 // ── SUMMARY ──
654 doc.add_paragraph("Summary of Demonstrated Features")
655 .style("Heading1");
656 let features = [
657 "Page setup: size, margins, header/footer distance, gutter",
658 "Document metadata: title, author, subject, keywords",
659 "Headers and footers: text, images, different first page",
660 "Background images (full-page behind text)",
661 "Table of Contents generation with bookmarks",
662 "Text formatting: bold, italic, underline styles, strike, color, size, font",
663 "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
664 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
665 "Pagination controls: keep-with-next, keep-together, widow control",
666 "Bullet and numbered lists with nesting",
667 "Tab stops with dot/underscore/hyphen leaders",
668 "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
669 "Row properties: header rows, exact height, cant-split",
670 "Cell properties: width, no-wrap, vertical alignment",
671 "Inline images and header images",
672 "Placeholder replacement (single and batch)",
673 "Regex-based find and replace",
674 "Content insertion at specific positions",
675 "Section breaks with mixed portrait/landscape",
676 "Custom paragraph and character styles",
677 "Document intelligence: word count, headings, outline, images, links",
678 "Accessibility audit",
679 "Document merging (append)",
680 "Export: DOCX, PDF, HTML, Markdown",
681 ];
682 for f in &features {
683 doc.add_bullet_list_item(f, 0);
684 }
685 doc.add_paragraph("");
686 doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
687 .alignment(Alignment::Center)
688 .shading("E2EFDA")
689 .border_all(BorderStyle::Single, 2, "00AA00");
690
691 doc
692}Sourcepub fn links(&self) -> Vec<LinkInfo>
pub fn links(&self) -> Vec<LinkInfo>
Get information about all hyperlinks in the document.
Resolves hyperlink relationship IDs to their target URLs where possible.
Examples found in repository?
97fn generate_feature_showcase(_samples_dir: &Path) -> Document {
98 let mut doc = Document::new();
99
100 // ── Page Setup & Metadata ──
101 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
102 doc.set_margins(
103 Length::inches(1.0),
104 Length::inches(1.0),
105 Length::inches(1.0),
106 Length::inches(1.0),
107 );
108 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
109 doc.set_gutter(Length::twips(0));
110 doc.set_title("rdocx Feature Showcase");
111 doc.set_author("rdocx Sample Generator");
112 doc.set_subject("Comprehensive feature demonstration");
113 doc.set_keywords("rdocx, docx, rust, sample, showcase");
114
115 // Headers & Footers with different first page
116 doc.set_different_first_page(true);
117 doc.set_first_page_header("rdocx");
118 doc.set_first_page_footer("Feature Showcase — Cover Page");
119 doc.set_header("rdocx Feature Showcase");
120 doc.set_footer("Generated by rdocx");
121
122 // ── COVER PAGE ──
123 let bg = create_sample_png(612, 792, [20, 45, 90]);
124 doc.add_background_image(&bg, "cover_bg.png");
125
126 for _ in 0..3 {
127 doc.add_paragraph("");
128 }
129 {
130 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
131 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
132 }
133 {
134 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
135 p.add_run("Complete Feature Showcase")
136 .size(28.0)
137 .color("FFFFFF")
138 .italic(true);
139 }
140 doc.add_paragraph("");
141 {
142 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
143 p.add_run("Every feature of the rdocx Rust library")
144 .size(13.0)
145 .color("B0C4DE");
146 }
147 {
148 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
149 p.add_run("demonstrated in a single document.")
150 .size(13.0)
151 .color("B0C4DE");
152 }
153
154 // ── TABLE OF CONTENTS ──
155 doc.add_paragraph("").page_break_before(true);
156 doc.insert_toc(doc.content_count(), 3);
157
158 // ── SECTION 1: TEXT FORMATTING ──
159 doc.add_paragraph("").page_break_before(true);
160 doc.add_paragraph("1. Text Formatting").style("Heading1");
161
162 // Paragraph Alignment
163 doc.add_paragraph("1.1 Paragraph Alignment")
164 .style("Heading2");
165 doc.add_paragraph("Left-aligned paragraph (default).")
166 .alignment(Alignment::Left);
167 doc.add_paragraph("Center-aligned paragraph.")
168 .alignment(Alignment::Center);
169 doc.add_paragraph("Right-aligned paragraph.")
170 .alignment(Alignment::Right);
171 doc.add_paragraph(
172 "Justified paragraph — this text is long enough to demonstrate how justified alignment \
173 distributes extra space across word gaps so lines fill the full width of the text area.",
174 )
175 .alignment(Alignment::Justify);
176
177 doc.add_paragraph("");
178
179 // Run Formatting
180 doc.add_paragraph("1.2 Run Formatting").style("Heading2");
181 {
182 let mut p = doc.add_paragraph("");
183 p.add_run("Bold").bold(true);
184 p.add_run(" | ");
185 p.add_run("Italic").italic(true);
186 p.add_run(" | ");
187 p.add_run("Bold Italic").bold(true).italic(true);
188 p.add_run(" | ");
189 p.add_run("Underline").underline(true);
190 p.add_run(" | ");
191 p.add_run("Strikethrough").strike(true);
192 p.add_run(" | ");
193 p.add_run("Double Strike").double_strike(true);
194 }
195 {
196 let mut p = doc.add_paragraph("");
197 p.add_run("Red").color("FF0000");
198 p.add_run(" | ");
199 p.add_run("Blue").color("0000FF");
200 p.add_run(" | ");
201 p.add_run("Green").color("00AA00");
202 p.add_run(" | ");
203 p.add_run("Highlighted").highlight("FFFF00");
204 }
205 {
206 let mut p = doc.add_paragraph("");
207 p.add_run("8pt").size(8.0);
208 p.add_run(" | ");
209 p.add_run("11pt").size(11.0);
210 p.add_run(" | ");
211 p.add_run("16pt").size(16.0);
212 p.add_run(" | ");
213 p.add_run("24pt").size(24.0);
214 }
215 {
216 let mut p = doc.add_paragraph("");
217 p.add_run("Arial").font("Arial");
218 p.add_run(" | ");
219 p.add_run("Times New Roman").font("Times New Roman");
220 p.add_run(" | ");
221 p.add_run("Courier New").font("Courier New");
222 }
223 {
224 let mut p = doc.add_paragraph("");
225 p.add_run("H");
226 p.add_run("2").subscript();
227 p.add_run("O (subscript) | E=mc");
228 p.add_run("2").superscript();
229 p.add_run(" (superscript)");
230 }
231 {
232 let mut p = doc.add_paragraph("");
233 p.add_run("ALL CAPS").all_caps(true);
234 p.add_run(" | ");
235 p.add_run("Small Caps").small_caps(true);
236 p.add_run(" | ");
237 p.add_run("Expanded").character_spacing(Length::twips(40));
238 p.add_run(" | ");
239 p.add_run("Hidden text (not visible)").hidden(true);
240 }
241
242 doc.add_paragraph("");
243
244 // Underline Styles
245 doc.add_paragraph("1.3 Underline Styles").style("Heading2");
246 {
247 let mut p = doc.add_paragraph("");
248 p.add_run("Single ")
249 .underline_style(rdocx::UnderlineStyle::Single);
250 p.add_run("Double ")
251 .underline_style(rdocx::UnderlineStyle::Double);
252 p.add_run("Thick ")
253 .underline_style(rdocx::UnderlineStyle::Thick);
254 p.add_run("Dotted ")
255 .underline_style(rdocx::UnderlineStyle::Dotted);
256 p.add_run("Dash ")
257 .underline_style(rdocx::UnderlineStyle::Dash);
258 p.add_run("Wave ")
259 .underline_style(rdocx::UnderlineStyle::Wave);
260 }
261
262 // ── SECTION 2: PARAGRAPH FORMATTING ──
263 doc.add_paragraph("").page_break_before(true);
264 doc.add_paragraph("2. Paragraph Formatting")
265 .style("Heading1");
266
267 doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
268 doc.add_paragraph("Paragraph with light green shading")
269 .shading("E2EFDA");
270 doc.add_paragraph("Paragraph with bottom border")
271 .border_bottom(BorderStyle::Single, 6, "2E75B6");
272 doc.add_paragraph("Paragraph with all borders (red)")
273 .border_all(BorderStyle::Single, 4, "FF0000");
274
275 doc.add_paragraph("2.2 Indentation").style("Heading2");
276 doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
277 .indent_left(Length::inches(1.0))
278 .hanging_indent(Length::inches(0.5));
279 doc.add_paragraph("0.5-inch first-line indent on this paragraph")
280 .first_line_indent(Length::inches(0.5));
281 doc.add_paragraph("Both left and right indent (0.75 inches each)")
282 .indent_left(Length::inches(0.75))
283 .indent_right(Length::inches(0.75));
284
285 doc.add_paragraph("2.3 Spacing & Line Height")
286 .style("Heading2");
287 doc.add_paragraph("Extra space before (24pt) and after (12pt)")
288 .space_before(Length::pt(24.0))
289 .space_after(Length::pt(12.0));
290 doc.add_paragraph(
291 "Double line spacing paragraph. This text should have extra vertical space between \
292 lines to demonstrate line_spacing_multiple(2.0).",
293 )
294 .line_spacing_multiple(2.0);
295 doc.add_paragraph("Exact 20pt line spacing (fixed height).")
296 .line_spacing(20.0);
297
298 doc.add_paragraph("2.4 Pagination Controls")
299 .style("Heading2");
300 doc.add_paragraph("keep_with_next — this paragraph stays with the next")
301 .keep_with_next(true);
302 doc.add_paragraph("(This paragraph was kept with the one above.)");
303 doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
304 .keep_together(true);
305 doc.add_paragraph("widow_control — prevents widow/orphan lines")
306 .widow_control(true);
307
308 // ── SECTION 3: LISTS ──
309 doc.add_paragraph("").page_break_before(true);
310 doc.add_paragraph("3. Lists").style("Heading1");
311
312 doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
313 doc.add_bullet_list_item("First item", 0);
314 doc.add_bullet_list_item("Second item", 0);
315 doc.add_bullet_list_item("Nested level 1", 1);
316 doc.add_bullet_list_item("Nested level 2", 2);
317 doc.add_bullet_list_item("Back to level 1", 1);
318 doc.add_bullet_list_item("Third item", 0);
319
320 doc.add_paragraph("");
321
322 doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
323 doc.add_numbered_list_item("First numbered item", 0);
324 doc.add_numbered_list_item("Second numbered item", 0);
325 doc.add_numbered_list_item("Sub-item A", 1);
326 doc.add_numbered_list_item("Sub-item B", 1);
327 doc.add_numbered_list_item("Third numbered item", 0);
328
329 // ── SECTION 4: TAB STOPS ──
330 doc.add_paragraph("");
331 doc.add_paragraph("4. Tab Stops").style("Heading1");
332
333 doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
334 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
335 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
336 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
337 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
338 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
339
340 doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
341 doc.add_paragraph("Item\t\tPrice")
342 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
343 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
344 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
345 doc.add_paragraph("Widget\t\t$19.99")
346 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
347 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
348 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
349 doc.add_paragraph("Gadget\t\t$249.50")
350 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
351 .add_tab_stop_with_leader(
352 TabAlignment::Right,
353 Length::inches(4.0),
354 TabLeader::Underscore,
355 )
356 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
357
358 // ── SECTION 5: TABLES ──
359 doc.add_paragraph("").page_break_before(true);
360 doc.add_paragraph("5. Tables").style("Heading1");
361
362 doc.add_paragraph("5.1 Basic Table").style("Heading2");
363 {
364 let mut tbl = doc
365 .add_table(4, 3)
366 .borders(BorderStyle::Single, 4, "000000");
367 for col in 0..3 {
368 tbl.cell(0, col).unwrap().shading("2E75B6");
369 }
370 tbl.cell(0, 0).unwrap().set_text("Name");
371 tbl.cell(0, 1).unwrap().set_text("Role");
372 tbl.cell(0, 2).unwrap().set_text("Location");
373 tbl.cell(1, 0).unwrap().set_text("Walter White");
374 tbl.cell(1, 1).unwrap().set_text("CEO");
375 tbl.cell(1, 2).unwrap().set_text("Albuquerque");
376 tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
377 tbl.cell(2, 1).unwrap().set_text("CTO");
378 tbl.cell(2, 2).unwrap().set_text("Remote");
379 tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
380 tbl.cell(3, 1).unwrap().set_text("Security");
381 tbl.cell(3, 2).unwrap().set_text("Washington");
382 }
383
384 doc.add_paragraph("");
385
386 doc.add_paragraph("5.2 Column Span & Cell Shading")
387 .style("Heading2");
388 {
389 let mut tbl = doc
390 .add_table(3, 4)
391 .borders(BorderStyle::Single, 4, "000000")
392 .width_pct(100.0);
393 tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
394 tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
395 tbl.cell(1, 0).unwrap().set_text("Region");
396 tbl.cell(1, 0).unwrap().shading("D6E4F0");
397 tbl.cell(1, 1).unwrap().set_text("Q1");
398 tbl.cell(1, 1).unwrap().shading("D6E4F0");
399 tbl.cell(1, 2).unwrap().set_text("Q2");
400 tbl.cell(1, 2).unwrap().shading("D6E4F0");
401 tbl.cell(1, 3).unwrap().set_text("Total");
402 tbl.cell(1, 3).unwrap().shading("D6E4F0");
403 tbl.cell(2, 0).unwrap().set_text("Americas");
404 tbl.cell(2, 1).unwrap().set_text("$2.4M");
405 tbl.cell(2, 2).unwrap().set_text("$2.7M");
406 tbl.cell(2, 3).unwrap().set_text("$5.1M");
407 }
408
409 doc.add_paragraph("");
410
411 doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
412 {
413 let mut tbl = doc
414 .add_table(4, 3)
415 .borders(BorderStyle::Single, 4, "000000");
416 tbl.cell(0, 0).unwrap().set_text("Category");
417 tbl.cell(0, 0).unwrap().shading("E2EFDA");
418 tbl.cell(0, 1).unwrap().set_text("Item");
419 tbl.cell(0, 1).unwrap().shading("E2EFDA");
420 tbl.cell(0, 2).unwrap().set_text("Price");
421 tbl.cell(0, 2).unwrap().shading("E2EFDA");
422 tbl.cell(1, 0).unwrap().set_text("Hardware");
423 tbl.cell(1, 0).unwrap().v_merge_restart();
424 tbl.cell(1, 1).unwrap().set_text("Laptop");
425 tbl.cell(1, 2).unwrap().set_text("$1,200");
426 tbl.cell(2, 0).unwrap().v_merge_continue();
427 tbl.cell(2, 1).unwrap().set_text("Monitor");
428 tbl.cell(2, 2).unwrap().set_text("$450");
429 tbl.cell(3, 0).unwrap().set_text("Software");
430 tbl.cell(3, 1).unwrap().set_text("IDE License");
431 tbl.cell(3, 2).unwrap().set_text("$200/yr");
432 }
433
434 doc.add_paragraph("");
435
436 doc.add_paragraph("5.4 Nested Table").style("Heading2");
437 {
438 let mut tbl = doc
439 .add_table(2, 2)
440 .borders(BorderStyle::Single, 6, "2E75B6");
441 tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
442 tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
443 tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
444 {
445 let mut cell = tbl.cell(1, 1).unwrap();
446 cell.set_text("Nested table below:");
447 let mut nested = cell
448 .add_table(2, 2)
449 .borders(BorderStyle::Single, 2, "FF6600");
450 nested.cell(0, 0).unwrap().set_text("A");
451 nested.cell(0, 1).unwrap().set_text("B");
452 nested.cell(1, 0).unwrap().set_text("C");
453 nested.cell(1, 1).unwrap().set_text("D");
454 }
455 }
456
457 doc.add_paragraph("");
458
459 doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
460 .style("Heading2");
461 {
462 let mut tbl = doc
463 .add_table(2, 3)
464 .borders(BorderStyle::Single, 4, "666666");
465 tbl.row(0).unwrap().height(Length::pt(50.0)).header();
466 tbl.cell(0, 0).unwrap().set_text("Top");
467 tbl.cell(0, 0)
468 .unwrap()
469 .vertical_alignment(VerticalAlignment::Top)
470 .shading("FFF2CC");
471 tbl.cell(0, 1).unwrap().set_text("Center");
472 tbl.cell(0, 1)
473 .unwrap()
474 .vertical_alignment(VerticalAlignment::Center)
475 .shading("D9E2F3");
476 tbl.cell(0, 2).unwrap().set_text("Bottom");
477 tbl.cell(0, 2)
478 .unwrap()
479 .vertical_alignment(VerticalAlignment::Bottom)
480 .shading("E2EFDA");
481 tbl.row(1).unwrap().cant_split();
482 tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
483 tbl.cell(1, 0).unwrap().no_wrap();
484 tbl.cell(1, 1).unwrap().set_text("Fixed width");
485 tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
486 tbl.cell(1, 2).unwrap().set_text("Normal");
487 }
488
489 // ── SECTION 6: IMAGES ──
490 doc.add_paragraph("").page_break_before(true);
491 doc.add_paragraph("6. Images").style("Heading1");
492
493 doc.add_paragraph("6.1 Inline Image").style("Heading2");
494 doc.add_paragraph("A blue gradient image below:");
495 let img = create_sample_png(200, 50, [0, 80, 200]);
496 doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
497
498 doc.add_paragraph("");
499 doc.add_paragraph("6.2 Header Image").style("Heading2");
500 let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
501 doc.set_header_image(
502 &hdr_img,
503 "header_logo.png",
504 Length::inches(2.0),
505 Length::inches(0.2),
506 );
507 doc.add_paragraph("The header now contains an inline image (check the top of this page).");
508
509 doc.add_paragraph("");
510 doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
511
512 // ── SECTION 7: CONTENT MANIPULATION ──
513 doc.add_paragraph("").page_break_before(true);
514 doc.add_paragraph("7. Content Manipulation")
515 .style("Heading1");
516
517 doc.add_paragraph("7.1 Placeholder Replacement")
518 .style("Heading2");
519 doc.add_paragraph("Customer: {{customer}}");
520 doc.add_paragraph("Date: {{date}}");
521 doc.add_paragraph("Reference: {{ref_number}}");
522 {
523 let mut tbl = doc
524 .add_table(2, 2)
525 .borders(BorderStyle::Single, 4, "000000");
526 tbl.cell(0, 0).unwrap().set_text("Project");
527 tbl.cell(0, 0).unwrap().shading("D6E4F0");
528 tbl.cell(0, 1).unwrap().set_text("{{project}}");
529 tbl.cell(1, 0).unwrap().set_text("Status");
530 tbl.cell(1, 0).unwrap().shading("D6E4F0");
531 tbl.cell(1, 1).unwrap().set_text("{{status}}");
532 }
533
534 let mut replacements = HashMap::new();
535 replacements.insert("{{customer}}", "Tensorbee Inc.");
536 replacements.insert("{{date}}", "February 22, 2026");
537 replacements.insert("{{ref_number}}", "TB-2026-001");
538 replacements.insert("{{project}}", "Infrastructure Upgrade");
539 replacements.insert("{{status}}", "In Progress");
540 let n = doc.replace_all(&replacements);
541 doc.add_paragraph(&format!("({n} placeholders replaced above)"));
542
543 doc.add_paragraph("");
544
545 doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
546 doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
547 let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
548 doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
549
550 doc.add_paragraph("");
551
552 doc.add_paragraph("7.3 Content Insertion").style("Heading2");
553 doc.add_paragraph("Section A: First content.");
554 doc.add_paragraph("Section C: Third content.");
555 if let Some(idx) = doc.find_content_index("Section C") {
556 doc.insert_paragraph(
557 idx,
558 "Section B: Inserted between A and C via find_content_index().",
559 );
560 }
561
562 // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
563 doc.add_paragraph("").section_break(SectionBreak::NextPage);
564 doc.add_paragraph("8. Section Breaks & Orientation")
565 .style("Heading1");
566 doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
567
568 {
569 let mut tbl = doc
570 .add_table(3, 7)
571 .borders(BorderStyle::Single, 4, "2E75B6");
572 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
573 for (c, h) in headers.iter().enumerate() {
574 tbl.cell(0, c).unwrap().set_text(h);
575 tbl.cell(0, c).unwrap().shading("2E75B6");
576 }
577 let data = [
578 [
579 "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
580 ],
581 [
582 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
583 ],
584 ];
585 for (r, row) in data.iter().enumerate() {
586 for (c, v) in row.iter().enumerate() {
587 tbl.cell(r + 1, c).unwrap().set_text(v);
588 }
589 }
590 }
591
592 doc.add_paragraph("")
593 .section_break(SectionBreak::NextPage)
594 .section_landscape();
595
596 // ── SECTION 9: CUSTOM STYLES ──
597 doc.add_paragraph("9. Custom Styles").style("Heading1");
598 doc.add_style(
599 StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
600 .based_on("Normal")
601 .paragraph_properties(rdocx_oxml::properties::CT_PPr {
602 shading: Some(rdocx_oxml::properties::CT_Shd {
603 val: "clear".to_string(),
604 color: None,
605 fill: Some("FFF2CC".to_string()),
606 }),
607 ..Default::default()
608 })
609 .run_properties(rdocx_oxml::properties::CT_RPr {
610 bold: Some(true),
611 color: Some("C45911".to_string()),
612 ..Default::default()
613 }),
614 );
615 doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
616 .style("CustomHighlight");
617
618 doc.add_paragraph("");
619
620 // ── SECTION 10: DOCUMENT INTELLIGENCE ──
621 doc.add_paragraph("10. Document Intelligence API")
622 .style("Heading1");
623
624 let wc = doc.word_count();
625 let hc = doc.headings().len();
626 let ic = doc.images().len();
627 let lc = doc.links().len();
628 doc.add_paragraph(&format!("Word count: {wc}"));
629 doc.add_paragraph(&format!("Heading count: {hc}"));
630 doc.add_paragraph(&format!("Image count: {ic}"));
631 doc.add_paragraph(&format!("Link count: {lc}"));
632
633 let outline = doc.document_outline();
634 doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
635
636 let issues = doc.audit_accessibility();
637 doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
638 for issue in &issues {
639 doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
640 }
641
642 // ── SECTION 11: DOCUMENT MERGING ──
643 doc.add_paragraph("");
644 doc.add_paragraph("11. Document Merging").style("Heading1");
645 {
646 let mut other = Document::new();
647 other.add_paragraph("This paragraph was merged from another document using append().");
648 doc.append(&other);
649 }
650
651 doc.add_paragraph("");
652
653 // ── SUMMARY ──
654 doc.add_paragraph("Summary of Demonstrated Features")
655 .style("Heading1");
656 let features = [
657 "Page setup: size, margins, header/footer distance, gutter",
658 "Document metadata: title, author, subject, keywords",
659 "Headers and footers: text, images, different first page",
660 "Background images (full-page behind text)",
661 "Table of Contents generation with bookmarks",
662 "Text formatting: bold, italic, underline styles, strike, color, size, font",
663 "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
664 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
665 "Pagination controls: keep-with-next, keep-together, widow control",
666 "Bullet and numbered lists with nesting",
667 "Tab stops with dot/underscore/hyphen leaders",
668 "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
669 "Row properties: header rows, exact height, cant-split",
670 "Cell properties: width, no-wrap, vertical alignment",
671 "Inline images and header images",
672 "Placeholder replacement (single and batch)",
673 "Regex-based find and replace",
674 "Content insertion at specific positions",
675 "Section breaks with mixed portrait/landscape",
676 "Custom paragraph and character styles",
677 "Document intelligence: word count, headings, outline, images, links",
678 "Accessibility audit",
679 "Document merging (append)",
680 "Export: DOCX, PDF, HTML, Markdown",
681 ];
682 for f in &features {
683 doc.add_bullet_list_item(f, 0);
684 }
685 doc.add_paragraph("");
686 doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
687 .alignment(Alignment::Center)
688 .shading("E2EFDA")
689 .border_all(BorderStyle::Single, 2, "00AA00");
690
691 doc
692}Sourcepub fn word_count(&self) -> usize
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?
97fn generate_feature_showcase(_samples_dir: &Path) -> Document {
98 let mut doc = Document::new();
99
100 // ── Page Setup & Metadata ──
101 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
102 doc.set_margins(
103 Length::inches(1.0),
104 Length::inches(1.0),
105 Length::inches(1.0),
106 Length::inches(1.0),
107 );
108 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
109 doc.set_gutter(Length::twips(0));
110 doc.set_title("rdocx Feature Showcase");
111 doc.set_author("rdocx Sample Generator");
112 doc.set_subject("Comprehensive feature demonstration");
113 doc.set_keywords("rdocx, docx, rust, sample, showcase");
114
115 // Headers & Footers with different first page
116 doc.set_different_first_page(true);
117 doc.set_first_page_header("rdocx");
118 doc.set_first_page_footer("Feature Showcase — Cover Page");
119 doc.set_header("rdocx Feature Showcase");
120 doc.set_footer("Generated by rdocx");
121
122 // ── COVER PAGE ──
123 let bg = create_sample_png(612, 792, [20, 45, 90]);
124 doc.add_background_image(&bg, "cover_bg.png");
125
126 for _ in 0..3 {
127 doc.add_paragraph("");
128 }
129 {
130 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
131 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
132 }
133 {
134 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
135 p.add_run("Complete Feature Showcase")
136 .size(28.0)
137 .color("FFFFFF")
138 .italic(true);
139 }
140 doc.add_paragraph("");
141 {
142 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
143 p.add_run("Every feature of the rdocx Rust library")
144 .size(13.0)
145 .color("B0C4DE");
146 }
147 {
148 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
149 p.add_run("demonstrated in a single document.")
150 .size(13.0)
151 .color("B0C4DE");
152 }
153
154 // ── TABLE OF CONTENTS ──
155 doc.add_paragraph("").page_break_before(true);
156 doc.insert_toc(doc.content_count(), 3);
157
158 // ── SECTION 1: TEXT FORMATTING ──
159 doc.add_paragraph("").page_break_before(true);
160 doc.add_paragraph("1. Text Formatting").style("Heading1");
161
162 // Paragraph Alignment
163 doc.add_paragraph("1.1 Paragraph Alignment")
164 .style("Heading2");
165 doc.add_paragraph("Left-aligned paragraph (default).")
166 .alignment(Alignment::Left);
167 doc.add_paragraph("Center-aligned paragraph.")
168 .alignment(Alignment::Center);
169 doc.add_paragraph("Right-aligned paragraph.")
170 .alignment(Alignment::Right);
171 doc.add_paragraph(
172 "Justified paragraph — this text is long enough to demonstrate how justified alignment \
173 distributes extra space across word gaps so lines fill the full width of the text area.",
174 )
175 .alignment(Alignment::Justify);
176
177 doc.add_paragraph("");
178
179 // Run Formatting
180 doc.add_paragraph("1.2 Run Formatting").style("Heading2");
181 {
182 let mut p = doc.add_paragraph("");
183 p.add_run("Bold").bold(true);
184 p.add_run(" | ");
185 p.add_run("Italic").italic(true);
186 p.add_run(" | ");
187 p.add_run("Bold Italic").bold(true).italic(true);
188 p.add_run(" | ");
189 p.add_run("Underline").underline(true);
190 p.add_run(" | ");
191 p.add_run("Strikethrough").strike(true);
192 p.add_run(" | ");
193 p.add_run("Double Strike").double_strike(true);
194 }
195 {
196 let mut p = doc.add_paragraph("");
197 p.add_run("Red").color("FF0000");
198 p.add_run(" | ");
199 p.add_run("Blue").color("0000FF");
200 p.add_run(" | ");
201 p.add_run("Green").color("00AA00");
202 p.add_run(" | ");
203 p.add_run("Highlighted").highlight("FFFF00");
204 }
205 {
206 let mut p = doc.add_paragraph("");
207 p.add_run("8pt").size(8.0);
208 p.add_run(" | ");
209 p.add_run("11pt").size(11.0);
210 p.add_run(" | ");
211 p.add_run("16pt").size(16.0);
212 p.add_run(" | ");
213 p.add_run("24pt").size(24.0);
214 }
215 {
216 let mut p = doc.add_paragraph("");
217 p.add_run("Arial").font("Arial");
218 p.add_run(" | ");
219 p.add_run("Times New Roman").font("Times New Roman");
220 p.add_run(" | ");
221 p.add_run("Courier New").font("Courier New");
222 }
223 {
224 let mut p = doc.add_paragraph("");
225 p.add_run("H");
226 p.add_run("2").subscript();
227 p.add_run("O (subscript) | E=mc");
228 p.add_run("2").superscript();
229 p.add_run(" (superscript)");
230 }
231 {
232 let mut p = doc.add_paragraph("");
233 p.add_run("ALL CAPS").all_caps(true);
234 p.add_run(" | ");
235 p.add_run("Small Caps").small_caps(true);
236 p.add_run(" | ");
237 p.add_run("Expanded").character_spacing(Length::twips(40));
238 p.add_run(" | ");
239 p.add_run("Hidden text (not visible)").hidden(true);
240 }
241
242 doc.add_paragraph("");
243
244 // Underline Styles
245 doc.add_paragraph("1.3 Underline Styles").style("Heading2");
246 {
247 let mut p = doc.add_paragraph("");
248 p.add_run("Single ")
249 .underline_style(rdocx::UnderlineStyle::Single);
250 p.add_run("Double ")
251 .underline_style(rdocx::UnderlineStyle::Double);
252 p.add_run("Thick ")
253 .underline_style(rdocx::UnderlineStyle::Thick);
254 p.add_run("Dotted ")
255 .underline_style(rdocx::UnderlineStyle::Dotted);
256 p.add_run("Dash ")
257 .underline_style(rdocx::UnderlineStyle::Dash);
258 p.add_run("Wave ")
259 .underline_style(rdocx::UnderlineStyle::Wave);
260 }
261
262 // ── SECTION 2: PARAGRAPH FORMATTING ──
263 doc.add_paragraph("").page_break_before(true);
264 doc.add_paragraph("2. Paragraph Formatting")
265 .style("Heading1");
266
267 doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
268 doc.add_paragraph("Paragraph with light green shading")
269 .shading("E2EFDA");
270 doc.add_paragraph("Paragraph with bottom border")
271 .border_bottom(BorderStyle::Single, 6, "2E75B6");
272 doc.add_paragraph("Paragraph with all borders (red)")
273 .border_all(BorderStyle::Single, 4, "FF0000");
274
275 doc.add_paragraph("2.2 Indentation").style("Heading2");
276 doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
277 .indent_left(Length::inches(1.0))
278 .hanging_indent(Length::inches(0.5));
279 doc.add_paragraph("0.5-inch first-line indent on this paragraph")
280 .first_line_indent(Length::inches(0.5));
281 doc.add_paragraph("Both left and right indent (0.75 inches each)")
282 .indent_left(Length::inches(0.75))
283 .indent_right(Length::inches(0.75));
284
285 doc.add_paragraph("2.3 Spacing & Line Height")
286 .style("Heading2");
287 doc.add_paragraph("Extra space before (24pt) and after (12pt)")
288 .space_before(Length::pt(24.0))
289 .space_after(Length::pt(12.0));
290 doc.add_paragraph(
291 "Double line spacing paragraph. This text should have extra vertical space between \
292 lines to demonstrate line_spacing_multiple(2.0).",
293 )
294 .line_spacing_multiple(2.0);
295 doc.add_paragraph("Exact 20pt line spacing (fixed height).")
296 .line_spacing(20.0);
297
298 doc.add_paragraph("2.4 Pagination Controls")
299 .style("Heading2");
300 doc.add_paragraph("keep_with_next — this paragraph stays with the next")
301 .keep_with_next(true);
302 doc.add_paragraph("(This paragraph was kept with the one above.)");
303 doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
304 .keep_together(true);
305 doc.add_paragraph("widow_control — prevents widow/orphan lines")
306 .widow_control(true);
307
308 // ── SECTION 3: LISTS ──
309 doc.add_paragraph("").page_break_before(true);
310 doc.add_paragraph("3. Lists").style("Heading1");
311
312 doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
313 doc.add_bullet_list_item("First item", 0);
314 doc.add_bullet_list_item("Second item", 0);
315 doc.add_bullet_list_item("Nested level 1", 1);
316 doc.add_bullet_list_item("Nested level 2", 2);
317 doc.add_bullet_list_item("Back to level 1", 1);
318 doc.add_bullet_list_item("Third item", 0);
319
320 doc.add_paragraph("");
321
322 doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
323 doc.add_numbered_list_item("First numbered item", 0);
324 doc.add_numbered_list_item("Second numbered item", 0);
325 doc.add_numbered_list_item("Sub-item A", 1);
326 doc.add_numbered_list_item("Sub-item B", 1);
327 doc.add_numbered_list_item("Third numbered item", 0);
328
329 // ── SECTION 4: TAB STOPS ──
330 doc.add_paragraph("");
331 doc.add_paragraph("4. Tab Stops").style("Heading1");
332
333 doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
334 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
335 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
336 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
337 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
338 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
339
340 doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
341 doc.add_paragraph("Item\t\tPrice")
342 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
343 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
344 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
345 doc.add_paragraph("Widget\t\t$19.99")
346 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
347 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
348 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
349 doc.add_paragraph("Gadget\t\t$249.50")
350 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
351 .add_tab_stop_with_leader(
352 TabAlignment::Right,
353 Length::inches(4.0),
354 TabLeader::Underscore,
355 )
356 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
357
358 // ── SECTION 5: TABLES ──
359 doc.add_paragraph("").page_break_before(true);
360 doc.add_paragraph("5. Tables").style("Heading1");
361
362 doc.add_paragraph("5.1 Basic Table").style("Heading2");
363 {
364 let mut tbl = doc
365 .add_table(4, 3)
366 .borders(BorderStyle::Single, 4, "000000");
367 for col in 0..3 {
368 tbl.cell(0, col).unwrap().shading("2E75B6");
369 }
370 tbl.cell(0, 0).unwrap().set_text("Name");
371 tbl.cell(0, 1).unwrap().set_text("Role");
372 tbl.cell(0, 2).unwrap().set_text("Location");
373 tbl.cell(1, 0).unwrap().set_text("Walter White");
374 tbl.cell(1, 1).unwrap().set_text("CEO");
375 tbl.cell(1, 2).unwrap().set_text("Albuquerque");
376 tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
377 tbl.cell(2, 1).unwrap().set_text("CTO");
378 tbl.cell(2, 2).unwrap().set_text("Remote");
379 tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
380 tbl.cell(3, 1).unwrap().set_text("Security");
381 tbl.cell(3, 2).unwrap().set_text("Washington");
382 }
383
384 doc.add_paragraph("");
385
386 doc.add_paragraph("5.2 Column Span & Cell Shading")
387 .style("Heading2");
388 {
389 let mut tbl = doc
390 .add_table(3, 4)
391 .borders(BorderStyle::Single, 4, "000000")
392 .width_pct(100.0);
393 tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
394 tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
395 tbl.cell(1, 0).unwrap().set_text("Region");
396 tbl.cell(1, 0).unwrap().shading("D6E4F0");
397 tbl.cell(1, 1).unwrap().set_text("Q1");
398 tbl.cell(1, 1).unwrap().shading("D6E4F0");
399 tbl.cell(1, 2).unwrap().set_text("Q2");
400 tbl.cell(1, 2).unwrap().shading("D6E4F0");
401 tbl.cell(1, 3).unwrap().set_text("Total");
402 tbl.cell(1, 3).unwrap().shading("D6E4F0");
403 tbl.cell(2, 0).unwrap().set_text("Americas");
404 tbl.cell(2, 1).unwrap().set_text("$2.4M");
405 tbl.cell(2, 2).unwrap().set_text("$2.7M");
406 tbl.cell(2, 3).unwrap().set_text("$5.1M");
407 }
408
409 doc.add_paragraph("");
410
411 doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
412 {
413 let mut tbl = doc
414 .add_table(4, 3)
415 .borders(BorderStyle::Single, 4, "000000");
416 tbl.cell(0, 0).unwrap().set_text("Category");
417 tbl.cell(0, 0).unwrap().shading("E2EFDA");
418 tbl.cell(0, 1).unwrap().set_text("Item");
419 tbl.cell(0, 1).unwrap().shading("E2EFDA");
420 tbl.cell(0, 2).unwrap().set_text("Price");
421 tbl.cell(0, 2).unwrap().shading("E2EFDA");
422 tbl.cell(1, 0).unwrap().set_text("Hardware");
423 tbl.cell(1, 0).unwrap().v_merge_restart();
424 tbl.cell(1, 1).unwrap().set_text("Laptop");
425 tbl.cell(1, 2).unwrap().set_text("$1,200");
426 tbl.cell(2, 0).unwrap().v_merge_continue();
427 tbl.cell(2, 1).unwrap().set_text("Monitor");
428 tbl.cell(2, 2).unwrap().set_text("$450");
429 tbl.cell(3, 0).unwrap().set_text("Software");
430 tbl.cell(3, 1).unwrap().set_text("IDE License");
431 tbl.cell(3, 2).unwrap().set_text("$200/yr");
432 }
433
434 doc.add_paragraph("");
435
436 doc.add_paragraph("5.4 Nested Table").style("Heading2");
437 {
438 let mut tbl = doc
439 .add_table(2, 2)
440 .borders(BorderStyle::Single, 6, "2E75B6");
441 tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
442 tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
443 tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
444 {
445 let mut cell = tbl.cell(1, 1).unwrap();
446 cell.set_text("Nested table below:");
447 let mut nested = cell
448 .add_table(2, 2)
449 .borders(BorderStyle::Single, 2, "FF6600");
450 nested.cell(0, 0).unwrap().set_text("A");
451 nested.cell(0, 1).unwrap().set_text("B");
452 nested.cell(1, 0).unwrap().set_text("C");
453 nested.cell(1, 1).unwrap().set_text("D");
454 }
455 }
456
457 doc.add_paragraph("");
458
459 doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
460 .style("Heading2");
461 {
462 let mut tbl = doc
463 .add_table(2, 3)
464 .borders(BorderStyle::Single, 4, "666666");
465 tbl.row(0).unwrap().height(Length::pt(50.0)).header();
466 tbl.cell(0, 0).unwrap().set_text("Top");
467 tbl.cell(0, 0)
468 .unwrap()
469 .vertical_alignment(VerticalAlignment::Top)
470 .shading("FFF2CC");
471 tbl.cell(0, 1).unwrap().set_text("Center");
472 tbl.cell(0, 1)
473 .unwrap()
474 .vertical_alignment(VerticalAlignment::Center)
475 .shading("D9E2F3");
476 tbl.cell(0, 2).unwrap().set_text("Bottom");
477 tbl.cell(0, 2)
478 .unwrap()
479 .vertical_alignment(VerticalAlignment::Bottom)
480 .shading("E2EFDA");
481 tbl.row(1).unwrap().cant_split();
482 tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
483 tbl.cell(1, 0).unwrap().no_wrap();
484 tbl.cell(1, 1).unwrap().set_text("Fixed width");
485 tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
486 tbl.cell(1, 2).unwrap().set_text("Normal");
487 }
488
489 // ── SECTION 6: IMAGES ──
490 doc.add_paragraph("").page_break_before(true);
491 doc.add_paragraph("6. Images").style("Heading1");
492
493 doc.add_paragraph("6.1 Inline Image").style("Heading2");
494 doc.add_paragraph("A blue gradient image below:");
495 let img = create_sample_png(200, 50, [0, 80, 200]);
496 doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
497
498 doc.add_paragraph("");
499 doc.add_paragraph("6.2 Header Image").style("Heading2");
500 let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
501 doc.set_header_image(
502 &hdr_img,
503 "header_logo.png",
504 Length::inches(2.0),
505 Length::inches(0.2),
506 );
507 doc.add_paragraph("The header now contains an inline image (check the top of this page).");
508
509 doc.add_paragraph("");
510 doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
511
512 // ── SECTION 7: CONTENT MANIPULATION ──
513 doc.add_paragraph("").page_break_before(true);
514 doc.add_paragraph("7. Content Manipulation")
515 .style("Heading1");
516
517 doc.add_paragraph("7.1 Placeholder Replacement")
518 .style("Heading2");
519 doc.add_paragraph("Customer: {{customer}}");
520 doc.add_paragraph("Date: {{date}}");
521 doc.add_paragraph("Reference: {{ref_number}}");
522 {
523 let mut tbl = doc
524 .add_table(2, 2)
525 .borders(BorderStyle::Single, 4, "000000");
526 tbl.cell(0, 0).unwrap().set_text("Project");
527 tbl.cell(0, 0).unwrap().shading("D6E4F0");
528 tbl.cell(0, 1).unwrap().set_text("{{project}}");
529 tbl.cell(1, 0).unwrap().set_text("Status");
530 tbl.cell(1, 0).unwrap().shading("D6E4F0");
531 tbl.cell(1, 1).unwrap().set_text("{{status}}");
532 }
533
534 let mut replacements = HashMap::new();
535 replacements.insert("{{customer}}", "Tensorbee Inc.");
536 replacements.insert("{{date}}", "February 22, 2026");
537 replacements.insert("{{ref_number}}", "TB-2026-001");
538 replacements.insert("{{project}}", "Infrastructure Upgrade");
539 replacements.insert("{{status}}", "In Progress");
540 let n = doc.replace_all(&replacements);
541 doc.add_paragraph(&format!("({n} placeholders replaced above)"));
542
543 doc.add_paragraph("");
544
545 doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
546 doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
547 let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
548 doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
549
550 doc.add_paragraph("");
551
552 doc.add_paragraph("7.3 Content Insertion").style("Heading2");
553 doc.add_paragraph("Section A: First content.");
554 doc.add_paragraph("Section C: Third content.");
555 if let Some(idx) = doc.find_content_index("Section C") {
556 doc.insert_paragraph(
557 idx,
558 "Section B: Inserted between A and C via find_content_index().",
559 );
560 }
561
562 // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
563 doc.add_paragraph("").section_break(SectionBreak::NextPage);
564 doc.add_paragraph("8. Section Breaks & Orientation")
565 .style("Heading1");
566 doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
567
568 {
569 let mut tbl = doc
570 .add_table(3, 7)
571 .borders(BorderStyle::Single, 4, "2E75B6");
572 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
573 for (c, h) in headers.iter().enumerate() {
574 tbl.cell(0, c).unwrap().set_text(h);
575 tbl.cell(0, c).unwrap().shading("2E75B6");
576 }
577 let data = [
578 [
579 "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
580 ],
581 [
582 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
583 ],
584 ];
585 for (r, row) in data.iter().enumerate() {
586 for (c, v) in row.iter().enumerate() {
587 tbl.cell(r + 1, c).unwrap().set_text(v);
588 }
589 }
590 }
591
592 doc.add_paragraph("")
593 .section_break(SectionBreak::NextPage)
594 .section_landscape();
595
596 // ── SECTION 9: CUSTOM STYLES ──
597 doc.add_paragraph("9. Custom Styles").style("Heading1");
598 doc.add_style(
599 StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
600 .based_on("Normal")
601 .paragraph_properties(rdocx_oxml::properties::CT_PPr {
602 shading: Some(rdocx_oxml::properties::CT_Shd {
603 val: "clear".to_string(),
604 color: None,
605 fill: Some("FFF2CC".to_string()),
606 }),
607 ..Default::default()
608 })
609 .run_properties(rdocx_oxml::properties::CT_RPr {
610 bold: Some(true),
611 color: Some("C45911".to_string()),
612 ..Default::default()
613 }),
614 );
615 doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
616 .style("CustomHighlight");
617
618 doc.add_paragraph("");
619
620 // ── SECTION 10: DOCUMENT INTELLIGENCE ──
621 doc.add_paragraph("10. Document Intelligence API")
622 .style("Heading1");
623
624 let wc = doc.word_count();
625 let hc = doc.headings().len();
626 let ic = doc.images().len();
627 let lc = doc.links().len();
628 doc.add_paragraph(&format!("Word count: {wc}"));
629 doc.add_paragraph(&format!("Heading count: {hc}"));
630 doc.add_paragraph(&format!("Image count: {ic}"));
631 doc.add_paragraph(&format!("Link count: {lc}"));
632
633 let outline = doc.document_outline();
634 doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
635
636 let issues = doc.audit_accessibility();
637 doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
638 for issue in &issues {
639 doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
640 }
641
642 // ── SECTION 11: DOCUMENT MERGING ──
643 doc.add_paragraph("");
644 doc.add_paragraph("11. Document Merging").style("Heading1");
645 {
646 let mut other = Document::new();
647 other.add_paragraph("This paragraph was merged from another document using append().");
648 doc.append(&other);
649 }
650
651 doc.add_paragraph("");
652
653 // ── SUMMARY ──
654 doc.add_paragraph("Summary of Demonstrated Features")
655 .style("Heading1");
656 let features = [
657 "Page setup: size, margins, header/footer distance, gutter",
658 "Document metadata: title, author, subject, keywords",
659 "Headers and footers: text, images, different first page",
660 "Background images (full-page behind text)",
661 "Table of Contents generation with bookmarks",
662 "Text formatting: bold, italic, underline styles, strike, color, size, font",
663 "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
664 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
665 "Pagination controls: keep-with-next, keep-together, widow control",
666 "Bullet and numbered lists with nesting",
667 "Tab stops with dot/underscore/hyphen leaders",
668 "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
669 "Row properties: header rows, exact height, cant-split",
670 "Cell properties: width, no-wrap, vertical alignment",
671 "Inline images and header images",
672 "Placeholder replacement (single and batch)",
673 "Regex-based find and replace",
674 "Content insertion at specific positions",
675 "Section breaks with mixed portrait/landscape",
676 "Custom paragraph and character styles",
677 "Document intelligence: word count, headings, outline, images, links",
678 "Accessibility audit",
679 "Document merging (append)",
680 "Export: DOCX, PDF, HTML, Markdown",
681 ];
682 for f in &features {
683 doc.add_bullet_list_item(f, 0);
684 }
685 doc.add_paragraph("");
686 doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
687 .alignment(Alignment::Center)
688 .shading("E2EFDA")
689 .border_all(BorderStyle::Single, 2, "00AA00");
690
691 doc
692}Sourcepub fn audit_accessibility(&self) -> Vec<AccessibilityIssue>
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?
97fn generate_feature_showcase(_samples_dir: &Path) -> Document {
98 let mut doc = Document::new();
99
100 // ── Page Setup & Metadata ──
101 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
102 doc.set_margins(
103 Length::inches(1.0),
104 Length::inches(1.0),
105 Length::inches(1.0),
106 Length::inches(1.0),
107 );
108 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
109 doc.set_gutter(Length::twips(0));
110 doc.set_title("rdocx Feature Showcase");
111 doc.set_author("rdocx Sample Generator");
112 doc.set_subject("Comprehensive feature demonstration");
113 doc.set_keywords("rdocx, docx, rust, sample, showcase");
114
115 // Headers & Footers with different first page
116 doc.set_different_first_page(true);
117 doc.set_first_page_header("rdocx");
118 doc.set_first_page_footer("Feature Showcase — Cover Page");
119 doc.set_header("rdocx Feature Showcase");
120 doc.set_footer("Generated by rdocx");
121
122 // ── COVER PAGE ──
123 let bg = create_sample_png(612, 792, [20, 45, 90]);
124 doc.add_background_image(&bg, "cover_bg.png");
125
126 for _ in 0..3 {
127 doc.add_paragraph("");
128 }
129 {
130 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
131 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
132 }
133 {
134 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
135 p.add_run("Complete Feature Showcase")
136 .size(28.0)
137 .color("FFFFFF")
138 .italic(true);
139 }
140 doc.add_paragraph("");
141 {
142 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
143 p.add_run("Every feature of the rdocx Rust library")
144 .size(13.0)
145 .color("B0C4DE");
146 }
147 {
148 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
149 p.add_run("demonstrated in a single document.")
150 .size(13.0)
151 .color("B0C4DE");
152 }
153
154 // ── TABLE OF CONTENTS ──
155 doc.add_paragraph("").page_break_before(true);
156 doc.insert_toc(doc.content_count(), 3);
157
158 // ── SECTION 1: TEXT FORMATTING ──
159 doc.add_paragraph("").page_break_before(true);
160 doc.add_paragraph("1. Text Formatting").style("Heading1");
161
162 // Paragraph Alignment
163 doc.add_paragraph("1.1 Paragraph Alignment")
164 .style("Heading2");
165 doc.add_paragraph("Left-aligned paragraph (default).")
166 .alignment(Alignment::Left);
167 doc.add_paragraph("Center-aligned paragraph.")
168 .alignment(Alignment::Center);
169 doc.add_paragraph("Right-aligned paragraph.")
170 .alignment(Alignment::Right);
171 doc.add_paragraph(
172 "Justified paragraph — this text is long enough to demonstrate how justified alignment \
173 distributes extra space across word gaps so lines fill the full width of the text area.",
174 )
175 .alignment(Alignment::Justify);
176
177 doc.add_paragraph("");
178
179 // Run Formatting
180 doc.add_paragraph("1.2 Run Formatting").style("Heading2");
181 {
182 let mut p = doc.add_paragraph("");
183 p.add_run("Bold").bold(true);
184 p.add_run(" | ");
185 p.add_run("Italic").italic(true);
186 p.add_run(" | ");
187 p.add_run("Bold Italic").bold(true).italic(true);
188 p.add_run(" | ");
189 p.add_run("Underline").underline(true);
190 p.add_run(" | ");
191 p.add_run("Strikethrough").strike(true);
192 p.add_run(" | ");
193 p.add_run("Double Strike").double_strike(true);
194 }
195 {
196 let mut p = doc.add_paragraph("");
197 p.add_run("Red").color("FF0000");
198 p.add_run(" | ");
199 p.add_run("Blue").color("0000FF");
200 p.add_run(" | ");
201 p.add_run("Green").color("00AA00");
202 p.add_run(" | ");
203 p.add_run("Highlighted").highlight("FFFF00");
204 }
205 {
206 let mut p = doc.add_paragraph("");
207 p.add_run("8pt").size(8.0);
208 p.add_run(" | ");
209 p.add_run("11pt").size(11.0);
210 p.add_run(" | ");
211 p.add_run("16pt").size(16.0);
212 p.add_run(" | ");
213 p.add_run("24pt").size(24.0);
214 }
215 {
216 let mut p = doc.add_paragraph("");
217 p.add_run("Arial").font("Arial");
218 p.add_run(" | ");
219 p.add_run("Times New Roman").font("Times New Roman");
220 p.add_run(" | ");
221 p.add_run("Courier New").font("Courier New");
222 }
223 {
224 let mut p = doc.add_paragraph("");
225 p.add_run("H");
226 p.add_run("2").subscript();
227 p.add_run("O (subscript) | E=mc");
228 p.add_run("2").superscript();
229 p.add_run(" (superscript)");
230 }
231 {
232 let mut p = doc.add_paragraph("");
233 p.add_run("ALL CAPS").all_caps(true);
234 p.add_run(" | ");
235 p.add_run("Small Caps").small_caps(true);
236 p.add_run(" | ");
237 p.add_run("Expanded").character_spacing(Length::twips(40));
238 p.add_run(" | ");
239 p.add_run("Hidden text (not visible)").hidden(true);
240 }
241
242 doc.add_paragraph("");
243
244 // Underline Styles
245 doc.add_paragraph("1.3 Underline Styles").style("Heading2");
246 {
247 let mut p = doc.add_paragraph("");
248 p.add_run("Single ")
249 .underline_style(rdocx::UnderlineStyle::Single);
250 p.add_run("Double ")
251 .underline_style(rdocx::UnderlineStyle::Double);
252 p.add_run("Thick ")
253 .underline_style(rdocx::UnderlineStyle::Thick);
254 p.add_run("Dotted ")
255 .underline_style(rdocx::UnderlineStyle::Dotted);
256 p.add_run("Dash ")
257 .underline_style(rdocx::UnderlineStyle::Dash);
258 p.add_run("Wave ")
259 .underline_style(rdocx::UnderlineStyle::Wave);
260 }
261
262 // ── SECTION 2: PARAGRAPH FORMATTING ──
263 doc.add_paragraph("").page_break_before(true);
264 doc.add_paragraph("2. Paragraph Formatting")
265 .style("Heading1");
266
267 doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
268 doc.add_paragraph("Paragraph with light green shading")
269 .shading("E2EFDA");
270 doc.add_paragraph("Paragraph with bottom border")
271 .border_bottom(BorderStyle::Single, 6, "2E75B6");
272 doc.add_paragraph("Paragraph with all borders (red)")
273 .border_all(BorderStyle::Single, 4, "FF0000");
274
275 doc.add_paragraph("2.2 Indentation").style("Heading2");
276 doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
277 .indent_left(Length::inches(1.0))
278 .hanging_indent(Length::inches(0.5));
279 doc.add_paragraph("0.5-inch first-line indent on this paragraph")
280 .first_line_indent(Length::inches(0.5));
281 doc.add_paragraph("Both left and right indent (0.75 inches each)")
282 .indent_left(Length::inches(0.75))
283 .indent_right(Length::inches(0.75));
284
285 doc.add_paragraph("2.3 Spacing & Line Height")
286 .style("Heading2");
287 doc.add_paragraph("Extra space before (24pt) and after (12pt)")
288 .space_before(Length::pt(24.0))
289 .space_after(Length::pt(12.0));
290 doc.add_paragraph(
291 "Double line spacing paragraph. This text should have extra vertical space between \
292 lines to demonstrate line_spacing_multiple(2.0).",
293 )
294 .line_spacing_multiple(2.0);
295 doc.add_paragraph("Exact 20pt line spacing (fixed height).")
296 .line_spacing(20.0);
297
298 doc.add_paragraph("2.4 Pagination Controls")
299 .style("Heading2");
300 doc.add_paragraph("keep_with_next — this paragraph stays with the next")
301 .keep_with_next(true);
302 doc.add_paragraph("(This paragraph was kept with the one above.)");
303 doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
304 .keep_together(true);
305 doc.add_paragraph("widow_control — prevents widow/orphan lines")
306 .widow_control(true);
307
308 // ── SECTION 3: LISTS ──
309 doc.add_paragraph("").page_break_before(true);
310 doc.add_paragraph("3. Lists").style("Heading1");
311
312 doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
313 doc.add_bullet_list_item("First item", 0);
314 doc.add_bullet_list_item("Second item", 0);
315 doc.add_bullet_list_item("Nested level 1", 1);
316 doc.add_bullet_list_item("Nested level 2", 2);
317 doc.add_bullet_list_item("Back to level 1", 1);
318 doc.add_bullet_list_item("Third item", 0);
319
320 doc.add_paragraph("");
321
322 doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
323 doc.add_numbered_list_item("First numbered item", 0);
324 doc.add_numbered_list_item("Second numbered item", 0);
325 doc.add_numbered_list_item("Sub-item A", 1);
326 doc.add_numbered_list_item("Sub-item B", 1);
327 doc.add_numbered_list_item("Third numbered item", 0);
328
329 // ── SECTION 4: TAB STOPS ──
330 doc.add_paragraph("");
331 doc.add_paragraph("4. Tab Stops").style("Heading1");
332
333 doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
334 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
335 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
336 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
337 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
338 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
339
340 doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
341 doc.add_paragraph("Item\t\tPrice")
342 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
343 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
344 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
345 doc.add_paragraph("Widget\t\t$19.99")
346 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
347 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
348 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
349 doc.add_paragraph("Gadget\t\t$249.50")
350 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
351 .add_tab_stop_with_leader(
352 TabAlignment::Right,
353 Length::inches(4.0),
354 TabLeader::Underscore,
355 )
356 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
357
358 // ── SECTION 5: TABLES ──
359 doc.add_paragraph("").page_break_before(true);
360 doc.add_paragraph("5. Tables").style("Heading1");
361
362 doc.add_paragraph("5.1 Basic Table").style("Heading2");
363 {
364 let mut tbl = doc
365 .add_table(4, 3)
366 .borders(BorderStyle::Single, 4, "000000");
367 for col in 0..3 {
368 tbl.cell(0, col).unwrap().shading("2E75B6");
369 }
370 tbl.cell(0, 0).unwrap().set_text("Name");
371 tbl.cell(0, 1).unwrap().set_text("Role");
372 tbl.cell(0, 2).unwrap().set_text("Location");
373 tbl.cell(1, 0).unwrap().set_text("Walter White");
374 tbl.cell(1, 1).unwrap().set_text("CEO");
375 tbl.cell(1, 2).unwrap().set_text("Albuquerque");
376 tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
377 tbl.cell(2, 1).unwrap().set_text("CTO");
378 tbl.cell(2, 2).unwrap().set_text("Remote");
379 tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
380 tbl.cell(3, 1).unwrap().set_text("Security");
381 tbl.cell(3, 2).unwrap().set_text("Washington");
382 }
383
384 doc.add_paragraph("");
385
386 doc.add_paragraph("5.2 Column Span & Cell Shading")
387 .style("Heading2");
388 {
389 let mut tbl = doc
390 .add_table(3, 4)
391 .borders(BorderStyle::Single, 4, "000000")
392 .width_pct(100.0);
393 tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
394 tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
395 tbl.cell(1, 0).unwrap().set_text("Region");
396 tbl.cell(1, 0).unwrap().shading("D6E4F0");
397 tbl.cell(1, 1).unwrap().set_text("Q1");
398 tbl.cell(1, 1).unwrap().shading("D6E4F0");
399 tbl.cell(1, 2).unwrap().set_text("Q2");
400 tbl.cell(1, 2).unwrap().shading("D6E4F0");
401 tbl.cell(1, 3).unwrap().set_text("Total");
402 tbl.cell(1, 3).unwrap().shading("D6E4F0");
403 tbl.cell(2, 0).unwrap().set_text("Americas");
404 tbl.cell(2, 1).unwrap().set_text("$2.4M");
405 tbl.cell(2, 2).unwrap().set_text("$2.7M");
406 tbl.cell(2, 3).unwrap().set_text("$5.1M");
407 }
408
409 doc.add_paragraph("");
410
411 doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
412 {
413 let mut tbl = doc
414 .add_table(4, 3)
415 .borders(BorderStyle::Single, 4, "000000");
416 tbl.cell(0, 0).unwrap().set_text("Category");
417 tbl.cell(0, 0).unwrap().shading("E2EFDA");
418 tbl.cell(0, 1).unwrap().set_text("Item");
419 tbl.cell(0, 1).unwrap().shading("E2EFDA");
420 tbl.cell(0, 2).unwrap().set_text("Price");
421 tbl.cell(0, 2).unwrap().shading("E2EFDA");
422 tbl.cell(1, 0).unwrap().set_text("Hardware");
423 tbl.cell(1, 0).unwrap().v_merge_restart();
424 tbl.cell(1, 1).unwrap().set_text("Laptop");
425 tbl.cell(1, 2).unwrap().set_text("$1,200");
426 tbl.cell(2, 0).unwrap().v_merge_continue();
427 tbl.cell(2, 1).unwrap().set_text("Monitor");
428 tbl.cell(2, 2).unwrap().set_text("$450");
429 tbl.cell(3, 0).unwrap().set_text("Software");
430 tbl.cell(3, 1).unwrap().set_text("IDE License");
431 tbl.cell(3, 2).unwrap().set_text("$200/yr");
432 }
433
434 doc.add_paragraph("");
435
436 doc.add_paragraph("5.4 Nested Table").style("Heading2");
437 {
438 let mut tbl = doc
439 .add_table(2, 2)
440 .borders(BorderStyle::Single, 6, "2E75B6");
441 tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
442 tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
443 tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
444 {
445 let mut cell = tbl.cell(1, 1).unwrap();
446 cell.set_text("Nested table below:");
447 let mut nested = cell
448 .add_table(2, 2)
449 .borders(BorderStyle::Single, 2, "FF6600");
450 nested.cell(0, 0).unwrap().set_text("A");
451 nested.cell(0, 1).unwrap().set_text("B");
452 nested.cell(1, 0).unwrap().set_text("C");
453 nested.cell(1, 1).unwrap().set_text("D");
454 }
455 }
456
457 doc.add_paragraph("");
458
459 doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
460 .style("Heading2");
461 {
462 let mut tbl = doc
463 .add_table(2, 3)
464 .borders(BorderStyle::Single, 4, "666666");
465 tbl.row(0).unwrap().height(Length::pt(50.0)).header();
466 tbl.cell(0, 0).unwrap().set_text("Top");
467 tbl.cell(0, 0)
468 .unwrap()
469 .vertical_alignment(VerticalAlignment::Top)
470 .shading("FFF2CC");
471 tbl.cell(0, 1).unwrap().set_text("Center");
472 tbl.cell(0, 1)
473 .unwrap()
474 .vertical_alignment(VerticalAlignment::Center)
475 .shading("D9E2F3");
476 tbl.cell(0, 2).unwrap().set_text("Bottom");
477 tbl.cell(0, 2)
478 .unwrap()
479 .vertical_alignment(VerticalAlignment::Bottom)
480 .shading("E2EFDA");
481 tbl.row(1).unwrap().cant_split();
482 tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
483 tbl.cell(1, 0).unwrap().no_wrap();
484 tbl.cell(1, 1).unwrap().set_text("Fixed width");
485 tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
486 tbl.cell(1, 2).unwrap().set_text("Normal");
487 }
488
489 // ── SECTION 6: IMAGES ──
490 doc.add_paragraph("").page_break_before(true);
491 doc.add_paragraph("6. Images").style("Heading1");
492
493 doc.add_paragraph("6.1 Inline Image").style("Heading2");
494 doc.add_paragraph("A blue gradient image below:");
495 let img = create_sample_png(200, 50, [0, 80, 200]);
496 doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
497
498 doc.add_paragraph("");
499 doc.add_paragraph("6.2 Header Image").style("Heading2");
500 let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
501 doc.set_header_image(
502 &hdr_img,
503 "header_logo.png",
504 Length::inches(2.0),
505 Length::inches(0.2),
506 );
507 doc.add_paragraph("The header now contains an inline image (check the top of this page).");
508
509 doc.add_paragraph("");
510 doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
511
512 // ── SECTION 7: CONTENT MANIPULATION ──
513 doc.add_paragraph("").page_break_before(true);
514 doc.add_paragraph("7. Content Manipulation")
515 .style("Heading1");
516
517 doc.add_paragraph("7.1 Placeholder Replacement")
518 .style("Heading2");
519 doc.add_paragraph("Customer: {{customer}}");
520 doc.add_paragraph("Date: {{date}}");
521 doc.add_paragraph("Reference: {{ref_number}}");
522 {
523 let mut tbl = doc
524 .add_table(2, 2)
525 .borders(BorderStyle::Single, 4, "000000");
526 tbl.cell(0, 0).unwrap().set_text("Project");
527 tbl.cell(0, 0).unwrap().shading("D6E4F0");
528 tbl.cell(0, 1).unwrap().set_text("{{project}}");
529 tbl.cell(1, 0).unwrap().set_text("Status");
530 tbl.cell(1, 0).unwrap().shading("D6E4F0");
531 tbl.cell(1, 1).unwrap().set_text("{{status}}");
532 }
533
534 let mut replacements = HashMap::new();
535 replacements.insert("{{customer}}", "Tensorbee Inc.");
536 replacements.insert("{{date}}", "February 22, 2026");
537 replacements.insert("{{ref_number}}", "TB-2026-001");
538 replacements.insert("{{project}}", "Infrastructure Upgrade");
539 replacements.insert("{{status}}", "In Progress");
540 let n = doc.replace_all(&replacements);
541 doc.add_paragraph(&format!("({n} placeholders replaced above)"));
542
543 doc.add_paragraph("");
544
545 doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
546 doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
547 let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
548 doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
549
550 doc.add_paragraph("");
551
552 doc.add_paragraph("7.3 Content Insertion").style("Heading2");
553 doc.add_paragraph("Section A: First content.");
554 doc.add_paragraph("Section C: Third content.");
555 if let Some(idx) = doc.find_content_index("Section C") {
556 doc.insert_paragraph(
557 idx,
558 "Section B: Inserted between A and C via find_content_index().",
559 );
560 }
561
562 // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
563 doc.add_paragraph("").section_break(SectionBreak::NextPage);
564 doc.add_paragraph("8. Section Breaks & Orientation")
565 .style("Heading1");
566 doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
567
568 {
569 let mut tbl = doc
570 .add_table(3, 7)
571 .borders(BorderStyle::Single, 4, "2E75B6");
572 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
573 for (c, h) in headers.iter().enumerate() {
574 tbl.cell(0, c).unwrap().set_text(h);
575 tbl.cell(0, c).unwrap().shading("2E75B6");
576 }
577 let data = [
578 [
579 "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
580 ],
581 [
582 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
583 ],
584 ];
585 for (r, row) in data.iter().enumerate() {
586 for (c, v) in row.iter().enumerate() {
587 tbl.cell(r + 1, c).unwrap().set_text(v);
588 }
589 }
590 }
591
592 doc.add_paragraph("")
593 .section_break(SectionBreak::NextPage)
594 .section_landscape();
595
596 // ── SECTION 9: CUSTOM STYLES ──
597 doc.add_paragraph("9. Custom Styles").style("Heading1");
598 doc.add_style(
599 StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
600 .based_on("Normal")
601 .paragraph_properties(rdocx_oxml::properties::CT_PPr {
602 shading: Some(rdocx_oxml::properties::CT_Shd {
603 val: "clear".to_string(),
604 color: None,
605 fill: Some("FFF2CC".to_string()),
606 }),
607 ..Default::default()
608 })
609 .run_properties(rdocx_oxml::properties::CT_RPr {
610 bold: Some(true),
611 color: Some("C45911".to_string()),
612 ..Default::default()
613 }),
614 );
615 doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
616 .style("CustomHighlight");
617
618 doc.add_paragraph("");
619
620 // ── SECTION 10: DOCUMENT INTELLIGENCE ──
621 doc.add_paragraph("10. Document Intelligence API")
622 .style("Heading1");
623
624 let wc = doc.word_count();
625 let hc = doc.headings().len();
626 let ic = doc.images().len();
627 let lc = doc.links().len();
628 doc.add_paragraph(&format!("Word count: {wc}"));
629 doc.add_paragraph(&format!("Heading count: {hc}"));
630 doc.add_paragraph(&format!("Image count: {ic}"));
631 doc.add_paragraph(&format!("Link count: {lc}"));
632
633 let outline = doc.document_outline();
634 doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
635
636 let issues = doc.audit_accessibility();
637 doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
638 for issue in &issues {
639 doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
640 }
641
642 // ── SECTION 11: DOCUMENT MERGING ──
643 doc.add_paragraph("");
644 doc.add_paragraph("11. Document Merging").style("Heading1");
645 {
646 let mut other = Document::new();
647 other.add_paragraph("This paragraph was merged from another document using append().");
648 doc.append(&other);
649 }
650
651 doc.add_paragraph("");
652
653 // ── SUMMARY ──
654 doc.add_paragraph("Summary of Demonstrated Features")
655 .style("Heading1");
656 let features = [
657 "Page setup: size, margins, header/footer distance, gutter",
658 "Document metadata: title, author, subject, keywords",
659 "Headers and footers: text, images, different first page",
660 "Background images (full-page behind text)",
661 "Table of Contents generation with bookmarks",
662 "Text formatting: bold, italic, underline styles, strike, color, size, font",
663 "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
664 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
665 "Pagination controls: keep-with-next, keep-together, widow control",
666 "Bullet and numbered lists with nesting",
667 "Tab stops with dot/underscore/hyphen leaders",
668 "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
669 "Row properties: header rows, exact height, cant-split",
670 "Cell properties: width, no-wrap, vertical alignment",
671 "Inline images and header images",
672 "Placeholder replacement (single and batch)",
673 "Regex-based find and replace",
674 "Content insertion at specific positions",
675 "Section breaks with mixed portrait/landscape",
676 "Custom paragraph and character styles",
677 "Document intelligence: word count, headings, outline, images, links",
678 "Accessibility audit",
679 "Document merging (append)",
680 "Export: DOCX, PDF, HTML, Markdown",
681 ];
682 for f in &features {
683 doc.add_bullet_list_item(f, 0);
684 }
685 doc.add_paragraph("");
686 doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
687 .alignment(Alignment::Center)
688 .shading("E2EFDA")
689 .border_all(BorderStyle::Single, 2, "00AA00");
690
691 doc
692}Trait Implementations§
Auto Trait Implementations§
impl !Freeze for Document
impl RefUnwindSafe for Document
impl Send for Document
impl Sync for Document
impl Unpin for Document
impl UnsafeUnpin for Document
impl UnwindSafe for Document
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<U, T> ToOwnedObj<U> for Twhere
U: FromObjRef<T>,
impl<U, T> ToOwnedObj<U> for Twhere
U: FromObjRef<T>,
Source§fn to_owned_obj(&self, data: FontData<'_>) -> U
fn to_owned_obj(&self, data: FontData<'_>) -> U
T, using the provided data to resolve any offsets.