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}34fn generate_feature_showcase(path: &Path) {
35 let mut doc = Document::new();
36
37 // =========================================================================
38 // PAGE SETUP & METADATA
39 // =========================================================================
40 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
41 doc.set_margins(
42 Length::inches(1.0), // top
43 Length::inches(1.0), // right
44 Length::inches(1.0), // bottom
45 Length::inches(1.0), // left
46 );
47 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
48 doc.set_gutter(Length::twips(0));
49
50 doc.set_title("rdocx Feature Showcase");
51 doc.set_author("rdocx Sample Generator");
52 doc.set_subject("Comprehensive feature demonstration");
53 doc.set_keywords("rdocx, docx, rust, sample");
54
55 // Header & Footer
56 doc.set_header("rdocx Feature Showcase");
57 doc.set_footer("Generated by rdocx — Page");
58
59 // Different first page header
60 doc.set_different_first_page(true);
61 doc.set_first_page_header("rdocx");
62 doc.set_first_page_footer("Feature Showcase — Cover Page");
63
64 // =========================================================================
65 // PAGE 1: COVER PAGE — background image, run formatting
66 // =========================================================================
67 let bg_cover = create_sample_png(612, 792, [30, 60, 120]);
68 doc.add_background_image(&bg_cover, "cover_bg.png");
69
70 doc.add_paragraph(""); // spacer
71 doc.add_paragraph(""); // spacer
72 doc.add_paragraph(""); // spacer
73
74 {
75 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
76 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
77 }
78 {
79 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
80 p.add_run("Feature Showcase")
81 .size(28.0)
82 .color("FFFFFF")
83 .italic(true);
84 }
85
86 doc.add_paragraph(""); // spacer
87
88 {
89 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
90 p.add_run("A comprehensive demonstration of every feature")
91 .size(14.0)
92 .color("CCDDFF");
93 }
94 {
95 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
96 p.add_run("provided by the rdocx Rust crate for DOCX generation.")
97 .size(14.0)
98 .color("CCDDFF");
99 }
100
101 // =========================================================================
102 // PAGE 2: TEXT FORMATTING
103 // =========================================================================
104 doc.add_paragraph("").page_break_before(true);
105
106 doc.add_paragraph("1. Text Formatting").style("Heading1");
107
108 doc.add_paragraph("This section demonstrates paragraph and run-level formatting options.");
109 doc.add_paragraph("");
110
111 // --- Paragraph alignment ---
112 doc.add_paragraph("Paragraph Alignment").style("Heading2");
113
114 doc.add_paragraph("This paragraph is left-aligned (the default).")
115 .alignment(Alignment::Left);
116 doc.add_paragraph("This paragraph is center-aligned.")
117 .alignment(Alignment::Center);
118 doc.add_paragraph("This paragraph is right-aligned.")
119 .alignment(Alignment::Right);
120 doc.add_paragraph(
121 "This paragraph is justified. To demonstrate justified text properly, it needs \
122 to be long enough to span multiple lines so the word spacing adjustment is visible \
123 across the full width of the text area on the page.",
124 )
125 .alignment(Alignment::Justify);
126
127 doc.add_paragraph("");
128
129 // --- Run formatting ---
130 doc.add_paragraph("Run Formatting").style("Heading2");
131
132 {
133 let mut p = doc.add_paragraph("");
134 p.add_run("Bold text").bold(true);
135 p.add_run(" | ");
136 p.add_run("Italic text").italic(true);
137 p.add_run(" | ");
138 p.add_run("Bold + Italic").bold(true).italic(true);
139 }
140 {
141 let mut p = doc.add_paragraph("");
142 p.add_run("Single underline").underline(true);
143 p.add_run(" | ");
144 p.add_run("Strikethrough").strike(true);
145 p.add_run(" | ");
146 p.add_run("Double strikethrough").double_strike(true);
147 }
148 {
149 let mut p = doc.add_paragraph("");
150 p.add_run("Red text").color("FF0000");
151 p.add_run(" | ");
152 p.add_run("Blue text").color("0000FF");
153 p.add_run(" | ");
154 p.add_run("Green text").color("00AA00");
155 p.add_run(" | ");
156 p.add_run("Highlighted").highlight("FFFF00");
157 }
158 {
159 let mut p = doc.add_paragraph("");
160 p.add_run("8pt small").size(8.0);
161 p.add_run(" | ");
162 p.add_run("11pt normal").size(11.0);
163 p.add_run(" | ");
164 p.add_run("16pt large").size(16.0);
165 p.add_run(" | ");
166 p.add_run("24pt extra-large").size(24.0);
167 }
168 {
169 let mut p = doc.add_paragraph("");
170 p.add_run("Arial font").font("Arial");
171 p.add_run(" | ");
172 p.add_run("Times New Roman font").font("Times New Roman");
173 p.add_run(" | ");
174 p.add_run("Courier New font").font("Courier New");
175 }
176 {
177 let mut p = doc.add_paragraph("");
178 p.add_run("Normal");
179 p.add_run(" H").size(11.0);
180 p.add_run("2").subscript();
181 p.add_run("O (subscript)").size(11.0);
182 p.add_run(" | E = mc").size(11.0);
183 p.add_run("2").superscript();
184 p.add_run(" (superscript)").size(11.0);
185 }
186 {
187 let mut p = doc.add_paragraph("");
188 p.add_run("ALL CAPS").all_caps(true);
189 p.add_run(" | ");
190 p.add_run("Small Caps").small_caps(true);
191 p.add_run(" | ");
192 p.add_run("Expanded spacing")
193 .character_spacing(Length::twips(40));
194 }
195
196 doc.add_paragraph("");
197
198 // --- Paragraph formatting ---
199 doc.add_paragraph("Paragraph Formatting").style("Heading2");
200
201 doc.add_paragraph("Paragraph with shading (light green background)")
202 .shading("E2EFDA");
203
204 doc.add_paragraph("Paragraph with bottom border")
205 .border_bottom(BorderStyle::Single, 6, "2E75B6");
206
207 doc.add_paragraph("Paragraph with all borders")
208 .border_all(BorderStyle::Single, 4, "FF0000");
209
210 doc.add_paragraph("Paragraph with 1-inch left indent and hanging indent")
211 .indent_left(Length::inches(1.0))
212 .hanging_indent(Length::inches(0.5));
213
214 doc.add_paragraph("Paragraph with first-line indent of 0.5 inches")
215 .first_line_indent(Length::inches(0.5));
216
217 doc.add_paragraph("Paragraph with extra space before (24pt) and after (12pt)")
218 .space_before(Length::pt(24.0))
219 .space_after(Length::pt(12.0));
220
221 doc.add_paragraph(
222 "Paragraph with double line spacing. This text should have extra vertical \
223 space between lines to demonstrate the line_spacing_multiple setting.",
224 )
225 .line_spacing_multiple(2.0);
226
227 doc.add_paragraph("Paragraph with keep-with-next (won't break from the next paragraph)")
228 .keep_with_next(true);
229 doc.add_paragraph("(This stays with the paragraph above.)");
230
231 // =========================================================================
232 // PAGE 3: LISTS & TAB STOPS
233 // =========================================================================
234 doc.add_paragraph("").page_break_before(true);
235
236 doc.add_paragraph("2. Lists").style("Heading1");
237
238 doc.add_paragraph("Bullet List").style("Heading2");
239
240 doc.add_bullet_list_item("First bullet item", 0);
241 doc.add_bullet_list_item("Second bullet item", 0);
242 doc.add_bullet_list_item("Nested level 1", 1);
243 doc.add_bullet_list_item("Nested level 2", 2);
244 doc.add_bullet_list_item("Back to level 1", 1);
245 doc.add_bullet_list_item("Third bullet item", 0);
246
247 doc.add_paragraph("");
248
249 doc.add_paragraph("Numbered List").style("Heading2");
250
251 doc.add_numbered_list_item("First numbered item", 0);
252 doc.add_numbered_list_item("Second numbered item", 0);
253 doc.add_numbered_list_item("Sub-item A", 1);
254 doc.add_numbered_list_item("Sub-item B", 1);
255 doc.add_numbered_list_item("Third numbered item", 0);
256
257 doc.add_paragraph("");
258
259 // --- Tab stops ---
260 doc.add_paragraph("Tab Stops").style("Heading2");
261
262 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
263 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
264 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
265 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
266 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
267
268 doc.add_paragraph("Item\t........\tPrice")
269 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
270 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
271 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
272
273 doc.add_paragraph("Widget A\t........\t$19.99")
274 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
275 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
276 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
277
278 doc.add_paragraph("Gadget B\t________\t$249.50")
279 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
280 .add_tab_stop_with_leader(
281 TabAlignment::Right,
282 Length::inches(4.0),
283 TabLeader::Underscore,
284 )
285 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
286
287 // =========================================================================
288 // PAGE 4: TABLES
289 // =========================================================================
290 doc.add_paragraph("").page_break_before(true);
291
292 doc.add_paragraph("3. Tables").style("Heading1");
293
294 // --- Basic table with borders ---
295 doc.add_paragraph("Basic Table with Borders")
296 .style("Heading2");
297
298 {
299 let mut tbl = doc.add_table(4, 3);
300 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
301
302 // Header row
303 for col in 0..3 {
304 tbl.cell(0, col).unwrap().shading("2E75B6");
305 }
306 tbl.cell(0, 0).unwrap().set_text("Name");
307 tbl.cell(0, 1).unwrap().set_text("Role");
308 tbl.cell(0, 2).unwrap().set_text("Location");
309
310 tbl.cell(1, 0).unwrap().set_text("Alice Johnson");
311 tbl.cell(1, 1).unwrap().set_text("Engineering Lead");
312 tbl.cell(1, 2).unwrap().set_text("New York");
313
314 tbl.cell(2, 0).unwrap().set_text("Bob Smith");
315 tbl.cell(2, 1).unwrap().set_text("Product Manager");
316 tbl.cell(2, 2).unwrap().set_text("San Francisco");
317
318 tbl.cell(3, 0).unwrap().set_text("Carol Davis");
319 tbl.cell(3, 1).unwrap().set_text("Designer");
320 tbl.cell(3, 2).unwrap().set_text("London");
321 }
322
323 doc.add_paragraph("");
324
325 // --- Table with cell merging ---
326 doc.add_paragraph("Table with Cell Merging & Vertical Alignment")
327 .style("Heading2");
328
329 {
330 let mut tbl = doc.add_table(4, 4);
331 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
332 tbl = tbl.width_pct(100.0);
333
334 // Header spanning all columns
335 tbl.cell(0, 0).unwrap().set_text("Quarterly Revenue Report");
336 tbl.cell(0, 0).unwrap().shading("1F4E79");
337 tbl.cell(0, 0).unwrap().grid_span(4);
338
339 // Sub-header
340 tbl.cell(1, 0).unwrap().set_text("Region");
341 tbl.cell(1, 0).unwrap().shading("D6E4F0");
342 tbl.cell(1, 1).unwrap().set_text("Q1");
343 tbl.cell(1, 1).unwrap().shading("D6E4F0");
344 tbl.cell(1, 2).unwrap().set_text("Q2");
345 tbl.cell(1, 2).unwrap().shading("D6E4F0");
346 tbl.cell(1, 3).unwrap().set_text("Total");
347 tbl.cell(1, 3).unwrap().shading("D6E4F0");
348
349 // Data
350 tbl.cell(2, 0).unwrap().set_text("North America");
351 tbl.cell(2, 1).unwrap().set_text("$2.4M");
352 tbl.cell(2, 2).unwrap().set_text("$2.7M");
353 tbl.cell(2, 3).unwrap().set_text("$5.1M");
354
355 tbl.cell(3, 0).unwrap().set_text("Europe");
356 tbl.cell(3, 1).unwrap().set_text("$1.8M");
357 tbl.cell(3, 2).unwrap().set_text("$2.0M");
358 tbl.cell(3, 3).unwrap().set_text("$3.8M");
359
360 // Vertical alignment on data cells
361 tbl.cell(2, 3)
362 .unwrap()
363 .vertical_alignment(VerticalAlignment::Center);
364 tbl.cell(3, 3)
365 .unwrap()
366 .vertical_alignment(VerticalAlignment::Bottom);
367 }
368
369 doc.add_paragraph("");
370
371 // --- Table with vertical merge ---
372 doc.add_paragraph("Table with Vertical Merge")
373 .style("Heading2");
374
375 {
376 let mut tbl = doc.add_table(4, 3);
377 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
378
379 tbl.cell(0, 0).unwrap().set_text("Category");
380 tbl.cell(0, 0).unwrap().shading("E2EFDA");
381 tbl.cell(0, 1).unwrap().set_text("Item");
382 tbl.cell(0, 1).unwrap().shading("E2EFDA");
383 tbl.cell(0, 2).unwrap().set_text("Price");
384 tbl.cell(0, 2).unwrap().shading("E2EFDA");
385
386 // "Hardware" spans rows 1-2
387 tbl.cell(1, 0).unwrap().set_text("Hardware");
388 tbl.cell(1, 0).unwrap().v_merge_restart();
389 tbl.cell(1, 1).unwrap().set_text("Laptop");
390 tbl.cell(1, 2).unwrap().set_text("$1,200");
391
392 tbl.cell(2, 0).unwrap().v_merge_continue();
393 tbl.cell(2, 1).unwrap().set_text("Monitor");
394 tbl.cell(2, 2).unwrap().set_text("$450");
395
396 // "Software" on row 3
397 tbl.cell(3, 0).unwrap().set_text("Software");
398 tbl.cell(3, 1).unwrap().set_text("IDE License");
399 tbl.cell(3, 2).unwrap().set_text("$200/yr");
400 }
401
402 doc.add_paragraph("");
403
404 // --- Nested table ---
405 doc.add_paragraph("Nested Table").style("Heading2");
406
407 {
408 let mut tbl = doc.add_table(2, 2);
409 tbl = tbl.borders(BorderStyle::Single, 6, "2E75B6");
410
411 tbl.cell(0, 0).unwrap().set_text("Outer Cell (0,0)");
412 tbl.cell(0, 1).unwrap().set_text("Outer Cell (0,1)");
413 tbl.cell(1, 0).unwrap().set_text("Outer Cell (1,0)");
414
415 // Nested table inside cell (1,1)
416 {
417 let mut cell = tbl.cell(1, 1).unwrap();
418 cell.set_text("Contains nested table:");
419 let mut nested = cell.add_table(2, 2);
420 nested = nested.borders(BorderStyle::Single, 2, "FF6600");
421 nested.cell(0, 0).unwrap().set_text("Inner A");
422 nested.cell(0, 1).unwrap().set_text("Inner B");
423 nested.cell(1, 0).unwrap().set_text("Inner C");
424 nested.cell(1, 1).unwrap().set_text("Inner D");
425 }
426 }
427
428 // =========================================================================
429 // PAGE 5: IMAGES
430 // =========================================================================
431 doc.add_paragraph("").page_break_before(true);
432
433 doc.add_paragraph("4. Images").style("Heading1");
434
435 doc.add_paragraph("Inline Image").style("Heading2");
436
437 doc.add_paragraph("Below is an inline image (200x50 pixels, blue gradient):");
438 let inline_img = create_sample_png(200, 50, [0, 80, 200]);
439 doc.add_picture(
440 &inline_img,
441 "inline_chart.png",
442 Length::inches(3.0),
443 Length::inches(0.75),
444 );
445
446 doc.add_paragraph("");
447
448 doc.add_paragraph("Header Image").style("Heading2");
449
450 // Replace the text-only header with an image header
451 let header_img = create_sample_png(400, 40, [40, 40, 40]);
452 doc.set_header_image(
453 &header_img,
454 "header_logo.png",
455 Length::inches(2.0),
456 Length::inches(0.2),
457 );
458
459 doc.add_paragraph(
460 "The document header has been replaced with an inline image. \
461 Check the header area at the top of this page.",
462 );
463
464 doc.add_paragraph("");
465 doc.add_paragraph(
466 "Note: The cover page uses a full-page background image behind the text, \
467 demonstrated on page 1 via add_background_image().",
468 );
469
470 // =========================================================================
471 // PAGE 6: CONTENT MANIPULATION — placeholder replacement, insertion
472 // =========================================================================
473 doc.add_paragraph("").page_break_before(true);
474
475 doc.add_paragraph("5. Content Manipulation")
476 .style("Heading1");
477
478 // --- Placeholder replacement ---
479 doc.add_paragraph("Placeholder Replacement")
480 .style("Heading2");
481
482 doc.add_paragraph(
483 "Before replacement, this document contained {{customer}} and {{date}} placeholders.",
484 );
485
486 {
487 let mut p = doc.add_paragraph("");
488 p.add_run("Customer: ").bold(true);
489 p.add_run("{{customer}}");
490 }
491 {
492 let mut p = doc.add_paragraph("");
493 p.add_run("Date: ").bold(true);
494 p.add_run("{{date}}");
495 }
496 doc.add_paragraph("Reference: {{ref_number}}");
497
498 // Table with placeholders
499 {
500 let mut tbl = doc.add_table(3, 2);
501 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
502 tbl.cell(0, 0).unwrap().set_text("Field");
503 tbl.cell(0, 0).unwrap().shading("D6E4F0");
504 tbl.cell(0, 1).unwrap().set_text("Value");
505 tbl.cell(0, 1).unwrap().shading("D6E4F0");
506 tbl.cell(1, 0).unwrap().set_text("Project");
507 tbl.cell(1, 1).unwrap().set_text("{{project}}");
508 tbl.cell(2, 0).unwrap().set_text("Status");
509 tbl.cell(2, 1).unwrap().set_text("{{status}}");
510 }
511
512 // Perform replacements
513 let mut replacements = HashMap::new();
514 replacements.insert("{{customer}}", "Acme Corporation");
515 replacements.insert("{{date}}", "February 22, 2026");
516 replacements.insert("{{ref_number}}", "REF-2026-001");
517 replacements.insert("{{project}}", "Infrastructure Upgrade");
518 replacements.insert("{{status}}", "In Progress");
519 let replace_count = doc.replace_all(&replacements);
520
521 doc.add_paragraph("");
522 doc.add_paragraph(&format!(
523 "(Replaced {} placeholders above — in body text and table cells)",
524 replace_count
525 ));
526
527 doc.add_paragraph("");
528
529 // --- Content insertion ---
530 doc.add_paragraph("Content Insertion").style("Heading2");
531
532 doc.add_paragraph("Section A: First section of content.");
533 doc.add_paragraph("Section C: Third section of content.");
534
535 // Insert "Section B" between A and C
536 if let Some(idx) = doc.find_content_index("Section C") {
537 doc.insert_paragraph(
538 idx,
539 "Section B: Inserted between A and C using find_content_index().",
540 );
541 }
542
543 doc.add_paragraph("");
544 doc.add_paragraph(
545 "The paragraph above ('Section B') was inserted at a specific position \
546 using find_content_index() + insert_paragraph().",
547 );
548
549 // =========================================================================
550 // PAGE 7: LANDSCAPE — section break, wide table
551 // =========================================================================
552 doc.add_paragraph("").section_break(SectionBreak::NextPage);
553
554 doc.add_paragraph("6. Mixed Page Orientation")
555 .style("Heading1");
556
557 doc.add_paragraph(
558 "This page is in LANDSCAPE orientation. It was created using a section break \
559 followed by section_landscape(). This is useful for wide tables or charts.",
560 );
561
562 doc.add_paragraph("");
563
564 // Wide table for landscape
565 {
566 let mut tbl = doc.add_table(4, 7);
567 tbl = tbl.borders(BorderStyle::Single, 4, "2E75B6");
568
569 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
570 for (col, h) in headers.iter().enumerate() {
571 tbl.cell(0, col).unwrap().set_text(h);
572 tbl.cell(0, col).unwrap().shading("2E75B6");
573 }
574
575 let data = [
576 [
577 "North America",
578 "$1.2M",
579 "$1.3M",
580 "$1.4M",
581 "$1.5M",
582 "$1.6M",
583 "$7.0M",
584 ],
585 [
586 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
587 ],
588 [
589 "Asia Pacific",
590 "$0.5M",
591 "$0.6M",
592 "$0.7M",
593 "$0.7M",
594 "$0.8M",
595 "$3.3M",
596 ],
597 ];
598 for (row_idx, row_data) in data.iter().enumerate() {
599 for (col, val) in row_data.iter().enumerate() {
600 tbl.cell(row_idx + 1, col).unwrap().set_text(val);
601 }
602 }
603 }
604
605 // End landscape, return to portrait
606 doc.add_paragraph("")
607 .section_break(SectionBreak::NextPage)
608 .section_landscape();
609
610 // =========================================================================
611 // PAGE 8: BACK TO PORTRAIT — styles, final notes
612 // =========================================================================
613 doc.add_paragraph("7. Custom Styles & Summary")
614 .style("Heading1");
615
616 doc.add_paragraph(
617 "This final page is back in portrait orientation after a section break. \
618 The document has demonstrated:",
619 );
620
621 doc.add_bullet_list_item(
622 "Page setup: size, margins, header/footer distance, gutter",
623 0,
624 );
625 doc.add_bullet_list_item("Document metadata: title, author, subject, keywords", 0);
626 doc.add_bullet_list_item("Headers and footers: text, images, different first page", 0);
627 doc.add_bullet_list_item("Background images: full-page behind text", 0);
628 doc.add_bullet_list_item(
629 "Text formatting: bold, italic, underline, strike, color, size, font",
630 0,
631 );
632 doc.add_bullet_list_item(
633 "Advanced run formatting: superscript, subscript, caps, spacing",
634 0,
635 );
636 doc.add_bullet_list_item(
637 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
638 0,
639 );
640 doc.add_bullet_list_item("Bullet and numbered lists with nesting levels", 0);
641 doc.add_bullet_list_item("Tab stops with dot/underscore leaders", 0);
642 doc.add_bullet_list_item(
643 "Tables: borders, shading, column spans, row spans, nesting",
644 0,
645 );
646 doc.add_bullet_list_item("Vertical alignment in table cells", 0);
647 doc.add_bullet_list_item("Inline images", 0);
648 doc.add_bullet_list_item("Placeholder replacement in body and table cells", 0);
649 doc.add_bullet_list_item("Content insertion at specific positions", 0);
650 doc.add_bullet_list_item(
651 "Section breaks with mixed portrait/landscape orientation",
652 0,
653 );
654
655 doc.add_paragraph("");
656 doc.add_paragraph("All features above were built entirely from scratch using the rdocx API.")
657 .alignment(Alignment::Center)
658 .shading("E2EFDA")
659 .border_all(BorderStyle::Single, 2, "00AA00");
660
661 doc.save(path).unwrap();
662}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?
58fn export_all(dir: &Path, name: &str, mut doc: Document) {
59 let docx_path = dir.join(format!("{name}.docx"));
60 doc.save(&docx_path).unwrap();
61 println!(" {name}.docx");
62
63 match doc.to_pdf() {
64 Ok(pdf) => {
65 let pdf_path = dir.join(format!("{name}.pdf"));
66 std::fs::write(&pdf_path, &pdf).unwrap();
67 println!(" {name}.pdf ({} bytes)", pdf.len());
68 }
69 Err(e) => println!(" {name}.pdf — skipped: {e}"),
70 }
71
72 let html = doc.to_html();
73 let html_path = dir.join(format!("{name}.html"));
74 std::fs::write(&html_path, &html).unwrap();
75 println!(" {name}.html ({} bytes)", html.len());
76
77 let md = doc.to_markdown();
78 let md_path = dir.join(format!("{name}.md"));
79 std::fs::write(&md_path, &md).unwrap();
80 println!(" {name}.md ({} bytes)", md.len());
81}More examples
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}34fn generate_feature_showcase(path: &Path) {
35 let mut doc = Document::new();
36
37 // =========================================================================
38 // PAGE SETUP & METADATA
39 // =========================================================================
40 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
41 doc.set_margins(
42 Length::inches(1.0), // top
43 Length::inches(1.0), // right
44 Length::inches(1.0), // bottom
45 Length::inches(1.0), // left
46 );
47 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
48 doc.set_gutter(Length::twips(0));
49
50 doc.set_title("rdocx Feature Showcase");
51 doc.set_author("rdocx Sample Generator");
52 doc.set_subject("Comprehensive feature demonstration");
53 doc.set_keywords("rdocx, docx, rust, sample");
54
55 // Header & Footer
56 doc.set_header("rdocx Feature Showcase");
57 doc.set_footer("Generated by rdocx — Page");
58
59 // Different first page header
60 doc.set_different_first_page(true);
61 doc.set_first_page_header("rdocx");
62 doc.set_first_page_footer("Feature Showcase — Cover Page");
63
64 // =========================================================================
65 // PAGE 1: COVER PAGE — background image, run formatting
66 // =========================================================================
67 let bg_cover = create_sample_png(612, 792, [30, 60, 120]);
68 doc.add_background_image(&bg_cover, "cover_bg.png");
69
70 doc.add_paragraph(""); // spacer
71 doc.add_paragraph(""); // spacer
72 doc.add_paragraph(""); // spacer
73
74 {
75 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
76 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
77 }
78 {
79 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
80 p.add_run("Feature Showcase")
81 .size(28.0)
82 .color("FFFFFF")
83 .italic(true);
84 }
85
86 doc.add_paragraph(""); // spacer
87
88 {
89 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
90 p.add_run("A comprehensive demonstration of every feature")
91 .size(14.0)
92 .color("CCDDFF");
93 }
94 {
95 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
96 p.add_run("provided by the rdocx Rust crate for DOCX generation.")
97 .size(14.0)
98 .color("CCDDFF");
99 }
100
101 // =========================================================================
102 // PAGE 2: TEXT FORMATTING
103 // =========================================================================
104 doc.add_paragraph("").page_break_before(true);
105
106 doc.add_paragraph("1. Text Formatting").style("Heading1");
107
108 doc.add_paragraph("This section demonstrates paragraph and run-level formatting options.");
109 doc.add_paragraph("");
110
111 // --- Paragraph alignment ---
112 doc.add_paragraph("Paragraph Alignment").style("Heading2");
113
114 doc.add_paragraph("This paragraph is left-aligned (the default).")
115 .alignment(Alignment::Left);
116 doc.add_paragraph("This paragraph is center-aligned.")
117 .alignment(Alignment::Center);
118 doc.add_paragraph("This paragraph is right-aligned.")
119 .alignment(Alignment::Right);
120 doc.add_paragraph(
121 "This paragraph is justified. To demonstrate justified text properly, it needs \
122 to be long enough to span multiple lines so the word spacing adjustment is visible \
123 across the full width of the text area on the page.",
124 )
125 .alignment(Alignment::Justify);
126
127 doc.add_paragraph("");
128
129 // --- Run formatting ---
130 doc.add_paragraph("Run Formatting").style("Heading2");
131
132 {
133 let mut p = doc.add_paragraph("");
134 p.add_run("Bold text").bold(true);
135 p.add_run(" | ");
136 p.add_run("Italic text").italic(true);
137 p.add_run(" | ");
138 p.add_run("Bold + Italic").bold(true).italic(true);
139 }
140 {
141 let mut p = doc.add_paragraph("");
142 p.add_run("Single underline").underline(true);
143 p.add_run(" | ");
144 p.add_run("Strikethrough").strike(true);
145 p.add_run(" | ");
146 p.add_run("Double strikethrough").double_strike(true);
147 }
148 {
149 let mut p = doc.add_paragraph("");
150 p.add_run("Red text").color("FF0000");
151 p.add_run(" | ");
152 p.add_run("Blue text").color("0000FF");
153 p.add_run(" | ");
154 p.add_run("Green text").color("00AA00");
155 p.add_run(" | ");
156 p.add_run("Highlighted").highlight("FFFF00");
157 }
158 {
159 let mut p = doc.add_paragraph("");
160 p.add_run("8pt small").size(8.0);
161 p.add_run(" | ");
162 p.add_run("11pt normal").size(11.0);
163 p.add_run(" | ");
164 p.add_run("16pt large").size(16.0);
165 p.add_run(" | ");
166 p.add_run("24pt extra-large").size(24.0);
167 }
168 {
169 let mut p = doc.add_paragraph("");
170 p.add_run("Arial font").font("Arial");
171 p.add_run(" | ");
172 p.add_run("Times New Roman font").font("Times New Roman");
173 p.add_run(" | ");
174 p.add_run("Courier New font").font("Courier New");
175 }
176 {
177 let mut p = doc.add_paragraph("");
178 p.add_run("Normal");
179 p.add_run(" H").size(11.0);
180 p.add_run("2").subscript();
181 p.add_run("O (subscript)").size(11.0);
182 p.add_run(" | E = mc").size(11.0);
183 p.add_run("2").superscript();
184 p.add_run(" (superscript)").size(11.0);
185 }
186 {
187 let mut p = doc.add_paragraph("");
188 p.add_run("ALL CAPS").all_caps(true);
189 p.add_run(" | ");
190 p.add_run("Small Caps").small_caps(true);
191 p.add_run(" | ");
192 p.add_run("Expanded spacing")
193 .character_spacing(Length::twips(40));
194 }
195
196 doc.add_paragraph("");
197
198 // --- Paragraph formatting ---
199 doc.add_paragraph("Paragraph Formatting").style("Heading2");
200
201 doc.add_paragraph("Paragraph with shading (light green background)")
202 .shading("E2EFDA");
203
204 doc.add_paragraph("Paragraph with bottom border")
205 .border_bottom(BorderStyle::Single, 6, "2E75B6");
206
207 doc.add_paragraph("Paragraph with all borders")
208 .border_all(BorderStyle::Single, 4, "FF0000");
209
210 doc.add_paragraph("Paragraph with 1-inch left indent and hanging indent")
211 .indent_left(Length::inches(1.0))
212 .hanging_indent(Length::inches(0.5));
213
214 doc.add_paragraph("Paragraph with first-line indent of 0.5 inches")
215 .first_line_indent(Length::inches(0.5));
216
217 doc.add_paragraph("Paragraph with extra space before (24pt) and after (12pt)")
218 .space_before(Length::pt(24.0))
219 .space_after(Length::pt(12.0));
220
221 doc.add_paragraph(
222 "Paragraph with double line spacing. This text should have extra vertical \
223 space between lines to demonstrate the line_spacing_multiple setting.",
224 )
225 .line_spacing_multiple(2.0);
226
227 doc.add_paragraph("Paragraph with keep-with-next (won't break from the next paragraph)")
228 .keep_with_next(true);
229 doc.add_paragraph("(This stays with the paragraph above.)");
230
231 // =========================================================================
232 // PAGE 3: LISTS & TAB STOPS
233 // =========================================================================
234 doc.add_paragraph("").page_break_before(true);
235
236 doc.add_paragraph("2. Lists").style("Heading1");
237
238 doc.add_paragraph("Bullet List").style("Heading2");
239
240 doc.add_bullet_list_item("First bullet item", 0);
241 doc.add_bullet_list_item("Second bullet item", 0);
242 doc.add_bullet_list_item("Nested level 1", 1);
243 doc.add_bullet_list_item("Nested level 2", 2);
244 doc.add_bullet_list_item("Back to level 1", 1);
245 doc.add_bullet_list_item("Third bullet item", 0);
246
247 doc.add_paragraph("");
248
249 doc.add_paragraph("Numbered List").style("Heading2");
250
251 doc.add_numbered_list_item("First numbered item", 0);
252 doc.add_numbered_list_item("Second numbered item", 0);
253 doc.add_numbered_list_item("Sub-item A", 1);
254 doc.add_numbered_list_item("Sub-item B", 1);
255 doc.add_numbered_list_item("Third numbered item", 0);
256
257 doc.add_paragraph("");
258
259 // --- Tab stops ---
260 doc.add_paragraph("Tab Stops").style("Heading2");
261
262 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
263 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
264 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
265 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
266 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
267
268 doc.add_paragraph("Item\t........\tPrice")
269 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
270 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
271 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
272
273 doc.add_paragraph("Widget A\t........\t$19.99")
274 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
275 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
276 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
277
278 doc.add_paragraph("Gadget B\t________\t$249.50")
279 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
280 .add_tab_stop_with_leader(
281 TabAlignment::Right,
282 Length::inches(4.0),
283 TabLeader::Underscore,
284 )
285 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
286
287 // =========================================================================
288 // PAGE 4: TABLES
289 // =========================================================================
290 doc.add_paragraph("").page_break_before(true);
291
292 doc.add_paragraph("3. Tables").style("Heading1");
293
294 // --- Basic table with borders ---
295 doc.add_paragraph("Basic Table with Borders")
296 .style("Heading2");
297
298 {
299 let mut tbl = doc.add_table(4, 3);
300 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
301
302 // Header row
303 for col in 0..3 {
304 tbl.cell(0, col).unwrap().shading("2E75B6");
305 }
306 tbl.cell(0, 0).unwrap().set_text("Name");
307 tbl.cell(0, 1).unwrap().set_text("Role");
308 tbl.cell(0, 2).unwrap().set_text("Location");
309
310 tbl.cell(1, 0).unwrap().set_text("Alice Johnson");
311 tbl.cell(1, 1).unwrap().set_text("Engineering Lead");
312 tbl.cell(1, 2).unwrap().set_text("New York");
313
314 tbl.cell(2, 0).unwrap().set_text("Bob Smith");
315 tbl.cell(2, 1).unwrap().set_text("Product Manager");
316 tbl.cell(2, 2).unwrap().set_text("San Francisco");
317
318 tbl.cell(3, 0).unwrap().set_text("Carol Davis");
319 tbl.cell(3, 1).unwrap().set_text("Designer");
320 tbl.cell(3, 2).unwrap().set_text("London");
321 }
322
323 doc.add_paragraph("");
324
325 // --- Table with cell merging ---
326 doc.add_paragraph("Table with Cell Merging & Vertical Alignment")
327 .style("Heading2");
328
329 {
330 let mut tbl = doc.add_table(4, 4);
331 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
332 tbl = tbl.width_pct(100.0);
333
334 // Header spanning all columns
335 tbl.cell(0, 0).unwrap().set_text("Quarterly Revenue Report");
336 tbl.cell(0, 0).unwrap().shading("1F4E79");
337 tbl.cell(0, 0).unwrap().grid_span(4);
338
339 // Sub-header
340 tbl.cell(1, 0).unwrap().set_text("Region");
341 tbl.cell(1, 0).unwrap().shading("D6E4F0");
342 tbl.cell(1, 1).unwrap().set_text("Q1");
343 tbl.cell(1, 1).unwrap().shading("D6E4F0");
344 tbl.cell(1, 2).unwrap().set_text("Q2");
345 tbl.cell(1, 2).unwrap().shading("D6E4F0");
346 tbl.cell(1, 3).unwrap().set_text("Total");
347 tbl.cell(1, 3).unwrap().shading("D6E4F0");
348
349 // Data
350 tbl.cell(2, 0).unwrap().set_text("North America");
351 tbl.cell(2, 1).unwrap().set_text("$2.4M");
352 tbl.cell(2, 2).unwrap().set_text("$2.7M");
353 tbl.cell(2, 3).unwrap().set_text("$5.1M");
354
355 tbl.cell(3, 0).unwrap().set_text("Europe");
356 tbl.cell(3, 1).unwrap().set_text("$1.8M");
357 tbl.cell(3, 2).unwrap().set_text("$2.0M");
358 tbl.cell(3, 3).unwrap().set_text("$3.8M");
359
360 // Vertical alignment on data cells
361 tbl.cell(2, 3)
362 .unwrap()
363 .vertical_alignment(VerticalAlignment::Center);
364 tbl.cell(3, 3)
365 .unwrap()
366 .vertical_alignment(VerticalAlignment::Bottom);
367 }
368
369 doc.add_paragraph("");
370
371 // --- Table with vertical merge ---
372 doc.add_paragraph("Table with Vertical Merge")
373 .style("Heading2");
374
375 {
376 let mut tbl = doc.add_table(4, 3);
377 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
378
379 tbl.cell(0, 0).unwrap().set_text("Category");
380 tbl.cell(0, 0).unwrap().shading("E2EFDA");
381 tbl.cell(0, 1).unwrap().set_text("Item");
382 tbl.cell(0, 1).unwrap().shading("E2EFDA");
383 tbl.cell(0, 2).unwrap().set_text("Price");
384 tbl.cell(0, 2).unwrap().shading("E2EFDA");
385
386 // "Hardware" spans rows 1-2
387 tbl.cell(1, 0).unwrap().set_text("Hardware");
388 tbl.cell(1, 0).unwrap().v_merge_restart();
389 tbl.cell(1, 1).unwrap().set_text("Laptop");
390 tbl.cell(1, 2).unwrap().set_text("$1,200");
391
392 tbl.cell(2, 0).unwrap().v_merge_continue();
393 tbl.cell(2, 1).unwrap().set_text("Monitor");
394 tbl.cell(2, 2).unwrap().set_text("$450");
395
396 // "Software" on row 3
397 tbl.cell(3, 0).unwrap().set_text("Software");
398 tbl.cell(3, 1).unwrap().set_text("IDE License");
399 tbl.cell(3, 2).unwrap().set_text("$200/yr");
400 }
401
402 doc.add_paragraph("");
403
404 // --- Nested table ---
405 doc.add_paragraph("Nested Table").style("Heading2");
406
407 {
408 let mut tbl = doc.add_table(2, 2);
409 tbl = tbl.borders(BorderStyle::Single, 6, "2E75B6");
410
411 tbl.cell(0, 0).unwrap().set_text("Outer Cell (0,0)");
412 tbl.cell(0, 1).unwrap().set_text("Outer Cell (0,1)");
413 tbl.cell(1, 0).unwrap().set_text("Outer Cell (1,0)");
414
415 // Nested table inside cell (1,1)
416 {
417 let mut cell = tbl.cell(1, 1).unwrap();
418 cell.set_text("Contains nested table:");
419 let mut nested = cell.add_table(2, 2);
420 nested = nested.borders(BorderStyle::Single, 2, "FF6600");
421 nested.cell(0, 0).unwrap().set_text("Inner A");
422 nested.cell(0, 1).unwrap().set_text("Inner B");
423 nested.cell(1, 0).unwrap().set_text("Inner C");
424 nested.cell(1, 1).unwrap().set_text("Inner D");
425 }
426 }
427
428 // =========================================================================
429 // PAGE 5: IMAGES
430 // =========================================================================
431 doc.add_paragraph("").page_break_before(true);
432
433 doc.add_paragraph("4. Images").style("Heading1");
434
435 doc.add_paragraph("Inline Image").style("Heading2");
436
437 doc.add_paragraph("Below is an inline image (200x50 pixels, blue gradient):");
438 let inline_img = create_sample_png(200, 50, [0, 80, 200]);
439 doc.add_picture(
440 &inline_img,
441 "inline_chart.png",
442 Length::inches(3.0),
443 Length::inches(0.75),
444 );
445
446 doc.add_paragraph("");
447
448 doc.add_paragraph("Header Image").style("Heading2");
449
450 // Replace the text-only header with an image header
451 let header_img = create_sample_png(400, 40, [40, 40, 40]);
452 doc.set_header_image(
453 &header_img,
454 "header_logo.png",
455 Length::inches(2.0),
456 Length::inches(0.2),
457 );
458
459 doc.add_paragraph(
460 "The document header has been replaced with an inline image. \
461 Check the header area at the top of this page.",
462 );
463
464 doc.add_paragraph("");
465 doc.add_paragraph(
466 "Note: The cover page uses a full-page background image behind the text, \
467 demonstrated on page 1 via add_background_image().",
468 );
469
470 // =========================================================================
471 // PAGE 6: CONTENT MANIPULATION — placeholder replacement, insertion
472 // =========================================================================
473 doc.add_paragraph("").page_break_before(true);
474
475 doc.add_paragraph("5. Content Manipulation")
476 .style("Heading1");
477
478 // --- Placeholder replacement ---
479 doc.add_paragraph("Placeholder Replacement")
480 .style("Heading2");
481
482 doc.add_paragraph(
483 "Before replacement, this document contained {{customer}} and {{date}} placeholders.",
484 );
485
486 {
487 let mut p = doc.add_paragraph("");
488 p.add_run("Customer: ").bold(true);
489 p.add_run("{{customer}}");
490 }
491 {
492 let mut p = doc.add_paragraph("");
493 p.add_run("Date: ").bold(true);
494 p.add_run("{{date}}");
495 }
496 doc.add_paragraph("Reference: {{ref_number}}");
497
498 // Table with placeholders
499 {
500 let mut tbl = doc.add_table(3, 2);
501 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
502 tbl.cell(0, 0).unwrap().set_text("Field");
503 tbl.cell(0, 0).unwrap().shading("D6E4F0");
504 tbl.cell(0, 1).unwrap().set_text("Value");
505 tbl.cell(0, 1).unwrap().shading("D6E4F0");
506 tbl.cell(1, 0).unwrap().set_text("Project");
507 tbl.cell(1, 1).unwrap().set_text("{{project}}");
508 tbl.cell(2, 0).unwrap().set_text("Status");
509 tbl.cell(2, 1).unwrap().set_text("{{status}}");
510 }
511
512 // Perform replacements
513 let mut replacements = HashMap::new();
514 replacements.insert("{{customer}}", "Acme Corporation");
515 replacements.insert("{{date}}", "February 22, 2026");
516 replacements.insert("{{ref_number}}", "REF-2026-001");
517 replacements.insert("{{project}}", "Infrastructure Upgrade");
518 replacements.insert("{{status}}", "In Progress");
519 let replace_count = doc.replace_all(&replacements);
520
521 doc.add_paragraph("");
522 doc.add_paragraph(&format!(
523 "(Replaced {} placeholders above — in body text and table cells)",
524 replace_count
525 ));
526
527 doc.add_paragraph("");
528
529 // --- Content insertion ---
530 doc.add_paragraph("Content Insertion").style("Heading2");
531
532 doc.add_paragraph("Section A: First section of content.");
533 doc.add_paragraph("Section C: Third section of content.");
534
535 // Insert "Section B" between A and C
536 if let Some(idx) = doc.find_content_index("Section C") {
537 doc.insert_paragraph(
538 idx,
539 "Section B: Inserted between A and C using find_content_index().",
540 );
541 }
542
543 doc.add_paragraph("");
544 doc.add_paragraph(
545 "The paragraph above ('Section B') was inserted at a specific position \
546 using find_content_index() + insert_paragraph().",
547 );
548
549 // =========================================================================
550 // PAGE 7: LANDSCAPE — section break, wide table
551 // =========================================================================
552 doc.add_paragraph("").section_break(SectionBreak::NextPage);
553
554 doc.add_paragraph("6. Mixed Page Orientation")
555 .style("Heading1");
556
557 doc.add_paragraph(
558 "This page is in LANDSCAPE orientation. It was created using a section break \
559 followed by section_landscape(). This is useful for wide tables or charts.",
560 );
561
562 doc.add_paragraph("");
563
564 // Wide table for landscape
565 {
566 let mut tbl = doc.add_table(4, 7);
567 tbl = tbl.borders(BorderStyle::Single, 4, "2E75B6");
568
569 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
570 for (col, h) in headers.iter().enumerate() {
571 tbl.cell(0, col).unwrap().set_text(h);
572 tbl.cell(0, col).unwrap().shading("2E75B6");
573 }
574
575 let data = [
576 [
577 "North America",
578 "$1.2M",
579 "$1.3M",
580 "$1.4M",
581 "$1.5M",
582 "$1.6M",
583 "$7.0M",
584 ],
585 [
586 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
587 ],
588 [
589 "Asia Pacific",
590 "$0.5M",
591 "$0.6M",
592 "$0.7M",
593 "$0.7M",
594 "$0.8M",
595 "$3.3M",
596 ],
597 ];
598 for (row_idx, row_data) in data.iter().enumerate() {
599 for (col, val) in row_data.iter().enumerate() {
600 tbl.cell(row_idx + 1, col).unwrap().set_text(val);
601 }
602 }
603 }
604
605 // End landscape, return to portrait
606 doc.add_paragraph("")
607 .section_break(SectionBreak::NextPage)
608 .section_landscape();
609
610 // =========================================================================
611 // PAGE 8: BACK TO PORTRAIT — styles, final notes
612 // =========================================================================
613 doc.add_paragraph("7. Custom Styles & Summary")
614 .style("Heading1");
615
616 doc.add_paragraph(
617 "This final page is back in portrait orientation after a section break. \
618 The document has demonstrated:",
619 );
620
621 doc.add_bullet_list_item(
622 "Page setup: size, margins, header/footer distance, gutter",
623 0,
624 );
625 doc.add_bullet_list_item("Document metadata: title, author, subject, keywords", 0);
626 doc.add_bullet_list_item("Headers and footers: text, images, different first page", 0);
627 doc.add_bullet_list_item("Background images: full-page behind text", 0);
628 doc.add_bullet_list_item(
629 "Text formatting: bold, italic, underline, strike, color, size, font",
630 0,
631 );
632 doc.add_bullet_list_item(
633 "Advanced run formatting: superscript, subscript, caps, spacing",
634 0,
635 );
636 doc.add_bullet_list_item(
637 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
638 0,
639 );
640 doc.add_bullet_list_item("Bullet and numbered lists with nesting levels", 0);
641 doc.add_bullet_list_item("Tab stops with dot/underscore leaders", 0);
642 doc.add_bullet_list_item(
643 "Tables: borders, shading, column spans, row spans, nesting",
644 0,
645 );
646 doc.add_bullet_list_item("Vertical alignment in table cells", 0);
647 doc.add_bullet_list_item("Inline images", 0);
648 doc.add_bullet_list_item("Placeholder replacement in body and table cells", 0);
649 doc.add_bullet_list_item("Content insertion at specific positions", 0);
650 doc.add_bullet_list_item(
651 "Section breaks with mixed portrait/landscape orientation",
652 0,
653 );
654
655 doc.add_paragraph("");
656 doc.add_paragraph("All features above were built entirely from scratch using the rdocx API.")
657 .alignment(Alignment::Center)
658 .shading("E2EFDA")
659 .border_all(BorderStyle::Single, 2, "00AA00");
660
661 doc.save(path).unwrap();
662}Sourcepub fn paragraphs(&self) -> Vec<ParagraphRef<'_>>
pub fn paragraphs(&self) -> Vec<ParagraphRef<'_>>
Get immutable references to all paragraphs.
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}34fn generate_feature_showcase(path: &Path) {
35 let mut doc = Document::new();
36
37 // =========================================================================
38 // PAGE SETUP & METADATA
39 // =========================================================================
40 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
41 doc.set_margins(
42 Length::inches(1.0), // top
43 Length::inches(1.0), // right
44 Length::inches(1.0), // bottom
45 Length::inches(1.0), // left
46 );
47 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
48 doc.set_gutter(Length::twips(0));
49
50 doc.set_title("rdocx Feature Showcase");
51 doc.set_author("rdocx Sample Generator");
52 doc.set_subject("Comprehensive feature demonstration");
53 doc.set_keywords("rdocx, docx, rust, sample");
54
55 // Header & Footer
56 doc.set_header("rdocx Feature Showcase");
57 doc.set_footer("Generated by rdocx — Page");
58
59 // Different first page header
60 doc.set_different_first_page(true);
61 doc.set_first_page_header("rdocx");
62 doc.set_first_page_footer("Feature Showcase — Cover Page");
63
64 // =========================================================================
65 // PAGE 1: COVER PAGE — background image, run formatting
66 // =========================================================================
67 let bg_cover = create_sample_png(612, 792, [30, 60, 120]);
68 doc.add_background_image(&bg_cover, "cover_bg.png");
69
70 doc.add_paragraph(""); // spacer
71 doc.add_paragraph(""); // spacer
72 doc.add_paragraph(""); // spacer
73
74 {
75 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
76 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
77 }
78 {
79 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
80 p.add_run("Feature Showcase")
81 .size(28.0)
82 .color("FFFFFF")
83 .italic(true);
84 }
85
86 doc.add_paragraph(""); // spacer
87
88 {
89 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
90 p.add_run("A comprehensive demonstration of every feature")
91 .size(14.0)
92 .color("CCDDFF");
93 }
94 {
95 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
96 p.add_run("provided by the rdocx Rust crate for DOCX generation.")
97 .size(14.0)
98 .color("CCDDFF");
99 }
100
101 // =========================================================================
102 // PAGE 2: TEXT FORMATTING
103 // =========================================================================
104 doc.add_paragraph("").page_break_before(true);
105
106 doc.add_paragraph("1. Text Formatting").style("Heading1");
107
108 doc.add_paragraph("This section demonstrates paragraph and run-level formatting options.");
109 doc.add_paragraph("");
110
111 // --- Paragraph alignment ---
112 doc.add_paragraph("Paragraph Alignment").style("Heading2");
113
114 doc.add_paragraph("This paragraph is left-aligned (the default).")
115 .alignment(Alignment::Left);
116 doc.add_paragraph("This paragraph is center-aligned.")
117 .alignment(Alignment::Center);
118 doc.add_paragraph("This paragraph is right-aligned.")
119 .alignment(Alignment::Right);
120 doc.add_paragraph(
121 "This paragraph is justified. To demonstrate justified text properly, it needs \
122 to be long enough to span multiple lines so the word spacing adjustment is visible \
123 across the full width of the text area on the page.",
124 )
125 .alignment(Alignment::Justify);
126
127 doc.add_paragraph("");
128
129 // --- Run formatting ---
130 doc.add_paragraph("Run Formatting").style("Heading2");
131
132 {
133 let mut p = doc.add_paragraph("");
134 p.add_run("Bold text").bold(true);
135 p.add_run(" | ");
136 p.add_run("Italic text").italic(true);
137 p.add_run(" | ");
138 p.add_run("Bold + Italic").bold(true).italic(true);
139 }
140 {
141 let mut p = doc.add_paragraph("");
142 p.add_run("Single underline").underline(true);
143 p.add_run(" | ");
144 p.add_run("Strikethrough").strike(true);
145 p.add_run(" | ");
146 p.add_run("Double strikethrough").double_strike(true);
147 }
148 {
149 let mut p = doc.add_paragraph("");
150 p.add_run("Red text").color("FF0000");
151 p.add_run(" | ");
152 p.add_run("Blue text").color("0000FF");
153 p.add_run(" | ");
154 p.add_run("Green text").color("00AA00");
155 p.add_run(" | ");
156 p.add_run("Highlighted").highlight("FFFF00");
157 }
158 {
159 let mut p = doc.add_paragraph("");
160 p.add_run("8pt small").size(8.0);
161 p.add_run(" | ");
162 p.add_run("11pt normal").size(11.0);
163 p.add_run(" | ");
164 p.add_run("16pt large").size(16.0);
165 p.add_run(" | ");
166 p.add_run("24pt extra-large").size(24.0);
167 }
168 {
169 let mut p = doc.add_paragraph("");
170 p.add_run("Arial font").font("Arial");
171 p.add_run(" | ");
172 p.add_run("Times New Roman font").font("Times New Roman");
173 p.add_run(" | ");
174 p.add_run("Courier New font").font("Courier New");
175 }
176 {
177 let mut p = doc.add_paragraph("");
178 p.add_run("Normal");
179 p.add_run(" H").size(11.0);
180 p.add_run("2").subscript();
181 p.add_run("O (subscript)").size(11.0);
182 p.add_run(" | E = mc").size(11.0);
183 p.add_run("2").superscript();
184 p.add_run(" (superscript)").size(11.0);
185 }
186 {
187 let mut p = doc.add_paragraph("");
188 p.add_run("ALL CAPS").all_caps(true);
189 p.add_run(" | ");
190 p.add_run("Small Caps").small_caps(true);
191 p.add_run(" | ");
192 p.add_run("Expanded spacing")
193 .character_spacing(Length::twips(40));
194 }
195
196 doc.add_paragraph("");
197
198 // --- Paragraph formatting ---
199 doc.add_paragraph("Paragraph Formatting").style("Heading2");
200
201 doc.add_paragraph("Paragraph with shading (light green background)")
202 .shading("E2EFDA");
203
204 doc.add_paragraph("Paragraph with bottom border")
205 .border_bottom(BorderStyle::Single, 6, "2E75B6");
206
207 doc.add_paragraph("Paragraph with all borders")
208 .border_all(BorderStyle::Single, 4, "FF0000");
209
210 doc.add_paragraph("Paragraph with 1-inch left indent and hanging indent")
211 .indent_left(Length::inches(1.0))
212 .hanging_indent(Length::inches(0.5));
213
214 doc.add_paragraph("Paragraph with first-line indent of 0.5 inches")
215 .first_line_indent(Length::inches(0.5));
216
217 doc.add_paragraph("Paragraph with extra space before (24pt) and after (12pt)")
218 .space_before(Length::pt(24.0))
219 .space_after(Length::pt(12.0));
220
221 doc.add_paragraph(
222 "Paragraph with double line spacing. This text should have extra vertical \
223 space between lines to demonstrate the line_spacing_multiple setting.",
224 )
225 .line_spacing_multiple(2.0);
226
227 doc.add_paragraph("Paragraph with keep-with-next (won't break from the next paragraph)")
228 .keep_with_next(true);
229 doc.add_paragraph("(This stays with the paragraph above.)");
230
231 // =========================================================================
232 // PAGE 3: LISTS & TAB STOPS
233 // =========================================================================
234 doc.add_paragraph("").page_break_before(true);
235
236 doc.add_paragraph("2. Lists").style("Heading1");
237
238 doc.add_paragraph("Bullet List").style("Heading2");
239
240 doc.add_bullet_list_item("First bullet item", 0);
241 doc.add_bullet_list_item("Second bullet item", 0);
242 doc.add_bullet_list_item("Nested level 1", 1);
243 doc.add_bullet_list_item("Nested level 2", 2);
244 doc.add_bullet_list_item("Back to level 1", 1);
245 doc.add_bullet_list_item("Third bullet item", 0);
246
247 doc.add_paragraph("");
248
249 doc.add_paragraph("Numbered List").style("Heading2");
250
251 doc.add_numbered_list_item("First numbered item", 0);
252 doc.add_numbered_list_item("Second numbered item", 0);
253 doc.add_numbered_list_item("Sub-item A", 1);
254 doc.add_numbered_list_item("Sub-item B", 1);
255 doc.add_numbered_list_item("Third numbered item", 0);
256
257 doc.add_paragraph("");
258
259 // --- Tab stops ---
260 doc.add_paragraph("Tab Stops").style("Heading2");
261
262 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
263 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
264 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
265 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
266 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
267
268 doc.add_paragraph("Item\t........\tPrice")
269 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
270 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
271 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
272
273 doc.add_paragraph("Widget A\t........\t$19.99")
274 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
275 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
276 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
277
278 doc.add_paragraph("Gadget B\t________\t$249.50")
279 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
280 .add_tab_stop_with_leader(
281 TabAlignment::Right,
282 Length::inches(4.0),
283 TabLeader::Underscore,
284 )
285 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
286
287 // =========================================================================
288 // PAGE 4: TABLES
289 // =========================================================================
290 doc.add_paragraph("").page_break_before(true);
291
292 doc.add_paragraph("3. Tables").style("Heading1");
293
294 // --- Basic table with borders ---
295 doc.add_paragraph("Basic Table with Borders")
296 .style("Heading2");
297
298 {
299 let mut tbl = doc.add_table(4, 3);
300 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
301
302 // Header row
303 for col in 0..3 {
304 tbl.cell(0, col).unwrap().shading("2E75B6");
305 }
306 tbl.cell(0, 0).unwrap().set_text("Name");
307 tbl.cell(0, 1).unwrap().set_text("Role");
308 tbl.cell(0, 2).unwrap().set_text("Location");
309
310 tbl.cell(1, 0).unwrap().set_text("Alice Johnson");
311 tbl.cell(1, 1).unwrap().set_text("Engineering Lead");
312 tbl.cell(1, 2).unwrap().set_text("New York");
313
314 tbl.cell(2, 0).unwrap().set_text("Bob Smith");
315 tbl.cell(2, 1).unwrap().set_text("Product Manager");
316 tbl.cell(2, 2).unwrap().set_text("San Francisco");
317
318 tbl.cell(3, 0).unwrap().set_text("Carol Davis");
319 tbl.cell(3, 1).unwrap().set_text("Designer");
320 tbl.cell(3, 2).unwrap().set_text("London");
321 }
322
323 doc.add_paragraph("");
324
325 // --- Table with cell merging ---
326 doc.add_paragraph("Table with Cell Merging & Vertical Alignment")
327 .style("Heading2");
328
329 {
330 let mut tbl = doc.add_table(4, 4);
331 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
332 tbl = tbl.width_pct(100.0);
333
334 // Header spanning all columns
335 tbl.cell(0, 0).unwrap().set_text("Quarterly Revenue Report");
336 tbl.cell(0, 0).unwrap().shading("1F4E79");
337 tbl.cell(0, 0).unwrap().grid_span(4);
338
339 // Sub-header
340 tbl.cell(1, 0).unwrap().set_text("Region");
341 tbl.cell(1, 0).unwrap().shading("D6E4F0");
342 tbl.cell(1, 1).unwrap().set_text("Q1");
343 tbl.cell(1, 1).unwrap().shading("D6E4F0");
344 tbl.cell(1, 2).unwrap().set_text("Q2");
345 tbl.cell(1, 2).unwrap().shading("D6E4F0");
346 tbl.cell(1, 3).unwrap().set_text("Total");
347 tbl.cell(1, 3).unwrap().shading("D6E4F0");
348
349 // Data
350 tbl.cell(2, 0).unwrap().set_text("North America");
351 tbl.cell(2, 1).unwrap().set_text("$2.4M");
352 tbl.cell(2, 2).unwrap().set_text("$2.7M");
353 tbl.cell(2, 3).unwrap().set_text("$5.1M");
354
355 tbl.cell(3, 0).unwrap().set_text("Europe");
356 tbl.cell(3, 1).unwrap().set_text("$1.8M");
357 tbl.cell(3, 2).unwrap().set_text("$2.0M");
358 tbl.cell(3, 3).unwrap().set_text("$3.8M");
359
360 // Vertical alignment on data cells
361 tbl.cell(2, 3)
362 .unwrap()
363 .vertical_alignment(VerticalAlignment::Center);
364 tbl.cell(3, 3)
365 .unwrap()
366 .vertical_alignment(VerticalAlignment::Bottom);
367 }
368
369 doc.add_paragraph("");
370
371 // --- Table with vertical merge ---
372 doc.add_paragraph("Table with Vertical Merge")
373 .style("Heading2");
374
375 {
376 let mut tbl = doc.add_table(4, 3);
377 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
378
379 tbl.cell(0, 0).unwrap().set_text("Category");
380 tbl.cell(0, 0).unwrap().shading("E2EFDA");
381 tbl.cell(0, 1).unwrap().set_text("Item");
382 tbl.cell(0, 1).unwrap().shading("E2EFDA");
383 tbl.cell(0, 2).unwrap().set_text("Price");
384 tbl.cell(0, 2).unwrap().shading("E2EFDA");
385
386 // "Hardware" spans rows 1-2
387 tbl.cell(1, 0).unwrap().set_text("Hardware");
388 tbl.cell(1, 0).unwrap().v_merge_restart();
389 tbl.cell(1, 1).unwrap().set_text("Laptop");
390 tbl.cell(1, 2).unwrap().set_text("$1,200");
391
392 tbl.cell(2, 0).unwrap().v_merge_continue();
393 tbl.cell(2, 1).unwrap().set_text("Monitor");
394 tbl.cell(2, 2).unwrap().set_text("$450");
395
396 // "Software" on row 3
397 tbl.cell(3, 0).unwrap().set_text("Software");
398 tbl.cell(3, 1).unwrap().set_text("IDE License");
399 tbl.cell(3, 2).unwrap().set_text("$200/yr");
400 }
401
402 doc.add_paragraph("");
403
404 // --- Nested table ---
405 doc.add_paragraph("Nested Table").style("Heading2");
406
407 {
408 let mut tbl = doc.add_table(2, 2);
409 tbl = tbl.borders(BorderStyle::Single, 6, "2E75B6");
410
411 tbl.cell(0, 0).unwrap().set_text("Outer Cell (0,0)");
412 tbl.cell(0, 1).unwrap().set_text("Outer Cell (0,1)");
413 tbl.cell(1, 0).unwrap().set_text("Outer Cell (1,0)");
414
415 // Nested table inside cell (1,1)
416 {
417 let mut cell = tbl.cell(1, 1).unwrap();
418 cell.set_text("Contains nested table:");
419 let mut nested = cell.add_table(2, 2);
420 nested = nested.borders(BorderStyle::Single, 2, "FF6600");
421 nested.cell(0, 0).unwrap().set_text("Inner A");
422 nested.cell(0, 1).unwrap().set_text("Inner B");
423 nested.cell(1, 0).unwrap().set_text("Inner C");
424 nested.cell(1, 1).unwrap().set_text("Inner D");
425 }
426 }
427
428 // =========================================================================
429 // PAGE 5: IMAGES
430 // =========================================================================
431 doc.add_paragraph("").page_break_before(true);
432
433 doc.add_paragraph("4. Images").style("Heading1");
434
435 doc.add_paragraph("Inline Image").style("Heading2");
436
437 doc.add_paragraph("Below is an inline image (200x50 pixels, blue gradient):");
438 let inline_img = create_sample_png(200, 50, [0, 80, 200]);
439 doc.add_picture(
440 &inline_img,
441 "inline_chart.png",
442 Length::inches(3.0),
443 Length::inches(0.75),
444 );
445
446 doc.add_paragraph("");
447
448 doc.add_paragraph("Header Image").style("Heading2");
449
450 // Replace the text-only header with an image header
451 let header_img = create_sample_png(400, 40, [40, 40, 40]);
452 doc.set_header_image(
453 &header_img,
454 "header_logo.png",
455 Length::inches(2.0),
456 Length::inches(0.2),
457 );
458
459 doc.add_paragraph(
460 "The document header has been replaced with an inline image. \
461 Check the header area at the top of this page.",
462 );
463
464 doc.add_paragraph("");
465 doc.add_paragraph(
466 "Note: The cover page uses a full-page background image behind the text, \
467 demonstrated on page 1 via add_background_image().",
468 );
469
470 // =========================================================================
471 // PAGE 6: CONTENT MANIPULATION — placeholder replacement, insertion
472 // =========================================================================
473 doc.add_paragraph("").page_break_before(true);
474
475 doc.add_paragraph("5. Content Manipulation")
476 .style("Heading1");
477
478 // --- Placeholder replacement ---
479 doc.add_paragraph("Placeholder Replacement")
480 .style("Heading2");
481
482 doc.add_paragraph(
483 "Before replacement, this document contained {{customer}} and {{date}} placeholders.",
484 );
485
486 {
487 let mut p = doc.add_paragraph("");
488 p.add_run("Customer: ").bold(true);
489 p.add_run("{{customer}}");
490 }
491 {
492 let mut p = doc.add_paragraph("");
493 p.add_run("Date: ").bold(true);
494 p.add_run("{{date}}");
495 }
496 doc.add_paragraph("Reference: {{ref_number}}");
497
498 // Table with placeholders
499 {
500 let mut tbl = doc.add_table(3, 2);
501 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
502 tbl.cell(0, 0).unwrap().set_text("Field");
503 tbl.cell(0, 0).unwrap().shading("D6E4F0");
504 tbl.cell(0, 1).unwrap().set_text("Value");
505 tbl.cell(0, 1).unwrap().shading("D6E4F0");
506 tbl.cell(1, 0).unwrap().set_text("Project");
507 tbl.cell(1, 1).unwrap().set_text("{{project}}");
508 tbl.cell(2, 0).unwrap().set_text("Status");
509 tbl.cell(2, 1).unwrap().set_text("{{status}}");
510 }
511
512 // Perform replacements
513 let mut replacements = HashMap::new();
514 replacements.insert("{{customer}}", "Acme Corporation");
515 replacements.insert("{{date}}", "February 22, 2026");
516 replacements.insert("{{ref_number}}", "REF-2026-001");
517 replacements.insert("{{project}}", "Infrastructure Upgrade");
518 replacements.insert("{{status}}", "In Progress");
519 let replace_count = doc.replace_all(&replacements);
520
521 doc.add_paragraph("");
522 doc.add_paragraph(&format!(
523 "(Replaced {} placeholders above — in body text and table cells)",
524 replace_count
525 ));
526
527 doc.add_paragraph("");
528
529 // --- Content insertion ---
530 doc.add_paragraph("Content Insertion").style("Heading2");
531
532 doc.add_paragraph("Section A: First section of content.");
533 doc.add_paragraph("Section C: Third section of content.");
534
535 // Insert "Section B" between A and C
536 if let Some(idx) = doc.find_content_index("Section C") {
537 doc.insert_paragraph(
538 idx,
539 "Section B: Inserted between A and C using find_content_index().",
540 );
541 }
542
543 doc.add_paragraph("");
544 doc.add_paragraph(
545 "The paragraph above ('Section B') was inserted at a specific position \
546 using find_content_index() + insert_paragraph().",
547 );
548
549 // =========================================================================
550 // PAGE 7: LANDSCAPE — section break, wide table
551 // =========================================================================
552 doc.add_paragraph("").section_break(SectionBreak::NextPage);
553
554 doc.add_paragraph("6. Mixed Page Orientation")
555 .style("Heading1");
556
557 doc.add_paragraph(
558 "This page is in LANDSCAPE orientation. It was created using a section break \
559 followed by section_landscape(). This is useful for wide tables or charts.",
560 );
561
562 doc.add_paragraph("");
563
564 // Wide table for landscape
565 {
566 let mut tbl = doc.add_table(4, 7);
567 tbl = tbl.borders(BorderStyle::Single, 4, "2E75B6");
568
569 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
570 for (col, h) in headers.iter().enumerate() {
571 tbl.cell(0, col).unwrap().set_text(h);
572 tbl.cell(0, col).unwrap().shading("2E75B6");
573 }
574
575 let data = [
576 [
577 "North America",
578 "$1.2M",
579 "$1.3M",
580 "$1.4M",
581 "$1.5M",
582 "$1.6M",
583 "$7.0M",
584 ],
585 [
586 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
587 ],
588 [
589 "Asia Pacific",
590 "$0.5M",
591 "$0.6M",
592 "$0.7M",
593 "$0.7M",
594 "$0.8M",
595 "$3.3M",
596 ],
597 ];
598 for (row_idx, row_data) in data.iter().enumerate() {
599 for (col, val) in row_data.iter().enumerate() {
600 tbl.cell(row_idx + 1, col).unwrap().set_text(val);
601 }
602 }
603 }
604
605 // End landscape, return to portrait
606 doc.add_paragraph("")
607 .section_break(SectionBreak::NextPage)
608 .section_landscape();
609
610 // =========================================================================
611 // PAGE 8: BACK TO PORTRAIT — styles, final notes
612 // =========================================================================
613 doc.add_paragraph("7. Custom Styles & Summary")
614 .style("Heading1");
615
616 doc.add_paragraph(
617 "This final page is back in portrait orientation after a section break. \
618 The document has demonstrated:",
619 );
620
621 doc.add_bullet_list_item(
622 "Page setup: size, margins, header/footer distance, gutter",
623 0,
624 );
625 doc.add_bullet_list_item("Document metadata: title, author, subject, keywords", 0);
626 doc.add_bullet_list_item("Headers and footers: text, images, different first page", 0);
627 doc.add_bullet_list_item("Background images: full-page behind text", 0);
628 doc.add_bullet_list_item(
629 "Text formatting: bold, italic, underline, strike, color, size, font",
630 0,
631 );
632 doc.add_bullet_list_item(
633 "Advanced run formatting: superscript, subscript, caps, spacing",
634 0,
635 );
636 doc.add_bullet_list_item(
637 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
638 0,
639 );
640 doc.add_bullet_list_item("Bullet and numbered lists with nesting levels", 0);
641 doc.add_bullet_list_item("Tab stops with dot/underscore leaders", 0);
642 doc.add_bullet_list_item(
643 "Tables: borders, shading, column spans, row spans, nesting",
644 0,
645 );
646 doc.add_bullet_list_item("Vertical alignment in table cells", 0);
647 doc.add_bullet_list_item("Inline images", 0);
648 doc.add_bullet_list_item("Placeholder replacement in body and table cells", 0);
649 doc.add_bullet_list_item("Content insertion at specific positions", 0);
650 doc.add_bullet_list_item(
651 "Section breaks with mixed portrait/landscape orientation",
652 0,
653 );
654
655 doc.add_paragraph("");
656 doc.add_paragraph("All features above were built entirely from scratch using the rdocx API.")
657 .alignment(Alignment::Center)
658 .shading("E2EFDA")
659 .border_all(BorderStyle::Single, 2, "00AA00");
660
661 doc.save(path).unwrap();
662}Sourcepub fn paragraph_count(&self) -> usize
pub fn paragraph_count(&self) -> usize
Get the number of paragraphs.
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 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}34fn generate_feature_showcase(path: &Path) {
35 let mut doc = Document::new();
36
37 // =========================================================================
38 // PAGE SETUP & METADATA
39 // =========================================================================
40 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
41 doc.set_margins(
42 Length::inches(1.0), // top
43 Length::inches(1.0), // right
44 Length::inches(1.0), // bottom
45 Length::inches(1.0), // left
46 );
47 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
48 doc.set_gutter(Length::twips(0));
49
50 doc.set_title("rdocx Feature Showcase");
51 doc.set_author("rdocx Sample Generator");
52 doc.set_subject("Comprehensive feature demonstration");
53 doc.set_keywords("rdocx, docx, rust, sample");
54
55 // Header & Footer
56 doc.set_header("rdocx Feature Showcase");
57 doc.set_footer("Generated by rdocx — Page");
58
59 // Different first page header
60 doc.set_different_first_page(true);
61 doc.set_first_page_header("rdocx");
62 doc.set_first_page_footer("Feature Showcase — Cover Page");
63
64 // =========================================================================
65 // PAGE 1: COVER PAGE — background image, run formatting
66 // =========================================================================
67 let bg_cover = create_sample_png(612, 792, [30, 60, 120]);
68 doc.add_background_image(&bg_cover, "cover_bg.png");
69
70 doc.add_paragraph(""); // spacer
71 doc.add_paragraph(""); // spacer
72 doc.add_paragraph(""); // spacer
73
74 {
75 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
76 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
77 }
78 {
79 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
80 p.add_run("Feature Showcase")
81 .size(28.0)
82 .color("FFFFFF")
83 .italic(true);
84 }
85
86 doc.add_paragraph(""); // spacer
87
88 {
89 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
90 p.add_run("A comprehensive demonstration of every feature")
91 .size(14.0)
92 .color("CCDDFF");
93 }
94 {
95 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
96 p.add_run("provided by the rdocx Rust crate for DOCX generation.")
97 .size(14.0)
98 .color("CCDDFF");
99 }
100
101 // =========================================================================
102 // PAGE 2: TEXT FORMATTING
103 // =========================================================================
104 doc.add_paragraph("").page_break_before(true);
105
106 doc.add_paragraph("1. Text Formatting").style("Heading1");
107
108 doc.add_paragraph("This section demonstrates paragraph and run-level formatting options.");
109 doc.add_paragraph("");
110
111 // --- Paragraph alignment ---
112 doc.add_paragraph("Paragraph Alignment").style("Heading2");
113
114 doc.add_paragraph("This paragraph is left-aligned (the default).")
115 .alignment(Alignment::Left);
116 doc.add_paragraph("This paragraph is center-aligned.")
117 .alignment(Alignment::Center);
118 doc.add_paragraph("This paragraph is right-aligned.")
119 .alignment(Alignment::Right);
120 doc.add_paragraph(
121 "This paragraph is justified. To demonstrate justified text properly, it needs \
122 to be long enough to span multiple lines so the word spacing adjustment is visible \
123 across the full width of the text area on the page.",
124 )
125 .alignment(Alignment::Justify);
126
127 doc.add_paragraph("");
128
129 // --- Run formatting ---
130 doc.add_paragraph("Run Formatting").style("Heading2");
131
132 {
133 let mut p = doc.add_paragraph("");
134 p.add_run("Bold text").bold(true);
135 p.add_run(" | ");
136 p.add_run("Italic text").italic(true);
137 p.add_run(" | ");
138 p.add_run("Bold + Italic").bold(true).italic(true);
139 }
140 {
141 let mut p = doc.add_paragraph("");
142 p.add_run("Single underline").underline(true);
143 p.add_run(" | ");
144 p.add_run("Strikethrough").strike(true);
145 p.add_run(" | ");
146 p.add_run("Double strikethrough").double_strike(true);
147 }
148 {
149 let mut p = doc.add_paragraph("");
150 p.add_run("Red text").color("FF0000");
151 p.add_run(" | ");
152 p.add_run("Blue text").color("0000FF");
153 p.add_run(" | ");
154 p.add_run("Green text").color("00AA00");
155 p.add_run(" | ");
156 p.add_run("Highlighted").highlight("FFFF00");
157 }
158 {
159 let mut p = doc.add_paragraph("");
160 p.add_run("8pt small").size(8.0);
161 p.add_run(" | ");
162 p.add_run("11pt normal").size(11.0);
163 p.add_run(" | ");
164 p.add_run("16pt large").size(16.0);
165 p.add_run(" | ");
166 p.add_run("24pt extra-large").size(24.0);
167 }
168 {
169 let mut p = doc.add_paragraph("");
170 p.add_run("Arial font").font("Arial");
171 p.add_run(" | ");
172 p.add_run("Times New Roman font").font("Times New Roman");
173 p.add_run(" | ");
174 p.add_run("Courier New font").font("Courier New");
175 }
176 {
177 let mut p = doc.add_paragraph("");
178 p.add_run("Normal");
179 p.add_run(" H").size(11.0);
180 p.add_run("2").subscript();
181 p.add_run("O (subscript)").size(11.0);
182 p.add_run(" | E = mc").size(11.0);
183 p.add_run("2").superscript();
184 p.add_run(" (superscript)").size(11.0);
185 }
186 {
187 let mut p = doc.add_paragraph("");
188 p.add_run("ALL CAPS").all_caps(true);
189 p.add_run(" | ");
190 p.add_run("Small Caps").small_caps(true);
191 p.add_run(" | ");
192 p.add_run("Expanded spacing")
193 .character_spacing(Length::twips(40));
194 }
195
196 doc.add_paragraph("");
197
198 // --- Paragraph formatting ---
199 doc.add_paragraph("Paragraph Formatting").style("Heading2");
200
201 doc.add_paragraph("Paragraph with shading (light green background)")
202 .shading("E2EFDA");
203
204 doc.add_paragraph("Paragraph with bottom border")
205 .border_bottom(BorderStyle::Single, 6, "2E75B6");
206
207 doc.add_paragraph("Paragraph with all borders")
208 .border_all(BorderStyle::Single, 4, "FF0000");
209
210 doc.add_paragraph("Paragraph with 1-inch left indent and hanging indent")
211 .indent_left(Length::inches(1.0))
212 .hanging_indent(Length::inches(0.5));
213
214 doc.add_paragraph("Paragraph with first-line indent of 0.5 inches")
215 .first_line_indent(Length::inches(0.5));
216
217 doc.add_paragraph("Paragraph with extra space before (24pt) and after (12pt)")
218 .space_before(Length::pt(24.0))
219 .space_after(Length::pt(12.0));
220
221 doc.add_paragraph(
222 "Paragraph with double line spacing. This text should have extra vertical \
223 space between lines to demonstrate the line_spacing_multiple setting.",
224 )
225 .line_spacing_multiple(2.0);
226
227 doc.add_paragraph("Paragraph with keep-with-next (won't break from the next paragraph)")
228 .keep_with_next(true);
229 doc.add_paragraph("(This stays with the paragraph above.)");
230
231 // =========================================================================
232 // PAGE 3: LISTS & TAB STOPS
233 // =========================================================================
234 doc.add_paragraph("").page_break_before(true);
235
236 doc.add_paragraph("2. Lists").style("Heading1");
237
238 doc.add_paragraph("Bullet List").style("Heading2");
239
240 doc.add_bullet_list_item("First bullet item", 0);
241 doc.add_bullet_list_item("Second bullet item", 0);
242 doc.add_bullet_list_item("Nested level 1", 1);
243 doc.add_bullet_list_item("Nested level 2", 2);
244 doc.add_bullet_list_item("Back to level 1", 1);
245 doc.add_bullet_list_item("Third bullet item", 0);
246
247 doc.add_paragraph("");
248
249 doc.add_paragraph("Numbered List").style("Heading2");
250
251 doc.add_numbered_list_item("First numbered item", 0);
252 doc.add_numbered_list_item("Second numbered item", 0);
253 doc.add_numbered_list_item("Sub-item A", 1);
254 doc.add_numbered_list_item("Sub-item B", 1);
255 doc.add_numbered_list_item("Third numbered item", 0);
256
257 doc.add_paragraph("");
258
259 // --- Tab stops ---
260 doc.add_paragraph("Tab Stops").style("Heading2");
261
262 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
263 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
264 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
265 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
266 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
267
268 doc.add_paragraph("Item\t........\tPrice")
269 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
270 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
271 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
272
273 doc.add_paragraph("Widget A\t........\t$19.99")
274 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
275 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
276 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
277
278 doc.add_paragraph("Gadget B\t________\t$249.50")
279 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
280 .add_tab_stop_with_leader(
281 TabAlignment::Right,
282 Length::inches(4.0),
283 TabLeader::Underscore,
284 )
285 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
286
287 // =========================================================================
288 // PAGE 4: TABLES
289 // =========================================================================
290 doc.add_paragraph("").page_break_before(true);
291
292 doc.add_paragraph("3. Tables").style("Heading1");
293
294 // --- Basic table with borders ---
295 doc.add_paragraph("Basic Table with Borders")
296 .style("Heading2");
297
298 {
299 let mut tbl = doc.add_table(4, 3);
300 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
301
302 // Header row
303 for col in 0..3 {
304 tbl.cell(0, col).unwrap().shading("2E75B6");
305 }
306 tbl.cell(0, 0).unwrap().set_text("Name");
307 tbl.cell(0, 1).unwrap().set_text("Role");
308 tbl.cell(0, 2).unwrap().set_text("Location");
309
310 tbl.cell(1, 0).unwrap().set_text("Alice Johnson");
311 tbl.cell(1, 1).unwrap().set_text("Engineering Lead");
312 tbl.cell(1, 2).unwrap().set_text("New York");
313
314 tbl.cell(2, 0).unwrap().set_text("Bob Smith");
315 tbl.cell(2, 1).unwrap().set_text("Product Manager");
316 tbl.cell(2, 2).unwrap().set_text("San Francisco");
317
318 tbl.cell(3, 0).unwrap().set_text("Carol Davis");
319 tbl.cell(3, 1).unwrap().set_text("Designer");
320 tbl.cell(3, 2).unwrap().set_text("London");
321 }
322
323 doc.add_paragraph("");
324
325 // --- Table with cell merging ---
326 doc.add_paragraph("Table with Cell Merging & Vertical Alignment")
327 .style("Heading2");
328
329 {
330 let mut tbl = doc.add_table(4, 4);
331 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
332 tbl = tbl.width_pct(100.0);
333
334 // Header spanning all columns
335 tbl.cell(0, 0).unwrap().set_text("Quarterly Revenue Report");
336 tbl.cell(0, 0).unwrap().shading("1F4E79");
337 tbl.cell(0, 0).unwrap().grid_span(4);
338
339 // Sub-header
340 tbl.cell(1, 0).unwrap().set_text("Region");
341 tbl.cell(1, 0).unwrap().shading("D6E4F0");
342 tbl.cell(1, 1).unwrap().set_text("Q1");
343 tbl.cell(1, 1).unwrap().shading("D6E4F0");
344 tbl.cell(1, 2).unwrap().set_text("Q2");
345 tbl.cell(1, 2).unwrap().shading("D6E4F0");
346 tbl.cell(1, 3).unwrap().set_text("Total");
347 tbl.cell(1, 3).unwrap().shading("D6E4F0");
348
349 // Data
350 tbl.cell(2, 0).unwrap().set_text("North America");
351 tbl.cell(2, 1).unwrap().set_text("$2.4M");
352 tbl.cell(2, 2).unwrap().set_text("$2.7M");
353 tbl.cell(2, 3).unwrap().set_text("$5.1M");
354
355 tbl.cell(3, 0).unwrap().set_text("Europe");
356 tbl.cell(3, 1).unwrap().set_text("$1.8M");
357 tbl.cell(3, 2).unwrap().set_text("$2.0M");
358 tbl.cell(3, 3).unwrap().set_text("$3.8M");
359
360 // Vertical alignment on data cells
361 tbl.cell(2, 3)
362 .unwrap()
363 .vertical_alignment(VerticalAlignment::Center);
364 tbl.cell(3, 3)
365 .unwrap()
366 .vertical_alignment(VerticalAlignment::Bottom);
367 }
368
369 doc.add_paragraph("");
370
371 // --- Table with vertical merge ---
372 doc.add_paragraph("Table with Vertical Merge")
373 .style("Heading2");
374
375 {
376 let mut tbl = doc.add_table(4, 3);
377 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
378
379 tbl.cell(0, 0).unwrap().set_text("Category");
380 tbl.cell(0, 0).unwrap().shading("E2EFDA");
381 tbl.cell(0, 1).unwrap().set_text("Item");
382 tbl.cell(0, 1).unwrap().shading("E2EFDA");
383 tbl.cell(0, 2).unwrap().set_text("Price");
384 tbl.cell(0, 2).unwrap().shading("E2EFDA");
385
386 // "Hardware" spans rows 1-2
387 tbl.cell(1, 0).unwrap().set_text("Hardware");
388 tbl.cell(1, 0).unwrap().v_merge_restart();
389 tbl.cell(1, 1).unwrap().set_text("Laptop");
390 tbl.cell(1, 2).unwrap().set_text("$1,200");
391
392 tbl.cell(2, 0).unwrap().v_merge_continue();
393 tbl.cell(2, 1).unwrap().set_text("Monitor");
394 tbl.cell(2, 2).unwrap().set_text("$450");
395
396 // "Software" on row 3
397 tbl.cell(3, 0).unwrap().set_text("Software");
398 tbl.cell(3, 1).unwrap().set_text("IDE License");
399 tbl.cell(3, 2).unwrap().set_text("$200/yr");
400 }
401
402 doc.add_paragraph("");
403
404 // --- Nested table ---
405 doc.add_paragraph("Nested Table").style("Heading2");
406
407 {
408 let mut tbl = doc.add_table(2, 2);
409 tbl = tbl.borders(BorderStyle::Single, 6, "2E75B6");
410
411 tbl.cell(0, 0).unwrap().set_text("Outer Cell (0,0)");
412 tbl.cell(0, 1).unwrap().set_text("Outer Cell (0,1)");
413 tbl.cell(1, 0).unwrap().set_text("Outer Cell (1,0)");
414
415 // Nested table inside cell (1,1)
416 {
417 let mut cell = tbl.cell(1, 1).unwrap();
418 cell.set_text("Contains nested table:");
419 let mut nested = cell.add_table(2, 2);
420 nested = nested.borders(BorderStyle::Single, 2, "FF6600");
421 nested.cell(0, 0).unwrap().set_text("Inner A");
422 nested.cell(0, 1).unwrap().set_text("Inner B");
423 nested.cell(1, 0).unwrap().set_text("Inner C");
424 nested.cell(1, 1).unwrap().set_text("Inner D");
425 }
426 }
427
428 // =========================================================================
429 // PAGE 5: IMAGES
430 // =========================================================================
431 doc.add_paragraph("").page_break_before(true);
432
433 doc.add_paragraph("4. Images").style("Heading1");
434
435 doc.add_paragraph("Inline Image").style("Heading2");
436
437 doc.add_paragraph("Below is an inline image (200x50 pixels, blue gradient):");
438 let inline_img = create_sample_png(200, 50, [0, 80, 200]);
439 doc.add_picture(
440 &inline_img,
441 "inline_chart.png",
442 Length::inches(3.0),
443 Length::inches(0.75),
444 );
445
446 doc.add_paragraph("");
447
448 doc.add_paragraph("Header Image").style("Heading2");
449
450 // Replace the text-only header with an image header
451 let header_img = create_sample_png(400, 40, [40, 40, 40]);
452 doc.set_header_image(
453 &header_img,
454 "header_logo.png",
455 Length::inches(2.0),
456 Length::inches(0.2),
457 );
458
459 doc.add_paragraph(
460 "The document header has been replaced with an inline image. \
461 Check the header area at the top of this page.",
462 );
463
464 doc.add_paragraph("");
465 doc.add_paragraph(
466 "Note: The cover page uses a full-page background image behind the text, \
467 demonstrated on page 1 via add_background_image().",
468 );
469
470 // =========================================================================
471 // PAGE 6: CONTENT MANIPULATION — placeholder replacement, insertion
472 // =========================================================================
473 doc.add_paragraph("").page_break_before(true);
474
475 doc.add_paragraph("5. Content Manipulation")
476 .style("Heading1");
477
478 // --- Placeholder replacement ---
479 doc.add_paragraph("Placeholder Replacement")
480 .style("Heading2");
481
482 doc.add_paragraph(
483 "Before replacement, this document contained {{customer}} and {{date}} placeholders.",
484 );
485
486 {
487 let mut p = doc.add_paragraph("");
488 p.add_run("Customer: ").bold(true);
489 p.add_run("{{customer}}");
490 }
491 {
492 let mut p = doc.add_paragraph("");
493 p.add_run("Date: ").bold(true);
494 p.add_run("{{date}}");
495 }
496 doc.add_paragraph("Reference: {{ref_number}}");
497
498 // Table with placeholders
499 {
500 let mut tbl = doc.add_table(3, 2);
501 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
502 tbl.cell(0, 0).unwrap().set_text("Field");
503 tbl.cell(0, 0).unwrap().shading("D6E4F0");
504 tbl.cell(0, 1).unwrap().set_text("Value");
505 tbl.cell(0, 1).unwrap().shading("D6E4F0");
506 tbl.cell(1, 0).unwrap().set_text("Project");
507 tbl.cell(1, 1).unwrap().set_text("{{project}}");
508 tbl.cell(2, 0).unwrap().set_text("Status");
509 tbl.cell(2, 1).unwrap().set_text("{{status}}");
510 }
511
512 // Perform replacements
513 let mut replacements = HashMap::new();
514 replacements.insert("{{customer}}", "Acme Corporation");
515 replacements.insert("{{date}}", "February 22, 2026");
516 replacements.insert("{{ref_number}}", "REF-2026-001");
517 replacements.insert("{{project}}", "Infrastructure Upgrade");
518 replacements.insert("{{status}}", "In Progress");
519 let replace_count = doc.replace_all(&replacements);
520
521 doc.add_paragraph("");
522 doc.add_paragraph(&format!(
523 "(Replaced {} placeholders above — in body text and table cells)",
524 replace_count
525 ));
526
527 doc.add_paragraph("");
528
529 // --- Content insertion ---
530 doc.add_paragraph("Content Insertion").style("Heading2");
531
532 doc.add_paragraph("Section A: First section of content.");
533 doc.add_paragraph("Section C: Third section of content.");
534
535 // Insert "Section B" between A and C
536 if let Some(idx) = doc.find_content_index("Section C") {
537 doc.insert_paragraph(
538 idx,
539 "Section B: Inserted between A and C using find_content_index().",
540 );
541 }
542
543 doc.add_paragraph("");
544 doc.add_paragraph(
545 "The paragraph above ('Section B') was inserted at a specific position \
546 using find_content_index() + insert_paragraph().",
547 );
548
549 // =========================================================================
550 // PAGE 7: LANDSCAPE — section break, wide table
551 // =========================================================================
552 doc.add_paragraph("").section_break(SectionBreak::NextPage);
553
554 doc.add_paragraph("6. Mixed Page Orientation")
555 .style("Heading1");
556
557 doc.add_paragraph(
558 "This page is in LANDSCAPE orientation. It was created using a section break \
559 followed by section_landscape(). This is useful for wide tables or charts.",
560 );
561
562 doc.add_paragraph("");
563
564 // Wide table for landscape
565 {
566 let mut tbl = doc.add_table(4, 7);
567 tbl = tbl.borders(BorderStyle::Single, 4, "2E75B6");
568
569 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
570 for (col, h) in headers.iter().enumerate() {
571 tbl.cell(0, col).unwrap().set_text(h);
572 tbl.cell(0, col).unwrap().shading("2E75B6");
573 }
574
575 let data = [
576 [
577 "North America",
578 "$1.2M",
579 "$1.3M",
580 "$1.4M",
581 "$1.5M",
582 "$1.6M",
583 "$7.0M",
584 ],
585 [
586 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
587 ],
588 [
589 "Asia Pacific",
590 "$0.5M",
591 "$0.6M",
592 "$0.7M",
593 "$0.7M",
594 "$0.8M",
595 "$3.3M",
596 ],
597 ];
598 for (row_idx, row_data) in data.iter().enumerate() {
599 for (col, val) in row_data.iter().enumerate() {
600 tbl.cell(row_idx + 1, col).unwrap().set_text(val);
601 }
602 }
603 }
604
605 // End landscape, return to portrait
606 doc.add_paragraph("")
607 .section_break(SectionBreak::NextPage)
608 .section_landscape();
609
610 // =========================================================================
611 // PAGE 8: BACK TO PORTRAIT — styles, final notes
612 // =========================================================================
613 doc.add_paragraph("7. Custom Styles & Summary")
614 .style("Heading1");
615
616 doc.add_paragraph(
617 "This final page is back in portrait orientation after a section break. \
618 The document has demonstrated:",
619 );
620
621 doc.add_bullet_list_item(
622 "Page setup: size, margins, header/footer distance, gutter",
623 0,
624 );
625 doc.add_bullet_list_item("Document metadata: title, author, subject, keywords", 0);
626 doc.add_bullet_list_item("Headers and footers: text, images, different first page", 0);
627 doc.add_bullet_list_item("Background images: full-page behind text", 0);
628 doc.add_bullet_list_item(
629 "Text formatting: bold, italic, underline, strike, color, size, font",
630 0,
631 );
632 doc.add_bullet_list_item(
633 "Advanced run formatting: superscript, subscript, caps, spacing",
634 0,
635 );
636 doc.add_bullet_list_item(
637 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
638 0,
639 );
640 doc.add_bullet_list_item("Bullet and numbered lists with nesting levels", 0);
641 doc.add_bullet_list_item("Tab stops with dot/underscore leaders", 0);
642 doc.add_bullet_list_item(
643 "Tables: borders, shading, column spans, row spans, nesting",
644 0,
645 );
646 doc.add_bullet_list_item("Vertical alignment in table cells", 0);
647 doc.add_bullet_list_item("Inline images", 0);
648 doc.add_bullet_list_item("Placeholder replacement in body and table cells", 0);
649 doc.add_bullet_list_item("Content insertion at specific positions", 0);
650 doc.add_bullet_list_item(
651 "Section breaks with mixed portrait/landscape orientation",
652 0,
653 );
654
655 doc.add_paragraph("");
656 doc.add_paragraph("All features above were built entirely from scratch using the rdocx API.")
657 .alignment(Alignment::Center)
658 .shading("E2EFDA")
659 .border_all(BorderStyle::Single, 2, "00AA00");
660
661 doc.save(path).unwrap();
662}86fn generate_feature_showcase(_samples_dir: &Path) -> Document {
87 let mut doc = Document::new();
88
89 // ── Page Setup & Metadata ──
90 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
91 doc.set_margins(
92 Length::inches(1.0),
93 Length::inches(1.0),
94 Length::inches(1.0),
95 Length::inches(1.0),
96 );
97 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
98 doc.set_gutter(Length::twips(0));
99 doc.set_title("rdocx Feature Showcase");
100 doc.set_author("rdocx Sample Generator");
101 doc.set_subject("Comprehensive feature demonstration");
102 doc.set_keywords("rdocx, docx, rust, sample, showcase");
103
104 // Headers & Footers with different first page
105 doc.set_different_first_page(true);
106 doc.set_first_page_header("rdocx");
107 doc.set_first_page_footer("Feature Showcase — Cover Page");
108 doc.set_header("rdocx Feature Showcase");
109 doc.set_footer("Generated by rdocx");
110
111 // ── COVER PAGE ──
112 let bg = create_sample_png(612, 792, [20, 45, 90]);
113 doc.add_background_image(&bg, "cover_bg.png");
114
115 for _ in 0..3 {
116 doc.add_paragraph("");
117 }
118 {
119 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
120 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
121 }
122 {
123 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
124 p.add_run("Complete Feature Showcase")
125 .size(28.0)
126 .color("FFFFFF")
127 .italic(true);
128 }
129 doc.add_paragraph("");
130 {
131 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
132 p.add_run("Every feature of the rdocx Rust library")
133 .size(13.0)
134 .color("B0C4DE");
135 }
136 {
137 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
138 p.add_run("demonstrated in a single document.")
139 .size(13.0)
140 .color("B0C4DE");
141 }
142
143 // ── TABLE OF CONTENTS ──
144 doc.add_paragraph("").page_break_before(true);
145 doc.insert_toc(doc.content_count(), 3);
146
147 // ── SECTION 1: TEXT FORMATTING ──
148 doc.add_paragraph("").page_break_before(true);
149 doc.add_paragraph("1. Text Formatting").style("Heading1");
150
151 // Paragraph Alignment
152 doc.add_paragraph("1.1 Paragraph Alignment")
153 .style("Heading2");
154 doc.add_paragraph("Left-aligned paragraph (default).")
155 .alignment(Alignment::Left);
156 doc.add_paragraph("Center-aligned paragraph.")
157 .alignment(Alignment::Center);
158 doc.add_paragraph("Right-aligned paragraph.")
159 .alignment(Alignment::Right);
160 doc.add_paragraph(
161 "Justified paragraph — this text is long enough to demonstrate how justified alignment \
162 distributes extra space across word gaps so lines fill the full width of the text area.",
163 )
164 .alignment(Alignment::Justify);
165
166 doc.add_paragraph("");
167
168 // Run Formatting
169 doc.add_paragraph("1.2 Run Formatting").style("Heading2");
170 {
171 let mut p = doc.add_paragraph("");
172 p.add_run("Bold").bold(true);
173 p.add_run(" | ");
174 p.add_run("Italic").italic(true);
175 p.add_run(" | ");
176 p.add_run("Bold Italic").bold(true).italic(true);
177 p.add_run(" | ");
178 p.add_run("Underline").underline(true);
179 p.add_run(" | ");
180 p.add_run("Strikethrough").strike(true);
181 p.add_run(" | ");
182 p.add_run("Double Strike").double_strike(true);
183 }
184 {
185 let mut p = doc.add_paragraph("");
186 p.add_run("Red").color("FF0000");
187 p.add_run(" | ");
188 p.add_run("Blue").color("0000FF");
189 p.add_run(" | ");
190 p.add_run("Green").color("00AA00");
191 p.add_run(" | ");
192 p.add_run("Highlighted").highlight("FFFF00");
193 }
194 {
195 let mut p = doc.add_paragraph("");
196 p.add_run("8pt").size(8.0);
197 p.add_run(" | ");
198 p.add_run("11pt").size(11.0);
199 p.add_run(" | ");
200 p.add_run("16pt").size(16.0);
201 p.add_run(" | ");
202 p.add_run("24pt").size(24.0);
203 }
204 {
205 let mut p = doc.add_paragraph("");
206 p.add_run("Arial").font("Arial");
207 p.add_run(" | ");
208 p.add_run("Times New Roman").font("Times New Roman");
209 p.add_run(" | ");
210 p.add_run("Courier New").font("Courier New");
211 }
212 {
213 let mut p = doc.add_paragraph("");
214 p.add_run("H");
215 p.add_run("2").subscript();
216 p.add_run("O (subscript) | E=mc");
217 p.add_run("2").superscript();
218 p.add_run(" (superscript)");
219 }
220 {
221 let mut p = doc.add_paragraph("");
222 p.add_run("ALL CAPS").all_caps(true);
223 p.add_run(" | ");
224 p.add_run("Small Caps").small_caps(true);
225 p.add_run(" | ");
226 p.add_run("Expanded").character_spacing(Length::twips(40));
227 p.add_run(" | ");
228 p.add_run("Hidden text (not visible)").hidden(true);
229 }
230
231 doc.add_paragraph("");
232
233 // Underline Styles
234 doc.add_paragraph("1.3 Underline Styles").style("Heading2");
235 {
236 let mut p = doc.add_paragraph("");
237 p.add_run("Single ")
238 .underline_style(rdocx::UnderlineStyle::Single);
239 p.add_run("Double ")
240 .underline_style(rdocx::UnderlineStyle::Double);
241 p.add_run("Thick ")
242 .underline_style(rdocx::UnderlineStyle::Thick);
243 p.add_run("Dotted ")
244 .underline_style(rdocx::UnderlineStyle::Dotted);
245 p.add_run("Dash ")
246 .underline_style(rdocx::UnderlineStyle::Dash);
247 p.add_run("Wave ")
248 .underline_style(rdocx::UnderlineStyle::Wave);
249 }
250
251 // ── SECTION 2: PARAGRAPH FORMATTING ──
252 doc.add_paragraph("").page_break_before(true);
253 doc.add_paragraph("2. Paragraph Formatting")
254 .style("Heading1");
255
256 doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
257 doc.add_paragraph("Paragraph with light green shading")
258 .shading("E2EFDA");
259 doc.add_paragraph("Paragraph with bottom border")
260 .border_bottom(BorderStyle::Single, 6, "2E75B6");
261 doc.add_paragraph("Paragraph with all borders (red)")
262 .border_all(BorderStyle::Single, 4, "FF0000");
263
264 doc.add_paragraph("2.2 Indentation").style("Heading2");
265 doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
266 .indent_left(Length::inches(1.0))
267 .hanging_indent(Length::inches(0.5));
268 doc.add_paragraph("0.5-inch first-line indent on this paragraph")
269 .first_line_indent(Length::inches(0.5));
270 doc.add_paragraph("Both left and right indent (0.75 inches each)")
271 .indent_left(Length::inches(0.75))
272 .indent_right(Length::inches(0.75));
273
274 doc.add_paragraph("2.3 Spacing & Line Height")
275 .style("Heading2");
276 doc.add_paragraph("Extra space before (24pt) and after (12pt)")
277 .space_before(Length::pt(24.0))
278 .space_after(Length::pt(12.0));
279 doc.add_paragraph(
280 "Double line spacing paragraph. This text should have extra vertical space between \
281 lines to demonstrate line_spacing_multiple(2.0).",
282 )
283 .line_spacing_multiple(2.0);
284 doc.add_paragraph("Exact 20pt line spacing (fixed height).")
285 .line_spacing(20.0);
286
287 doc.add_paragraph("2.4 Pagination Controls")
288 .style("Heading2");
289 doc.add_paragraph("keep_with_next — this paragraph stays with the next")
290 .keep_with_next(true);
291 doc.add_paragraph("(This paragraph was kept with the one above.)");
292 doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
293 .keep_together(true);
294 doc.add_paragraph("widow_control — prevents widow/orphan lines")
295 .widow_control(true);
296
297 // ── SECTION 3: LISTS ──
298 doc.add_paragraph("").page_break_before(true);
299 doc.add_paragraph("3. Lists").style("Heading1");
300
301 doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
302 doc.add_bullet_list_item("First item", 0);
303 doc.add_bullet_list_item("Second item", 0);
304 doc.add_bullet_list_item("Nested level 1", 1);
305 doc.add_bullet_list_item("Nested level 2", 2);
306 doc.add_bullet_list_item("Back to level 1", 1);
307 doc.add_bullet_list_item("Third item", 0);
308
309 doc.add_paragraph("");
310
311 doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
312 doc.add_numbered_list_item("First numbered item", 0);
313 doc.add_numbered_list_item("Second numbered item", 0);
314 doc.add_numbered_list_item("Sub-item A", 1);
315 doc.add_numbered_list_item("Sub-item B", 1);
316 doc.add_numbered_list_item("Third numbered item", 0);
317
318 // ── SECTION 4: TAB STOPS ──
319 doc.add_paragraph("");
320 doc.add_paragraph("4. Tab Stops").style("Heading1");
321
322 doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
323 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
324 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
325 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
326 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
327 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
328
329 doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
330 doc.add_paragraph("Item\t\tPrice")
331 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
332 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
333 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
334 doc.add_paragraph("Widget\t\t$19.99")
335 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
336 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
337 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
338 doc.add_paragraph("Gadget\t\t$249.50")
339 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
340 .add_tab_stop_with_leader(
341 TabAlignment::Right,
342 Length::inches(4.0),
343 TabLeader::Underscore,
344 )
345 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
346
347 // ── SECTION 5: TABLES ──
348 doc.add_paragraph("").page_break_before(true);
349 doc.add_paragraph("5. Tables").style("Heading1");
350
351 doc.add_paragraph("5.1 Basic Table").style("Heading2");
352 {
353 let mut tbl = doc
354 .add_table(4, 3)
355 .borders(BorderStyle::Single, 4, "000000");
356 for col in 0..3 {
357 tbl.cell(0, col).unwrap().shading("2E75B6");
358 }
359 tbl.cell(0, 0).unwrap().set_text("Name");
360 tbl.cell(0, 1).unwrap().set_text("Role");
361 tbl.cell(0, 2).unwrap().set_text("Location");
362 tbl.cell(1, 0).unwrap().set_text("Walter White");
363 tbl.cell(1, 1).unwrap().set_text("CEO");
364 tbl.cell(1, 2).unwrap().set_text("Albuquerque");
365 tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
366 tbl.cell(2, 1).unwrap().set_text("CTO");
367 tbl.cell(2, 2).unwrap().set_text("Remote");
368 tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
369 tbl.cell(3, 1).unwrap().set_text("Security");
370 tbl.cell(3, 2).unwrap().set_text("Washington");
371 }
372
373 doc.add_paragraph("");
374
375 doc.add_paragraph("5.2 Column Span & Cell Shading")
376 .style("Heading2");
377 {
378 let mut tbl = doc
379 .add_table(3, 4)
380 .borders(BorderStyle::Single, 4, "000000")
381 .width_pct(100.0);
382 tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
383 tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
384 tbl.cell(1, 0).unwrap().set_text("Region");
385 tbl.cell(1, 0).unwrap().shading("D6E4F0");
386 tbl.cell(1, 1).unwrap().set_text("Q1");
387 tbl.cell(1, 1).unwrap().shading("D6E4F0");
388 tbl.cell(1, 2).unwrap().set_text("Q2");
389 tbl.cell(1, 2).unwrap().shading("D6E4F0");
390 tbl.cell(1, 3).unwrap().set_text("Total");
391 tbl.cell(1, 3).unwrap().shading("D6E4F0");
392 tbl.cell(2, 0).unwrap().set_text("Americas");
393 tbl.cell(2, 1).unwrap().set_text("$2.4M");
394 tbl.cell(2, 2).unwrap().set_text("$2.7M");
395 tbl.cell(2, 3).unwrap().set_text("$5.1M");
396 }
397
398 doc.add_paragraph("");
399
400 doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
401 {
402 let mut tbl = doc
403 .add_table(4, 3)
404 .borders(BorderStyle::Single, 4, "000000");
405 tbl.cell(0, 0).unwrap().set_text("Category");
406 tbl.cell(0, 0).unwrap().shading("E2EFDA");
407 tbl.cell(0, 1).unwrap().set_text("Item");
408 tbl.cell(0, 1).unwrap().shading("E2EFDA");
409 tbl.cell(0, 2).unwrap().set_text("Price");
410 tbl.cell(0, 2).unwrap().shading("E2EFDA");
411 tbl.cell(1, 0).unwrap().set_text("Hardware");
412 tbl.cell(1, 0).unwrap().v_merge_restart();
413 tbl.cell(1, 1).unwrap().set_text("Laptop");
414 tbl.cell(1, 2).unwrap().set_text("$1,200");
415 tbl.cell(2, 0).unwrap().v_merge_continue();
416 tbl.cell(2, 1).unwrap().set_text("Monitor");
417 tbl.cell(2, 2).unwrap().set_text("$450");
418 tbl.cell(3, 0).unwrap().set_text("Software");
419 tbl.cell(3, 1).unwrap().set_text("IDE License");
420 tbl.cell(3, 2).unwrap().set_text("$200/yr");
421 }
422
423 doc.add_paragraph("");
424
425 doc.add_paragraph("5.4 Nested Table").style("Heading2");
426 {
427 let mut tbl = doc
428 .add_table(2, 2)
429 .borders(BorderStyle::Single, 6, "2E75B6");
430 tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
431 tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
432 tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
433 {
434 let mut cell = tbl.cell(1, 1).unwrap();
435 cell.set_text("Nested table below:");
436 let mut nested = cell
437 .add_table(2, 2)
438 .borders(BorderStyle::Single, 2, "FF6600");
439 nested.cell(0, 0).unwrap().set_text("A");
440 nested.cell(0, 1).unwrap().set_text("B");
441 nested.cell(1, 0).unwrap().set_text("C");
442 nested.cell(1, 1).unwrap().set_text("D");
443 }
444 }
445
446 doc.add_paragraph("");
447
448 doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
449 .style("Heading2");
450 {
451 let mut tbl = doc
452 .add_table(2, 3)
453 .borders(BorderStyle::Single, 4, "666666");
454 tbl.row(0).unwrap().height(Length::pt(50.0)).header();
455 tbl.cell(0, 0).unwrap().set_text("Top");
456 tbl.cell(0, 0)
457 .unwrap()
458 .vertical_alignment(VerticalAlignment::Top)
459 .shading("FFF2CC");
460 tbl.cell(0, 1).unwrap().set_text("Center");
461 tbl.cell(0, 1)
462 .unwrap()
463 .vertical_alignment(VerticalAlignment::Center)
464 .shading("D9E2F3");
465 tbl.cell(0, 2).unwrap().set_text("Bottom");
466 tbl.cell(0, 2)
467 .unwrap()
468 .vertical_alignment(VerticalAlignment::Bottom)
469 .shading("E2EFDA");
470 tbl.row(1).unwrap().cant_split();
471 tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
472 tbl.cell(1, 0).unwrap().no_wrap();
473 tbl.cell(1, 1).unwrap().set_text("Fixed width");
474 tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
475 tbl.cell(1, 2).unwrap().set_text("Normal");
476 }
477
478 // ── SECTION 6: IMAGES ──
479 doc.add_paragraph("").page_break_before(true);
480 doc.add_paragraph("6. Images").style("Heading1");
481
482 doc.add_paragraph("6.1 Inline Image").style("Heading2");
483 doc.add_paragraph("A blue gradient image below:");
484 let img = create_sample_png(200, 50, [0, 80, 200]);
485 doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
486
487 doc.add_paragraph("");
488 doc.add_paragraph("6.2 Header Image").style("Heading2");
489 let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
490 doc.set_header_image(
491 &hdr_img,
492 "header_logo.png",
493 Length::inches(2.0),
494 Length::inches(0.2),
495 );
496 doc.add_paragraph("The header now contains an inline image (check the top of this page).");
497
498 doc.add_paragraph("");
499 doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
500
501 // ── SECTION 7: CONTENT MANIPULATION ──
502 doc.add_paragraph("").page_break_before(true);
503 doc.add_paragraph("7. Content Manipulation")
504 .style("Heading1");
505
506 doc.add_paragraph("7.1 Placeholder Replacement")
507 .style("Heading2");
508 doc.add_paragraph("Customer: {{customer}}");
509 doc.add_paragraph("Date: {{date}}");
510 doc.add_paragraph("Reference: {{ref_number}}");
511 {
512 let mut tbl = doc
513 .add_table(2, 2)
514 .borders(BorderStyle::Single, 4, "000000");
515 tbl.cell(0, 0).unwrap().set_text("Project");
516 tbl.cell(0, 0).unwrap().shading("D6E4F0");
517 tbl.cell(0, 1).unwrap().set_text("{{project}}");
518 tbl.cell(1, 0).unwrap().set_text("Status");
519 tbl.cell(1, 0).unwrap().shading("D6E4F0");
520 tbl.cell(1, 1).unwrap().set_text("{{status}}");
521 }
522
523 let mut replacements = HashMap::new();
524 replacements.insert("{{customer}}", "Tensorbee Inc.");
525 replacements.insert("{{date}}", "February 22, 2026");
526 replacements.insert("{{ref_number}}", "TB-2026-001");
527 replacements.insert("{{project}}", "Infrastructure Upgrade");
528 replacements.insert("{{status}}", "In Progress");
529 let n = doc.replace_all(&replacements);
530 doc.add_paragraph(&format!("({n} placeholders replaced above)"));
531
532 doc.add_paragraph("");
533
534 doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
535 doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
536 let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
537 doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
538
539 doc.add_paragraph("");
540
541 doc.add_paragraph("7.3 Content Insertion").style("Heading2");
542 doc.add_paragraph("Section A: First content.");
543 doc.add_paragraph("Section C: Third content.");
544 if let Some(idx) = doc.find_content_index("Section C") {
545 doc.insert_paragraph(
546 idx,
547 "Section B: Inserted between A and C via find_content_index().",
548 );
549 }
550
551 // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
552 doc.add_paragraph("").section_break(SectionBreak::NextPage);
553 doc.add_paragraph("8. Section Breaks & Orientation")
554 .style("Heading1");
555 doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
556
557 {
558 let mut tbl = doc
559 .add_table(3, 7)
560 .borders(BorderStyle::Single, 4, "2E75B6");
561 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
562 for (c, h) in headers.iter().enumerate() {
563 tbl.cell(0, c).unwrap().set_text(h);
564 tbl.cell(0, c).unwrap().shading("2E75B6");
565 }
566 let data = [
567 [
568 "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
569 ],
570 [
571 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
572 ],
573 ];
574 for (r, row) in data.iter().enumerate() {
575 for (c, v) in row.iter().enumerate() {
576 tbl.cell(r + 1, c).unwrap().set_text(v);
577 }
578 }
579 }
580
581 doc.add_paragraph("")
582 .section_break(SectionBreak::NextPage)
583 .section_landscape();
584
585 // ── SECTION 9: CUSTOM STYLES ──
586 doc.add_paragraph("9. Custom Styles").style("Heading1");
587 doc.add_style(
588 StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
589 .based_on("Normal")
590 .paragraph_properties({
591 let mut ppr = rdocx_oxml::properties::CT_PPr::default();
592 ppr.shading = Some(rdocx_oxml::properties::CT_Shd {
593 val: "clear".to_string(),
594 color: None,
595 fill: Some("FFF2CC".to_string()),
596 });
597 ppr
598 })
599 .run_properties({
600 let mut rpr = rdocx_oxml::properties::CT_RPr::default();
601 rpr.bold = Some(true);
602 rpr.color = Some("C45911".to_string());
603 rpr
604 }),
605 );
606 doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
607 .style("CustomHighlight");
608
609 doc.add_paragraph("");
610
611 // ── SECTION 10: DOCUMENT INTELLIGENCE ──
612 doc.add_paragraph("10. Document Intelligence API")
613 .style("Heading1");
614
615 let wc = doc.word_count();
616 let hc = doc.headings().len();
617 let ic = doc.images().len();
618 let lc = doc.links().len();
619 doc.add_paragraph(&format!("Word count: {wc}"));
620 doc.add_paragraph(&format!("Heading count: {hc}"));
621 doc.add_paragraph(&format!("Image count: {ic}"));
622 doc.add_paragraph(&format!("Link count: {lc}"));
623
624 let outline = doc.document_outline();
625 doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
626
627 let issues = doc.audit_accessibility();
628 doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
629 for issue in &issues {
630 doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
631 }
632
633 // ── SECTION 11: DOCUMENT MERGING ──
634 doc.add_paragraph("");
635 doc.add_paragraph("11. Document Merging").style("Heading1");
636 {
637 let mut other = Document::new();
638 other.add_paragraph("This paragraph was merged from another document using append().");
639 doc.append(&other);
640 }
641
642 doc.add_paragraph("");
643
644 // ── SUMMARY ──
645 doc.add_paragraph("Summary of Demonstrated Features")
646 .style("Heading1");
647 let features = [
648 "Page setup: size, margins, header/footer distance, gutter",
649 "Document metadata: title, author, subject, keywords",
650 "Headers and footers: text, images, different first page",
651 "Background images (full-page behind text)",
652 "Table of Contents generation with bookmarks",
653 "Text formatting: bold, italic, underline styles, strike, color, size, font",
654 "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
655 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
656 "Pagination controls: keep-with-next, keep-together, widow control",
657 "Bullet and numbered lists with nesting",
658 "Tab stops with dot/underscore/hyphen leaders",
659 "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
660 "Row properties: header rows, exact height, cant-split",
661 "Cell properties: width, no-wrap, vertical alignment",
662 "Inline images and header images",
663 "Placeholder replacement (single and batch)",
664 "Regex-based find and replace",
665 "Content insertion at specific positions",
666 "Section breaks with mixed portrait/landscape",
667 "Custom paragraph and character styles",
668 "Document intelligence: word count, headings, outline, images, links",
669 "Accessibility audit",
670 "Document merging (append)",
671 "Export: DOCX, PDF, HTML, Markdown",
672 ];
673 for f in &features {
674 doc.add_bullet_list_item(f, 0);
675 }
676 doc.add_paragraph("");
677 doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
678 .alignment(Alignment::Center)
679 .shading("E2EFDA")
680 .border_all(BorderStyle::Single, 2, "00AA00");
681
682 doc
683}
684
685// =============================================================================
686// 2. PROPOSAL DOCUMENT — Deep navy + gold scheme
687// =============================================================================
688fn generate_proposal(_samples_dir: &Path) -> Document {
689 let mut doc = Document::new();
690
691 // Colors: Navy #1B2A4A, Gold #C5922E, Light #F4F1EB
692 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
693 doc.set_margins(
694 Length::inches(1.0),
695 Length::inches(1.0),
696 Length::inches(1.0),
697 Length::inches(1.0),
698 );
699 doc.set_title("AI Platform Modernization Proposal");
700 doc.set_author("Walter White");
701 doc.set_subject("Technology Proposal");
702 doc.set_keywords("proposal, AI, modernization, Tensorbee");
703
704 // Banner header
705 let logo_img = create_logo_png(220, 48);
706 let banner = build_header_banner_xml(
707 "rId1",
708 &BannerOpts {
709 bg_color: "1B2A4A",
710 banner_width: 7772400,
711 banner_height: 914400,
712 logo_width: 2011680,
713 logo_height: 438912,
714 logo_x_offset: 295125,
715 logo_y_offset: 237744,
716 },
717 );
718 doc.set_raw_header_with_images(
719 banner,
720 &[("rId1", &logo_img, "logo.png")],
721 rdocx_oxml::header_footer::HdrFtrType::Default,
722 );
723 doc.set_different_first_page(true);
724 let cover_banner = build_header_banner_xml(
725 "rId1",
726 &BannerOpts {
727 bg_color: "C5922E",
728 banner_width: 7772400,
729 banner_height: 914400,
730 logo_width: 2011680,
731 logo_height: 438912,
732 logo_x_offset: 295125,
733 logo_y_offset: 237744,
734 },
735 );
736 doc.set_raw_header_with_images(
737 cover_banner,
738 &[("rId1", &logo_img, "logo.png")],
739 rdocx_oxml::header_footer::HdrFtrType::First,
740 );
741 doc.set_margins(
742 Length::twips(2292),
743 Length::twips(1440),
744 Length::twips(1440),
745 Length::twips(1440),
746 );
747 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
748 doc.set_footer("Tensorbee — Confidential");
749
750 // Custom styles
751 doc.add_style(
752 StyleBuilder::paragraph("ProposalTitle", "Proposal Title")
753 .based_on("Normal")
754 .run_properties({
755 let mut rpr = rdocx_oxml::properties::CT_RPr::default();
756 rpr.bold = Some(true);
757 rpr.font_ascii = Some("Georgia".to_string());
758 rpr.font_hansi = Some("Georgia".to_string());
759 rpr.sz = Some(rdocx_oxml::HalfPoint(56)); // half-points → 28pt
760 rpr.color = Some("1B2A4A".to_string());
761 rpr
762 }),
763 );
764
765 // ── Cover Page ──
766 for _ in 0..4 {
767 doc.add_paragraph("");
768 }
769 doc.add_paragraph("AI Platform Modernization")
770 .style("ProposalTitle")
771 .alignment(Alignment::Center);
772 doc.add_paragraph("")
773 .alignment(Alignment::Center)
774 .border_bottom(BorderStyle::Single, 8, "C5922E");
775 {
776 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
777 p.add_run("Prepared for Global Financial Corp.")
778 .size(14.0)
779 .color("666666")
780 .italic(true);
781 }
782 {
783 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
784 p.add_run("Prepared by: Walter White, CEO — Tensorbee")
785 .size(12.0)
786 .color("888888");
787 }
788 {
789 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
790 p.add_run("February 22, 2026").size(12.0).color("888888");
791 }
792
793 // ── TOC ──
794 doc.add_paragraph("").page_break_before(true);
795 doc.add_paragraph("Table of Contents").style("Heading1");
796 // We'll add a manual-style TOC since the insert_toc goes at a specific position
797 doc.insert_toc(doc.content_count(), 2);
798
799 // ── Executive Summary ──
800 doc.add_paragraph("").page_break_before(true);
801 doc.add_paragraph("Executive Summary").style("Heading1");
802 doc.add_paragraph(
803 "Tensorbee proposes a comprehensive modernization of Global Financial Corp's AI platform, \
804 transitioning from legacy batch-processing systems to a real-time inference architecture. \
805 This transformation will reduce model deployment time from weeks to hours, improve \
806 prediction accuracy by an estimated 23%, and deliver $4.2M in annual operational savings.",
807 )
808 .first_line_indent(Length::inches(0.3));
809 doc.add_paragraph(
810 "The project spans three phases over 18 months, with a total investment of $2.8M. \
811 Tensorbee brings deep expertise in ML infrastructure, having successfully completed \
812 similar transformations for three Fortune 500 financial institutions.",
813 )
814 .first_line_indent(Length::inches(0.3));
815
816 // Key metrics table
817 doc.add_paragraph("");
818 {
819 let mut tbl = doc
820 .add_table(5, 2)
821 .borders(BorderStyle::Single, 4, "1B2A4A")
822 .width_pct(80.0);
823 tbl.cell(0, 0).unwrap().set_text("Key Metric");
824 tbl.cell(0, 0).unwrap().shading("1B2A4A");
825 tbl.cell(0, 1).unwrap().set_text("Value");
826 tbl.cell(0, 1).unwrap().shading("1B2A4A");
827 let rows = [
828 ("Total Investment", "$2.8M"),
829 ("Annual Savings", "$4.2M"),
830 ("ROI (Year 1)", "150%"),
831 ("Timeline", "18 months"),
832 ];
833 for (i, (k, v)) in rows.iter().enumerate() {
834 tbl.cell(i + 1, 0).unwrap().set_text(k);
835 if i % 2 == 0 {
836 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
837 }
838 tbl.cell(i + 1, 1).unwrap().set_text(v);
839 if i % 2 == 0 {
840 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
841 }
842 }
843 }
844
845 // ── Problem Statement ──
846 doc.add_paragraph("").page_break_before(true);
847 doc.add_paragraph("Problem Statement").style("Heading1");
848 doc.add_paragraph(
849 "Global Financial Corp's current AI infrastructure faces three critical challenges:",
850 );
851 doc.add_numbered_list_item("Deployment Latency — New models take 3-4 weeks to deploy to production, compared to the industry benchmark of 2-3 days.", 0);
852 doc.add_numbered_list_item("Scalability Constraints — The batch processing architecture cannot handle real-time inference demands during peak trading hours.", 0);
853 doc.add_numbered_list_item("Technical Debt — Legacy Python 2.7 codebases and manual deployment scripts create reliability risks and slow iteration speed.", 0);
854
855 // ── Proposed Solution ──
856 doc.add_paragraph("").page_break_before(true);
857 doc.add_paragraph("Proposed Solution").style("Heading1");
858
859 doc.add_paragraph("Phase 1: Foundation (Months 1-6)")
860 .style("Heading2");
861 doc.add_bullet_list_item("Deploy Kubernetes-based ML serving infrastructure", 0);
862 doc.add_bullet_list_item("Implement CI/CD pipeline for model deployment", 0);
863 doc.add_bullet_list_item("Migrate top 5 production models to new platform", 0);
864
865 doc.add_paragraph("Phase 2: Expansion (Months 7-12)")
866 .style("Heading2");
867 doc.add_bullet_list_item(
868 "Real-time feature engineering pipeline (Apache Kafka + Flink)",
869 0,
870 );
871 doc.add_bullet_list_item("A/B testing framework for model variants", 0);
872 doc.add_bullet_list_item("Automated model monitoring and drift detection", 0);
873
874 doc.add_paragraph("Phase 3: Optimization (Months 13-18)")
875 .style("Heading2");
876 doc.add_bullet_list_item("GPU inference optimization (TensorRT/ONNX Runtime)", 0);
877 doc.add_bullet_list_item("Multi-region deployment for latency reduction", 0);
878 doc.add_bullet_list_item(
879 "Self-service model deployment portal for data science teams",
880 0,
881 );
882
883 // ── Budget ──
884 doc.add_paragraph("Budget Breakdown").style("Heading1");
885 {
886 let mut tbl = doc
887 .add_table(6, 3)
888 .borders(BorderStyle::Single, 4, "1B2A4A")
889 .width_pct(100.0);
890 tbl.cell(0, 0).unwrap().set_text("Category");
891 tbl.cell(0, 0).unwrap().shading("1B2A4A");
892 tbl.cell(0, 1).unwrap().set_text("Cost");
893 tbl.cell(0, 1).unwrap().shading("1B2A4A");
894 tbl.cell(0, 2).unwrap().set_text("Notes");
895 tbl.cell(0, 2).unwrap().shading("1B2A4A");
896 let items = [
897 (
898 "Infrastructure",
899 "$450,000",
900 "Cloud compute + GPU instances",
901 ),
902 ("Engineering Services", "$1,600,000", "12 FTE x 18 months"),
903 (
904 "Software Licenses",
905 "$350,000",
906 "Kafka, monitoring, CI/CD tools",
907 ),
908 (
909 "Training & Enablement",
910 "$200,000",
911 "Team training, documentation",
912 ),
913 ("Contingency (10%)", "$200,000", "Risk buffer"),
914 ];
915 for (i, (cat, cost, note)) in items.iter().enumerate() {
916 tbl.cell(i + 1, 0).unwrap().set_text(cat);
917 tbl.cell(i + 1, 1).unwrap().set_text(cost);
918 tbl.cell(i + 1, 2).unwrap().set_text(note);
919 if i % 2 == 0 {
920 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
921 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
922 tbl.cell(i + 1, 2).unwrap().shading("F4F1EB");
923 }
924 }
925 }
926
927 // ── Closing ──
928 doc.add_paragraph("");
929 doc.add_paragraph("Next Steps").style("Heading1");
930 doc.add_numbered_list_item(
931 "Schedule technical deep-dive with GFC engineering team (Week 1)",
932 0,
933 );
934 doc.add_numbered_list_item("Finalize scope and sign SOW (Week 2-3)", 0);
935 doc.add_numbered_list_item("Kick off Phase 1 with joint planning session (Week 4)", 0);
936
937 doc.add_paragraph("");
938 {
939 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
940 p.add_run("Walter White")
941 .bold(true)
942 .size(14.0)
943 .color("1B2A4A");
944 }
945 {
946 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
947 p.add_run("CEO, Tensorbee | walter@tensorbee.com")
948 .size(10.0)
949 .color("888888");
950 }
951
952 doc
953}
954
955// =============================================================================
956// 3. QUOTE / BILL OF MATERIALS — Teal + orange scheme
957// =============================================================================
958fn generate_quote(_samples_dir: &Path) -> Document {
959 let mut doc = Document::new();
960
961 // Colors: Teal #008B8B, Dark #1A3C3C, Orange #E07020, Light #F0F8F8
962 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
963 doc.set_margins(
964 Length::inches(0.75),
965 Length::inches(0.75),
966 Length::inches(0.75),
967 Length::inches(0.75),
968 );
969 doc.set_title("Quotation QT-2026-0147");
970 doc.set_author("Walter White");
971 doc.set_keywords("quote, BOM, Tensorbee");
972
973 doc.set_header("Tensorbee — Quotation");
974 doc.set_footer("QT-2026-0147 | Page");
975
976 // ── Header Block ──
977 {
978 let mut p = doc.add_paragraph("").alignment(Alignment::Left);
979 p.add_run("TENSORBEE")
980 .bold(true)
981 .size(28.0)
982 .color("008B8B")
983 .font("Helvetica");
984 }
985 doc.add_paragraph("123 Innovation Drive, Suite 400")
986 .alignment(Alignment::Left);
987 doc.add_paragraph("San Francisco, CA 94105 | +1 (415) 555-0199")
988 .alignment(Alignment::Left);
989 doc.add_paragraph("")
990 .border_bottom(BorderStyle::Single, 8, "008B8B");
991
992 // Quote meta
993 doc.add_paragraph("");
994 {
995 let mut tbl = doc.add_table(4, 4).width_pct(100.0);
996 tbl.cell(0, 0).unwrap().set_text("QUOTATION");
997 tbl.cell(0, 0).unwrap().shading("008B8B").grid_span(2);
998 tbl.cell(0, 2).unwrap().set_text("DATE");
999 tbl.cell(0, 2).unwrap().shading("008B8B");
1000 tbl.cell(0, 3).unwrap().set_text("VALID UNTIL");
1001 tbl.cell(0, 3).unwrap().shading("008B8B");
1002 tbl.cell(1, 0).unwrap().set_text("Quote #:");
1003 tbl.cell(1, 1).unwrap().set_text("QT-2026-0147");
1004 tbl.cell(1, 2).unwrap().set_text("Feb 22, 2026");
1005 tbl.cell(1, 3).unwrap().set_text("Mar 22, 2026");
1006 tbl.cell(2, 0).unwrap().set_text("Prepared by:");
1007 tbl.cell(2, 1).unwrap().set_text("Walter White");
1008 tbl.cell(2, 2).unwrap().set_text("Payment:");
1009 tbl.cell(2, 3).unwrap().set_text("Net 30");
1010 tbl.cell(3, 0).unwrap().set_text("Customer:");
1011 tbl.cell(3, 1).unwrap().set_text("Meridian Dynamics LLC");
1012 tbl.cell(3, 2).unwrap().set_text("Currency:");
1013 tbl.cell(3, 3).unwrap().set_text("USD");
1014 }
1015
1016 doc.add_paragraph("");
1017
1018 // ── Bill of Materials ──
1019 {
1020 let mut p = doc.add_paragraph("");
1021 p.add_run("Bill of Materials")
1022 .bold(true)
1023 .size(16.0)
1024 .color("1A3C3C");
1025 }
1026 doc.add_paragraph("");
1027
1028 {
1029 let mut tbl = doc
1030 .add_table(10, 6)
1031 .borders(BorderStyle::Single, 2, "008B8B")
1032 .width_pct(100.0)
1033 .layout_fixed();
1034 // Headers
1035 let hdrs = [
1036 "#",
1037 "Part Number",
1038 "Description",
1039 "Qty",
1040 "Unit Price",
1041 "Total",
1042 ];
1043 for (c, h) in hdrs.iter().enumerate() {
1044 tbl.cell(0, c).unwrap().set_text(h);
1045 tbl.cell(0, c).unwrap().shading("008B8B");
1046 }
1047
1048 let items: Vec<(&str, &str, &str, &str, &str)> = vec![
1049 (
1050 "TB-GPU-A100",
1051 "NVIDIA A100 80GB GPU",
1052 "4",
1053 "$12,500.00",
1054 "$50,000.00",
1055 ),
1056 (
1057 "TB-SRV-R750",
1058 "Dell R750xa Server Chassis",
1059 "2",
1060 "$8,200.00",
1061 "$16,400.00",
1062 ),
1063 (
1064 "TB-RAM-256G",
1065 "256GB DDR5 ECC Memory Module",
1066 "8",
1067 "$890.00",
1068 "$7,120.00",
1069 ),
1070 (
1071 "TB-SSD-3840",
1072 "3.84TB NVMe U.2 SSD",
1073 "8",
1074 "$1,150.00",
1075 "$9,200.00",
1076 ),
1077 (
1078 "TB-NET-CX7",
1079 "ConnectX-7 200GbE NIC",
1080 "4",
1081 "$1,800.00",
1082 "$7,200.00",
1083 ),
1084 (
1085 "TB-SW-48P",
1086 "48-Port 100GbE Switch",
1087 "1",
1088 "$22,000.00",
1089 "$22,000.00",
1090 ),
1091 (
1092 "TB-CAB-RACK",
1093 "42U Server Rack + PDU",
1094 "1",
1095 "$4,500.00",
1096 "$4,500.00",
1097 ),
1098 (
1099 "TB-SVC-INST",
1100 "Installation & Configuration",
1101 "1",
1102 "$8,500.00",
1103 "$8,500.00",
1104 ),
1105 (
1106 "TB-SVC-SUPP",
1107 "3-Year Premium Support",
1108 "1",
1109 "$15,000.00",
1110 "$15,000.00",
1111 ),
1112 ];
1113
1114 for (i, (pn, desc, qty, unit, total)) in items.iter().enumerate() {
1115 let row = i + 1;
1116 tbl.cell(row, 0).unwrap().set_text(&format!("{}", i + 1));
1117 tbl.cell(row, 1).unwrap().set_text(pn);
1118 tbl.cell(row, 2).unwrap().set_text(desc);
1119 tbl.cell(row, 3).unwrap().set_text(qty);
1120 tbl.cell(row, 4).unwrap().set_text(unit);
1121 tbl.cell(row, 5).unwrap().set_text(total);
1122 if i % 2 == 0 {
1123 for c in 0..6 {
1124 tbl.cell(row, c).unwrap().shading("F0F8F8");
1125 }
1126 }
1127 }
1128 }
1129
1130 doc.add_paragraph("");
1131
1132 // ── Totals ──
1133 {
1134 let mut tbl = doc
1135 .add_table(4, 2)
1136 .borders(BorderStyle::Single, 2, "008B8B")
1137 .width(Length::inches(3.5))
1138 .alignment(Alignment::Right);
1139 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1140 tbl.cell(0, 1).unwrap().set_text("$139,920.00");
1141 tbl.cell(1, 0).unwrap().set_text("Shipping & Handling");
1142 tbl.cell(1, 1).unwrap().set_text("$2,500.00");
1143 tbl.cell(2, 0).unwrap().set_text("Tax (8.625%)");
1144 tbl.cell(2, 1).unwrap().set_text("$12,068.10");
1145 tbl.cell(3, 0).unwrap().set_text("TOTAL");
1146 tbl.cell(3, 0).unwrap().shading("E07020");
1147 tbl.cell(3, 1).unwrap().set_text("$154,488.10");
1148 tbl.cell(3, 1).unwrap().shading("E07020");
1149 }
1150
1151 doc.add_paragraph("");
1152
1153 // ── Terms & Conditions ──
1154 {
1155 let mut p = doc.add_paragraph("");
1156 p.add_run("Terms & Conditions")
1157 .bold(true)
1158 .size(14.0)
1159 .color("1A3C3C");
1160 }
1161 doc.add_numbered_list_item(
1162 "This quotation is valid for 30 calendar days from the date of issue.",
1163 0,
1164 );
1165 doc.add_numbered_list_item(
1166 "All prices are in USD and exclusive of applicable taxes unless stated.",
1167 0,
1168 );
1169 doc.add_numbered_list_item("Standard lead time is 4-6 weeks from PO receipt.", 0);
1170 doc.add_numbered_list_item(
1171 "Payment terms: Net 30 from invoice date. 2% discount for payment within 10 days.",
1172 0,
1173 );
1174 doc.add_numbered_list_item(
1175 "Warranty: 3-year manufacturer warranty on all hardware components.",
1176 0,
1177 );
1178 doc.add_numbered_list_item(
1179 "Returns subject to 15% restocking fee if initiated after 14 days.",
1180 0,
1181 );
1182
1183 doc.add_paragraph("");
1184 doc.add_paragraph("")
1185 .border_bottom(BorderStyle::Single, 4, "008B8B");
1186 doc.add_paragraph("");
1187 {
1188 let mut p = doc.add_paragraph("");
1189 p.add_run("Accepted by: ").bold(true);
1190 p.add_run("___________________________ Date: ___________");
1191 }
1192 {
1193 let mut p = doc.add_paragraph("");
1194 p.add_run("Print Name: ").bold(true);
1195 p.add_run("___________________________ Title: ___________");
1196 }
1197
1198 doc
1199}
1200
1201// =============================================================================
1202// 4. INVOICE — Crimson + charcoal scheme
1203// =============================================================================
1204fn generate_invoice(_samples_dir: &Path) -> Document {
1205 let mut doc = Document::new();
1206
1207 // Colors: Crimson #B22222, Charcoal #333333, Light #FAF0F0
1208 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1209 doc.set_margins(
1210 Length::inches(0.75),
1211 Length::inches(0.75),
1212 Length::inches(0.75),
1213 Length::inches(0.75),
1214 );
1215 doc.set_title("Invoice INV-2026-0392");
1216 doc.set_author("Walter White");
1217 doc.set_keywords("invoice, Tensorbee");
1218
1219 doc.set_footer("Tensorbee — Thank you for your business!");
1220
1221 // ── Company & Invoice Header ──
1222 {
1223 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1224 // Company name left, INVOICE right
1225 {
1226 let mut cell = tbl.cell(0, 0).unwrap();
1227 let mut p = cell.add_paragraph("");
1228 p.add_run("TENSORBEE")
1229 .bold(true)
1230 .size(32.0)
1231 .color("B22222")
1232 .font("Helvetica");
1233 }
1234 {
1235 let mut cell = tbl.cell(0, 1).unwrap();
1236 let mut p = cell.add_paragraph("");
1237 p.add_run("INVOICE").bold(true).size(32.0).color("333333");
1238 }
1239 tbl.cell(1, 0)
1240 .unwrap()
1241 .set_text("123 Innovation Drive, Suite 400");
1242 tbl.cell(1, 1).unwrap().set_text("Invoice #: INV-2026-0392");
1243 tbl.cell(2, 0).unwrap().set_text("San Francisco, CA 94105");
1244 tbl.cell(2, 1).unwrap().set_text("Date: February 22, 2026");
1245 tbl.cell(3, 0).unwrap().set_text("walter@tensorbee.com");
1246 tbl.cell(3, 1).unwrap().set_text("Due Date: March 24, 2026");
1247 }
1248
1249 doc.add_paragraph("")
1250 .border_bottom(BorderStyle::Thick, 8, "B22222");
1251 doc.add_paragraph("");
1252
1253 // ── Bill To / Ship To ──
1254 {
1255 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1256 tbl.cell(0, 0).unwrap().set_text("BILL TO");
1257 tbl.cell(0, 0).unwrap().shading("B22222");
1258 tbl.cell(0, 1).unwrap().set_text("SHIP TO");
1259 tbl.cell(0, 1).unwrap().shading("B22222");
1260 tbl.cell(1, 0).unwrap().set_text("Meridian Dynamics LLC");
1261 tbl.cell(1, 1).unwrap().set_text("Meridian Dynamics LLC");
1262 tbl.cell(2, 0).unwrap().set_text("456 Enterprise Blvd");
1263 tbl.cell(2, 1).unwrap().set_text("Attn: Server Room B");
1264 tbl.cell(3, 0).unwrap().set_text("Austin, TX 78701");
1265 tbl.cell(3, 1)
1266 .unwrap()
1267 .set_text("456 Enterprise Blvd, Austin, TX 78701");
1268 }
1269
1270 doc.add_paragraph("");
1271
1272 // ── Line Items ──
1273 {
1274 let mut tbl = doc
1275 .add_table(8, 5)
1276 .borders(BorderStyle::Single, 2, "B22222")
1277 .width_pct(100.0);
1278 let hdrs = ["Description", "Qty", "Unit Price", "Tax", "Amount"];
1279 for (c, h) in hdrs.iter().enumerate() {
1280 tbl.cell(0, c).unwrap().set_text(h);
1281 tbl.cell(0, c).unwrap().shading("B22222");
1282 }
1283 let items = [
1284 (
1285 "ML Infrastructure Setup — Phase 1",
1286 "1",
1287 "$45,000.00",
1288 "$3,881.25",
1289 "$48,881.25",
1290 ),
1291 (
1292 "Data Pipeline Development (160 hrs)",
1293 "160",
1294 "$225.00",
1295 "$3,105.00",
1296 "$39,105.00",
1297 ),
1298 (
1299 "GPU Cluster Configuration",
1300 "1",
1301 "$12,000.00",
1302 "$1,035.00",
1303 "$13,035.00",
1304 ),
1305 (
1306 "API Gateway Implementation",
1307 "1",
1308 "$18,500.00",
1309 "$1,595.63",
1310 "$20,095.63",
1311 ),
1312 (
1313 "Load Testing & QA (80 hrs)",
1314 "80",
1315 "$195.00",
1316 "$1,345.50",
1317 "$16,945.50",
1318 ),
1319 (
1320 "Documentation & Training",
1321 "1",
1322 "$8,000.00",
1323 "$690.00",
1324 "$8,690.00",
1325 ),
1326 (
1327 "Project Management (3 months)",
1328 "3",
1329 "$6,500.00",
1330 "$1,679.63",
1331 "$21,179.63",
1332 ),
1333 ];
1334 for (i, (desc, qty, unit, tax, amt)) in items.iter().enumerate() {
1335 let r = i + 1;
1336 tbl.cell(r, 0).unwrap().set_text(desc);
1337 tbl.cell(r, 1).unwrap().set_text(qty);
1338 tbl.cell(r, 2).unwrap().set_text(unit);
1339 tbl.cell(r, 3).unwrap().set_text(tax);
1340 tbl.cell(r, 4).unwrap().set_text(amt);
1341 if i % 2 == 0 {
1342 for c in 0..5 {
1343 tbl.cell(r, c).unwrap().shading("FAF0F0");
1344 }
1345 }
1346 }
1347 }
1348
1349 doc.add_paragraph("");
1350
1351 // ── Totals ──
1352 {
1353 let mut tbl = doc
1354 .add_table(4, 2)
1355 .borders(BorderStyle::Single, 2, "B22222")
1356 .width(Length::inches(3.0))
1357 .alignment(Alignment::Right);
1358 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1359 tbl.cell(0, 1).unwrap().set_text("$154,600.00");
1360 tbl.cell(1, 0).unwrap().set_text("Tax (8.625%)");
1361 tbl.cell(1, 1).unwrap().set_text("$13,334.25");
1362 tbl.cell(2, 0).unwrap().set_text("Discount (5%)");
1363 tbl.cell(2, 1).unwrap().set_text("-$7,730.00");
1364 tbl.cell(3, 0).unwrap().set_text("AMOUNT DUE");
1365 tbl.cell(3, 0).unwrap().shading("B22222");
1366 tbl.cell(3, 1).unwrap().set_text("$160,204.25");
1367 tbl.cell(3, 1).unwrap().shading("B22222");
1368 }
1369
1370 doc.add_paragraph("");
1371 doc.add_paragraph("");
1372
1373 // ── Payment Details ──
1374 {
1375 let mut p = doc.add_paragraph("");
1376 p.add_run("Payment Details")
1377 .bold(true)
1378 .size(14.0)
1379 .color("333333");
1380 }
1381 doc.add_paragraph("Bank: Silicon Valley Bank")
1382 .indent_left(Length::inches(0.3));
1383 doc.add_paragraph("Account: Tensorbee Inc. — 0847-2953-1120")
1384 .indent_left(Length::inches(0.3));
1385 doc.add_paragraph("Routing: 121140399")
1386 .indent_left(Length::inches(0.3));
1387 doc.add_paragraph("Swift: SVBKUS6S")
1388 .indent_left(Length::inches(0.3));
1389
1390 doc.add_paragraph("");
1391
1392 doc.add_paragraph("Please include invoice number INV-2026-0392 in the payment reference.")
1393 .shading("FAF0F0")
1394 .border_all(BorderStyle::Single, 2, "B22222");
1395
1396 doc
1397}
1398
1399// =============================================================================
1400// 5. REPORT — Forest green + earth tones, with images & hierarchical sections
1401// =============================================================================
1402fn generate_report(_samples_dir: &Path) -> Document {
1403 let mut doc = Document::new();
1404
1405 // Colors: Forest #2D5016, Sage #6B8E23, Earth #8B7355, Cream #FFFAF0
1406 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1407 doc.set_margins(
1408 Length::inches(1.0),
1409 Length::inches(1.0),
1410 Length::inches(1.0),
1411 Length::inches(1.0),
1412 );
1413 doc.set_title("Q4 2025 Environmental Impact Report");
1414 doc.set_author("Walter White");
1415 doc.set_subject("Quarterly Environmental Report");
1416 doc.set_keywords("environment, sustainability, report, Tensorbee");
1417
1418 doc.set_different_first_page(true);
1419 doc.set_first_page_header("");
1420 doc.set_header("Tensorbee — Q4 2025 Environmental Impact Report");
1421 doc.set_footer("CONFIDENTIAL — Page");
1422
1423 // ── Cover ──
1424 let cover_bg = create_sample_png(612, 792, [20, 50, 15]);
1425 doc.add_background_image(&cover_bg, "report_cover.png");
1426
1427 for _ in 0..5 {
1428 doc.add_paragraph("");
1429 }
1430 {
1431 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1432 p.add_run("Q4 2025")
1433 .bold(true)
1434 .size(48.0)
1435 .color("FFFFFF")
1436 .font("Georgia");
1437 }
1438 {
1439 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1440 p.add_run("Environmental Impact Report")
1441 .size(24.0)
1442 .color("90EE90")
1443 .font("Georgia")
1444 .italic(true);
1445 }
1446 doc.add_paragraph("");
1447 {
1448 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1449 p.add_run("Tensorbee — Sustainability Division")
1450 .size(14.0)
1451 .color("C0C0C0");
1452 }
1453 {
1454 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1455 p.add_run("Prepared by Walter White, Chief Sustainability Officer")
1456 .size(11.0)
1457 .color("AAAAAA");
1458 }
1459
1460 // ── TOC ──
1461 doc.add_paragraph("").page_break_before(true);
1462 doc.insert_toc(doc.content_count(), 3);
1463
1464 // ── Section 1: Executive Overview ──
1465 doc.add_paragraph("").page_break_before(true);
1466 doc.add_paragraph("Executive Overview").style("Heading1");
1467 doc.add_paragraph(
1468 "This report presents Tensorbee's environmental performance for Q4 2025. Our \
1469 sustainability initiatives have yielded a 34% reduction in carbon emissions compared \
1470 to Q4 2024, exceeding our target of 25%. Key achievements include the transition to \
1471 100% renewable energy in our primary data centers and the launch of our carbon offset \
1472 marketplace.",
1473 )
1474 .first_line_indent(Length::inches(0.3));
1475
1476 // Image: performance chart
1477 let chart_img = create_chart_png(400, 200);
1478 doc.add_paragraph("");
1479 doc.add_picture(
1480 &chart_img,
1481 "performance_chart.png",
1482 Length::inches(5.0),
1483 Length::inches(2.5),
1484 )
1485 .alignment(Alignment::Center);
1486 {
1487 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1488 p.add_run("Figure 1: Quarterly Carbon Emissions (tonnes CO2e)")
1489 .italic(true)
1490 .size(9.0)
1491 .color("666666");
1492 }
1493
1494 // ── Section 2: Energy Consumption ──
1495 doc.add_paragraph("").page_break_before(true);
1496 doc.add_paragraph("Energy Consumption").style("Heading1");
1497
1498 doc.add_paragraph("Data Center Operations")
1499 .style("Heading2");
1500 doc.add_paragraph(
1501 "Our three primary data centers consumed a combined 4.2 GWh in Q4 2025, \
1502 a 12% reduction from Q3 2025 driven by improved cooling efficiency and \
1503 server consolidation.",
1504 );
1505
1506 // Data table
1507 {
1508 let mut tbl = doc
1509 .add_table(5, 4)
1510 .borders(BorderStyle::Single, 4, "2D5016")
1511 .width_pct(100.0);
1512 let hdrs = ["Data Center", "Capacity (MW)", "Usage (GWh)", "PUE"];
1513 for (c, h) in hdrs.iter().enumerate() {
1514 tbl.cell(0, c).unwrap().set_text(h);
1515 tbl.cell(0, c).unwrap().shading("2D5016");
1516 }
1517 let rows = [
1518 ("San Francisco (Primary)", "3.2", "1.8", "1.12"),
1519 ("Dublin (EU)", "2.1", "1.2", "1.18"),
1520 ("Singapore (APAC)", "1.8", "1.2", "1.24"),
1521 ("TOTAL", "7.1", "4.2", "1.17 avg"),
1522 ];
1523 for (i, (dc, cap, use_, pue)) in rows.iter().enumerate() {
1524 tbl.cell(i + 1, 0).unwrap().set_text(dc);
1525 tbl.cell(i + 1, 1).unwrap().set_text(cap);
1526 tbl.cell(i + 1, 2).unwrap().set_text(use_);
1527 tbl.cell(i + 1, 3).unwrap().set_text(pue);
1528 if i == 3 {
1529 for c in 0..4 {
1530 tbl.cell(i + 1, c).unwrap().shading("E2EFDA");
1531 }
1532 }
1533 }
1534 }
1535
1536 doc.add_paragraph("");
1537
1538 doc.add_paragraph("Renewable Energy Mix").style("Heading2");
1539 doc.add_paragraph("Breakdown of energy sources across all facilities:");
1540
1541 doc.add_bullet_list_item("Solar PV: 42% (1.76 GWh)", 0);
1542 doc.add_bullet_list_item("Wind Power Purchase Agreements: 38% (1.60 GWh)", 0);
1543 doc.add_bullet_list_item("Hydroelectric: 12% (0.50 GWh)", 0);
1544 doc.add_bullet_list_item("Grid (non-renewable): 8% (0.34 GWh)", 0);
1545
1546 // ── Section 3: Water & Waste ──
1547 doc.add_paragraph("Water & Waste Management")
1548 .style("Heading1");
1549
1550 doc.add_paragraph("Water Usage").style("Heading2");
1551 doc.add_paragraph(
1552 "Total water consumption was 12.4 million gallons, a 15% reduction from Q3. \
1553 Our closed-loop cooling systems now recycle 78% of water used in cooling operations.",
1554 );
1555
1556 doc.add_paragraph("Waste Reduction").style("Heading2");
1557 doc.add_paragraph("E-waste management results for Q4:")
1558 .keep_with_next(true);
1559 {
1560 let mut tbl = doc
1561 .add_table(4, 3)
1562 .borders(BorderStyle::Single, 2, "6B8E23")
1563 .width_pct(80.0);
1564 tbl.cell(0, 0).unwrap().set_text("Category");
1565 tbl.cell(0, 0).unwrap().shading("6B8E23");
1566 tbl.cell(0, 1).unwrap().set_text("Weight (kg)");
1567 tbl.cell(0, 1).unwrap().shading("6B8E23");
1568 tbl.cell(0, 2).unwrap().set_text("Recycled %");
1569 tbl.cell(0, 2).unwrap().shading("6B8E23");
1570 let rows = [
1571 ("Server Hardware", "2,450", "94%"),
1572 ("Networking Equipment", "820", "91%"),
1573 ("Storage Media", "340", "99%"),
1574 ];
1575 for (i, (cat, wt, pct)) in rows.iter().enumerate() {
1576 tbl.cell(i + 1, 0).unwrap().set_text(cat);
1577 tbl.cell(i + 1, 1).unwrap().set_text(wt);
1578 tbl.cell(i + 1, 2).unwrap().set_text(pct);
1579 }
1580 }
1581
1582 // ── Section 4: Initiatives ──
1583 doc.add_paragraph("").page_break_before(true);
1584 doc.add_paragraph("Q1 2026 Initiatives").style("Heading1");
1585
1586 doc.add_paragraph("Planned Programs").style("Heading2");
1587 doc.add_numbered_list_item(
1588 "Deploy on-site battery storage at SF data center (2 MWh capacity)",
1589 0,
1590 );
1591 doc.add_numbered_list_item("Pilot immersion cooling in Dublin facility", 0);
1592 doc.add_numbered_list_item("Launch employee commute carbon offset program", 0);
1593 doc.add_numbered_list_item("Achieve ISO 14001 certification for Singapore facility", 0);
1594
1595 doc.add_paragraph("Investment Targets").style("Heading2");
1596 doc.add_paragraph("Sustainability CapEx allocation for FY2026:")
1597 .keep_with_next(true);
1598 {
1599 let mut tbl = doc
1600 .add_table(5, 2)
1601 .borders(BorderStyle::Single, 4, "2D5016");
1602 tbl.cell(0, 0).unwrap().set_text("Initiative");
1603 tbl.cell(0, 0).unwrap().shading("2D5016");
1604 tbl.cell(0, 1).unwrap().set_text("Budget");
1605 tbl.cell(0, 1).unwrap().shading("2D5016");
1606 let rows = [
1607 ("Battery Storage", "$1.2M"),
1608 ("Immersion Cooling Pilot", "$800K"),
1609 ("Solar Panel Expansion", "$2.1M"),
1610 ("Carbon Credits", "$500K"),
1611 ];
1612 for (i, (init, budget)) in rows.iter().enumerate() {
1613 tbl.cell(i + 1, 0).unwrap().set_text(init);
1614 tbl.cell(i + 1, 1).unwrap().set_text(budget);
1615 if i % 2 == 0 {
1616 tbl.cell(i + 1, 0).unwrap().shading("FFFAF0");
1617 tbl.cell(i + 1, 1).unwrap().shading("FFFAF0");
1618 }
1619 }
1620 }
1621
1622 // Image: sustainability roadmap
1623 let roadmap_img = create_sample_png(500, 100, [40, 80, 30]);
1624 doc.add_paragraph("");
1625 doc.add_picture(
1626 &roadmap_img,
1627 "roadmap.png",
1628 Length::inches(6.0),
1629 Length::inches(1.2),
1630 )
1631 .alignment(Alignment::Center);
1632 {
1633 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1634 p.add_run("Figure 2: 2026 Sustainability Roadmap")
1635 .italic(true)
1636 .size(9.0)
1637 .color("666666");
1638 }
1639
1640 doc.add_paragraph("");
1641 doc.add_paragraph(
1642 "For questions about this report, contact Walter White at sustainability@tensorbee.com.",
1643 )
1644 .shading("FFFAF0")
1645 .border_all(BorderStyle::Single, 2, "2D5016");
1646
1647 doc
1648}
1649
1650// =============================================================================
1651// 6. LETTER — Slate blue + silver scheme
1652// =============================================================================
1653fn generate_letter(_samples_dir: &Path) -> Document {
1654 let mut doc = Document::new();
1655
1656 // Colors: Slate #4A5568, Blue accent #3182CE
1657 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1658 doc.set_margins(
1659 Length::inches(1.25),
1660 Length::inches(1.25),
1661 Length::inches(1.25),
1662 Length::inches(1.25),
1663 );
1664 doc.set_title("Business Letter");
1665 doc.set_author("Walter White");
1666
1667 // Banner header using raw XML
1668 let logo_img = create_logo_png(220, 48);
1669 let banner = build_header_banner_xml(
1670 "rId1",
1671 &BannerOpts {
1672 bg_color: "4A5568",
1673 banner_width: 7772400,
1674 banner_height: 731520, // ~0.8"
1675 logo_width: 2011680,
1676 logo_height: 438912,
1677 logo_x_offset: 295125,
1678 logo_y_offset: 146304,
1679 },
1680 );
1681 doc.set_raw_header_with_images(
1682 banner,
1683 &[("rId1", &logo_img, "logo.png")],
1684 rdocx_oxml::header_footer::HdrFtrType::Default,
1685 );
1686 doc.set_margins(
1687 Length::twips(2000),
1688 Length::twips(1800),
1689 Length::twips(1440),
1690 Length::twips(1800),
1691 );
1692 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
1693
1694 // Footer with contact
1695 doc.set_footer(
1696 "Tensorbee | 123 Innovation Drive, Suite 400 | San Francisco, CA 94105 | tensorbee.com",
1697 );
1698
1699 // ── Sender Address ──
1700 doc.add_paragraph("Walter White");
1701 doc.add_paragraph("Chief Executive Officer");
1702 doc.add_paragraph("Tensorbee");
1703 doc.add_paragraph("123 Innovation Drive, Suite 400");
1704 doc.add_paragraph("San Francisco, CA 94105");
1705 doc.add_paragraph("");
1706 doc.add_paragraph("February 22, 2026");
1707
1708 doc.add_paragraph("");
1709
1710 // ── Recipient ──
1711 doc.add_paragraph("Ms. Sarah Chen");
1712 doc.add_paragraph("VP of Engineering");
1713 doc.add_paragraph("Meridian Dynamics LLC");
1714 doc.add_paragraph("456 Enterprise Boulevard");
1715 doc.add_paragraph("Austin, TX 78701");
1716
1717 doc.add_paragraph("");
1718
1719 // ── Subject ──
1720 {
1721 let mut p = doc.add_paragraph("");
1722 p.add_run("Re: Strategic Technology Partnership — Phase 2 Expansion")
1723 .bold(true);
1724 }
1725
1726 doc.add_paragraph("");
1727
1728 // ── Body ──
1729 doc.add_paragraph("Dear Ms. Chen,");
1730 doc.add_paragraph("");
1731
1732 doc.add_paragraph(
1733 "I am writing to express Tensorbee's enthusiasm for expanding our strategic technology \
1734 partnership with Meridian Dynamics. Following the successful completion of Phase 1, \
1735 which delivered a 40% improvement in your ML inference pipeline throughput, we believe \
1736 the foundation is firmly established for an ambitious Phase 2 engagement.",
1737 )
1738 .first_line_indent(Length::inches(0.5));
1739
1740 doc.add_paragraph(
1741 "During our recent executive review, your team highlighted three priority areas for \
1742 the next phase of collaboration:",
1743 )
1744 .first_line_indent(Length::inches(0.5));
1745
1746 doc.add_numbered_list_item(
1747 "Real-time anomaly detection for your financial transaction monitoring system, \
1748 targeting sub-100ms latency at 50,000 transactions per second.",
1749 0,
1750 );
1751 doc.add_numbered_list_item(
1752 "Federated learning infrastructure to enable model training across your distributed \
1753 data centers without centralizing sensitive financial data.",
1754 0,
1755 );
1756 doc.add_numbered_list_item(
1757 "MLOps automation to reduce your model deployment cycle from the current 5 days \
1758 to under 4 hours.",
1759 0,
1760 );
1761
1762 doc.add_paragraph(
1763 "Our engineering team has prepared a detailed technical proposal addressing each of \
1764 these areas. We have allocated a dedicated team of eight senior engineers, led by \
1765 our CTO, to ensure continuity with the Phase 1 team your organization has already \
1766 built a productive working relationship with.",
1767 )
1768 .first_line_indent(Length::inches(0.5));
1769
1770 doc.add_paragraph(
1771 "I would welcome the opportunity to present our Phase 2 proposal in person. My \
1772 assistant will reach out to coordinate a meeting at your convenience during the \
1773 first week of March.",
1774 )
1775 .first_line_indent(Length::inches(0.5));
1776
1777 doc.add_paragraph("");
1778
1779 doc.add_paragraph("Warm regards,");
1780 doc.add_paragraph("");
1781 doc.add_paragraph("");
1782
1783 // Signature line
1784 doc.add_paragraph("")
1785 .border_bottom(BorderStyle::Single, 4, "4A5568");
1786 {
1787 let mut p = doc.add_paragraph("");
1788 p.add_run("Walter White").bold(true).size(12.0);
1789 }
1790 {
1791 let mut p = doc.add_paragraph("");
1792 p.add_run("Chief Executive Officer, Tensorbee")
1793 .italic(true)
1794 .size(10.0)
1795 .color("666666");
1796 }
1797 {
1798 let mut p = doc.add_paragraph("");
1799 p.add_run("walter@tensorbee.com | +1 (415) 555-0199")
1800 .size(10.0)
1801 .color("888888");
1802 }
1803
1804 doc
1805}
1806
1807// =============================================================================
1808// 7. EMPLOYMENT CONTRACT — Purple + charcoal scheme
1809// =============================================================================
1810fn generate_contract(_samples_dir: &Path) -> Document {
1811 let mut doc = Document::new();
1812
1813 // Colors: Purple #5B2C6F, Plum #8E44AD, Gray #2C3E50
1814 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1815 doc.set_margins(
1816 Length::inches(1.25),
1817 Length::inches(1.0),
1818 Length::inches(1.0),
1819 Length::inches(1.0),
1820 );
1821 doc.set_title("Employment Agreement");
1822 doc.set_author("Tensorbee HR Department");
1823 doc.set_subject("Employment Contract — Walter White");
1824 doc.set_keywords("employment, contract, agreement, Tensorbee");
1825
1826 doc.set_header("TENSORBEE — Employment Agreement");
1827 doc.set_footer("Employment Agreement — Walter White — Page");
1828
1829 // ── Title Block ──
1830 doc.add_paragraph("")
1831 .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1832 {
1833 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1834 p.add_run("EMPLOYMENT AGREEMENT")
1835 .bold(true)
1836 .size(24.0)
1837 .color("5B2C6F")
1838 .font("Georgia");
1839 }
1840 doc.add_paragraph("")
1841 .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1842 doc.add_paragraph("");
1843
1844 // ── Parties ──
1845 doc.add_paragraph(
1846 "This Employment Agreement (\"Agreement\") is entered into as of February 22, 2026 \
1847 (\"Effective Date\"), by and between:",
1848 );
1849
1850 doc.add_paragraph("");
1851
1852 {
1853 let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1854 p.add_run("EMPLOYER: ").bold(true);
1855 p.add_run(
1856 "Tensorbee, Inc., a Delaware corporation with its principal place of business at \
1857 123 Innovation Drive, Suite 400, San Francisco, CA 94105 (\"Company\")",
1858 );
1859 }
1860 doc.add_paragraph("");
1861 {
1862 let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1863 p.add_run("EMPLOYEE: ").bold(true);
1864 p.add_run(
1865 "Walter White, residing at 308 Negra Arroyo Lane, Albuquerque, NM 87104 (\"Employee\")",
1866 );
1867 }
1868
1869 doc.add_paragraph("");
1870 doc.add_paragraph("The Company and Employee are collectively referred to as the \"Parties.\"");
1871 doc.add_paragraph("");
1872
1873 // ── Article 1: Position and Duties ──
1874 doc.add_paragraph("Article 1 — Position and Duties")
1875 .style("Heading1");
1876 {
1877 let mut p = doc.add_paragraph("");
1878 p.add_run("1.1 ").bold(true);
1879 p.add_run("The Company hereby employs the Employee as ");
1880 p.add_run("Chief Executive Officer (CEO)")
1881 .bold(true)
1882 .italic(true);
1883 p.add_run(", reporting directly to the Board of Directors.");
1884 }
1885 {
1886 let mut p = doc.add_paragraph("");
1887 p.add_run("1.2 ").bold(true);
1888 p.add_run("The Employee shall devote full working time, attention, and best efforts to \
1889 the performance of duties as reasonably assigned by the Board, including but not limited to:");
1890 }
1891 doc.add_bullet_list_item(
1892 "Setting and executing the Company's strategic vision and business plan",
1893 0,
1894 );
1895 doc.add_bullet_list_item(
1896 "Overseeing all operations, engineering, and commercial activities",
1897 0,
1898 );
1899 doc.add_bullet_list_item(
1900 "Representing the Company to investors, customers, and the public",
1901 0,
1902 );
1903 doc.add_bullet_list_item("Recruiting, developing, and retaining key talent", 0);
1904
1905 {
1906 let mut p = doc.add_paragraph("");
1907 p.add_run("1.3 ").bold(true);
1908 p.add_run(
1909 "The Employee's primary work location shall be the Company's San Francisco \
1910 headquarters, with reasonable travel as required by business needs.",
1911 );
1912 }
1913
1914 // ── Article 2: Compensation ──
1915 doc.add_paragraph("Article 2 — Compensation and Benefits")
1916 .style("Heading1");
1917 {
1918 let mut p = doc.add_paragraph("");
1919 p.add_run("2.1 Base Salary. ").bold(true);
1920 p.add_run("The Company shall pay the Employee an annual base salary of ");
1921 p.add_run("$375,000.00").bold(true);
1922 p.add_run(" (Three Hundred Seventy-Five Thousand Dollars), payable in accordance with the \
1923 Company's standard payroll schedule, less applicable withholdings and deductions.");
1924 }
1925 {
1926 let mut p = doc.add_paragraph("");
1927 p.add_run("2.2 Annual Bonus. ").bold(true);
1928 p.add_run("The Employee shall be eligible for an annual performance bonus of up to ");
1929 p.add_run("40%").bold(true);
1930 p.add_run(
1931 " of base salary, based on achievement of mutually agreed performance objectives.",
1932 );
1933 }
1934 {
1935 let mut p = doc.add_paragraph("");
1936 p.add_run("2.3 Equity. ").bold(true);
1937 p.add_run("Subject to Board approval, the Employee shall receive a stock option grant of ");
1938 p.add_run("500,000 shares").bold(true);
1939 p.add_run(
1940 " of the Company's common stock, vesting over four (4) years with a one-year cliff, \
1941 at an exercise price equal to the fair market value on the date of grant.",
1942 );
1943 }
1944
1945 // Compensation summary table
1946 doc.add_paragraph("");
1947 {
1948 let mut tbl = doc
1949 .add_table(5, 2)
1950 .borders(BorderStyle::Single, 4, "5B2C6F")
1951 .width_pct(70.0)
1952 .alignment(Alignment::Center);
1953 tbl.cell(0, 0).unwrap().set_text("Compensation Element");
1954 tbl.cell(0, 0).unwrap().shading("5B2C6F");
1955 tbl.cell(0, 1).unwrap().set_text("Value");
1956 tbl.cell(0, 1).unwrap().shading("5B2C6F");
1957 let items = [
1958 ("Base Salary", "$375,000/year"),
1959 ("Target Bonus", "Up to 40% ($150,000)"),
1960 ("Equity Grant", "500,000 shares (4yr vest)"),
1961 ("Total Target Comp", "$525,000 + equity"),
1962 ];
1963 for (i, (elem, val)) in items.iter().enumerate() {
1964 tbl.cell(i + 1, 0).unwrap().set_text(elem);
1965 tbl.cell(i + 1, 1).unwrap().set_text(val);
1966 if i == 3 {
1967 tbl.cell(i + 1, 0).unwrap().shading("E8DAEF");
1968 tbl.cell(i + 1, 1).unwrap().shading("E8DAEF");
1969 }
1970 }
1971 }
1972
1973 // ── Article 3: Benefits ──
1974 doc.add_paragraph("").page_break_before(true);
1975 doc.add_paragraph("Article 3 — Benefits").style("Heading1");
1976 {
1977 let mut p = doc.add_paragraph("");
1978 p.add_run("3.1 ").bold(true);
1979 p.add_run(
1980 "The Employee shall be entitled to participate in all benefit programs \
1981 generally available to senior executives, including:",
1982 );
1983 }
1984 doc.add_bullet_list_item(
1985 "Medical, dental, and vision insurance (100% premium coverage for employee and dependents)",
1986 0,
1987 );
1988 doc.add_bullet_list_item("401(k) retirement plan with 6% company match", 0);
1989 doc.add_bullet_list_item("Life insurance and long-term disability coverage", 0);
1990 doc.add_bullet_list_item("Annual professional development allowance of $10,000", 0);
1991
1992 {
1993 let mut p = doc.add_paragraph("");
1994 p.add_run("3.2 Paid Time Off. ").bold(true);
1995 p.add_run(
1996 "The Employee shall receive 25 days of paid vacation per year, plus Company holidays, \
1997 accruing on a monthly basis.",
1998 );
1999 }
2000
2001 // ── Article 4: Term and Termination ──
2002 doc.add_paragraph("Article 4 — Term and Termination")
2003 .style("Heading1");
2004 {
2005 let mut p = doc.add_paragraph("");
2006 p.add_run("4.1 At-Will Employment. ").bold(true);
2007 p.add_run(
2008 "This Agreement is for at-will employment and may be terminated by either Party \
2009 at any time, with or without cause, subject to the notice provisions herein.",
2010 );
2011 }
2012 {
2013 let mut p = doc.add_paragraph("");
2014 p.add_run("4.2 Notice Period. ").bold(true);
2015 p.add_run("Either Party shall provide ");
2016 p.add_run("ninety (90) days").bold(true);
2017 p.add_run(" written notice of termination, or pay in lieu thereof.");
2018 }
2019 {
2020 let mut p = doc.add_paragraph("");
2021 p.add_run("4.3 Severance. ").bold(true);
2022 p.add_run("In the event of termination by the Company without Cause, the Employee shall \
2023 receive (a) twelve (12) months of base salary continuation, (b) pro-rata bonus \
2024 for the year of termination, and (c) twelve (12) months of COBRA premium coverage.");
2025 }
2026
2027 // ── Article 5: Confidentiality ──
2028 doc.add_paragraph("Article 5 — Confidentiality and IP")
2029 .style("Heading1");
2030 {
2031 let mut p = doc.add_paragraph("");
2032 p.add_run("5.1 ").bold(true);
2033 p.add_run(
2034 "The Employee agrees to maintain strict confidentiality of all proprietary \
2035 information, trade secrets, and business plans of the Company during and after \
2036 employment.",
2037 );
2038 }
2039 {
2040 let mut p = doc.add_paragraph("");
2041 p.add_run("5.2 ").bold(true);
2042 p.add_run(
2043 "All intellectual property, inventions, and works of authorship created by the \
2044 Employee during the term of employment and within the scope of duties shall be \
2045 the sole and exclusive property of the Company.",
2046 );
2047 }
2048
2049 // ── Article 6: Non-Compete ──
2050 doc.add_paragraph("Article 6 — Non-Competition")
2051 .style("Heading1");
2052 {
2053 let mut p = doc.add_paragraph("");
2054 p.add_run("6.1 ").bold(true);
2055 p.add_run("For a period of twelve (12) months following termination, the Employee agrees \
2056 not to engage in any business that directly competes with the Company's core \
2057 business of AI infrastructure and ML operations services, within the United States.");
2058 }
2059
2060 // ── Article 7: General Provisions ──
2061 doc.add_paragraph("Article 7 — General Provisions")
2062 .style("Heading1");
2063 {
2064 let mut p = doc.add_paragraph("");
2065 p.add_run("7.1 Governing Law. ").bold(true);
2066 p.add_run(
2067 "This Agreement shall be governed by and construed in accordance with the laws of \
2068 the State of California.",
2069 );
2070 }
2071 {
2072 let mut p = doc.add_paragraph("");
2073 p.add_run("7.2 Entire Agreement. ").bold(true);
2074 p.add_run(
2075 "This Agreement constitutes the entire agreement between the Parties and supersedes \
2076 all prior negotiations, representations, and agreements.",
2077 );
2078 }
2079 {
2080 let mut p = doc.add_paragraph("");
2081 p.add_run("7.3 Amendment. ").bold(true);
2082 p.add_run(
2083 "This Agreement may only be amended by a written instrument signed by both Parties.",
2084 );
2085 }
2086
2087 // ── Signature Block ──
2088 doc.add_paragraph("").page_break_before(true);
2089 doc.add_paragraph("IN WITNESS WHEREOF, the Parties have executed this Employment Agreement as of the Effective Date.")
2090 .space_after(Length::pt(24.0));
2091
2092 // Signature table
2093 {
2094 let mut tbl = doc.add_table(6, 2).width_pct(100.0);
2095 tbl.cell(0, 0).unwrap().set_text("FOR THE COMPANY:");
2096 tbl.cell(0, 0).unwrap().shading("5B2C6F");
2097 tbl.cell(0, 1).unwrap().set_text("EMPLOYEE:");
2098 tbl.cell(0, 1).unwrap().shading("5B2C6F");
2099
2100 tbl.cell(1, 0).unwrap().set_text("");
2101 tbl.cell(1, 1).unwrap().set_text("");
2102 tbl.row(1).unwrap().height(Length::pt(40.0));
2103
2104 tbl.cell(2, 0)
2105 .unwrap()
2106 .set_text("Signature: ___________________________");
2107 tbl.cell(2, 1)
2108 .unwrap()
2109 .set_text("Signature: ___________________________");
2110 tbl.cell(3, 0)
2111 .unwrap()
2112 .set_text("Name: Board Representative");
2113 tbl.cell(3, 1).unwrap().set_text("Name: Walter White");
2114 tbl.cell(4, 0)
2115 .unwrap()
2116 .set_text("Title: Chair, Board of Directors");
2117 tbl.cell(4, 1)
2118 .unwrap()
2119 .set_text("Title: Chief Executive Officer");
2120 tbl.cell(5, 0).unwrap().set_text("Date: _______________");
2121 tbl.cell(5, 1).unwrap().set_text("Date: _______________");
2122 }
2123
2124 doc
2125}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?
86fn generate_feature_showcase(_samples_dir: &Path) -> Document {
87 let mut doc = Document::new();
88
89 // ── Page Setup & Metadata ──
90 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
91 doc.set_margins(
92 Length::inches(1.0),
93 Length::inches(1.0),
94 Length::inches(1.0),
95 Length::inches(1.0),
96 );
97 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
98 doc.set_gutter(Length::twips(0));
99 doc.set_title("rdocx Feature Showcase");
100 doc.set_author("rdocx Sample Generator");
101 doc.set_subject("Comprehensive feature demonstration");
102 doc.set_keywords("rdocx, docx, rust, sample, showcase");
103
104 // Headers & Footers with different first page
105 doc.set_different_first_page(true);
106 doc.set_first_page_header("rdocx");
107 doc.set_first_page_footer("Feature Showcase — Cover Page");
108 doc.set_header("rdocx Feature Showcase");
109 doc.set_footer("Generated by rdocx");
110
111 // ── COVER PAGE ──
112 let bg = create_sample_png(612, 792, [20, 45, 90]);
113 doc.add_background_image(&bg, "cover_bg.png");
114
115 for _ in 0..3 {
116 doc.add_paragraph("");
117 }
118 {
119 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
120 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
121 }
122 {
123 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
124 p.add_run("Complete Feature Showcase")
125 .size(28.0)
126 .color("FFFFFF")
127 .italic(true);
128 }
129 doc.add_paragraph("");
130 {
131 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
132 p.add_run("Every feature of the rdocx Rust library")
133 .size(13.0)
134 .color("B0C4DE");
135 }
136 {
137 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
138 p.add_run("demonstrated in a single document.")
139 .size(13.0)
140 .color("B0C4DE");
141 }
142
143 // ── TABLE OF CONTENTS ──
144 doc.add_paragraph("").page_break_before(true);
145 doc.insert_toc(doc.content_count(), 3);
146
147 // ── SECTION 1: TEXT FORMATTING ──
148 doc.add_paragraph("").page_break_before(true);
149 doc.add_paragraph("1. Text Formatting").style("Heading1");
150
151 // Paragraph Alignment
152 doc.add_paragraph("1.1 Paragraph Alignment")
153 .style("Heading2");
154 doc.add_paragraph("Left-aligned paragraph (default).")
155 .alignment(Alignment::Left);
156 doc.add_paragraph("Center-aligned paragraph.")
157 .alignment(Alignment::Center);
158 doc.add_paragraph("Right-aligned paragraph.")
159 .alignment(Alignment::Right);
160 doc.add_paragraph(
161 "Justified paragraph — this text is long enough to demonstrate how justified alignment \
162 distributes extra space across word gaps so lines fill the full width of the text area.",
163 )
164 .alignment(Alignment::Justify);
165
166 doc.add_paragraph("");
167
168 // Run Formatting
169 doc.add_paragraph("1.2 Run Formatting").style("Heading2");
170 {
171 let mut p = doc.add_paragraph("");
172 p.add_run("Bold").bold(true);
173 p.add_run(" | ");
174 p.add_run("Italic").italic(true);
175 p.add_run(" | ");
176 p.add_run("Bold Italic").bold(true).italic(true);
177 p.add_run(" | ");
178 p.add_run("Underline").underline(true);
179 p.add_run(" | ");
180 p.add_run("Strikethrough").strike(true);
181 p.add_run(" | ");
182 p.add_run("Double Strike").double_strike(true);
183 }
184 {
185 let mut p = doc.add_paragraph("");
186 p.add_run("Red").color("FF0000");
187 p.add_run(" | ");
188 p.add_run("Blue").color("0000FF");
189 p.add_run(" | ");
190 p.add_run("Green").color("00AA00");
191 p.add_run(" | ");
192 p.add_run("Highlighted").highlight("FFFF00");
193 }
194 {
195 let mut p = doc.add_paragraph("");
196 p.add_run("8pt").size(8.0);
197 p.add_run(" | ");
198 p.add_run("11pt").size(11.0);
199 p.add_run(" | ");
200 p.add_run("16pt").size(16.0);
201 p.add_run(" | ");
202 p.add_run("24pt").size(24.0);
203 }
204 {
205 let mut p = doc.add_paragraph("");
206 p.add_run("Arial").font("Arial");
207 p.add_run(" | ");
208 p.add_run("Times New Roman").font("Times New Roman");
209 p.add_run(" | ");
210 p.add_run("Courier New").font("Courier New");
211 }
212 {
213 let mut p = doc.add_paragraph("");
214 p.add_run("H");
215 p.add_run("2").subscript();
216 p.add_run("O (subscript) | E=mc");
217 p.add_run("2").superscript();
218 p.add_run(" (superscript)");
219 }
220 {
221 let mut p = doc.add_paragraph("");
222 p.add_run("ALL CAPS").all_caps(true);
223 p.add_run(" | ");
224 p.add_run("Small Caps").small_caps(true);
225 p.add_run(" | ");
226 p.add_run("Expanded").character_spacing(Length::twips(40));
227 p.add_run(" | ");
228 p.add_run("Hidden text (not visible)").hidden(true);
229 }
230
231 doc.add_paragraph("");
232
233 // Underline Styles
234 doc.add_paragraph("1.3 Underline Styles").style("Heading2");
235 {
236 let mut p = doc.add_paragraph("");
237 p.add_run("Single ")
238 .underline_style(rdocx::UnderlineStyle::Single);
239 p.add_run("Double ")
240 .underline_style(rdocx::UnderlineStyle::Double);
241 p.add_run("Thick ")
242 .underline_style(rdocx::UnderlineStyle::Thick);
243 p.add_run("Dotted ")
244 .underline_style(rdocx::UnderlineStyle::Dotted);
245 p.add_run("Dash ")
246 .underline_style(rdocx::UnderlineStyle::Dash);
247 p.add_run("Wave ")
248 .underline_style(rdocx::UnderlineStyle::Wave);
249 }
250
251 // ── SECTION 2: PARAGRAPH FORMATTING ──
252 doc.add_paragraph("").page_break_before(true);
253 doc.add_paragraph("2. Paragraph Formatting")
254 .style("Heading1");
255
256 doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
257 doc.add_paragraph("Paragraph with light green shading")
258 .shading("E2EFDA");
259 doc.add_paragraph("Paragraph with bottom border")
260 .border_bottom(BorderStyle::Single, 6, "2E75B6");
261 doc.add_paragraph("Paragraph with all borders (red)")
262 .border_all(BorderStyle::Single, 4, "FF0000");
263
264 doc.add_paragraph("2.2 Indentation").style("Heading2");
265 doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
266 .indent_left(Length::inches(1.0))
267 .hanging_indent(Length::inches(0.5));
268 doc.add_paragraph("0.5-inch first-line indent on this paragraph")
269 .first_line_indent(Length::inches(0.5));
270 doc.add_paragraph("Both left and right indent (0.75 inches each)")
271 .indent_left(Length::inches(0.75))
272 .indent_right(Length::inches(0.75));
273
274 doc.add_paragraph("2.3 Spacing & Line Height")
275 .style("Heading2");
276 doc.add_paragraph("Extra space before (24pt) and after (12pt)")
277 .space_before(Length::pt(24.0))
278 .space_after(Length::pt(12.0));
279 doc.add_paragraph(
280 "Double line spacing paragraph. This text should have extra vertical space between \
281 lines to demonstrate line_spacing_multiple(2.0).",
282 )
283 .line_spacing_multiple(2.0);
284 doc.add_paragraph("Exact 20pt line spacing (fixed height).")
285 .line_spacing(20.0);
286
287 doc.add_paragraph("2.4 Pagination Controls")
288 .style("Heading2");
289 doc.add_paragraph("keep_with_next — this paragraph stays with the next")
290 .keep_with_next(true);
291 doc.add_paragraph("(This paragraph was kept with the one above.)");
292 doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
293 .keep_together(true);
294 doc.add_paragraph("widow_control — prevents widow/orphan lines")
295 .widow_control(true);
296
297 // ── SECTION 3: LISTS ──
298 doc.add_paragraph("").page_break_before(true);
299 doc.add_paragraph("3. Lists").style("Heading1");
300
301 doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
302 doc.add_bullet_list_item("First item", 0);
303 doc.add_bullet_list_item("Second item", 0);
304 doc.add_bullet_list_item("Nested level 1", 1);
305 doc.add_bullet_list_item("Nested level 2", 2);
306 doc.add_bullet_list_item("Back to level 1", 1);
307 doc.add_bullet_list_item("Third item", 0);
308
309 doc.add_paragraph("");
310
311 doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
312 doc.add_numbered_list_item("First numbered item", 0);
313 doc.add_numbered_list_item("Second numbered item", 0);
314 doc.add_numbered_list_item("Sub-item A", 1);
315 doc.add_numbered_list_item("Sub-item B", 1);
316 doc.add_numbered_list_item("Third numbered item", 0);
317
318 // ── SECTION 4: TAB STOPS ──
319 doc.add_paragraph("");
320 doc.add_paragraph("4. Tab Stops").style("Heading1");
321
322 doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
323 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
324 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
325 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
326 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
327 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
328
329 doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
330 doc.add_paragraph("Item\t\tPrice")
331 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
332 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
333 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
334 doc.add_paragraph("Widget\t\t$19.99")
335 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
336 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
337 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
338 doc.add_paragraph("Gadget\t\t$249.50")
339 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
340 .add_tab_stop_with_leader(
341 TabAlignment::Right,
342 Length::inches(4.0),
343 TabLeader::Underscore,
344 )
345 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
346
347 // ── SECTION 5: TABLES ──
348 doc.add_paragraph("").page_break_before(true);
349 doc.add_paragraph("5. Tables").style("Heading1");
350
351 doc.add_paragraph("5.1 Basic Table").style("Heading2");
352 {
353 let mut tbl = doc
354 .add_table(4, 3)
355 .borders(BorderStyle::Single, 4, "000000");
356 for col in 0..3 {
357 tbl.cell(0, col).unwrap().shading("2E75B6");
358 }
359 tbl.cell(0, 0).unwrap().set_text("Name");
360 tbl.cell(0, 1).unwrap().set_text("Role");
361 tbl.cell(0, 2).unwrap().set_text("Location");
362 tbl.cell(1, 0).unwrap().set_text("Walter White");
363 tbl.cell(1, 1).unwrap().set_text("CEO");
364 tbl.cell(1, 2).unwrap().set_text("Albuquerque");
365 tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
366 tbl.cell(2, 1).unwrap().set_text("CTO");
367 tbl.cell(2, 2).unwrap().set_text("Remote");
368 tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
369 tbl.cell(3, 1).unwrap().set_text("Security");
370 tbl.cell(3, 2).unwrap().set_text("Washington");
371 }
372
373 doc.add_paragraph("");
374
375 doc.add_paragraph("5.2 Column Span & Cell Shading")
376 .style("Heading2");
377 {
378 let mut tbl = doc
379 .add_table(3, 4)
380 .borders(BorderStyle::Single, 4, "000000")
381 .width_pct(100.0);
382 tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
383 tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
384 tbl.cell(1, 0).unwrap().set_text("Region");
385 tbl.cell(1, 0).unwrap().shading("D6E4F0");
386 tbl.cell(1, 1).unwrap().set_text("Q1");
387 tbl.cell(1, 1).unwrap().shading("D6E4F0");
388 tbl.cell(1, 2).unwrap().set_text("Q2");
389 tbl.cell(1, 2).unwrap().shading("D6E4F0");
390 tbl.cell(1, 3).unwrap().set_text("Total");
391 tbl.cell(1, 3).unwrap().shading("D6E4F0");
392 tbl.cell(2, 0).unwrap().set_text("Americas");
393 tbl.cell(2, 1).unwrap().set_text("$2.4M");
394 tbl.cell(2, 2).unwrap().set_text("$2.7M");
395 tbl.cell(2, 3).unwrap().set_text("$5.1M");
396 }
397
398 doc.add_paragraph("");
399
400 doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
401 {
402 let mut tbl = doc
403 .add_table(4, 3)
404 .borders(BorderStyle::Single, 4, "000000");
405 tbl.cell(0, 0).unwrap().set_text("Category");
406 tbl.cell(0, 0).unwrap().shading("E2EFDA");
407 tbl.cell(0, 1).unwrap().set_text("Item");
408 tbl.cell(0, 1).unwrap().shading("E2EFDA");
409 tbl.cell(0, 2).unwrap().set_text("Price");
410 tbl.cell(0, 2).unwrap().shading("E2EFDA");
411 tbl.cell(1, 0).unwrap().set_text("Hardware");
412 tbl.cell(1, 0).unwrap().v_merge_restart();
413 tbl.cell(1, 1).unwrap().set_text("Laptop");
414 tbl.cell(1, 2).unwrap().set_text("$1,200");
415 tbl.cell(2, 0).unwrap().v_merge_continue();
416 tbl.cell(2, 1).unwrap().set_text("Monitor");
417 tbl.cell(2, 2).unwrap().set_text("$450");
418 tbl.cell(3, 0).unwrap().set_text("Software");
419 tbl.cell(3, 1).unwrap().set_text("IDE License");
420 tbl.cell(3, 2).unwrap().set_text("$200/yr");
421 }
422
423 doc.add_paragraph("");
424
425 doc.add_paragraph("5.4 Nested Table").style("Heading2");
426 {
427 let mut tbl = doc
428 .add_table(2, 2)
429 .borders(BorderStyle::Single, 6, "2E75B6");
430 tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
431 tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
432 tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
433 {
434 let mut cell = tbl.cell(1, 1).unwrap();
435 cell.set_text("Nested table below:");
436 let mut nested = cell
437 .add_table(2, 2)
438 .borders(BorderStyle::Single, 2, "FF6600");
439 nested.cell(0, 0).unwrap().set_text("A");
440 nested.cell(0, 1).unwrap().set_text("B");
441 nested.cell(1, 0).unwrap().set_text("C");
442 nested.cell(1, 1).unwrap().set_text("D");
443 }
444 }
445
446 doc.add_paragraph("");
447
448 doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
449 .style("Heading2");
450 {
451 let mut tbl = doc
452 .add_table(2, 3)
453 .borders(BorderStyle::Single, 4, "666666");
454 tbl.row(0).unwrap().height(Length::pt(50.0)).header();
455 tbl.cell(0, 0).unwrap().set_text("Top");
456 tbl.cell(0, 0)
457 .unwrap()
458 .vertical_alignment(VerticalAlignment::Top)
459 .shading("FFF2CC");
460 tbl.cell(0, 1).unwrap().set_text("Center");
461 tbl.cell(0, 1)
462 .unwrap()
463 .vertical_alignment(VerticalAlignment::Center)
464 .shading("D9E2F3");
465 tbl.cell(0, 2).unwrap().set_text("Bottom");
466 tbl.cell(0, 2)
467 .unwrap()
468 .vertical_alignment(VerticalAlignment::Bottom)
469 .shading("E2EFDA");
470 tbl.row(1).unwrap().cant_split();
471 tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
472 tbl.cell(1, 0).unwrap().no_wrap();
473 tbl.cell(1, 1).unwrap().set_text("Fixed width");
474 tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
475 tbl.cell(1, 2).unwrap().set_text("Normal");
476 }
477
478 // ── SECTION 6: IMAGES ──
479 doc.add_paragraph("").page_break_before(true);
480 doc.add_paragraph("6. Images").style("Heading1");
481
482 doc.add_paragraph("6.1 Inline Image").style("Heading2");
483 doc.add_paragraph("A blue gradient image below:");
484 let img = create_sample_png(200, 50, [0, 80, 200]);
485 doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
486
487 doc.add_paragraph("");
488 doc.add_paragraph("6.2 Header Image").style("Heading2");
489 let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
490 doc.set_header_image(
491 &hdr_img,
492 "header_logo.png",
493 Length::inches(2.0),
494 Length::inches(0.2),
495 );
496 doc.add_paragraph("The header now contains an inline image (check the top of this page).");
497
498 doc.add_paragraph("");
499 doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
500
501 // ── SECTION 7: CONTENT MANIPULATION ──
502 doc.add_paragraph("").page_break_before(true);
503 doc.add_paragraph("7. Content Manipulation")
504 .style("Heading1");
505
506 doc.add_paragraph("7.1 Placeholder Replacement")
507 .style("Heading2");
508 doc.add_paragraph("Customer: {{customer}}");
509 doc.add_paragraph("Date: {{date}}");
510 doc.add_paragraph("Reference: {{ref_number}}");
511 {
512 let mut tbl = doc
513 .add_table(2, 2)
514 .borders(BorderStyle::Single, 4, "000000");
515 tbl.cell(0, 0).unwrap().set_text("Project");
516 tbl.cell(0, 0).unwrap().shading("D6E4F0");
517 tbl.cell(0, 1).unwrap().set_text("{{project}}");
518 tbl.cell(1, 0).unwrap().set_text("Status");
519 tbl.cell(1, 0).unwrap().shading("D6E4F0");
520 tbl.cell(1, 1).unwrap().set_text("{{status}}");
521 }
522
523 let mut replacements = HashMap::new();
524 replacements.insert("{{customer}}", "Tensorbee Inc.");
525 replacements.insert("{{date}}", "February 22, 2026");
526 replacements.insert("{{ref_number}}", "TB-2026-001");
527 replacements.insert("{{project}}", "Infrastructure Upgrade");
528 replacements.insert("{{status}}", "In Progress");
529 let n = doc.replace_all(&replacements);
530 doc.add_paragraph(&format!("({n} placeholders replaced above)"));
531
532 doc.add_paragraph("");
533
534 doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
535 doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
536 let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
537 doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
538
539 doc.add_paragraph("");
540
541 doc.add_paragraph("7.3 Content Insertion").style("Heading2");
542 doc.add_paragraph("Section A: First content.");
543 doc.add_paragraph("Section C: Third content.");
544 if let Some(idx) = doc.find_content_index("Section C") {
545 doc.insert_paragraph(
546 idx,
547 "Section B: Inserted between A and C via find_content_index().",
548 );
549 }
550
551 // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
552 doc.add_paragraph("").section_break(SectionBreak::NextPage);
553 doc.add_paragraph("8. Section Breaks & Orientation")
554 .style("Heading1");
555 doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
556
557 {
558 let mut tbl = doc
559 .add_table(3, 7)
560 .borders(BorderStyle::Single, 4, "2E75B6");
561 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
562 for (c, h) in headers.iter().enumerate() {
563 tbl.cell(0, c).unwrap().set_text(h);
564 tbl.cell(0, c).unwrap().shading("2E75B6");
565 }
566 let data = [
567 [
568 "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
569 ],
570 [
571 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
572 ],
573 ];
574 for (r, row) in data.iter().enumerate() {
575 for (c, v) in row.iter().enumerate() {
576 tbl.cell(r + 1, c).unwrap().set_text(v);
577 }
578 }
579 }
580
581 doc.add_paragraph("")
582 .section_break(SectionBreak::NextPage)
583 .section_landscape();
584
585 // ── SECTION 9: CUSTOM STYLES ──
586 doc.add_paragraph("9. Custom Styles").style("Heading1");
587 doc.add_style(
588 StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
589 .based_on("Normal")
590 .paragraph_properties({
591 let mut ppr = rdocx_oxml::properties::CT_PPr::default();
592 ppr.shading = Some(rdocx_oxml::properties::CT_Shd {
593 val: "clear".to_string(),
594 color: None,
595 fill: Some("FFF2CC".to_string()),
596 });
597 ppr
598 })
599 .run_properties({
600 let mut rpr = rdocx_oxml::properties::CT_RPr::default();
601 rpr.bold = Some(true);
602 rpr.color = Some("C45911".to_string());
603 rpr
604 }),
605 );
606 doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
607 .style("CustomHighlight");
608
609 doc.add_paragraph("");
610
611 // ── SECTION 10: DOCUMENT INTELLIGENCE ──
612 doc.add_paragraph("10. Document Intelligence API")
613 .style("Heading1");
614
615 let wc = doc.word_count();
616 let hc = doc.headings().len();
617 let ic = doc.images().len();
618 let lc = doc.links().len();
619 doc.add_paragraph(&format!("Word count: {wc}"));
620 doc.add_paragraph(&format!("Heading count: {hc}"));
621 doc.add_paragraph(&format!("Image count: {ic}"));
622 doc.add_paragraph(&format!("Link count: {lc}"));
623
624 let outline = doc.document_outline();
625 doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
626
627 let issues = doc.audit_accessibility();
628 doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
629 for issue in &issues {
630 doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
631 }
632
633 // ── SECTION 11: DOCUMENT MERGING ──
634 doc.add_paragraph("");
635 doc.add_paragraph("11. Document Merging").style("Heading1");
636 {
637 let mut other = Document::new();
638 other.add_paragraph("This paragraph was merged from another document using append().");
639 doc.append(&other);
640 }
641
642 doc.add_paragraph("");
643
644 // ── SUMMARY ──
645 doc.add_paragraph("Summary of Demonstrated Features")
646 .style("Heading1");
647 let features = [
648 "Page setup: size, margins, header/footer distance, gutter",
649 "Document metadata: title, author, subject, keywords",
650 "Headers and footers: text, images, different first page",
651 "Background images (full-page behind text)",
652 "Table of Contents generation with bookmarks",
653 "Text formatting: bold, italic, underline styles, strike, color, size, font",
654 "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
655 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
656 "Pagination controls: keep-with-next, keep-together, widow control",
657 "Bullet and numbered lists with nesting",
658 "Tab stops with dot/underscore/hyphen leaders",
659 "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
660 "Row properties: header rows, exact height, cant-split",
661 "Cell properties: width, no-wrap, vertical alignment",
662 "Inline images and header images",
663 "Placeholder replacement (single and batch)",
664 "Regex-based find and replace",
665 "Content insertion at specific positions",
666 "Section breaks with mixed portrait/landscape",
667 "Custom paragraph and character styles",
668 "Document intelligence: word count, headings, outline, images, links",
669 "Accessibility audit",
670 "Document merging (append)",
671 "Export: DOCX, PDF, HTML, Markdown",
672 ];
673 for f in &features {
674 doc.add_bullet_list_item(f, 0);
675 }
676 doc.add_paragraph("");
677 doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
678 .alignment(Alignment::Center)
679 .shading("E2EFDA")
680 .border_all(BorderStyle::Single, 2, "00AA00");
681
682 doc
683}
684
685// =============================================================================
686// 2. PROPOSAL DOCUMENT — Deep navy + gold scheme
687// =============================================================================
688fn generate_proposal(_samples_dir: &Path) -> Document {
689 let mut doc = Document::new();
690
691 // Colors: Navy #1B2A4A, Gold #C5922E, Light #F4F1EB
692 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
693 doc.set_margins(
694 Length::inches(1.0),
695 Length::inches(1.0),
696 Length::inches(1.0),
697 Length::inches(1.0),
698 );
699 doc.set_title("AI Platform Modernization Proposal");
700 doc.set_author("Walter White");
701 doc.set_subject("Technology Proposal");
702 doc.set_keywords("proposal, AI, modernization, Tensorbee");
703
704 // Banner header
705 let logo_img = create_logo_png(220, 48);
706 let banner = build_header_banner_xml(
707 "rId1",
708 &BannerOpts {
709 bg_color: "1B2A4A",
710 banner_width: 7772400,
711 banner_height: 914400,
712 logo_width: 2011680,
713 logo_height: 438912,
714 logo_x_offset: 295125,
715 logo_y_offset: 237744,
716 },
717 );
718 doc.set_raw_header_with_images(
719 banner,
720 &[("rId1", &logo_img, "logo.png")],
721 rdocx_oxml::header_footer::HdrFtrType::Default,
722 );
723 doc.set_different_first_page(true);
724 let cover_banner = build_header_banner_xml(
725 "rId1",
726 &BannerOpts {
727 bg_color: "C5922E",
728 banner_width: 7772400,
729 banner_height: 914400,
730 logo_width: 2011680,
731 logo_height: 438912,
732 logo_x_offset: 295125,
733 logo_y_offset: 237744,
734 },
735 );
736 doc.set_raw_header_with_images(
737 cover_banner,
738 &[("rId1", &logo_img, "logo.png")],
739 rdocx_oxml::header_footer::HdrFtrType::First,
740 );
741 doc.set_margins(
742 Length::twips(2292),
743 Length::twips(1440),
744 Length::twips(1440),
745 Length::twips(1440),
746 );
747 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
748 doc.set_footer("Tensorbee — Confidential");
749
750 // Custom styles
751 doc.add_style(
752 StyleBuilder::paragraph("ProposalTitle", "Proposal Title")
753 .based_on("Normal")
754 .run_properties({
755 let mut rpr = rdocx_oxml::properties::CT_RPr::default();
756 rpr.bold = Some(true);
757 rpr.font_ascii = Some("Georgia".to_string());
758 rpr.font_hansi = Some("Georgia".to_string());
759 rpr.sz = Some(rdocx_oxml::HalfPoint(56)); // half-points → 28pt
760 rpr.color = Some("1B2A4A".to_string());
761 rpr
762 }),
763 );
764
765 // ── Cover Page ──
766 for _ in 0..4 {
767 doc.add_paragraph("");
768 }
769 doc.add_paragraph("AI Platform Modernization")
770 .style("ProposalTitle")
771 .alignment(Alignment::Center);
772 doc.add_paragraph("")
773 .alignment(Alignment::Center)
774 .border_bottom(BorderStyle::Single, 8, "C5922E");
775 {
776 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
777 p.add_run("Prepared for Global Financial Corp.")
778 .size(14.0)
779 .color("666666")
780 .italic(true);
781 }
782 {
783 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
784 p.add_run("Prepared by: Walter White, CEO — Tensorbee")
785 .size(12.0)
786 .color("888888");
787 }
788 {
789 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
790 p.add_run("February 22, 2026").size(12.0).color("888888");
791 }
792
793 // ── TOC ──
794 doc.add_paragraph("").page_break_before(true);
795 doc.add_paragraph("Table of Contents").style("Heading1");
796 // We'll add a manual-style TOC since the insert_toc goes at a specific position
797 doc.insert_toc(doc.content_count(), 2);
798
799 // ── Executive Summary ──
800 doc.add_paragraph("").page_break_before(true);
801 doc.add_paragraph("Executive Summary").style("Heading1");
802 doc.add_paragraph(
803 "Tensorbee proposes a comprehensive modernization of Global Financial Corp's AI platform, \
804 transitioning from legacy batch-processing systems to a real-time inference architecture. \
805 This transformation will reduce model deployment time from weeks to hours, improve \
806 prediction accuracy by an estimated 23%, and deliver $4.2M in annual operational savings.",
807 )
808 .first_line_indent(Length::inches(0.3));
809 doc.add_paragraph(
810 "The project spans three phases over 18 months, with a total investment of $2.8M. \
811 Tensorbee brings deep expertise in ML infrastructure, having successfully completed \
812 similar transformations for three Fortune 500 financial institutions.",
813 )
814 .first_line_indent(Length::inches(0.3));
815
816 // Key metrics table
817 doc.add_paragraph("");
818 {
819 let mut tbl = doc
820 .add_table(5, 2)
821 .borders(BorderStyle::Single, 4, "1B2A4A")
822 .width_pct(80.0);
823 tbl.cell(0, 0).unwrap().set_text("Key Metric");
824 tbl.cell(0, 0).unwrap().shading("1B2A4A");
825 tbl.cell(0, 1).unwrap().set_text("Value");
826 tbl.cell(0, 1).unwrap().shading("1B2A4A");
827 let rows = [
828 ("Total Investment", "$2.8M"),
829 ("Annual Savings", "$4.2M"),
830 ("ROI (Year 1)", "150%"),
831 ("Timeline", "18 months"),
832 ];
833 for (i, (k, v)) in rows.iter().enumerate() {
834 tbl.cell(i + 1, 0).unwrap().set_text(k);
835 if i % 2 == 0 {
836 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
837 }
838 tbl.cell(i + 1, 1).unwrap().set_text(v);
839 if i % 2 == 0 {
840 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
841 }
842 }
843 }
844
845 // ── Problem Statement ──
846 doc.add_paragraph("").page_break_before(true);
847 doc.add_paragraph("Problem Statement").style("Heading1");
848 doc.add_paragraph(
849 "Global Financial Corp's current AI infrastructure faces three critical challenges:",
850 );
851 doc.add_numbered_list_item("Deployment Latency — New models take 3-4 weeks to deploy to production, compared to the industry benchmark of 2-3 days.", 0);
852 doc.add_numbered_list_item("Scalability Constraints — The batch processing architecture cannot handle real-time inference demands during peak trading hours.", 0);
853 doc.add_numbered_list_item("Technical Debt — Legacy Python 2.7 codebases and manual deployment scripts create reliability risks and slow iteration speed.", 0);
854
855 // ── Proposed Solution ──
856 doc.add_paragraph("").page_break_before(true);
857 doc.add_paragraph("Proposed Solution").style("Heading1");
858
859 doc.add_paragraph("Phase 1: Foundation (Months 1-6)")
860 .style("Heading2");
861 doc.add_bullet_list_item("Deploy Kubernetes-based ML serving infrastructure", 0);
862 doc.add_bullet_list_item("Implement CI/CD pipeline for model deployment", 0);
863 doc.add_bullet_list_item("Migrate top 5 production models to new platform", 0);
864
865 doc.add_paragraph("Phase 2: Expansion (Months 7-12)")
866 .style("Heading2");
867 doc.add_bullet_list_item(
868 "Real-time feature engineering pipeline (Apache Kafka + Flink)",
869 0,
870 );
871 doc.add_bullet_list_item("A/B testing framework for model variants", 0);
872 doc.add_bullet_list_item("Automated model monitoring and drift detection", 0);
873
874 doc.add_paragraph("Phase 3: Optimization (Months 13-18)")
875 .style("Heading2");
876 doc.add_bullet_list_item("GPU inference optimization (TensorRT/ONNX Runtime)", 0);
877 doc.add_bullet_list_item("Multi-region deployment for latency reduction", 0);
878 doc.add_bullet_list_item(
879 "Self-service model deployment portal for data science teams",
880 0,
881 );
882
883 // ── Budget ──
884 doc.add_paragraph("Budget Breakdown").style("Heading1");
885 {
886 let mut tbl = doc
887 .add_table(6, 3)
888 .borders(BorderStyle::Single, 4, "1B2A4A")
889 .width_pct(100.0);
890 tbl.cell(0, 0).unwrap().set_text("Category");
891 tbl.cell(0, 0).unwrap().shading("1B2A4A");
892 tbl.cell(0, 1).unwrap().set_text("Cost");
893 tbl.cell(0, 1).unwrap().shading("1B2A4A");
894 tbl.cell(0, 2).unwrap().set_text("Notes");
895 tbl.cell(0, 2).unwrap().shading("1B2A4A");
896 let items = [
897 (
898 "Infrastructure",
899 "$450,000",
900 "Cloud compute + GPU instances",
901 ),
902 ("Engineering Services", "$1,600,000", "12 FTE x 18 months"),
903 (
904 "Software Licenses",
905 "$350,000",
906 "Kafka, monitoring, CI/CD tools",
907 ),
908 (
909 "Training & Enablement",
910 "$200,000",
911 "Team training, documentation",
912 ),
913 ("Contingency (10%)", "$200,000", "Risk buffer"),
914 ];
915 for (i, (cat, cost, note)) in items.iter().enumerate() {
916 tbl.cell(i + 1, 0).unwrap().set_text(cat);
917 tbl.cell(i + 1, 1).unwrap().set_text(cost);
918 tbl.cell(i + 1, 2).unwrap().set_text(note);
919 if i % 2 == 0 {
920 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
921 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
922 tbl.cell(i + 1, 2).unwrap().shading("F4F1EB");
923 }
924 }
925 }
926
927 // ── Closing ──
928 doc.add_paragraph("");
929 doc.add_paragraph("Next Steps").style("Heading1");
930 doc.add_numbered_list_item(
931 "Schedule technical deep-dive with GFC engineering team (Week 1)",
932 0,
933 );
934 doc.add_numbered_list_item("Finalize scope and sign SOW (Week 2-3)", 0);
935 doc.add_numbered_list_item("Kick off Phase 1 with joint planning session (Week 4)", 0);
936
937 doc.add_paragraph("");
938 {
939 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
940 p.add_run("Walter White")
941 .bold(true)
942 .size(14.0)
943 .color("1B2A4A");
944 }
945 {
946 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
947 p.add_run("CEO, Tensorbee | walter@tensorbee.com")
948 .size(10.0)
949 .color("888888");
950 }
951
952 doc
953}
954
955// =============================================================================
956// 3. QUOTE / BILL OF MATERIALS — Teal + orange scheme
957// =============================================================================
958fn generate_quote(_samples_dir: &Path) -> Document {
959 let mut doc = Document::new();
960
961 // Colors: Teal #008B8B, Dark #1A3C3C, Orange #E07020, Light #F0F8F8
962 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
963 doc.set_margins(
964 Length::inches(0.75),
965 Length::inches(0.75),
966 Length::inches(0.75),
967 Length::inches(0.75),
968 );
969 doc.set_title("Quotation QT-2026-0147");
970 doc.set_author("Walter White");
971 doc.set_keywords("quote, BOM, Tensorbee");
972
973 doc.set_header("Tensorbee — Quotation");
974 doc.set_footer("QT-2026-0147 | Page");
975
976 // ── Header Block ──
977 {
978 let mut p = doc.add_paragraph("").alignment(Alignment::Left);
979 p.add_run("TENSORBEE")
980 .bold(true)
981 .size(28.0)
982 .color("008B8B")
983 .font("Helvetica");
984 }
985 doc.add_paragraph("123 Innovation Drive, Suite 400")
986 .alignment(Alignment::Left);
987 doc.add_paragraph("San Francisco, CA 94105 | +1 (415) 555-0199")
988 .alignment(Alignment::Left);
989 doc.add_paragraph("")
990 .border_bottom(BorderStyle::Single, 8, "008B8B");
991
992 // Quote meta
993 doc.add_paragraph("");
994 {
995 let mut tbl = doc.add_table(4, 4).width_pct(100.0);
996 tbl.cell(0, 0).unwrap().set_text("QUOTATION");
997 tbl.cell(0, 0).unwrap().shading("008B8B").grid_span(2);
998 tbl.cell(0, 2).unwrap().set_text("DATE");
999 tbl.cell(0, 2).unwrap().shading("008B8B");
1000 tbl.cell(0, 3).unwrap().set_text("VALID UNTIL");
1001 tbl.cell(0, 3).unwrap().shading("008B8B");
1002 tbl.cell(1, 0).unwrap().set_text("Quote #:");
1003 tbl.cell(1, 1).unwrap().set_text("QT-2026-0147");
1004 tbl.cell(1, 2).unwrap().set_text("Feb 22, 2026");
1005 tbl.cell(1, 3).unwrap().set_text("Mar 22, 2026");
1006 tbl.cell(2, 0).unwrap().set_text("Prepared by:");
1007 tbl.cell(2, 1).unwrap().set_text("Walter White");
1008 tbl.cell(2, 2).unwrap().set_text("Payment:");
1009 tbl.cell(2, 3).unwrap().set_text("Net 30");
1010 tbl.cell(3, 0).unwrap().set_text("Customer:");
1011 tbl.cell(3, 1).unwrap().set_text("Meridian Dynamics LLC");
1012 tbl.cell(3, 2).unwrap().set_text("Currency:");
1013 tbl.cell(3, 3).unwrap().set_text("USD");
1014 }
1015
1016 doc.add_paragraph("");
1017
1018 // ── Bill of Materials ──
1019 {
1020 let mut p = doc.add_paragraph("");
1021 p.add_run("Bill of Materials")
1022 .bold(true)
1023 .size(16.0)
1024 .color("1A3C3C");
1025 }
1026 doc.add_paragraph("");
1027
1028 {
1029 let mut tbl = doc
1030 .add_table(10, 6)
1031 .borders(BorderStyle::Single, 2, "008B8B")
1032 .width_pct(100.0)
1033 .layout_fixed();
1034 // Headers
1035 let hdrs = [
1036 "#",
1037 "Part Number",
1038 "Description",
1039 "Qty",
1040 "Unit Price",
1041 "Total",
1042 ];
1043 for (c, h) in hdrs.iter().enumerate() {
1044 tbl.cell(0, c).unwrap().set_text(h);
1045 tbl.cell(0, c).unwrap().shading("008B8B");
1046 }
1047
1048 let items: Vec<(&str, &str, &str, &str, &str)> = vec![
1049 (
1050 "TB-GPU-A100",
1051 "NVIDIA A100 80GB GPU",
1052 "4",
1053 "$12,500.00",
1054 "$50,000.00",
1055 ),
1056 (
1057 "TB-SRV-R750",
1058 "Dell R750xa Server Chassis",
1059 "2",
1060 "$8,200.00",
1061 "$16,400.00",
1062 ),
1063 (
1064 "TB-RAM-256G",
1065 "256GB DDR5 ECC Memory Module",
1066 "8",
1067 "$890.00",
1068 "$7,120.00",
1069 ),
1070 (
1071 "TB-SSD-3840",
1072 "3.84TB NVMe U.2 SSD",
1073 "8",
1074 "$1,150.00",
1075 "$9,200.00",
1076 ),
1077 (
1078 "TB-NET-CX7",
1079 "ConnectX-7 200GbE NIC",
1080 "4",
1081 "$1,800.00",
1082 "$7,200.00",
1083 ),
1084 (
1085 "TB-SW-48P",
1086 "48-Port 100GbE Switch",
1087 "1",
1088 "$22,000.00",
1089 "$22,000.00",
1090 ),
1091 (
1092 "TB-CAB-RACK",
1093 "42U Server Rack + PDU",
1094 "1",
1095 "$4,500.00",
1096 "$4,500.00",
1097 ),
1098 (
1099 "TB-SVC-INST",
1100 "Installation & Configuration",
1101 "1",
1102 "$8,500.00",
1103 "$8,500.00",
1104 ),
1105 (
1106 "TB-SVC-SUPP",
1107 "3-Year Premium Support",
1108 "1",
1109 "$15,000.00",
1110 "$15,000.00",
1111 ),
1112 ];
1113
1114 for (i, (pn, desc, qty, unit, total)) in items.iter().enumerate() {
1115 let row = i + 1;
1116 tbl.cell(row, 0).unwrap().set_text(&format!("{}", i + 1));
1117 tbl.cell(row, 1).unwrap().set_text(pn);
1118 tbl.cell(row, 2).unwrap().set_text(desc);
1119 tbl.cell(row, 3).unwrap().set_text(qty);
1120 tbl.cell(row, 4).unwrap().set_text(unit);
1121 tbl.cell(row, 5).unwrap().set_text(total);
1122 if i % 2 == 0 {
1123 for c in 0..6 {
1124 tbl.cell(row, c).unwrap().shading("F0F8F8");
1125 }
1126 }
1127 }
1128 }
1129
1130 doc.add_paragraph("");
1131
1132 // ── Totals ──
1133 {
1134 let mut tbl = doc
1135 .add_table(4, 2)
1136 .borders(BorderStyle::Single, 2, "008B8B")
1137 .width(Length::inches(3.5))
1138 .alignment(Alignment::Right);
1139 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1140 tbl.cell(0, 1).unwrap().set_text("$139,920.00");
1141 tbl.cell(1, 0).unwrap().set_text("Shipping & Handling");
1142 tbl.cell(1, 1).unwrap().set_text("$2,500.00");
1143 tbl.cell(2, 0).unwrap().set_text("Tax (8.625%)");
1144 tbl.cell(2, 1).unwrap().set_text("$12,068.10");
1145 tbl.cell(3, 0).unwrap().set_text("TOTAL");
1146 tbl.cell(3, 0).unwrap().shading("E07020");
1147 tbl.cell(3, 1).unwrap().set_text("$154,488.10");
1148 tbl.cell(3, 1).unwrap().shading("E07020");
1149 }
1150
1151 doc.add_paragraph("");
1152
1153 // ── Terms & Conditions ──
1154 {
1155 let mut p = doc.add_paragraph("");
1156 p.add_run("Terms & Conditions")
1157 .bold(true)
1158 .size(14.0)
1159 .color("1A3C3C");
1160 }
1161 doc.add_numbered_list_item(
1162 "This quotation is valid for 30 calendar days from the date of issue.",
1163 0,
1164 );
1165 doc.add_numbered_list_item(
1166 "All prices are in USD and exclusive of applicable taxes unless stated.",
1167 0,
1168 );
1169 doc.add_numbered_list_item("Standard lead time is 4-6 weeks from PO receipt.", 0);
1170 doc.add_numbered_list_item(
1171 "Payment terms: Net 30 from invoice date. 2% discount for payment within 10 days.",
1172 0,
1173 );
1174 doc.add_numbered_list_item(
1175 "Warranty: 3-year manufacturer warranty on all hardware components.",
1176 0,
1177 );
1178 doc.add_numbered_list_item(
1179 "Returns subject to 15% restocking fee if initiated after 14 days.",
1180 0,
1181 );
1182
1183 doc.add_paragraph("");
1184 doc.add_paragraph("")
1185 .border_bottom(BorderStyle::Single, 4, "008B8B");
1186 doc.add_paragraph("");
1187 {
1188 let mut p = doc.add_paragraph("");
1189 p.add_run("Accepted by: ").bold(true);
1190 p.add_run("___________________________ Date: ___________");
1191 }
1192 {
1193 let mut p = doc.add_paragraph("");
1194 p.add_run("Print Name: ").bold(true);
1195 p.add_run("___________________________ Title: ___________");
1196 }
1197
1198 doc
1199}
1200
1201// =============================================================================
1202// 4. INVOICE — Crimson + charcoal scheme
1203// =============================================================================
1204fn generate_invoice(_samples_dir: &Path) -> Document {
1205 let mut doc = Document::new();
1206
1207 // Colors: Crimson #B22222, Charcoal #333333, Light #FAF0F0
1208 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1209 doc.set_margins(
1210 Length::inches(0.75),
1211 Length::inches(0.75),
1212 Length::inches(0.75),
1213 Length::inches(0.75),
1214 );
1215 doc.set_title("Invoice INV-2026-0392");
1216 doc.set_author("Walter White");
1217 doc.set_keywords("invoice, Tensorbee");
1218
1219 doc.set_footer("Tensorbee — Thank you for your business!");
1220
1221 // ── Company & Invoice Header ──
1222 {
1223 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1224 // Company name left, INVOICE right
1225 {
1226 let mut cell = tbl.cell(0, 0).unwrap();
1227 let mut p = cell.add_paragraph("");
1228 p.add_run("TENSORBEE")
1229 .bold(true)
1230 .size(32.0)
1231 .color("B22222")
1232 .font("Helvetica");
1233 }
1234 {
1235 let mut cell = tbl.cell(0, 1).unwrap();
1236 let mut p = cell.add_paragraph("");
1237 p.add_run("INVOICE").bold(true).size(32.0).color("333333");
1238 }
1239 tbl.cell(1, 0)
1240 .unwrap()
1241 .set_text("123 Innovation Drive, Suite 400");
1242 tbl.cell(1, 1).unwrap().set_text("Invoice #: INV-2026-0392");
1243 tbl.cell(2, 0).unwrap().set_text("San Francisco, CA 94105");
1244 tbl.cell(2, 1).unwrap().set_text("Date: February 22, 2026");
1245 tbl.cell(3, 0).unwrap().set_text("walter@tensorbee.com");
1246 tbl.cell(3, 1).unwrap().set_text("Due Date: March 24, 2026");
1247 }
1248
1249 doc.add_paragraph("")
1250 .border_bottom(BorderStyle::Thick, 8, "B22222");
1251 doc.add_paragraph("");
1252
1253 // ── Bill To / Ship To ──
1254 {
1255 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1256 tbl.cell(0, 0).unwrap().set_text("BILL TO");
1257 tbl.cell(0, 0).unwrap().shading("B22222");
1258 tbl.cell(0, 1).unwrap().set_text("SHIP TO");
1259 tbl.cell(0, 1).unwrap().shading("B22222");
1260 tbl.cell(1, 0).unwrap().set_text("Meridian Dynamics LLC");
1261 tbl.cell(1, 1).unwrap().set_text("Meridian Dynamics LLC");
1262 tbl.cell(2, 0).unwrap().set_text("456 Enterprise Blvd");
1263 tbl.cell(2, 1).unwrap().set_text("Attn: Server Room B");
1264 tbl.cell(3, 0).unwrap().set_text("Austin, TX 78701");
1265 tbl.cell(3, 1)
1266 .unwrap()
1267 .set_text("456 Enterprise Blvd, Austin, TX 78701");
1268 }
1269
1270 doc.add_paragraph("");
1271
1272 // ── Line Items ──
1273 {
1274 let mut tbl = doc
1275 .add_table(8, 5)
1276 .borders(BorderStyle::Single, 2, "B22222")
1277 .width_pct(100.0);
1278 let hdrs = ["Description", "Qty", "Unit Price", "Tax", "Amount"];
1279 for (c, h) in hdrs.iter().enumerate() {
1280 tbl.cell(0, c).unwrap().set_text(h);
1281 tbl.cell(0, c).unwrap().shading("B22222");
1282 }
1283 let items = [
1284 (
1285 "ML Infrastructure Setup — Phase 1",
1286 "1",
1287 "$45,000.00",
1288 "$3,881.25",
1289 "$48,881.25",
1290 ),
1291 (
1292 "Data Pipeline Development (160 hrs)",
1293 "160",
1294 "$225.00",
1295 "$3,105.00",
1296 "$39,105.00",
1297 ),
1298 (
1299 "GPU Cluster Configuration",
1300 "1",
1301 "$12,000.00",
1302 "$1,035.00",
1303 "$13,035.00",
1304 ),
1305 (
1306 "API Gateway Implementation",
1307 "1",
1308 "$18,500.00",
1309 "$1,595.63",
1310 "$20,095.63",
1311 ),
1312 (
1313 "Load Testing & QA (80 hrs)",
1314 "80",
1315 "$195.00",
1316 "$1,345.50",
1317 "$16,945.50",
1318 ),
1319 (
1320 "Documentation & Training",
1321 "1",
1322 "$8,000.00",
1323 "$690.00",
1324 "$8,690.00",
1325 ),
1326 (
1327 "Project Management (3 months)",
1328 "3",
1329 "$6,500.00",
1330 "$1,679.63",
1331 "$21,179.63",
1332 ),
1333 ];
1334 for (i, (desc, qty, unit, tax, amt)) in items.iter().enumerate() {
1335 let r = i + 1;
1336 tbl.cell(r, 0).unwrap().set_text(desc);
1337 tbl.cell(r, 1).unwrap().set_text(qty);
1338 tbl.cell(r, 2).unwrap().set_text(unit);
1339 tbl.cell(r, 3).unwrap().set_text(tax);
1340 tbl.cell(r, 4).unwrap().set_text(amt);
1341 if i % 2 == 0 {
1342 for c in 0..5 {
1343 tbl.cell(r, c).unwrap().shading("FAF0F0");
1344 }
1345 }
1346 }
1347 }
1348
1349 doc.add_paragraph("");
1350
1351 // ── Totals ──
1352 {
1353 let mut tbl = doc
1354 .add_table(4, 2)
1355 .borders(BorderStyle::Single, 2, "B22222")
1356 .width(Length::inches(3.0))
1357 .alignment(Alignment::Right);
1358 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1359 tbl.cell(0, 1).unwrap().set_text("$154,600.00");
1360 tbl.cell(1, 0).unwrap().set_text("Tax (8.625%)");
1361 tbl.cell(1, 1).unwrap().set_text("$13,334.25");
1362 tbl.cell(2, 0).unwrap().set_text("Discount (5%)");
1363 tbl.cell(2, 1).unwrap().set_text("-$7,730.00");
1364 tbl.cell(3, 0).unwrap().set_text("AMOUNT DUE");
1365 tbl.cell(3, 0).unwrap().shading("B22222");
1366 tbl.cell(3, 1).unwrap().set_text("$160,204.25");
1367 tbl.cell(3, 1).unwrap().shading("B22222");
1368 }
1369
1370 doc.add_paragraph("");
1371 doc.add_paragraph("");
1372
1373 // ── Payment Details ──
1374 {
1375 let mut p = doc.add_paragraph("");
1376 p.add_run("Payment Details")
1377 .bold(true)
1378 .size(14.0)
1379 .color("333333");
1380 }
1381 doc.add_paragraph("Bank: Silicon Valley Bank")
1382 .indent_left(Length::inches(0.3));
1383 doc.add_paragraph("Account: Tensorbee Inc. — 0847-2953-1120")
1384 .indent_left(Length::inches(0.3));
1385 doc.add_paragraph("Routing: 121140399")
1386 .indent_left(Length::inches(0.3));
1387 doc.add_paragraph("Swift: SVBKUS6S")
1388 .indent_left(Length::inches(0.3));
1389
1390 doc.add_paragraph("");
1391
1392 doc.add_paragraph("Please include invoice number INV-2026-0392 in the payment reference.")
1393 .shading("FAF0F0")
1394 .border_all(BorderStyle::Single, 2, "B22222");
1395
1396 doc
1397}
1398
1399// =============================================================================
1400// 5. REPORT — Forest green + earth tones, with images & hierarchical sections
1401// =============================================================================
1402fn generate_report(_samples_dir: &Path) -> Document {
1403 let mut doc = Document::new();
1404
1405 // Colors: Forest #2D5016, Sage #6B8E23, Earth #8B7355, Cream #FFFAF0
1406 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1407 doc.set_margins(
1408 Length::inches(1.0),
1409 Length::inches(1.0),
1410 Length::inches(1.0),
1411 Length::inches(1.0),
1412 );
1413 doc.set_title("Q4 2025 Environmental Impact Report");
1414 doc.set_author("Walter White");
1415 doc.set_subject("Quarterly Environmental Report");
1416 doc.set_keywords("environment, sustainability, report, Tensorbee");
1417
1418 doc.set_different_first_page(true);
1419 doc.set_first_page_header("");
1420 doc.set_header("Tensorbee — Q4 2025 Environmental Impact Report");
1421 doc.set_footer("CONFIDENTIAL — Page");
1422
1423 // ── Cover ──
1424 let cover_bg = create_sample_png(612, 792, [20, 50, 15]);
1425 doc.add_background_image(&cover_bg, "report_cover.png");
1426
1427 for _ in 0..5 {
1428 doc.add_paragraph("");
1429 }
1430 {
1431 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1432 p.add_run("Q4 2025")
1433 .bold(true)
1434 .size(48.0)
1435 .color("FFFFFF")
1436 .font("Georgia");
1437 }
1438 {
1439 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1440 p.add_run("Environmental Impact Report")
1441 .size(24.0)
1442 .color("90EE90")
1443 .font("Georgia")
1444 .italic(true);
1445 }
1446 doc.add_paragraph("");
1447 {
1448 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1449 p.add_run("Tensorbee — Sustainability Division")
1450 .size(14.0)
1451 .color("C0C0C0");
1452 }
1453 {
1454 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1455 p.add_run("Prepared by Walter White, Chief Sustainability Officer")
1456 .size(11.0)
1457 .color("AAAAAA");
1458 }
1459
1460 // ── TOC ──
1461 doc.add_paragraph("").page_break_before(true);
1462 doc.insert_toc(doc.content_count(), 3);
1463
1464 // ── Section 1: Executive Overview ──
1465 doc.add_paragraph("").page_break_before(true);
1466 doc.add_paragraph("Executive Overview").style("Heading1");
1467 doc.add_paragraph(
1468 "This report presents Tensorbee's environmental performance for Q4 2025. Our \
1469 sustainability initiatives have yielded a 34% reduction in carbon emissions compared \
1470 to Q4 2024, exceeding our target of 25%. Key achievements include the transition to \
1471 100% renewable energy in our primary data centers and the launch of our carbon offset \
1472 marketplace.",
1473 )
1474 .first_line_indent(Length::inches(0.3));
1475
1476 // Image: performance chart
1477 let chart_img = create_chart_png(400, 200);
1478 doc.add_paragraph("");
1479 doc.add_picture(
1480 &chart_img,
1481 "performance_chart.png",
1482 Length::inches(5.0),
1483 Length::inches(2.5),
1484 )
1485 .alignment(Alignment::Center);
1486 {
1487 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1488 p.add_run("Figure 1: Quarterly Carbon Emissions (tonnes CO2e)")
1489 .italic(true)
1490 .size(9.0)
1491 .color("666666");
1492 }
1493
1494 // ── Section 2: Energy Consumption ──
1495 doc.add_paragraph("").page_break_before(true);
1496 doc.add_paragraph("Energy Consumption").style("Heading1");
1497
1498 doc.add_paragraph("Data Center Operations")
1499 .style("Heading2");
1500 doc.add_paragraph(
1501 "Our three primary data centers consumed a combined 4.2 GWh in Q4 2025, \
1502 a 12% reduction from Q3 2025 driven by improved cooling efficiency and \
1503 server consolidation.",
1504 );
1505
1506 // Data table
1507 {
1508 let mut tbl = doc
1509 .add_table(5, 4)
1510 .borders(BorderStyle::Single, 4, "2D5016")
1511 .width_pct(100.0);
1512 let hdrs = ["Data Center", "Capacity (MW)", "Usage (GWh)", "PUE"];
1513 for (c, h) in hdrs.iter().enumerate() {
1514 tbl.cell(0, c).unwrap().set_text(h);
1515 tbl.cell(0, c).unwrap().shading("2D5016");
1516 }
1517 let rows = [
1518 ("San Francisco (Primary)", "3.2", "1.8", "1.12"),
1519 ("Dublin (EU)", "2.1", "1.2", "1.18"),
1520 ("Singapore (APAC)", "1.8", "1.2", "1.24"),
1521 ("TOTAL", "7.1", "4.2", "1.17 avg"),
1522 ];
1523 for (i, (dc, cap, use_, pue)) in rows.iter().enumerate() {
1524 tbl.cell(i + 1, 0).unwrap().set_text(dc);
1525 tbl.cell(i + 1, 1).unwrap().set_text(cap);
1526 tbl.cell(i + 1, 2).unwrap().set_text(use_);
1527 tbl.cell(i + 1, 3).unwrap().set_text(pue);
1528 if i == 3 {
1529 for c in 0..4 {
1530 tbl.cell(i + 1, c).unwrap().shading("E2EFDA");
1531 }
1532 }
1533 }
1534 }
1535
1536 doc.add_paragraph("");
1537
1538 doc.add_paragraph("Renewable Energy Mix").style("Heading2");
1539 doc.add_paragraph("Breakdown of energy sources across all facilities:");
1540
1541 doc.add_bullet_list_item("Solar PV: 42% (1.76 GWh)", 0);
1542 doc.add_bullet_list_item("Wind Power Purchase Agreements: 38% (1.60 GWh)", 0);
1543 doc.add_bullet_list_item("Hydroelectric: 12% (0.50 GWh)", 0);
1544 doc.add_bullet_list_item("Grid (non-renewable): 8% (0.34 GWh)", 0);
1545
1546 // ── Section 3: Water & Waste ──
1547 doc.add_paragraph("Water & Waste Management")
1548 .style("Heading1");
1549
1550 doc.add_paragraph("Water Usage").style("Heading2");
1551 doc.add_paragraph(
1552 "Total water consumption was 12.4 million gallons, a 15% reduction from Q3. \
1553 Our closed-loop cooling systems now recycle 78% of water used in cooling operations.",
1554 );
1555
1556 doc.add_paragraph("Waste Reduction").style("Heading2");
1557 doc.add_paragraph("E-waste management results for Q4:")
1558 .keep_with_next(true);
1559 {
1560 let mut tbl = doc
1561 .add_table(4, 3)
1562 .borders(BorderStyle::Single, 2, "6B8E23")
1563 .width_pct(80.0);
1564 tbl.cell(0, 0).unwrap().set_text("Category");
1565 tbl.cell(0, 0).unwrap().shading("6B8E23");
1566 tbl.cell(0, 1).unwrap().set_text("Weight (kg)");
1567 tbl.cell(0, 1).unwrap().shading("6B8E23");
1568 tbl.cell(0, 2).unwrap().set_text("Recycled %");
1569 tbl.cell(0, 2).unwrap().shading("6B8E23");
1570 let rows = [
1571 ("Server Hardware", "2,450", "94%"),
1572 ("Networking Equipment", "820", "91%"),
1573 ("Storage Media", "340", "99%"),
1574 ];
1575 for (i, (cat, wt, pct)) in rows.iter().enumerate() {
1576 tbl.cell(i + 1, 0).unwrap().set_text(cat);
1577 tbl.cell(i + 1, 1).unwrap().set_text(wt);
1578 tbl.cell(i + 1, 2).unwrap().set_text(pct);
1579 }
1580 }
1581
1582 // ── Section 4: Initiatives ──
1583 doc.add_paragraph("").page_break_before(true);
1584 doc.add_paragraph("Q1 2026 Initiatives").style("Heading1");
1585
1586 doc.add_paragraph("Planned Programs").style("Heading2");
1587 doc.add_numbered_list_item(
1588 "Deploy on-site battery storage at SF data center (2 MWh capacity)",
1589 0,
1590 );
1591 doc.add_numbered_list_item("Pilot immersion cooling in Dublin facility", 0);
1592 doc.add_numbered_list_item("Launch employee commute carbon offset program", 0);
1593 doc.add_numbered_list_item("Achieve ISO 14001 certification for Singapore facility", 0);
1594
1595 doc.add_paragraph("Investment Targets").style("Heading2");
1596 doc.add_paragraph("Sustainability CapEx allocation for FY2026:")
1597 .keep_with_next(true);
1598 {
1599 let mut tbl = doc
1600 .add_table(5, 2)
1601 .borders(BorderStyle::Single, 4, "2D5016");
1602 tbl.cell(0, 0).unwrap().set_text("Initiative");
1603 tbl.cell(0, 0).unwrap().shading("2D5016");
1604 tbl.cell(0, 1).unwrap().set_text("Budget");
1605 tbl.cell(0, 1).unwrap().shading("2D5016");
1606 let rows = [
1607 ("Battery Storage", "$1.2M"),
1608 ("Immersion Cooling Pilot", "$800K"),
1609 ("Solar Panel Expansion", "$2.1M"),
1610 ("Carbon Credits", "$500K"),
1611 ];
1612 for (i, (init, budget)) in rows.iter().enumerate() {
1613 tbl.cell(i + 1, 0).unwrap().set_text(init);
1614 tbl.cell(i + 1, 1).unwrap().set_text(budget);
1615 if i % 2 == 0 {
1616 tbl.cell(i + 1, 0).unwrap().shading("FFFAF0");
1617 tbl.cell(i + 1, 1).unwrap().shading("FFFAF0");
1618 }
1619 }
1620 }
1621
1622 // Image: sustainability roadmap
1623 let roadmap_img = create_sample_png(500, 100, [40, 80, 30]);
1624 doc.add_paragraph("");
1625 doc.add_picture(
1626 &roadmap_img,
1627 "roadmap.png",
1628 Length::inches(6.0),
1629 Length::inches(1.2),
1630 )
1631 .alignment(Alignment::Center);
1632 {
1633 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1634 p.add_run("Figure 2: 2026 Sustainability Roadmap")
1635 .italic(true)
1636 .size(9.0)
1637 .color("666666");
1638 }
1639
1640 doc.add_paragraph("");
1641 doc.add_paragraph(
1642 "For questions about this report, contact Walter White at sustainability@tensorbee.com.",
1643 )
1644 .shading("FFFAF0")
1645 .border_all(BorderStyle::Single, 2, "2D5016");
1646
1647 doc
1648}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 if index > content_count().
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
34fn generate_feature_showcase(path: &Path) {
35 let mut doc = Document::new();
36
37 // =========================================================================
38 // PAGE SETUP & METADATA
39 // =========================================================================
40 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
41 doc.set_margins(
42 Length::inches(1.0), // top
43 Length::inches(1.0), // right
44 Length::inches(1.0), // bottom
45 Length::inches(1.0), // left
46 );
47 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
48 doc.set_gutter(Length::twips(0));
49
50 doc.set_title("rdocx Feature Showcase");
51 doc.set_author("rdocx Sample Generator");
52 doc.set_subject("Comprehensive feature demonstration");
53 doc.set_keywords("rdocx, docx, rust, sample");
54
55 // Header & Footer
56 doc.set_header("rdocx Feature Showcase");
57 doc.set_footer("Generated by rdocx — Page");
58
59 // Different first page header
60 doc.set_different_first_page(true);
61 doc.set_first_page_header("rdocx");
62 doc.set_first_page_footer("Feature Showcase — Cover Page");
63
64 // =========================================================================
65 // PAGE 1: COVER PAGE — background image, run formatting
66 // =========================================================================
67 let bg_cover = create_sample_png(612, 792, [30, 60, 120]);
68 doc.add_background_image(&bg_cover, "cover_bg.png");
69
70 doc.add_paragraph(""); // spacer
71 doc.add_paragraph(""); // spacer
72 doc.add_paragraph(""); // spacer
73
74 {
75 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
76 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
77 }
78 {
79 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
80 p.add_run("Feature Showcase")
81 .size(28.0)
82 .color("FFFFFF")
83 .italic(true);
84 }
85
86 doc.add_paragraph(""); // spacer
87
88 {
89 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
90 p.add_run("A comprehensive demonstration of every feature")
91 .size(14.0)
92 .color("CCDDFF");
93 }
94 {
95 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
96 p.add_run("provided by the rdocx Rust crate for DOCX generation.")
97 .size(14.0)
98 .color("CCDDFF");
99 }
100
101 // =========================================================================
102 // PAGE 2: TEXT FORMATTING
103 // =========================================================================
104 doc.add_paragraph("").page_break_before(true);
105
106 doc.add_paragraph("1. Text Formatting").style("Heading1");
107
108 doc.add_paragraph("This section demonstrates paragraph and run-level formatting options.");
109 doc.add_paragraph("");
110
111 // --- Paragraph alignment ---
112 doc.add_paragraph("Paragraph Alignment").style("Heading2");
113
114 doc.add_paragraph("This paragraph is left-aligned (the default).")
115 .alignment(Alignment::Left);
116 doc.add_paragraph("This paragraph is center-aligned.")
117 .alignment(Alignment::Center);
118 doc.add_paragraph("This paragraph is right-aligned.")
119 .alignment(Alignment::Right);
120 doc.add_paragraph(
121 "This paragraph is justified. To demonstrate justified text properly, it needs \
122 to be long enough to span multiple lines so the word spacing adjustment is visible \
123 across the full width of the text area on the page.",
124 )
125 .alignment(Alignment::Justify);
126
127 doc.add_paragraph("");
128
129 // --- Run formatting ---
130 doc.add_paragraph("Run Formatting").style("Heading2");
131
132 {
133 let mut p = doc.add_paragraph("");
134 p.add_run("Bold text").bold(true);
135 p.add_run(" | ");
136 p.add_run("Italic text").italic(true);
137 p.add_run(" | ");
138 p.add_run("Bold + Italic").bold(true).italic(true);
139 }
140 {
141 let mut p = doc.add_paragraph("");
142 p.add_run("Single underline").underline(true);
143 p.add_run(" | ");
144 p.add_run("Strikethrough").strike(true);
145 p.add_run(" | ");
146 p.add_run("Double strikethrough").double_strike(true);
147 }
148 {
149 let mut p = doc.add_paragraph("");
150 p.add_run("Red text").color("FF0000");
151 p.add_run(" | ");
152 p.add_run("Blue text").color("0000FF");
153 p.add_run(" | ");
154 p.add_run("Green text").color("00AA00");
155 p.add_run(" | ");
156 p.add_run("Highlighted").highlight("FFFF00");
157 }
158 {
159 let mut p = doc.add_paragraph("");
160 p.add_run("8pt small").size(8.0);
161 p.add_run(" | ");
162 p.add_run("11pt normal").size(11.0);
163 p.add_run(" | ");
164 p.add_run("16pt large").size(16.0);
165 p.add_run(" | ");
166 p.add_run("24pt extra-large").size(24.0);
167 }
168 {
169 let mut p = doc.add_paragraph("");
170 p.add_run("Arial font").font("Arial");
171 p.add_run(" | ");
172 p.add_run("Times New Roman font").font("Times New Roman");
173 p.add_run(" | ");
174 p.add_run("Courier New font").font("Courier New");
175 }
176 {
177 let mut p = doc.add_paragraph("");
178 p.add_run("Normal");
179 p.add_run(" H").size(11.0);
180 p.add_run("2").subscript();
181 p.add_run("O (subscript)").size(11.0);
182 p.add_run(" | E = mc").size(11.0);
183 p.add_run("2").superscript();
184 p.add_run(" (superscript)").size(11.0);
185 }
186 {
187 let mut p = doc.add_paragraph("");
188 p.add_run("ALL CAPS").all_caps(true);
189 p.add_run(" | ");
190 p.add_run("Small Caps").small_caps(true);
191 p.add_run(" | ");
192 p.add_run("Expanded spacing")
193 .character_spacing(Length::twips(40));
194 }
195
196 doc.add_paragraph("");
197
198 // --- Paragraph formatting ---
199 doc.add_paragraph("Paragraph Formatting").style("Heading2");
200
201 doc.add_paragraph("Paragraph with shading (light green background)")
202 .shading("E2EFDA");
203
204 doc.add_paragraph("Paragraph with bottom border")
205 .border_bottom(BorderStyle::Single, 6, "2E75B6");
206
207 doc.add_paragraph("Paragraph with all borders")
208 .border_all(BorderStyle::Single, 4, "FF0000");
209
210 doc.add_paragraph("Paragraph with 1-inch left indent and hanging indent")
211 .indent_left(Length::inches(1.0))
212 .hanging_indent(Length::inches(0.5));
213
214 doc.add_paragraph("Paragraph with first-line indent of 0.5 inches")
215 .first_line_indent(Length::inches(0.5));
216
217 doc.add_paragraph("Paragraph with extra space before (24pt) and after (12pt)")
218 .space_before(Length::pt(24.0))
219 .space_after(Length::pt(12.0));
220
221 doc.add_paragraph(
222 "Paragraph with double line spacing. This text should have extra vertical \
223 space between lines to demonstrate the line_spacing_multiple setting.",
224 )
225 .line_spacing_multiple(2.0);
226
227 doc.add_paragraph("Paragraph with keep-with-next (won't break from the next paragraph)")
228 .keep_with_next(true);
229 doc.add_paragraph("(This stays with the paragraph above.)");
230
231 // =========================================================================
232 // PAGE 3: LISTS & TAB STOPS
233 // =========================================================================
234 doc.add_paragraph("").page_break_before(true);
235
236 doc.add_paragraph("2. Lists").style("Heading1");
237
238 doc.add_paragraph("Bullet List").style("Heading2");
239
240 doc.add_bullet_list_item("First bullet item", 0);
241 doc.add_bullet_list_item("Second bullet item", 0);
242 doc.add_bullet_list_item("Nested level 1", 1);
243 doc.add_bullet_list_item("Nested level 2", 2);
244 doc.add_bullet_list_item("Back to level 1", 1);
245 doc.add_bullet_list_item("Third bullet item", 0);
246
247 doc.add_paragraph("");
248
249 doc.add_paragraph("Numbered List").style("Heading2");
250
251 doc.add_numbered_list_item("First numbered item", 0);
252 doc.add_numbered_list_item("Second numbered item", 0);
253 doc.add_numbered_list_item("Sub-item A", 1);
254 doc.add_numbered_list_item("Sub-item B", 1);
255 doc.add_numbered_list_item("Third numbered item", 0);
256
257 doc.add_paragraph("");
258
259 // --- Tab stops ---
260 doc.add_paragraph("Tab Stops").style("Heading2");
261
262 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
263 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
264 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
265 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
266 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
267
268 doc.add_paragraph("Item\t........\tPrice")
269 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
270 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
271 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
272
273 doc.add_paragraph("Widget A\t........\t$19.99")
274 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
275 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
276 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
277
278 doc.add_paragraph("Gadget B\t________\t$249.50")
279 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
280 .add_tab_stop_with_leader(
281 TabAlignment::Right,
282 Length::inches(4.0),
283 TabLeader::Underscore,
284 )
285 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
286
287 // =========================================================================
288 // PAGE 4: TABLES
289 // =========================================================================
290 doc.add_paragraph("").page_break_before(true);
291
292 doc.add_paragraph("3. Tables").style("Heading1");
293
294 // --- Basic table with borders ---
295 doc.add_paragraph("Basic Table with Borders")
296 .style("Heading2");
297
298 {
299 let mut tbl = doc.add_table(4, 3);
300 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
301
302 // Header row
303 for col in 0..3 {
304 tbl.cell(0, col).unwrap().shading("2E75B6");
305 }
306 tbl.cell(0, 0).unwrap().set_text("Name");
307 tbl.cell(0, 1).unwrap().set_text("Role");
308 tbl.cell(0, 2).unwrap().set_text("Location");
309
310 tbl.cell(1, 0).unwrap().set_text("Alice Johnson");
311 tbl.cell(1, 1).unwrap().set_text("Engineering Lead");
312 tbl.cell(1, 2).unwrap().set_text("New York");
313
314 tbl.cell(2, 0).unwrap().set_text("Bob Smith");
315 tbl.cell(2, 1).unwrap().set_text("Product Manager");
316 tbl.cell(2, 2).unwrap().set_text("San Francisco");
317
318 tbl.cell(3, 0).unwrap().set_text("Carol Davis");
319 tbl.cell(3, 1).unwrap().set_text("Designer");
320 tbl.cell(3, 2).unwrap().set_text("London");
321 }
322
323 doc.add_paragraph("");
324
325 // --- Table with cell merging ---
326 doc.add_paragraph("Table with Cell Merging & Vertical Alignment")
327 .style("Heading2");
328
329 {
330 let mut tbl = doc.add_table(4, 4);
331 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
332 tbl = tbl.width_pct(100.0);
333
334 // Header spanning all columns
335 tbl.cell(0, 0).unwrap().set_text("Quarterly Revenue Report");
336 tbl.cell(0, 0).unwrap().shading("1F4E79");
337 tbl.cell(0, 0).unwrap().grid_span(4);
338
339 // Sub-header
340 tbl.cell(1, 0).unwrap().set_text("Region");
341 tbl.cell(1, 0).unwrap().shading("D6E4F0");
342 tbl.cell(1, 1).unwrap().set_text("Q1");
343 tbl.cell(1, 1).unwrap().shading("D6E4F0");
344 tbl.cell(1, 2).unwrap().set_text("Q2");
345 tbl.cell(1, 2).unwrap().shading("D6E4F0");
346 tbl.cell(1, 3).unwrap().set_text("Total");
347 tbl.cell(1, 3).unwrap().shading("D6E4F0");
348
349 // Data
350 tbl.cell(2, 0).unwrap().set_text("North America");
351 tbl.cell(2, 1).unwrap().set_text("$2.4M");
352 tbl.cell(2, 2).unwrap().set_text("$2.7M");
353 tbl.cell(2, 3).unwrap().set_text("$5.1M");
354
355 tbl.cell(3, 0).unwrap().set_text("Europe");
356 tbl.cell(3, 1).unwrap().set_text("$1.8M");
357 tbl.cell(3, 2).unwrap().set_text("$2.0M");
358 tbl.cell(3, 3).unwrap().set_text("$3.8M");
359
360 // Vertical alignment on data cells
361 tbl.cell(2, 3)
362 .unwrap()
363 .vertical_alignment(VerticalAlignment::Center);
364 tbl.cell(3, 3)
365 .unwrap()
366 .vertical_alignment(VerticalAlignment::Bottom);
367 }
368
369 doc.add_paragraph("");
370
371 // --- Table with vertical merge ---
372 doc.add_paragraph("Table with Vertical Merge")
373 .style("Heading2");
374
375 {
376 let mut tbl = doc.add_table(4, 3);
377 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
378
379 tbl.cell(0, 0).unwrap().set_text("Category");
380 tbl.cell(0, 0).unwrap().shading("E2EFDA");
381 tbl.cell(0, 1).unwrap().set_text("Item");
382 tbl.cell(0, 1).unwrap().shading("E2EFDA");
383 tbl.cell(0, 2).unwrap().set_text("Price");
384 tbl.cell(0, 2).unwrap().shading("E2EFDA");
385
386 // "Hardware" spans rows 1-2
387 tbl.cell(1, 0).unwrap().set_text("Hardware");
388 tbl.cell(1, 0).unwrap().v_merge_restart();
389 tbl.cell(1, 1).unwrap().set_text("Laptop");
390 tbl.cell(1, 2).unwrap().set_text("$1,200");
391
392 tbl.cell(2, 0).unwrap().v_merge_continue();
393 tbl.cell(2, 1).unwrap().set_text("Monitor");
394 tbl.cell(2, 2).unwrap().set_text("$450");
395
396 // "Software" on row 3
397 tbl.cell(3, 0).unwrap().set_text("Software");
398 tbl.cell(3, 1).unwrap().set_text("IDE License");
399 tbl.cell(3, 2).unwrap().set_text("$200/yr");
400 }
401
402 doc.add_paragraph("");
403
404 // --- Nested table ---
405 doc.add_paragraph("Nested Table").style("Heading2");
406
407 {
408 let mut tbl = doc.add_table(2, 2);
409 tbl = tbl.borders(BorderStyle::Single, 6, "2E75B6");
410
411 tbl.cell(0, 0).unwrap().set_text("Outer Cell (0,0)");
412 tbl.cell(0, 1).unwrap().set_text("Outer Cell (0,1)");
413 tbl.cell(1, 0).unwrap().set_text("Outer Cell (1,0)");
414
415 // Nested table inside cell (1,1)
416 {
417 let mut cell = tbl.cell(1, 1).unwrap();
418 cell.set_text("Contains nested table:");
419 let mut nested = cell.add_table(2, 2);
420 nested = nested.borders(BorderStyle::Single, 2, "FF6600");
421 nested.cell(0, 0).unwrap().set_text("Inner A");
422 nested.cell(0, 1).unwrap().set_text("Inner B");
423 nested.cell(1, 0).unwrap().set_text("Inner C");
424 nested.cell(1, 1).unwrap().set_text("Inner D");
425 }
426 }
427
428 // =========================================================================
429 // PAGE 5: IMAGES
430 // =========================================================================
431 doc.add_paragraph("").page_break_before(true);
432
433 doc.add_paragraph("4. Images").style("Heading1");
434
435 doc.add_paragraph("Inline Image").style("Heading2");
436
437 doc.add_paragraph("Below is an inline image (200x50 pixels, blue gradient):");
438 let inline_img = create_sample_png(200, 50, [0, 80, 200]);
439 doc.add_picture(
440 &inline_img,
441 "inline_chart.png",
442 Length::inches(3.0),
443 Length::inches(0.75),
444 );
445
446 doc.add_paragraph("");
447
448 doc.add_paragraph("Header Image").style("Heading2");
449
450 // Replace the text-only header with an image header
451 let header_img = create_sample_png(400, 40, [40, 40, 40]);
452 doc.set_header_image(
453 &header_img,
454 "header_logo.png",
455 Length::inches(2.0),
456 Length::inches(0.2),
457 );
458
459 doc.add_paragraph(
460 "The document header has been replaced with an inline image. \
461 Check the header area at the top of this page.",
462 );
463
464 doc.add_paragraph("");
465 doc.add_paragraph(
466 "Note: The cover page uses a full-page background image behind the text, \
467 demonstrated on page 1 via add_background_image().",
468 );
469
470 // =========================================================================
471 // PAGE 6: CONTENT MANIPULATION — placeholder replacement, insertion
472 // =========================================================================
473 doc.add_paragraph("").page_break_before(true);
474
475 doc.add_paragraph("5. Content Manipulation")
476 .style("Heading1");
477
478 // --- Placeholder replacement ---
479 doc.add_paragraph("Placeholder Replacement")
480 .style("Heading2");
481
482 doc.add_paragraph(
483 "Before replacement, this document contained {{customer}} and {{date}} placeholders.",
484 );
485
486 {
487 let mut p = doc.add_paragraph("");
488 p.add_run("Customer: ").bold(true);
489 p.add_run("{{customer}}");
490 }
491 {
492 let mut p = doc.add_paragraph("");
493 p.add_run("Date: ").bold(true);
494 p.add_run("{{date}}");
495 }
496 doc.add_paragraph("Reference: {{ref_number}}");
497
498 // Table with placeholders
499 {
500 let mut tbl = doc.add_table(3, 2);
501 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
502 tbl.cell(0, 0).unwrap().set_text("Field");
503 tbl.cell(0, 0).unwrap().shading("D6E4F0");
504 tbl.cell(0, 1).unwrap().set_text("Value");
505 tbl.cell(0, 1).unwrap().shading("D6E4F0");
506 tbl.cell(1, 0).unwrap().set_text("Project");
507 tbl.cell(1, 1).unwrap().set_text("{{project}}");
508 tbl.cell(2, 0).unwrap().set_text("Status");
509 tbl.cell(2, 1).unwrap().set_text("{{status}}");
510 }
511
512 // Perform replacements
513 let mut replacements = HashMap::new();
514 replacements.insert("{{customer}}", "Acme Corporation");
515 replacements.insert("{{date}}", "February 22, 2026");
516 replacements.insert("{{ref_number}}", "REF-2026-001");
517 replacements.insert("{{project}}", "Infrastructure Upgrade");
518 replacements.insert("{{status}}", "In Progress");
519 let replace_count = doc.replace_all(&replacements);
520
521 doc.add_paragraph("");
522 doc.add_paragraph(&format!(
523 "(Replaced {} placeholders above — in body text and table cells)",
524 replace_count
525 ));
526
527 doc.add_paragraph("");
528
529 // --- Content insertion ---
530 doc.add_paragraph("Content Insertion").style("Heading2");
531
532 doc.add_paragraph("Section A: First section of content.");
533 doc.add_paragraph("Section C: Third section of content.");
534
535 // Insert "Section B" between A and C
536 if let Some(idx) = doc.find_content_index("Section C") {
537 doc.insert_paragraph(
538 idx,
539 "Section B: Inserted between A and C using find_content_index().",
540 );
541 }
542
543 doc.add_paragraph("");
544 doc.add_paragraph(
545 "The paragraph above ('Section B') was inserted at a specific position \
546 using find_content_index() + insert_paragraph().",
547 );
548
549 // =========================================================================
550 // PAGE 7: LANDSCAPE — section break, wide table
551 // =========================================================================
552 doc.add_paragraph("").section_break(SectionBreak::NextPage);
553
554 doc.add_paragraph("6. Mixed Page Orientation")
555 .style("Heading1");
556
557 doc.add_paragraph(
558 "This page is in LANDSCAPE orientation. It was created using a section break \
559 followed by section_landscape(). This is useful for wide tables or charts.",
560 );
561
562 doc.add_paragraph("");
563
564 // Wide table for landscape
565 {
566 let mut tbl = doc.add_table(4, 7);
567 tbl = tbl.borders(BorderStyle::Single, 4, "2E75B6");
568
569 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
570 for (col, h) in headers.iter().enumerate() {
571 tbl.cell(0, col).unwrap().set_text(h);
572 tbl.cell(0, col).unwrap().shading("2E75B6");
573 }
574
575 let data = [
576 [
577 "North America",
578 "$1.2M",
579 "$1.3M",
580 "$1.4M",
581 "$1.5M",
582 "$1.6M",
583 "$7.0M",
584 ],
585 [
586 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
587 ],
588 [
589 "Asia Pacific",
590 "$0.5M",
591 "$0.6M",
592 "$0.7M",
593 "$0.7M",
594 "$0.8M",
595 "$3.3M",
596 ],
597 ];
598 for (row_idx, row_data) in data.iter().enumerate() {
599 for (col, val) in row_data.iter().enumerate() {
600 tbl.cell(row_idx + 1, col).unwrap().set_text(val);
601 }
602 }
603 }
604
605 // End landscape, return to portrait
606 doc.add_paragraph("")
607 .section_break(SectionBreak::NextPage)
608 .section_landscape();
609
610 // =========================================================================
611 // PAGE 8: BACK TO PORTRAIT — styles, final notes
612 // =========================================================================
613 doc.add_paragraph("7. Custom Styles & Summary")
614 .style("Heading1");
615
616 doc.add_paragraph(
617 "This final page is back in portrait orientation after a section break. \
618 The document has demonstrated:",
619 );
620
621 doc.add_bullet_list_item(
622 "Page setup: size, margins, header/footer distance, gutter",
623 0,
624 );
625 doc.add_bullet_list_item("Document metadata: title, author, subject, keywords", 0);
626 doc.add_bullet_list_item("Headers and footers: text, images, different first page", 0);
627 doc.add_bullet_list_item("Background images: full-page behind text", 0);
628 doc.add_bullet_list_item(
629 "Text formatting: bold, italic, underline, strike, color, size, font",
630 0,
631 );
632 doc.add_bullet_list_item(
633 "Advanced run formatting: superscript, subscript, caps, spacing",
634 0,
635 );
636 doc.add_bullet_list_item(
637 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
638 0,
639 );
640 doc.add_bullet_list_item("Bullet and numbered lists with nesting levels", 0);
641 doc.add_bullet_list_item("Tab stops with dot/underscore leaders", 0);
642 doc.add_bullet_list_item(
643 "Tables: borders, shading, column spans, row spans, nesting",
644 0,
645 );
646 doc.add_bullet_list_item("Vertical alignment in table cells", 0);
647 doc.add_bullet_list_item("Inline images", 0);
648 doc.add_bullet_list_item("Placeholder replacement in body and table cells", 0);
649 doc.add_bullet_list_item("Content insertion at specific positions", 0);
650 doc.add_bullet_list_item(
651 "Section breaks with mixed portrait/landscape orientation",
652 0,
653 );
654
655 doc.add_paragraph("");
656 doc.add_paragraph("All features above were built entirely from scratch using the rdocx API.")
657 .alignment(Alignment::Center)
658 .shading("E2EFDA")
659 .border_all(BorderStyle::Single, 2, "00AA00");
660
661 doc.save(path).unwrap();
662}86fn generate_feature_showcase(_samples_dir: &Path) -> Document {
87 let mut doc = Document::new();
88
89 // ── Page Setup & Metadata ──
90 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
91 doc.set_margins(
92 Length::inches(1.0),
93 Length::inches(1.0),
94 Length::inches(1.0),
95 Length::inches(1.0),
96 );
97 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
98 doc.set_gutter(Length::twips(0));
99 doc.set_title("rdocx Feature Showcase");
100 doc.set_author("rdocx Sample Generator");
101 doc.set_subject("Comprehensive feature demonstration");
102 doc.set_keywords("rdocx, docx, rust, sample, showcase");
103
104 // Headers & Footers with different first page
105 doc.set_different_first_page(true);
106 doc.set_first_page_header("rdocx");
107 doc.set_first_page_footer("Feature Showcase — Cover Page");
108 doc.set_header("rdocx Feature Showcase");
109 doc.set_footer("Generated by rdocx");
110
111 // ── COVER PAGE ──
112 let bg = create_sample_png(612, 792, [20, 45, 90]);
113 doc.add_background_image(&bg, "cover_bg.png");
114
115 for _ in 0..3 {
116 doc.add_paragraph("");
117 }
118 {
119 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
120 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
121 }
122 {
123 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
124 p.add_run("Complete Feature Showcase")
125 .size(28.0)
126 .color("FFFFFF")
127 .italic(true);
128 }
129 doc.add_paragraph("");
130 {
131 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
132 p.add_run("Every feature of the rdocx Rust library")
133 .size(13.0)
134 .color("B0C4DE");
135 }
136 {
137 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
138 p.add_run("demonstrated in a single document.")
139 .size(13.0)
140 .color("B0C4DE");
141 }
142
143 // ── TABLE OF CONTENTS ──
144 doc.add_paragraph("").page_break_before(true);
145 doc.insert_toc(doc.content_count(), 3);
146
147 // ── SECTION 1: TEXT FORMATTING ──
148 doc.add_paragraph("").page_break_before(true);
149 doc.add_paragraph("1. Text Formatting").style("Heading1");
150
151 // Paragraph Alignment
152 doc.add_paragraph("1.1 Paragraph Alignment")
153 .style("Heading2");
154 doc.add_paragraph("Left-aligned paragraph (default).")
155 .alignment(Alignment::Left);
156 doc.add_paragraph("Center-aligned paragraph.")
157 .alignment(Alignment::Center);
158 doc.add_paragraph("Right-aligned paragraph.")
159 .alignment(Alignment::Right);
160 doc.add_paragraph(
161 "Justified paragraph — this text is long enough to demonstrate how justified alignment \
162 distributes extra space across word gaps so lines fill the full width of the text area.",
163 )
164 .alignment(Alignment::Justify);
165
166 doc.add_paragraph("");
167
168 // Run Formatting
169 doc.add_paragraph("1.2 Run Formatting").style("Heading2");
170 {
171 let mut p = doc.add_paragraph("");
172 p.add_run("Bold").bold(true);
173 p.add_run(" | ");
174 p.add_run("Italic").italic(true);
175 p.add_run(" | ");
176 p.add_run("Bold Italic").bold(true).italic(true);
177 p.add_run(" | ");
178 p.add_run("Underline").underline(true);
179 p.add_run(" | ");
180 p.add_run("Strikethrough").strike(true);
181 p.add_run(" | ");
182 p.add_run("Double Strike").double_strike(true);
183 }
184 {
185 let mut p = doc.add_paragraph("");
186 p.add_run("Red").color("FF0000");
187 p.add_run(" | ");
188 p.add_run("Blue").color("0000FF");
189 p.add_run(" | ");
190 p.add_run("Green").color("00AA00");
191 p.add_run(" | ");
192 p.add_run("Highlighted").highlight("FFFF00");
193 }
194 {
195 let mut p = doc.add_paragraph("");
196 p.add_run("8pt").size(8.0);
197 p.add_run(" | ");
198 p.add_run("11pt").size(11.0);
199 p.add_run(" | ");
200 p.add_run("16pt").size(16.0);
201 p.add_run(" | ");
202 p.add_run("24pt").size(24.0);
203 }
204 {
205 let mut p = doc.add_paragraph("");
206 p.add_run("Arial").font("Arial");
207 p.add_run(" | ");
208 p.add_run("Times New Roman").font("Times New Roman");
209 p.add_run(" | ");
210 p.add_run("Courier New").font("Courier New");
211 }
212 {
213 let mut p = doc.add_paragraph("");
214 p.add_run("H");
215 p.add_run("2").subscript();
216 p.add_run("O (subscript) | E=mc");
217 p.add_run("2").superscript();
218 p.add_run(" (superscript)");
219 }
220 {
221 let mut p = doc.add_paragraph("");
222 p.add_run("ALL CAPS").all_caps(true);
223 p.add_run(" | ");
224 p.add_run("Small Caps").small_caps(true);
225 p.add_run(" | ");
226 p.add_run("Expanded").character_spacing(Length::twips(40));
227 p.add_run(" | ");
228 p.add_run("Hidden text (not visible)").hidden(true);
229 }
230
231 doc.add_paragraph("");
232
233 // Underline Styles
234 doc.add_paragraph("1.3 Underline Styles").style("Heading2");
235 {
236 let mut p = doc.add_paragraph("");
237 p.add_run("Single ")
238 .underline_style(rdocx::UnderlineStyle::Single);
239 p.add_run("Double ")
240 .underline_style(rdocx::UnderlineStyle::Double);
241 p.add_run("Thick ")
242 .underline_style(rdocx::UnderlineStyle::Thick);
243 p.add_run("Dotted ")
244 .underline_style(rdocx::UnderlineStyle::Dotted);
245 p.add_run("Dash ")
246 .underline_style(rdocx::UnderlineStyle::Dash);
247 p.add_run("Wave ")
248 .underline_style(rdocx::UnderlineStyle::Wave);
249 }
250
251 // ── SECTION 2: PARAGRAPH FORMATTING ──
252 doc.add_paragraph("").page_break_before(true);
253 doc.add_paragraph("2. Paragraph Formatting")
254 .style("Heading1");
255
256 doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
257 doc.add_paragraph("Paragraph with light green shading")
258 .shading("E2EFDA");
259 doc.add_paragraph("Paragraph with bottom border")
260 .border_bottom(BorderStyle::Single, 6, "2E75B6");
261 doc.add_paragraph("Paragraph with all borders (red)")
262 .border_all(BorderStyle::Single, 4, "FF0000");
263
264 doc.add_paragraph("2.2 Indentation").style("Heading2");
265 doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
266 .indent_left(Length::inches(1.0))
267 .hanging_indent(Length::inches(0.5));
268 doc.add_paragraph("0.5-inch first-line indent on this paragraph")
269 .first_line_indent(Length::inches(0.5));
270 doc.add_paragraph("Both left and right indent (0.75 inches each)")
271 .indent_left(Length::inches(0.75))
272 .indent_right(Length::inches(0.75));
273
274 doc.add_paragraph("2.3 Spacing & Line Height")
275 .style("Heading2");
276 doc.add_paragraph("Extra space before (24pt) and after (12pt)")
277 .space_before(Length::pt(24.0))
278 .space_after(Length::pt(12.0));
279 doc.add_paragraph(
280 "Double line spacing paragraph. This text should have extra vertical space between \
281 lines to demonstrate line_spacing_multiple(2.0).",
282 )
283 .line_spacing_multiple(2.0);
284 doc.add_paragraph("Exact 20pt line spacing (fixed height).")
285 .line_spacing(20.0);
286
287 doc.add_paragraph("2.4 Pagination Controls")
288 .style("Heading2");
289 doc.add_paragraph("keep_with_next — this paragraph stays with the next")
290 .keep_with_next(true);
291 doc.add_paragraph("(This paragraph was kept with the one above.)");
292 doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
293 .keep_together(true);
294 doc.add_paragraph("widow_control — prevents widow/orphan lines")
295 .widow_control(true);
296
297 // ── SECTION 3: LISTS ──
298 doc.add_paragraph("").page_break_before(true);
299 doc.add_paragraph("3. Lists").style("Heading1");
300
301 doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
302 doc.add_bullet_list_item("First item", 0);
303 doc.add_bullet_list_item("Second item", 0);
304 doc.add_bullet_list_item("Nested level 1", 1);
305 doc.add_bullet_list_item("Nested level 2", 2);
306 doc.add_bullet_list_item("Back to level 1", 1);
307 doc.add_bullet_list_item("Third item", 0);
308
309 doc.add_paragraph("");
310
311 doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
312 doc.add_numbered_list_item("First numbered item", 0);
313 doc.add_numbered_list_item("Second numbered item", 0);
314 doc.add_numbered_list_item("Sub-item A", 1);
315 doc.add_numbered_list_item("Sub-item B", 1);
316 doc.add_numbered_list_item("Third numbered item", 0);
317
318 // ── SECTION 4: TAB STOPS ──
319 doc.add_paragraph("");
320 doc.add_paragraph("4. Tab Stops").style("Heading1");
321
322 doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
323 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
324 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
325 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
326 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
327 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
328
329 doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
330 doc.add_paragraph("Item\t\tPrice")
331 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
332 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
333 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
334 doc.add_paragraph("Widget\t\t$19.99")
335 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
336 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
337 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
338 doc.add_paragraph("Gadget\t\t$249.50")
339 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
340 .add_tab_stop_with_leader(
341 TabAlignment::Right,
342 Length::inches(4.0),
343 TabLeader::Underscore,
344 )
345 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
346
347 // ── SECTION 5: TABLES ──
348 doc.add_paragraph("").page_break_before(true);
349 doc.add_paragraph("5. Tables").style("Heading1");
350
351 doc.add_paragraph("5.1 Basic Table").style("Heading2");
352 {
353 let mut tbl = doc
354 .add_table(4, 3)
355 .borders(BorderStyle::Single, 4, "000000");
356 for col in 0..3 {
357 tbl.cell(0, col).unwrap().shading("2E75B6");
358 }
359 tbl.cell(0, 0).unwrap().set_text("Name");
360 tbl.cell(0, 1).unwrap().set_text("Role");
361 tbl.cell(0, 2).unwrap().set_text("Location");
362 tbl.cell(1, 0).unwrap().set_text("Walter White");
363 tbl.cell(1, 1).unwrap().set_text("CEO");
364 tbl.cell(1, 2).unwrap().set_text("Albuquerque");
365 tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
366 tbl.cell(2, 1).unwrap().set_text("CTO");
367 tbl.cell(2, 2).unwrap().set_text("Remote");
368 tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
369 tbl.cell(3, 1).unwrap().set_text("Security");
370 tbl.cell(3, 2).unwrap().set_text("Washington");
371 }
372
373 doc.add_paragraph("");
374
375 doc.add_paragraph("5.2 Column Span & Cell Shading")
376 .style("Heading2");
377 {
378 let mut tbl = doc
379 .add_table(3, 4)
380 .borders(BorderStyle::Single, 4, "000000")
381 .width_pct(100.0);
382 tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
383 tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
384 tbl.cell(1, 0).unwrap().set_text("Region");
385 tbl.cell(1, 0).unwrap().shading("D6E4F0");
386 tbl.cell(1, 1).unwrap().set_text("Q1");
387 tbl.cell(1, 1).unwrap().shading("D6E4F0");
388 tbl.cell(1, 2).unwrap().set_text("Q2");
389 tbl.cell(1, 2).unwrap().shading("D6E4F0");
390 tbl.cell(1, 3).unwrap().set_text("Total");
391 tbl.cell(1, 3).unwrap().shading("D6E4F0");
392 tbl.cell(2, 0).unwrap().set_text("Americas");
393 tbl.cell(2, 1).unwrap().set_text("$2.4M");
394 tbl.cell(2, 2).unwrap().set_text("$2.7M");
395 tbl.cell(2, 3).unwrap().set_text("$5.1M");
396 }
397
398 doc.add_paragraph("");
399
400 doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
401 {
402 let mut tbl = doc
403 .add_table(4, 3)
404 .borders(BorderStyle::Single, 4, "000000");
405 tbl.cell(0, 0).unwrap().set_text("Category");
406 tbl.cell(0, 0).unwrap().shading("E2EFDA");
407 tbl.cell(0, 1).unwrap().set_text("Item");
408 tbl.cell(0, 1).unwrap().shading("E2EFDA");
409 tbl.cell(0, 2).unwrap().set_text("Price");
410 tbl.cell(0, 2).unwrap().shading("E2EFDA");
411 tbl.cell(1, 0).unwrap().set_text("Hardware");
412 tbl.cell(1, 0).unwrap().v_merge_restart();
413 tbl.cell(1, 1).unwrap().set_text("Laptop");
414 tbl.cell(1, 2).unwrap().set_text("$1,200");
415 tbl.cell(2, 0).unwrap().v_merge_continue();
416 tbl.cell(2, 1).unwrap().set_text("Monitor");
417 tbl.cell(2, 2).unwrap().set_text("$450");
418 tbl.cell(3, 0).unwrap().set_text("Software");
419 tbl.cell(3, 1).unwrap().set_text("IDE License");
420 tbl.cell(3, 2).unwrap().set_text("$200/yr");
421 }
422
423 doc.add_paragraph("");
424
425 doc.add_paragraph("5.4 Nested Table").style("Heading2");
426 {
427 let mut tbl = doc
428 .add_table(2, 2)
429 .borders(BorderStyle::Single, 6, "2E75B6");
430 tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
431 tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
432 tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
433 {
434 let mut cell = tbl.cell(1, 1).unwrap();
435 cell.set_text("Nested table below:");
436 let mut nested = cell
437 .add_table(2, 2)
438 .borders(BorderStyle::Single, 2, "FF6600");
439 nested.cell(0, 0).unwrap().set_text("A");
440 nested.cell(0, 1).unwrap().set_text("B");
441 nested.cell(1, 0).unwrap().set_text("C");
442 nested.cell(1, 1).unwrap().set_text("D");
443 }
444 }
445
446 doc.add_paragraph("");
447
448 doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
449 .style("Heading2");
450 {
451 let mut tbl = doc
452 .add_table(2, 3)
453 .borders(BorderStyle::Single, 4, "666666");
454 tbl.row(0).unwrap().height(Length::pt(50.0)).header();
455 tbl.cell(0, 0).unwrap().set_text("Top");
456 tbl.cell(0, 0)
457 .unwrap()
458 .vertical_alignment(VerticalAlignment::Top)
459 .shading("FFF2CC");
460 tbl.cell(0, 1).unwrap().set_text("Center");
461 tbl.cell(0, 1)
462 .unwrap()
463 .vertical_alignment(VerticalAlignment::Center)
464 .shading("D9E2F3");
465 tbl.cell(0, 2).unwrap().set_text("Bottom");
466 tbl.cell(0, 2)
467 .unwrap()
468 .vertical_alignment(VerticalAlignment::Bottom)
469 .shading("E2EFDA");
470 tbl.row(1).unwrap().cant_split();
471 tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
472 tbl.cell(1, 0).unwrap().no_wrap();
473 tbl.cell(1, 1).unwrap().set_text("Fixed width");
474 tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
475 tbl.cell(1, 2).unwrap().set_text("Normal");
476 }
477
478 // ── SECTION 6: IMAGES ──
479 doc.add_paragraph("").page_break_before(true);
480 doc.add_paragraph("6. Images").style("Heading1");
481
482 doc.add_paragraph("6.1 Inline Image").style("Heading2");
483 doc.add_paragraph("A blue gradient image below:");
484 let img = create_sample_png(200, 50, [0, 80, 200]);
485 doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
486
487 doc.add_paragraph("");
488 doc.add_paragraph("6.2 Header Image").style("Heading2");
489 let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
490 doc.set_header_image(
491 &hdr_img,
492 "header_logo.png",
493 Length::inches(2.0),
494 Length::inches(0.2),
495 );
496 doc.add_paragraph("The header now contains an inline image (check the top of this page).");
497
498 doc.add_paragraph("");
499 doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
500
501 // ── SECTION 7: CONTENT MANIPULATION ──
502 doc.add_paragraph("").page_break_before(true);
503 doc.add_paragraph("7. Content Manipulation")
504 .style("Heading1");
505
506 doc.add_paragraph("7.1 Placeholder Replacement")
507 .style("Heading2");
508 doc.add_paragraph("Customer: {{customer}}");
509 doc.add_paragraph("Date: {{date}}");
510 doc.add_paragraph("Reference: {{ref_number}}");
511 {
512 let mut tbl = doc
513 .add_table(2, 2)
514 .borders(BorderStyle::Single, 4, "000000");
515 tbl.cell(0, 0).unwrap().set_text("Project");
516 tbl.cell(0, 0).unwrap().shading("D6E4F0");
517 tbl.cell(0, 1).unwrap().set_text("{{project}}");
518 tbl.cell(1, 0).unwrap().set_text("Status");
519 tbl.cell(1, 0).unwrap().shading("D6E4F0");
520 tbl.cell(1, 1).unwrap().set_text("{{status}}");
521 }
522
523 let mut replacements = HashMap::new();
524 replacements.insert("{{customer}}", "Tensorbee Inc.");
525 replacements.insert("{{date}}", "February 22, 2026");
526 replacements.insert("{{ref_number}}", "TB-2026-001");
527 replacements.insert("{{project}}", "Infrastructure Upgrade");
528 replacements.insert("{{status}}", "In Progress");
529 let n = doc.replace_all(&replacements);
530 doc.add_paragraph(&format!("({n} placeholders replaced above)"));
531
532 doc.add_paragraph("");
533
534 doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
535 doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
536 let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
537 doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
538
539 doc.add_paragraph("");
540
541 doc.add_paragraph("7.3 Content Insertion").style("Heading2");
542 doc.add_paragraph("Section A: First content.");
543 doc.add_paragraph("Section C: Third content.");
544 if let Some(idx) = doc.find_content_index("Section C") {
545 doc.insert_paragraph(
546 idx,
547 "Section B: Inserted between A and C via find_content_index().",
548 );
549 }
550
551 // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
552 doc.add_paragraph("").section_break(SectionBreak::NextPage);
553 doc.add_paragraph("8. Section Breaks & Orientation")
554 .style("Heading1");
555 doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
556
557 {
558 let mut tbl = doc
559 .add_table(3, 7)
560 .borders(BorderStyle::Single, 4, "2E75B6");
561 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
562 for (c, h) in headers.iter().enumerate() {
563 tbl.cell(0, c).unwrap().set_text(h);
564 tbl.cell(0, c).unwrap().shading("2E75B6");
565 }
566 let data = [
567 [
568 "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
569 ],
570 [
571 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
572 ],
573 ];
574 for (r, row) in data.iter().enumerate() {
575 for (c, v) in row.iter().enumerate() {
576 tbl.cell(r + 1, c).unwrap().set_text(v);
577 }
578 }
579 }
580
581 doc.add_paragraph("")
582 .section_break(SectionBreak::NextPage)
583 .section_landscape();
584
585 // ── SECTION 9: CUSTOM STYLES ──
586 doc.add_paragraph("9. Custom Styles").style("Heading1");
587 doc.add_style(
588 StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
589 .based_on("Normal")
590 .paragraph_properties({
591 let mut ppr = rdocx_oxml::properties::CT_PPr::default();
592 ppr.shading = Some(rdocx_oxml::properties::CT_Shd {
593 val: "clear".to_string(),
594 color: None,
595 fill: Some("FFF2CC".to_string()),
596 });
597 ppr
598 })
599 .run_properties({
600 let mut rpr = rdocx_oxml::properties::CT_RPr::default();
601 rpr.bold = Some(true);
602 rpr.color = Some("C45911".to_string());
603 rpr
604 }),
605 );
606 doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
607 .style("CustomHighlight");
608
609 doc.add_paragraph("");
610
611 // ── SECTION 10: DOCUMENT INTELLIGENCE ──
612 doc.add_paragraph("10. Document Intelligence API")
613 .style("Heading1");
614
615 let wc = doc.word_count();
616 let hc = doc.headings().len();
617 let ic = doc.images().len();
618 let lc = doc.links().len();
619 doc.add_paragraph(&format!("Word count: {wc}"));
620 doc.add_paragraph(&format!("Heading count: {hc}"));
621 doc.add_paragraph(&format!("Image count: {ic}"));
622 doc.add_paragraph(&format!("Link count: {lc}"));
623
624 let outline = doc.document_outline();
625 doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
626
627 let issues = doc.audit_accessibility();
628 doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
629 for issue in &issues {
630 doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
631 }
632
633 // ── SECTION 11: DOCUMENT MERGING ──
634 doc.add_paragraph("");
635 doc.add_paragraph("11. Document Merging").style("Heading1");
636 {
637 let mut other = Document::new();
638 other.add_paragraph("This paragraph was merged from another document using append().");
639 doc.append(&other);
640 }
641
642 doc.add_paragraph("");
643
644 // ── SUMMARY ──
645 doc.add_paragraph("Summary of Demonstrated Features")
646 .style("Heading1");
647 let features = [
648 "Page setup: size, margins, header/footer distance, gutter",
649 "Document metadata: title, author, subject, keywords",
650 "Headers and footers: text, images, different first page",
651 "Background images (full-page behind text)",
652 "Table of Contents generation with bookmarks",
653 "Text formatting: bold, italic, underline styles, strike, color, size, font",
654 "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
655 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
656 "Pagination controls: keep-with-next, keep-together, widow control",
657 "Bullet and numbered lists with nesting",
658 "Tab stops with dot/underscore/hyphen leaders",
659 "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
660 "Row properties: header rows, exact height, cant-split",
661 "Cell properties: width, no-wrap, vertical alignment",
662 "Inline images and header images",
663 "Placeholder replacement (single and batch)",
664 "Regex-based find and replace",
665 "Content insertion at specific positions",
666 "Section breaks with mixed portrait/landscape",
667 "Custom paragraph and character styles",
668 "Document intelligence: word count, headings, outline, images, links",
669 "Accessibility audit",
670 "Document merging (append)",
671 "Export: DOCX, PDF, HTML, Markdown",
672 ];
673 for f in &features {
674 doc.add_bullet_list_item(f, 0);
675 }
676 doc.add_paragraph("");
677 doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
678 .alignment(Alignment::Center)
679 .shading("E2EFDA")
680 .border_all(BorderStyle::Single, 2, "00AA00");
681
682 doc
683}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.
Panics if index > content_count().
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
34fn generate_feature_showcase(path: &Path) {
35 let mut doc = Document::new();
36
37 // =========================================================================
38 // PAGE SETUP & METADATA
39 // =========================================================================
40 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
41 doc.set_margins(
42 Length::inches(1.0), // top
43 Length::inches(1.0), // right
44 Length::inches(1.0), // bottom
45 Length::inches(1.0), // left
46 );
47 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
48 doc.set_gutter(Length::twips(0));
49
50 doc.set_title("rdocx Feature Showcase");
51 doc.set_author("rdocx Sample Generator");
52 doc.set_subject("Comprehensive feature demonstration");
53 doc.set_keywords("rdocx, docx, rust, sample");
54
55 // Header & Footer
56 doc.set_header("rdocx Feature Showcase");
57 doc.set_footer("Generated by rdocx — Page");
58
59 // Different first page header
60 doc.set_different_first_page(true);
61 doc.set_first_page_header("rdocx");
62 doc.set_first_page_footer("Feature Showcase — Cover Page");
63
64 // =========================================================================
65 // PAGE 1: COVER PAGE — background image, run formatting
66 // =========================================================================
67 let bg_cover = create_sample_png(612, 792, [30, 60, 120]);
68 doc.add_background_image(&bg_cover, "cover_bg.png");
69
70 doc.add_paragraph(""); // spacer
71 doc.add_paragraph(""); // spacer
72 doc.add_paragraph(""); // spacer
73
74 {
75 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
76 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
77 }
78 {
79 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
80 p.add_run("Feature Showcase")
81 .size(28.0)
82 .color("FFFFFF")
83 .italic(true);
84 }
85
86 doc.add_paragraph(""); // spacer
87
88 {
89 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
90 p.add_run("A comprehensive demonstration of every feature")
91 .size(14.0)
92 .color("CCDDFF");
93 }
94 {
95 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
96 p.add_run("provided by the rdocx Rust crate for DOCX generation.")
97 .size(14.0)
98 .color("CCDDFF");
99 }
100
101 // =========================================================================
102 // PAGE 2: TEXT FORMATTING
103 // =========================================================================
104 doc.add_paragraph("").page_break_before(true);
105
106 doc.add_paragraph("1. Text Formatting").style("Heading1");
107
108 doc.add_paragraph("This section demonstrates paragraph and run-level formatting options.");
109 doc.add_paragraph("");
110
111 // --- Paragraph alignment ---
112 doc.add_paragraph("Paragraph Alignment").style("Heading2");
113
114 doc.add_paragraph("This paragraph is left-aligned (the default).")
115 .alignment(Alignment::Left);
116 doc.add_paragraph("This paragraph is center-aligned.")
117 .alignment(Alignment::Center);
118 doc.add_paragraph("This paragraph is right-aligned.")
119 .alignment(Alignment::Right);
120 doc.add_paragraph(
121 "This paragraph is justified. To demonstrate justified text properly, it needs \
122 to be long enough to span multiple lines so the word spacing adjustment is visible \
123 across the full width of the text area on the page.",
124 )
125 .alignment(Alignment::Justify);
126
127 doc.add_paragraph("");
128
129 // --- Run formatting ---
130 doc.add_paragraph("Run Formatting").style("Heading2");
131
132 {
133 let mut p = doc.add_paragraph("");
134 p.add_run("Bold text").bold(true);
135 p.add_run(" | ");
136 p.add_run("Italic text").italic(true);
137 p.add_run(" | ");
138 p.add_run("Bold + Italic").bold(true).italic(true);
139 }
140 {
141 let mut p = doc.add_paragraph("");
142 p.add_run("Single underline").underline(true);
143 p.add_run(" | ");
144 p.add_run("Strikethrough").strike(true);
145 p.add_run(" | ");
146 p.add_run("Double strikethrough").double_strike(true);
147 }
148 {
149 let mut p = doc.add_paragraph("");
150 p.add_run("Red text").color("FF0000");
151 p.add_run(" | ");
152 p.add_run("Blue text").color("0000FF");
153 p.add_run(" | ");
154 p.add_run("Green text").color("00AA00");
155 p.add_run(" | ");
156 p.add_run("Highlighted").highlight("FFFF00");
157 }
158 {
159 let mut p = doc.add_paragraph("");
160 p.add_run("8pt small").size(8.0);
161 p.add_run(" | ");
162 p.add_run("11pt normal").size(11.0);
163 p.add_run(" | ");
164 p.add_run("16pt large").size(16.0);
165 p.add_run(" | ");
166 p.add_run("24pt extra-large").size(24.0);
167 }
168 {
169 let mut p = doc.add_paragraph("");
170 p.add_run("Arial font").font("Arial");
171 p.add_run(" | ");
172 p.add_run("Times New Roman font").font("Times New Roman");
173 p.add_run(" | ");
174 p.add_run("Courier New font").font("Courier New");
175 }
176 {
177 let mut p = doc.add_paragraph("");
178 p.add_run("Normal");
179 p.add_run(" H").size(11.0);
180 p.add_run("2").subscript();
181 p.add_run("O (subscript)").size(11.0);
182 p.add_run(" | E = mc").size(11.0);
183 p.add_run("2").superscript();
184 p.add_run(" (superscript)").size(11.0);
185 }
186 {
187 let mut p = doc.add_paragraph("");
188 p.add_run("ALL CAPS").all_caps(true);
189 p.add_run(" | ");
190 p.add_run("Small Caps").small_caps(true);
191 p.add_run(" | ");
192 p.add_run("Expanded spacing")
193 .character_spacing(Length::twips(40));
194 }
195
196 doc.add_paragraph("");
197
198 // --- Paragraph formatting ---
199 doc.add_paragraph("Paragraph Formatting").style("Heading2");
200
201 doc.add_paragraph("Paragraph with shading (light green background)")
202 .shading("E2EFDA");
203
204 doc.add_paragraph("Paragraph with bottom border")
205 .border_bottom(BorderStyle::Single, 6, "2E75B6");
206
207 doc.add_paragraph("Paragraph with all borders")
208 .border_all(BorderStyle::Single, 4, "FF0000");
209
210 doc.add_paragraph("Paragraph with 1-inch left indent and hanging indent")
211 .indent_left(Length::inches(1.0))
212 .hanging_indent(Length::inches(0.5));
213
214 doc.add_paragraph("Paragraph with first-line indent of 0.5 inches")
215 .first_line_indent(Length::inches(0.5));
216
217 doc.add_paragraph("Paragraph with extra space before (24pt) and after (12pt)")
218 .space_before(Length::pt(24.0))
219 .space_after(Length::pt(12.0));
220
221 doc.add_paragraph(
222 "Paragraph with double line spacing. This text should have extra vertical \
223 space between lines to demonstrate the line_spacing_multiple setting.",
224 )
225 .line_spacing_multiple(2.0);
226
227 doc.add_paragraph("Paragraph with keep-with-next (won't break from the next paragraph)")
228 .keep_with_next(true);
229 doc.add_paragraph("(This stays with the paragraph above.)");
230
231 // =========================================================================
232 // PAGE 3: LISTS & TAB STOPS
233 // =========================================================================
234 doc.add_paragraph("").page_break_before(true);
235
236 doc.add_paragraph("2. Lists").style("Heading1");
237
238 doc.add_paragraph("Bullet List").style("Heading2");
239
240 doc.add_bullet_list_item("First bullet item", 0);
241 doc.add_bullet_list_item("Second bullet item", 0);
242 doc.add_bullet_list_item("Nested level 1", 1);
243 doc.add_bullet_list_item("Nested level 2", 2);
244 doc.add_bullet_list_item("Back to level 1", 1);
245 doc.add_bullet_list_item("Third bullet item", 0);
246
247 doc.add_paragraph("");
248
249 doc.add_paragraph("Numbered List").style("Heading2");
250
251 doc.add_numbered_list_item("First numbered item", 0);
252 doc.add_numbered_list_item("Second numbered item", 0);
253 doc.add_numbered_list_item("Sub-item A", 1);
254 doc.add_numbered_list_item("Sub-item B", 1);
255 doc.add_numbered_list_item("Third numbered item", 0);
256
257 doc.add_paragraph("");
258
259 // --- Tab stops ---
260 doc.add_paragraph("Tab Stops").style("Heading2");
261
262 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
263 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
264 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
265 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
266 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
267
268 doc.add_paragraph("Item\t........\tPrice")
269 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
270 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
271 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
272
273 doc.add_paragraph("Widget A\t........\t$19.99")
274 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
275 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
276 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
277
278 doc.add_paragraph("Gadget B\t________\t$249.50")
279 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
280 .add_tab_stop_with_leader(
281 TabAlignment::Right,
282 Length::inches(4.0),
283 TabLeader::Underscore,
284 )
285 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
286
287 // =========================================================================
288 // PAGE 4: TABLES
289 // =========================================================================
290 doc.add_paragraph("").page_break_before(true);
291
292 doc.add_paragraph("3. Tables").style("Heading1");
293
294 // --- Basic table with borders ---
295 doc.add_paragraph("Basic Table with Borders")
296 .style("Heading2");
297
298 {
299 let mut tbl = doc.add_table(4, 3);
300 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
301
302 // Header row
303 for col in 0..3 {
304 tbl.cell(0, col).unwrap().shading("2E75B6");
305 }
306 tbl.cell(0, 0).unwrap().set_text("Name");
307 tbl.cell(0, 1).unwrap().set_text("Role");
308 tbl.cell(0, 2).unwrap().set_text("Location");
309
310 tbl.cell(1, 0).unwrap().set_text("Alice Johnson");
311 tbl.cell(1, 1).unwrap().set_text("Engineering Lead");
312 tbl.cell(1, 2).unwrap().set_text("New York");
313
314 tbl.cell(2, 0).unwrap().set_text("Bob Smith");
315 tbl.cell(2, 1).unwrap().set_text("Product Manager");
316 tbl.cell(2, 2).unwrap().set_text("San Francisco");
317
318 tbl.cell(3, 0).unwrap().set_text("Carol Davis");
319 tbl.cell(3, 1).unwrap().set_text("Designer");
320 tbl.cell(3, 2).unwrap().set_text("London");
321 }
322
323 doc.add_paragraph("");
324
325 // --- Table with cell merging ---
326 doc.add_paragraph("Table with Cell Merging & Vertical Alignment")
327 .style("Heading2");
328
329 {
330 let mut tbl = doc.add_table(4, 4);
331 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
332 tbl = tbl.width_pct(100.0);
333
334 // Header spanning all columns
335 tbl.cell(0, 0).unwrap().set_text("Quarterly Revenue Report");
336 tbl.cell(0, 0).unwrap().shading("1F4E79");
337 tbl.cell(0, 0).unwrap().grid_span(4);
338
339 // Sub-header
340 tbl.cell(1, 0).unwrap().set_text("Region");
341 tbl.cell(1, 0).unwrap().shading("D6E4F0");
342 tbl.cell(1, 1).unwrap().set_text("Q1");
343 tbl.cell(1, 1).unwrap().shading("D6E4F0");
344 tbl.cell(1, 2).unwrap().set_text("Q2");
345 tbl.cell(1, 2).unwrap().shading("D6E4F0");
346 tbl.cell(1, 3).unwrap().set_text("Total");
347 tbl.cell(1, 3).unwrap().shading("D6E4F0");
348
349 // Data
350 tbl.cell(2, 0).unwrap().set_text("North America");
351 tbl.cell(2, 1).unwrap().set_text("$2.4M");
352 tbl.cell(2, 2).unwrap().set_text("$2.7M");
353 tbl.cell(2, 3).unwrap().set_text("$5.1M");
354
355 tbl.cell(3, 0).unwrap().set_text("Europe");
356 tbl.cell(3, 1).unwrap().set_text("$1.8M");
357 tbl.cell(3, 2).unwrap().set_text("$2.0M");
358 tbl.cell(3, 3).unwrap().set_text("$3.8M");
359
360 // Vertical alignment on data cells
361 tbl.cell(2, 3)
362 .unwrap()
363 .vertical_alignment(VerticalAlignment::Center);
364 tbl.cell(3, 3)
365 .unwrap()
366 .vertical_alignment(VerticalAlignment::Bottom);
367 }
368
369 doc.add_paragraph("");
370
371 // --- Table with vertical merge ---
372 doc.add_paragraph("Table with Vertical Merge")
373 .style("Heading2");
374
375 {
376 let mut tbl = doc.add_table(4, 3);
377 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
378
379 tbl.cell(0, 0).unwrap().set_text("Category");
380 tbl.cell(0, 0).unwrap().shading("E2EFDA");
381 tbl.cell(0, 1).unwrap().set_text("Item");
382 tbl.cell(0, 1).unwrap().shading("E2EFDA");
383 tbl.cell(0, 2).unwrap().set_text("Price");
384 tbl.cell(0, 2).unwrap().shading("E2EFDA");
385
386 // "Hardware" spans rows 1-2
387 tbl.cell(1, 0).unwrap().set_text("Hardware");
388 tbl.cell(1, 0).unwrap().v_merge_restart();
389 tbl.cell(1, 1).unwrap().set_text("Laptop");
390 tbl.cell(1, 2).unwrap().set_text("$1,200");
391
392 tbl.cell(2, 0).unwrap().v_merge_continue();
393 tbl.cell(2, 1).unwrap().set_text("Monitor");
394 tbl.cell(2, 2).unwrap().set_text("$450");
395
396 // "Software" on row 3
397 tbl.cell(3, 0).unwrap().set_text("Software");
398 tbl.cell(3, 1).unwrap().set_text("IDE License");
399 tbl.cell(3, 2).unwrap().set_text("$200/yr");
400 }
401
402 doc.add_paragraph("");
403
404 // --- Nested table ---
405 doc.add_paragraph("Nested Table").style("Heading2");
406
407 {
408 let mut tbl = doc.add_table(2, 2);
409 tbl = tbl.borders(BorderStyle::Single, 6, "2E75B6");
410
411 tbl.cell(0, 0).unwrap().set_text("Outer Cell (0,0)");
412 tbl.cell(0, 1).unwrap().set_text("Outer Cell (0,1)");
413 tbl.cell(1, 0).unwrap().set_text("Outer Cell (1,0)");
414
415 // Nested table inside cell (1,1)
416 {
417 let mut cell = tbl.cell(1, 1).unwrap();
418 cell.set_text("Contains nested table:");
419 let mut nested = cell.add_table(2, 2);
420 nested = nested.borders(BorderStyle::Single, 2, "FF6600");
421 nested.cell(0, 0).unwrap().set_text("Inner A");
422 nested.cell(0, 1).unwrap().set_text("Inner B");
423 nested.cell(1, 0).unwrap().set_text("Inner C");
424 nested.cell(1, 1).unwrap().set_text("Inner D");
425 }
426 }
427
428 // =========================================================================
429 // PAGE 5: IMAGES
430 // =========================================================================
431 doc.add_paragraph("").page_break_before(true);
432
433 doc.add_paragraph("4. Images").style("Heading1");
434
435 doc.add_paragraph("Inline Image").style("Heading2");
436
437 doc.add_paragraph("Below is an inline image (200x50 pixels, blue gradient):");
438 let inline_img = create_sample_png(200, 50, [0, 80, 200]);
439 doc.add_picture(
440 &inline_img,
441 "inline_chart.png",
442 Length::inches(3.0),
443 Length::inches(0.75),
444 );
445
446 doc.add_paragraph("");
447
448 doc.add_paragraph("Header Image").style("Heading2");
449
450 // Replace the text-only header with an image header
451 let header_img = create_sample_png(400, 40, [40, 40, 40]);
452 doc.set_header_image(
453 &header_img,
454 "header_logo.png",
455 Length::inches(2.0),
456 Length::inches(0.2),
457 );
458
459 doc.add_paragraph(
460 "The document header has been replaced with an inline image. \
461 Check the header area at the top of this page.",
462 );
463
464 doc.add_paragraph("");
465 doc.add_paragraph(
466 "Note: The cover page uses a full-page background image behind the text, \
467 demonstrated on page 1 via add_background_image().",
468 );
469
470 // =========================================================================
471 // PAGE 6: CONTENT MANIPULATION — placeholder replacement, insertion
472 // =========================================================================
473 doc.add_paragraph("").page_break_before(true);
474
475 doc.add_paragraph("5. Content Manipulation")
476 .style("Heading1");
477
478 // --- Placeholder replacement ---
479 doc.add_paragraph("Placeholder Replacement")
480 .style("Heading2");
481
482 doc.add_paragraph(
483 "Before replacement, this document contained {{customer}} and {{date}} placeholders.",
484 );
485
486 {
487 let mut p = doc.add_paragraph("");
488 p.add_run("Customer: ").bold(true);
489 p.add_run("{{customer}}");
490 }
491 {
492 let mut p = doc.add_paragraph("");
493 p.add_run("Date: ").bold(true);
494 p.add_run("{{date}}");
495 }
496 doc.add_paragraph("Reference: {{ref_number}}");
497
498 // Table with placeholders
499 {
500 let mut tbl = doc.add_table(3, 2);
501 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
502 tbl.cell(0, 0).unwrap().set_text("Field");
503 tbl.cell(0, 0).unwrap().shading("D6E4F0");
504 tbl.cell(0, 1).unwrap().set_text("Value");
505 tbl.cell(0, 1).unwrap().shading("D6E4F0");
506 tbl.cell(1, 0).unwrap().set_text("Project");
507 tbl.cell(1, 1).unwrap().set_text("{{project}}");
508 tbl.cell(2, 0).unwrap().set_text("Status");
509 tbl.cell(2, 1).unwrap().set_text("{{status}}");
510 }
511
512 // Perform replacements
513 let mut replacements = HashMap::new();
514 replacements.insert("{{customer}}", "Acme Corporation");
515 replacements.insert("{{date}}", "February 22, 2026");
516 replacements.insert("{{ref_number}}", "REF-2026-001");
517 replacements.insert("{{project}}", "Infrastructure Upgrade");
518 replacements.insert("{{status}}", "In Progress");
519 let replace_count = doc.replace_all(&replacements);
520
521 doc.add_paragraph("");
522 doc.add_paragraph(&format!(
523 "(Replaced {} placeholders above — in body text and table cells)",
524 replace_count
525 ));
526
527 doc.add_paragraph("");
528
529 // --- Content insertion ---
530 doc.add_paragraph("Content Insertion").style("Heading2");
531
532 doc.add_paragraph("Section A: First section of content.");
533 doc.add_paragraph("Section C: Third section of content.");
534
535 // Insert "Section B" between A and C
536 if let Some(idx) = doc.find_content_index("Section C") {
537 doc.insert_paragraph(
538 idx,
539 "Section B: Inserted between A and C using find_content_index().",
540 );
541 }
542
543 doc.add_paragraph("");
544 doc.add_paragraph(
545 "The paragraph above ('Section B') was inserted at a specific position \
546 using find_content_index() + insert_paragraph().",
547 );
548
549 // =========================================================================
550 // PAGE 7: LANDSCAPE — section break, wide table
551 // =========================================================================
552 doc.add_paragraph("").section_break(SectionBreak::NextPage);
553
554 doc.add_paragraph("6. Mixed Page Orientation")
555 .style("Heading1");
556
557 doc.add_paragraph(
558 "This page is in LANDSCAPE orientation. It was created using a section break \
559 followed by section_landscape(). This is useful for wide tables or charts.",
560 );
561
562 doc.add_paragraph("");
563
564 // Wide table for landscape
565 {
566 let mut tbl = doc.add_table(4, 7);
567 tbl = tbl.borders(BorderStyle::Single, 4, "2E75B6");
568
569 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
570 for (col, h) in headers.iter().enumerate() {
571 tbl.cell(0, col).unwrap().set_text(h);
572 tbl.cell(0, col).unwrap().shading("2E75B6");
573 }
574
575 let data = [
576 [
577 "North America",
578 "$1.2M",
579 "$1.3M",
580 "$1.4M",
581 "$1.5M",
582 "$1.6M",
583 "$7.0M",
584 ],
585 [
586 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
587 ],
588 [
589 "Asia Pacific",
590 "$0.5M",
591 "$0.6M",
592 "$0.7M",
593 "$0.7M",
594 "$0.8M",
595 "$3.3M",
596 ],
597 ];
598 for (row_idx, row_data) in data.iter().enumerate() {
599 for (col, val) in row_data.iter().enumerate() {
600 tbl.cell(row_idx + 1, col).unwrap().set_text(val);
601 }
602 }
603 }
604
605 // End landscape, return to portrait
606 doc.add_paragraph("")
607 .section_break(SectionBreak::NextPage)
608 .section_landscape();
609
610 // =========================================================================
611 // PAGE 8: BACK TO PORTRAIT — styles, final notes
612 // =========================================================================
613 doc.add_paragraph("7. Custom Styles & Summary")
614 .style("Heading1");
615
616 doc.add_paragraph(
617 "This final page is back in portrait orientation after a section break. \
618 The document has demonstrated:",
619 );
620
621 doc.add_bullet_list_item(
622 "Page setup: size, margins, header/footer distance, gutter",
623 0,
624 );
625 doc.add_bullet_list_item("Document metadata: title, author, subject, keywords", 0);
626 doc.add_bullet_list_item("Headers and footers: text, images, different first page", 0);
627 doc.add_bullet_list_item("Background images: full-page behind text", 0);
628 doc.add_bullet_list_item(
629 "Text formatting: bold, italic, underline, strike, color, size, font",
630 0,
631 );
632 doc.add_bullet_list_item(
633 "Advanced run formatting: superscript, subscript, caps, spacing",
634 0,
635 );
636 doc.add_bullet_list_item(
637 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
638 0,
639 );
640 doc.add_bullet_list_item("Bullet and numbered lists with nesting levels", 0);
641 doc.add_bullet_list_item("Tab stops with dot/underscore leaders", 0);
642 doc.add_bullet_list_item(
643 "Tables: borders, shading, column spans, row spans, nesting",
644 0,
645 );
646 doc.add_bullet_list_item("Vertical alignment in table cells", 0);
647 doc.add_bullet_list_item("Inline images", 0);
648 doc.add_bullet_list_item("Placeholder replacement in body and table cells", 0);
649 doc.add_bullet_list_item("Content insertion at specific positions", 0);
650 doc.add_bullet_list_item(
651 "Section breaks with mixed portrait/landscape orientation",
652 0,
653 );
654
655 doc.add_paragraph("");
656 doc.add_paragraph("All features above were built entirely from scratch using the rdocx API.")
657 .alignment(Alignment::Center)
658 .shading("E2EFDA")
659 .border_all(BorderStyle::Single, 2, "00AA00");
660
661 doc.save(path).unwrap();
662}86fn generate_feature_showcase(_samples_dir: &Path) -> Document {
87 let mut doc = Document::new();
88
89 // ── Page Setup & Metadata ──
90 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
91 doc.set_margins(
92 Length::inches(1.0),
93 Length::inches(1.0),
94 Length::inches(1.0),
95 Length::inches(1.0),
96 );
97 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
98 doc.set_gutter(Length::twips(0));
99 doc.set_title("rdocx Feature Showcase");
100 doc.set_author("rdocx Sample Generator");
101 doc.set_subject("Comprehensive feature demonstration");
102 doc.set_keywords("rdocx, docx, rust, sample, showcase");
103
104 // Headers & Footers with different first page
105 doc.set_different_first_page(true);
106 doc.set_first_page_header("rdocx");
107 doc.set_first_page_footer("Feature Showcase — Cover Page");
108 doc.set_header("rdocx Feature Showcase");
109 doc.set_footer("Generated by rdocx");
110
111 // ── COVER PAGE ──
112 let bg = create_sample_png(612, 792, [20, 45, 90]);
113 doc.add_background_image(&bg, "cover_bg.png");
114
115 for _ in 0..3 {
116 doc.add_paragraph("");
117 }
118 {
119 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
120 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
121 }
122 {
123 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
124 p.add_run("Complete Feature Showcase")
125 .size(28.0)
126 .color("FFFFFF")
127 .italic(true);
128 }
129 doc.add_paragraph("");
130 {
131 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
132 p.add_run("Every feature of the rdocx Rust library")
133 .size(13.0)
134 .color("B0C4DE");
135 }
136 {
137 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
138 p.add_run("demonstrated in a single document.")
139 .size(13.0)
140 .color("B0C4DE");
141 }
142
143 // ── TABLE OF CONTENTS ──
144 doc.add_paragraph("").page_break_before(true);
145 doc.insert_toc(doc.content_count(), 3);
146
147 // ── SECTION 1: TEXT FORMATTING ──
148 doc.add_paragraph("").page_break_before(true);
149 doc.add_paragraph("1. Text Formatting").style("Heading1");
150
151 // Paragraph Alignment
152 doc.add_paragraph("1.1 Paragraph Alignment")
153 .style("Heading2");
154 doc.add_paragraph("Left-aligned paragraph (default).")
155 .alignment(Alignment::Left);
156 doc.add_paragraph("Center-aligned paragraph.")
157 .alignment(Alignment::Center);
158 doc.add_paragraph("Right-aligned paragraph.")
159 .alignment(Alignment::Right);
160 doc.add_paragraph(
161 "Justified paragraph — this text is long enough to demonstrate how justified alignment \
162 distributes extra space across word gaps so lines fill the full width of the text area.",
163 )
164 .alignment(Alignment::Justify);
165
166 doc.add_paragraph("");
167
168 // Run Formatting
169 doc.add_paragraph("1.2 Run Formatting").style("Heading2");
170 {
171 let mut p = doc.add_paragraph("");
172 p.add_run("Bold").bold(true);
173 p.add_run(" | ");
174 p.add_run("Italic").italic(true);
175 p.add_run(" | ");
176 p.add_run("Bold Italic").bold(true).italic(true);
177 p.add_run(" | ");
178 p.add_run("Underline").underline(true);
179 p.add_run(" | ");
180 p.add_run("Strikethrough").strike(true);
181 p.add_run(" | ");
182 p.add_run("Double Strike").double_strike(true);
183 }
184 {
185 let mut p = doc.add_paragraph("");
186 p.add_run("Red").color("FF0000");
187 p.add_run(" | ");
188 p.add_run("Blue").color("0000FF");
189 p.add_run(" | ");
190 p.add_run("Green").color("00AA00");
191 p.add_run(" | ");
192 p.add_run("Highlighted").highlight("FFFF00");
193 }
194 {
195 let mut p = doc.add_paragraph("");
196 p.add_run("8pt").size(8.0);
197 p.add_run(" | ");
198 p.add_run("11pt").size(11.0);
199 p.add_run(" | ");
200 p.add_run("16pt").size(16.0);
201 p.add_run(" | ");
202 p.add_run("24pt").size(24.0);
203 }
204 {
205 let mut p = doc.add_paragraph("");
206 p.add_run("Arial").font("Arial");
207 p.add_run(" | ");
208 p.add_run("Times New Roman").font("Times New Roman");
209 p.add_run(" | ");
210 p.add_run("Courier New").font("Courier New");
211 }
212 {
213 let mut p = doc.add_paragraph("");
214 p.add_run("H");
215 p.add_run("2").subscript();
216 p.add_run("O (subscript) | E=mc");
217 p.add_run("2").superscript();
218 p.add_run(" (superscript)");
219 }
220 {
221 let mut p = doc.add_paragraph("");
222 p.add_run("ALL CAPS").all_caps(true);
223 p.add_run(" | ");
224 p.add_run("Small Caps").small_caps(true);
225 p.add_run(" | ");
226 p.add_run("Expanded").character_spacing(Length::twips(40));
227 p.add_run(" | ");
228 p.add_run("Hidden text (not visible)").hidden(true);
229 }
230
231 doc.add_paragraph("");
232
233 // Underline Styles
234 doc.add_paragraph("1.3 Underline Styles").style("Heading2");
235 {
236 let mut p = doc.add_paragraph("");
237 p.add_run("Single ")
238 .underline_style(rdocx::UnderlineStyle::Single);
239 p.add_run("Double ")
240 .underline_style(rdocx::UnderlineStyle::Double);
241 p.add_run("Thick ")
242 .underline_style(rdocx::UnderlineStyle::Thick);
243 p.add_run("Dotted ")
244 .underline_style(rdocx::UnderlineStyle::Dotted);
245 p.add_run("Dash ")
246 .underline_style(rdocx::UnderlineStyle::Dash);
247 p.add_run("Wave ")
248 .underline_style(rdocx::UnderlineStyle::Wave);
249 }
250
251 // ── SECTION 2: PARAGRAPH FORMATTING ──
252 doc.add_paragraph("").page_break_before(true);
253 doc.add_paragraph("2. Paragraph Formatting")
254 .style("Heading1");
255
256 doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
257 doc.add_paragraph("Paragraph with light green shading")
258 .shading("E2EFDA");
259 doc.add_paragraph("Paragraph with bottom border")
260 .border_bottom(BorderStyle::Single, 6, "2E75B6");
261 doc.add_paragraph("Paragraph with all borders (red)")
262 .border_all(BorderStyle::Single, 4, "FF0000");
263
264 doc.add_paragraph("2.2 Indentation").style("Heading2");
265 doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
266 .indent_left(Length::inches(1.0))
267 .hanging_indent(Length::inches(0.5));
268 doc.add_paragraph("0.5-inch first-line indent on this paragraph")
269 .first_line_indent(Length::inches(0.5));
270 doc.add_paragraph("Both left and right indent (0.75 inches each)")
271 .indent_left(Length::inches(0.75))
272 .indent_right(Length::inches(0.75));
273
274 doc.add_paragraph("2.3 Spacing & Line Height")
275 .style("Heading2");
276 doc.add_paragraph("Extra space before (24pt) and after (12pt)")
277 .space_before(Length::pt(24.0))
278 .space_after(Length::pt(12.0));
279 doc.add_paragraph(
280 "Double line spacing paragraph. This text should have extra vertical space between \
281 lines to demonstrate line_spacing_multiple(2.0).",
282 )
283 .line_spacing_multiple(2.0);
284 doc.add_paragraph("Exact 20pt line spacing (fixed height).")
285 .line_spacing(20.0);
286
287 doc.add_paragraph("2.4 Pagination Controls")
288 .style("Heading2");
289 doc.add_paragraph("keep_with_next — this paragraph stays with the next")
290 .keep_with_next(true);
291 doc.add_paragraph("(This paragraph was kept with the one above.)");
292 doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
293 .keep_together(true);
294 doc.add_paragraph("widow_control — prevents widow/orphan lines")
295 .widow_control(true);
296
297 // ── SECTION 3: LISTS ──
298 doc.add_paragraph("").page_break_before(true);
299 doc.add_paragraph("3. Lists").style("Heading1");
300
301 doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
302 doc.add_bullet_list_item("First item", 0);
303 doc.add_bullet_list_item("Second item", 0);
304 doc.add_bullet_list_item("Nested level 1", 1);
305 doc.add_bullet_list_item("Nested level 2", 2);
306 doc.add_bullet_list_item("Back to level 1", 1);
307 doc.add_bullet_list_item("Third item", 0);
308
309 doc.add_paragraph("");
310
311 doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
312 doc.add_numbered_list_item("First numbered item", 0);
313 doc.add_numbered_list_item("Second numbered item", 0);
314 doc.add_numbered_list_item("Sub-item A", 1);
315 doc.add_numbered_list_item("Sub-item B", 1);
316 doc.add_numbered_list_item("Third numbered item", 0);
317
318 // ── SECTION 4: TAB STOPS ──
319 doc.add_paragraph("");
320 doc.add_paragraph("4. Tab Stops").style("Heading1");
321
322 doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
323 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
324 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
325 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
326 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
327 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
328
329 doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
330 doc.add_paragraph("Item\t\tPrice")
331 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
332 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
333 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
334 doc.add_paragraph("Widget\t\t$19.99")
335 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
336 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
337 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
338 doc.add_paragraph("Gadget\t\t$249.50")
339 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
340 .add_tab_stop_with_leader(
341 TabAlignment::Right,
342 Length::inches(4.0),
343 TabLeader::Underscore,
344 )
345 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
346
347 // ── SECTION 5: TABLES ──
348 doc.add_paragraph("").page_break_before(true);
349 doc.add_paragraph("5. Tables").style("Heading1");
350
351 doc.add_paragraph("5.1 Basic Table").style("Heading2");
352 {
353 let mut tbl = doc
354 .add_table(4, 3)
355 .borders(BorderStyle::Single, 4, "000000");
356 for col in 0..3 {
357 tbl.cell(0, col).unwrap().shading("2E75B6");
358 }
359 tbl.cell(0, 0).unwrap().set_text("Name");
360 tbl.cell(0, 1).unwrap().set_text("Role");
361 tbl.cell(0, 2).unwrap().set_text("Location");
362 tbl.cell(1, 0).unwrap().set_text("Walter White");
363 tbl.cell(1, 1).unwrap().set_text("CEO");
364 tbl.cell(1, 2).unwrap().set_text("Albuquerque");
365 tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
366 tbl.cell(2, 1).unwrap().set_text("CTO");
367 tbl.cell(2, 2).unwrap().set_text("Remote");
368 tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
369 tbl.cell(3, 1).unwrap().set_text("Security");
370 tbl.cell(3, 2).unwrap().set_text("Washington");
371 }
372
373 doc.add_paragraph("");
374
375 doc.add_paragraph("5.2 Column Span & Cell Shading")
376 .style("Heading2");
377 {
378 let mut tbl = doc
379 .add_table(3, 4)
380 .borders(BorderStyle::Single, 4, "000000")
381 .width_pct(100.0);
382 tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
383 tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
384 tbl.cell(1, 0).unwrap().set_text("Region");
385 tbl.cell(1, 0).unwrap().shading("D6E4F0");
386 tbl.cell(1, 1).unwrap().set_text("Q1");
387 tbl.cell(1, 1).unwrap().shading("D6E4F0");
388 tbl.cell(1, 2).unwrap().set_text("Q2");
389 tbl.cell(1, 2).unwrap().shading("D6E4F0");
390 tbl.cell(1, 3).unwrap().set_text("Total");
391 tbl.cell(1, 3).unwrap().shading("D6E4F0");
392 tbl.cell(2, 0).unwrap().set_text("Americas");
393 tbl.cell(2, 1).unwrap().set_text("$2.4M");
394 tbl.cell(2, 2).unwrap().set_text("$2.7M");
395 tbl.cell(2, 3).unwrap().set_text("$5.1M");
396 }
397
398 doc.add_paragraph("");
399
400 doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
401 {
402 let mut tbl = doc
403 .add_table(4, 3)
404 .borders(BorderStyle::Single, 4, "000000");
405 tbl.cell(0, 0).unwrap().set_text("Category");
406 tbl.cell(0, 0).unwrap().shading("E2EFDA");
407 tbl.cell(0, 1).unwrap().set_text("Item");
408 tbl.cell(0, 1).unwrap().shading("E2EFDA");
409 tbl.cell(0, 2).unwrap().set_text("Price");
410 tbl.cell(0, 2).unwrap().shading("E2EFDA");
411 tbl.cell(1, 0).unwrap().set_text("Hardware");
412 tbl.cell(1, 0).unwrap().v_merge_restart();
413 tbl.cell(1, 1).unwrap().set_text("Laptop");
414 tbl.cell(1, 2).unwrap().set_text("$1,200");
415 tbl.cell(2, 0).unwrap().v_merge_continue();
416 tbl.cell(2, 1).unwrap().set_text("Monitor");
417 tbl.cell(2, 2).unwrap().set_text("$450");
418 tbl.cell(3, 0).unwrap().set_text("Software");
419 tbl.cell(3, 1).unwrap().set_text("IDE License");
420 tbl.cell(3, 2).unwrap().set_text("$200/yr");
421 }
422
423 doc.add_paragraph("");
424
425 doc.add_paragraph("5.4 Nested Table").style("Heading2");
426 {
427 let mut tbl = doc
428 .add_table(2, 2)
429 .borders(BorderStyle::Single, 6, "2E75B6");
430 tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
431 tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
432 tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
433 {
434 let mut cell = tbl.cell(1, 1).unwrap();
435 cell.set_text("Nested table below:");
436 let mut nested = cell
437 .add_table(2, 2)
438 .borders(BorderStyle::Single, 2, "FF6600");
439 nested.cell(0, 0).unwrap().set_text("A");
440 nested.cell(0, 1).unwrap().set_text("B");
441 nested.cell(1, 0).unwrap().set_text("C");
442 nested.cell(1, 1).unwrap().set_text("D");
443 }
444 }
445
446 doc.add_paragraph("");
447
448 doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
449 .style("Heading2");
450 {
451 let mut tbl = doc
452 .add_table(2, 3)
453 .borders(BorderStyle::Single, 4, "666666");
454 tbl.row(0).unwrap().height(Length::pt(50.0)).header();
455 tbl.cell(0, 0).unwrap().set_text("Top");
456 tbl.cell(0, 0)
457 .unwrap()
458 .vertical_alignment(VerticalAlignment::Top)
459 .shading("FFF2CC");
460 tbl.cell(0, 1).unwrap().set_text("Center");
461 tbl.cell(0, 1)
462 .unwrap()
463 .vertical_alignment(VerticalAlignment::Center)
464 .shading("D9E2F3");
465 tbl.cell(0, 2).unwrap().set_text("Bottom");
466 tbl.cell(0, 2)
467 .unwrap()
468 .vertical_alignment(VerticalAlignment::Bottom)
469 .shading("E2EFDA");
470 tbl.row(1).unwrap().cant_split();
471 tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
472 tbl.cell(1, 0).unwrap().no_wrap();
473 tbl.cell(1, 1).unwrap().set_text("Fixed width");
474 tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
475 tbl.cell(1, 2).unwrap().set_text("Normal");
476 }
477
478 // ── SECTION 6: IMAGES ──
479 doc.add_paragraph("").page_break_before(true);
480 doc.add_paragraph("6. Images").style("Heading1");
481
482 doc.add_paragraph("6.1 Inline Image").style("Heading2");
483 doc.add_paragraph("A blue gradient image below:");
484 let img = create_sample_png(200, 50, [0, 80, 200]);
485 doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
486
487 doc.add_paragraph("");
488 doc.add_paragraph("6.2 Header Image").style("Heading2");
489 let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
490 doc.set_header_image(
491 &hdr_img,
492 "header_logo.png",
493 Length::inches(2.0),
494 Length::inches(0.2),
495 );
496 doc.add_paragraph("The header now contains an inline image (check the top of this page).");
497
498 doc.add_paragraph("");
499 doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
500
501 // ── SECTION 7: CONTENT MANIPULATION ──
502 doc.add_paragraph("").page_break_before(true);
503 doc.add_paragraph("7. Content Manipulation")
504 .style("Heading1");
505
506 doc.add_paragraph("7.1 Placeholder Replacement")
507 .style("Heading2");
508 doc.add_paragraph("Customer: {{customer}}");
509 doc.add_paragraph("Date: {{date}}");
510 doc.add_paragraph("Reference: {{ref_number}}");
511 {
512 let mut tbl = doc
513 .add_table(2, 2)
514 .borders(BorderStyle::Single, 4, "000000");
515 tbl.cell(0, 0).unwrap().set_text("Project");
516 tbl.cell(0, 0).unwrap().shading("D6E4F0");
517 tbl.cell(0, 1).unwrap().set_text("{{project}}");
518 tbl.cell(1, 0).unwrap().set_text("Status");
519 tbl.cell(1, 0).unwrap().shading("D6E4F0");
520 tbl.cell(1, 1).unwrap().set_text("{{status}}");
521 }
522
523 let mut replacements = HashMap::new();
524 replacements.insert("{{customer}}", "Tensorbee Inc.");
525 replacements.insert("{{date}}", "February 22, 2026");
526 replacements.insert("{{ref_number}}", "TB-2026-001");
527 replacements.insert("{{project}}", "Infrastructure Upgrade");
528 replacements.insert("{{status}}", "In Progress");
529 let n = doc.replace_all(&replacements);
530 doc.add_paragraph(&format!("({n} placeholders replaced above)"));
531
532 doc.add_paragraph("");
533
534 doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
535 doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
536 let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
537 doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
538
539 doc.add_paragraph("");
540
541 doc.add_paragraph("7.3 Content Insertion").style("Heading2");
542 doc.add_paragraph("Section A: First content.");
543 doc.add_paragraph("Section C: Third content.");
544 if let Some(idx) = doc.find_content_index("Section C") {
545 doc.insert_paragraph(
546 idx,
547 "Section B: Inserted between A and C via find_content_index().",
548 );
549 }
550
551 // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
552 doc.add_paragraph("").section_break(SectionBreak::NextPage);
553 doc.add_paragraph("8. Section Breaks & Orientation")
554 .style("Heading1");
555 doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
556
557 {
558 let mut tbl = doc
559 .add_table(3, 7)
560 .borders(BorderStyle::Single, 4, "2E75B6");
561 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
562 for (c, h) in headers.iter().enumerate() {
563 tbl.cell(0, c).unwrap().set_text(h);
564 tbl.cell(0, c).unwrap().shading("2E75B6");
565 }
566 let data = [
567 [
568 "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
569 ],
570 [
571 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
572 ],
573 ];
574 for (r, row) in data.iter().enumerate() {
575 for (c, v) in row.iter().enumerate() {
576 tbl.cell(r + 1, c).unwrap().set_text(v);
577 }
578 }
579 }
580
581 doc.add_paragraph("")
582 .section_break(SectionBreak::NextPage)
583 .section_landscape();
584
585 // ── SECTION 9: CUSTOM STYLES ──
586 doc.add_paragraph("9. Custom Styles").style("Heading1");
587 doc.add_style(
588 StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
589 .based_on("Normal")
590 .paragraph_properties({
591 let mut ppr = rdocx_oxml::properties::CT_PPr::default();
592 ppr.shading = Some(rdocx_oxml::properties::CT_Shd {
593 val: "clear".to_string(),
594 color: None,
595 fill: Some("FFF2CC".to_string()),
596 });
597 ppr
598 })
599 .run_properties({
600 let mut rpr = rdocx_oxml::properties::CT_RPr::default();
601 rpr.bold = Some(true);
602 rpr.color = Some("C45911".to_string());
603 rpr
604 }),
605 );
606 doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
607 .style("CustomHighlight");
608
609 doc.add_paragraph("");
610
611 // ── SECTION 10: DOCUMENT INTELLIGENCE ──
612 doc.add_paragraph("10. Document Intelligence API")
613 .style("Heading1");
614
615 let wc = doc.word_count();
616 let hc = doc.headings().len();
617 let ic = doc.images().len();
618 let lc = doc.links().len();
619 doc.add_paragraph(&format!("Word count: {wc}"));
620 doc.add_paragraph(&format!("Heading count: {hc}"));
621 doc.add_paragraph(&format!("Image count: {ic}"));
622 doc.add_paragraph(&format!("Link count: {lc}"));
623
624 let outline = doc.document_outline();
625 doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
626
627 let issues = doc.audit_accessibility();
628 doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
629 for issue in &issues {
630 doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
631 }
632
633 // ── SECTION 11: DOCUMENT MERGING ──
634 doc.add_paragraph("");
635 doc.add_paragraph("11. Document Merging").style("Heading1");
636 {
637 let mut other = Document::new();
638 other.add_paragraph("This paragraph was merged from another document using append().");
639 doc.append(&other);
640 }
641
642 doc.add_paragraph("");
643
644 // ── SUMMARY ──
645 doc.add_paragraph("Summary of Demonstrated Features")
646 .style("Heading1");
647 let features = [
648 "Page setup: size, margins, header/footer distance, gutter",
649 "Document metadata: title, author, subject, keywords",
650 "Headers and footers: text, images, different first page",
651 "Background images (full-page behind text)",
652 "Table of Contents generation with bookmarks",
653 "Text formatting: bold, italic, underline styles, strike, color, size, font",
654 "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
655 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
656 "Pagination controls: keep-with-next, keep-together, widow control",
657 "Bullet and numbered lists with nesting",
658 "Tab stops with dot/underscore/hyphen leaders",
659 "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
660 "Row properties: header rows, exact height, cant-split",
661 "Cell properties: width, no-wrap, vertical alignment",
662 "Inline images and header images",
663 "Placeholder replacement (single and batch)",
664 "Regex-based find and replace",
665 "Content insertion at specific positions",
666 "Section breaks with mixed portrait/landscape",
667 "Custom paragraph and character styles",
668 "Document intelligence: word count, headings, outline, images, links",
669 "Accessibility audit",
670 "Document merging (append)",
671 "Export: DOCX, PDF, HTML, Markdown",
672 ];
673 for f in &features {
674 doc.add_bullet_list_item(f, 0);
675 }
676 doc.add_paragraph("");
677 doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
678 .alignment(Alignment::Center)
679 .shading("E2EFDA")
680 .border_all(BorderStyle::Single, 2, "00AA00");
681
682 doc
683}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?
34fn generate_feature_showcase(path: &Path) {
35 let mut doc = Document::new();
36
37 // =========================================================================
38 // PAGE SETUP & METADATA
39 // =========================================================================
40 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
41 doc.set_margins(
42 Length::inches(1.0), // top
43 Length::inches(1.0), // right
44 Length::inches(1.0), // bottom
45 Length::inches(1.0), // left
46 );
47 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
48 doc.set_gutter(Length::twips(0));
49
50 doc.set_title("rdocx Feature Showcase");
51 doc.set_author("rdocx Sample Generator");
52 doc.set_subject("Comprehensive feature demonstration");
53 doc.set_keywords("rdocx, docx, rust, sample");
54
55 // Header & Footer
56 doc.set_header("rdocx Feature Showcase");
57 doc.set_footer("Generated by rdocx — Page");
58
59 // Different first page header
60 doc.set_different_first_page(true);
61 doc.set_first_page_header("rdocx");
62 doc.set_first_page_footer("Feature Showcase — Cover Page");
63
64 // =========================================================================
65 // PAGE 1: COVER PAGE — background image, run formatting
66 // =========================================================================
67 let bg_cover = create_sample_png(612, 792, [30, 60, 120]);
68 doc.add_background_image(&bg_cover, "cover_bg.png");
69
70 doc.add_paragraph(""); // spacer
71 doc.add_paragraph(""); // spacer
72 doc.add_paragraph(""); // spacer
73
74 {
75 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
76 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
77 }
78 {
79 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
80 p.add_run("Feature Showcase")
81 .size(28.0)
82 .color("FFFFFF")
83 .italic(true);
84 }
85
86 doc.add_paragraph(""); // spacer
87
88 {
89 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
90 p.add_run("A comprehensive demonstration of every feature")
91 .size(14.0)
92 .color("CCDDFF");
93 }
94 {
95 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
96 p.add_run("provided by the rdocx Rust crate for DOCX generation.")
97 .size(14.0)
98 .color("CCDDFF");
99 }
100
101 // =========================================================================
102 // PAGE 2: TEXT FORMATTING
103 // =========================================================================
104 doc.add_paragraph("").page_break_before(true);
105
106 doc.add_paragraph("1. Text Formatting").style("Heading1");
107
108 doc.add_paragraph("This section demonstrates paragraph and run-level formatting options.");
109 doc.add_paragraph("");
110
111 // --- Paragraph alignment ---
112 doc.add_paragraph("Paragraph Alignment").style("Heading2");
113
114 doc.add_paragraph("This paragraph is left-aligned (the default).")
115 .alignment(Alignment::Left);
116 doc.add_paragraph("This paragraph is center-aligned.")
117 .alignment(Alignment::Center);
118 doc.add_paragraph("This paragraph is right-aligned.")
119 .alignment(Alignment::Right);
120 doc.add_paragraph(
121 "This paragraph is justified. To demonstrate justified text properly, it needs \
122 to be long enough to span multiple lines so the word spacing adjustment is visible \
123 across the full width of the text area on the page.",
124 )
125 .alignment(Alignment::Justify);
126
127 doc.add_paragraph("");
128
129 // --- Run formatting ---
130 doc.add_paragraph("Run Formatting").style("Heading2");
131
132 {
133 let mut p = doc.add_paragraph("");
134 p.add_run("Bold text").bold(true);
135 p.add_run(" | ");
136 p.add_run("Italic text").italic(true);
137 p.add_run(" | ");
138 p.add_run("Bold + Italic").bold(true).italic(true);
139 }
140 {
141 let mut p = doc.add_paragraph("");
142 p.add_run("Single underline").underline(true);
143 p.add_run(" | ");
144 p.add_run("Strikethrough").strike(true);
145 p.add_run(" | ");
146 p.add_run("Double strikethrough").double_strike(true);
147 }
148 {
149 let mut p = doc.add_paragraph("");
150 p.add_run("Red text").color("FF0000");
151 p.add_run(" | ");
152 p.add_run("Blue text").color("0000FF");
153 p.add_run(" | ");
154 p.add_run("Green text").color("00AA00");
155 p.add_run(" | ");
156 p.add_run("Highlighted").highlight("FFFF00");
157 }
158 {
159 let mut p = doc.add_paragraph("");
160 p.add_run("8pt small").size(8.0);
161 p.add_run(" | ");
162 p.add_run("11pt normal").size(11.0);
163 p.add_run(" | ");
164 p.add_run("16pt large").size(16.0);
165 p.add_run(" | ");
166 p.add_run("24pt extra-large").size(24.0);
167 }
168 {
169 let mut p = doc.add_paragraph("");
170 p.add_run("Arial font").font("Arial");
171 p.add_run(" | ");
172 p.add_run("Times New Roman font").font("Times New Roman");
173 p.add_run(" | ");
174 p.add_run("Courier New font").font("Courier New");
175 }
176 {
177 let mut p = doc.add_paragraph("");
178 p.add_run("Normal");
179 p.add_run(" H").size(11.0);
180 p.add_run("2").subscript();
181 p.add_run("O (subscript)").size(11.0);
182 p.add_run(" | E = mc").size(11.0);
183 p.add_run("2").superscript();
184 p.add_run(" (superscript)").size(11.0);
185 }
186 {
187 let mut p = doc.add_paragraph("");
188 p.add_run("ALL CAPS").all_caps(true);
189 p.add_run(" | ");
190 p.add_run("Small Caps").small_caps(true);
191 p.add_run(" | ");
192 p.add_run("Expanded spacing")
193 .character_spacing(Length::twips(40));
194 }
195
196 doc.add_paragraph("");
197
198 // --- Paragraph formatting ---
199 doc.add_paragraph("Paragraph Formatting").style("Heading2");
200
201 doc.add_paragraph("Paragraph with shading (light green background)")
202 .shading("E2EFDA");
203
204 doc.add_paragraph("Paragraph with bottom border")
205 .border_bottom(BorderStyle::Single, 6, "2E75B6");
206
207 doc.add_paragraph("Paragraph with all borders")
208 .border_all(BorderStyle::Single, 4, "FF0000");
209
210 doc.add_paragraph("Paragraph with 1-inch left indent and hanging indent")
211 .indent_left(Length::inches(1.0))
212 .hanging_indent(Length::inches(0.5));
213
214 doc.add_paragraph("Paragraph with first-line indent of 0.5 inches")
215 .first_line_indent(Length::inches(0.5));
216
217 doc.add_paragraph("Paragraph with extra space before (24pt) and after (12pt)")
218 .space_before(Length::pt(24.0))
219 .space_after(Length::pt(12.0));
220
221 doc.add_paragraph(
222 "Paragraph with double line spacing. This text should have extra vertical \
223 space between lines to demonstrate the line_spacing_multiple setting.",
224 )
225 .line_spacing_multiple(2.0);
226
227 doc.add_paragraph("Paragraph with keep-with-next (won't break from the next paragraph)")
228 .keep_with_next(true);
229 doc.add_paragraph("(This stays with the paragraph above.)");
230
231 // =========================================================================
232 // PAGE 3: LISTS & TAB STOPS
233 // =========================================================================
234 doc.add_paragraph("").page_break_before(true);
235
236 doc.add_paragraph("2. Lists").style("Heading1");
237
238 doc.add_paragraph("Bullet List").style("Heading2");
239
240 doc.add_bullet_list_item("First bullet item", 0);
241 doc.add_bullet_list_item("Second bullet item", 0);
242 doc.add_bullet_list_item("Nested level 1", 1);
243 doc.add_bullet_list_item("Nested level 2", 2);
244 doc.add_bullet_list_item("Back to level 1", 1);
245 doc.add_bullet_list_item("Third bullet item", 0);
246
247 doc.add_paragraph("");
248
249 doc.add_paragraph("Numbered List").style("Heading2");
250
251 doc.add_numbered_list_item("First numbered item", 0);
252 doc.add_numbered_list_item("Second numbered item", 0);
253 doc.add_numbered_list_item("Sub-item A", 1);
254 doc.add_numbered_list_item("Sub-item B", 1);
255 doc.add_numbered_list_item("Third numbered item", 0);
256
257 doc.add_paragraph("");
258
259 // --- Tab stops ---
260 doc.add_paragraph("Tab Stops").style("Heading2");
261
262 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
263 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
264 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
265 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
266 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
267
268 doc.add_paragraph("Item\t........\tPrice")
269 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
270 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
271 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
272
273 doc.add_paragraph("Widget A\t........\t$19.99")
274 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
275 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
276 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
277
278 doc.add_paragraph("Gadget B\t________\t$249.50")
279 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
280 .add_tab_stop_with_leader(
281 TabAlignment::Right,
282 Length::inches(4.0),
283 TabLeader::Underscore,
284 )
285 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
286
287 // =========================================================================
288 // PAGE 4: TABLES
289 // =========================================================================
290 doc.add_paragraph("").page_break_before(true);
291
292 doc.add_paragraph("3. Tables").style("Heading1");
293
294 // --- Basic table with borders ---
295 doc.add_paragraph("Basic Table with Borders")
296 .style("Heading2");
297
298 {
299 let mut tbl = doc.add_table(4, 3);
300 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
301
302 // Header row
303 for col in 0..3 {
304 tbl.cell(0, col).unwrap().shading("2E75B6");
305 }
306 tbl.cell(0, 0).unwrap().set_text("Name");
307 tbl.cell(0, 1).unwrap().set_text("Role");
308 tbl.cell(0, 2).unwrap().set_text("Location");
309
310 tbl.cell(1, 0).unwrap().set_text("Alice Johnson");
311 tbl.cell(1, 1).unwrap().set_text("Engineering Lead");
312 tbl.cell(1, 2).unwrap().set_text("New York");
313
314 tbl.cell(2, 0).unwrap().set_text("Bob Smith");
315 tbl.cell(2, 1).unwrap().set_text("Product Manager");
316 tbl.cell(2, 2).unwrap().set_text("San Francisco");
317
318 tbl.cell(3, 0).unwrap().set_text("Carol Davis");
319 tbl.cell(3, 1).unwrap().set_text("Designer");
320 tbl.cell(3, 2).unwrap().set_text("London");
321 }
322
323 doc.add_paragraph("");
324
325 // --- Table with cell merging ---
326 doc.add_paragraph("Table with Cell Merging & Vertical Alignment")
327 .style("Heading2");
328
329 {
330 let mut tbl = doc.add_table(4, 4);
331 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
332 tbl = tbl.width_pct(100.0);
333
334 // Header spanning all columns
335 tbl.cell(0, 0).unwrap().set_text("Quarterly Revenue Report");
336 tbl.cell(0, 0).unwrap().shading("1F4E79");
337 tbl.cell(0, 0).unwrap().grid_span(4);
338
339 // Sub-header
340 tbl.cell(1, 0).unwrap().set_text("Region");
341 tbl.cell(1, 0).unwrap().shading("D6E4F0");
342 tbl.cell(1, 1).unwrap().set_text("Q1");
343 tbl.cell(1, 1).unwrap().shading("D6E4F0");
344 tbl.cell(1, 2).unwrap().set_text("Q2");
345 tbl.cell(1, 2).unwrap().shading("D6E4F0");
346 tbl.cell(1, 3).unwrap().set_text("Total");
347 tbl.cell(1, 3).unwrap().shading("D6E4F0");
348
349 // Data
350 tbl.cell(2, 0).unwrap().set_text("North America");
351 tbl.cell(2, 1).unwrap().set_text("$2.4M");
352 tbl.cell(2, 2).unwrap().set_text("$2.7M");
353 tbl.cell(2, 3).unwrap().set_text("$5.1M");
354
355 tbl.cell(3, 0).unwrap().set_text("Europe");
356 tbl.cell(3, 1).unwrap().set_text("$1.8M");
357 tbl.cell(3, 2).unwrap().set_text("$2.0M");
358 tbl.cell(3, 3).unwrap().set_text("$3.8M");
359
360 // Vertical alignment on data cells
361 tbl.cell(2, 3)
362 .unwrap()
363 .vertical_alignment(VerticalAlignment::Center);
364 tbl.cell(3, 3)
365 .unwrap()
366 .vertical_alignment(VerticalAlignment::Bottom);
367 }
368
369 doc.add_paragraph("");
370
371 // --- Table with vertical merge ---
372 doc.add_paragraph("Table with Vertical Merge")
373 .style("Heading2");
374
375 {
376 let mut tbl = doc.add_table(4, 3);
377 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
378
379 tbl.cell(0, 0).unwrap().set_text("Category");
380 tbl.cell(0, 0).unwrap().shading("E2EFDA");
381 tbl.cell(0, 1).unwrap().set_text("Item");
382 tbl.cell(0, 1).unwrap().shading("E2EFDA");
383 tbl.cell(0, 2).unwrap().set_text("Price");
384 tbl.cell(0, 2).unwrap().shading("E2EFDA");
385
386 // "Hardware" spans rows 1-2
387 tbl.cell(1, 0).unwrap().set_text("Hardware");
388 tbl.cell(1, 0).unwrap().v_merge_restart();
389 tbl.cell(1, 1).unwrap().set_text("Laptop");
390 tbl.cell(1, 2).unwrap().set_text("$1,200");
391
392 tbl.cell(2, 0).unwrap().v_merge_continue();
393 tbl.cell(2, 1).unwrap().set_text("Monitor");
394 tbl.cell(2, 2).unwrap().set_text("$450");
395
396 // "Software" on row 3
397 tbl.cell(3, 0).unwrap().set_text("Software");
398 tbl.cell(3, 1).unwrap().set_text("IDE License");
399 tbl.cell(3, 2).unwrap().set_text("$200/yr");
400 }
401
402 doc.add_paragraph("");
403
404 // --- Nested table ---
405 doc.add_paragraph("Nested Table").style("Heading2");
406
407 {
408 let mut tbl = doc.add_table(2, 2);
409 tbl = tbl.borders(BorderStyle::Single, 6, "2E75B6");
410
411 tbl.cell(0, 0).unwrap().set_text("Outer Cell (0,0)");
412 tbl.cell(0, 1).unwrap().set_text("Outer Cell (0,1)");
413 tbl.cell(1, 0).unwrap().set_text("Outer Cell (1,0)");
414
415 // Nested table inside cell (1,1)
416 {
417 let mut cell = tbl.cell(1, 1).unwrap();
418 cell.set_text("Contains nested table:");
419 let mut nested = cell.add_table(2, 2);
420 nested = nested.borders(BorderStyle::Single, 2, "FF6600");
421 nested.cell(0, 0).unwrap().set_text("Inner A");
422 nested.cell(0, 1).unwrap().set_text("Inner B");
423 nested.cell(1, 0).unwrap().set_text("Inner C");
424 nested.cell(1, 1).unwrap().set_text("Inner D");
425 }
426 }
427
428 // =========================================================================
429 // PAGE 5: IMAGES
430 // =========================================================================
431 doc.add_paragraph("").page_break_before(true);
432
433 doc.add_paragraph("4. Images").style("Heading1");
434
435 doc.add_paragraph("Inline Image").style("Heading2");
436
437 doc.add_paragraph("Below is an inline image (200x50 pixels, blue gradient):");
438 let inline_img = create_sample_png(200, 50, [0, 80, 200]);
439 doc.add_picture(
440 &inline_img,
441 "inline_chart.png",
442 Length::inches(3.0),
443 Length::inches(0.75),
444 );
445
446 doc.add_paragraph("");
447
448 doc.add_paragraph("Header Image").style("Heading2");
449
450 // Replace the text-only header with an image header
451 let header_img = create_sample_png(400, 40, [40, 40, 40]);
452 doc.set_header_image(
453 &header_img,
454 "header_logo.png",
455 Length::inches(2.0),
456 Length::inches(0.2),
457 );
458
459 doc.add_paragraph(
460 "The document header has been replaced with an inline image. \
461 Check the header area at the top of this page.",
462 );
463
464 doc.add_paragraph("");
465 doc.add_paragraph(
466 "Note: The cover page uses a full-page background image behind the text, \
467 demonstrated on page 1 via add_background_image().",
468 );
469
470 // =========================================================================
471 // PAGE 6: CONTENT MANIPULATION — placeholder replacement, insertion
472 // =========================================================================
473 doc.add_paragraph("").page_break_before(true);
474
475 doc.add_paragraph("5. Content Manipulation")
476 .style("Heading1");
477
478 // --- Placeholder replacement ---
479 doc.add_paragraph("Placeholder Replacement")
480 .style("Heading2");
481
482 doc.add_paragraph(
483 "Before replacement, this document contained {{customer}} and {{date}} placeholders.",
484 );
485
486 {
487 let mut p = doc.add_paragraph("");
488 p.add_run("Customer: ").bold(true);
489 p.add_run("{{customer}}");
490 }
491 {
492 let mut p = doc.add_paragraph("");
493 p.add_run("Date: ").bold(true);
494 p.add_run("{{date}}");
495 }
496 doc.add_paragraph("Reference: {{ref_number}}");
497
498 // Table with placeholders
499 {
500 let mut tbl = doc.add_table(3, 2);
501 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
502 tbl.cell(0, 0).unwrap().set_text("Field");
503 tbl.cell(0, 0).unwrap().shading("D6E4F0");
504 tbl.cell(0, 1).unwrap().set_text("Value");
505 tbl.cell(0, 1).unwrap().shading("D6E4F0");
506 tbl.cell(1, 0).unwrap().set_text("Project");
507 tbl.cell(1, 1).unwrap().set_text("{{project}}");
508 tbl.cell(2, 0).unwrap().set_text("Status");
509 tbl.cell(2, 1).unwrap().set_text("{{status}}");
510 }
511
512 // Perform replacements
513 let mut replacements = HashMap::new();
514 replacements.insert("{{customer}}", "Acme Corporation");
515 replacements.insert("{{date}}", "February 22, 2026");
516 replacements.insert("{{ref_number}}", "REF-2026-001");
517 replacements.insert("{{project}}", "Infrastructure Upgrade");
518 replacements.insert("{{status}}", "In Progress");
519 let replace_count = doc.replace_all(&replacements);
520
521 doc.add_paragraph("");
522 doc.add_paragraph(&format!(
523 "(Replaced {} placeholders above — in body text and table cells)",
524 replace_count
525 ));
526
527 doc.add_paragraph("");
528
529 // --- Content insertion ---
530 doc.add_paragraph("Content Insertion").style("Heading2");
531
532 doc.add_paragraph("Section A: First section of content.");
533 doc.add_paragraph("Section C: Third section of content.");
534
535 // Insert "Section B" between A and C
536 if let Some(idx) = doc.find_content_index("Section C") {
537 doc.insert_paragraph(
538 idx,
539 "Section B: Inserted between A and C using find_content_index().",
540 );
541 }
542
543 doc.add_paragraph("");
544 doc.add_paragraph(
545 "The paragraph above ('Section B') was inserted at a specific position \
546 using find_content_index() + insert_paragraph().",
547 );
548
549 // =========================================================================
550 // PAGE 7: LANDSCAPE — section break, wide table
551 // =========================================================================
552 doc.add_paragraph("").section_break(SectionBreak::NextPage);
553
554 doc.add_paragraph("6. Mixed Page Orientation")
555 .style("Heading1");
556
557 doc.add_paragraph(
558 "This page is in LANDSCAPE orientation. It was created using a section break \
559 followed by section_landscape(). This is useful for wide tables or charts.",
560 );
561
562 doc.add_paragraph("");
563
564 // Wide table for landscape
565 {
566 let mut tbl = doc.add_table(4, 7);
567 tbl = tbl.borders(BorderStyle::Single, 4, "2E75B6");
568
569 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
570 for (col, h) in headers.iter().enumerate() {
571 tbl.cell(0, col).unwrap().set_text(h);
572 tbl.cell(0, col).unwrap().shading("2E75B6");
573 }
574
575 let data = [
576 [
577 "North America",
578 "$1.2M",
579 "$1.3M",
580 "$1.4M",
581 "$1.5M",
582 "$1.6M",
583 "$7.0M",
584 ],
585 [
586 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
587 ],
588 [
589 "Asia Pacific",
590 "$0.5M",
591 "$0.6M",
592 "$0.7M",
593 "$0.7M",
594 "$0.8M",
595 "$3.3M",
596 ],
597 ];
598 for (row_idx, row_data) in data.iter().enumerate() {
599 for (col, val) in row_data.iter().enumerate() {
600 tbl.cell(row_idx + 1, col).unwrap().set_text(val);
601 }
602 }
603 }
604
605 // End landscape, return to portrait
606 doc.add_paragraph("")
607 .section_break(SectionBreak::NextPage)
608 .section_landscape();
609
610 // =========================================================================
611 // PAGE 8: BACK TO PORTRAIT — styles, final notes
612 // =========================================================================
613 doc.add_paragraph("7. Custom Styles & Summary")
614 .style("Heading1");
615
616 doc.add_paragraph(
617 "This final page is back in portrait orientation after a section break. \
618 The document has demonstrated:",
619 );
620
621 doc.add_bullet_list_item(
622 "Page setup: size, margins, header/footer distance, gutter",
623 0,
624 );
625 doc.add_bullet_list_item("Document metadata: title, author, subject, keywords", 0);
626 doc.add_bullet_list_item("Headers and footers: text, images, different first page", 0);
627 doc.add_bullet_list_item("Background images: full-page behind text", 0);
628 doc.add_bullet_list_item(
629 "Text formatting: bold, italic, underline, strike, color, size, font",
630 0,
631 );
632 doc.add_bullet_list_item(
633 "Advanced run formatting: superscript, subscript, caps, spacing",
634 0,
635 );
636 doc.add_bullet_list_item(
637 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
638 0,
639 );
640 doc.add_bullet_list_item("Bullet and numbered lists with nesting levels", 0);
641 doc.add_bullet_list_item("Tab stops with dot/underscore leaders", 0);
642 doc.add_bullet_list_item(
643 "Tables: borders, shading, column spans, row spans, nesting",
644 0,
645 );
646 doc.add_bullet_list_item("Vertical alignment in table cells", 0);
647 doc.add_bullet_list_item("Inline images", 0);
648 doc.add_bullet_list_item("Placeholder replacement in body and table cells", 0);
649 doc.add_bullet_list_item("Content insertion at specific positions", 0);
650 doc.add_bullet_list_item(
651 "Section breaks with mixed portrait/landscape orientation",
652 0,
653 );
654
655 doc.add_paragraph("");
656 doc.add_paragraph("All features above were built entirely from scratch using the rdocx API.")
657 .alignment(Alignment::Center)
658 .shading("E2EFDA")
659 .border_all(BorderStyle::Single, 2, "00AA00");
660
661 doc.save(path).unwrap();
662}More examples
86fn generate_feature_showcase(_samples_dir: &Path) -> Document {
87 let mut doc = Document::new();
88
89 // ── Page Setup & Metadata ──
90 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
91 doc.set_margins(
92 Length::inches(1.0),
93 Length::inches(1.0),
94 Length::inches(1.0),
95 Length::inches(1.0),
96 );
97 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
98 doc.set_gutter(Length::twips(0));
99 doc.set_title("rdocx Feature Showcase");
100 doc.set_author("rdocx Sample Generator");
101 doc.set_subject("Comprehensive feature demonstration");
102 doc.set_keywords("rdocx, docx, rust, sample, showcase");
103
104 // Headers & Footers with different first page
105 doc.set_different_first_page(true);
106 doc.set_first_page_header("rdocx");
107 doc.set_first_page_footer("Feature Showcase — Cover Page");
108 doc.set_header("rdocx Feature Showcase");
109 doc.set_footer("Generated by rdocx");
110
111 // ── COVER PAGE ──
112 let bg = create_sample_png(612, 792, [20, 45, 90]);
113 doc.add_background_image(&bg, "cover_bg.png");
114
115 for _ in 0..3 {
116 doc.add_paragraph("");
117 }
118 {
119 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
120 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
121 }
122 {
123 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
124 p.add_run("Complete Feature Showcase")
125 .size(28.0)
126 .color("FFFFFF")
127 .italic(true);
128 }
129 doc.add_paragraph("");
130 {
131 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
132 p.add_run("Every feature of the rdocx Rust library")
133 .size(13.0)
134 .color("B0C4DE");
135 }
136 {
137 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
138 p.add_run("demonstrated in a single document.")
139 .size(13.0)
140 .color("B0C4DE");
141 }
142
143 // ── TABLE OF CONTENTS ──
144 doc.add_paragraph("").page_break_before(true);
145 doc.insert_toc(doc.content_count(), 3);
146
147 // ── SECTION 1: TEXT FORMATTING ──
148 doc.add_paragraph("").page_break_before(true);
149 doc.add_paragraph("1. Text Formatting").style("Heading1");
150
151 // Paragraph Alignment
152 doc.add_paragraph("1.1 Paragraph Alignment")
153 .style("Heading2");
154 doc.add_paragraph("Left-aligned paragraph (default).")
155 .alignment(Alignment::Left);
156 doc.add_paragraph("Center-aligned paragraph.")
157 .alignment(Alignment::Center);
158 doc.add_paragraph("Right-aligned paragraph.")
159 .alignment(Alignment::Right);
160 doc.add_paragraph(
161 "Justified paragraph — this text is long enough to demonstrate how justified alignment \
162 distributes extra space across word gaps so lines fill the full width of the text area.",
163 )
164 .alignment(Alignment::Justify);
165
166 doc.add_paragraph("");
167
168 // Run Formatting
169 doc.add_paragraph("1.2 Run Formatting").style("Heading2");
170 {
171 let mut p = doc.add_paragraph("");
172 p.add_run("Bold").bold(true);
173 p.add_run(" | ");
174 p.add_run("Italic").italic(true);
175 p.add_run(" | ");
176 p.add_run("Bold Italic").bold(true).italic(true);
177 p.add_run(" | ");
178 p.add_run("Underline").underline(true);
179 p.add_run(" | ");
180 p.add_run("Strikethrough").strike(true);
181 p.add_run(" | ");
182 p.add_run("Double Strike").double_strike(true);
183 }
184 {
185 let mut p = doc.add_paragraph("");
186 p.add_run("Red").color("FF0000");
187 p.add_run(" | ");
188 p.add_run("Blue").color("0000FF");
189 p.add_run(" | ");
190 p.add_run("Green").color("00AA00");
191 p.add_run(" | ");
192 p.add_run("Highlighted").highlight("FFFF00");
193 }
194 {
195 let mut p = doc.add_paragraph("");
196 p.add_run("8pt").size(8.0);
197 p.add_run(" | ");
198 p.add_run("11pt").size(11.0);
199 p.add_run(" | ");
200 p.add_run("16pt").size(16.0);
201 p.add_run(" | ");
202 p.add_run("24pt").size(24.0);
203 }
204 {
205 let mut p = doc.add_paragraph("");
206 p.add_run("Arial").font("Arial");
207 p.add_run(" | ");
208 p.add_run("Times New Roman").font("Times New Roman");
209 p.add_run(" | ");
210 p.add_run("Courier New").font("Courier New");
211 }
212 {
213 let mut p = doc.add_paragraph("");
214 p.add_run("H");
215 p.add_run("2").subscript();
216 p.add_run("O (subscript) | E=mc");
217 p.add_run("2").superscript();
218 p.add_run(" (superscript)");
219 }
220 {
221 let mut p = doc.add_paragraph("");
222 p.add_run("ALL CAPS").all_caps(true);
223 p.add_run(" | ");
224 p.add_run("Small Caps").small_caps(true);
225 p.add_run(" | ");
226 p.add_run("Expanded").character_spacing(Length::twips(40));
227 p.add_run(" | ");
228 p.add_run("Hidden text (not visible)").hidden(true);
229 }
230
231 doc.add_paragraph("");
232
233 // Underline Styles
234 doc.add_paragraph("1.3 Underline Styles").style("Heading2");
235 {
236 let mut p = doc.add_paragraph("");
237 p.add_run("Single ")
238 .underline_style(rdocx::UnderlineStyle::Single);
239 p.add_run("Double ")
240 .underline_style(rdocx::UnderlineStyle::Double);
241 p.add_run("Thick ")
242 .underline_style(rdocx::UnderlineStyle::Thick);
243 p.add_run("Dotted ")
244 .underline_style(rdocx::UnderlineStyle::Dotted);
245 p.add_run("Dash ")
246 .underline_style(rdocx::UnderlineStyle::Dash);
247 p.add_run("Wave ")
248 .underline_style(rdocx::UnderlineStyle::Wave);
249 }
250
251 // ── SECTION 2: PARAGRAPH FORMATTING ──
252 doc.add_paragraph("").page_break_before(true);
253 doc.add_paragraph("2. Paragraph Formatting")
254 .style("Heading1");
255
256 doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
257 doc.add_paragraph("Paragraph with light green shading")
258 .shading("E2EFDA");
259 doc.add_paragraph("Paragraph with bottom border")
260 .border_bottom(BorderStyle::Single, 6, "2E75B6");
261 doc.add_paragraph("Paragraph with all borders (red)")
262 .border_all(BorderStyle::Single, 4, "FF0000");
263
264 doc.add_paragraph("2.2 Indentation").style("Heading2");
265 doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
266 .indent_left(Length::inches(1.0))
267 .hanging_indent(Length::inches(0.5));
268 doc.add_paragraph("0.5-inch first-line indent on this paragraph")
269 .first_line_indent(Length::inches(0.5));
270 doc.add_paragraph("Both left and right indent (0.75 inches each)")
271 .indent_left(Length::inches(0.75))
272 .indent_right(Length::inches(0.75));
273
274 doc.add_paragraph("2.3 Spacing & Line Height")
275 .style("Heading2");
276 doc.add_paragraph("Extra space before (24pt) and after (12pt)")
277 .space_before(Length::pt(24.0))
278 .space_after(Length::pt(12.0));
279 doc.add_paragraph(
280 "Double line spacing paragraph. This text should have extra vertical space between \
281 lines to demonstrate line_spacing_multiple(2.0).",
282 )
283 .line_spacing_multiple(2.0);
284 doc.add_paragraph("Exact 20pt line spacing (fixed height).")
285 .line_spacing(20.0);
286
287 doc.add_paragraph("2.4 Pagination Controls")
288 .style("Heading2");
289 doc.add_paragraph("keep_with_next — this paragraph stays with the next")
290 .keep_with_next(true);
291 doc.add_paragraph("(This paragraph was kept with the one above.)");
292 doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
293 .keep_together(true);
294 doc.add_paragraph("widow_control — prevents widow/orphan lines")
295 .widow_control(true);
296
297 // ── SECTION 3: LISTS ──
298 doc.add_paragraph("").page_break_before(true);
299 doc.add_paragraph("3. Lists").style("Heading1");
300
301 doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
302 doc.add_bullet_list_item("First item", 0);
303 doc.add_bullet_list_item("Second item", 0);
304 doc.add_bullet_list_item("Nested level 1", 1);
305 doc.add_bullet_list_item("Nested level 2", 2);
306 doc.add_bullet_list_item("Back to level 1", 1);
307 doc.add_bullet_list_item("Third item", 0);
308
309 doc.add_paragraph("");
310
311 doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
312 doc.add_numbered_list_item("First numbered item", 0);
313 doc.add_numbered_list_item("Second numbered item", 0);
314 doc.add_numbered_list_item("Sub-item A", 1);
315 doc.add_numbered_list_item("Sub-item B", 1);
316 doc.add_numbered_list_item("Third numbered item", 0);
317
318 // ── SECTION 4: TAB STOPS ──
319 doc.add_paragraph("");
320 doc.add_paragraph("4. Tab Stops").style("Heading1");
321
322 doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
323 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
324 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
325 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
326 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
327 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
328
329 doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
330 doc.add_paragraph("Item\t\tPrice")
331 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
332 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
333 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
334 doc.add_paragraph("Widget\t\t$19.99")
335 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
336 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
337 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
338 doc.add_paragraph("Gadget\t\t$249.50")
339 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
340 .add_tab_stop_with_leader(
341 TabAlignment::Right,
342 Length::inches(4.0),
343 TabLeader::Underscore,
344 )
345 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
346
347 // ── SECTION 5: TABLES ──
348 doc.add_paragraph("").page_break_before(true);
349 doc.add_paragraph("5. Tables").style("Heading1");
350
351 doc.add_paragraph("5.1 Basic Table").style("Heading2");
352 {
353 let mut tbl = doc
354 .add_table(4, 3)
355 .borders(BorderStyle::Single, 4, "000000");
356 for col in 0..3 {
357 tbl.cell(0, col).unwrap().shading("2E75B6");
358 }
359 tbl.cell(0, 0).unwrap().set_text("Name");
360 tbl.cell(0, 1).unwrap().set_text("Role");
361 tbl.cell(0, 2).unwrap().set_text("Location");
362 tbl.cell(1, 0).unwrap().set_text("Walter White");
363 tbl.cell(1, 1).unwrap().set_text("CEO");
364 tbl.cell(1, 2).unwrap().set_text("Albuquerque");
365 tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
366 tbl.cell(2, 1).unwrap().set_text("CTO");
367 tbl.cell(2, 2).unwrap().set_text("Remote");
368 tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
369 tbl.cell(3, 1).unwrap().set_text("Security");
370 tbl.cell(3, 2).unwrap().set_text("Washington");
371 }
372
373 doc.add_paragraph("");
374
375 doc.add_paragraph("5.2 Column Span & Cell Shading")
376 .style("Heading2");
377 {
378 let mut tbl = doc
379 .add_table(3, 4)
380 .borders(BorderStyle::Single, 4, "000000")
381 .width_pct(100.0);
382 tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
383 tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
384 tbl.cell(1, 0).unwrap().set_text("Region");
385 tbl.cell(1, 0).unwrap().shading("D6E4F0");
386 tbl.cell(1, 1).unwrap().set_text("Q1");
387 tbl.cell(1, 1).unwrap().shading("D6E4F0");
388 tbl.cell(1, 2).unwrap().set_text("Q2");
389 tbl.cell(1, 2).unwrap().shading("D6E4F0");
390 tbl.cell(1, 3).unwrap().set_text("Total");
391 tbl.cell(1, 3).unwrap().shading("D6E4F0");
392 tbl.cell(2, 0).unwrap().set_text("Americas");
393 tbl.cell(2, 1).unwrap().set_text("$2.4M");
394 tbl.cell(2, 2).unwrap().set_text("$2.7M");
395 tbl.cell(2, 3).unwrap().set_text("$5.1M");
396 }
397
398 doc.add_paragraph("");
399
400 doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
401 {
402 let mut tbl = doc
403 .add_table(4, 3)
404 .borders(BorderStyle::Single, 4, "000000");
405 tbl.cell(0, 0).unwrap().set_text("Category");
406 tbl.cell(0, 0).unwrap().shading("E2EFDA");
407 tbl.cell(0, 1).unwrap().set_text("Item");
408 tbl.cell(0, 1).unwrap().shading("E2EFDA");
409 tbl.cell(0, 2).unwrap().set_text("Price");
410 tbl.cell(0, 2).unwrap().shading("E2EFDA");
411 tbl.cell(1, 0).unwrap().set_text("Hardware");
412 tbl.cell(1, 0).unwrap().v_merge_restart();
413 tbl.cell(1, 1).unwrap().set_text("Laptop");
414 tbl.cell(1, 2).unwrap().set_text("$1,200");
415 tbl.cell(2, 0).unwrap().v_merge_continue();
416 tbl.cell(2, 1).unwrap().set_text("Monitor");
417 tbl.cell(2, 2).unwrap().set_text("$450");
418 tbl.cell(3, 0).unwrap().set_text("Software");
419 tbl.cell(3, 1).unwrap().set_text("IDE License");
420 tbl.cell(3, 2).unwrap().set_text("$200/yr");
421 }
422
423 doc.add_paragraph("");
424
425 doc.add_paragraph("5.4 Nested Table").style("Heading2");
426 {
427 let mut tbl = doc
428 .add_table(2, 2)
429 .borders(BorderStyle::Single, 6, "2E75B6");
430 tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
431 tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
432 tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
433 {
434 let mut cell = tbl.cell(1, 1).unwrap();
435 cell.set_text("Nested table below:");
436 let mut nested = cell
437 .add_table(2, 2)
438 .borders(BorderStyle::Single, 2, "FF6600");
439 nested.cell(0, 0).unwrap().set_text("A");
440 nested.cell(0, 1).unwrap().set_text("B");
441 nested.cell(1, 0).unwrap().set_text("C");
442 nested.cell(1, 1).unwrap().set_text("D");
443 }
444 }
445
446 doc.add_paragraph("");
447
448 doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
449 .style("Heading2");
450 {
451 let mut tbl = doc
452 .add_table(2, 3)
453 .borders(BorderStyle::Single, 4, "666666");
454 tbl.row(0).unwrap().height(Length::pt(50.0)).header();
455 tbl.cell(0, 0).unwrap().set_text("Top");
456 tbl.cell(0, 0)
457 .unwrap()
458 .vertical_alignment(VerticalAlignment::Top)
459 .shading("FFF2CC");
460 tbl.cell(0, 1).unwrap().set_text("Center");
461 tbl.cell(0, 1)
462 .unwrap()
463 .vertical_alignment(VerticalAlignment::Center)
464 .shading("D9E2F3");
465 tbl.cell(0, 2).unwrap().set_text("Bottom");
466 tbl.cell(0, 2)
467 .unwrap()
468 .vertical_alignment(VerticalAlignment::Bottom)
469 .shading("E2EFDA");
470 tbl.row(1).unwrap().cant_split();
471 tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
472 tbl.cell(1, 0).unwrap().no_wrap();
473 tbl.cell(1, 1).unwrap().set_text("Fixed width");
474 tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
475 tbl.cell(1, 2).unwrap().set_text("Normal");
476 }
477
478 // ── SECTION 6: IMAGES ──
479 doc.add_paragraph("").page_break_before(true);
480 doc.add_paragraph("6. Images").style("Heading1");
481
482 doc.add_paragraph("6.1 Inline Image").style("Heading2");
483 doc.add_paragraph("A blue gradient image below:");
484 let img = create_sample_png(200, 50, [0, 80, 200]);
485 doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
486
487 doc.add_paragraph("");
488 doc.add_paragraph("6.2 Header Image").style("Heading2");
489 let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
490 doc.set_header_image(
491 &hdr_img,
492 "header_logo.png",
493 Length::inches(2.0),
494 Length::inches(0.2),
495 );
496 doc.add_paragraph("The header now contains an inline image (check the top of this page).");
497
498 doc.add_paragraph("");
499 doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
500
501 // ── SECTION 7: CONTENT MANIPULATION ──
502 doc.add_paragraph("").page_break_before(true);
503 doc.add_paragraph("7. Content Manipulation")
504 .style("Heading1");
505
506 doc.add_paragraph("7.1 Placeholder Replacement")
507 .style("Heading2");
508 doc.add_paragraph("Customer: {{customer}}");
509 doc.add_paragraph("Date: {{date}}");
510 doc.add_paragraph("Reference: {{ref_number}}");
511 {
512 let mut tbl = doc
513 .add_table(2, 2)
514 .borders(BorderStyle::Single, 4, "000000");
515 tbl.cell(0, 0).unwrap().set_text("Project");
516 tbl.cell(0, 0).unwrap().shading("D6E4F0");
517 tbl.cell(0, 1).unwrap().set_text("{{project}}");
518 tbl.cell(1, 0).unwrap().set_text("Status");
519 tbl.cell(1, 0).unwrap().shading("D6E4F0");
520 tbl.cell(1, 1).unwrap().set_text("{{status}}");
521 }
522
523 let mut replacements = HashMap::new();
524 replacements.insert("{{customer}}", "Tensorbee Inc.");
525 replacements.insert("{{date}}", "February 22, 2026");
526 replacements.insert("{{ref_number}}", "TB-2026-001");
527 replacements.insert("{{project}}", "Infrastructure Upgrade");
528 replacements.insert("{{status}}", "In Progress");
529 let n = doc.replace_all(&replacements);
530 doc.add_paragraph(&format!("({n} placeholders replaced above)"));
531
532 doc.add_paragraph("");
533
534 doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
535 doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
536 let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
537 doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
538
539 doc.add_paragraph("");
540
541 doc.add_paragraph("7.3 Content Insertion").style("Heading2");
542 doc.add_paragraph("Section A: First content.");
543 doc.add_paragraph("Section C: Third content.");
544 if let Some(idx) = doc.find_content_index("Section C") {
545 doc.insert_paragraph(
546 idx,
547 "Section B: Inserted between A and C via find_content_index().",
548 );
549 }
550
551 // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
552 doc.add_paragraph("").section_break(SectionBreak::NextPage);
553 doc.add_paragraph("8. Section Breaks & Orientation")
554 .style("Heading1");
555 doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
556
557 {
558 let mut tbl = doc
559 .add_table(3, 7)
560 .borders(BorderStyle::Single, 4, "2E75B6");
561 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
562 for (c, h) in headers.iter().enumerate() {
563 tbl.cell(0, c).unwrap().set_text(h);
564 tbl.cell(0, c).unwrap().shading("2E75B6");
565 }
566 let data = [
567 [
568 "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
569 ],
570 [
571 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
572 ],
573 ];
574 for (r, row) in data.iter().enumerate() {
575 for (c, v) in row.iter().enumerate() {
576 tbl.cell(r + 1, c).unwrap().set_text(v);
577 }
578 }
579 }
580
581 doc.add_paragraph("")
582 .section_break(SectionBreak::NextPage)
583 .section_landscape();
584
585 // ── SECTION 9: CUSTOM STYLES ──
586 doc.add_paragraph("9. Custom Styles").style("Heading1");
587 doc.add_style(
588 StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
589 .based_on("Normal")
590 .paragraph_properties({
591 let mut ppr = rdocx_oxml::properties::CT_PPr::default();
592 ppr.shading = Some(rdocx_oxml::properties::CT_Shd {
593 val: "clear".to_string(),
594 color: None,
595 fill: Some("FFF2CC".to_string()),
596 });
597 ppr
598 })
599 .run_properties({
600 let mut rpr = rdocx_oxml::properties::CT_RPr::default();
601 rpr.bold = Some(true);
602 rpr.color = Some("C45911".to_string());
603 rpr
604 }),
605 );
606 doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
607 .style("CustomHighlight");
608
609 doc.add_paragraph("");
610
611 // ── SECTION 10: DOCUMENT INTELLIGENCE ──
612 doc.add_paragraph("10. Document Intelligence API")
613 .style("Heading1");
614
615 let wc = doc.word_count();
616 let hc = doc.headings().len();
617 let ic = doc.images().len();
618 let lc = doc.links().len();
619 doc.add_paragraph(&format!("Word count: {wc}"));
620 doc.add_paragraph(&format!("Heading count: {hc}"));
621 doc.add_paragraph(&format!("Image count: {ic}"));
622 doc.add_paragraph(&format!("Link count: {lc}"));
623
624 let outline = doc.document_outline();
625 doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
626
627 let issues = doc.audit_accessibility();
628 doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
629 for issue in &issues {
630 doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
631 }
632
633 // ── SECTION 11: DOCUMENT MERGING ──
634 doc.add_paragraph("");
635 doc.add_paragraph("11. Document Merging").style("Heading1");
636 {
637 let mut other = Document::new();
638 other.add_paragraph("This paragraph was merged from another document using append().");
639 doc.append(&other);
640 }
641
642 doc.add_paragraph("");
643
644 // ── SUMMARY ──
645 doc.add_paragraph("Summary of Demonstrated Features")
646 .style("Heading1");
647 let features = [
648 "Page setup: size, margins, header/footer distance, gutter",
649 "Document metadata: title, author, subject, keywords",
650 "Headers and footers: text, images, different first page",
651 "Background images (full-page behind text)",
652 "Table of Contents generation with bookmarks",
653 "Text formatting: bold, italic, underline styles, strike, color, size, font",
654 "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
655 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
656 "Pagination controls: keep-with-next, keep-together, widow control",
657 "Bullet and numbered lists with nesting",
658 "Tab stops with dot/underscore/hyphen leaders",
659 "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
660 "Row properties: header rows, exact height, cant-split",
661 "Cell properties: width, no-wrap, vertical alignment",
662 "Inline images and header images",
663 "Placeholder replacement (single and batch)",
664 "Regex-based find and replace",
665 "Content insertion at specific positions",
666 "Section breaks with mixed portrait/landscape",
667 "Custom paragraph and character styles",
668 "Document intelligence: word count, headings, outline, images, links",
669 "Accessibility audit",
670 "Document merging (append)",
671 "Export: DOCX, PDF, HTML, Markdown",
672 ];
673 for f in &features {
674 doc.add_bullet_list_item(f, 0);
675 }
676 doc.add_paragraph("");
677 doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
678 .alignment(Alignment::Center)
679 .shading("E2EFDA")
680 .border_all(BorderStyle::Single, 2, "00AA00");
681
682 doc
683}
684
685// =============================================================================
686// 2. PROPOSAL DOCUMENT — Deep navy + gold scheme
687// =============================================================================
688fn generate_proposal(_samples_dir: &Path) -> Document {
689 let mut doc = Document::new();
690
691 // Colors: Navy #1B2A4A, Gold #C5922E, Light #F4F1EB
692 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
693 doc.set_margins(
694 Length::inches(1.0),
695 Length::inches(1.0),
696 Length::inches(1.0),
697 Length::inches(1.0),
698 );
699 doc.set_title("AI Platform Modernization Proposal");
700 doc.set_author("Walter White");
701 doc.set_subject("Technology Proposal");
702 doc.set_keywords("proposal, AI, modernization, Tensorbee");
703
704 // Banner header
705 let logo_img = create_logo_png(220, 48);
706 let banner = build_header_banner_xml(
707 "rId1",
708 &BannerOpts {
709 bg_color: "1B2A4A",
710 banner_width: 7772400,
711 banner_height: 914400,
712 logo_width: 2011680,
713 logo_height: 438912,
714 logo_x_offset: 295125,
715 logo_y_offset: 237744,
716 },
717 );
718 doc.set_raw_header_with_images(
719 banner,
720 &[("rId1", &logo_img, "logo.png")],
721 rdocx_oxml::header_footer::HdrFtrType::Default,
722 );
723 doc.set_different_first_page(true);
724 let cover_banner = build_header_banner_xml(
725 "rId1",
726 &BannerOpts {
727 bg_color: "C5922E",
728 banner_width: 7772400,
729 banner_height: 914400,
730 logo_width: 2011680,
731 logo_height: 438912,
732 logo_x_offset: 295125,
733 logo_y_offset: 237744,
734 },
735 );
736 doc.set_raw_header_with_images(
737 cover_banner,
738 &[("rId1", &logo_img, "logo.png")],
739 rdocx_oxml::header_footer::HdrFtrType::First,
740 );
741 doc.set_margins(
742 Length::twips(2292),
743 Length::twips(1440),
744 Length::twips(1440),
745 Length::twips(1440),
746 );
747 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
748 doc.set_footer("Tensorbee — Confidential");
749
750 // Custom styles
751 doc.add_style(
752 StyleBuilder::paragraph("ProposalTitle", "Proposal Title")
753 .based_on("Normal")
754 .run_properties({
755 let mut rpr = rdocx_oxml::properties::CT_RPr::default();
756 rpr.bold = Some(true);
757 rpr.font_ascii = Some("Georgia".to_string());
758 rpr.font_hansi = Some("Georgia".to_string());
759 rpr.sz = Some(rdocx_oxml::HalfPoint(56)); // half-points → 28pt
760 rpr.color = Some("1B2A4A".to_string());
761 rpr
762 }),
763 );
764
765 // ── Cover Page ──
766 for _ in 0..4 {
767 doc.add_paragraph("");
768 }
769 doc.add_paragraph("AI Platform Modernization")
770 .style("ProposalTitle")
771 .alignment(Alignment::Center);
772 doc.add_paragraph("")
773 .alignment(Alignment::Center)
774 .border_bottom(BorderStyle::Single, 8, "C5922E");
775 {
776 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
777 p.add_run("Prepared for Global Financial Corp.")
778 .size(14.0)
779 .color("666666")
780 .italic(true);
781 }
782 {
783 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
784 p.add_run("Prepared by: Walter White, CEO — Tensorbee")
785 .size(12.0)
786 .color("888888");
787 }
788 {
789 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
790 p.add_run("February 22, 2026").size(12.0).color("888888");
791 }
792
793 // ── TOC ──
794 doc.add_paragraph("").page_break_before(true);
795 doc.add_paragraph("Table of Contents").style("Heading1");
796 // We'll add a manual-style TOC since the insert_toc goes at a specific position
797 doc.insert_toc(doc.content_count(), 2);
798
799 // ── Executive Summary ──
800 doc.add_paragraph("").page_break_before(true);
801 doc.add_paragraph("Executive Summary").style("Heading1");
802 doc.add_paragraph(
803 "Tensorbee proposes a comprehensive modernization of Global Financial Corp's AI platform, \
804 transitioning from legacy batch-processing systems to a real-time inference architecture. \
805 This transformation will reduce model deployment time from weeks to hours, improve \
806 prediction accuracy by an estimated 23%, and deliver $4.2M in annual operational savings.",
807 )
808 .first_line_indent(Length::inches(0.3));
809 doc.add_paragraph(
810 "The project spans three phases over 18 months, with a total investment of $2.8M. \
811 Tensorbee brings deep expertise in ML infrastructure, having successfully completed \
812 similar transformations for three Fortune 500 financial institutions.",
813 )
814 .first_line_indent(Length::inches(0.3));
815
816 // Key metrics table
817 doc.add_paragraph("");
818 {
819 let mut tbl = doc
820 .add_table(5, 2)
821 .borders(BorderStyle::Single, 4, "1B2A4A")
822 .width_pct(80.0);
823 tbl.cell(0, 0).unwrap().set_text("Key Metric");
824 tbl.cell(0, 0).unwrap().shading("1B2A4A");
825 tbl.cell(0, 1).unwrap().set_text("Value");
826 tbl.cell(0, 1).unwrap().shading("1B2A4A");
827 let rows = [
828 ("Total Investment", "$2.8M"),
829 ("Annual Savings", "$4.2M"),
830 ("ROI (Year 1)", "150%"),
831 ("Timeline", "18 months"),
832 ];
833 for (i, (k, v)) in rows.iter().enumerate() {
834 tbl.cell(i + 1, 0).unwrap().set_text(k);
835 if i % 2 == 0 {
836 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
837 }
838 tbl.cell(i + 1, 1).unwrap().set_text(v);
839 if i % 2 == 0 {
840 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
841 }
842 }
843 }
844
845 // ── Problem Statement ──
846 doc.add_paragraph("").page_break_before(true);
847 doc.add_paragraph("Problem Statement").style("Heading1");
848 doc.add_paragraph(
849 "Global Financial Corp's current AI infrastructure faces three critical challenges:",
850 );
851 doc.add_numbered_list_item("Deployment Latency — New models take 3-4 weeks to deploy to production, compared to the industry benchmark of 2-3 days.", 0);
852 doc.add_numbered_list_item("Scalability Constraints — The batch processing architecture cannot handle real-time inference demands during peak trading hours.", 0);
853 doc.add_numbered_list_item("Technical Debt — Legacy Python 2.7 codebases and manual deployment scripts create reliability risks and slow iteration speed.", 0);
854
855 // ── Proposed Solution ──
856 doc.add_paragraph("").page_break_before(true);
857 doc.add_paragraph("Proposed Solution").style("Heading1");
858
859 doc.add_paragraph("Phase 1: Foundation (Months 1-6)")
860 .style("Heading2");
861 doc.add_bullet_list_item("Deploy Kubernetes-based ML serving infrastructure", 0);
862 doc.add_bullet_list_item("Implement CI/CD pipeline for model deployment", 0);
863 doc.add_bullet_list_item("Migrate top 5 production models to new platform", 0);
864
865 doc.add_paragraph("Phase 2: Expansion (Months 7-12)")
866 .style("Heading2");
867 doc.add_bullet_list_item(
868 "Real-time feature engineering pipeline (Apache Kafka + Flink)",
869 0,
870 );
871 doc.add_bullet_list_item("A/B testing framework for model variants", 0);
872 doc.add_bullet_list_item("Automated model monitoring and drift detection", 0);
873
874 doc.add_paragraph("Phase 3: Optimization (Months 13-18)")
875 .style("Heading2");
876 doc.add_bullet_list_item("GPU inference optimization (TensorRT/ONNX Runtime)", 0);
877 doc.add_bullet_list_item("Multi-region deployment for latency reduction", 0);
878 doc.add_bullet_list_item(
879 "Self-service model deployment portal for data science teams",
880 0,
881 );
882
883 // ── Budget ──
884 doc.add_paragraph("Budget Breakdown").style("Heading1");
885 {
886 let mut tbl = doc
887 .add_table(6, 3)
888 .borders(BorderStyle::Single, 4, "1B2A4A")
889 .width_pct(100.0);
890 tbl.cell(0, 0).unwrap().set_text("Category");
891 tbl.cell(0, 0).unwrap().shading("1B2A4A");
892 tbl.cell(0, 1).unwrap().set_text("Cost");
893 tbl.cell(0, 1).unwrap().shading("1B2A4A");
894 tbl.cell(0, 2).unwrap().set_text("Notes");
895 tbl.cell(0, 2).unwrap().shading("1B2A4A");
896 let items = [
897 (
898 "Infrastructure",
899 "$450,000",
900 "Cloud compute + GPU instances",
901 ),
902 ("Engineering Services", "$1,600,000", "12 FTE x 18 months"),
903 (
904 "Software Licenses",
905 "$350,000",
906 "Kafka, monitoring, CI/CD tools",
907 ),
908 (
909 "Training & Enablement",
910 "$200,000",
911 "Team training, documentation",
912 ),
913 ("Contingency (10%)", "$200,000", "Risk buffer"),
914 ];
915 for (i, (cat, cost, note)) in items.iter().enumerate() {
916 tbl.cell(i + 1, 0).unwrap().set_text(cat);
917 tbl.cell(i + 1, 1).unwrap().set_text(cost);
918 tbl.cell(i + 1, 2).unwrap().set_text(note);
919 if i % 2 == 0 {
920 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
921 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
922 tbl.cell(i + 1, 2).unwrap().shading("F4F1EB");
923 }
924 }
925 }
926
927 // ── Closing ──
928 doc.add_paragraph("");
929 doc.add_paragraph("Next Steps").style("Heading1");
930 doc.add_numbered_list_item(
931 "Schedule technical deep-dive with GFC engineering team (Week 1)",
932 0,
933 );
934 doc.add_numbered_list_item("Finalize scope and sign SOW (Week 2-3)", 0);
935 doc.add_numbered_list_item("Kick off Phase 1 with joint planning session (Week 4)", 0);
936
937 doc.add_paragraph("");
938 {
939 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
940 p.add_run("Walter White")
941 .bold(true)
942 .size(14.0)
943 .color("1B2A4A");
944 }
945 {
946 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
947 p.add_run("CEO, Tensorbee | walter@tensorbee.com")
948 .size(10.0)
949 .color("888888");
950 }
951
952 doc
953}
954
955// =============================================================================
956// 3. QUOTE / BILL OF MATERIALS — Teal + orange scheme
957// =============================================================================
958fn generate_quote(_samples_dir: &Path) -> Document {
959 let mut doc = Document::new();
960
961 // Colors: Teal #008B8B, Dark #1A3C3C, Orange #E07020, Light #F0F8F8
962 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
963 doc.set_margins(
964 Length::inches(0.75),
965 Length::inches(0.75),
966 Length::inches(0.75),
967 Length::inches(0.75),
968 );
969 doc.set_title("Quotation QT-2026-0147");
970 doc.set_author("Walter White");
971 doc.set_keywords("quote, BOM, Tensorbee");
972
973 doc.set_header("Tensorbee — Quotation");
974 doc.set_footer("QT-2026-0147 | Page");
975
976 // ── Header Block ──
977 {
978 let mut p = doc.add_paragraph("").alignment(Alignment::Left);
979 p.add_run("TENSORBEE")
980 .bold(true)
981 .size(28.0)
982 .color("008B8B")
983 .font("Helvetica");
984 }
985 doc.add_paragraph("123 Innovation Drive, Suite 400")
986 .alignment(Alignment::Left);
987 doc.add_paragraph("San Francisco, CA 94105 | +1 (415) 555-0199")
988 .alignment(Alignment::Left);
989 doc.add_paragraph("")
990 .border_bottom(BorderStyle::Single, 8, "008B8B");
991
992 // Quote meta
993 doc.add_paragraph("");
994 {
995 let mut tbl = doc.add_table(4, 4).width_pct(100.0);
996 tbl.cell(0, 0).unwrap().set_text("QUOTATION");
997 tbl.cell(0, 0).unwrap().shading("008B8B").grid_span(2);
998 tbl.cell(0, 2).unwrap().set_text("DATE");
999 tbl.cell(0, 2).unwrap().shading("008B8B");
1000 tbl.cell(0, 3).unwrap().set_text("VALID UNTIL");
1001 tbl.cell(0, 3).unwrap().shading("008B8B");
1002 tbl.cell(1, 0).unwrap().set_text("Quote #:");
1003 tbl.cell(1, 1).unwrap().set_text("QT-2026-0147");
1004 tbl.cell(1, 2).unwrap().set_text("Feb 22, 2026");
1005 tbl.cell(1, 3).unwrap().set_text("Mar 22, 2026");
1006 tbl.cell(2, 0).unwrap().set_text("Prepared by:");
1007 tbl.cell(2, 1).unwrap().set_text("Walter White");
1008 tbl.cell(2, 2).unwrap().set_text("Payment:");
1009 tbl.cell(2, 3).unwrap().set_text("Net 30");
1010 tbl.cell(3, 0).unwrap().set_text("Customer:");
1011 tbl.cell(3, 1).unwrap().set_text("Meridian Dynamics LLC");
1012 tbl.cell(3, 2).unwrap().set_text("Currency:");
1013 tbl.cell(3, 3).unwrap().set_text("USD");
1014 }
1015
1016 doc.add_paragraph("");
1017
1018 // ── Bill of Materials ──
1019 {
1020 let mut p = doc.add_paragraph("");
1021 p.add_run("Bill of Materials")
1022 .bold(true)
1023 .size(16.0)
1024 .color("1A3C3C");
1025 }
1026 doc.add_paragraph("");
1027
1028 {
1029 let mut tbl = doc
1030 .add_table(10, 6)
1031 .borders(BorderStyle::Single, 2, "008B8B")
1032 .width_pct(100.0)
1033 .layout_fixed();
1034 // Headers
1035 let hdrs = [
1036 "#",
1037 "Part Number",
1038 "Description",
1039 "Qty",
1040 "Unit Price",
1041 "Total",
1042 ];
1043 for (c, h) in hdrs.iter().enumerate() {
1044 tbl.cell(0, c).unwrap().set_text(h);
1045 tbl.cell(0, c).unwrap().shading("008B8B");
1046 }
1047
1048 let items: Vec<(&str, &str, &str, &str, &str)> = vec![
1049 (
1050 "TB-GPU-A100",
1051 "NVIDIA A100 80GB GPU",
1052 "4",
1053 "$12,500.00",
1054 "$50,000.00",
1055 ),
1056 (
1057 "TB-SRV-R750",
1058 "Dell R750xa Server Chassis",
1059 "2",
1060 "$8,200.00",
1061 "$16,400.00",
1062 ),
1063 (
1064 "TB-RAM-256G",
1065 "256GB DDR5 ECC Memory Module",
1066 "8",
1067 "$890.00",
1068 "$7,120.00",
1069 ),
1070 (
1071 "TB-SSD-3840",
1072 "3.84TB NVMe U.2 SSD",
1073 "8",
1074 "$1,150.00",
1075 "$9,200.00",
1076 ),
1077 (
1078 "TB-NET-CX7",
1079 "ConnectX-7 200GbE NIC",
1080 "4",
1081 "$1,800.00",
1082 "$7,200.00",
1083 ),
1084 (
1085 "TB-SW-48P",
1086 "48-Port 100GbE Switch",
1087 "1",
1088 "$22,000.00",
1089 "$22,000.00",
1090 ),
1091 (
1092 "TB-CAB-RACK",
1093 "42U Server Rack + PDU",
1094 "1",
1095 "$4,500.00",
1096 "$4,500.00",
1097 ),
1098 (
1099 "TB-SVC-INST",
1100 "Installation & Configuration",
1101 "1",
1102 "$8,500.00",
1103 "$8,500.00",
1104 ),
1105 (
1106 "TB-SVC-SUPP",
1107 "3-Year Premium Support",
1108 "1",
1109 "$15,000.00",
1110 "$15,000.00",
1111 ),
1112 ];
1113
1114 for (i, (pn, desc, qty, unit, total)) in items.iter().enumerate() {
1115 let row = i + 1;
1116 tbl.cell(row, 0).unwrap().set_text(&format!("{}", i + 1));
1117 tbl.cell(row, 1).unwrap().set_text(pn);
1118 tbl.cell(row, 2).unwrap().set_text(desc);
1119 tbl.cell(row, 3).unwrap().set_text(qty);
1120 tbl.cell(row, 4).unwrap().set_text(unit);
1121 tbl.cell(row, 5).unwrap().set_text(total);
1122 if i % 2 == 0 {
1123 for c in 0..6 {
1124 tbl.cell(row, c).unwrap().shading("F0F8F8");
1125 }
1126 }
1127 }
1128 }
1129
1130 doc.add_paragraph("");
1131
1132 // ── Totals ──
1133 {
1134 let mut tbl = doc
1135 .add_table(4, 2)
1136 .borders(BorderStyle::Single, 2, "008B8B")
1137 .width(Length::inches(3.5))
1138 .alignment(Alignment::Right);
1139 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1140 tbl.cell(0, 1).unwrap().set_text("$139,920.00");
1141 tbl.cell(1, 0).unwrap().set_text("Shipping & Handling");
1142 tbl.cell(1, 1).unwrap().set_text("$2,500.00");
1143 tbl.cell(2, 0).unwrap().set_text("Tax (8.625%)");
1144 tbl.cell(2, 1).unwrap().set_text("$12,068.10");
1145 tbl.cell(3, 0).unwrap().set_text("TOTAL");
1146 tbl.cell(3, 0).unwrap().shading("E07020");
1147 tbl.cell(3, 1).unwrap().set_text("$154,488.10");
1148 tbl.cell(3, 1).unwrap().shading("E07020");
1149 }
1150
1151 doc.add_paragraph("");
1152
1153 // ── Terms & Conditions ──
1154 {
1155 let mut p = doc.add_paragraph("");
1156 p.add_run("Terms & Conditions")
1157 .bold(true)
1158 .size(14.0)
1159 .color("1A3C3C");
1160 }
1161 doc.add_numbered_list_item(
1162 "This quotation is valid for 30 calendar days from the date of issue.",
1163 0,
1164 );
1165 doc.add_numbered_list_item(
1166 "All prices are in USD and exclusive of applicable taxes unless stated.",
1167 0,
1168 );
1169 doc.add_numbered_list_item("Standard lead time is 4-6 weeks from PO receipt.", 0);
1170 doc.add_numbered_list_item(
1171 "Payment terms: Net 30 from invoice date. 2% discount for payment within 10 days.",
1172 0,
1173 );
1174 doc.add_numbered_list_item(
1175 "Warranty: 3-year manufacturer warranty on all hardware components.",
1176 0,
1177 );
1178 doc.add_numbered_list_item(
1179 "Returns subject to 15% restocking fee if initiated after 14 days.",
1180 0,
1181 );
1182
1183 doc.add_paragraph("");
1184 doc.add_paragraph("")
1185 .border_bottom(BorderStyle::Single, 4, "008B8B");
1186 doc.add_paragraph("");
1187 {
1188 let mut p = doc.add_paragraph("");
1189 p.add_run("Accepted by: ").bold(true);
1190 p.add_run("___________________________ Date: ___________");
1191 }
1192 {
1193 let mut p = doc.add_paragraph("");
1194 p.add_run("Print Name: ").bold(true);
1195 p.add_run("___________________________ Title: ___________");
1196 }
1197
1198 doc
1199}
1200
1201// =============================================================================
1202// 4. INVOICE — Crimson + charcoal scheme
1203// =============================================================================
1204fn generate_invoice(_samples_dir: &Path) -> Document {
1205 let mut doc = Document::new();
1206
1207 // Colors: Crimson #B22222, Charcoal #333333, Light #FAF0F0
1208 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1209 doc.set_margins(
1210 Length::inches(0.75),
1211 Length::inches(0.75),
1212 Length::inches(0.75),
1213 Length::inches(0.75),
1214 );
1215 doc.set_title("Invoice INV-2026-0392");
1216 doc.set_author("Walter White");
1217 doc.set_keywords("invoice, Tensorbee");
1218
1219 doc.set_footer("Tensorbee — Thank you for your business!");
1220
1221 // ── Company & Invoice Header ──
1222 {
1223 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1224 // Company name left, INVOICE right
1225 {
1226 let mut cell = tbl.cell(0, 0).unwrap();
1227 let mut p = cell.add_paragraph("");
1228 p.add_run("TENSORBEE")
1229 .bold(true)
1230 .size(32.0)
1231 .color("B22222")
1232 .font("Helvetica");
1233 }
1234 {
1235 let mut cell = tbl.cell(0, 1).unwrap();
1236 let mut p = cell.add_paragraph("");
1237 p.add_run("INVOICE").bold(true).size(32.0).color("333333");
1238 }
1239 tbl.cell(1, 0)
1240 .unwrap()
1241 .set_text("123 Innovation Drive, Suite 400");
1242 tbl.cell(1, 1).unwrap().set_text("Invoice #: INV-2026-0392");
1243 tbl.cell(2, 0).unwrap().set_text("San Francisco, CA 94105");
1244 tbl.cell(2, 1).unwrap().set_text("Date: February 22, 2026");
1245 tbl.cell(3, 0).unwrap().set_text("walter@tensorbee.com");
1246 tbl.cell(3, 1).unwrap().set_text("Due Date: March 24, 2026");
1247 }
1248
1249 doc.add_paragraph("")
1250 .border_bottom(BorderStyle::Thick, 8, "B22222");
1251 doc.add_paragraph("");
1252
1253 // ── Bill To / Ship To ──
1254 {
1255 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1256 tbl.cell(0, 0).unwrap().set_text("BILL TO");
1257 tbl.cell(0, 0).unwrap().shading("B22222");
1258 tbl.cell(0, 1).unwrap().set_text("SHIP TO");
1259 tbl.cell(0, 1).unwrap().shading("B22222");
1260 tbl.cell(1, 0).unwrap().set_text("Meridian Dynamics LLC");
1261 tbl.cell(1, 1).unwrap().set_text("Meridian Dynamics LLC");
1262 tbl.cell(2, 0).unwrap().set_text("456 Enterprise Blvd");
1263 tbl.cell(2, 1).unwrap().set_text("Attn: Server Room B");
1264 tbl.cell(3, 0).unwrap().set_text("Austin, TX 78701");
1265 tbl.cell(3, 1)
1266 .unwrap()
1267 .set_text("456 Enterprise Blvd, Austin, TX 78701");
1268 }
1269
1270 doc.add_paragraph("");
1271
1272 // ── Line Items ──
1273 {
1274 let mut tbl = doc
1275 .add_table(8, 5)
1276 .borders(BorderStyle::Single, 2, "B22222")
1277 .width_pct(100.0);
1278 let hdrs = ["Description", "Qty", "Unit Price", "Tax", "Amount"];
1279 for (c, h) in hdrs.iter().enumerate() {
1280 tbl.cell(0, c).unwrap().set_text(h);
1281 tbl.cell(0, c).unwrap().shading("B22222");
1282 }
1283 let items = [
1284 (
1285 "ML Infrastructure Setup — Phase 1",
1286 "1",
1287 "$45,000.00",
1288 "$3,881.25",
1289 "$48,881.25",
1290 ),
1291 (
1292 "Data Pipeline Development (160 hrs)",
1293 "160",
1294 "$225.00",
1295 "$3,105.00",
1296 "$39,105.00",
1297 ),
1298 (
1299 "GPU Cluster Configuration",
1300 "1",
1301 "$12,000.00",
1302 "$1,035.00",
1303 "$13,035.00",
1304 ),
1305 (
1306 "API Gateway Implementation",
1307 "1",
1308 "$18,500.00",
1309 "$1,595.63",
1310 "$20,095.63",
1311 ),
1312 (
1313 "Load Testing & QA (80 hrs)",
1314 "80",
1315 "$195.00",
1316 "$1,345.50",
1317 "$16,945.50",
1318 ),
1319 (
1320 "Documentation & Training",
1321 "1",
1322 "$8,000.00",
1323 "$690.00",
1324 "$8,690.00",
1325 ),
1326 (
1327 "Project Management (3 months)",
1328 "3",
1329 "$6,500.00",
1330 "$1,679.63",
1331 "$21,179.63",
1332 ),
1333 ];
1334 for (i, (desc, qty, unit, tax, amt)) in items.iter().enumerate() {
1335 let r = i + 1;
1336 tbl.cell(r, 0).unwrap().set_text(desc);
1337 tbl.cell(r, 1).unwrap().set_text(qty);
1338 tbl.cell(r, 2).unwrap().set_text(unit);
1339 tbl.cell(r, 3).unwrap().set_text(tax);
1340 tbl.cell(r, 4).unwrap().set_text(amt);
1341 if i % 2 == 0 {
1342 for c in 0..5 {
1343 tbl.cell(r, c).unwrap().shading("FAF0F0");
1344 }
1345 }
1346 }
1347 }
1348
1349 doc.add_paragraph("");
1350
1351 // ── Totals ──
1352 {
1353 let mut tbl = doc
1354 .add_table(4, 2)
1355 .borders(BorderStyle::Single, 2, "B22222")
1356 .width(Length::inches(3.0))
1357 .alignment(Alignment::Right);
1358 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1359 tbl.cell(0, 1).unwrap().set_text("$154,600.00");
1360 tbl.cell(1, 0).unwrap().set_text("Tax (8.625%)");
1361 tbl.cell(1, 1).unwrap().set_text("$13,334.25");
1362 tbl.cell(2, 0).unwrap().set_text("Discount (5%)");
1363 tbl.cell(2, 1).unwrap().set_text("-$7,730.00");
1364 tbl.cell(3, 0).unwrap().set_text("AMOUNT DUE");
1365 tbl.cell(3, 0).unwrap().shading("B22222");
1366 tbl.cell(3, 1).unwrap().set_text("$160,204.25");
1367 tbl.cell(3, 1).unwrap().shading("B22222");
1368 }
1369
1370 doc.add_paragraph("");
1371 doc.add_paragraph("");
1372
1373 // ── Payment Details ──
1374 {
1375 let mut p = doc.add_paragraph("");
1376 p.add_run("Payment Details")
1377 .bold(true)
1378 .size(14.0)
1379 .color("333333");
1380 }
1381 doc.add_paragraph("Bank: Silicon Valley Bank")
1382 .indent_left(Length::inches(0.3));
1383 doc.add_paragraph("Account: Tensorbee Inc. — 0847-2953-1120")
1384 .indent_left(Length::inches(0.3));
1385 doc.add_paragraph("Routing: 121140399")
1386 .indent_left(Length::inches(0.3));
1387 doc.add_paragraph("Swift: SVBKUS6S")
1388 .indent_left(Length::inches(0.3));
1389
1390 doc.add_paragraph("");
1391
1392 doc.add_paragraph("Please include invoice number INV-2026-0392 in the payment reference.")
1393 .shading("FAF0F0")
1394 .border_all(BorderStyle::Single, 2, "B22222");
1395
1396 doc
1397}
1398
1399// =============================================================================
1400// 5. REPORT — Forest green + earth tones, with images & hierarchical sections
1401// =============================================================================
1402fn generate_report(_samples_dir: &Path) -> Document {
1403 let mut doc = Document::new();
1404
1405 // Colors: Forest #2D5016, Sage #6B8E23, Earth #8B7355, Cream #FFFAF0
1406 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1407 doc.set_margins(
1408 Length::inches(1.0),
1409 Length::inches(1.0),
1410 Length::inches(1.0),
1411 Length::inches(1.0),
1412 );
1413 doc.set_title("Q4 2025 Environmental Impact Report");
1414 doc.set_author("Walter White");
1415 doc.set_subject("Quarterly Environmental Report");
1416 doc.set_keywords("environment, sustainability, report, Tensorbee");
1417
1418 doc.set_different_first_page(true);
1419 doc.set_first_page_header("");
1420 doc.set_header("Tensorbee — Q4 2025 Environmental Impact Report");
1421 doc.set_footer("CONFIDENTIAL — Page");
1422
1423 // ── Cover ──
1424 let cover_bg = create_sample_png(612, 792, [20, 50, 15]);
1425 doc.add_background_image(&cover_bg, "report_cover.png");
1426
1427 for _ in 0..5 {
1428 doc.add_paragraph("");
1429 }
1430 {
1431 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1432 p.add_run("Q4 2025")
1433 .bold(true)
1434 .size(48.0)
1435 .color("FFFFFF")
1436 .font("Georgia");
1437 }
1438 {
1439 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1440 p.add_run("Environmental Impact Report")
1441 .size(24.0)
1442 .color("90EE90")
1443 .font("Georgia")
1444 .italic(true);
1445 }
1446 doc.add_paragraph("");
1447 {
1448 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1449 p.add_run("Tensorbee — Sustainability Division")
1450 .size(14.0)
1451 .color("C0C0C0");
1452 }
1453 {
1454 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1455 p.add_run("Prepared by Walter White, Chief Sustainability Officer")
1456 .size(11.0)
1457 .color("AAAAAA");
1458 }
1459
1460 // ── TOC ──
1461 doc.add_paragraph("").page_break_before(true);
1462 doc.insert_toc(doc.content_count(), 3);
1463
1464 // ── Section 1: Executive Overview ──
1465 doc.add_paragraph("").page_break_before(true);
1466 doc.add_paragraph("Executive Overview").style("Heading1");
1467 doc.add_paragraph(
1468 "This report presents Tensorbee's environmental performance for Q4 2025. Our \
1469 sustainability initiatives have yielded a 34% reduction in carbon emissions compared \
1470 to Q4 2024, exceeding our target of 25%. Key achievements include the transition to \
1471 100% renewable energy in our primary data centers and the launch of our carbon offset \
1472 marketplace.",
1473 )
1474 .first_line_indent(Length::inches(0.3));
1475
1476 // Image: performance chart
1477 let chart_img = create_chart_png(400, 200);
1478 doc.add_paragraph("");
1479 doc.add_picture(
1480 &chart_img,
1481 "performance_chart.png",
1482 Length::inches(5.0),
1483 Length::inches(2.5),
1484 )
1485 .alignment(Alignment::Center);
1486 {
1487 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1488 p.add_run("Figure 1: Quarterly Carbon Emissions (tonnes CO2e)")
1489 .italic(true)
1490 .size(9.0)
1491 .color("666666");
1492 }
1493
1494 // ── Section 2: Energy Consumption ──
1495 doc.add_paragraph("").page_break_before(true);
1496 doc.add_paragraph("Energy Consumption").style("Heading1");
1497
1498 doc.add_paragraph("Data Center Operations")
1499 .style("Heading2");
1500 doc.add_paragraph(
1501 "Our three primary data centers consumed a combined 4.2 GWh in Q4 2025, \
1502 a 12% reduction from Q3 2025 driven by improved cooling efficiency and \
1503 server consolidation.",
1504 );
1505
1506 // Data table
1507 {
1508 let mut tbl = doc
1509 .add_table(5, 4)
1510 .borders(BorderStyle::Single, 4, "2D5016")
1511 .width_pct(100.0);
1512 let hdrs = ["Data Center", "Capacity (MW)", "Usage (GWh)", "PUE"];
1513 for (c, h) in hdrs.iter().enumerate() {
1514 tbl.cell(0, c).unwrap().set_text(h);
1515 tbl.cell(0, c).unwrap().shading("2D5016");
1516 }
1517 let rows = [
1518 ("San Francisco (Primary)", "3.2", "1.8", "1.12"),
1519 ("Dublin (EU)", "2.1", "1.2", "1.18"),
1520 ("Singapore (APAC)", "1.8", "1.2", "1.24"),
1521 ("TOTAL", "7.1", "4.2", "1.17 avg"),
1522 ];
1523 for (i, (dc, cap, use_, pue)) in rows.iter().enumerate() {
1524 tbl.cell(i + 1, 0).unwrap().set_text(dc);
1525 tbl.cell(i + 1, 1).unwrap().set_text(cap);
1526 tbl.cell(i + 1, 2).unwrap().set_text(use_);
1527 tbl.cell(i + 1, 3).unwrap().set_text(pue);
1528 if i == 3 {
1529 for c in 0..4 {
1530 tbl.cell(i + 1, c).unwrap().shading("E2EFDA");
1531 }
1532 }
1533 }
1534 }
1535
1536 doc.add_paragraph("");
1537
1538 doc.add_paragraph("Renewable Energy Mix").style("Heading2");
1539 doc.add_paragraph("Breakdown of energy sources across all facilities:");
1540
1541 doc.add_bullet_list_item("Solar PV: 42% (1.76 GWh)", 0);
1542 doc.add_bullet_list_item("Wind Power Purchase Agreements: 38% (1.60 GWh)", 0);
1543 doc.add_bullet_list_item("Hydroelectric: 12% (0.50 GWh)", 0);
1544 doc.add_bullet_list_item("Grid (non-renewable): 8% (0.34 GWh)", 0);
1545
1546 // ── Section 3: Water & Waste ──
1547 doc.add_paragraph("Water & Waste Management")
1548 .style("Heading1");
1549
1550 doc.add_paragraph("Water Usage").style("Heading2");
1551 doc.add_paragraph(
1552 "Total water consumption was 12.4 million gallons, a 15% reduction from Q3. \
1553 Our closed-loop cooling systems now recycle 78% of water used in cooling operations.",
1554 );
1555
1556 doc.add_paragraph("Waste Reduction").style("Heading2");
1557 doc.add_paragraph("E-waste management results for Q4:")
1558 .keep_with_next(true);
1559 {
1560 let mut tbl = doc
1561 .add_table(4, 3)
1562 .borders(BorderStyle::Single, 2, "6B8E23")
1563 .width_pct(80.0);
1564 tbl.cell(0, 0).unwrap().set_text("Category");
1565 tbl.cell(0, 0).unwrap().shading("6B8E23");
1566 tbl.cell(0, 1).unwrap().set_text("Weight (kg)");
1567 tbl.cell(0, 1).unwrap().shading("6B8E23");
1568 tbl.cell(0, 2).unwrap().set_text("Recycled %");
1569 tbl.cell(0, 2).unwrap().shading("6B8E23");
1570 let rows = [
1571 ("Server Hardware", "2,450", "94%"),
1572 ("Networking Equipment", "820", "91%"),
1573 ("Storage Media", "340", "99%"),
1574 ];
1575 for (i, (cat, wt, pct)) in rows.iter().enumerate() {
1576 tbl.cell(i + 1, 0).unwrap().set_text(cat);
1577 tbl.cell(i + 1, 1).unwrap().set_text(wt);
1578 tbl.cell(i + 1, 2).unwrap().set_text(pct);
1579 }
1580 }
1581
1582 // ── Section 4: Initiatives ──
1583 doc.add_paragraph("").page_break_before(true);
1584 doc.add_paragraph("Q1 2026 Initiatives").style("Heading1");
1585
1586 doc.add_paragraph("Planned Programs").style("Heading2");
1587 doc.add_numbered_list_item(
1588 "Deploy on-site battery storage at SF data center (2 MWh capacity)",
1589 0,
1590 );
1591 doc.add_numbered_list_item("Pilot immersion cooling in Dublin facility", 0);
1592 doc.add_numbered_list_item("Launch employee commute carbon offset program", 0);
1593 doc.add_numbered_list_item("Achieve ISO 14001 certification for Singapore facility", 0);
1594
1595 doc.add_paragraph("Investment Targets").style("Heading2");
1596 doc.add_paragraph("Sustainability CapEx allocation for FY2026:")
1597 .keep_with_next(true);
1598 {
1599 let mut tbl = doc
1600 .add_table(5, 2)
1601 .borders(BorderStyle::Single, 4, "2D5016");
1602 tbl.cell(0, 0).unwrap().set_text("Initiative");
1603 tbl.cell(0, 0).unwrap().shading("2D5016");
1604 tbl.cell(0, 1).unwrap().set_text("Budget");
1605 tbl.cell(0, 1).unwrap().shading("2D5016");
1606 let rows = [
1607 ("Battery Storage", "$1.2M"),
1608 ("Immersion Cooling Pilot", "$800K"),
1609 ("Solar Panel Expansion", "$2.1M"),
1610 ("Carbon Credits", "$500K"),
1611 ];
1612 for (i, (init, budget)) in rows.iter().enumerate() {
1613 tbl.cell(i + 1, 0).unwrap().set_text(init);
1614 tbl.cell(i + 1, 1).unwrap().set_text(budget);
1615 if i % 2 == 0 {
1616 tbl.cell(i + 1, 0).unwrap().shading("FFFAF0");
1617 tbl.cell(i + 1, 1).unwrap().shading("FFFAF0");
1618 }
1619 }
1620 }
1621
1622 // Image: sustainability roadmap
1623 let roadmap_img = create_sample_png(500, 100, [40, 80, 30]);
1624 doc.add_paragraph("");
1625 doc.add_picture(
1626 &roadmap_img,
1627 "roadmap.png",
1628 Length::inches(6.0),
1629 Length::inches(1.2),
1630 )
1631 .alignment(Alignment::Center);
1632 {
1633 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1634 p.add_run("Figure 2: 2026 Sustainability Roadmap")
1635 .italic(true)
1636 .size(9.0)
1637 .color("666666");
1638 }
1639
1640 doc.add_paragraph("");
1641 doc.add_paragraph(
1642 "For questions about this report, contact Walter White at sustainability@tensorbee.com.",
1643 )
1644 .shading("FFFAF0")
1645 .border_all(BorderStyle::Single, 2, "2D5016");
1646
1647 doc
1648}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?
34fn generate_feature_showcase(path: &Path) {
35 let mut doc = Document::new();
36
37 // =========================================================================
38 // PAGE SETUP & METADATA
39 // =========================================================================
40 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
41 doc.set_margins(
42 Length::inches(1.0), // top
43 Length::inches(1.0), // right
44 Length::inches(1.0), // bottom
45 Length::inches(1.0), // left
46 );
47 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
48 doc.set_gutter(Length::twips(0));
49
50 doc.set_title("rdocx Feature Showcase");
51 doc.set_author("rdocx Sample Generator");
52 doc.set_subject("Comprehensive feature demonstration");
53 doc.set_keywords("rdocx, docx, rust, sample");
54
55 // Header & Footer
56 doc.set_header("rdocx Feature Showcase");
57 doc.set_footer("Generated by rdocx — Page");
58
59 // Different first page header
60 doc.set_different_first_page(true);
61 doc.set_first_page_header("rdocx");
62 doc.set_first_page_footer("Feature Showcase — Cover Page");
63
64 // =========================================================================
65 // PAGE 1: COVER PAGE — background image, run formatting
66 // =========================================================================
67 let bg_cover = create_sample_png(612, 792, [30, 60, 120]);
68 doc.add_background_image(&bg_cover, "cover_bg.png");
69
70 doc.add_paragraph(""); // spacer
71 doc.add_paragraph(""); // spacer
72 doc.add_paragraph(""); // spacer
73
74 {
75 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
76 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
77 }
78 {
79 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
80 p.add_run("Feature Showcase")
81 .size(28.0)
82 .color("FFFFFF")
83 .italic(true);
84 }
85
86 doc.add_paragraph(""); // spacer
87
88 {
89 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
90 p.add_run("A comprehensive demonstration of every feature")
91 .size(14.0)
92 .color("CCDDFF");
93 }
94 {
95 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
96 p.add_run("provided by the rdocx Rust crate for DOCX generation.")
97 .size(14.0)
98 .color("CCDDFF");
99 }
100
101 // =========================================================================
102 // PAGE 2: TEXT FORMATTING
103 // =========================================================================
104 doc.add_paragraph("").page_break_before(true);
105
106 doc.add_paragraph("1. Text Formatting").style("Heading1");
107
108 doc.add_paragraph("This section demonstrates paragraph and run-level formatting options.");
109 doc.add_paragraph("");
110
111 // --- Paragraph alignment ---
112 doc.add_paragraph("Paragraph Alignment").style("Heading2");
113
114 doc.add_paragraph("This paragraph is left-aligned (the default).")
115 .alignment(Alignment::Left);
116 doc.add_paragraph("This paragraph is center-aligned.")
117 .alignment(Alignment::Center);
118 doc.add_paragraph("This paragraph is right-aligned.")
119 .alignment(Alignment::Right);
120 doc.add_paragraph(
121 "This paragraph is justified. To demonstrate justified text properly, it needs \
122 to be long enough to span multiple lines so the word spacing adjustment is visible \
123 across the full width of the text area on the page.",
124 )
125 .alignment(Alignment::Justify);
126
127 doc.add_paragraph("");
128
129 // --- Run formatting ---
130 doc.add_paragraph("Run Formatting").style("Heading2");
131
132 {
133 let mut p = doc.add_paragraph("");
134 p.add_run("Bold text").bold(true);
135 p.add_run(" | ");
136 p.add_run("Italic text").italic(true);
137 p.add_run(" | ");
138 p.add_run("Bold + Italic").bold(true).italic(true);
139 }
140 {
141 let mut p = doc.add_paragraph("");
142 p.add_run("Single underline").underline(true);
143 p.add_run(" | ");
144 p.add_run("Strikethrough").strike(true);
145 p.add_run(" | ");
146 p.add_run("Double strikethrough").double_strike(true);
147 }
148 {
149 let mut p = doc.add_paragraph("");
150 p.add_run("Red text").color("FF0000");
151 p.add_run(" | ");
152 p.add_run("Blue text").color("0000FF");
153 p.add_run(" | ");
154 p.add_run("Green text").color("00AA00");
155 p.add_run(" | ");
156 p.add_run("Highlighted").highlight("FFFF00");
157 }
158 {
159 let mut p = doc.add_paragraph("");
160 p.add_run("8pt small").size(8.0);
161 p.add_run(" | ");
162 p.add_run("11pt normal").size(11.0);
163 p.add_run(" | ");
164 p.add_run("16pt large").size(16.0);
165 p.add_run(" | ");
166 p.add_run("24pt extra-large").size(24.0);
167 }
168 {
169 let mut p = doc.add_paragraph("");
170 p.add_run("Arial font").font("Arial");
171 p.add_run(" | ");
172 p.add_run("Times New Roman font").font("Times New Roman");
173 p.add_run(" | ");
174 p.add_run("Courier New font").font("Courier New");
175 }
176 {
177 let mut p = doc.add_paragraph("");
178 p.add_run("Normal");
179 p.add_run(" H").size(11.0);
180 p.add_run("2").subscript();
181 p.add_run("O (subscript)").size(11.0);
182 p.add_run(" | E = mc").size(11.0);
183 p.add_run("2").superscript();
184 p.add_run(" (superscript)").size(11.0);
185 }
186 {
187 let mut p = doc.add_paragraph("");
188 p.add_run("ALL CAPS").all_caps(true);
189 p.add_run(" | ");
190 p.add_run("Small Caps").small_caps(true);
191 p.add_run(" | ");
192 p.add_run("Expanded spacing")
193 .character_spacing(Length::twips(40));
194 }
195
196 doc.add_paragraph("");
197
198 // --- Paragraph formatting ---
199 doc.add_paragraph("Paragraph Formatting").style("Heading2");
200
201 doc.add_paragraph("Paragraph with shading (light green background)")
202 .shading("E2EFDA");
203
204 doc.add_paragraph("Paragraph with bottom border")
205 .border_bottom(BorderStyle::Single, 6, "2E75B6");
206
207 doc.add_paragraph("Paragraph with all borders")
208 .border_all(BorderStyle::Single, 4, "FF0000");
209
210 doc.add_paragraph("Paragraph with 1-inch left indent and hanging indent")
211 .indent_left(Length::inches(1.0))
212 .hanging_indent(Length::inches(0.5));
213
214 doc.add_paragraph("Paragraph with first-line indent of 0.5 inches")
215 .first_line_indent(Length::inches(0.5));
216
217 doc.add_paragraph("Paragraph with extra space before (24pt) and after (12pt)")
218 .space_before(Length::pt(24.0))
219 .space_after(Length::pt(12.0));
220
221 doc.add_paragraph(
222 "Paragraph with double line spacing. This text should have extra vertical \
223 space between lines to demonstrate the line_spacing_multiple setting.",
224 )
225 .line_spacing_multiple(2.0);
226
227 doc.add_paragraph("Paragraph with keep-with-next (won't break from the next paragraph)")
228 .keep_with_next(true);
229 doc.add_paragraph("(This stays with the paragraph above.)");
230
231 // =========================================================================
232 // PAGE 3: LISTS & TAB STOPS
233 // =========================================================================
234 doc.add_paragraph("").page_break_before(true);
235
236 doc.add_paragraph("2. Lists").style("Heading1");
237
238 doc.add_paragraph("Bullet List").style("Heading2");
239
240 doc.add_bullet_list_item("First bullet item", 0);
241 doc.add_bullet_list_item("Second bullet item", 0);
242 doc.add_bullet_list_item("Nested level 1", 1);
243 doc.add_bullet_list_item("Nested level 2", 2);
244 doc.add_bullet_list_item("Back to level 1", 1);
245 doc.add_bullet_list_item("Third bullet item", 0);
246
247 doc.add_paragraph("");
248
249 doc.add_paragraph("Numbered List").style("Heading2");
250
251 doc.add_numbered_list_item("First numbered item", 0);
252 doc.add_numbered_list_item("Second numbered item", 0);
253 doc.add_numbered_list_item("Sub-item A", 1);
254 doc.add_numbered_list_item("Sub-item B", 1);
255 doc.add_numbered_list_item("Third numbered item", 0);
256
257 doc.add_paragraph("");
258
259 // --- Tab stops ---
260 doc.add_paragraph("Tab Stops").style("Heading2");
261
262 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
263 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
264 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
265 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
266 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
267
268 doc.add_paragraph("Item\t........\tPrice")
269 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
270 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
271 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
272
273 doc.add_paragraph("Widget A\t........\t$19.99")
274 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
275 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
276 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
277
278 doc.add_paragraph("Gadget B\t________\t$249.50")
279 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
280 .add_tab_stop_with_leader(
281 TabAlignment::Right,
282 Length::inches(4.0),
283 TabLeader::Underscore,
284 )
285 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
286
287 // =========================================================================
288 // PAGE 4: TABLES
289 // =========================================================================
290 doc.add_paragraph("").page_break_before(true);
291
292 doc.add_paragraph("3. Tables").style("Heading1");
293
294 // --- Basic table with borders ---
295 doc.add_paragraph("Basic Table with Borders")
296 .style("Heading2");
297
298 {
299 let mut tbl = doc.add_table(4, 3);
300 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
301
302 // Header row
303 for col in 0..3 {
304 tbl.cell(0, col).unwrap().shading("2E75B6");
305 }
306 tbl.cell(0, 0).unwrap().set_text("Name");
307 tbl.cell(0, 1).unwrap().set_text("Role");
308 tbl.cell(0, 2).unwrap().set_text("Location");
309
310 tbl.cell(1, 0).unwrap().set_text("Alice Johnson");
311 tbl.cell(1, 1).unwrap().set_text("Engineering Lead");
312 tbl.cell(1, 2).unwrap().set_text("New York");
313
314 tbl.cell(2, 0).unwrap().set_text("Bob Smith");
315 tbl.cell(2, 1).unwrap().set_text("Product Manager");
316 tbl.cell(2, 2).unwrap().set_text("San Francisco");
317
318 tbl.cell(3, 0).unwrap().set_text("Carol Davis");
319 tbl.cell(3, 1).unwrap().set_text("Designer");
320 tbl.cell(3, 2).unwrap().set_text("London");
321 }
322
323 doc.add_paragraph("");
324
325 // --- Table with cell merging ---
326 doc.add_paragraph("Table with Cell Merging & Vertical Alignment")
327 .style("Heading2");
328
329 {
330 let mut tbl = doc.add_table(4, 4);
331 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
332 tbl = tbl.width_pct(100.0);
333
334 // Header spanning all columns
335 tbl.cell(0, 0).unwrap().set_text("Quarterly Revenue Report");
336 tbl.cell(0, 0).unwrap().shading("1F4E79");
337 tbl.cell(0, 0).unwrap().grid_span(4);
338
339 // Sub-header
340 tbl.cell(1, 0).unwrap().set_text("Region");
341 tbl.cell(1, 0).unwrap().shading("D6E4F0");
342 tbl.cell(1, 1).unwrap().set_text("Q1");
343 tbl.cell(1, 1).unwrap().shading("D6E4F0");
344 tbl.cell(1, 2).unwrap().set_text("Q2");
345 tbl.cell(1, 2).unwrap().shading("D6E4F0");
346 tbl.cell(1, 3).unwrap().set_text("Total");
347 tbl.cell(1, 3).unwrap().shading("D6E4F0");
348
349 // Data
350 tbl.cell(2, 0).unwrap().set_text("North America");
351 tbl.cell(2, 1).unwrap().set_text("$2.4M");
352 tbl.cell(2, 2).unwrap().set_text("$2.7M");
353 tbl.cell(2, 3).unwrap().set_text("$5.1M");
354
355 tbl.cell(3, 0).unwrap().set_text("Europe");
356 tbl.cell(3, 1).unwrap().set_text("$1.8M");
357 tbl.cell(3, 2).unwrap().set_text("$2.0M");
358 tbl.cell(3, 3).unwrap().set_text("$3.8M");
359
360 // Vertical alignment on data cells
361 tbl.cell(2, 3)
362 .unwrap()
363 .vertical_alignment(VerticalAlignment::Center);
364 tbl.cell(3, 3)
365 .unwrap()
366 .vertical_alignment(VerticalAlignment::Bottom);
367 }
368
369 doc.add_paragraph("");
370
371 // --- Table with vertical merge ---
372 doc.add_paragraph("Table with Vertical Merge")
373 .style("Heading2");
374
375 {
376 let mut tbl = doc.add_table(4, 3);
377 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
378
379 tbl.cell(0, 0).unwrap().set_text("Category");
380 tbl.cell(0, 0).unwrap().shading("E2EFDA");
381 tbl.cell(0, 1).unwrap().set_text("Item");
382 tbl.cell(0, 1).unwrap().shading("E2EFDA");
383 tbl.cell(0, 2).unwrap().set_text("Price");
384 tbl.cell(0, 2).unwrap().shading("E2EFDA");
385
386 // "Hardware" spans rows 1-2
387 tbl.cell(1, 0).unwrap().set_text("Hardware");
388 tbl.cell(1, 0).unwrap().v_merge_restart();
389 tbl.cell(1, 1).unwrap().set_text("Laptop");
390 tbl.cell(1, 2).unwrap().set_text("$1,200");
391
392 tbl.cell(2, 0).unwrap().v_merge_continue();
393 tbl.cell(2, 1).unwrap().set_text("Monitor");
394 tbl.cell(2, 2).unwrap().set_text("$450");
395
396 // "Software" on row 3
397 tbl.cell(3, 0).unwrap().set_text("Software");
398 tbl.cell(3, 1).unwrap().set_text("IDE License");
399 tbl.cell(3, 2).unwrap().set_text("$200/yr");
400 }
401
402 doc.add_paragraph("");
403
404 // --- Nested table ---
405 doc.add_paragraph("Nested Table").style("Heading2");
406
407 {
408 let mut tbl = doc.add_table(2, 2);
409 tbl = tbl.borders(BorderStyle::Single, 6, "2E75B6");
410
411 tbl.cell(0, 0).unwrap().set_text("Outer Cell (0,0)");
412 tbl.cell(0, 1).unwrap().set_text("Outer Cell (0,1)");
413 tbl.cell(1, 0).unwrap().set_text("Outer Cell (1,0)");
414
415 // Nested table inside cell (1,1)
416 {
417 let mut cell = tbl.cell(1, 1).unwrap();
418 cell.set_text("Contains nested table:");
419 let mut nested = cell.add_table(2, 2);
420 nested = nested.borders(BorderStyle::Single, 2, "FF6600");
421 nested.cell(0, 0).unwrap().set_text("Inner A");
422 nested.cell(0, 1).unwrap().set_text("Inner B");
423 nested.cell(1, 0).unwrap().set_text("Inner C");
424 nested.cell(1, 1).unwrap().set_text("Inner D");
425 }
426 }
427
428 // =========================================================================
429 // PAGE 5: IMAGES
430 // =========================================================================
431 doc.add_paragraph("").page_break_before(true);
432
433 doc.add_paragraph("4. Images").style("Heading1");
434
435 doc.add_paragraph("Inline Image").style("Heading2");
436
437 doc.add_paragraph("Below is an inline image (200x50 pixels, blue gradient):");
438 let inline_img = create_sample_png(200, 50, [0, 80, 200]);
439 doc.add_picture(
440 &inline_img,
441 "inline_chart.png",
442 Length::inches(3.0),
443 Length::inches(0.75),
444 );
445
446 doc.add_paragraph("");
447
448 doc.add_paragraph("Header Image").style("Heading2");
449
450 // Replace the text-only header with an image header
451 let header_img = create_sample_png(400, 40, [40, 40, 40]);
452 doc.set_header_image(
453 &header_img,
454 "header_logo.png",
455 Length::inches(2.0),
456 Length::inches(0.2),
457 );
458
459 doc.add_paragraph(
460 "The document header has been replaced with an inline image. \
461 Check the header area at the top of this page.",
462 );
463
464 doc.add_paragraph("");
465 doc.add_paragraph(
466 "Note: The cover page uses a full-page background image behind the text, \
467 demonstrated on page 1 via add_background_image().",
468 );
469
470 // =========================================================================
471 // PAGE 6: CONTENT MANIPULATION — placeholder replacement, insertion
472 // =========================================================================
473 doc.add_paragraph("").page_break_before(true);
474
475 doc.add_paragraph("5. Content Manipulation")
476 .style("Heading1");
477
478 // --- Placeholder replacement ---
479 doc.add_paragraph("Placeholder Replacement")
480 .style("Heading2");
481
482 doc.add_paragraph(
483 "Before replacement, this document contained {{customer}} and {{date}} placeholders.",
484 );
485
486 {
487 let mut p = doc.add_paragraph("");
488 p.add_run("Customer: ").bold(true);
489 p.add_run("{{customer}}");
490 }
491 {
492 let mut p = doc.add_paragraph("");
493 p.add_run("Date: ").bold(true);
494 p.add_run("{{date}}");
495 }
496 doc.add_paragraph("Reference: {{ref_number}}");
497
498 // Table with placeholders
499 {
500 let mut tbl = doc.add_table(3, 2);
501 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
502 tbl.cell(0, 0).unwrap().set_text("Field");
503 tbl.cell(0, 0).unwrap().shading("D6E4F0");
504 tbl.cell(0, 1).unwrap().set_text("Value");
505 tbl.cell(0, 1).unwrap().shading("D6E4F0");
506 tbl.cell(1, 0).unwrap().set_text("Project");
507 tbl.cell(1, 1).unwrap().set_text("{{project}}");
508 tbl.cell(2, 0).unwrap().set_text("Status");
509 tbl.cell(2, 1).unwrap().set_text("{{status}}");
510 }
511
512 // Perform replacements
513 let mut replacements = HashMap::new();
514 replacements.insert("{{customer}}", "Acme Corporation");
515 replacements.insert("{{date}}", "February 22, 2026");
516 replacements.insert("{{ref_number}}", "REF-2026-001");
517 replacements.insert("{{project}}", "Infrastructure Upgrade");
518 replacements.insert("{{status}}", "In Progress");
519 let replace_count = doc.replace_all(&replacements);
520
521 doc.add_paragraph("");
522 doc.add_paragraph(&format!(
523 "(Replaced {} placeholders above — in body text and table cells)",
524 replace_count
525 ));
526
527 doc.add_paragraph("");
528
529 // --- Content insertion ---
530 doc.add_paragraph("Content Insertion").style("Heading2");
531
532 doc.add_paragraph("Section A: First section of content.");
533 doc.add_paragraph("Section C: Third section of content.");
534
535 // Insert "Section B" between A and C
536 if let Some(idx) = doc.find_content_index("Section C") {
537 doc.insert_paragraph(
538 idx,
539 "Section B: Inserted between A and C using find_content_index().",
540 );
541 }
542
543 doc.add_paragraph("");
544 doc.add_paragraph(
545 "The paragraph above ('Section B') was inserted at a specific position \
546 using find_content_index() + insert_paragraph().",
547 );
548
549 // =========================================================================
550 // PAGE 7: LANDSCAPE — section break, wide table
551 // =========================================================================
552 doc.add_paragraph("").section_break(SectionBreak::NextPage);
553
554 doc.add_paragraph("6. Mixed Page Orientation")
555 .style("Heading1");
556
557 doc.add_paragraph(
558 "This page is in LANDSCAPE orientation. It was created using a section break \
559 followed by section_landscape(). This is useful for wide tables or charts.",
560 );
561
562 doc.add_paragraph("");
563
564 // Wide table for landscape
565 {
566 let mut tbl = doc.add_table(4, 7);
567 tbl = tbl.borders(BorderStyle::Single, 4, "2E75B6");
568
569 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
570 for (col, h) in headers.iter().enumerate() {
571 tbl.cell(0, col).unwrap().set_text(h);
572 tbl.cell(0, col).unwrap().shading("2E75B6");
573 }
574
575 let data = [
576 [
577 "North America",
578 "$1.2M",
579 "$1.3M",
580 "$1.4M",
581 "$1.5M",
582 "$1.6M",
583 "$7.0M",
584 ],
585 [
586 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
587 ],
588 [
589 "Asia Pacific",
590 "$0.5M",
591 "$0.6M",
592 "$0.7M",
593 "$0.7M",
594 "$0.8M",
595 "$3.3M",
596 ],
597 ];
598 for (row_idx, row_data) in data.iter().enumerate() {
599 for (col, val) in row_data.iter().enumerate() {
600 tbl.cell(row_idx + 1, col).unwrap().set_text(val);
601 }
602 }
603 }
604
605 // End landscape, return to portrait
606 doc.add_paragraph("")
607 .section_break(SectionBreak::NextPage)
608 .section_landscape();
609
610 // =========================================================================
611 // PAGE 8: BACK TO PORTRAIT — styles, final notes
612 // =========================================================================
613 doc.add_paragraph("7. Custom Styles & Summary")
614 .style("Heading1");
615
616 doc.add_paragraph(
617 "This final page is back in portrait orientation after a section break. \
618 The document has demonstrated:",
619 );
620
621 doc.add_bullet_list_item(
622 "Page setup: size, margins, header/footer distance, gutter",
623 0,
624 );
625 doc.add_bullet_list_item("Document metadata: title, author, subject, keywords", 0);
626 doc.add_bullet_list_item("Headers and footers: text, images, different first page", 0);
627 doc.add_bullet_list_item("Background images: full-page behind text", 0);
628 doc.add_bullet_list_item(
629 "Text formatting: bold, italic, underline, strike, color, size, font",
630 0,
631 );
632 doc.add_bullet_list_item(
633 "Advanced run formatting: superscript, subscript, caps, spacing",
634 0,
635 );
636 doc.add_bullet_list_item(
637 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
638 0,
639 );
640 doc.add_bullet_list_item("Bullet and numbered lists with nesting levels", 0);
641 doc.add_bullet_list_item("Tab stops with dot/underscore leaders", 0);
642 doc.add_bullet_list_item(
643 "Tables: borders, shading, column spans, row spans, nesting",
644 0,
645 );
646 doc.add_bullet_list_item("Vertical alignment in table cells", 0);
647 doc.add_bullet_list_item("Inline images", 0);
648 doc.add_bullet_list_item("Placeholder replacement in body and table cells", 0);
649 doc.add_bullet_list_item("Content insertion at specific positions", 0);
650 doc.add_bullet_list_item(
651 "Section breaks with mixed portrait/landscape orientation",
652 0,
653 );
654
655 doc.add_paragraph("");
656 doc.add_paragraph("All features above were built entirely from scratch using the rdocx API.")
657 .alignment(Alignment::Center)
658 .shading("E2EFDA")
659 .border_all(BorderStyle::Single, 2, "00AA00");
660
661 doc.save(path).unwrap();
662}More examples
86fn generate_feature_showcase(_samples_dir: &Path) -> Document {
87 let mut doc = Document::new();
88
89 // ── Page Setup & Metadata ──
90 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
91 doc.set_margins(
92 Length::inches(1.0),
93 Length::inches(1.0),
94 Length::inches(1.0),
95 Length::inches(1.0),
96 );
97 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
98 doc.set_gutter(Length::twips(0));
99 doc.set_title("rdocx Feature Showcase");
100 doc.set_author("rdocx Sample Generator");
101 doc.set_subject("Comprehensive feature demonstration");
102 doc.set_keywords("rdocx, docx, rust, sample, showcase");
103
104 // Headers & Footers with different first page
105 doc.set_different_first_page(true);
106 doc.set_first_page_header("rdocx");
107 doc.set_first_page_footer("Feature Showcase — Cover Page");
108 doc.set_header("rdocx Feature Showcase");
109 doc.set_footer("Generated by rdocx");
110
111 // ── COVER PAGE ──
112 let bg = create_sample_png(612, 792, [20, 45, 90]);
113 doc.add_background_image(&bg, "cover_bg.png");
114
115 for _ in 0..3 {
116 doc.add_paragraph("");
117 }
118 {
119 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
120 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
121 }
122 {
123 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
124 p.add_run("Complete Feature Showcase")
125 .size(28.0)
126 .color("FFFFFF")
127 .italic(true);
128 }
129 doc.add_paragraph("");
130 {
131 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
132 p.add_run("Every feature of the rdocx Rust library")
133 .size(13.0)
134 .color("B0C4DE");
135 }
136 {
137 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
138 p.add_run("demonstrated in a single document.")
139 .size(13.0)
140 .color("B0C4DE");
141 }
142
143 // ── TABLE OF CONTENTS ──
144 doc.add_paragraph("").page_break_before(true);
145 doc.insert_toc(doc.content_count(), 3);
146
147 // ── SECTION 1: TEXT FORMATTING ──
148 doc.add_paragraph("").page_break_before(true);
149 doc.add_paragraph("1. Text Formatting").style("Heading1");
150
151 // Paragraph Alignment
152 doc.add_paragraph("1.1 Paragraph Alignment")
153 .style("Heading2");
154 doc.add_paragraph("Left-aligned paragraph (default).")
155 .alignment(Alignment::Left);
156 doc.add_paragraph("Center-aligned paragraph.")
157 .alignment(Alignment::Center);
158 doc.add_paragraph("Right-aligned paragraph.")
159 .alignment(Alignment::Right);
160 doc.add_paragraph(
161 "Justified paragraph — this text is long enough to demonstrate how justified alignment \
162 distributes extra space across word gaps so lines fill the full width of the text area.",
163 )
164 .alignment(Alignment::Justify);
165
166 doc.add_paragraph("");
167
168 // Run Formatting
169 doc.add_paragraph("1.2 Run Formatting").style("Heading2");
170 {
171 let mut p = doc.add_paragraph("");
172 p.add_run("Bold").bold(true);
173 p.add_run(" | ");
174 p.add_run("Italic").italic(true);
175 p.add_run(" | ");
176 p.add_run("Bold Italic").bold(true).italic(true);
177 p.add_run(" | ");
178 p.add_run("Underline").underline(true);
179 p.add_run(" | ");
180 p.add_run("Strikethrough").strike(true);
181 p.add_run(" | ");
182 p.add_run("Double Strike").double_strike(true);
183 }
184 {
185 let mut p = doc.add_paragraph("");
186 p.add_run("Red").color("FF0000");
187 p.add_run(" | ");
188 p.add_run("Blue").color("0000FF");
189 p.add_run(" | ");
190 p.add_run("Green").color("00AA00");
191 p.add_run(" | ");
192 p.add_run("Highlighted").highlight("FFFF00");
193 }
194 {
195 let mut p = doc.add_paragraph("");
196 p.add_run("8pt").size(8.0);
197 p.add_run(" | ");
198 p.add_run("11pt").size(11.0);
199 p.add_run(" | ");
200 p.add_run("16pt").size(16.0);
201 p.add_run(" | ");
202 p.add_run("24pt").size(24.0);
203 }
204 {
205 let mut p = doc.add_paragraph("");
206 p.add_run("Arial").font("Arial");
207 p.add_run(" | ");
208 p.add_run("Times New Roman").font("Times New Roman");
209 p.add_run(" | ");
210 p.add_run("Courier New").font("Courier New");
211 }
212 {
213 let mut p = doc.add_paragraph("");
214 p.add_run("H");
215 p.add_run("2").subscript();
216 p.add_run("O (subscript) | E=mc");
217 p.add_run("2").superscript();
218 p.add_run(" (superscript)");
219 }
220 {
221 let mut p = doc.add_paragraph("");
222 p.add_run("ALL CAPS").all_caps(true);
223 p.add_run(" | ");
224 p.add_run("Small Caps").small_caps(true);
225 p.add_run(" | ");
226 p.add_run("Expanded").character_spacing(Length::twips(40));
227 p.add_run(" | ");
228 p.add_run("Hidden text (not visible)").hidden(true);
229 }
230
231 doc.add_paragraph("");
232
233 // Underline Styles
234 doc.add_paragraph("1.3 Underline Styles").style("Heading2");
235 {
236 let mut p = doc.add_paragraph("");
237 p.add_run("Single ")
238 .underline_style(rdocx::UnderlineStyle::Single);
239 p.add_run("Double ")
240 .underline_style(rdocx::UnderlineStyle::Double);
241 p.add_run("Thick ")
242 .underline_style(rdocx::UnderlineStyle::Thick);
243 p.add_run("Dotted ")
244 .underline_style(rdocx::UnderlineStyle::Dotted);
245 p.add_run("Dash ")
246 .underline_style(rdocx::UnderlineStyle::Dash);
247 p.add_run("Wave ")
248 .underline_style(rdocx::UnderlineStyle::Wave);
249 }
250
251 // ── SECTION 2: PARAGRAPH FORMATTING ──
252 doc.add_paragraph("").page_break_before(true);
253 doc.add_paragraph("2. Paragraph Formatting")
254 .style("Heading1");
255
256 doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
257 doc.add_paragraph("Paragraph with light green shading")
258 .shading("E2EFDA");
259 doc.add_paragraph("Paragraph with bottom border")
260 .border_bottom(BorderStyle::Single, 6, "2E75B6");
261 doc.add_paragraph("Paragraph with all borders (red)")
262 .border_all(BorderStyle::Single, 4, "FF0000");
263
264 doc.add_paragraph("2.2 Indentation").style("Heading2");
265 doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
266 .indent_left(Length::inches(1.0))
267 .hanging_indent(Length::inches(0.5));
268 doc.add_paragraph("0.5-inch first-line indent on this paragraph")
269 .first_line_indent(Length::inches(0.5));
270 doc.add_paragraph("Both left and right indent (0.75 inches each)")
271 .indent_left(Length::inches(0.75))
272 .indent_right(Length::inches(0.75));
273
274 doc.add_paragraph("2.3 Spacing & Line Height")
275 .style("Heading2");
276 doc.add_paragraph("Extra space before (24pt) and after (12pt)")
277 .space_before(Length::pt(24.0))
278 .space_after(Length::pt(12.0));
279 doc.add_paragraph(
280 "Double line spacing paragraph. This text should have extra vertical space between \
281 lines to demonstrate line_spacing_multiple(2.0).",
282 )
283 .line_spacing_multiple(2.0);
284 doc.add_paragraph("Exact 20pt line spacing (fixed height).")
285 .line_spacing(20.0);
286
287 doc.add_paragraph("2.4 Pagination Controls")
288 .style("Heading2");
289 doc.add_paragraph("keep_with_next — this paragraph stays with the next")
290 .keep_with_next(true);
291 doc.add_paragraph("(This paragraph was kept with the one above.)");
292 doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
293 .keep_together(true);
294 doc.add_paragraph("widow_control — prevents widow/orphan lines")
295 .widow_control(true);
296
297 // ── SECTION 3: LISTS ──
298 doc.add_paragraph("").page_break_before(true);
299 doc.add_paragraph("3. Lists").style("Heading1");
300
301 doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
302 doc.add_bullet_list_item("First item", 0);
303 doc.add_bullet_list_item("Second item", 0);
304 doc.add_bullet_list_item("Nested level 1", 1);
305 doc.add_bullet_list_item("Nested level 2", 2);
306 doc.add_bullet_list_item("Back to level 1", 1);
307 doc.add_bullet_list_item("Third item", 0);
308
309 doc.add_paragraph("");
310
311 doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
312 doc.add_numbered_list_item("First numbered item", 0);
313 doc.add_numbered_list_item("Second numbered item", 0);
314 doc.add_numbered_list_item("Sub-item A", 1);
315 doc.add_numbered_list_item("Sub-item B", 1);
316 doc.add_numbered_list_item("Third numbered item", 0);
317
318 // ── SECTION 4: TAB STOPS ──
319 doc.add_paragraph("");
320 doc.add_paragraph("4. Tab Stops").style("Heading1");
321
322 doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
323 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
324 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
325 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
326 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
327 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
328
329 doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
330 doc.add_paragraph("Item\t\tPrice")
331 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
332 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
333 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
334 doc.add_paragraph("Widget\t\t$19.99")
335 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
336 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
337 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
338 doc.add_paragraph("Gadget\t\t$249.50")
339 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
340 .add_tab_stop_with_leader(
341 TabAlignment::Right,
342 Length::inches(4.0),
343 TabLeader::Underscore,
344 )
345 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
346
347 // ── SECTION 5: TABLES ──
348 doc.add_paragraph("").page_break_before(true);
349 doc.add_paragraph("5. Tables").style("Heading1");
350
351 doc.add_paragraph("5.1 Basic Table").style("Heading2");
352 {
353 let mut tbl = doc
354 .add_table(4, 3)
355 .borders(BorderStyle::Single, 4, "000000");
356 for col in 0..3 {
357 tbl.cell(0, col).unwrap().shading("2E75B6");
358 }
359 tbl.cell(0, 0).unwrap().set_text("Name");
360 tbl.cell(0, 1).unwrap().set_text("Role");
361 tbl.cell(0, 2).unwrap().set_text("Location");
362 tbl.cell(1, 0).unwrap().set_text("Walter White");
363 tbl.cell(1, 1).unwrap().set_text("CEO");
364 tbl.cell(1, 2).unwrap().set_text("Albuquerque");
365 tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
366 tbl.cell(2, 1).unwrap().set_text("CTO");
367 tbl.cell(2, 2).unwrap().set_text("Remote");
368 tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
369 tbl.cell(3, 1).unwrap().set_text("Security");
370 tbl.cell(3, 2).unwrap().set_text("Washington");
371 }
372
373 doc.add_paragraph("");
374
375 doc.add_paragraph("5.2 Column Span & Cell Shading")
376 .style("Heading2");
377 {
378 let mut tbl = doc
379 .add_table(3, 4)
380 .borders(BorderStyle::Single, 4, "000000")
381 .width_pct(100.0);
382 tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
383 tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
384 tbl.cell(1, 0).unwrap().set_text("Region");
385 tbl.cell(1, 0).unwrap().shading("D6E4F0");
386 tbl.cell(1, 1).unwrap().set_text("Q1");
387 tbl.cell(1, 1).unwrap().shading("D6E4F0");
388 tbl.cell(1, 2).unwrap().set_text("Q2");
389 tbl.cell(1, 2).unwrap().shading("D6E4F0");
390 tbl.cell(1, 3).unwrap().set_text("Total");
391 tbl.cell(1, 3).unwrap().shading("D6E4F0");
392 tbl.cell(2, 0).unwrap().set_text("Americas");
393 tbl.cell(2, 1).unwrap().set_text("$2.4M");
394 tbl.cell(2, 2).unwrap().set_text("$2.7M");
395 tbl.cell(2, 3).unwrap().set_text("$5.1M");
396 }
397
398 doc.add_paragraph("");
399
400 doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
401 {
402 let mut tbl = doc
403 .add_table(4, 3)
404 .borders(BorderStyle::Single, 4, "000000");
405 tbl.cell(0, 0).unwrap().set_text("Category");
406 tbl.cell(0, 0).unwrap().shading("E2EFDA");
407 tbl.cell(0, 1).unwrap().set_text("Item");
408 tbl.cell(0, 1).unwrap().shading("E2EFDA");
409 tbl.cell(0, 2).unwrap().set_text("Price");
410 tbl.cell(0, 2).unwrap().shading("E2EFDA");
411 tbl.cell(1, 0).unwrap().set_text("Hardware");
412 tbl.cell(1, 0).unwrap().v_merge_restart();
413 tbl.cell(1, 1).unwrap().set_text("Laptop");
414 tbl.cell(1, 2).unwrap().set_text("$1,200");
415 tbl.cell(2, 0).unwrap().v_merge_continue();
416 tbl.cell(2, 1).unwrap().set_text("Monitor");
417 tbl.cell(2, 2).unwrap().set_text("$450");
418 tbl.cell(3, 0).unwrap().set_text("Software");
419 tbl.cell(3, 1).unwrap().set_text("IDE License");
420 tbl.cell(3, 2).unwrap().set_text("$200/yr");
421 }
422
423 doc.add_paragraph("");
424
425 doc.add_paragraph("5.4 Nested Table").style("Heading2");
426 {
427 let mut tbl = doc
428 .add_table(2, 2)
429 .borders(BorderStyle::Single, 6, "2E75B6");
430 tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
431 tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
432 tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
433 {
434 let mut cell = tbl.cell(1, 1).unwrap();
435 cell.set_text("Nested table below:");
436 let mut nested = cell
437 .add_table(2, 2)
438 .borders(BorderStyle::Single, 2, "FF6600");
439 nested.cell(0, 0).unwrap().set_text("A");
440 nested.cell(0, 1).unwrap().set_text("B");
441 nested.cell(1, 0).unwrap().set_text("C");
442 nested.cell(1, 1).unwrap().set_text("D");
443 }
444 }
445
446 doc.add_paragraph("");
447
448 doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
449 .style("Heading2");
450 {
451 let mut tbl = doc
452 .add_table(2, 3)
453 .borders(BorderStyle::Single, 4, "666666");
454 tbl.row(0).unwrap().height(Length::pt(50.0)).header();
455 tbl.cell(0, 0).unwrap().set_text("Top");
456 tbl.cell(0, 0)
457 .unwrap()
458 .vertical_alignment(VerticalAlignment::Top)
459 .shading("FFF2CC");
460 tbl.cell(0, 1).unwrap().set_text("Center");
461 tbl.cell(0, 1)
462 .unwrap()
463 .vertical_alignment(VerticalAlignment::Center)
464 .shading("D9E2F3");
465 tbl.cell(0, 2).unwrap().set_text("Bottom");
466 tbl.cell(0, 2)
467 .unwrap()
468 .vertical_alignment(VerticalAlignment::Bottom)
469 .shading("E2EFDA");
470 tbl.row(1).unwrap().cant_split();
471 tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
472 tbl.cell(1, 0).unwrap().no_wrap();
473 tbl.cell(1, 1).unwrap().set_text("Fixed width");
474 tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
475 tbl.cell(1, 2).unwrap().set_text("Normal");
476 }
477
478 // ── SECTION 6: IMAGES ──
479 doc.add_paragraph("").page_break_before(true);
480 doc.add_paragraph("6. Images").style("Heading1");
481
482 doc.add_paragraph("6.1 Inline Image").style("Heading2");
483 doc.add_paragraph("A blue gradient image below:");
484 let img = create_sample_png(200, 50, [0, 80, 200]);
485 doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
486
487 doc.add_paragraph("");
488 doc.add_paragraph("6.2 Header Image").style("Heading2");
489 let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
490 doc.set_header_image(
491 &hdr_img,
492 "header_logo.png",
493 Length::inches(2.0),
494 Length::inches(0.2),
495 );
496 doc.add_paragraph("The header now contains an inline image (check the top of this page).");
497
498 doc.add_paragraph("");
499 doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
500
501 // ── SECTION 7: CONTENT MANIPULATION ──
502 doc.add_paragraph("").page_break_before(true);
503 doc.add_paragraph("7. Content Manipulation")
504 .style("Heading1");
505
506 doc.add_paragraph("7.1 Placeholder Replacement")
507 .style("Heading2");
508 doc.add_paragraph("Customer: {{customer}}");
509 doc.add_paragraph("Date: {{date}}");
510 doc.add_paragraph("Reference: {{ref_number}}");
511 {
512 let mut tbl = doc
513 .add_table(2, 2)
514 .borders(BorderStyle::Single, 4, "000000");
515 tbl.cell(0, 0).unwrap().set_text("Project");
516 tbl.cell(0, 0).unwrap().shading("D6E4F0");
517 tbl.cell(0, 1).unwrap().set_text("{{project}}");
518 tbl.cell(1, 0).unwrap().set_text("Status");
519 tbl.cell(1, 0).unwrap().shading("D6E4F0");
520 tbl.cell(1, 1).unwrap().set_text("{{status}}");
521 }
522
523 let mut replacements = HashMap::new();
524 replacements.insert("{{customer}}", "Tensorbee Inc.");
525 replacements.insert("{{date}}", "February 22, 2026");
526 replacements.insert("{{ref_number}}", "TB-2026-001");
527 replacements.insert("{{project}}", "Infrastructure Upgrade");
528 replacements.insert("{{status}}", "In Progress");
529 let n = doc.replace_all(&replacements);
530 doc.add_paragraph(&format!("({n} placeholders replaced above)"));
531
532 doc.add_paragraph("");
533
534 doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
535 doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
536 let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
537 doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
538
539 doc.add_paragraph("");
540
541 doc.add_paragraph("7.3 Content Insertion").style("Heading2");
542 doc.add_paragraph("Section A: First content.");
543 doc.add_paragraph("Section C: Third content.");
544 if let Some(idx) = doc.find_content_index("Section C") {
545 doc.insert_paragraph(
546 idx,
547 "Section B: Inserted between A and C via find_content_index().",
548 );
549 }
550
551 // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
552 doc.add_paragraph("").section_break(SectionBreak::NextPage);
553 doc.add_paragraph("8. Section Breaks & Orientation")
554 .style("Heading1");
555 doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
556
557 {
558 let mut tbl = doc
559 .add_table(3, 7)
560 .borders(BorderStyle::Single, 4, "2E75B6");
561 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
562 for (c, h) in headers.iter().enumerate() {
563 tbl.cell(0, c).unwrap().set_text(h);
564 tbl.cell(0, c).unwrap().shading("2E75B6");
565 }
566 let data = [
567 [
568 "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
569 ],
570 [
571 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
572 ],
573 ];
574 for (r, row) in data.iter().enumerate() {
575 for (c, v) in row.iter().enumerate() {
576 tbl.cell(r + 1, c).unwrap().set_text(v);
577 }
578 }
579 }
580
581 doc.add_paragraph("")
582 .section_break(SectionBreak::NextPage)
583 .section_landscape();
584
585 // ── SECTION 9: CUSTOM STYLES ──
586 doc.add_paragraph("9. Custom Styles").style("Heading1");
587 doc.add_style(
588 StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
589 .based_on("Normal")
590 .paragraph_properties({
591 let mut ppr = rdocx_oxml::properties::CT_PPr::default();
592 ppr.shading = Some(rdocx_oxml::properties::CT_Shd {
593 val: "clear".to_string(),
594 color: None,
595 fill: Some("FFF2CC".to_string()),
596 });
597 ppr
598 })
599 .run_properties({
600 let mut rpr = rdocx_oxml::properties::CT_RPr::default();
601 rpr.bold = Some(true);
602 rpr.color = Some("C45911".to_string());
603 rpr
604 }),
605 );
606 doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
607 .style("CustomHighlight");
608
609 doc.add_paragraph("");
610
611 // ── SECTION 10: DOCUMENT INTELLIGENCE ──
612 doc.add_paragraph("10. Document Intelligence API")
613 .style("Heading1");
614
615 let wc = doc.word_count();
616 let hc = doc.headings().len();
617 let ic = doc.images().len();
618 let lc = doc.links().len();
619 doc.add_paragraph(&format!("Word count: {wc}"));
620 doc.add_paragraph(&format!("Heading count: {hc}"));
621 doc.add_paragraph(&format!("Image count: {ic}"));
622 doc.add_paragraph(&format!("Link count: {lc}"));
623
624 let outline = doc.document_outline();
625 doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
626
627 let issues = doc.audit_accessibility();
628 doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
629 for issue in &issues {
630 doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
631 }
632
633 // ── SECTION 11: DOCUMENT MERGING ──
634 doc.add_paragraph("");
635 doc.add_paragraph("11. Document Merging").style("Heading1");
636 {
637 let mut other = Document::new();
638 other.add_paragraph("This paragraph was merged from another document using append().");
639 doc.append(&other);
640 }
641
642 doc.add_paragraph("");
643
644 // ── SUMMARY ──
645 doc.add_paragraph("Summary of Demonstrated Features")
646 .style("Heading1");
647 let features = [
648 "Page setup: size, margins, header/footer distance, gutter",
649 "Document metadata: title, author, subject, keywords",
650 "Headers and footers: text, images, different first page",
651 "Background images (full-page behind text)",
652 "Table of Contents generation with bookmarks",
653 "Text formatting: bold, italic, underline styles, strike, color, size, font",
654 "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
655 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
656 "Pagination controls: keep-with-next, keep-together, widow control",
657 "Bullet and numbered lists with nesting",
658 "Tab stops with dot/underscore/hyphen leaders",
659 "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
660 "Row properties: header rows, exact height, cant-split",
661 "Cell properties: width, no-wrap, vertical alignment",
662 "Inline images and header images",
663 "Placeholder replacement (single and batch)",
664 "Regex-based find and replace",
665 "Content insertion at specific positions",
666 "Section breaks with mixed portrait/landscape",
667 "Custom paragraph and character styles",
668 "Document intelligence: word count, headings, outline, images, links",
669 "Accessibility audit",
670 "Document merging (append)",
671 "Export: DOCX, PDF, HTML, Markdown",
672 ];
673 for f in &features {
674 doc.add_bullet_list_item(f, 0);
675 }
676 doc.add_paragraph("");
677 doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
678 .alignment(Alignment::Center)
679 .shading("E2EFDA")
680 .border_all(BorderStyle::Single, 2, "00AA00");
681
682 doc
683}
684
685// =============================================================================
686// 2. PROPOSAL DOCUMENT — Deep navy + gold scheme
687// =============================================================================
688fn generate_proposal(_samples_dir: &Path) -> Document {
689 let mut doc = Document::new();
690
691 // Colors: Navy #1B2A4A, Gold #C5922E, Light #F4F1EB
692 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
693 doc.set_margins(
694 Length::inches(1.0),
695 Length::inches(1.0),
696 Length::inches(1.0),
697 Length::inches(1.0),
698 );
699 doc.set_title("AI Platform Modernization Proposal");
700 doc.set_author("Walter White");
701 doc.set_subject("Technology Proposal");
702 doc.set_keywords("proposal, AI, modernization, Tensorbee");
703
704 // Banner header
705 let logo_img = create_logo_png(220, 48);
706 let banner = build_header_banner_xml(
707 "rId1",
708 &BannerOpts {
709 bg_color: "1B2A4A",
710 banner_width: 7772400,
711 banner_height: 914400,
712 logo_width: 2011680,
713 logo_height: 438912,
714 logo_x_offset: 295125,
715 logo_y_offset: 237744,
716 },
717 );
718 doc.set_raw_header_with_images(
719 banner,
720 &[("rId1", &logo_img, "logo.png")],
721 rdocx_oxml::header_footer::HdrFtrType::Default,
722 );
723 doc.set_different_first_page(true);
724 let cover_banner = build_header_banner_xml(
725 "rId1",
726 &BannerOpts {
727 bg_color: "C5922E",
728 banner_width: 7772400,
729 banner_height: 914400,
730 logo_width: 2011680,
731 logo_height: 438912,
732 logo_x_offset: 295125,
733 logo_y_offset: 237744,
734 },
735 );
736 doc.set_raw_header_with_images(
737 cover_banner,
738 &[("rId1", &logo_img, "logo.png")],
739 rdocx_oxml::header_footer::HdrFtrType::First,
740 );
741 doc.set_margins(
742 Length::twips(2292),
743 Length::twips(1440),
744 Length::twips(1440),
745 Length::twips(1440),
746 );
747 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
748 doc.set_footer("Tensorbee — Confidential");
749
750 // Custom styles
751 doc.add_style(
752 StyleBuilder::paragraph("ProposalTitle", "Proposal Title")
753 .based_on("Normal")
754 .run_properties({
755 let mut rpr = rdocx_oxml::properties::CT_RPr::default();
756 rpr.bold = Some(true);
757 rpr.font_ascii = Some("Georgia".to_string());
758 rpr.font_hansi = Some("Georgia".to_string());
759 rpr.sz = Some(rdocx_oxml::HalfPoint(56)); // half-points → 28pt
760 rpr.color = Some("1B2A4A".to_string());
761 rpr
762 }),
763 );
764
765 // ── Cover Page ──
766 for _ in 0..4 {
767 doc.add_paragraph("");
768 }
769 doc.add_paragraph("AI Platform Modernization")
770 .style("ProposalTitle")
771 .alignment(Alignment::Center);
772 doc.add_paragraph("")
773 .alignment(Alignment::Center)
774 .border_bottom(BorderStyle::Single, 8, "C5922E");
775 {
776 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
777 p.add_run("Prepared for Global Financial Corp.")
778 .size(14.0)
779 .color("666666")
780 .italic(true);
781 }
782 {
783 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
784 p.add_run("Prepared by: Walter White, CEO — Tensorbee")
785 .size(12.0)
786 .color("888888");
787 }
788 {
789 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
790 p.add_run("February 22, 2026").size(12.0).color("888888");
791 }
792
793 // ── TOC ──
794 doc.add_paragraph("").page_break_before(true);
795 doc.add_paragraph("Table of Contents").style("Heading1");
796 // We'll add a manual-style TOC since the insert_toc goes at a specific position
797 doc.insert_toc(doc.content_count(), 2);
798
799 // ── Executive Summary ──
800 doc.add_paragraph("").page_break_before(true);
801 doc.add_paragraph("Executive Summary").style("Heading1");
802 doc.add_paragraph(
803 "Tensorbee proposes a comprehensive modernization of Global Financial Corp's AI platform, \
804 transitioning from legacy batch-processing systems to a real-time inference architecture. \
805 This transformation will reduce model deployment time from weeks to hours, improve \
806 prediction accuracy by an estimated 23%, and deliver $4.2M in annual operational savings.",
807 )
808 .first_line_indent(Length::inches(0.3));
809 doc.add_paragraph(
810 "The project spans three phases over 18 months, with a total investment of $2.8M. \
811 Tensorbee brings deep expertise in ML infrastructure, having successfully completed \
812 similar transformations for three Fortune 500 financial institutions.",
813 )
814 .first_line_indent(Length::inches(0.3));
815
816 // Key metrics table
817 doc.add_paragraph("");
818 {
819 let mut tbl = doc
820 .add_table(5, 2)
821 .borders(BorderStyle::Single, 4, "1B2A4A")
822 .width_pct(80.0);
823 tbl.cell(0, 0).unwrap().set_text("Key Metric");
824 tbl.cell(0, 0).unwrap().shading("1B2A4A");
825 tbl.cell(0, 1).unwrap().set_text("Value");
826 tbl.cell(0, 1).unwrap().shading("1B2A4A");
827 let rows = [
828 ("Total Investment", "$2.8M"),
829 ("Annual Savings", "$4.2M"),
830 ("ROI (Year 1)", "150%"),
831 ("Timeline", "18 months"),
832 ];
833 for (i, (k, v)) in rows.iter().enumerate() {
834 tbl.cell(i + 1, 0).unwrap().set_text(k);
835 if i % 2 == 0 {
836 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
837 }
838 tbl.cell(i + 1, 1).unwrap().set_text(v);
839 if i % 2 == 0 {
840 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
841 }
842 }
843 }
844
845 // ── Problem Statement ──
846 doc.add_paragraph("").page_break_before(true);
847 doc.add_paragraph("Problem Statement").style("Heading1");
848 doc.add_paragraph(
849 "Global Financial Corp's current AI infrastructure faces three critical challenges:",
850 );
851 doc.add_numbered_list_item("Deployment Latency — New models take 3-4 weeks to deploy to production, compared to the industry benchmark of 2-3 days.", 0);
852 doc.add_numbered_list_item("Scalability Constraints — The batch processing architecture cannot handle real-time inference demands during peak trading hours.", 0);
853 doc.add_numbered_list_item("Technical Debt — Legacy Python 2.7 codebases and manual deployment scripts create reliability risks and slow iteration speed.", 0);
854
855 // ── Proposed Solution ──
856 doc.add_paragraph("").page_break_before(true);
857 doc.add_paragraph("Proposed Solution").style("Heading1");
858
859 doc.add_paragraph("Phase 1: Foundation (Months 1-6)")
860 .style("Heading2");
861 doc.add_bullet_list_item("Deploy Kubernetes-based ML serving infrastructure", 0);
862 doc.add_bullet_list_item("Implement CI/CD pipeline for model deployment", 0);
863 doc.add_bullet_list_item("Migrate top 5 production models to new platform", 0);
864
865 doc.add_paragraph("Phase 2: Expansion (Months 7-12)")
866 .style("Heading2");
867 doc.add_bullet_list_item(
868 "Real-time feature engineering pipeline (Apache Kafka + Flink)",
869 0,
870 );
871 doc.add_bullet_list_item("A/B testing framework for model variants", 0);
872 doc.add_bullet_list_item("Automated model monitoring and drift detection", 0);
873
874 doc.add_paragraph("Phase 3: Optimization (Months 13-18)")
875 .style("Heading2");
876 doc.add_bullet_list_item("GPU inference optimization (TensorRT/ONNX Runtime)", 0);
877 doc.add_bullet_list_item("Multi-region deployment for latency reduction", 0);
878 doc.add_bullet_list_item(
879 "Self-service model deployment portal for data science teams",
880 0,
881 );
882
883 // ── Budget ──
884 doc.add_paragraph("Budget Breakdown").style("Heading1");
885 {
886 let mut tbl = doc
887 .add_table(6, 3)
888 .borders(BorderStyle::Single, 4, "1B2A4A")
889 .width_pct(100.0);
890 tbl.cell(0, 0).unwrap().set_text("Category");
891 tbl.cell(0, 0).unwrap().shading("1B2A4A");
892 tbl.cell(0, 1).unwrap().set_text("Cost");
893 tbl.cell(0, 1).unwrap().shading("1B2A4A");
894 tbl.cell(0, 2).unwrap().set_text("Notes");
895 tbl.cell(0, 2).unwrap().shading("1B2A4A");
896 let items = [
897 (
898 "Infrastructure",
899 "$450,000",
900 "Cloud compute + GPU instances",
901 ),
902 ("Engineering Services", "$1,600,000", "12 FTE x 18 months"),
903 (
904 "Software Licenses",
905 "$350,000",
906 "Kafka, monitoring, CI/CD tools",
907 ),
908 (
909 "Training & Enablement",
910 "$200,000",
911 "Team training, documentation",
912 ),
913 ("Contingency (10%)", "$200,000", "Risk buffer"),
914 ];
915 for (i, (cat, cost, note)) in items.iter().enumerate() {
916 tbl.cell(i + 1, 0).unwrap().set_text(cat);
917 tbl.cell(i + 1, 1).unwrap().set_text(cost);
918 tbl.cell(i + 1, 2).unwrap().set_text(note);
919 if i % 2 == 0 {
920 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
921 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
922 tbl.cell(i + 1, 2).unwrap().shading("F4F1EB");
923 }
924 }
925 }
926
927 // ── Closing ──
928 doc.add_paragraph("");
929 doc.add_paragraph("Next Steps").style("Heading1");
930 doc.add_numbered_list_item(
931 "Schedule technical deep-dive with GFC engineering team (Week 1)",
932 0,
933 );
934 doc.add_numbered_list_item("Finalize scope and sign SOW (Week 2-3)", 0);
935 doc.add_numbered_list_item("Kick off Phase 1 with joint planning session (Week 4)", 0);
936
937 doc.add_paragraph("");
938 {
939 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
940 p.add_run("Walter White")
941 .bold(true)
942 .size(14.0)
943 .color("1B2A4A");
944 }
945 {
946 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
947 p.add_run("CEO, Tensorbee | walter@tensorbee.com")
948 .size(10.0)
949 .color("888888");
950 }
951
952 doc
953}
954
955// =============================================================================
956// 3. QUOTE / BILL OF MATERIALS — Teal + orange scheme
957// =============================================================================
958fn generate_quote(_samples_dir: &Path) -> Document {
959 let mut doc = Document::new();
960
961 // Colors: Teal #008B8B, Dark #1A3C3C, Orange #E07020, Light #F0F8F8
962 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
963 doc.set_margins(
964 Length::inches(0.75),
965 Length::inches(0.75),
966 Length::inches(0.75),
967 Length::inches(0.75),
968 );
969 doc.set_title("Quotation QT-2026-0147");
970 doc.set_author("Walter White");
971 doc.set_keywords("quote, BOM, Tensorbee");
972
973 doc.set_header("Tensorbee — Quotation");
974 doc.set_footer("QT-2026-0147 | Page");
975
976 // ── Header Block ──
977 {
978 let mut p = doc.add_paragraph("").alignment(Alignment::Left);
979 p.add_run("TENSORBEE")
980 .bold(true)
981 .size(28.0)
982 .color("008B8B")
983 .font("Helvetica");
984 }
985 doc.add_paragraph("123 Innovation Drive, Suite 400")
986 .alignment(Alignment::Left);
987 doc.add_paragraph("San Francisco, CA 94105 | +1 (415) 555-0199")
988 .alignment(Alignment::Left);
989 doc.add_paragraph("")
990 .border_bottom(BorderStyle::Single, 8, "008B8B");
991
992 // Quote meta
993 doc.add_paragraph("");
994 {
995 let mut tbl = doc.add_table(4, 4).width_pct(100.0);
996 tbl.cell(0, 0).unwrap().set_text("QUOTATION");
997 tbl.cell(0, 0).unwrap().shading("008B8B").grid_span(2);
998 tbl.cell(0, 2).unwrap().set_text("DATE");
999 tbl.cell(0, 2).unwrap().shading("008B8B");
1000 tbl.cell(0, 3).unwrap().set_text("VALID UNTIL");
1001 tbl.cell(0, 3).unwrap().shading("008B8B");
1002 tbl.cell(1, 0).unwrap().set_text("Quote #:");
1003 tbl.cell(1, 1).unwrap().set_text("QT-2026-0147");
1004 tbl.cell(1, 2).unwrap().set_text("Feb 22, 2026");
1005 tbl.cell(1, 3).unwrap().set_text("Mar 22, 2026");
1006 tbl.cell(2, 0).unwrap().set_text("Prepared by:");
1007 tbl.cell(2, 1).unwrap().set_text("Walter White");
1008 tbl.cell(2, 2).unwrap().set_text("Payment:");
1009 tbl.cell(2, 3).unwrap().set_text("Net 30");
1010 tbl.cell(3, 0).unwrap().set_text("Customer:");
1011 tbl.cell(3, 1).unwrap().set_text("Meridian Dynamics LLC");
1012 tbl.cell(3, 2).unwrap().set_text("Currency:");
1013 tbl.cell(3, 3).unwrap().set_text("USD");
1014 }
1015
1016 doc.add_paragraph("");
1017
1018 // ── Bill of Materials ──
1019 {
1020 let mut p = doc.add_paragraph("");
1021 p.add_run("Bill of Materials")
1022 .bold(true)
1023 .size(16.0)
1024 .color("1A3C3C");
1025 }
1026 doc.add_paragraph("");
1027
1028 {
1029 let mut tbl = doc
1030 .add_table(10, 6)
1031 .borders(BorderStyle::Single, 2, "008B8B")
1032 .width_pct(100.0)
1033 .layout_fixed();
1034 // Headers
1035 let hdrs = [
1036 "#",
1037 "Part Number",
1038 "Description",
1039 "Qty",
1040 "Unit Price",
1041 "Total",
1042 ];
1043 for (c, h) in hdrs.iter().enumerate() {
1044 tbl.cell(0, c).unwrap().set_text(h);
1045 tbl.cell(0, c).unwrap().shading("008B8B");
1046 }
1047
1048 let items: Vec<(&str, &str, &str, &str, &str)> = vec![
1049 (
1050 "TB-GPU-A100",
1051 "NVIDIA A100 80GB GPU",
1052 "4",
1053 "$12,500.00",
1054 "$50,000.00",
1055 ),
1056 (
1057 "TB-SRV-R750",
1058 "Dell R750xa Server Chassis",
1059 "2",
1060 "$8,200.00",
1061 "$16,400.00",
1062 ),
1063 (
1064 "TB-RAM-256G",
1065 "256GB DDR5 ECC Memory Module",
1066 "8",
1067 "$890.00",
1068 "$7,120.00",
1069 ),
1070 (
1071 "TB-SSD-3840",
1072 "3.84TB NVMe U.2 SSD",
1073 "8",
1074 "$1,150.00",
1075 "$9,200.00",
1076 ),
1077 (
1078 "TB-NET-CX7",
1079 "ConnectX-7 200GbE NIC",
1080 "4",
1081 "$1,800.00",
1082 "$7,200.00",
1083 ),
1084 (
1085 "TB-SW-48P",
1086 "48-Port 100GbE Switch",
1087 "1",
1088 "$22,000.00",
1089 "$22,000.00",
1090 ),
1091 (
1092 "TB-CAB-RACK",
1093 "42U Server Rack + PDU",
1094 "1",
1095 "$4,500.00",
1096 "$4,500.00",
1097 ),
1098 (
1099 "TB-SVC-INST",
1100 "Installation & Configuration",
1101 "1",
1102 "$8,500.00",
1103 "$8,500.00",
1104 ),
1105 (
1106 "TB-SVC-SUPP",
1107 "3-Year Premium Support",
1108 "1",
1109 "$15,000.00",
1110 "$15,000.00",
1111 ),
1112 ];
1113
1114 for (i, (pn, desc, qty, unit, total)) in items.iter().enumerate() {
1115 let row = i + 1;
1116 tbl.cell(row, 0).unwrap().set_text(&format!("{}", i + 1));
1117 tbl.cell(row, 1).unwrap().set_text(pn);
1118 tbl.cell(row, 2).unwrap().set_text(desc);
1119 tbl.cell(row, 3).unwrap().set_text(qty);
1120 tbl.cell(row, 4).unwrap().set_text(unit);
1121 tbl.cell(row, 5).unwrap().set_text(total);
1122 if i % 2 == 0 {
1123 for c in 0..6 {
1124 tbl.cell(row, c).unwrap().shading("F0F8F8");
1125 }
1126 }
1127 }
1128 }
1129
1130 doc.add_paragraph("");
1131
1132 // ── Totals ──
1133 {
1134 let mut tbl = doc
1135 .add_table(4, 2)
1136 .borders(BorderStyle::Single, 2, "008B8B")
1137 .width(Length::inches(3.5))
1138 .alignment(Alignment::Right);
1139 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1140 tbl.cell(0, 1).unwrap().set_text("$139,920.00");
1141 tbl.cell(1, 0).unwrap().set_text("Shipping & Handling");
1142 tbl.cell(1, 1).unwrap().set_text("$2,500.00");
1143 tbl.cell(2, 0).unwrap().set_text("Tax (8.625%)");
1144 tbl.cell(2, 1).unwrap().set_text("$12,068.10");
1145 tbl.cell(3, 0).unwrap().set_text("TOTAL");
1146 tbl.cell(3, 0).unwrap().shading("E07020");
1147 tbl.cell(3, 1).unwrap().set_text("$154,488.10");
1148 tbl.cell(3, 1).unwrap().shading("E07020");
1149 }
1150
1151 doc.add_paragraph("");
1152
1153 // ── Terms & Conditions ──
1154 {
1155 let mut p = doc.add_paragraph("");
1156 p.add_run("Terms & Conditions")
1157 .bold(true)
1158 .size(14.0)
1159 .color("1A3C3C");
1160 }
1161 doc.add_numbered_list_item(
1162 "This quotation is valid for 30 calendar days from the date of issue.",
1163 0,
1164 );
1165 doc.add_numbered_list_item(
1166 "All prices are in USD and exclusive of applicable taxes unless stated.",
1167 0,
1168 );
1169 doc.add_numbered_list_item("Standard lead time is 4-6 weeks from PO receipt.", 0);
1170 doc.add_numbered_list_item(
1171 "Payment terms: Net 30 from invoice date. 2% discount for payment within 10 days.",
1172 0,
1173 );
1174 doc.add_numbered_list_item(
1175 "Warranty: 3-year manufacturer warranty on all hardware components.",
1176 0,
1177 );
1178 doc.add_numbered_list_item(
1179 "Returns subject to 15% restocking fee if initiated after 14 days.",
1180 0,
1181 );
1182
1183 doc.add_paragraph("");
1184 doc.add_paragraph("")
1185 .border_bottom(BorderStyle::Single, 4, "008B8B");
1186 doc.add_paragraph("");
1187 {
1188 let mut p = doc.add_paragraph("");
1189 p.add_run("Accepted by: ").bold(true);
1190 p.add_run("___________________________ Date: ___________");
1191 }
1192 {
1193 let mut p = doc.add_paragraph("");
1194 p.add_run("Print Name: ").bold(true);
1195 p.add_run("___________________________ Title: ___________");
1196 }
1197
1198 doc
1199}
1200
1201// =============================================================================
1202// 4. INVOICE — Crimson + charcoal scheme
1203// =============================================================================
1204fn generate_invoice(_samples_dir: &Path) -> Document {
1205 let mut doc = Document::new();
1206
1207 // Colors: Crimson #B22222, Charcoal #333333, Light #FAF0F0
1208 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1209 doc.set_margins(
1210 Length::inches(0.75),
1211 Length::inches(0.75),
1212 Length::inches(0.75),
1213 Length::inches(0.75),
1214 );
1215 doc.set_title("Invoice INV-2026-0392");
1216 doc.set_author("Walter White");
1217 doc.set_keywords("invoice, Tensorbee");
1218
1219 doc.set_footer("Tensorbee — Thank you for your business!");
1220
1221 // ── Company & Invoice Header ──
1222 {
1223 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1224 // Company name left, INVOICE right
1225 {
1226 let mut cell = tbl.cell(0, 0).unwrap();
1227 let mut p = cell.add_paragraph("");
1228 p.add_run("TENSORBEE")
1229 .bold(true)
1230 .size(32.0)
1231 .color("B22222")
1232 .font("Helvetica");
1233 }
1234 {
1235 let mut cell = tbl.cell(0, 1).unwrap();
1236 let mut p = cell.add_paragraph("");
1237 p.add_run("INVOICE").bold(true).size(32.0).color("333333");
1238 }
1239 tbl.cell(1, 0)
1240 .unwrap()
1241 .set_text("123 Innovation Drive, Suite 400");
1242 tbl.cell(1, 1).unwrap().set_text("Invoice #: INV-2026-0392");
1243 tbl.cell(2, 0).unwrap().set_text("San Francisco, CA 94105");
1244 tbl.cell(2, 1).unwrap().set_text("Date: February 22, 2026");
1245 tbl.cell(3, 0).unwrap().set_text("walter@tensorbee.com");
1246 tbl.cell(3, 1).unwrap().set_text("Due Date: March 24, 2026");
1247 }
1248
1249 doc.add_paragraph("")
1250 .border_bottom(BorderStyle::Thick, 8, "B22222");
1251 doc.add_paragraph("");
1252
1253 // ── Bill To / Ship To ──
1254 {
1255 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1256 tbl.cell(0, 0).unwrap().set_text("BILL TO");
1257 tbl.cell(0, 0).unwrap().shading("B22222");
1258 tbl.cell(0, 1).unwrap().set_text("SHIP TO");
1259 tbl.cell(0, 1).unwrap().shading("B22222");
1260 tbl.cell(1, 0).unwrap().set_text("Meridian Dynamics LLC");
1261 tbl.cell(1, 1).unwrap().set_text("Meridian Dynamics LLC");
1262 tbl.cell(2, 0).unwrap().set_text("456 Enterprise Blvd");
1263 tbl.cell(2, 1).unwrap().set_text("Attn: Server Room B");
1264 tbl.cell(3, 0).unwrap().set_text("Austin, TX 78701");
1265 tbl.cell(3, 1)
1266 .unwrap()
1267 .set_text("456 Enterprise Blvd, Austin, TX 78701");
1268 }
1269
1270 doc.add_paragraph("");
1271
1272 // ── Line Items ──
1273 {
1274 let mut tbl = doc
1275 .add_table(8, 5)
1276 .borders(BorderStyle::Single, 2, "B22222")
1277 .width_pct(100.0);
1278 let hdrs = ["Description", "Qty", "Unit Price", "Tax", "Amount"];
1279 for (c, h) in hdrs.iter().enumerate() {
1280 tbl.cell(0, c).unwrap().set_text(h);
1281 tbl.cell(0, c).unwrap().shading("B22222");
1282 }
1283 let items = [
1284 (
1285 "ML Infrastructure Setup — Phase 1",
1286 "1",
1287 "$45,000.00",
1288 "$3,881.25",
1289 "$48,881.25",
1290 ),
1291 (
1292 "Data Pipeline Development (160 hrs)",
1293 "160",
1294 "$225.00",
1295 "$3,105.00",
1296 "$39,105.00",
1297 ),
1298 (
1299 "GPU Cluster Configuration",
1300 "1",
1301 "$12,000.00",
1302 "$1,035.00",
1303 "$13,035.00",
1304 ),
1305 (
1306 "API Gateway Implementation",
1307 "1",
1308 "$18,500.00",
1309 "$1,595.63",
1310 "$20,095.63",
1311 ),
1312 (
1313 "Load Testing & QA (80 hrs)",
1314 "80",
1315 "$195.00",
1316 "$1,345.50",
1317 "$16,945.50",
1318 ),
1319 (
1320 "Documentation & Training",
1321 "1",
1322 "$8,000.00",
1323 "$690.00",
1324 "$8,690.00",
1325 ),
1326 (
1327 "Project Management (3 months)",
1328 "3",
1329 "$6,500.00",
1330 "$1,679.63",
1331 "$21,179.63",
1332 ),
1333 ];
1334 for (i, (desc, qty, unit, tax, amt)) in items.iter().enumerate() {
1335 let r = i + 1;
1336 tbl.cell(r, 0).unwrap().set_text(desc);
1337 tbl.cell(r, 1).unwrap().set_text(qty);
1338 tbl.cell(r, 2).unwrap().set_text(unit);
1339 tbl.cell(r, 3).unwrap().set_text(tax);
1340 tbl.cell(r, 4).unwrap().set_text(amt);
1341 if i % 2 == 0 {
1342 for c in 0..5 {
1343 tbl.cell(r, c).unwrap().shading("FAF0F0");
1344 }
1345 }
1346 }
1347 }
1348
1349 doc.add_paragraph("");
1350
1351 // ── Totals ──
1352 {
1353 let mut tbl = doc
1354 .add_table(4, 2)
1355 .borders(BorderStyle::Single, 2, "B22222")
1356 .width(Length::inches(3.0))
1357 .alignment(Alignment::Right);
1358 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1359 tbl.cell(0, 1).unwrap().set_text("$154,600.00");
1360 tbl.cell(1, 0).unwrap().set_text("Tax (8.625%)");
1361 tbl.cell(1, 1).unwrap().set_text("$13,334.25");
1362 tbl.cell(2, 0).unwrap().set_text("Discount (5%)");
1363 tbl.cell(2, 1).unwrap().set_text("-$7,730.00");
1364 tbl.cell(3, 0).unwrap().set_text("AMOUNT DUE");
1365 tbl.cell(3, 0).unwrap().shading("B22222");
1366 tbl.cell(3, 1).unwrap().set_text("$160,204.25");
1367 tbl.cell(3, 1).unwrap().shading("B22222");
1368 }
1369
1370 doc.add_paragraph("");
1371 doc.add_paragraph("");
1372
1373 // ── Payment Details ──
1374 {
1375 let mut p = doc.add_paragraph("");
1376 p.add_run("Payment Details")
1377 .bold(true)
1378 .size(14.0)
1379 .color("333333");
1380 }
1381 doc.add_paragraph("Bank: Silicon Valley Bank")
1382 .indent_left(Length::inches(0.3));
1383 doc.add_paragraph("Account: Tensorbee Inc. — 0847-2953-1120")
1384 .indent_left(Length::inches(0.3));
1385 doc.add_paragraph("Routing: 121140399")
1386 .indent_left(Length::inches(0.3));
1387 doc.add_paragraph("Swift: SVBKUS6S")
1388 .indent_left(Length::inches(0.3));
1389
1390 doc.add_paragraph("");
1391
1392 doc.add_paragraph("Please include invoice number INV-2026-0392 in the payment reference.")
1393 .shading("FAF0F0")
1394 .border_all(BorderStyle::Single, 2, "B22222");
1395
1396 doc
1397}
1398
1399// =============================================================================
1400// 5. REPORT — Forest green + earth tones, with images & hierarchical sections
1401// =============================================================================
1402fn generate_report(_samples_dir: &Path) -> Document {
1403 let mut doc = Document::new();
1404
1405 // Colors: Forest #2D5016, Sage #6B8E23, Earth #8B7355, Cream #FFFAF0
1406 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1407 doc.set_margins(
1408 Length::inches(1.0),
1409 Length::inches(1.0),
1410 Length::inches(1.0),
1411 Length::inches(1.0),
1412 );
1413 doc.set_title("Q4 2025 Environmental Impact Report");
1414 doc.set_author("Walter White");
1415 doc.set_subject("Quarterly Environmental Report");
1416 doc.set_keywords("environment, sustainability, report, Tensorbee");
1417
1418 doc.set_different_first_page(true);
1419 doc.set_first_page_header("");
1420 doc.set_header("Tensorbee — Q4 2025 Environmental Impact Report");
1421 doc.set_footer("CONFIDENTIAL — Page");
1422
1423 // ── Cover ──
1424 let cover_bg = create_sample_png(612, 792, [20, 50, 15]);
1425 doc.add_background_image(&cover_bg, "report_cover.png");
1426
1427 for _ in 0..5 {
1428 doc.add_paragraph("");
1429 }
1430 {
1431 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1432 p.add_run("Q4 2025")
1433 .bold(true)
1434 .size(48.0)
1435 .color("FFFFFF")
1436 .font("Georgia");
1437 }
1438 {
1439 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1440 p.add_run("Environmental Impact Report")
1441 .size(24.0)
1442 .color("90EE90")
1443 .font("Georgia")
1444 .italic(true);
1445 }
1446 doc.add_paragraph("");
1447 {
1448 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1449 p.add_run("Tensorbee — Sustainability Division")
1450 .size(14.0)
1451 .color("C0C0C0");
1452 }
1453 {
1454 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1455 p.add_run("Prepared by Walter White, Chief Sustainability Officer")
1456 .size(11.0)
1457 .color("AAAAAA");
1458 }
1459
1460 // ── TOC ──
1461 doc.add_paragraph("").page_break_before(true);
1462 doc.insert_toc(doc.content_count(), 3);
1463
1464 // ── Section 1: Executive Overview ──
1465 doc.add_paragraph("").page_break_before(true);
1466 doc.add_paragraph("Executive Overview").style("Heading1");
1467 doc.add_paragraph(
1468 "This report presents Tensorbee's environmental performance for Q4 2025. Our \
1469 sustainability initiatives have yielded a 34% reduction in carbon emissions compared \
1470 to Q4 2024, exceeding our target of 25%. Key achievements include the transition to \
1471 100% renewable energy in our primary data centers and the launch of our carbon offset \
1472 marketplace.",
1473 )
1474 .first_line_indent(Length::inches(0.3));
1475
1476 // Image: performance chart
1477 let chart_img = create_chart_png(400, 200);
1478 doc.add_paragraph("");
1479 doc.add_picture(
1480 &chart_img,
1481 "performance_chart.png",
1482 Length::inches(5.0),
1483 Length::inches(2.5),
1484 )
1485 .alignment(Alignment::Center);
1486 {
1487 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1488 p.add_run("Figure 1: Quarterly Carbon Emissions (tonnes CO2e)")
1489 .italic(true)
1490 .size(9.0)
1491 .color("666666");
1492 }
1493
1494 // ── Section 2: Energy Consumption ──
1495 doc.add_paragraph("").page_break_before(true);
1496 doc.add_paragraph("Energy Consumption").style("Heading1");
1497
1498 doc.add_paragraph("Data Center Operations")
1499 .style("Heading2");
1500 doc.add_paragraph(
1501 "Our three primary data centers consumed a combined 4.2 GWh in Q4 2025, \
1502 a 12% reduction from Q3 2025 driven by improved cooling efficiency and \
1503 server consolidation.",
1504 );
1505
1506 // Data table
1507 {
1508 let mut tbl = doc
1509 .add_table(5, 4)
1510 .borders(BorderStyle::Single, 4, "2D5016")
1511 .width_pct(100.0);
1512 let hdrs = ["Data Center", "Capacity (MW)", "Usage (GWh)", "PUE"];
1513 for (c, h) in hdrs.iter().enumerate() {
1514 tbl.cell(0, c).unwrap().set_text(h);
1515 tbl.cell(0, c).unwrap().shading("2D5016");
1516 }
1517 let rows = [
1518 ("San Francisco (Primary)", "3.2", "1.8", "1.12"),
1519 ("Dublin (EU)", "2.1", "1.2", "1.18"),
1520 ("Singapore (APAC)", "1.8", "1.2", "1.24"),
1521 ("TOTAL", "7.1", "4.2", "1.17 avg"),
1522 ];
1523 for (i, (dc, cap, use_, pue)) in rows.iter().enumerate() {
1524 tbl.cell(i + 1, 0).unwrap().set_text(dc);
1525 tbl.cell(i + 1, 1).unwrap().set_text(cap);
1526 tbl.cell(i + 1, 2).unwrap().set_text(use_);
1527 tbl.cell(i + 1, 3).unwrap().set_text(pue);
1528 if i == 3 {
1529 for c in 0..4 {
1530 tbl.cell(i + 1, c).unwrap().shading("E2EFDA");
1531 }
1532 }
1533 }
1534 }
1535
1536 doc.add_paragraph("");
1537
1538 doc.add_paragraph("Renewable Energy Mix").style("Heading2");
1539 doc.add_paragraph("Breakdown of energy sources across all facilities:");
1540
1541 doc.add_bullet_list_item("Solar PV: 42% (1.76 GWh)", 0);
1542 doc.add_bullet_list_item("Wind Power Purchase Agreements: 38% (1.60 GWh)", 0);
1543 doc.add_bullet_list_item("Hydroelectric: 12% (0.50 GWh)", 0);
1544 doc.add_bullet_list_item("Grid (non-renewable): 8% (0.34 GWh)", 0);
1545
1546 // ── Section 3: Water & Waste ──
1547 doc.add_paragraph("Water & Waste Management")
1548 .style("Heading1");
1549
1550 doc.add_paragraph("Water Usage").style("Heading2");
1551 doc.add_paragraph(
1552 "Total water consumption was 12.4 million gallons, a 15% reduction from Q3. \
1553 Our closed-loop cooling systems now recycle 78% of water used in cooling operations.",
1554 );
1555
1556 doc.add_paragraph("Waste Reduction").style("Heading2");
1557 doc.add_paragraph("E-waste management results for Q4:")
1558 .keep_with_next(true);
1559 {
1560 let mut tbl = doc
1561 .add_table(4, 3)
1562 .borders(BorderStyle::Single, 2, "6B8E23")
1563 .width_pct(80.0);
1564 tbl.cell(0, 0).unwrap().set_text("Category");
1565 tbl.cell(0, 0).unwrap().shading("6B8E23");
1566 tbl.cell(0, 1).unwrap().set_text("Weight (kg)");
1567 tbl.cell(0, 1).unwrap().shading("6B8E23");
1568 tbl.cell(0, 2).unwrap().set_text("Recycled %");
1569 tbl.cell(0, 2).unwrap().shading("6B8E23");
1570 let rows = [
1571 ("Server Hardware", "2,450", "94%"),
1572 ("Networking Equipment", "820", "91%"),
1573 ("Storage Media", "340", "99%"),
1574 ];
1575 for (i, (cat, wt, pct)) in rows.iter().enumerate() {
1576 tbl.cell(i + 1, 0).unwrap().set_text(cat);
1577 tbl.cell(i + 1, 1).unwrap().set_text(wt);
1578 tbl.cell(i + 1, 2).unwrap().set_text(pct);
1579 }
1580 }
1581
1582 // ── Section 4: Initiatives ──
1583 doc.add_paragraph("").page_break_before(true);
1584 doc.add_paragraph("Q1 2026 Initiatives").style("Heading1");
1585
1586 doc.add_paragraph("Planned Programs").style("Heading2");
1587 doc.add_numbered_list_item(
1588 "Deploy on-site battery storage at SF data center (2 MWh capacity)",
1589 0,
1590 );
1591 doc.add_numbered_list_item("Pilot immersion cooling in Dublin facility", 0);
1592 doc.add_numbered_list_item("Launch employee commute carbon offset program", 0);
1593 doc.add_numbered_list_item("Achieve ISO 14001 certification for Singapore facility", 0);
1594
1595 doc.add_paragraph("Investment Targets").style("Heading2");
1596 doc.add_paragraph("Sustainability CapEx allocation for FY2026:")
1597 .keep_with_next(true);
1598 {
1599 let mut tbl = doc
1600 .add_table(5, 2)
1601 .borders(BorderStyle::Single, 4, "2D5016");
1602 tbl.cell(0, 0).unwrap().set_text("Initiative");
1603 tbl.cell(0, 0).unwrap().shading("2D5016");
1604 tbl.cell(0, 1).unwrap().set_text("Budget");
1605 tbl.cell(0, 1).unwrap().shading("2D5016");
1606 let rows = [
1607 ("Battery Storage", "$1.2M"),
1608 ("Immersion Cooling Pilot", "$800K"),
1609 ("Solar Panel Expansion", "$2.1M"),
1610 ("Carbon Credits", "$500K"),
1611 ];
1612 for (i, (init, budget)) in rows.iter().enumerate() {
1613 tbl.cell(i + 1, 0).unwrap().set_text(init);
1614 tbl.cell(i + 1, 1).unwrap().set_text(budget);
1615 if i % 2 == 0 {
1616 tbl.cell(i + 1, 0).unwrap().shading("FFFAF0");
1617 tbl.cell(i + 1, 1).unwrap().shading("FFFAF0");
1618 }
1619 }
1620 }
1621
1622 // Image: sustainability roadmap
1623 let roadmap_img = create_sample_png(500, 100, [40, 80, 30]);
1624 doc.add_paragraph("");
1625 doc.add_picture(
1626 &roadmap_img,
1627 "roadmap.png",
1628 Length::inches(6.0),
1629 Length::inches(1.2),
1630 )
1631 .alignment(Alignment::Center);
1632 {
1633 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1634 p.add_run("Figure 2: 2026 Sustainability Roadmap")
1635 .italic(true)
1636 .size(9.0)
1637 .color("666666");
1638 }
1639
1640 doc.add_paragraph("");
1641 doc.add_paragraph(
1642 "For questions about this report, contact Walter White at sustainability@tensorbee.com.",
1643 )
1644 .shading("FFFAF0")
1645 .border_all(BorderStyle::Single, 2, "2D5016");
1646
1647 doc
1648}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 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
34fn generate_feature_showcase(path: &Path) {
35 let mut doc = Document::new();
36
37 // =========================================================================
38 // PAGE SETUP & METADATA
39 // =========================================================================
40 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
41 doc.set_margins(
42 Length::inches(1.0), // top
43 Length::inches(1.0), // right
44 Length::inches(1.0), // bottom
45 Length::inches(1.0), // left
46 );
47 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
48 doc.set_gutter(Length::twips(0));
49
50 doc.set_title("rdocx Feature Showcase");
51 doc.set_author("rdocx Sample Generator");
52 doc.set_subject("Comprehensive feature demonstration");
53 doc.set_keywords("rdocx, docx, rust, sample");
54
55 // Header & Footer
56 doc.set_header("rdocx Feature Showcase");
57 doc.set_footer("Generated by rdocx — Page");
58
59 // Different first page header
60 doc.set_different_first_page(true);
61 doc.set_first_page_header("rdocx");
62 doc.set_first_page_footer("Feature Showcase — Cover Page");
63
64 // =========================================================================
65 // PAGE 1: COVER PAGE — background image, run formatting
66 // =========================================================================
67 let bg_cover = create_sample_png(612, 792, [30, 60, 120]);
68 doc.add_background_image(&bg_cover, "cover_bg.png");
69
70 doc.add_paragraph(""); // spacer
71 doc.add_paragraph(""); // spacer
72 doc.add_paragraph(""); // spacer
73
74 {
75 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
76 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
77 }
78 {
79 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
80 p.add_run("Feature Showcase")
81 .size(28.0)
82 .color("FFFFFF")
83 .italic(true);
84 }
85
86 doc.add_paragraph(""); // spacer
87
88 {
89 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
90 p.add_run("A comprehensive demonstration of every feature")
91 .size(14.0)
92 .color("CCDDFF");
93 }
94 {
95 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
96 p.add_run("provided by the rdocx Rust crate for DOCX generation.")
97 .size(14.0)
98 .color("CCDDFF");
99 }
100
101 // =========================================================================
102 // PAGE 2: TEXT FORMATTING
103 // =========================================================================
104 doc.add_paragraph("").page_break_before(true);
105
106 doc.add_paragraph("1. Text Formatting").style("Heading1");
107
108 doc.add_paragraph("This section demonstrates paragraph and run-level formatting options.");
109 doc.add_paragraph("");
110
111 // --- Paragraph alignment ---
112 doc.add_paragraph("Paragraph Alignment").style("Heading2");
113
114 doc.add_paragraph("This paragraph is left-aligned (the default).")
115 .alignment(Alignment::Left);
116 doc.add_paragraph("This paragraph is center-aligned.")
117 .alignment(Alignment::Center);
118 doc.add_paragraph("This paragraph is right-aligned.")
119 .alignment(Alignment::Right);
120 doc.add_paragraph(
121 "This paragraph is justified. To demonstrate justified text properly, it needs \
122 to be long enough to span multiple lines so the word spacing adjustment is visible \
123 across the full width of the text area on the page.",
124 )
125 .alignment(Alignment::Justify);
126
127 doc.add_paragraph("");
128
129 // --- Run formatting ---
130 doc.add_paragraph("Run Formatting").style("Heading2");
131
132 {
133 let mut p = doc.add_paragraph("");
134 p.add_run("Bold text").bold(true);
135 p.add_run(" | ");
136 p.add_run("Italic text").italic(true);
137 p.add_run(" | ");
138 p.add_run("Bold + Italic").bold(true).italic(true);
139 }
140 {
141 let mut p = doc.add_paragraph("");
142 p.add_run("Single underline").underline(true);
143 p.add_run(" | ");
144 p.add_run("Strikethrough").strike(true);
145 p.add_run(" | ");
146 p.add_run("Double strikethrough").double_strike(true);
147 }
148 {
149 let mut p = doc.add_paragraph("");
150 p.add_run("Red text").color("FF0000");
151 p.add_run(" | ");
152 p.add_run("Blue text").color("0000FF");
153 p.add_run(" | ");
154 p.add_run("Green text").color("00AA00");
155 p.add_run(" | ");
156 p.add_run("Highlighted").highlight("FFFF00");
157 }
158 {
159 let mut p = doc.add_paragraph("");
160 p.add_run("8pt small").size(8.0);
161 p.add_run(" | ");
162 p.add_run("11pt normal").size(11.0);
163 p.add_run(" | ");
164 p.add_run("16pt large").size(16.0);
165 p.add_run(" | ");
166 p.add_run("24pt extra-large").size(24.0);
167 }
168 {
169 let mut p = doc.add_paragraph("");
170 p.add_run("Arial font").font("Arial");
171 p.add_run(" | ");
172 p.add_run("Times New Roman font").font("Times New Roman");
173 p.add_run(" | ");
174 p.add_run("Courier New font").font("Courier New");
175 }
176 {
177 let mut p = doc.add_paragraph("");
178 p.add_run("Normal");
179 p.add_run(" H").size(11.0);
180 p.add_run("2").subscript();
181 p.add_run("O (subscript)").size(11.0);
182 p.add_run(" | E = mc").size(11.0);
183 p.add_run("2").superscript();
184 p.add_run(" (superscript)").size(11.0);
185 }
186 {
187 let mut p = doc.add_paragraph("");
188 p.add_run("ALL CAPS").all_caps(true);
189 p.add_run(" | ");
190 p.add_run("Small Caps").small_caps(true);
191 p.add_run(" | ");
192 p.add_run("Expanded spacing")
193 .character_spacing(Length::twips(40));
194 }
195
196 doc.add_paragraph("");
197
198 // --- Paragraph formatting ---
199 doc.add_paragraph("Paragraph Formatting").style("Heading2");
200
201 doc.add_paragraph("Paragraph with shading (light green background)")
202 .shading("E2EFDA");
203
204 doc.add_paragraph("Paragraph with bottom border")
205 .border_bottom(BorderStyle::Single, 6, "2E75B6");
206
207 doc.add_paragraph("Paragraph with all borders")
208 .border_all(BorderStyle::Single, 4, "FF0000");
209
210 doc.add_paragraph("Paragraph with 1-inch left indent and hanging indent")
211 .indent_left(Length::inches(1.0))
212 .hanging_indent(Length::inches(0.5));
213
214 doc.add_paragraph("Paragraph with first-line indent of 0.5 inches")
215 .first_line_indent(Length::inches(0.5));
216
217 doc.add_paragraph("Paragraph with extra space before (24pt) and after (12pt)")
218 .space_before(Length::pt(24.0))
219 .space_after(Length::pt(12.0));
220
221 doc.add_paragraph(
222 "Paragraph with double line spacing. This text should have extra vertical \
223 space between lines to demonstrate the line_spacing_multiple setting.",
224 )
225 .line_spacing_multiple(2.0);
226
227 doc.add_paragraph("Paragraph with keep-with-next (won't break from the next paragraph)")
228 .keep_with_next(true);
229 doc.add_paragraph("(This stays with the paragraph above.)");
230
231 // =========================================================================
232 // PAGE 3: LISTS & TAB STOPS
233 // =========================================================================
234 doc.add_paragraph("").page_break_before(true);
235
236 doc.add_paragraph("2. Lists").style("Heading1");
237
238 doc.add_paragraph("Bullet List").style("Heading2");
239
240 doc.add_bullet_list_item("First bullet item", 0);
241 doc.add_bullet_list_item("Second bullet item", 0);
242 doc.add_bullet_list_item("Nested level 1", 1);
243 doc.add_bullet_list_item("Nested level 2", 2);
244 doc.add_bullet_list_item("Back to level 1", 1);
245 doc.add_bullet_list_item("Third bullet item", 0);
246
247 doc.add_paragraph("");
248
249 doc.add_paragraph("Numbered List").style("Heading2");
250
251 doc.add_numbered_list_item("First numbered item", 0);
252 doc.add_numbered_list_item("Second numbered item", 0);
253 doc.add_numbered_list_item("Sub-item A", 1);
254 doc.add_numbered_list_item("Sub-item B", 1);
255 doc.add_numbered_list_item("Third numbered item", 0);
256
257 doc.add_paragraph("");
258
259 // --- Tab stops ---
260 doc.add_paragraph("Tab Stops").style("Heading2");
261
262 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
263 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
264 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
265 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
266 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
267
268 doc.add_paragraph("Item\t........\tPrice")
269 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
270 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
271 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
272
273 doc.add_paragraph("Widget A\t........\t$19.99")
274 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
275 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
276 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
277
278 doc.add_paragraph("Gadget B\t________\t$249.50")
279 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
280 .add_tab_stop_with_leader(
281 TabAlignment::Right,
282 Length::inches(4.0),
283 TabLeader::Underscore,
284 )
285 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
286
287 // =========================================================================
288 // PAGE 4: TABLES
289 // =========================================================================
290 doc.add_paragraph("").page_break_before(true);
291
292 doc.add_paragraph("3. Tables").style("Heading1");
293
294 // --- Basic table with borders ---
295 doc.add_paragraph("Basic Table with Borders")
296 .style("Heading2");
297
298 {
299 let mut tbl = doc.add_table(4, 3);
300 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
301
302 // Header row
303 for col in 0..3 {
304 tbl.cell(0, col).unwrap().shading("2E75B6");
305 }
306 tbl.cell(0, 0).unwrap().set_text("Name");
307 tbl.cell(0, 1).unwrap().set_text("Role");
308 tbl.cell(0, 2).unwrap().set_text("Location");
309
310 tbl.cell(1, 0).unwrap().set_text("Alice Johnson");
311 tbl.cell(1, 1).unwrap().set_text("Engineering Lead");
312 tbl.cell(1, 2).unwrap().set_text("New York");
313
314 tbl.cell(2, 0).unwrap().set_text("Bob Smith");
315 tbl.cell(2, 1).unwrap().set_text("Product Manager");
316 tbl.cell(2, 2).unwrap().set_text("San Francisco");
317
318 tbl.cell(3, 0).unwrap().set_text("Carol Davis");
319 tbl.cell(3, 1).unwrap().set_text("Designer");
320 tbl.cell(3, 2).unwrap().set_text("London");
321 }
322
323 doc.add_paragraph("");
324
325 // --- Table with cell merging ---
326 doc.add_paragraph("Table with Cell Merging & Vertical Alignment")
327 .style("Heading2");
328
329 {
330 let mut tbl = doc.add_table(4, 4);
331 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
332 tbl = tbl.width_pct(100.0);
333
334 // Header spanning all columns
335 tbl.cell(0, 0).unwrap().set_text("Quarterly Revenue Report");
336 tbl.cell(0, 0).unwrap().shading("1F4E79");
337 tbl.cell(0, 0).unwrap().grid_span(4);
338
339 // Sub-header
340 tbl.cell(1, 0).unwrap().set_text("Region");
341 tbl.cell(1, 0).unwrap().shading("D6E4F0");
342 tbl.cell(1, 1).unwrap().set_text("Q1");
343 tbl.cell(1, 1).unwrap().shading("D6E4F0");
344 tbl.cell(1, 2).unwrap().set_text("Q2");
345 tbl.cell(1, 2).unwrap().shading("D6E4F0");
346 tbl.cell(1, 3).unwrap().set_text("Total");
347 tbl.cell(1, 3).unwrap().shading("D6E4F0");
348
349 // Data
350 tbl.cell(2, 0).unwrap().set_text("North America");
351 tbl.cell(2, 1).unwrap().set_text("$2.4M");
352 tbl.cell(2, 2).unwrap().set_text("$2.7M");
353 tbl.cell(2, 3).unwrap().set_text("$5.1M");
354
355 tbl.cell(3, 0).unwrap().set_text("Europe");
356 tbl.cell(3, 1).unwrap().set_text("$1.8M");
357 tbl.cell(3, 2).unwrap().set_text("$2.0M");
358 tbl.cell(3, 3).unwrap().set_text("$3.8M");
359
360 // Vertical alignment on data cells
361 tbl.cell(2, 3)
362 .unwrap()
363 .vertical_alignment(VerticalAlignment::Center);
364 tbl.cell(3, 3)
365 .unwrap()
366 .vertical_alignment(VerticalAlignment::Bottom);
367 }
368
369 doc.add_paragraph("");
370
371 // --- Table with vertical merge ---
372 doc.add_paragraph("Table with Vertical Merge")
373 .style("Heading2");
374
375 {
376 let mut tbl = doc.add_table(4, 3);
377 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
378
379 tbl.cell(0, 0).unwrap().set_text("Category");
380 tbl.cell(0, 0).unwrap().shading("E2EFDA");
381 tbl.cell(0, 1).unwrap().set_text("Item");
382 tbl.cell(0, 1).unwrap().shading("E2EFDA");
383 tbl.cell(0, 2).unwrap().set_text("Price");
384 tbl.cell(0, 2).unwrap().shading("E2EFDA");
385
386 // "Hardware" spans rows 1-2
387 tbl.cell(1, 0).unwrap().set_text("Hardware");
388 tbl.cell(1, 0).unwrap().v_merge_restart();
389 tbl.cell(1, 1).unwrap().set_text("Laptop");
390 tbl.cell(1, 2).unwrap().set_text("$1,200");
391
392 tbl.cell(2, 0).unwrap().v_merge_continue();
393 tbl.cell(2, 1).unwrap().set_text("Monitor");
394 tbl.cell(2, 2).unwrap().set_text("$450");
395
396 // "Software" on row 3
397 tbl.cell(3, 0).unwrap().set_text("Software");
398 tbl.cell(3, 1).unwrap().set_text("IDE License");
399 tbl.cell(3, 2).unwrap().set_text("$200/yr");
400 }
401
402 doc.add_paragraph("");
403
404 // --- Nested table ---
405 doc.add_paragraph("Nested Table").style("Heading2");
406
407 {
408 let mut tbl = doc.add_table(2, 2);
409 tbl = tbl.borders(BorderStyle::Single, 6, "2E75B6");
410
411 tbl.cell(0, 0).unwrap().set_text("Outer Cell (0,0)");
412 tbl.cell(0, 1).unwrap().set_text("Outer Cell (0,1)");
413 tbl.cell(1, 0).unwrap().set_text("Outer Cell (1,0)");
414
415 // Nested table inside cell (1,1)
416 {
417 let mut cell = tbl.cell(1, 1).unwrap();
418 cell.set_text("Contains nested table:");
419 let mut nested = cell.add_table(2, 2);
420 nested = nested.borders(BorderStyle::Single, 2, "FF6600");
421 nested.cell(0, 0).unwrap().set_text("Inner A");
422 nested.cell(0, 1).unwrap().set_text("Inner B");
423 nested.cell(1, 0).unwrap().set_text("Inner C");
424 nested.cell(1, 1).unwrap().set_text("Inner D");
425 }
426 }
427
428 // =========================================================================
429 // PAGE 5: IMAGES
430 // =========================================================================
431 doc.add_paragraph("").page_break_before(true);
432
433 doc.add_paragraph("4. Images").style("Heading1");
434
435 doc.add_paragraph("Inline Image").style("Heading2");
436
437 doc.add_paragraph("Below is an inline image (200x50 pixels, blue gradient):");
438 let inline_img = create_sample_png(200, 50, [0, 80, 200]);
439 doc.add_picture(
440 &inline_img,
441 "inline_chart.png",
442 Length::inches(3.0),
443 Length::inches(0.75),
444 );
445
446 doc.add_paragraph("");
447
448 doc.add_paragraph("Header Image").style("Heading2");
449
450 // Replace the text-only header with an image header
451 let header_img = create_sample_png(400, 40, [40, 40, 40]);
452 doc.set_header_image(
453 &header_img,
454 "header_logo.png",
455 Length::inches(2.0),
456 Length::inches(0.2),
457 );
458
459 doc.add_paragraph(
460 "The document header has been replaced with an inline image. \
461 Check the header area at the top of this page.",
462 );
463
464 doc.add_paragraph("");
465 doc.add_paragraph(
466 "Note: The cover page uses a full-page background image behind the text, \
467 demonstrated on page 1 via add_background_image().",
468 );
469
470 // =========================================================================
471 // PAGE 6: CONTENT MANIPULATION — placeholder replacement, insertion
472 // =========================================================================
473 doc.add_paragraph("").page_break_before(true);
474
475 doc.add_paragraph("5. Content Manipulation")
476 .style("Heading1");
477
478 // --- Placeholder replacement ---
479 doc.add_paragraph("Placeholder Replacement")
480 .style("Heading2");
481
482 doc.add_paragraph(
483 "Before replacement, this document contained {{customer}} and {{date}} placeholders.",
484 );
485
486 {
487 let mut p = doc.add_paragraph("");
488 p.add_run("Customer: ").bold(true);
489 p.add_run("{{customer}}");
490 }
491 {
492 let mut p = doc.add_paragraph("");
493 p.add_run("Date: ").bold(true);
494 p.add_run("{{date}}");
495 }
496 doc.add_paragraph("Reference: {{ref_number}}");
497
498 // Table with placeholders
499 {
500 let mut tbl = doc.add_table(3, 2);
501 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
502 tbl.cell(0, 0).unwrap().set_text("Field");
503 tbl.cell(0, 0).unwrap().shading("D6E4F0");
504 tbl.cell(0, 1).unwrap().set_text("Value");
505 tbl.cell(0, 1).unwrap().shading("D6E4F0");
506 tbl.cell(1, 0).unwrap().set_text("Project");
507 tbl.cell(1, 1).unwrap().set_text("{{project}}");
508 tbl.cell(2, 0).unwrap().set_text("Status");
509 tbl.cell(2, 1).unwrap().set_text("{{status}}");
510 }
511
512 // Perform replacements
513 let mut replacements = HashMap::new();
514 replacements.insert("{{customer}}", "Acme Corporation");
515 replacements.insert("{{date}}", "February 22, 2026");
516 replacements.insert("{{ref_number}}", "REF-2026-001");
517 replacements.insert("{{project}}", "Infrastructure Upgrade");
518 replacements.insert("{{status}}", "In Progress");
519 let replace_count = doc.replace_all(&replacements);
520
521 doc.add_paragraph("");
522 doc.add_paragraph(&format!(
523 "(Replaced {} placeholders above — in body text and table cells)",
524 replace_count
525 ));
526
527 doc.add_paragraph("");
528
529 // --- Content insertion ---
530 doc.add_paragraph("Content Insertion").style("Heading2");
531
532 doc.add_paragraph("Section A: First section of content.");
533 doc.add_paragraph("Section C: Third section of content.");
534
535 // Insert "Section B" between A and C
536 if let Some(idx) = doc.find_content_index("Section C") {
537 doc.insert_paragraph(
538 idx,
539 "Section B: Inserted between A and C using find_content_index().",
540 );
541 }
542
543 doc.add_paragraph("");
544 doc.add_paragraph(
545 "The paragraph above ('Section B') was inserted at a specific position \
546 using find_content_index() + insert_paragraph().",
547 );
548
549 // =========================================================================
550 // PAGE 7: LANDSCAPE — section break, wide table
551 // =========================================================================
552 doc.add_paragraph("").section_break(SectionBreak::NextPage);
553
554 doc.add_paragraph("6. Mixed Page Orientation")
555 .style("Heading1");
556
557 doc.add_paragraph(
558 "This page is in LANDSCAPE orientation. It was created using a section break \
559 followed by section_landscape(). This is useful for wide tables or charts.",
560 );
561
562 doc.add_paragraph("");
563
564 // Wide table for landscape
565 {
566 let mut tbl = doc.add_table(4, 7);
567 tbl = tbl.borders(BorderStyle::Single, 4, "2E75B6");
568
569 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
570 for (col, h) in headers.iter().enumerate() {
571 tbl.cell(0, col).unwrap().set_text(h);
572 tbl.cell(0, col).unwrap().shading("2E75B6");
573 }
574
575 let data = [
576 [
577 "North America",
578 "$1.2M",
579 "$1.3M",
580 "$1.4M",
581 "$1.5M",
582 "$1.6M",
583 "$7.0M",
584 ],
585 [
586 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
587 ],
588 [
589 "Asia Pacific",
590 "$0.5M",
591 "$0.6M",
592 "$0.7M",
593 "$0.7M",
594 "$0.8M",
595 "$3.3M",
596 ],
597 ];
598 for (row_idx, row_data) in data.iter().enumerate() {
599 for (col, val) in row_data.iter().enumerate() {
600 tbl.cell(row_idx + 1, col).unwrap().set_text(val);
601 }
602 }
603 }
604
605 // End landscape, return to portrait
606 doc.add_paragraph("")
607 .section_break(SectionBreak::NextPage)
608 .section_landscape();
609
610 // =========================================================================
611 // PAGE 8: BACK TO PORTRAIT — styles, final notes
612 // =========================================================================
613 doc.add_paragraph("7. Custom Styles & Summary")
614 .style("Heading1");
615
616 doc.add_paragraph(
617 "This final page is back in portrait orientation after a section break. \
618 The document has demonstrated:",
619 );
620
621 doc.add_bullet_list_item(
622 "Page setup: size, margins, header/footer distance, gutter",
623 0,
624 );
625 doc.add_bullet_list_item("Document metadata: title, author, subject, keywords", 0);
626 doc.add_bullet_list_item("Headers and footers: text, images, different first page", 0);
627 doc.add_bullet_list_item("Background images: full-page behind text", 0);
628 doc.add_bullet_list_item(
629 "Text formatting: bold, italic, underline, strike, color, size, font",
630 0,
631 );
632 doc.add_bullet_list_item(
633 "Advanced run formatting: superscript, subscript, caps, spacing",
634 0,
635 );
636 doc.add_bullet_list_item(
637 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
638 0,
639 );
640 doc.add_bullet_list_item("Bullet and numbered lists with nesting levels", 0);
641 doc.add_bullet_list_item("Tab stops with dot/underscore leaders", 0);
642 doc.add_bullet_list_item(
643 "Tables: borders, shading, column spans, row spans, nesting",
644 0,
645 );
646 doc.add_bullet_list_item("Vertical alignment in table cells", 0);
647 doc.add_bullet_list_item("Inline images", 0);
648 doc.add_bullet_list_item("Placeholder replacement in body and table cells", 0);
649 doc.add_bullet_list_item("Content insertion at specific positions", 0);
650 doc.add_bullet_list_item(
651 "Section breaks with mixed portrait/landscape orientation",
652 0,
653 );
654
655 doc.add_paragraph("");
656 doc.add_paragraph("All features above were built entirely from scratch using the rdocx API.")
657 .alignment(Alignment::Center)
658 .shading("E2EFDA")
659 .border_all(BorderStyle::Single, 2, "00AA00");
660
661 doc.save(path).unwrap();
662}86fn generate_feature_showcase(_samples_dir: &Path) -> Document {
87 let mut doc = Document::new();
88
89 // ── Page Setup & Metadata ──
90 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
91 doc.set_margins(
92 Length::inches(1.0),
93 Length::inches(1.0),
94 Length::inches(1.0),
95 Length::inches(1.0),
96 );
97 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
98 doc.set_gutter(Length::twips(0));
99 doc.set_title("rdocx Feature Showcase");
100 doc.set_author("rdocx Sample Generator");
101 doc.set_subject("Comprehensive feature demonstration");
102 doc.set_keywords("rdocx, docx, rust, sample, showcase");
103
104 // Headers & Footers with different first page
105 doc.set_different_first_page(true);
106 doc.set_first_page_header("rdocx");
107 doc.set_first_page_footer("Feature Showcase — Cover Page");
108 doc.set_header("rdocx Feature Showcase");
109 doc.set_footer("Generated by rdocx");
110
111 // ── COVER PAGE ──
112 let bg = create_sample_png(612, 792, [20, 45, 90]);
113 doc.add_background_image(&bg, "cover_bg.png");
114
115 for _ in 0..3 {
116 doc.add_paragraph("");
117 }
118 {
119 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
120 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
121 }
122 {
123 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
124 p.add_run("Complete Feature Showcase")
125 .size(28.0)
126 .color("FFFFFF")
127 .italic(true);
128 }
129 doc.add_paragraph("");
130 {
131 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
132 p.add_run("Every feature of the rdocx Rust library")
133 .size(13.0)
134 .color("B0C4DE");
135 }
136 {
137 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
138 p.add_run("demonstrated in a single document.")
139 .size(13.0)
140 .color("B0C4DE");
141 }
142
143 // ── TABLE OF CONTENTS ──
144 doc.add_paragraph("").page_break_before(true);
145 doc.insert_toc(doc.content_count(), 3);
146
147 // ── SECTION 1: TEXT FORMATTING ──
148 doc.add_paragraph("").page_break_before(true);
149 doc.add_paragraph("1. Text Formatting").style("Heading1");
150
151 // Paragraph Alignment
152 doc.add_paragraph("1.1 Paragraph Alignment")
153 .style("Heading2");
154 doc.add_paragraph("Left-aligned paragraph (default).")
155 .alignment(Alignment::Left);
156 doc.add_paragraph("Center-aligned paragraph.")
157 .alignment(Alignment::Center);
158 doc.add_paragraph("Right-aligned paragraph.")
159 .alignment(Alignment::Right);
160 doc.add_paragraph(
161 "Justified paragraph — this text is long enough to demonstrate how justified alignment \
162 distributes extra space across word gaps so lines fill the full width of the text area.",
163 )
164 .alignment(Alignment::Justify);
165
166 doc.add_paragraph("");
167
168 // Run Formatting
169 doc.add_paragraph("1.2 Run Formatting").style("Heading2");
170 {
171 let mut p = doc.add_paragraph("");
172 p.add_run("Bold").bold(true);
173 p.add_run(" | ");
174 p.add_run("Italic").italic(true);
175 p.add_run(" | ");
176 p.add_run("Bold Italic").bold(true).italic(true);
177 p.add_run(" | ");
178 p.add_run("Underline").underline(true);
179 p.add_run(" | ");
180 p.add_run("Strikethrough").strike(true);
181 p.add_run(" | ");
182 p.add_run("Double Strike").double_strike(true);
183 }
184 {
185 let mut p = doc.add_paragraph("");
186 p.add_run("Red").color("FF0000");
187 p.add_run(" | ");
188 p.add_run("Blue").color("0000FF");
189 p.add_run(" | ");
190 p.add_run("Green").color("00AA00");
191 p.add_run(" | ");
192 p.add_run("Highlighted").highlight("FFFF00");
193 }
194 {
195 let mut p = doc.add_paragraph("");
196 p.add_run("8pt").size(8.0);
197 p.add_run(" | ");
198 p.add_run("11pt").size(11.0);
199 p.add_run(" | ");
200 p.add_run("16pt").size(16.0);
201 p.add_run(" | ");
202 p.add_run("24pt").size(24.0);
203 }
204 {
205 let mut p = doc.add_paragraph("");
206 p.add_run("Arial").font("Arial");
207 p.add_run(" | ");
208 p.add_run("Times New Roman").font("Times New Roman");
209 p.add_run(" | ");
210 p.add_run("Courier New").font("Courier New");
211 }
212 {
213 let mut p = doc.add_paragraph("");
214 p.add_run("H");
215 p.add_run("2").subscript();
216 p.add_run("O (subscript) | E=mc");
217 p.add_run("2").superscript();
218 p.add_run(" (superscript)");
219 }
220 {
221 let mut p = doc.add_paragraph("");
222 p.add_run("ALL CAPS").all_caps(true);
223 p.add_run(" | ");
224 p.add_run("Small Caps").small_caps(true);
225 p.add_run(" | ");
226 p.add_run("Expanded").character_spacing(Length::twips(40));
227 p.add_run(" | ");
228 p.add_run("Hidden text (not visible)").hidden(true);
229 }
230
231 doc.add_paragraph("");
232
233 // Underline Styles
234 doc.add_paragraph("1.3 Underline Styles").style("Heading2");
235 {
236 let mut p = doc.add_paragraph("");
237 p.add_run("Single ")
238 .underline_style(rdocx::UnderlineStyle::Single);
239 p.add_run("Double ")
240 .underline_style(rdocx::UnderlineStyle::Double);
241 p.add_run("Thick ")
242 .underline_style(rdocx::UnderlineStyle::Thick);
243 p.add_run("Dotted ")
244 .underline_style(rdocx::UnderlineStyle::Dotted);
245 p.add_run("Dash ")
246 .underline_style(rdocx::UnderlineStyle::Dash);
247 p.add_run("Wave ")
248 .underline_style(rdocx::UnderlineStyle::Wave);
249 }
250
251 // ── SECTION 2: PARAGRAPH FORMATTING ──
252 doc.add_paragraph("").page_break_before(true);
253 doc.add_paragraph("2. Paragraph Formatting")
254 .style("Heading1");
255
256 doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
257 doc.add_paragraph("Paragraph with light green shading")
258 .shading("E2EFDA");
259 doc.add_paragraph("Paragraph with bottom border")
260 .border_bottom(BorderStyle::Single, 6, "2E75B6");
261 doc.add_paragraph("Paragraph with all borders (red)")
262 .border_all(BorderStyle::Single, 4, "FF0000");
263
264 doc.add_paragraph("2.2 Indentation").style("Heading2");
265 doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
266 .indent_left(Length::inches(1.0))
267 .hanging_indent(Length::inches(0.5));
268 doc.add_paragraph("0.5-inch first-line indent on this paragraph")
269 .first_line_indent(Length::inches(0.5));
270 doc.add_paragraph("Both left and right indent (0.75 inches each)")
271 .indent_left(Length::inches(0.75))
272 .indent_right(Length::inches(0.75));
273
274 doc.add_paragraph("2.3 Spacing & Line Height")
275 .style("Heading2");
276 doc.add_paragraph("Extra space before (24pt) and after (12pt)")
277 .space_before(Length::pt(24.0))
278 .space_after(Length::pt(12.0));
279 doc.add_paragraph(
280 "Double line spacing paragraph. This text should have extra vertical space between \
281 lines to demonstrate line_spacing_multiple(2.0).",
282 )
283 .line_spacing_multiple(2.0);
284 doc.add_paragraph("Exact 20pt line spacing (fixed height).")
285 .line_spacing(20.0);
286
287 doc.add_paragraph("2.4 Pagination Controls")
288 .style("Heading2");
289 doc.add_paragraph("keep_with_next — this paragraph stays with the next")
290 .keep_with_next(true);
291 doc.add_paragraph("(This paragraph was kept with the one above.)");
292 doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
293 .keep_together(true);
294 doc.add_paragraph("widow_control — prevents widow/orphan lines")
295 .widow_control(true);
296
297 // ── SECTION 3: LISTS ──
298 doc.add_paragraph("").page_break_before(true);
299 doc.add_paragraph("3. Lists").style("Heading1");
300
301 doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
302 doc.add_bullet_list_item("First item", 0);
303 doc.add_bullet_list_item("Second item", 0);
304 doc.add_bullet_list_item("Nested level 1", 1);
305 doc.add_bullet_list_item("Nested level 2", 2);
306 doc.add_bullet_list_item("Back to level 1", 1);
307 doc.add_bullet_list_item("Third item", 0);
308
309 doc.add_paragraph("");
310
311 doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
312 doc.add_numbered_list_item("First numbered item", 0);
313 doc.add_numbered_list_item("Second numbered item", 0);
314 doc.add_numbered_list_item("Sub-item A", 1);
315 doc.add_numbered_list_item("Sub-item B", 1);
316 doc.add_numbered_list_item("Third numbered item", 0);
317
318 // ── SECTION 4: TAB STOPS ──
319 doc.add_paragraph("");
320 doc.add_paragraph("4. Tab Stops").style("Heading1");
321
322 doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
323 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
324 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
325 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
326 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
327 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
328
329 doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
330 doc.add_paragraph("Item\t\tPrice")
331 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
332 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
333 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
334 doc.add_paragraph("Widget\t\t$19.99")
335 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
336 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
337 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
338 doc.add_paragraph("Gadget\t\t$249.50")
339 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
340 .add_tab_stop_with_leader(
341 TabAlignment::Right,
342 Length::inches(4.0),
343 TabLeader::Underscore,
344 )
345 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
346
347 // ── SECTION 5: TABLES ──
348 doc.add_paragraph("").page_break_before(true);
349 doc.add_paragraph("5. Tables").style("Heading1");
350
351 doc.add_paragraph("5.1 Basic Table").style("Heading2");
352 {
353 let mut tbl = doc
354 .add_table(4, 3)
355 .borders(BorderStyle::Single, 4, "000000");
356 for col in 0..3 {
357 tbl.cell(0, col).unwrap().shading("2E75B6");
358 }
359 tbl.cell(0, 0).unwrap().set_text("Name");
360 tbl.cell(0, 1).unwrap().set_text("Role");
361 tbl.cell(0, 2).unwrap().set_text("Location");
362 tbl.cell(1, 0).unwrap().set_text("Walter White");
363 tbl.cell(1, 1).unwrap().set_text("CEO");
364 tbl.cell(1, 2).unwrap().set_text("Albuquerque");
365 tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
366 tbl.cell(2, 1).unwrap().set_text("CTO");
367 tbl.cell(2, 2).unwrap().set_text("Remote");
368 tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
369 tbl.cell(3, 1).unwrap().set_text("Security");
370 tbl.cell(3, 2).unwrap().set_text("Washington");
371 }
372
373 doc.add_paragraph("");
374
375 doc.add_paragraph("5.2 Column Span & Cell Shading")
376 .style("Heading2");
377 {
378 let mut tbl = doc
379 .add_table(3, 4)
380 .borders(BorderStyle::Single, 4, "000000")
381 .width_pct(100.0);
382 tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
383 tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
384 tbl.cell(1, 0).unwrap().set_text("Region");
385 tbl.cell(1, 0).unwrap().shading("D6E4F0");
386 tbl.cell(1, 1).unwrap().set_text("Q1");
387 tbl.cell(1, 1).unwrap().shading("D6E4F0");
388 tbl.cell(1, 2).unwrap().set_text("Q2");
389 tbl.cell(1, 2).unwrap().shading("D6E4F0");
390 tbl.cell(1, 3).unwrap().set_text("Total");
391 tbl.cell(1, 3).unwrap().shading("D6E4F0");
392 tbl.cell(2, 0).unwrap().set_text("Americas");
393 tbl.cell(2, 1).unwrap().set_text("$2.4M");
394 tbl.cell(2, 2).unwrap().set_text("$2.7M");
395 tbl.cell(2, 3).unwrap().set_text("$5.1M");
396 }
397
398 doc.add_paragraph("");
399
400 doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
401 {
402 let mut tbl = doc
403 .add_table(4, 3)
404 .borders(BorderStyle::Single, 4, "000000");
405 tbl.cell(0, 0).unwrap().set_text("Category");
406 tbl.cell(0, 0).unwrap().shading("E2EFDA");
407 tbl.cell(0, 1).unwrap().set_text("Item");
408 tbl.cell(0, 1).unwrap().shading("E2EFDA");
409 tbl.cell(0, 2).unwrap().set_text("Price");
410 tbl.cell(0, 2).unwrap().shading("E2EFDA");
411 tbl.cell(1, 0).unwrap().set_text("Hardware");
412 tbl.cell(1, 0).unwrap().v_merge_restart();
413 tbl.cell(1, 1).unwrap().set_text("Laptop");
414 tbl.cell(1, 2).unwrap().set_text("$1,200");
415 tbl.cell(2, 0).unwrap().v_merge_continue();
416 tbl.cell(2, 1).unwrap().set_text("Monitor");
417 tbl.cell(2, 2).unwrap().set_text("$450");
418 tbl.cell(3, 0).unwrap().set_text("Software");
419 tbl.cell(3, 1).unwrap().set_text("IDE License");
420 tbl.cell(3, 2).unwrap().set_text("$200/yr");
421 }
422
423 doc.add_paragraph("");
424
425 doc.add_paragraph("5.4 Nested Table").style("Heading2");
426 {
427 let mut tbl = doc
428 .add_table(2, 2)
429 .borders(BorderStyle::Single, 6, "2E75B6");
430 tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
431 tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
432 tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
433 {
434 let mut cell = tbl.cell(1, 1).unwrap();
435 cell.set_text("Nested table below:");
436 let mut nested = cell
437 .add_table(2, 2)
438 .borders(BorderStyle::Single, 2, "FF6600");
439 nested.cell(0, 0).unwrap().set_text("A");
440 nested.cell(0, 1).unwrap().set_text("B");
441 nested.cell(1, 0).unwrap().set_text("C");
442 nested.cell(1, 1).unwrap().set_text("D");
443 }
444 }
445
446 doc.add_paragraph("");
447
448 doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
449 .style("Heading2");
450 {
451 let mut tbl = doc
452 .add_table(2, 3)
453 .borders(BorderStyle::Single, 4, "666666");
454 tbl.row(0).unwrap().height(Length::pt(50.0)).header();
455 tbl.cell(0, 0).unwrap().set_text("Top");
456 tbl.cell(0, 0)
457 .unwrap()
458 .vertical_alignment(VerticalAlignment::Top)
459 .shading("FFF2CC");
460 tbl.cell(0, 1).unwrap().set_text("Center");
461 tbl.cell(0, 1)
462 .unwrap()
463 .vertical_alignment(VerticalAlignment::Center)
464 .shading("D9E2F3");
465 tbl.cell(0, 2).unwrap().set_text("Bottom");
466 tbl.cell(0, 2)
467 .unwrap()
468 .vertical_alignment(VerticalAlignment::Bottom)
469 .shading("E2EFDA");
470 tbl.row(1).unwrap().cant_split();
471 tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
472 tbl.cell(1, 0).unwrap().no_wrap();
473 tbl.cell(1, 1).unwrap().set_text("Fixed width");
474 tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
475 tbl.cell(1, 2).unwrap().set_text("Normal");
476 }
477
478 // ── SECTION 6: IMAGES ──
479 doc.add_paragraph("").page_break_before(true);
480 doc.add_paragraph("6. Images").style("Heading1");
481
482 doc.add_paragraph("6.1 Inline Image").style("Heading2");
483 doc.add_paragraph("A blue gradient image below:");
484 let img = create_sample_png(200, 50, [0, 80, 200]);
485 doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
486
487 doc.add_paragraph("");
488 doc.add_paragraph("6.2 Header Image").style("Heading2");
489 let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
490 doc.set_header_image(
491 &hdr_img,
492 "header_logo.png",
493 Length::inches(2.0),
494 Length::inches(0.2),
495 );
496 doc.add_paragraph("The header now contains an inline image (check the top of this page).");
497
498 doc.add_paragraph("");
499 doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
500
501 // ── SECTION 7: CONTENT MANIPULATION ──
502 doc.add_paragraph("").page_break_before(true);
503 doc.add_paragraph("7. Content Manipulation")
504 .style("Heading1");
505
506 doc.add_paragraph("7.1 Placeholder Replacement")
507 .style("Heading2");
508 doc.add_paragraph("Customer: {{customer}}");
509 doc.add_paragraph("Date: {{date}}");
510 doc.add_paragraph("Reference: {{ref_number}}");
511 {
512 let mut tbl = doc
513 .add_table(2, 2)
514 .borders(BorderStyle::Single, 4, "000000");
515 tbl.cell(0, 0).unwrap().set_text("Project");
516 tbl.cell(0, 0).unwrap().shading("D6E4F0");
517 tbl.cell(0, 1).unwrap().set_text("{{project}}");
518 tbl.cell(1, 0).unwrap().set_text("Status");
519 tbl.cell(1, 0).unwrap().shading("D6E4F0");
520 tbl.cell(1, 1).unwrap().set_text("{{status}}");
521 }
522
523 let mut replacements = HashMap::new();
524 replacements.insert("{{customer}}", "Tensorbee Inc.");
525 replacements.insert("{{date}}", "February 22, 2026");
526 replacements.insert("{{ref_number}}", "TB-2026-001");
527 replacements.insert("{{project}}", "Infrastructure Upgrade");
528 replacements.insert("{{status}}", "In Progress");
529 let n = doc.replace_all(&replacements);
530 doc.add_paragraph(&format!("({n} placeholders replaced above)"));
531
532 doc.add_paragraph("");
533
534 doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
535 doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
536 let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
537 doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
538
539 doc.add_paragraph("");
540
541 doc.add_paragraph("7.3 Content Insertion").style("Heading2");
542 doc.add_paragraph("Section A: First content.");
543 doc.add_paragraph("Section C: Third content.");
544 if let Some(idx) = doc.find_content_index("Section C") {
545 doc.insert_paragraph(
546 idx,
547 "Section B: Inserted between A and C via find_content_index().",
548 );
549 }
550
551 // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
552 doc.add_paragraph("").section_break(SectionBreak::NextPage);
553 doc.add_paragraph("8. Section Breaks & Orientation")
554 .style("Heading1");
555 doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
556
557 {
558 let mut tbl = doc
559 .add_table(3, 7)
560 .borders(BorderStyle::Single, 4, "2E75B6");
561 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
562 for (c, h) in headers.iter().enumerate() {
563 tbl.cell(0, c).unwrap().set_text(h);
564 tbl.cell(0, c).unwrap().shading("2E75B6");
565 }
566 let data = [
567 [
568 "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
569 ],
570 [
571 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
572 ],
573 ];
574 for (r, row) in data.iter().enumerate() {
575 for (c, v) in row.iter().enumerate() {
576 tbl.cell(r + 1, c).unwrap().set_text(v);
577 }
578 }
579 }
580
581 doc.add_paragraph("")
582 .section_break(SectionBreak::NextPage)
583 .section_landscape();
584
585 // ── SECTION 9: CUSTOM STYLES ──
586 doc.add_paragraph("9. Custom Styles").style("Heading1");
587 doc.add_style(
588 StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
589 .based_on("Normal")
590 .paragraph_properties({
591 let mut ppr = rdocx_oxml::properties::CT_PPr::default();
592 ppr.shading = Some(rdocx_oxml::properties::CT_Shd {
593 val: "clear".to_string(),
594 color: None,
595 fill: Some("FFF2CC".to_string()),
596 });
597 ppr
598 })
599 .run_properties({
600 let mut rpr = rdocx_oxml::properties::CT_RPr::default();
601 rpr.bold = Some(true);
602 rpr.color = Some("C45911".to_string());
603 rpr
604 }),
605 );
606 doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
607 .style("CustomHighlight");
608
609 doc.add_paragraph("");
610
611 // ── SECTION 10: DOCUMENT INTELLIGENCE ──
612 doc.add_paragraph("10. Document Intelligence API")
613 .style("Heading1");
614
615 let wc = doc.word_count();
616 let hc = doc.headings().len();
617 let ic = doc.images().len();
618 let lc = doc.links().len();
619 doc.add_paragraph(&format!("Word count: {wc}"));
620 doc.add_paragraph(&format!("Heading count: {hc}"));
621 doc.add_paragraph(&format!("Image count: {ic}"));
622 doc.add_paragraph(&format!("Link count: {lc}"));
623
624 let outline = doc.document_outline();
625 doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
626
627 let issues = doc.audit_accessibility();
628 doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
629 for issue in &issues {
630 doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
631 }
632
633 // ── SECTION 11: DOCUMENT MERGING ──
634 doc.add_paragraph("");
635 doc.add_paragraph("11. Document Merging").style("Heading1");
636 {
637 let mut other = Document::new();
638 other.add_paragraph("This paragraph was merged from another document using append().");
639 doc.append(&other);
640 }
641
642 doc.add_paragraph("");
643
644 // ── SUMMARY ──
645 doc.add_paragraph("Summary of Demonstrated Features")
646 .style("Heading1");
647 let features = [
648 "Page setup: size, margins, header/footer distance, gutter",
649 "Document metadata: title, author, subject, keywords",
650 "Headers and footers: text, images, different first page",
651 "Background images (full-page behind text)",
652 "Table of Contents generation with bookmarks",
653 "Text formatting: bold, italic, underline styles, strike, color, size, font",
654 "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
655 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
656 "Pagination controls: keep-with-next, keep-together, widow control",
657 "Bullet and numbered lists with nesting",
658 "Tab stops with dot/underscore/hyphen leaders",
659 "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
660 "Row properties: header rows, exact height, cant-split",
661 "Cell properties: width, no-wrap, vertical alignment",
662 "Inline images and header images",
663 "Placeholder replacement (single and batch)",
664 "Regex-based find and replace",
665 "Content insertion at specific positions",
666 "Section breaks with mixed portrait/landscape",
667 "Custom paragraph and character styles",
668 "Document intelligence: word count, headings, outline, images, links",
669 "Accessibility audit",
670 "Document merging (append)",
671 "Export: DOCX, PDF, HTML, Markdown",
672 ];
673 for f in &features {
674 doc.add_bullet_list_item(f, 0);
675 }
676 doc.add_paragraph("");
677 doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
678 .alignment(Alignment::Center)
679 .shading("E2EFDA")
680 .border_all(BorderStyle::Single, 2, "00AA00");
681
682 doc
683}
684
685// =============================================================================
686// 2. PROPOSAL DOCUMENT — Deep navy + gold scheme
687// =============================================================================
688fn generate_proposal(_samples_dir: &Path) -> Document {
689 let mut doc = Document::new();
690
691 // Colors: Navy #1B2A4A, Gold #C5922E, Light #F4F1EB
692 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
693 doc.set_margins(
694 Length::inches(1.0),
695 Length::inches(1.0),
696 Length::inches(1.0),
697 Length::inches(1.0),
698 );
699 doc.set_title("AI Platform Modernization Proposal");
700 doc.set_author("Walter White");
701 doc.set_subject("Technology Proposal");
702 doc.set_keywords("proposal, AI, modernization, Tensorbee");
703
704 // Banner header
705 let logo_img = create_logo_png(220, 48);
706 let banner = build_header_banner_xml(
707 "rId1",
708 &BannerOpts {
709 bg_color: "1B2A4A",
710 banner_width: 7772400,
711 banner_height: 914400,
712 logo_width: 2011680,
713 logo_height: 438912,
714 logo_x_offset: 295125,
715 logo_y_offset: 237744,
716 },
717 );
718 doc.set_raw_header_with_images(
719 banner,
720 &[("rId1", &logo_img, "logo.png")],
721 rdocx_oxml::header_footer::HdrFtrType::Default,
722 );
723 doc.set_different_first_page(true);
724 let cover_banner = build_header_banner_xml(
725 "rId1",
726 &BannerOpts {
727 bg_color: "C5922E",
728 banner_width: 7772400,
729 banner_height: 914400,
730 logo_width: 2011680,
731 logo_height: 438912,
732 logo_x_offset: 295125,
733 logo_y_offset: 237744,
734 },
735 );
736 doc.set_raw_header_with_images(
737 cover_banner,
738 &[("rId1", &logo_img, "logo.png")],
739 rdocx_oxml::header_footer::HdrFtrType::First,
740 );
741 doc.set_margins(
742 Length::twips(2292),
743 Length::twips(1440),
744 Length::twips(1440),
745 Length::twips(1440),
746 );
747 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
748 doc.set_footer("Tensorbee — Confidential");
749
750 // Custom styles
751 doc.add_style(
752 StyleBuilder::paragraph("ProposalTitle", "Proposal Title")
753 .based_on("Normal")
754 .run_properties({
755 let mut rpr = rdocx_oxml::properties::CT_RPr::default();
756 rpr.bold = Some(true);
757 rpr.font_ascii = Some("Georgia".to_string());
758 rpr.font_hansi = Some("Georgia".to_string());
759 rpr.sz = Some(rdocx_oxml::HalfPoint(56)); // half-points → 28pt
760 rpr.color = Some("1B2A4A".to_string());
761 rpr
762 }),
763 );
764
765 // ── Cover Page ──
766 for _ in 0..4 {
767 doc.add_paragraph("");
768 }
769 doc.add_paragraph("AI Platform Modernization")
770 .style("ProposalTitle")
771 .alignment(Alignment::Center);
772 doc.add_paragraph("")
773 .alignment(Alignment::Center)
774 .border_bottom(BorderStyle::Single, 8, "C5922E");
775 {
776 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
777 p.add_run("Prepared for Global Financial Corp.")
778 .size(14.0)
779 .color("666666")
780 .italic(true);
781 }
782 {
783 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
784 p.add_run("Prepared by: Walter White, CEO — Tensorbee")
785 .size(12.0)
786 .color("888888");
787 }
788 {
789 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
790 p.add_run("February 22, 2026").size(12.0).color("888888");
791 }
792
793 // ── TOC ──
794 doc.add_paragraph("").page_break_before(true);
795 doc.add_paragraph("Table of Contents").style("Heading1");
796 // We'll add a manual-style TOC since the insert_toc goes at a specific position
797 doc.insert_toc(doc.content_count(), 2);
798
799 // ── Executive Summary ──
800 doc.add_paragraph("").page_break_before(true);
801 doc.add_paragraph("Executive Summary").style("Heading1");
802 doc.add_paragraph(
803 "Tensorbee proposes a comprehensive modernization of Global Financial Corp's AI platform, \
804 transitioning from legacy batch-processing systems to a real-time inference architecture. \
805 This transformation will reduce model deployment time from weeks to hours, improve \
806 prediction accuracy by an estimated 23%, and deliver $4.2M in annual operational savings.",
807 )
808 .first_line_indent(Length::inches(0.3));
809 doc.add_paragraph(
810 "The project spans three phases over 18 months, with a total investment of $2.8M. \
811 Tensorbee brings deep expertise in ML infrastructure, having successfully completed \
812 similar transformations for three Fortune 500 financial institutions.",
813 )
814 .first_line_indent(Length::inches(0.3));
815
816 // Key metrics table
817 doc.add_paragraph("");
818 {
819 let mut tbl = doc
820 .add_table(5, 2)
821 .borders(BorderStyle::Single, 4, "1B2A4A")
822 .width_pct(80.0);
823 tbl.cell(0, 0).unwrap().set_text("Key Metric");
824 tbl.cell(0, 0).unwrap().shading("1B2A4A");
825 tbl.cell(0, 1).unwrap().set_text("Value");
826 tbl.cell(0, 1).unwrap().shading("1B2A4A");
827 let rows = [
828 ("Total Investment", "$2.8M"),
829 ("Annual Savings", "$4.2M"),
830 ("ROI (Year 1)", "150%"),
831 ("Timeline", "18 months"),
832 ];
833 for (i, (k, v)) in rows.iter().enumerate() {
834 tbl.cell(i + 1, 0).unwrap().set_text(k);
835 if i % 2 == 0 {
836 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
837 }
838 tbl.cell(i + 1, 1).unwrap().set_text(v);
839 if i % 2 == 0 {
840 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
841 }
842 }
843 }
844
845 // ── Problem Statement ──
846 doc.add_paragraph("").page_break_before(true);
847 doc.add_paragraph("Problem Statement").style("Heading1");
848 doc.add_paragraph(
849 "Global Financial Corp's current AI infrastructure faces three critical challenges:",
850 );
851 doc.add_numbered_list_item("Deployment Latency — New models take 3-4 weeks to deploy to production, compared to the industry benchmark of 2-3 days.", 0);
852 doc.add_numbered_list_item("Scalability Constraints — The batch processing architecture cannot handle real-time inference demands during peak trading hours.", 0);
853 doc.add_numbered_list_item("Technical Debt — Legacy Python 2.7 codebases and manual deployment scripts create reliability risks and slow iteration speed.", 0);
854
855 // ── Proposed Solution ──
856 doc.add_paragraph("").page_break_before(true);
857 doc.add_paragraph("Proposed Solution").style("Heading1");
858
859 doc.add_paragraph("Phase 1: Foundation (Months 1-6)")
860 .style("Heading2");
861 doc.add_bullet_list_item("Deploy Kubernetes-based ML serving infrastructure", 0);
862 doc.add_bullet_list_item("Implement CI/CD pipeline for model deployment", 0);
863 doc.add_bullet_list_item("Migrate top 5 production models to new platform", 0);
864
865 doc.add_paragraph("Phase 2: Expansion (Months 7-12)")
866 .style("Heading2");
867 doc.add_bullet_list_item(
868 "Real-time feature engineering pipeline (Apache Kafka + Flink)",
869 0,
870 );
871 doc.add_bullet_list_item("A/B testing framework for model variants", 0);
872 doc.add_bullet_list_item("Automated model monitoring and drift detection", 0);
873
874 doc.add_paragraph("Phase 3: Optimization (Months 13-18)")
875 .style("Heading2");
876 doc.add_bullet_list_item("GPU inference optimization (TensorRT/ONNX Runtime)", 0);
877 doc.add_bullet_list_item("Multi-region deployment for latency reduction", 0);
878 doc.add_bullet_list_item(
879 "Self-service model deployment portal for data science teams",
880 0,
881 );
882
883 // ── Budget ──
884 doc.add_paragraph("Budget Breakdown").style("Heading1");
885 {
886 let mut tbl = doc
887 .add_table(6, 3)
888 .borders(BorderStyle::Single, 4, "1B2A4A")
889 .width_pct(100.0);
890 tbl.cell(0, 0).unwrap().set_text("Category");
891 tbl.cell(0, 0).unwrap().shading("1B2A4A");
892 tbl.cell(0, 1).unwrap().set_text("Cost");
893 tbl.cell(0, 1).unwrap().shading("1B2A4A");
894 tbl.cell(0, 2).unwrap().set_text("Notes");
895 tbl.cell(0, 2).unwrap().shading("1B2A4A");
896 let items = [
897 (
898 "Infrastructure",
899 "$450,000",
900 "Cloud compute + GPU instances",
901 ),
902 ("Engineering Services", "$1,600,000", "12 FTE x 18 months"),
903 (
904 "Software Licenses",
905 "$350,000",
906 "Kafka, monitoring, CI/CD tools",
907 ),
908 (
909 "Training & Enablement",
910 "$200,000",
911 "Team training, documentation",
912 ),
913 ("Contingency (10%)", "$200,000", "Risk buffer"),
914 ];
915 for (i, (cat, cost, note)) in items.iter().enumerate() {
916 tbl.cell(i + 1, 0).unwrap().set_text(cat);
917 tbl.cell(i + 1, 1).unwrap().set_text(cost);
918 tbl.cell(i + 1, 2).unwrap().set_text(note);
919 if i % 2 == 0 {
920 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
921 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
922 tbl.cell(i + 1, 2).unwrap().shading("F4F1EB");
923 }
924 }
925 }
926
927 // ── Closing ──
928 doc.add_paragraph("");
929 doc.add_paragraph("Next Steps").style("Heading1");
930 doc.add_numbered_list_item(
931 "Schedule technical deep-dive with GFC engineering team (Week 1)",
932 0,
933 );
934 doc.add_numbered_list_item("Finalize scope and sign SOW (Week 2-3)", 0);
935 doc.add_numbered_list_item("Kick off Phase 1 with joint planning session (Week 4)", 0);
936
937 doc.add_paragraph("");
938 {
939 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
940 p.add_run("Walter White")
941 .bold(true)
942 .size(14.0)
943 .color("1B2A4A");
944 }
945 {
946 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
947 p.add_run("CEO, Tensorbee | walter@tensorbee.com")
948 .size(10.0)
949 .color("888888");
950 }
951
952 doc
953}
954
955// =============================================================================
956// 3. QUOTE / BILL OF MATERIALS — Teal + orange scheme
957// =============================================================================
958fn generate_quote(_samples_dir: &Path) -> Document {
959 let mut doc = Document::new();
960
961 // Colors: Teal #008B8B, Dark #1A3C3C, Orange #E07020, Light #F0F8F8
962 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
963 doc.set_margins(
964 Length::inches(0.75),
965 Length::inches(0.75),
966 Length::inches(0.75),
967 Length::inches(0.75),
968 );
969 doc.set_title("Quotation QT-2026-0147");
970 doc.set_author("Walter White");
971 doc.set_keywords("quote, BOM, Tensorbee");
972
973 doc.set_header("Tensorbee — Quotation");
974 doc.set_footer("QT-2026-0147 | Page");
975
976 // ── Header Block ──
977 {
978 let mut p = doc.add_paragraph("").alignment(Alignment::Left);
979 p.add_run("TENSORBEE")
980 .bold(true)
981 .size(28.0)
982 .color("008B8B")
983 .font("Helvetica");
984 }
985 doc.add_paragraph("123 Innovation Drive, Suite 400")
986 .alignment(Alignment::Left);
987 doc.add_paragraph("San Francisco, CA 94105 | +1 (415) 555-0199")
988 .alignment(Alignment::Left);
989 doc.add_paragraph("")
990 .border_bottom(BorderStyle::Single, 8, "008B8B");
991
992 // Quote meta
993 doc.add_paragraph("");
994 {
995 let mut tbl = doc.add_table(4, 4).width_pct(100.0);
996 tbl.cell(0, 0).unwrap().set_text("QUOTATION");
997 tbl.cell(0, 0).unwrap().shading("008B8B").grid_span(2);
998 tbl.cell(0, 2).unwrap().set_text("DATE");
999 tbl.cell(0, 2).unwrap().shading("008B8B");
1000 tbl.cell(0, 3).unwrap().set_text("VALID UNTIL");
1001 tbl.cell(0, 3).unwrap().shading("008B8B");
1002 tbl.cell(1, 0).unwrap().set_text("Quote #:");
1003 tbl.cell(1, 1).unwrap().set_text("QT-2026-0147");
1004 tbl.cell(1, 2).unwrap().set_text("Feb 22, 2026");
1005 tbl.cell(1, 3).unwrap().set_text("Mar 22, 2026");
1006 tbl.cell(2, 0).unwrap().set_text("Prepared by:");
1007 tbl.cell(2, 1).unwrap().set_text("Walter White");
1008 tbl.cell(2, 2).unwrap().set_text("Payment:");
1009 tbl.cell(2, 3).unwrap().set_text("Net 30");
1010 tbl.cell(3, 0).unwrap().set_text("Customer:");
1011 tbl.cell(3, 1).unwrap().set_text("Meridian Dynamics LLC");
1012 tbl.cell(3, 2).unwrap().set_text("Currency:");
1013 tbl.cell(3, 3).unwrap().set_text("USD");
1014 }
1015
1016 doc.add_paragraph("");
1017
1018 // ── Bill of Materials ──
1019 {
1020 let mut p = doc.add_paragraph("");
1021 p.add_run("Bill of Materials")
1022 .bold(true)
1023 .size(16.0)
1024 .color("1A3C3C");
1025 }
1026 doc.add_paragraph("");
1027
1028 {
1029 let mut tbl = doc
1030 .add_table(10, 6)
1031 .borders(BorderStyle::Single, 2, "008B8B")
1032 .width_pct(100.0)
1033 .layout_fixed();
1034 // Headers
1035 let hdrs = [
1036 "#",
1037 "Part Number",
1038 "Description",
1039 "Qty",
1040 "Unit Price",
1041 "Total",
1042 ];
1043 for (c, h) in hdrs.iter().enumerate() {
1044 tbl.cell(0, c).unwrap().set_text(h);
1045 tbl.cell(0, c).unwrap().shading("008B8B");
1046 }
1047
1048 let items: Vec<(&str, &str, &str, &str, &str)> = vec![
1049 (
1050 "TB-GPU-A100",
1051 "NVIDIA A100 80GB GPU",
1052 "4",
1053 "$12,500.00",
1054 "$50,000.00",
1055 ),
1056 (
1057 "TB-SRV-R750",
1058 "Dell R750xa Server Chassis",
1059 "2",
1060 "$8,200.00",
1061 "$16,400.00",
1062 ),
1063 (
1064 "TB-RAM-256G",
1065 "256GB DDR5 ECC Memory Module",
1066 "8",
1067 "$890.00",
1068 "$7,120.00",
1069 ),
1070 (
1071 "TB-SSD-3840",
1072 "3.84TB NVMe U.2 SSD",
1073 "8",
1074 "$1,150.00",
1075 "$9,200.00",
1076 ),
1077 (
1078 "TB-NET-CX7",
1079 "ConnectX-7 200GbE NIC",
1080 "4",
1081 "$1,800.00",
1082 "$7,200.00",
1083 ),
1084 (
1085 "TB-SW-48P",
1086 "48-Port 100GbE Switch",
1087 "1",
1088 "$22,000.00",
1089 "$22,000.00",
1090 ),
1091 (
1092 "TB-CAB-RACK",
1093 "42U Server Rack + PDU",
1094 "1",
1095 "$4,500.00",
1096 "$4,500.00",
1097 ),
1098 (
1099 "TB-SVC-INST",
1100 "Installation & Configuration",
1101 "1",
1102 "$8,500.00",
1103 "$8,500.00",
1104 ),
1105 (
1106 "TB-SVC-SUPP",
1107 "3-Year Premium Support",
1108 "1",
1109 "$15,000.00",
1110 "$15,000.00",
1111 ),
1112 ];
1113
1114 for (i, (pn, desc, qty, unit, total)) in items.iter().enumerate() {
1115 let row = i + 1;
1116 tbl.cell(row, 0).unwrap().set_text(&format!("{}", i + 1));
1117 tbl.cell(row, 1).unwrap().set_text(pn);
1118 tbl.cell(row, 2).unwrap().set_text(desc);
1119 tbl.cell(row, 3).unwrap().set_text(qty);
1120 tbl.cell(row, 4).unwrap().set_text(unit);
1121 tbl.cell(row, 5).unwrap().set_text(total);
1122 if i % 2 == 0 {
1123 for c in 0..6 {
1124 tbl.cell(row, c).unwrap().shading("F0F8F8");
1125 }
1126 }
1127 }
1128 }
1129
1130 doc.add_paragraph("");
1131
1132 // ── Totals ──
1133 {
1134 let mut tbl = doc
1135 .add_table(4, 2)
1136 .borders(BorderStyle::Single, 2, "008B8B")
1137 .width(Length::inches(3.5))
1138 .alignment(Alignment::Right);
1139 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1140 tbl.cell(0, 1).unwrap().set_text("$139,920.00");
1141 tbl.cell(1, 0).unwrap().set_text("Shipping & Handling");
1142 tbl.cell(1, 1).unwrap().set_text("$2,500.00");
1143 tbl.cell(2, 0).unwrap().set_text("Tax (8.625%)");
1144 tbl.cell(2, 1).unwrap().set_text("$12,068.10");
1145 tbl.cell(3, 0).unwrap().set_text("TOTAL");
1146 tbl.cell(3, 0).unwrap().shading("E07020");
1147 tbl.cell(3, 1).unwrap().set_text("$154,488.10");
1148 tbl.cell(3, 1).unwrap().shading("E07020");
1149 }
1150
1151 doc.add_paragraph("");
1152
1153 // ── Terms & Conditions ──
1154 {
1155 let mut p = doc.add_paragraph("");
1156 p.add_run("Terms & Conditions")
1157 .bold(true)
1158 .size(14.0)
1159 .color("1A3C3C");
1160 }
1161 doc.add_numbered_list_item(
1162 "This quotation is valid for 30 calendar days from the date of issue.",
1163 0,
1164 );
1165 doc.add_numbered_list_item(
1166 "All prices are in USD and exclusive of applicable taxes unless stated.",
1167 0,
1168 );
1169 doc.add_numbered_list_item("Standard lead time is 4-6 weeks from PO receipt.", 0);
1170 doc.add_numbered_list_item(
1171 "Payment terms: Net 30 from invoice date. 2% discount for payment within 10 days.",
1172 0,
1173 );
1174 doc.add_numbered_list_item(
1175 "Warranty: 3-year manufacturer warranty on all hardware components.",
1176 0,
1177 );
1178 doc.add_numbered_list_item(
1179 "Returns subject to 15% restocking fee if initiated after 14 days.",
1180 0,
1181 );
1182
1183 doc.add_paragraph("");
1184 doc.add_paragraph("")
1185 .border_bottom(BorderStyle::Single, 4, "008B8B");
1186 doc.add_paragraph("");
1187 {
1188 let mut p = doc.add_paragraph("");
1189 p.add_run("Accepted by: ").bold(true);
1190 p.add_run("___________________________ Date: ___________");
1191 }
1192 {
1193 let mut p = doc.add_paragraph("");
1194 p.add_run("Print Name: ").bold(true);
1195 p.add_run("___________________________ Title: ___________");
1196 }
1197
1198 doc
1199}
1200
1201// =============================================================================
1202// 4. INVOICE — Crimson + charcoal scheme
1203// =============================================================================
1204fn generate_invoice(_samples_dir: &Path) -> Document {
1205 let mut doc = Document::new();
1206
1207 // Colors: Crimson #B22222, Charcoal #333333, Light #FAF0F0
1208 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1209 doc.set_margins(
1210 Length::inches(0.75),
1211 Length::inches(0.75),
1212 Length::inches(0.75),
1213 Length::inches(0.75),
1214 );
1215 doc.set_title("Invoice INV-2026-0392");
1216 doc.set_author("Walter White");
1217 doc.set_keywords("invoice, Tensorbee");
1218
1219 doc.set_footer("Tensorbee — Thank you for your business!");
1220
1221 // ── Company & Invoice Header ──
1222 {
1223 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1224 // Company name left, INVOICE right
1225 {
1226 let mut cell = tbl.cell(0, 0).unwrap();
1227 let mut p = cell.add_paragraph("");
1228 p.add_run("TENSORBEE")
1229 .bold(true)
1230 .size(32.0)
1231 .color("B22222")
1232 .font("Helvetica");
1233 }
1234 {
1235 let mut cell = tbl.cell(0, 1).unwrap();
1236 let mut p = cell.add_paragraph("");
1237 p.add_run("INVOICE").bold(true).size(32.0).color("333333");
1238 }
1239 tbl.cell(1, 0)
1240 .unwrap()
1241 .set_text("123 Innovation Drive, Suite 400");
1242 tbl.cell(1, 1).unwrap().set_text("Invoice #: INV-2026-0392");
1243 tbl.cell(2, 0).unwrap().set_text("San Francisco, CA 94105");
1244 tbl.cell(2, 1).unwrap().set_text("Date: February 22, 2026");
1245 tbl.cell(3, 0).unwrap().set_text("walter@tensorbee.com");
1246 tbl.cell(3, 1).unwrap().set_text("Due Date: March 24, 2026");
1247 }
1248
1249 doc.add_paragraph("")
1250 .border_bottom(BorderStyle::Thick, 8, "B22222");
1251 doc.add_paragraph("");
1252
1253 // ── Bill To / Ship To ──
1254 {
1255 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1256 tbl.cell(0, 0).unwrap().set_text("BILL TO");
1257 tbl.cell(0, 0).unwrap().shading("B22222");
1258 tbl.cell(0, 1).unwrap().set_text("SHIP TO");
1259 tbl.cell(0, 1).unwrap().shading("B22222");
1260 tbl.cell(1, 0).unwrap().set_text("Meridian Dynamics LLC");
1261 tbl.cell(1, 1).unwrap().set_text("Meridian Dynamics LLC");
1262 tbl.cell(2, 0).unwrap().set_text("456 Enterprise Blvd");
1263 tbl.cell(2, 1).unwrap().set_text("Attn: Server Room B");
1264 tbl.cell(3, 0).unwrap().set_text("Austin, TX 78701");
1265 tbl.cell(3, 1)
1266 .unwrap()
1267 .set_text("456 Enterprise Blvd, Austin, TX 78701");
1268 }
1269
1270 doc.add_paragraph("");
1271
1272 // ── Line Items ──
1273 {
1274 let mut tbl = doc
1275 .add_table(8, 5)
1276 .borders(BorderStyle::Single, 2, "B22222")
1277 .width_pct(100.0);
1278 let hdrs = ["Description", "Qty", "Unit Price", "Tax", "Amount"];
1279 for (c, h) in hdrs.iter().enumerate() {
1280 tbl.cell(0, c).unwrap().set_text(h);
1281 tbl.cell(0, c).unwrap().shading("B22222");
1282 }
1283 let items = [
1284 (
1285 "ML Infrastructure Setup — Phase 1",
1286 "1",
1287 "$45,000.00",
1288 "$3,881.25",
1289 "$48,881.25",
1290 ),
1291 (
1292 "Data Pipeline Development (160 hrs)",
1293 "160",
1294 "$225.00",
1295 "$3,105.00",
1296 "$39,105.00",
1297 ),
1298 (
1299 "GPU Cluster Configuration",
1300 "1",
1301 "$12,000.00",
1302 "$1,035.00",
1303 "$13,035.00",
1304 ),
1305 (
1306 "API Gateway Implementation",
1307 "1",
1308 "$18,500.00",
1309 "$1,595.63",
1310 "$20,095.63",
1311 ),
1312 (
1313 "Load Testing & QA (80 hrs)",
1314 "80",
1315 "$195.00",
1316 "$1,345.50",
1317 "$16,945.50",
1318 ),
1319 (
1320 "Documentation & Training",
1321 "1",
1322 "$8,000.00",
1323 "$690.00",
1324 "$8,690.00",
1325 ),
1326 (
1327 "Project Management (3 months)",
1328 "3",
1329 "$6,500.00",
1330 "$1,679.63",
1331 "$21,179.63",
1332 ),
1333 ];
1334 for (i, (desc, qty, unit, tax, amt)) in items.iter().enumerate() {
1335 let r = i + 1;
1336 tbl.cell(r, 0).unwrap().set_text(desc);
1337 tbl.cell(r, 1).unwrap().set_text(qty);
1338 tbl.cell(r, 2).unwrap().set_text(unit);
1339 tbl.cell(r, 3).unwrap().set_text(tax);
1340 tbl.cell(r, 4).unwrap().set_text(amt);
1341 if i % 2 == 0 {
1342 for c in 0..5 {
1343 tbl.cell(r, c).unwrap().shading("FAF0F0");
1344 }
1345 }
1346 }
1347 }
1348
1349 doc.add_paragraph("");
1350
1351 // ── Totals ──
1352 {
1353 let mut tbl = doc
1354 .add_table(4, 2)
1355 .borders(BorderStyle::Single, 2, "B22222")
1356 .width(Length::inches(3.0))
1357 .alignment(Alignment::Right);
1358 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1359 tbl.cell(0, 1).unwrap().set_text("$154,600.00");
1360 tbl.cell(1, 0).unwrap().set_text("Tax (8.625%)");
1361 tbl.cell(1, 1).unwrap().set_text("$13,334.25");
1362 tbl.cell(2, 0).unwrap().set_text("Discount (5%)");
1363 tbl.cell(2, 1).unwrap().set_text("-$7,730.00");
1364 tbl.cell(3, 0).unwrap().set_text("AMOUNT DUE");
1365 tbl.cell(3, 0).unwrap().shading("B22222");
1366 tbl.cell(3, 1).unwrap().set_text("$160,204.25");
1367 tbl.cell(3, 1).unwrap().shading("B22222");
1368 }
1369
1370 doc.add_paragraph("");
1371 doc.add_paragraph("");
1372
1373 // ── Payment Details ──
1374 {
1375 let mut p = doc.add_paragraph("");
1376 p.add_run("Payment Details")
1377 .bold(true)
1378 .size(14.0)
1379 .color("333333");
1380 }
1381 doc.add_paragraph("Bank: Silicon Valley Bank")
1382 .indent_left(Length::inches(0.3));
1383 doc.add_paragraph("Account: Tensorbee Inc. — 0847-2953-1120")
1384 .indent_left(Length::inches(0.3));
1385 doc.add_paragraph("Routing: 121140399")
1386 .indent_left(Length::inches(0.3));
1387 doc.add_paragraph("Swift: SVBKUS6S")
1388 .indent_left(Length::inches(0.3));
1389
1390 doc.add_paragraph("");
1391
1392 doc.add_paragraph("Please include invoice number INV-2026-0392 in the payment reference.")
1393 .shading("FAF0F0")
1394 .border_all(BorderStyle::Single, 2, "B22222");
1395
1396 doc
1397}
1398
1399// =============================================================================
1400// 5. REPORT — Forest green + earth tones, with images & hierarchical sections
1401// =============================================================================
1402fn generate_report(_samples_dir: &Path) -> Document {
1403 let mut doc = Document::new();
1404
1405 // Colors: Forest #2D5016, Sage #6B8E23, Earth #8B7355, Cream #FFFAF0
1406 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1407 doc.set_margins(
1408 Length::inches(1.0),
1409 Length::inches(1.0),
1410 Length::inches(1.0),
1411 Length::inches(1.0),
1412 );
1413 doc.set_title("Q4 2025 Environmental Impact Report");
1414 doc.set_author("Walter White");
1415 doc.set_subject("Quarterly Environmental Report");
1416 doc.set_keywords("environment, sustainability, report, Tensorbee");
1417
1418 doc.set_different_first_page(true);
1419 doc.set_first_page_header("");
1420 doc.set_header("Tensorbee — Q4 2025 Environmental Impact Report");
1421 doc.set_footer("CONFIDENTIAL — Page");
1422
1423 // ── Cover ──
1424 let cover_bg = create_sample_png(612, 792, [20, 50, 15]);
1425 doc.add_background_image(&cover_bg, "report_cover.png");
1426
1427 for _ in 0..5 {
1428 doc.add_paragraph("");
1429 }
1430 {
1431 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1432 p.add_run("Q4 2025")
1433 .bold(true)
1434 .size(48.0)
1435 .color("FFFFFF")
1436 .font("Georgia");
1437 }
1438 {
1439 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1440 p.add_run("Environmental Impact Report")
1441 .size(24.0)
1442 .color("90EE90")
1443 .font("Georgia")
1444 .italic(true);
1445 }
1446 doc.add_paragraph("");
1447 {
1448 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1449 p.add_run("Tensorbee — Sustainability Division")
1450 .size(14.0)
1451 .color("C0C0C0");
1452 }
1453 {
1454 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1455 p.add_run("Prepared by Walter White, Chief Sustainability Officer")
1456 .size(11.0)
1457 .color("AAAAAA");
1458 }
1459
1460 // ── TOC ──
1461 doc.add_paragraph("").page_break_before(true);
1462 doc.insert_toc(doc.content_count(), 3);
1463
1464 // ── Section 1: Executive Overview ──
1465 doc.add_paragraph("").page_break_before(true);
1466 doc.add_paragraph("Executive Overview").style("Heading1");
1467 doc.add_paragraph(
1468 "This report presents Tensorbee's environmental performance for Q4 2025. Our \
1469 sustainability initiatives have yielded a 34% reduction in carbon emissions compared \
1470 to Q4 2024, exceeding our target of 25%. Key achievements include the transition to \
1471 100% renewable energy in our primary data centers and the launch of our carbon offset \
1472 marketplace.",
1473 )
1474 .first_line_indent(Length::inches(0.3));
1475
1476 // Image: performance chart
1477 let chart_img = create_chart_png(400, 200);
1478 doc.add_paragraph("");
1479 doc.add_picture(
1480 &chart_img,
1481 "performance_chart.png",
1482 Length::inches(5.0),
1483 Length::inches(2.5),
1484 )
1485 .alignment(Alignment::Center);
1486 {
1487 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1488 p.add_run("Figure 1: Quarterly Carbon Emissions (tonnes CO2e)")
1489 .italic(true)
1490 .size(9.0)
1491 .color("666666");
1492 }
1493
1494 // ── Section 2: Energy Consumption ──
1495 doc.add_paragraph("").page_break_before(true);
1496 doc.add_paragraph("Energy Consumption").style("Heading1");
1497
1498 doc.add_paragraph("Data Center Operations")
1499 .style("Heading2");
1500 doc.add_paragraph(
1501 "Our three primary data centers consumed a combined 4.2 GWh in Q4 2025, \
1502 a 12% reduction from Q3 2025 driven by improved cooling efficiency and \
1503 server consolidation.",
1504 );
1505
1506 // Data table
1507 {
1508 let mut tbl = doc
1509 .add_table(5, 4)
1510 .borders(BorderStyle::Single, 4, "2D5016")
1511 .width_pct(100.0);
1512 let hdrs = ["Data Center", "Capacity (MW)", "Usage (GWh)", "PUE"];
1513 for (c, h) in hdrs.iter().enumerate() {
1514 tbl.cell(0, c).unwrap().set_text(h);
1515 tbl.cell(0, c).unwrap().shading("2D5016");
1516 }
1517 let rows = [
1518 ("San Francisco (Primary)", "3.2", "1.8", "1.12"),
1519 ("Dublin (EU)", "2.1", "1.2", "1.18"),
1520 ("Singapore (APAC)", "1.8", "1.2", "1.24"),
1521 ("TOTAL", "7.1", "4.2", "1.17 avg"),
1522 ];
1523 for (i, (dc, cap, use_, pue)) in rows.iter().enumerate() {
1524 tbl.cell(i + 1, 0).unwrap().set_text(dc);
1525 tbl.cell(i + 1, 1).unwrap().set_text(cap);
1526 tbl.cell(i + 1, 2).unwrap().set_text(use_);
1527 tbl.cell(i + 1, 3).unwrap().set_text(pue);
1528 if i == 3 {
1529 for c in 0..4 {
1530 tbl.cell(i + 1, c).unwrap().shading("E2EFDA");
1531 }
1532 }
1533 }
1534 }
1535
1536 doc.add_paragraph("");
1537
1538 doc.add_paragraph("Renewable Energy Mix").style("Heading2");
1539 doc.add_paragraph("Breakdown of energy sources across all facilities:");
1540
1541 doc.add_bullet_list_item("Solar PV: 42% (1.76 GWh)", 0);
1542 doc.add_bullet_list_item("Wind Power Purchase Agreements: 38% (1.60 GWh)", 0);
1543 doc.add_bullet_list_item("Hydroelectric: 12% (0.50 GWh)", 0);
1544 doc.add_bullet_list_item("Grid (non-renewable): 8% (0.34 GWh)", 0);
1545
1546 // ── Section 3: Water & Waste ──
1547 doc.add_paragraph("Water & Waste Management")
1548 .style("Heading1");
1549
1550 doc.add_paragraph("Water Usage").style("Heading2");
1551 doc.add_paragraph(
1552 "Total water consumption was 12.4 million gallons, a 15% reduction from Q3. \
1553 Our closed-loop cooling systems now recycle 78% of water used in cooling operations.",
1554 );
1555
1556 doc.add_paragraph("Waste Reduction").style("Heading2");
1557 doc.add_paragraph("E-waste management results for Q4:")
1558 .keep_with_next(true);
1559 {
1560 let mut tbl = doc
1561 .add_table(4, 3)
1562 .borders(BorderStyle::Single, 2, "6B8E23")
1563 .width_pct(80.0);
1564 tbl.cell(0, 0).unwrap().set_text("Category");
1565 tbl.cell(0, 0).unwrap().shading("6B8E23");
1566 tbl.cell(0, 1).unwrap().set_text("Weight (kg)");
1567 tbl.cell(0, 1).unwrap().shading("6B8E23");
1568 tbl.cell(0, 2).unwrap().set_text("Recycled %");
1569 tbl.cell(0, 2).unwrap().shading("6B8E23");
1570 let rows = [
1571 ("Server Hardware", "2,450", "94%"),
1572 ("Networking Equipment", "820", "91%"),
1573 ("Storage Media", "340", "99%"),
1574 ];
1575 for (i, (cat, wt, pct)) in rows.iter().enumerate() {
1576 tbl.cell(i + 1, 0).unwrap().set_text(cat);
1577 tbl.cell(i + 1, 1).unwrap().set_text(wt);
1578 tbl.cell(i + 1, 2).unwrap().set_text(pct);
1579 }
1580 }
1581
1582 // ── Section 4: Initiatives ──
1583 doc.add_paragraph("").page_break_before(true);
1584 doc.add_paragraph("Q1 2026 Initiatives").style("Heading1");
1585
1586 doc.add_paragraph("Planned Programs").style("Heading2");
1587 doc.add_numbered_list_item(
1588 "Deploy on-site battery storage at SF data center (2 MWh capacity)",
1589 0,
1590 );
1591 doc.add_numbered_list_item("Pilot immersion cooling in Dublin facility", 0);
1592 doc.add_numbered_list_item("Launch employee commute carbon offset program", 0);
1593 doc.add_numbered_list_item("Achieve ISO 14001 certification for Singapore facility", 0);
1594
1595 doc.add_paragraph("Investment Targets").style("Heading2");
1596 doc.add_paragraph("Sustainability CapEx allocation for FY2026:")
1597 .keep_with_next(true);
1598 {
1599 let mut tbl = doc
1600 .add_table(5, 2)
1601 .borders(BorderStyle::Single, 4, "2D5016");
1602 tbl.cell(0, 0).unwrap().set_text("Initiative");
1603 tbl.cell(0, 0).unwrap().shading("2D5016");
1604 tbl.cell(0, 1).unwrap().set_text("Budget");
1605 tbl.cell(0, 1).unwrap().shading("2D5016");
1606 let rows = [
1607 ("Battery Storage", "$1.2M"),
1608 ("Immersion Cooling Pilot", "$800K"),
1609 ("Solar Panel Expansion", "$2.1M"),
1610 ("Carbon Credits", "$500K"),
1611 ];
1612 for (i, (init, budget)) in rows.iter().enumerate() {
1613 tbl.cell(i + 1, 0).unwrap().set_text(init);
1614 tbl.cell(i + 1, 1).unwrap().set_text(budget);
1615 if i % 2 == 0 {
1616 tbl.cell(i + 1, 0).unwrap().shading("FFFAF0");
1617 tbl.cell(i + 1, 1).unwrap().shading("FFFAF0");
1618 }
1619 }
1620 }
1621
1622 // Image: sustainability roadmap
1623 let roadmap_img = create_sample_png(500, 100, [40, 80, 30]);
1624 doc.add_paragraph("");
1625 doc.add_picture(
1626 &roadmap_img,
1627 "roadmap.png",
1628 Length::inches(6.0),
1629 Length::inches(1.2),
1630 )
1631 .alignment(Alignment::Center);
1632 {
1633 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1634 p.add_run("Figure 2: 2026 Sustainability Roadmap")
1635 .italic(true)
1636 .size(9.0)
1637 .color("666666");
1638 }
1639
1640 doc.add_paragraph("");
1641 doc.add_paragraph(
1642 "For questions about this report, contact Walter White at sustainability@tensorbee.com.",
1643 )
1644 .shading("FFFAF0")
1645 .border_all(BorderStyle::Single, 2, "2D5016");
1646
1647 doc
1648}
1649
1650// =============================================================================
1651// 6. LETTER — Slate blue + silver scheme
1652// =============================================================================
1653fn generate_letter(_samples_dir: &Path) -> Document {
1654 let mut doc = Document::new();
1655
1656 // Colors: Slate #4A5568, Blue accent #3182CE
1657 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1658 doc.set_margins(
1659 Length::inches(1.25),
1660 Length::inches(1.25),
1661 Length::inches(1.25),
1662 Length::inches(1.25),
1663 );
1664 doc.set_title("Business Letter");
1665 doc.set_author("Walter White");
1666
1667 // Banner header using raw XML
1668 let logo_img = create_logo_png(220, 48);
1669 let banner = build_header_banner_xml(
1670 "rId1",
1671 &BannerOpts {
1672 bg_color: "4A5568",
1673 banner_width: 7772400,
1674 banner_height: 731520, // ~0.8"
1675 logo_width: 2011680,
1676 logo_height: 438912,
1677 logo_x_offset: 295125,
1678 logo_y_offset: 146304,
1679 },
1680 );
1681 doc.set_raw_header_with_images(
1682 banner,
1683 &[("rId1", &logo_img, "logo.png")],
1684 rdocx_oxml::header_footer::HdrFtrType::Default,
1685 );
1686 doc.set_margins(
1687 Length::twips(2000),
1688 Length::twips(1800),
1689 Length::twips(1440),
1690 Length::twips(1800),
1691 );
1692 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
1693
1694 // Footer with contact
1695 doc.set_footer(
1696 "Tensorbee | 123 Innovation Drive, Suite 400 | San Francisco, CA 94105 | tensorbee.com",
1697 );
1698
1699 // ── Sender Address ──
1700 doc.add_paragraph("Walter White");
1701 doc.add_paragraph("Chief Executive Officer");
1702 doc.add_paragraph("Tensorbee");
1703 doc.add_paragraph("123 Innovation Drive, Suite 400");
1704 doc.add_paragraph("San Francisco, CA 94105");
1705 doc.add_paragraph("");
1706 doc.add_paragraph("February 22, 2026");
1707
1708 doc.add_paragraph("");
1709
1710 // ── Recipient ──
1711 doc.add_paragraph("Ms. Sarah Chen");
1712 doc.add_paragraph("VP of Engineering");
1713 doc.add_paragraph("Meridian Dynamics LLC");
1714 doc.add_paragraph("456 Enterprise Boulevard");
1715 doc.add_paragraph("Austin, TX 78701");
1716
1717 doc.add_paragraph("");
1718
1719 // ── Subject ──
1720 {
1721 let mut p = doc.add_paragraph("");
1722 p.add_run("Re: Strategic Technology Partnership — Phase 2 Expansion")
1723 .bold(true);
1724 }
1725
1726 doc.add_paragraph("");
1727
1728 // ── Body ──
1729 doc.add_paragraph("Dear Ms. Chen,");
1730 doc.add_paragraph("");
1731
1732 doc.add_paragraph(
1733 "I am writing to express Tensorbee's enthusiasm for expanding our strategic technology \
1734 partnership with Meridian Dynamics. Following the successful completion of Phase 1, \
1735 which delivered a 40% improvement in your ML inference pipeline throughput, we believe \
1736 the foundation is firmly established for an ambitious Phase 2 engagement.",
1737 )
1738 .first_line_indent(Length::inches(0.5));
1739
1740 doc.add_paragraph(
1741 "During our recent executive review, your team highlighted three priority areas for \
1742 the next phase of collaboration:",
1743 )
1744 .first_line_indent(Length::inches(0.5));
1745
1746 doc.add_numbered_list_item(
1747 "Real-time anomaly detection for your financial transaction monitoring system, \
1748 targeting sub-100ms latency at 50,000 transactions per second.",
1749 0,
1750 );
1751 doc.add_numbered_list_item(
1752 "Federated learning infrastructure to enable model training across your distributed \
1753 data centers without centralizing sensitive financial data.",
1754 0,
1755 );
1756 doc.add_numbered_list_item(
1757 "MLOps automation to reduce your model deployment cycle from the current 5 days \
1758 to under 4 hours.",
1759 0,
1760 );
1761
1762 doc.add_paragraph(
1763 "Our engineering team has prepared a detailed technical proposal addressing each of \
1764 these areas. We have allocated a dedicated team of eight senior engineers, led by \
1765 our CTO, to ensure continuity with the Phase 1 team your organization has already \
1766 built a productive working relationship with.",
1767 )
1768 .first_line_indent(Length::inches(0.5));
1769
1770 doc.add_paragraph(
1771 "I would welcome the opportunity to present our Phase 2 proposal in person. My \
1772 assistant will reach out to coordinate a meeting at your convenience during the \
1773 first week of March.",
1774 )
1775 .first_line_indent(Length::inches(0.5));
1776
1777 doc.add_paragraph("");
1778
1779 doc.add_paragraph("Warm regards,");
1780 doc.add_paragraph("");
1781 doc.add_paragraph("");
1782
1783 // Signature line
1784 doc.add_paragraph("")
1785 .border_bottom(BorderStyle::Single, 4, "4A5568");
1786 {
1787 let mut p = doc.add_paragraph("");
1788 p.add_run("Walter White").bold(true).size(12.0);
1789 }
1790 {
1791 let mut p = doc.add_paragraph("");
1792 p.add_run("Chief Executive Officer, Tensorbee")
1793 .italic(true)
1794 .size(10.0)
1795 .color("666666");
1796 }
1797 {
1798 let mut p = doc.add_paragraph("");
1799 p.add_run("walter@tensorbee.com | +1 (415) 555-0199")
1800 .size(10.0)
1801 .color("888888");
1802 }
1803
1804 doc
1805}
1806
1807// =============================================================================
1808// 7. EMPLOYMENT CONTRACT — Purple + charcoal scheme
1809// =============================================================================
1810fn generate_contract(_samples_dir: &Path) -> Document {
1811 let mut doc = Document::new();
1812
1813 // Colors: Purple #5B2C6F, Plum #8E44AD, Gray #2C3E50
1814 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1815 doc.set_margins(
1816 Length::inches(1.25),
1817 Length::inches(1.0),
1818 Length::inches(1.0),
1819 Length::inches(1.0),
1820 );
1821 doc.set_title("Employment Agreement");
1822 doc.set_author("Tensorbee HR Department");
1823 doc.set_subject("Employment Contract — Walter White");
1824 doc.set_keywords("employment, contract, agreement, Tensorbee");
1825
1826 doc.set_header("TENSORBEE — Employment Agreement");
1827 doc.set_footer("Employment Agreement — Walter White — Page");
1828
1829 // ── Title Block ──
1830 doc.add_paragraph("")
1831 .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1832 {
1833 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1834 p.add_run("EMPLOYMENT AGREEMENT")
1835 .bold(true)
1836 .size(24.0)
1837 .color("5B2C6F")
1838 .font("Georgia");
1839 }
1840 doc.add_paragraph("")
1841 .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1842 doc.add_paragraph("");
1843
1844 // ── Parties ──
1845 doc.add_paragraph(
1846 "This Employment Agreement (\"Agreement\") is entered into as of February 22, 2026 \
1847 (\"Effective Date\"), by and between:",
1848 );
1849
1850 doc.add_paragraph("");
1851
1852 {
1853 let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1854 p.add_run("EMPLOYER: ").bold(true);
1855 p.add_run(
1856 "Tensorbee, Inc., a Delaware corporation with its principal place of business at \
1857 123 Innovation Drive, Suite 400, San Francisco, CA 94105 (\"Company\")",
1858 );
1859 }
1860 doc.add_paragraph("");
1861 {
1862 let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1863 p.add_run("EMPLOYEE: ").bold(true);
1864 p.add_run(
1865 "Walter White, residing at 308 Negra Arroyo Lane, Albuquerque, NM 87104 (\"Employee\")",
1866 );
1867 }
1868
1869 doc.add_paragraph("");
1870 doc.add_paragraph("The Company and Employee are collectively referred to as the \"Parties.\"");
1871 doc.add_paragraph("");
1872
1873 // ── Article 1: Position and Duties ──
1874 doc.add_paragraph("Article 1 — Position and Duties")
1875 .style("Heading1");
1876 {
1877 let mut p = doc.add_paragraph("");
1878 p.add_run("1.1 ").bold(true);
1879 p.add_run("The Company hereby employs the Employee as ");
1880 p.add_run("Chief Executive Officer (CEO)")
1881 .bold(true)
1882 .italic(true);
1883 p.add_run(", reporting directly to the Board of Directors.");
1884 }
1885 {
1886 let mut p = doc.add_paragraph("");
1887 p.add_run("1.2 ").bold(true);
1888 p.add_run("The Employee shall devote full working time, attention, and best efforts to \
1889 the performance of duties as reasonably assigned by the Board, including but not limited to:");
1890 }
1891 doc.add_bullet_list_item(
1892 "Setting and executing the Company's strategic vision and business plan",
1893 0,
1894 );
1895 doc.add_bullet_list_item(
1896 "Overseeing all operations, engineering, and commercial activities",
1897 0,
1898 );
1899 doc.add_bullet_list_item(
1900 "Representing the Company to investors, customers, and the public",
1901 0,
1902 );
1903 doc.add_bullet_list_item("Recruiting, developing, and retaining key talent", 0);
1904
1905 {
1906 let mut p = doc.add_paragraph("");
1907 p.add_run("1.3 ").bold(true);
1908 p.add_run(
1909 "The Employee's primary work location shall be the Company's San Francisco \
1910 headquarters, with reasonable travel as required by business needs.",
1911 );
1912 }
1913
1914 // ── Article 2: Compensation ──
1915 doc.add_paragraph("Article 2 — Compensation and Benefits")
1916 .style("Heading1");
1917 {
1918 let mut p = doc.add_paragraph("");
1919 p.add_run("2.1 Base Salary. ").bold(true);
1920 p.add_run("The Company shall pay the Employee an annual base salary of ");
1921 p.add_run("$375,000.00").bold(true);
1922 p.add_run(" (Three Hundred Seventy-Five Thousand Dollars), payable in accordance with the \
1923 Company's standard payroll schedule, less applicable withholdings and deductions.");
1924 }
1925 {
1926 let mut p = doc.add_paragraph("");
1927 p.add_run("2.2 Annual Bonus. ").bold(true);
1928 p.add_run("The Employee shall be eligible for an annual performance bonus of up to ");
1929 p.add_run("40%").bold(true);
1930 p.add_run(
1931 " of base salary, based on achievement of mutually agreed performance objectives.",
1932 );
1933 }
1934 {
1935 let mut p = doc.add_paragraph("");
1936 p.add_run("2.3 Equity. ").bold(true);
1937 p.add_run("Subject to Board approval, the Employee shall receive a stock option grant of ");
1938 p.add_run("500,000 shares").bold(true);
1939 p.add_run(
1940 " of the Company's common stock, vesting over four (4) years with a one-year cliff, \
1941 at an exercise price equal to the fair market value on the date of grant.",
1942 );
1943 }
1944
1945 // Compensation summary table
1946 doc.add_paragraph("");
1947 {
1948 let mut tbl = doc
1949 .add_table(5, 2)
1950 .borders(BorderStyle::Single, 4, "5B2C6F")
1951 .width_pct(70.0)
1952 .alignment(Alignment::Center);
1953 tbl.cell(0, 0).unwrap().set_text("Compensation Element");
1954 tbl.cell(0, 0).unwrap().shading("5B2C6F");
1955 tbl.cell(0, 1).unwrap().set_text("Value");
1956 tbl.cell(0, 1).unwrap().shading("5B2C6F");
1957 let items = [
1958 ("Base Salary", "$375,000/year"),
1959 ("Target Bonus", "Up to 40% ($150,000)"),
1960 ("Equity Grant", "500,000 shares (4yr vest)"),
1961 ("Total Target Comp", "$525,000 + equity"),
1962 ];
1963 for (i, (elem, val)) in items.iter().enumerate() {
1964 tbl.cell(i + 1, 0).unwrap().set_text(elem);
1965 tbl.cell(i + 1, 1).unwrap().set_text(val);
1966 if i == 3 {
1967 tbl.cell(i + 1, 0).unwrap().shading("E8DAEF");
1968 tbl.cell(i + 1, 1).unwrap().shading("E8DAEF");
1969 }
1970 }
1971 }
1972
1973 // ── Article 3: Benefits ──
1974 doc.add_paragraph("").page_break_before(true);
1975 doc.add_paragraph("Article 3 — Benefits").style("Heading1");
1976 {
1977 let mut p = doc.add_paragraph("");
1978 p.add_run("3.1 ").bold(true);
1979 p.add_run(
1980 "The Employee shall be entitled to participate in all benefit programs \
1981 generally available to senior executives, including:",
1982 );
1983 }
1984 doc.add_bullet_list_item(
1985 "Medical, dental, and vision insurance (100% premium coverage for employee and dependents)",
1986 0,
1987 );
1988 doc.add_bullet_list_item("401(k) retirement plan with 6% company match", 0);
1989 doc.add_bullet_list_item("Life insurance and long-term disability coverage", 0);
1990 doc.add_bullet_list_item("Annual professional development allowance of $10,000", 0);
1991
1992 {
1993 let mut p = doc.add_paragraph("");
1994 p.add_run("3.2 Paid Time Off. ").bold(true);
1995 p.add_run(
1996 "The Employee shall receive 25 days of paid vacation per year, plus Company holidays, \
1997 accruing on a monthly basis.",
1998 );
1999 }
2000
2001 // ── Article 4: Term and Termination ──
2002 doc.add_paragraph("Article 4 — Term and Termination")
2003 .style("Heading1");
2004 {
2005 let mut p = doc.add_paragraph("");
2006 p.add_run("4.1 At-Will Employment. ").bold(true);
2007 p.add_run(
2008 "This Agreement is for at-will employment and may be terminated by either Party \
2009 at any time, with or without cause, subject to the notice provisions herein.",
2010 );
2011 }
2012 {
2013 let mut p = doc.add_paragraph("");
2014 p.add_run("4.2 Notice Period. ").bold(true);
2015 p.add_run("Either Party shall provide ");
2016 p.add_run("ninety (90) days").bold(true);
2017 p.add_run(" written notice of termination, or pay in lieu thereof.");
2018 }
2019 {
2020 let mut p = doc.add_paragraph("");
2021 p.add_run("4.3 Severance. ").bold(true);
2022 p.add_run("In the event of termination by the Company without Cause, the Employee shall \
2023 receive (a) twelve (12) months of base salary continuation, (b) pro-rata bonus \
2024 for the year of termination, and (c) twelve (12) months of COBRA premium coverage.");
2025 }
2026
2027 // ── Article 5: Confidentiality ──
2028 doc.add_paragraph("Article 5 — Confidentiality and IP")
2029 .style("Heading1");
2030 {
2031 let mut p = doc.add_paragraph("");
2032 p.add_run("5.1 ").bold(true);
2033 p.add_run(
2034 "The Employee agrees to maintain strict confidentiality of all proprietary \
2035 information, trade secrets, and business plans of the Company during and after \
2036 employment.",
2037 );
2038 }
2039 {
2040 let mut p = doc.add_paragraph("");
2041 p.add_run("5.2 ").bold(true);
2042 p.add_run(
2043 "All intellectual property, inventions, and works of authorship created by the \
2044 Employee during the term of employment and within the scope of duties shall be \
2045 the sole and exclusive property of the Company.",
2046 );
2047 }
2048
2049 // ── Article 6: Non-Compete ──
2050 doc.add_paragraph("Article 6 — Non-Competition")
2051 .style("Heading1");
2052 {
2053 let mut p = doc.add_paragraph("");
2054 p.add_run("6.1 ").bold(true);
2055 p.add_run("For a period of twelve (12) months following termination, the Employee agrees \
2056 not to engage in any business that directly competes with the Company's core \
2057 business of AI infrastructure and ML operations services, within the United States.");
2058 }
2059
2060 // ── Article 7: General Provisions ──
2061 doc.add_paragraph("Article 7 — General Provisions")
2062 .style("Heading1");
2063 {
2064 let mut p = doc.add_paragraph("");
2065 p.add_run("7.1 Governing Law. ").bold(true);
2066 p.add_run(
2067 "This Agreement shall be governed by and construed in accordance with the laws of \
2068 the State of California.",
2069 );
2070 }
2071 {
2072 let mut p = doc.add_paragraph("");
2073 p.add_run("7.2 Entire Agreement. ").bold(true);
2074 p.add_run(
2075 "This Agreement constitutes the entire agreement between the Parties and supersedes \
2076 all prior negotiations, representations, and agreements.",
2077 );
2078 }
2079 {
2080 let mut p = doc.add_paragraph("");
2081 p.add_run("7.3 Amendment. ").bold(true);
2082 p.add_run(
2083 "This Agreement may only be amended by a written instrument signed by both Parties.",
2084 );
2085 }
2086
2087 // ── Signature Block ──
2088 doc.add_paragraph("").page_break_before(true);
2089 doc.add_paragraph("IN WITNESS WHEREOF, the Parties have executed this Employment Agreement as of the Effective Date.")
2090 .space_after(Length::pt(24.0));
2091
2092 // Signature table
2093 {
2094 let mut tbl = doc.add_table(6, 2).width_pct(100.0);
2095 tbl.cell(0, 0).unwrap().set_text("FOR THE COMPANY:");
2096 tbl.cell(0, 0).unwrap().shading("5B2C6F");
2097 tbl.cell(0, 1).unwrap().set_text("EMPLOYEE:");
2098 tbl.cell(0, 1).unwrap().shading("5B2C6F");
2099
2100 tbl.cell(1, 0).unwrap().set_text("");
2101 tbl.cell(1, 1).unwrap().set_text("");
2102 tbl.row(1).unwrap().height(Length::pt(40.0));
2103
2104 tbl.cell(2, 0)
2105 .unwrap()
2106 .set_text("Signature: ___________________________");
2107 tbl.cell(2, 1)
2108 .unwrap()
2109 .set_text("Signature: ___________________________");
2110 tbl.cell(3, 0)
2111 .unwrap()
2112 .set_text("Name: Board Representative");
2113 tbl.cell(3, 1).unwrap().set_text("Name: Walter White");
2114 tbl.cell(4, 0)
2115 .unwrap()
2116 .set_text("Title: Chair, Board of Directors");
2117 tbl.cell(4, 1)
2118 .unwrap()
2119 .set_text("Title: Chief Executive Officer");
2120 tbl.cell(5, 0).unwrap().set_text("Date: _______________");
2121 tbl.cell(5, 1).unwrap().set_text("Date: _______________");
2122 }
2123
2124 doc
2125}Set the default footer text.
Examples found in repository?
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}34fn generate_feature_showcase(path: &Path) {
35 let mut doc = Document::new();
36
37 // =========================================================================
38 // PAGE SETUP & METADATA
39 // =========================================================================
40 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
41 doc.set_margins(
42 Length::inches(1.0), // top
43 Length::inches(1.0), // right
44 Length::inches(1.0), // bottom
45 Length::inches(1.0), // left
46 );
47 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
48 doc.set_gutter(Length::twips(0));
49
50 doc.set_title("rdocx Feature Showcase");
51 doc.set_author("rdocx Sample Generator");
52 doc.set_subject("Comprehensive feature demonstration");
53 doc.set_keywords("rdocx, docx, rust, sample");
54
55 // Header & Footer
56 doc.set_header("rdocx Feature Showcase");
57 doc.set_footer("Generated by rdocx — Page");
58
59 // Different first page header
60 doc.set_different_first_page(true);
61 doc.set_first_page_header("rdocx");
62 doc.set_first_page_footer("Feature Showcase — Cover Page");
63
64 // =========================================================================
65 // PAGE 1: COVER PAGE — background image, run formatting
66 // =========================================================================
67 let bg_cover = create_sample_png(612, 792, [30, 60, 120]);
68 doc.add_background_image(&bg_cover, "cover_bg.png");
69
70 doc.add_paragraph(""); // spacer
71 doc.add_paragraph(""); // spacer
72 doc.add_paragraph(""); // spacer
73
74 {
75 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
76 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
77 }
78 {
79 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
80 p.add_run("Feature Showcase")
81 .size(28.0)
82 .color("FFFFFF")
83 .italic(true);
84 }
85
86 doc.add_paragraph(""); // spacer
87
88 {
89 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
90 p.add_run("A comprehensive demonstration of every feature")
91 .size(14.0)
92 .color("CCDDFF");
93 }
94 {
95 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
96 p.add_run("provided by the rdocx Rust crate for DOCX generation.")
97 .size(14.0)
98 .color("CCDDFF");
99 }
100
101 // =========================================================================
102 // PAGE 2: TEXT FORMATTING
103 // =========================================================================
104 doc.add_paragraph("").page_break_before(true);
105
106 doc.add_paragraph("1. Text Formatting").style("Heading1");
107
108 doc.add_paragraph("This section demonstrates paragraph and run-level formatting options.");
109 doc.add_paragraph("");
110
111 // --- Paragraph alignment ---
112 doc.add_paragraph("Paragraph Alignment").style("Heading2");
113
114 doc.add_paragraph("This paragraph is left-aligned (the default).")
115 .alignment(Alignment::Left);
116 doc.add_paragraph("This paragraph is center-aligned.")
117 .alignment(Alignment::Center);
118 doc.add_paragraph("This paragraph is right-aligned.")
119 .alignment(Alignment::Right);
120 doc.add_paragraph(
121 "This paragraph is justified. To demonstrate justified text properly, it needs \
122 to be long enough to span multiple lines so the word spacing adjustment is visible \
123 across the full width of the text area on the page.",
124 )
125 .alignment(Alignment::Justify);
126
127 doc.add_paragraph("");
128
129 // --- Run formatting ---
130 doc.add_paragraph("Run Formatting").style("Heading2");
131
132 {
133 let mut p = doc.add_paragraph("");
134 p.add_run("Bold text").bold(true);
135 p.add_run(" | ");
136 p.add_run("Italic text").italic(true);
137 p.add_run(" | ");
138 p.add_run("Bold + Italic").bold(true).italic(true);
139 }
140 {
141 let mut p = doc.add_paragraph("");
142 p.add_run("Single underline").underline(true);
143 p.add_run(" | ");
144 p.add_run("Strikethrough").strike(true);
145 p.add_run(" | ");
146 p.add_run("Double strikethrough").double_strike(true);
147 }
148 {
149 let mut p = doc.add_paragraph("");
150 p.add_run("Red text").color("FF0000");
151 p.add_run(" | ");
152 p.add_run("Blue text").color("0000FF");
153 p.add_run(" | ");
154 p.add_run("Green text").color("00AA00");
155 p.add_run(" | ");
156 p.add_run("Highlighted").highlight("FFFF00");
157 }
158 {
159 let mut p = doc.add_paragraph("");
160 p.add_run("8pt small").size(8.0);
161 p.add_run(" | ");
162 p.add_run("11pt normal").size(11.0);
163 p.add_run(" | ");
164 p.add_run("16pt large").size(16.0);
165 p.add_run(" | ");
166 p.add_run("24pt extra-large").size(24.0);
167 }
168 {
169 let mut p = doc.add_paragraph("");
170 p.add_run("Arial font").font("Arial");
171 p.add_run(" | ");
172 p.add_run("Times New Roman font").font("Times New Roman");
173 p.add_run(" | ");
174 p.add_run("Courier New font").font("Courier New");
175 }
176 {
177 let mut p = doc.add_paragraph("");
178 p.add_run("Normal");
179 p.add_run(" H").size(11.0);
180 p.add_run("2").subscript();
181 p.add_run("O (subscript)").size(11.0);
182 p.add_run(" | E = mc").size(11.0);
183 p.add_run("2").superscript();
184 p.add_run(" (superscript)").size(11.0);
185 }
186 {
187 let mut p = doc.add_paragraph("");
188 p.add_run("ALL CAPS").all_caps(true);
189 p.add_run(" | ");
190 p.add_run("Small Caps").small_caps(true);
191 p.add_run(" | ");
192 p.add_run("Expanded spacing")
193 .character_spacing(Length::twips(40));
194 }
195
196 doc.add_paragraph("");
197
198 // --- Paragraph formatting ---
199 doc.add_paragraph("Paragraph Formatting").style("Heading2");
200
201 doc.add_paragraph("Paragraph with shading (light green background)")
202 .shading("E2EFDA");
203
204 doc.add_paragraph("Paragraph with bottom border")
205 .border_bottom(BorderStyle::Single, 6, "2E75B6");
206
207 doc.add_paragraph("Paragraph with all borders")
208 .border_all(BorderStyle::Single, 4, "FF0000");
209
210 doc.add_paragraph("Paragraph with 1-inch left indent and hanging indent")
211 .indent_left(Length::inches(1.0))
212 .hanging_indent(Length::inches(0.5));
213
214 doc.add_paragraph("Paragraph with first-line indent of 0.5 inches")
215 .first_line_indent(Length::inches(0.5));
216
217 doc.add_paragraph("Paragraph with extra space before (24pt) and after (12pt)")
218 .space_before(Length::pt(24.0))
219 .space_after(Length::pt(12.0));
220
221 doc.add_paragraph(
222 "Paragraph with double line spacing. This text should have extra vertical \
223 space between lines to demonstrate the line_spacing_multiple setting.",
224 )
225 .line_spacing_multiple(2.0);
226
227 doc.add_paragraph("Paragraph with keep-with-next (won't break from the next paragraph)")
228 .keep_with_next(true);
229 doc.add_paragraph("(This stays with the paragraph above.)");
230
231 // =========================================================================
232 // PAGE 3: LISTS & TAB STOPS
233 // =========================================================================
234 doc.add_paragraph("").page_break_before(true);
235
236 doc.add_paragraph("2. Lists").style("Heading1");
237
238 doc.add_paragraph("Bullet List").style("Heading2");
239
240 doc.add_bullet_list_item("First bullet item", 0);
241 doc.add_bullet_list_item("Second bullet item", 0);
242 doc.add_bullet_list_item("Nested level 1", 1);
243 doc.add_bullet_list_item("Nested level 2", 2);
244 doc.add_bullet_list_item("Back to level 1", 1);
245 doc.add_bullet_list_item("Third bullet item", 0);
246
247 doc.add_paragraph("");
248
249 doc.add_paragraph("Numbered List").style("Heading2");
250
251 doc.add_numbered_list_item("First numbered item", 0);
252 doc.add_numbered_list_item("Second numbered item", 0);
253 doc.add_numbered_list_item("Sub-item A", 1);
254 doc.add_numbered_list_item("Sub-item B", 1);
255 doc.add_numbered_list_item("Third numbered item", 0);
256
257 doc.add_paragraph("");
258
259 // --- Tab stops ---
260 doc.add_paragraph("Tab Stops").style("Heading2");
261
262 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
263 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
264 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
265 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
266 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
267
268 doc.add_paragraph("Item\t........\tPrice")
269 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
270 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
271 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
272
273 doc.add_paragraph("Widget A\t........\t$19.99")
274 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
275 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
276 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
277
278 doc.add_paragraph("Gadget B\t________\t$249.50")
279 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
280 .add_tab_stop_with_leader(
281 TabAlignment::Right,
282 Length::inches(4.0),
283 TabLeader::Underscore,
284 )
285 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
286
287 // =========================================================================
288 // PAGE 4: TABLES
289 // =========================================================================
290 doc.add_paragraph("").page_break_before(true);
291
292 doc.add_paragraph("3. Tables").style("Heading1");
293
294 // --- Basic table with borders ---
295 doc.add_paragraph("Basic Table with Borders")
296 .style("Heading2");
297
298 {
299 let mut tbl = doc.add_table(4, 3);
300 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
301
302 // Header row
303 for col in 0..3 {
304 tbl.cell(0, col).unwrap().shading("2E75B6");
305 }
306 tbl.cell(0, 0).unwrap().set_text("Name");
307 tbl.cell(0, 1).unwrap().set_text("Role");
308 tbl.cell(0, 2).unwrap().set_text("Location");
309
310 tbl.cell(1, 0).unwrap().set_text("Alice Johnson");
311 tbl.cell(1, 1).unwrap().set_text("Engineering Lead");
312 tbl.cell(1, 2).unwrap().set_text("New York");
313
314 tbl.cell(2, 0).unwrap().set_text("Bob Smith");
315 tbl.cell(2, 1).unwrap().set_text("Product Manager");
316 tbl.cell(2, 2).unwrap().set_text("San Francisco");
317
318 tbl.cell(3, 0).unwrap().set_text("Carol Davis");
319 tbl.cell(3, 1).unwrap().set_text("Designer");
320 tbl.cell(3, 2).unwrap().set_text("London");
321 }
322
323 doc.add_paragraph("");
324
325 // --- Table with cell merging ---
326 doc.add_paragraph("Table with Cell Merging & Vertical Alignment")
327 .style("Heading2");
328
329 {
330 let mut tbl = doc.add_table(4, 4);
331 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
332 tbl = tbl.width_pct(100.0);
333
334 // Header spanning all columns
335 tbl.cell(0, 0).unwrap().set_text("Quarterly Revenue Report");
336 tbl.cell(0, 0).unwrap().shading("1F4E79");
337 tbl.cell(0, 0).unwrap().grid_span(4);
338
339 // Sub-header
340 tbl.cell(1, 0).unwrap().set_text("Region");
341 tbl.cell(1, 0).unwrap().shading("D6E4F0");
342 tbl.cell(1, 1).unwrap().set_text("Q1");
343 tbl.cell(1, 1).unwrap().shading("D6E4F0");
344 tbl.cell(1, 2).unwrap().set_text("Q2");
345 tbl.cell(1, 2).unwrap().shading("D6E4F0");
346 tbl.cell(1, 3).unwrap().set_text("Total");
347 tbl.cell(1, 3).unwrap().shading("D6E4F0");
348
349 // Data
350 tbl.cell(2, 0).unwrap().set_text("North America");
351 tbl.cell(2, 1).unwrap().set_text("$2.4M");
352 tbl.cell(2, 2).unwrap().set_text("$2.7M");
353 tbl.cell(2, 3).unwrap().set_text("$5.1M");
354
355 tbl.cell(3, 0).unwrap().set_text("Europe");
356 tbl.cell(3, 1).unwrap().set_text("$1.8M");
357 tbl.cell(3, 2).unwrap().set_text("$2.0M");
358 tbl.cell(3, 3).unwrap().set_text("$3.8M");
359
360 // Vertical alignment on data cells
361 tbl.cell(2, 3)
362 .unwrap()
363 .vertical_alignment(VerticalAlignment::Center);
364 tbl.cell(3, 3)
365 .unwrap()
366 .vertical_alignment(VerticalAlignment::Bottom);
367 }
368
369 doc.add_paragraph("");
370
371 // --- Table with vertical merge ---
372 doc.add_paragraph("Table with Vertical Merge")
373 .style("Heading2");
374
375 {
376 let mut tbl = doc.add_table(4, 3);
377 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
378
379 tbl.cell(0, 0).unwrap().set_text("Category");
380 tbl.cell(0, 0).unwrap().shading("E2EFDA");
381 tbl.cell(0, 1).unwrap().set_text("Item");
382 tbl.cell(0, 1).unwrap().shading("E2EFDA");
383 tbl.cell(0, 2).unwrap().set_text("Price");
384 tbl.cell(0, 2).unwrap().shading("E2EFDA");
385
386 // "Hardware" spans rows 1-2
387 tbl.cell(1, 0).unwrap().set_text("Hardware");
388 tbl.cell(1, 0).unwrap().v_merge_restart();
389 tbl.cell(1, 1).unwrap().set_text("Laptop");
390 tbl.cell(1, 2).unwrap().set_text("$1,200");
391
392 tbl.cell(2, 0).unwrap().v_merge_continue();
393 tbl.cell(2, 1).unwrap().set_text("Monitor");
394 tbl.cell(2, 2).unwrap().set_text("$450");
395
396 // "Software" on row 3
397 tbl.cell(3, 0).unwrap().set_text("Software");
398 tbl.cell(3, 1).unwrap().set_text("IDE License");
399 tbl.cell(3, 2).unwrap().set_text("$200/yr");
400 }
401
402 doc.add_paragraph("");
403
404 // --- Nested table ---
405 doc.add_paragraph("Nested Table").style("Heading2");
406
407 {
408 let mut tbl = doc.add_table(2, 2);
409 tbl = tbl.borders(BorderStyle::Single, 6, "2E75B6");
410
411 tbl.cell(0, 0).unwrap().set_text("Outer Cell (0,0)");
412 tbl.cell(0, 1).unwrap().set_text("Outer Cell (0,1)");
413 tbl.cell(1, 0).unwrap().set_text("Outer Cell (1,0)");
414
415 // Nested table inside cell (1,1)
416 {
417 let mut cell = tbl.cell(1, 1).unwrap();
418 cell.set_text("Contains nested table:");
419 let mut nested = cell.add_table(2, 2);
420 nested = nested.borders(BorderStyle::Single, 2, "FF6600");
421 nested.cell(0, 0).unwrap().set_text("Inner A");
422 nested.cell(0, 1).unwrap().set_text("Inner B");
423 nested.cell(1, 0).unwrap().set_text("Inner C");
424 nested.cell(1, 1).unwrap().set_text("Inner D");
425 }
426 }
427
428 // =========================================================================
429 // PAGE 5: IMAGES
430 // =========================================================================
431 doc.add_paragraph("").page_break_before(true);
432
433 doc.add_paragraph("4. Images").style("Heading1");
434
435 doc.add_paragraph("Inline Image").style("Heading2");
436
437 doc.add_paragraph("Below is an inline image (200x50 pixels, blue gradient):");
438 let inline_img = create_sample_png(200, 50, [0, 80, 200]);
439 doc.add_picture(
440 &inline_img,
441 "inline_chart.png",
442 Length::inches(3.0),
443 Length::inches(0.75),
444 );
445
446 doc.add_paragraph("");
447
448 doc.add_paragraph("Header Image").style("Heading2");
449
450 // Replace the text-only header with an image header
451 let header_img = create_sample_png(400, 40, [40, 40, 40]);
452 doc.set_header_image(
453 &header_img,
454 "header_logo.png",
455 Length::inches(2.0),
456 Length::inches(0.2),
457 );
458
459 doc.add_paragraph(
460 "The document header has been replaced with an inline image. \
461 Check the header area at the top of this page.",
462 );
463
464 doc.add_paragraph("");
465 doc.add_paragraph(
466 "Note: The cover page uses a full-page background image behind the text, \
467 demonstrated on page 1 via add_background_image().",
468 );
469
470 // =========================================================================
471 // PAGE 6: CONTENT MANIPULATION — placeholder replacement, insertion
472 // =========================================================================
473 doc.add_paragraph("").page_break_before(true);
474
475 doc.add_paragraph("5. Content Manipulation")
476 .style("Heading1");
477
478 // --- Placeholder replacement ---
479 doc.add_paragraph("Placeholder Replacement")
480 .style("Heading2");
481
482 doc.add_paragraph(
483 "Before replacement, this document contained {{customer}} and {{date}} placeholders.",
484 );
485
486 {
487 let mut p = doc.add_paragraph("");
488 p.add_run("Customer: ").bold(true);
489 p.add_run("{{customer}}");
490 }
491 {
492 let mut p = doc.add_paragraph("");
493 p.add_run("Date: ").bold(true);
494 p.add_run("{{date}}");
495 }
496 doc.add_paragraph("Reference: {{ref_number}}");
497
498 // Table with placeholders
499 {
500 let mut tbl = doc.add_table(3, 2);
501 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
502 tbl.cell(0, 0).unwrap().set_text("Field");
503 tbl.cell(0, 0).unwrap().shading("D6E4F0");
504 tbl.cell(0, 1).unwrap().set_text("Value");
505 tbl.cell(0, 1).unwrap().shading("D6E4F0");
506 tbl.cell(1, 0).unwrap().set_text("Project");
507 tbl.cell(1, 1).unwrap().set_text("{{project}}");
508 tbl.cell(2, 0).unwrap().set_text("Status");
509 tbl.cell(2, 1).unwrap().set_text("{{status}}");
510 }
511
512 // Perform replacements
513 let mut replacements = HashMap::new();
514 replacements.insert("{{customer}}", "Acme Corporation");
515 replacements.insert("{{date}}", "February 22, 2026");
516 replacements.insert("{{ref_number}}", "REF-2026-001");
517 replacements.insert("{{project}}", "Infrastructure Upgrade");
518 replacements.insert("{{status}}", "In Progress");
519 let replace_count = doc.replace_all(&replacements);
520
521 doc.add_paragraph("");
522 doc.add_paragraph(&format!(
523 "(Replaced {} placeholders above — in body text and table cells)",
524 replace_count
525 ));
526
527 doc.add_paragraph("");
528
529 // --- Content insertion ---
530 doc.add_paragraph("Content Insertion").style("Heading2");
531
532 doc.add_paragraph("Section A: First section of content.");
533 doc.add_paragraph("Section C: Third section of content.");
534
535 // Insert "Section B" between A and C
536 if let Some(idx) = doc.find_content_index("Section C") {
537 doc.insert_paragraph(
538 idx,
539 "Section B: Inserted between A and C using find_content_index().",
540 );
541 }
542
543 doc.add_paragraph("");
544 doc.add_paragraph(
545 "The paragraph above ('Section B') was inserted at a specific position \
546 using find_content_index() + insert_paragraph().",
547 );
548
549 // =========================================================================
550 // PAGE 7: LANDSCAPE — section break, wide table
551 // =========================================================================
552 doc.add_paragraph("").section_break(SectionBreak::NextPage);
553
554 doc.add_paragraph("6. Mixed Page Orientation")
555 .style("Heading1");
556
557 doc.add_paragraph(
558 "This page is in LANDSCAPE orientation. It was created using a section break \
559 followed by section_landscape(). This is useful for wide tables or charts.",
560 );
561
562 doc.add_paragraph("");
563
564 // Wide table for landscape
565 {
566 let mut tbl = doc.add_table(4, 7);
567 tbl = tbl.borders(BorderStyle::Single, 4, "2E75B6");
568
569 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
570 for (col, h) in headers.iter().enumerate() {
571 tbl.cell(0, col).unwrap().set_text(h);
572 tbl.cell(0, col).unwrap().shading("2E75B6");
573 }
574
575 let data = [
576 [
577 "North America",
578 "$1.2M",
579 "$1.3M",
580 "$1.4M",
581 "$1.5M",
582 "$1.6M",
583 "$7.0M",
584 ],
585 [
586 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
587 ],
588 [
589 "Asia Pacific",
590 "$0.5M",
591 "$0.6M",
592 "$0.7M",
593 "$0.7M",
594 "$0.8M",
595 "$3.3M",
596 ],
597 ];
598 for (row_idx, row_data) in data.iter().enumerate() {
599 for (col, val) in row_data.iter().enumerate() {
600 tbl.cell(row_idx + 1, col).unwrap().set_text(val);
601 }
602 }
603 }
604
605 // End landscape, return to portrait
606 doc.add_paragraph("")
607 .section_break(SectionBreak::NextPage)
608 .section_landscape();
609
610 // =========================================================================
611 // PAGE 8: BACK TO PORTRAIT — styles, final notes
612 // =========================================================================
613 doc.add_paragraph("7. Custom Styles & Summary")
614 .style("Heading1");
615
616 doc.add_paragraph(
617 "This final page is back in portrait orientation after a section break. \
618 The document has demonstrated:",
619 );
620
621 doc.add_bullet_list_item(
622 "Page setup: size, margins, header/footer distance, gutter",
623 0,
624 );
625 doc.add_bullet_list_item("Document metadata: title, author, subject, keywords", 0);
626 doc.add_bullet_list_item("Headers and footers: text, images, different first page", 0);
627 doc.add_bullet_list_item("Background images: full-page behind text", 0);
628 doc.add_bullet_list_item(
629 "Text formatting: bold, italic, underline, strike, color, size, font",
630 0,
631 );
632 doc.add_bullet_list_item(
633 "Advanced run formatting: superscript, subscript, caps, spacing",
634 0,
635 );
636 doc.add_bullet_list_item(
637 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
638 0,
639 );
640 doc.add_bullet_list_item("Bullet and numbered lists with nesting levels", 0);
641 doc.add_bullet_list_item("Tab stops with dot/underscore leaders", 0);
642 doc.add_bullet_list_item(
643 "Tables: borders, shading, column spans, row spans, nesting",
644 0,
645 );
646 doc.add_bullet_list_item("Vertical alignment in table cells", 0);
647 doc.add_bullet_list_item("Inline images", 0);
648 doc.add_bullet_list_item("Placeholder replacement in body and table cells", 0);
649 doc.add_bullet_list_item("Content insertion at specific positions", 0);
650 doc.add_bullet_list_item(
651 "Section breaks with mixed portrait/landscape orientation",
652 0,
653 );
654
655 doc.add_paragraph("");
656 doc.add_paragraph("All features above were built entirely from scratch using the rdocx API.")
657 .alignment(Alignment::Center)
658 .shading("E2EFDA")
659 .border_all(BorderStyle::Single, 2, "00AA00");
660
661 doc.save(path).unwrap();
662}86fn generate_feature_showcase(_samples_dir: &Path) -> Document {
87 let mut doc = Document::new();
88
89 // ── Page Setup & Metadata ──
90 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
91 doc.set_margins(
92 Length::inches(1.0),
93 Length::inches(1.0),
94 Length::inches(1.0),
95 Length::inches(1.0),
96 );
97 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
98 doc.set_gutter(Length::twips(0));
99 doc.set_title("rdocx Feature Showcase");
100 doc.set_author("rdocx Sample Generator");
101 doc.set_subject("Comprehensive feature demonstration");
102 doc.set_keywords("rdocx, docx, rust, sample, showcase");
103
104 // Headers & Footers with different first page
105 doc.set_different_first_page(true);
106 doc.set_first_page_header("rdocx");
107 doc.set_first_page_footer("Feature Showcase — Cover Page");
108 doc.set_header("rdocx Feature Showcase");
109 doc.set_footer("Generated by rdocx");
110
111 // ── COVER PAGE ──
112 let bg = create_sample_png(612, 792, [20, 45, 90]);
113 doc.add_background_image(&bg, "cover_bg.png");
114
115 for _ in 0..3 {
116 doc.add_paragraph("");
117 }
118 {
119 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
120 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
121 }
122 {
123 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
124 p.add_run("Complete Feature Showcase")
125 .size(28.0)
126 .color("FFFFFF")
127 .italic(true);
128 }
129 doc.add_paragraph("");
130 {
131 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
132 p.add_run("Every feature of the rdocx Rust library")
133 .size(13.0)
134 .color("B0C4DE");
135 }
136 {
137 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
138 p.add_run("demonstrated in a single document.")
139 .size(13.0)
140 .color("B0C4DE");
141 }
142
143 // ── TABLE OF CONTENTS ──
144 doc.add_paragraph("").page_break_before(true);
145 doc.insert_toc(doc.content_count(), 3);
146
147 // ── SECTION 1: TEXT FORMATTING ──
148 doc.add_paragraph("").page_break_before(true);
149 doc.add_paragraph("1. Text Formatting").style("Heading1");
150
151 // Paragraph Alignment
152 doc.add_paragraph("1.1 Paragraph Alignment")
153 .style("Heading2");
154 doc.add_paragraph("Left-aligned paragraph (default).")
155 .alignment(Alignment::Left);
156 doc.add_paragraph("Center-aligned paragraph.")
157 .alignment(Alignment::Center);
158 doc.add_paragraph("Right-aligned paragraph.")
159 .alignment(Alignment::Right);
160 doc.add_paragraph(
161 "Justified paragraph — this text is long enough to demonstrate how justified alignment \
162 distributes extra space across word gaps so lines fill the full width of the text area.",
163 )
164 .alignment(Alignment::Justify);
165
166 doc.add_paragraph("");
167
168 // Run Formatting
169 doc.add_paragraph("1.2 Run Formatting").style("Heading2");
170 {
171 let mut p = doc.add_paragraph("");
172 p.add_run("Bold").bold(true);
173 p.add_run(" | ");
174 p.add_run("Italic").italic(true);
175 p.add_run(" | ");
176 p.add_run("Bold Italic").bold(true).italic(true);
177 p.add_run(" | ");
178 p.add_run("Underline").underline(true);
179 p.add_run(" | ");
180 p.add_run("Strikethrough").strike(true);
181 p.add_run(" | ");
182 p.add_run("Double Strike").double_strike(true);
183 }
184 {
185 let mut p = doc.add_paragraph("");
186 p.add_run("Red").color("FF0000");
187 p.add_run(" | ");
188 p.add_run("Blue").color("0000FF");
189 p.add_run(" | ");
190 p.add_run("Green").color("00AA00");
191 p.add_run(" | ");
192 p.add_run("Highlighted").highlight("FFFF00");
193 }
194 {
195 let mut p = doc.add_paragraph("");
196 p.add_run("8pt").size(8.0);
197 p.add_run(" | ");
198 p.add_run("11pt").size(11.0);
199 p.add_run(" | ");
200 p.add_run("16pt").size(16.0);
201 p.add_run(" | ");
202 p.add_run("24pt").size(24.0);
203 }
204 {
205 let mut p = doc.add_paragraph("");
206 p.add_run("Arial").font("Arial");
207 p.add_run(" | ");
208 p.add_run("Times New Roman").font("Times New Roman");
209 p.add_run(" | ");
210 p.add_run("Courier New").font("Courier New");
211 }
212 {
213 let mut p = doc.add_paragraph("");
214 p.add_run("H");
215 p.add_run("2").subscript();
216 p.add_run("O (subscript) | E=mc");
217 p.add_run("2").superscript();
218 p.add_run(" (superscript)");
219 }
220 {
221 let mut p = doc.add_paragraph("");
222 p.add_run("ALL CAPS").all_caps(true);
223 p.add_run(" | ");
224 p.add_run("Small Caps").small_caps(true);
225 p.add_run(" | ");
226 p.add_run("Expanded").character_spacing(Length::twips(40));
227 p.add_run(" | ");
228 p.add_run("Hidden text (not visible)").hidden(true);
229 }
230
231 doc.add_paragraph("");
232
233 // Underline Styles
234 doc.add_paragraph("1.3 Underline Styles").style("Heading2");
235 {
236 let mut p = doc.add_paragraph("");
237 p.add_run("Single ")
238 .underline_style(rdocx::UnderlineStyle::Single);
239 p.add_run("Double ")
240 .underline_style(rdocx::UnderlineStyle::Double);
241 p.add_run("Thick ")
242 .underline_style(rdocx::UnderlineStyle::Thick);
243 p.add_run("Dotted ")
244 .underline_style(rdocx::UnderlineStyle::Dotted);
245 p.add_run("Dash ")
246 .underline_style(rdocx::UnderlineStyle::Dash);
247 p.add_run("Wave ")
248 .underline_style(rdocx::UnderlineStyle::Wave);
249 }
250
251 // ── SECTION 2: PARAGRAPH FORMATTING ──
252 doc.add_paragraph("").page_break_before(true);
253 doc.add_paragraph("2. Paragraph Formatting")
254 .style("Heading1");
255
256 doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
257 doc.add_paragraph("Paragraph with light green shading")
258 .shading("E2EFDA");
259 doc.add_paragraph("Paragraph with bottom border")
260 .border_bottom(BorderStyle::Single, 6, "2E75B6");
261 doc.add_paragraph("Paragraph with all borders (red)")
262 .border_all(BorderStyle::Single, 4, "FF0000");
263
264 doc.add_paragraph("2.2 Indentation").style("Heading2");
265 doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
266 .indent_left(Length::inches(1.0))
267 .hanging_indent(Length::inches(0.5));
268 doc.add_paragraph("0.5-inch first-line indent on this paragraph")
269 .first_line_indent(Length::inches(0.5));
270 doc.add_paragraph("Both left and right indent (0.75 inches each)")
271 .indent_left(Length::inches(0.75))
272 .indent_right(Length::inches(0.75));
273
274 doc.add_paragraph("2.3 Spacing & Line Height")
275 .style("Heading2");
276 doc.add_paragraph("Extra space before (24pt) and after (12pt)")
277 .space_before(Length::pt(24.0))
278 .space_after(Length::pt(12.0));
279 doc.add_paragraph(
280 "Double line spacing paragraph. This text should have extra vertical space between \
281 lines to demonstrate line_spacing_multiple(2.0).",
282 )
283 .line_spacing_multiple(2.0);
284 doc.add_paragraph("Exact 20pt line spacing (fixed height).")
285 .line_spacing(20.0);
286
287 doc.add_paragraph("2.4 Pagination Controls")
288 .style("Heading2");
289 doc.add_paragraph("keep_with_next — this paragraph stays with the next")
290 .keep_with_next(true);
291 doc.add_paragraph("(This paragraph was kept with the one above.)");
292 doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
293 .keep_together(true);
294 doc.add_paragraph("widow_control — prevents widow/orphan lines")
295 .widow_control(true);
296
297 // ── SECTION 3: LISTS ──
298 doc.add_paragraph("").page_break_before(true);
299 doc.add_paragraph("3. Lists").style("Heading1");
300
301 doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
302 doc.add_bullet_list_item("First item", 0);
303 doc.add_bullet_list_item("Second item", 0);
304 doc.add_bullet_list_item("Nested level 1", 1);
305 doc.add_bullet_list_item("Nested level 2", 2);
306 doc.add_bullet_list_item("Back to level 1", 1);
307 doc.add_bullet_list_item("Third item", 0);
308
309 doc.add_paragraph("");
310
311 doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
312 doc.add_numbered_list_item("First numbered item", 0);
313 doc.add_numbered_list_item("Second numbered item", 0);
314 doc.add_numbered_list_item("Sub-item A", 1);
315 doc.add_numbered_list_item("Sub-item B", 1);
316 doc.add_numbered_list_item("Third numbered item", 0);
317
318 // ── SECTION 4: TAB STOPS ──
319 doc.add_paragraph("");
320 doc.add_paragraph("4. Tab Stops").style("Heading1");
321
322 doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
323 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
324 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
325 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
326 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
327 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
328
329 doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
330 doc.add_paragraph("Item\t\tPrice")
331 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
332 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
333 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
334 doc.add_paragraph("Widget\t\t$19.99")
335 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
336 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
337 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
338 doc.add_paragraph("Gadget\t\t$249.50")
339 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
340 .add_tab_stop_with_leader(
341 TabAlignment::Right,
342 Length::inches(4.0),
343 TabLeader::Underscore,
344 )
345 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
346
347 // ── SECTION 5: TABLES ──
348 doc.add_paragraph("").page_break_before(true);
349 doc.add_paragraph("5. Tables").style("Heading1");
350
351 doc.add_paragraph("5.1 Basic Table").style("Heading2");
352 {
353 let mut tbl = doc
354 .add_table(4, 3)
355 .borders(BorderStyle::Single, 4, "000000");
356 for col in 0..3 {
357 tbl.cell(0, col).unwrap().shading("2E75B6");
358 }
359 tbl.cell(0, 0).unwrap().set_text("Name");
360 tbl.cell(0, 1).unwrap().set_text("Role");
361 tbl.cell(0, 2).unwrap().set_text("Location");
362 tbl.cell(1, 0).unwrap().set_text("Walter White");
363 tbl.cell(1, 1).unwrap().set_text("CEO");
364 tbl.cell(1, 2).unwrap().set_text("Albuquerque");
365 tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
366 tbl.cell(2, 1).unwrap().set_text("CTO");
367 tbl.cell(2, 2).unwrap().set_text("Remote");
368 tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
369 tbl.cell(3, 1).unwrap().set_text("Security");
370 tbl.cell(3, 2).unwrap().set_text("Washington");
371 }
372
373 doc.add_paragraph("");
374
375 doc.add_paragraph("5.2 Column Span & Cell Shading")
376 .style("Heading2");
377 {
378 let mut tbl = doc
379 .add_table(3, 4)
380 .borders(BorderStyle::Single, 4, "000000")
381 .width_pct(100.0);
382 tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
383 tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
384 tbl.cell(1, 0).unwrap().set_text("Region");
385 tbl.cell(1, 0).unwrap().shading("D6E4F0");
386 tbl.cell(1, 1).unwrap().set_text("Q1");
387 tbl.cell(1, 1).unwrap().shading("D6E4F0");
388 tbl.cell(1, 2).unwrap().set_text("Q2");
389 tbl.cell(1, 2).unwrap().shading("D6E4F0");
390 tbl.cell(1, 3).unwrap().set_text("Total");
391 tbl.cell(1, 3).unwrap().shading("D6E4F0");
392 tbl.cell(2, 0).unwrap().set_text("Americas");
393 tbl.cell(2, 1).unwrap().set_text("$2.4M");
394 tbl.cell(2, 2).unwrap().set_text("$2.7M");
395 tbl.cell(2, 3).unwrap().set_text("$5.1M");
396 }
397
398 doc.add_paragraph("");
399
400 doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
401 {
402 let mut tbl = doc
403 .add_table(4, 3)
404 .borders(BorderStyle::Single, 4, "000000");
405 tbl.cell(0, 0).unwrap().set_text("Category");
406 tbl.cell(0, 0).unwrap().shading("E2EFDA");
407 tbl.cell(0, 1).unwrap().set_text("Item");
408 tbl.cell(0, 1).unwrap().shading("E2EFDA");
409 tbl.cell(0, 2).unwrap().set_text("Price");
410 tbl.cell(0, 2).unwrap().shading("E2EFDA");
411 tbl.cell(1, 0).unwrap().set_text("Hardware");
412 tbl.cell(1, 0).unwrap().v_merge_restart();
413 tbl.cell(1, 1).unwrap().set_text("Laptop");
414 tbl.cell(1, 2).unwrap().set_text("$1,200");
415 tbl.cell(2, 0).unwrap().v_merge_continue();
416 tbl.cell(2, 1).unwrap().set_text("Monitor");
417 tbl.cell(2, 2).unwrap().set_text("$450");
418 tbl.cell(3, 0).unwrap().set_text("Software");
419 tbl.cell(3, 1).unwrap().set_text("IDE License");
420 tbl.cell(3, 2).unwrap().set_text("$200/yr");
421 }
422
423 doc.add_paragraph("");
424
425 doc.add_paragraph("5.4 Nested Table").style("Heading2");
426 {
427 let mut tbl = doc
428 .add_table(2, 2)
429 .borders(BorderStyle::Single, 6, "2E75B6");
430 tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
431 tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
432 tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
433 {
434 let mut cell = tbl.cell(1, 1).unwrap();
435 cell.set_text("Nested table below:");
436 let mut nested = cell
437 .add_table(2, 2)
438 .borders(BorderStyle::Single, 2, "FF6600");
439 nested.cell(0, 0).unwrap().set_text("A");
440 nested.cell(0, 1).unwrap().set_text("B");
441 nested.cell(1, 0).unwrap().set_text("C");
442 nested.cell(1, 1).unwrap().set_text("D");
443 }
444 }
445
446 doc.add_paragraph("");
447
448 doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
449 .style("Heading2");
450 {
451 let mut tbl = doc
452 .add_table(2, 3)
453 .borders(BorderStyle::Single, 4, "666666");
454 tbl.row(0).unwrap().height(Length::pt(50.0)).header();
455 tbl.cell(0, 0).unwrap().set_text("Top");
456 tbl.cell(0, 0)
457 .unwrap()
458 .vertical_alignment(VerticalAlignment::Top)
459 .shading("FFF2CC");
460 tbl.cell(0, 1).unwrap().set_text("Center");
461 tbl.cell(0, 1)
462 .unwrap()
463 .vertical_alignment(VerticalAlignment::Center)
464 .shading("D9E2F3");
465 tbl.cell(0, 2).unwrap().set_text("Bottom");
466 tbl.cell(0, 2)
467 .unwrap()
468 .vertical_alignment(VerticalAlignment::Bottom)
469 .shading("E2EFDA");
470 tbl.row(1).unwrap().cant_split();
471 tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
472 tbl.cell(1, 0).unwrap().no_wrap();
473 tbl.cell(1, 1).unwrap().set_text("Fixed width");
474 tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
475 tbl.cell(1, 2).unwrap().set_text("Normal");
476 }
477
478 // ── SECTION 6: IMAGES ──
479 doc.add_paragraph("").page_break_before(true);
480 doc.add_paragraph("6. Images").style("Heading1");
481
482 doc.add_paragraph("6.1 Inline Image").style("Heading2");
483 doc.add_paragraph("A blue gradient image below:");
484 let img = create_sample_png(200, 50, [0, 80, 200]);
485 doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
486
487 doc.add_paragraph("");
488 doc.add_paragraph("6.2 Header Image").style("Heading2");
489 let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
490 doc.set_header_image(
491 &hdr_img,
492 "header_logo.png",
493 Length::inches(2.0),
494 Length::inches(0.2),
495 );
496 doc.add_paragraph("The header now contains an inline image (check the top of this page).");
497
498 doc.add_paragraph("");
499 doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
500
501 // ── SECTION 7: CONTENT MANIPULATION ──
502 doc.add_paragraph("").page_break_before(true);
503 doc.add_paragraph("7. Content Manipulation")
504 .style("Heading1");
505
506 doc.add_paragraph("7.1 Placeholder Replacement")
507 .style("Heading2");
508 doc.add_paragraph("Customer: {{customer}}");
509 doc.add_paragraph("Date: {{date}}");
510 doc.add_paragraph("Reference: {{ref_number}}");
511 {
512 let mut tbl = doc
513 .add_table(2, 2)
514 .borders(BorderStyle::Single, 4, "000000");
515 tbl.cell(0, 0).unwrap().set_text("Project");
516 tbl.cell(0, 0).unwrap().shading("D6E4F0");
517 tbl.cell(0, 1).unwrap().set_text("{{project}}");
518 tbl.cell(1, 0).unwrap().set_text("Status");
519 tbl.cell(1, 0).unwrap().shading("D6E4F0");
520 tbl.cell(1, 1).unwrap().set_text("{{status}}");
521 }
522
523 let mut replacements = HashMap::new();
524 replacements.insert("{{customer}}", "Tensorbee Inc.");
525 replacements.insert("{{date}}", "February 22, 2026");
526 replacements.insert("{{ref_number}}", "TB-2026-001");
527 replacements.insert("{{project}}", "Infrastructure Upgrade");
528 replacements.insert("{{status}}", "In Progress");
529 let n = doc.replace_all(&replacements);
530 doc.add_paragraph(&format!("({n} placeholders replaced above)"));
531
532 doc.add_paragraph("");
533
534 doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
535 doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
536 let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
537 doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
538
539 doc.add_paragraph("");
540
541 doc.add_paragraph("7.3 Content Insertion").style("Heading2");
542 doc.add_paragraph("Section A: First content.");
543 doc.add_paragraph("Section C: Third content.");
544 if let Some(idx) = doc.find_content_index("Section C") {
545 doc.insert_paragraph(
546 idx,
547 "Section B: Inserted between A and C via find_content_index().",
548 );
549 }
550
551 // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
552 doc.add_paragraph("").section_break(SectionBreak::NextPage);
553 doc.add_paragraph("8. Section Breaks & Orientation")
554 .style("Heading1");
555 doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
556
557 {
558 let mut tbl = doc
559 .add_table(3, 7)
560 .borders(BorderStyle::Single, 4, "2E75B6");
561 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
562 for (c, h) in headers.iter().enumerate() {
563 tbl.cell(0, c).unwrap().set_text(h);
564 tbl.cell(0, c).unwrap().shading("2E75B6");
565 }
566 let data = [
567 [
568 "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
569 ],
570 [
571 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
572 ],
573 ];
574 for (r, row) in data.iter().enumerate() {
575 for (c, v) in row.iter().enumerate() {
576 tbl.cell(r + 1, c).unwrap().set_text(v);
577 }
578 }
579 }
580
581 doc.add_paragraph("")
582 .section_break(SectionBreak::NextPage)
583 .section_landscape();
584
585 // ── SECTION 9: CUSTOM STYLES ──
586 doc.add_paragraph("9. Custom Styles").style("Heading1");
587 doc.add_style(
588 StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
589 .based_on("Normal")
590 .paragraph_properties({
591 let mut ppr = rdocx_oxml::properties::CT_PPr::default();
592 ppr.shading = Some(rdocx_oxml::properties::CT_Shd {
593 val: "clear".to_string(),
594 color: None,
595 fill: Some("FFF2CC".to_string()),
596 });
597 ppr
598 })
599 .run_properties({
600 let mut rpr = rdocx_oxml::properties::CT_RPr::default();
601 rpr.bold = Some(true);
602 rpr.color = Some("C45911".to_string());
603 rpr
604 }),
605 );
606 doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
607 .style("CustomHighlight");
608
609 doc.add_paragraph("");
610
611 // ── SECTION 10: DOCUMENT INTELLIGENCE ──
612 doc.add_paragraph("10. Document Intelligence API")
613 .style("Heading1");
614
615 let wc = doc.word_count();
616 let hc = doc.headings().len();
617 let ic = doc.images().len();
618 let lc = doc.links().len();
619 doc.add_paragraph(&format!("Word count: {wc}"));
620 doc.add_paragraph(&format!("Heading count: {hc}"));
621 doc.add_paragraph(&format!("Image count: {ic}"));
622 doc.add_paragraph(&format!("Link count: {lc}"));
623
624 let outline = doc.document_outline();
625 doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
626
627 let issues = doc.audit_accessibility();
628 doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
629 for issue in &issues {
630 doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
631 }
632
633 // ── SECTION 11: DOCUMENT MERGING ──
634 doc.add_paragraph("");
635 doc.add_paragraph("11. Document Merging").style("Heading1");
636 {
637 let mut other = Document::new();
638 other.add_paragraph("This paragraph was merged from another document using append().");
639 doc.append(&other);
640 }
641
642 doc.add_paragraph("");
643
644 // ── SUMMARY ──
645 doc.add_paragraph("Summary of Demonstrated Features")
646 .style("Heading1");
647 let features = [
648 "Page setup: size, margins, header/footer distance, gutter",
649 "Document metadata: title, author, subject, keywords",
650 "Headers and footers: text, images, different first page",
651 "Background images (full-page behind text)",
652 "Table of Contents generation with bookmarks",
653 "Text formatting: bold, italic, underline styles, strike, color, size, font",
654 "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
655 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
656 "Pagination controls: keep-with-next, keep-together, widow control",
657 "Bullet and numbered lists with nesting",
658 "Tab stops with dot/underscore/hyphen leaders",
659 "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
660 "Row properties: header rows, exact height, cant-split",
661 "Cell properties: width, no-wrap, vertical alignment",
662 "Inline images and header images",
663 "Placeholder replacement (single and batch)",
664 "Regex-based find and replace",
665 "Content insertion at specific positions",
666 "Section breaks with mixed portrait/landscape",
667 "Custom paragraph and character styles",
668 "Document intelligence: word count, headings, outline, images, links",
669 "Accessibility audit",
670 "Document merging (append)",
671 "Export: DOCX, PDF, HTML, Markdown",
672 ];
673 for f in &features {
674 doc.add_bullet_list_item(f, 0);
675 }
676 doc.add_paragraph("");
677 doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
678 .alignment(Alignment::Center)
679 .shading("E2EFDA")
680 .border_all(BorderStyle::Single, 2, "00AA00");
681
682 doc
683}
684
685// =============================================================================
686// 2. PROPOSAL DOCUMENT — Deep navy + gold scheme
687// =============================================================================
688fn generate_proposal(_samples_dir: &Path) -> Document {
689 let mut doc = Document::new();
690
691 // Colors: Navy #1B2A4A, Gold #C5922E, Light #F4F1EB
692 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
693 doc.set_margins(
694 Length::inches(1.0),
695 Length::inches(1.0),
696 Length::inches(1.0),
697 Length::inches(1.0),
698 );
699 doc.set_title("AI Platform Modernization Proposal");
700 doc.set_author("Walter White");
701 doc.set_subject("Technology Proposal");
702 doc.set_keywords("proposal, AI, modernization, Tensorbee");
703
704 // Banner header
705 let logo_img = create_logo_png(220, 48);
706 let banner = build_header_banner_xml(
707 "rId1",
708 &BannerOpts {
709 bg_color: "1B2A4A",
710 banner_width: 7772400,
711 banner_height: 914400,
712 logo_width: 2011680,
713 logo_height: 438912,
714 logo_x_offset: 295125,
715 logo_y_offset: 237744,
716 },
717 );
718 doc.set_raw_header_with_images(
719 banner,
720 &[("rId1", &logo_img, "logo.png")],
721 rdocx_oxml::header_footer::HdrFtrType::Default,
722 );
723 doc.set_different_first_page(true);
724 let cover_banner = build_header_banner_xml(
725 "rId1",
726 &BannerOpts {
727 bg_color: "C5922E",
728 banner_width: 7772400,
729 banner_height: 914400,
730 logo_width: 2011680,
731 logo_height: 438912,
732 logo_x_offset: 295125,
733 logo_y_offset: 237744,
734 },
735 );
736 doc.set_raw_header_with_images(
737 cover_banner,
738 &[("rId1", &logo_img, "logo.png")],
739 rdocx_oxml::header_footer::HdrFtrType::First,
740 );
741 doc.set_margins(
742 Length::twips(2292),
743 Length::twips(1440),
744 Length::twips(1440),
745 Length::twips(1440),
746 );
747 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
748 doc.set_footer("Tensorbee — Confidential");
749
750 // Custom styles
751 doc.add_style(
752 StyleBuilder::paragraph("ProposalTitle", "Proposal Title")
753 .based_on("Normal")
754 .run_properties({
755 let mut rpr = rdocx_oxml::properties::CT_RPr::default();
756 rpr.bold = Some(true);
757 rpr.font_ascii = Some("Georgia".to_string());
758 rpr.font_hansi = Some("Georgia".to_string());
759 rpr.sz = Some(rdocx_oxml::HalfPoint(56)); // half-points → 28pt
760 rpr.color = Some("1B2A4A".to_string());
761 rpr
762 }),
763 );
764
765 // ── Cover Page ──
766 for _ in 0..4 {
767 doc.add_paragraph("");
768 }
769 doc.add_paragraph("AI Platform Modernization")
770 .style("ProposalTitle")
771 .alignment(Alignment::Center);
772 doc.add_paragraph("")
773 .alignment(Alignment::Center)
774 .border_bottom(BorderStyle::Single, 8, "C5922E");
775 {
776 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
777 p.add_run("Prepared for Global Financial Corp.")
778 .size(14.0)
779 .color("666666")
780 .italic(true);
781 }
782 {
783 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
784 p.add_run("Prepared by: Walter White, CEO — Tensorbee")
785 .size(12.0)
786 .color("888888");
787 }
788 {
789 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
790 p.add_run("February 22, 2026").size(12.0).color("888888");
791 }
792
793 // ── TOC ──
794 doc.add_paragraph("").page_break_before(true);
795 doc.add_paragraph("Table of Contents").style("Heading1");
796 // We'll add a manual-style TOC since the insert_toc goes at a specific position
797 doc.insert_toc(doc.content_count(), 2);
798
799 // ── Executive Summary ──
800 doc.add_paragraph("").page_break_before(true);
801 doc.add_paragraph("Executive Summary").style("Heading1");
802 doc.add_paragraph(
803 "Tensorbee proposes a comprehensive modernization of Global Financial Corp's AI platform, \
804 transitioning from legacy batch-processing systems to a real-time inference architecture. \
805 This transformation will reduce model deployment time from weeks to hours, improve \
806 prediction accuracy by an estimated 23%, and deliver $4.2M in annual operational savings.",
807 )
808 .first_line_indent(Length::inches(0.3));
809 doc.add_paragraph(
810 "The project spans three phases over 18 months, with a total investment of $2.8M. \
811 Tensorbee brings deep expertise in ML infrastructure, having successfully completed \
812 similar transformations for three Fortune 500 financial institutions.",
813 )
814 .first_line_indent(Length::inches(0.3));
815
816 // Key metrics table
817 doc.add_paragraph("");
818 {
819 let mut tbl = doc
820 .add_table(5, 2)
821 .borders(BorderStyle::Single, 4, "1B2A4A")
822 .width_pct(80.0);
823 tbl.cell(0, 0).unwrap().set_text("Key Metric");
824 tbl.cell(0, 0).unwrap().shading("1B2A4A");
825 tbl.cell(0, 1).unwrap().set_text("Value");
826 tbl.cell(0, 1).unwrap().shading("1B2A4A");
827 let rows = [
828 ("Total Investment", "$2.8M"),
829 ("Annual Savings", "$4.2M"),
830 ("ROI (Year 1)", "150%"),
831 ("Timeline", "18 months"),
832 ];
833 for (i, (k, v)) in rows.iter().enumerate() {
834 tbl.cell(i + 1, 0).unwrap().set_text(k);
835 if i % 2 == 0 {
836 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
837 }
838 tbl.cell(i + 1, 1).unwrap().set_text(v);
839 if i % 2 == 0 {
840 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
841 }
842 }
843 }
844
845 // ── Problem Statement ──
846 doc.add_paragraph("").page_break_before(true);
847 doc.add_paragraph("Problem Statement").style("Heading1");
848 doc.add_paragraph(
849 "Global Financial Corp's current AI infrastructure faces three critical challenges:",
850 );
851 doc.add_numbered_list_item("Deployment Latency — New models take 3-4 weeks to deploy to production, compared to the industry benchmark of 2-3 days.", 0);
852 doc.add_numbered_list_item("Scalability Constraints — The batch processing architecture cannot handle real-time inference demands during peak trading hours.", 0);
853 doc.add_numbered_list_item("Technical Debt — Legacy Python 2.7 codebases and manual deployment scripts create reliability risks and slow iteration speed.", 0);
854
855 // ── Proposed Solution ──
856 doc.add_paragraph("").page_break_before(true);
857 doc.add_paragraph("Proposed Solution").style("Heading1");
858
859 doc.add_paragraph("Phase 1: Foundation (Months 1-6)")
860 .style("Heading2");
861 doc.add_bullet_list_item("Deploy Kubernetes-based ML serving infrastructure", 0);
862 doc.add_bullet_list_item("Implement CI/CD pipeline for model deployment", 0);
863 doc.add_bullet_list_item("Migrate top 5 production models to new platform", 0);
864
865 doc.add_paragraph("Phase 2: Expansion (Months 7-12)")
866 .style("Heading2");
867 doc.add_bullet_list_item(
868 "Real-time feature engineering pipeline (Apache Kafka + Flink)",
869 0,
870 );
871 doc.add_bullet_list_item("A/B testing framework for model variants", 0);
872 doc.add_bullet_list_item("Automated model monitoring and drift detection", 0);
873
874 doc.add_paragraph("Phase 3: Optimization (Months 13-18)")
875 .style("Heading2");
876 doc.add_bullet_list_item("GPU inference optimization (TensorRT/ONNX Runtime)", 0);
877 doc.add_bullet_list_item("Multi-region deployment for latency reduction", 0);
878 doc.add_bullet_list_item(
879 "Self-service model deployment portal for data science teams",
880 0,
881 );
882
883 // ── Budget ──
884 doc.add_paragraph("Budget Breakdown").style("Heading1");
885 {
886 let mut tbl = doc
887 .add_table(6, 3)
888 .borders(BorderStyle::Single, 4, "1B2A4A")
889 .width_pct(100.0);
890 tbl.cell(0, 0).unwrap().set_text("Category");
891 tbl.cell(0, 0).unwrap().shading("1B2A4A");
892 tbl.cell(0, 1).unwrap().set_text("Cost");
893 tbl.cell(0, 1).unwrap().shading("1B2A4A");
894 tbl.cell(0, 2).unwrap().set_text("Notes");
895 tbl.cell(0, 2).unwrap().shading("1B2A4A");
896 let items = [
897 (
898 "Infrastructure",
899 "$450,000",
900 "Cloud compute + GPU instances",
901 ),
902 ("Engineering Services", "$1,600,000", "12 FTE x 18 months"),
903 (
904 "Software Licenses",
905 "$350,000",
906 "Kafka, monitoring, CI/CD tools",
907 ),
908 (
909 "Training & Enablement",
910 "$200,000",
911 "Team training, documentation",
912 ),
913 ("Contingency (10%)", "$200,000", "Risk buffer"),
914 ];
915 for (i, (cat, cost, note)) in items.iter().enumerate() {
916 tbl.cell(i + 1, 0).unwrap().set_text(cat);
917 tbl.cell(i + 1, 1).unwrap().set_text(cost);
918 tbl.cell(i + 1, 2).unwrap().set_text(note);
919 if i % 2 == 0 {
920 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
921 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
922 tbl.cell(i + 1, 2).unwrap().shading("F4F1EB");
923 }
924 }
925 }
926
927 // ── Closing ──
928 doc.add_paragraph("");
929 doc.add_paragraph("Next Steps").style("Heading1");
930 doc.add_numbered_list_item(
931 "Schedule technical deep-dive with GFC engineering team (Week 1)",
932 0,
933 );
934 doc.add_numbered_list_item("Finalize scope and sign SOW (Week 2-3)", 0);
935 doc.add_numbered_list_item("Kick off Phase 1 with joint planning session (Week 4)", 0);
936
937 doc.add_paragraph("");
938 {
939 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
940 p.add_run("Walter White")
941 .bold(true)
942 .size(14.0)
943 .color("1B2A4A");
944 }
945 {
946 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
947 p.add_run("CEO, Tensorbee | walter@tensorbee.com")
948 .size(10.0)
949 .color("888888");
950 }
951
952 doc
953}
954
955// =============================================================================
956// 3. QUOTE / BILL OF MATERIALS — Teal + orange scheme
957// =============================================================================
958fn generate_quote(_samples_dir: &Path) -> Document {
959 let mut doc = Document::new();
960
961 // Colors: Teal #008B8B, Dark #1A3C3C, Orange #E07020, Light #F0F8F8
962 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
963 doc.set_margins(
964 Length::inches(0.75),
965 Length::inches(0.75),
966 Length::inches(0.75),
967 Length::inches(0.75),
968 );
969 doc.set_title("Quotation QT-2026-0147");
970 doc.set_author("Walter White");
971 doc.set_keywords("quote, BOM, Tensorbee");
972
973 doc.set_header("Tensorbee — Quotation");
974 doc.set_footer("QT-2026-0147 | Page");
975
976 // ── Header Block ──
977 {
978 let mut p = doc.add_paragraph("").alignment(Alignment::Left);
979 p.add_run("TENSORBEE")
980 .bold(true)
981 .size(28.0)
982 .color("008B8B")
983 .font("Helvetica");
984 }
985 doc.add_paragraph("123 Innovation Drive, Suite 400")
986 .alignment(Alignment::Left);
987 doc.add_paragraph("San Francisco, CA 94105 | +1 (415) 555-0199")
988 .alignment(Alignment::Left);
989 doc.add_paragraph("")
990 .border_bottom(BorderStyle::Single, 8, "008B8B");
991
992 // Quote meta
993 doc.add_paragraph("");
994 {
995 let mut tbl = doc.add_table(4, 4).width_pct(100.0);
996 tbl.cell(0, 0).unwrap().set_text("QUOTATION");
997 tbl.cell(0, 0).unwrap().shading("008B8B").grid_span(2);
998 tbl.cell(0, 2).unwrap().set_text("DATE");
999 tbl.cell(0, 2).unwrap().shading("008B8B");
1000 tbl.cell(0, 3).unwrap().set_text("VALID UNTIL");
1001 tbl.cell(0, 3).unwrap().shading("008B8B");
1002 tbl.cell(1, 0).unwrap().set_text("Quote #:");
1003 tbl.cell(1, 1).unwrap().set_text("QT-2026-0147");
1004 tbl.cell(1, 2).unwrap().set_text("Feb 22, 2026");
1005 tbl.cell(1, 3).unwrap().set_text("Mar 22, 2026");
1006 tbl.cell(2, 0).unwrap().set_text("Prepared by:");
1007 tbl.cell(2, 1).unwrap().set_text("Walter White");
1008 tbl.cell(2, 2).unwrap().set_text("Payment:");
1009 tbl.cell(2, 3).unwrap().set_text("Net 30");
1010 tbl.cell(3, 0).unwrap().set_text("Customer:");
1011 tbl.cell(3, 1).unwrap().set_text("Meridian Dynamics LLC");
1012 tbl.cell(3, 2).unwrap().set_text("Currency:");
1013 tbl.cell(3, 3).unwrap().set_text("USD");
1014 }
1015
1016 doc.add_paragraph("");
1017
1018 // ── Bill of Materials ──
1019 {
1020 let mut p = doc.add_paragraph("");
1021 p.add_run("Bill of Materials")
1022 .bold(true)
1023 .size(16.0)
1024 .color("1A3C3C");
1025 }
1026 doc.add_paragraph("");
1027
1028 {
1029 let mut tbl = doc
1030 .add_table(10, 6)
1031 .borders(BorderStyle::Single, 2, "008B8B")
1032 .width_pct(100.0)
1033 .layout_fixed();
1034 // Headers
1035 let hdrs = [
1036 "#",
1037 "Part Number",
1038 "Description",
1039 "Qty",
1040 "Unit Price",
1041 "Total",
1042 ];
1043 for (c, h) in hdrs.iter().enumerate() {
1044 tbl.cell(0, c).unwrap().set_text(h);
1045 tbl.cell(0, c).unwrap().shading("008B8B");
1046 }
1047
1048 let items: Vec<(&str, &str, &str, &str, &str)> = vec![
1049 (
1050 "TB-GPU-A100",
1051 "NVIDIA A100 80GB GPU",
1052 "4",
1053 "$12,500.00",
1054 "$50,000.00",
1055 ),
1056 (
1057 "TB-SRV-R750",
1058 "Dell R750xa Server Chassis",
1059 "2",
1060 "$8,200.00",
1061 "$16,400.00",
1062 ),
1063 (
1064 "TB-RAM-256G",
1065 "256GB DDR5 ECC Memory Module",
1066 "8",
1067 "$890.00",
1068 "$7,120.00",
1069 ),
1070 (
1071 "TB-SSD-3840",
1072 "3.84TB NVMe U.2 SSD",
1073 "8",
1074 "$1,150.00",
1075 "$9,200.00",
1076 ),
1077 (
1078 "TB-NET-CX7",
1079 "ConnectX-7 200GbE NIC",
1080 "4",
1081 "$1,800.00",
1082 "$7,200.00",
1083 ),
1084 (
1085 "TB-SW-48P",
1086 "48-Port 100GbE Switch",
1087 "1",
1088 "$22,000.00",
1089 "$22,000.00",
1090 ),
1091 (
1092 "TB-CAB-RACK",
1093 "42U Server Rack + PDU",
1094 "1",
1095 "$4,500.00",
1096 "$4,500.00",
1097 ),
1098 (
1099 "TB-SVC-INST",
1100 "Installation & Configuration",
1101 "1",
1102 "$8,500.00",
1103 "$8,500.00",
1104 ),
1105 (
1106 "TB-SVC-SUPP",
1107 "3-Year Premium Support",
1108 "1",
1109 "$15,000.00",
1110 "$15,000.00",
1111 ),
1112 ];
1113
1114 for (i, (pn, desc, qty, unit, total)) in items.iter().enumerate() {
1115 let row = i + 1;
1116 tbl.cell(row, 0).unwrap().set_text(&format!("{}", i + 1));
1117 tbl.cell(row, 1).unwrap().set_text(pn);
1118 tbl.cell(row, 2).unwrap().set_text(desc);
1119 tbl.cell(row, 3).unwrap().set_text(qty);
1120 tbl.cell(row, 4).unwrap().set_text(unit);
1121 tbl.cell(row, 5).unwrap().set_text(total);
1122 if i % 2 == 0 {
1123 for c in 0..6 {
1124 tbl.cell(row, c).unwrap().shading("F0F8F8");
1125 }
1126 }
1127 }
1128 }
1129
1130 doc.add_paragraph("");
1131
1132 // ── Totals ──
1133 {
1134 let mut tbl = doc
1135 .add_table(4, 2)
1136 .borders(BorderStyle::Single, 2, "008B8B")
1137 .width(Length::inches(3.5))
1138 .alignment(Alignment::Right);
1139 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1140 tbl.cell(0, 1).unwrap().set_text("$139,920.00");
1141 tbl.cell(1, 0).unwrap().set_text("Shipping & Handling");
1142 tbl.cell(1, 1).unwrap().set_text("$2,500.00");
1143 tbl.cell(2, 0).unwrap().set_text("Tax (8.625%)");
1144 tbl.cell(2, 1).unwrap().set_text("$12,068.10");
1145 tbl.cell(3, 0).unwrap().set_text("TOTAL");
1146 tbl.cell(3, 0).unwrap().shading("E07020");
1147 tbl.cell(3, 1).unwrap().set_text("$154,488.10");
1148 tbl.cell(3, 1).unwrap().shading("E07020");
1149 }
1150
1151 doc.add_paragraph("");
1152
1153 // ── Terms & Conditions ──
1154 {
1155 let mut p = doc.add_paragraph("");
1156 p.add_run("Terms & Conditions")
1157 .bold(true)
1158 .size(14.0)
1159 .color("1A3C3C");
1160 }
1161 doc.add_numbered_list_item(
1162 "This quotation is valid for 30 calendar days from the date of issue.",
1163 0,
1164 );
1165 doc.add_numbered_list_item(
1166 "All prices are in USD and exclusive of applicable taxes unless stated.",
1167 0,
1168 );
1169 doc.add_numbered_list_item("Standard lead time is 4-6 weeks from PO receipt.", 0);
1170 doc.add_numbered_list_item(
1171 "Payment terms: Net 30 from invoice date. 2% discount for payment within 10 days.",
1172 0,
1173 );
1174 doc.add_numbered_list_item(
1175 "Warranty: 3-year manufacturer warranty on all hardware components.",
1176 0,
1177 );
1178 doc.add_numbered_list_item(
1179 "Returns subject to 15% restocking fee if initiated after 14 days.",
1180 0,
1181 );
1182
1183 doc.add_paragraph("");
1184 doc.add_paragraph("")
1185 .border_bottom(BorderStyle::Single, 4, "008B8B");
1186 doc.add_paragraph("");
1187 {
1188 let mut p = doc.add_paragraph("");
1189 p.add_run("Accepted by: ").bold(true);
1190 p.add_run("___________________________ Date: ___________");
1191 }
1192 {
1193 let mut p = doc.add_paragraph("");
1194 p.add_run("Print Name: ").bold(true);
1195 p.add_run("___________________________ Title: ___________");
1196 }
1197
1198 doc
1199}
1200
1201// =============================================================================
1202// 4. INVOICE — Crimson + charcoal scheme
1203// =============================================================================
1204fn generate_invoice(_samples_dir: &Path) -> Document {
1205 let mut doc = Document::new();
1206
1207 // Colors: Crimson #B22222, Charcoal #333333, Light #FAF0F0
1208 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1209 doc.set_margins(
1210 Length::inches(0.75),
1211 Length::inches(0.75),
1212 Length::inches(0.75),
1213 Length::inches(0.75),
1214 );
1215 doc.set_title("Invoice INV-2026-0392");
1216 doc.set_author("Walter White");
1217 doc.set_keywords("invoice, Tensorbee");
1218
1219 doc.set_footer("Tensorbee — Thank you for your business!");
1220
1221 // ── Company & Invoice Header ──
1222 {
1223 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1224 // Company name left, INVOICE right
1225 {
1226 let mut cell = tbl.cell(0, 0).unwrap();
1227 let mut p = cell.add_paragraph("");
1228 p.add_run("TENSORBEE")
1229 .bold(true)
1230 .size(32.0)
1231 .color("B22222")
1232 .font("Helvetica");
1233 }
1234 {
1235 let mut cell = tbl.cell(0, 1).unwrap();
1236 let mut p = cell.add_paragraph("");
1237 p.add_run("INVOICE").bold(true).size(32.0).color("333333");
1238 }
1239 tbl.cell(1, 0)
1240 .unwrap()
1241 .set_text("123 Innovation Drive, Suite 400");
1242 tbl.cell(1, 1).unwrap().set_text("Invoice #: INV-2026-0392");
1243 tbl.cell(2, 0).unwrap().set_text("San Francisco, CA 94105");
1244 tbl.cell(2, 1).unwrap().set_text("Date: February 22, 2026");
1245 tbl.cell(3, 0).unwrap().set_text("walter@tensorbee.com");
1246 tbl.cell(3, 1).unwrap().set_text("Due Date: March 24, 2026");
1247 }
1248
1249 doc.add_paragraph("")
1250 .border_bottom(BorderStyle::Thick, 8, "B22222");
1251 doc.add_paragraph("");
1252
1253 // ── Bill To / Ship To ──
1254 {
1255 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1256 tbl.cell(0, 0).unwrap().set_text("BILL TO");
1257 tbl.cell(0, 0).unwrap().shading("B22222");
1258 tbl.cell(0, 1).unwrap().set_text("SHIP TO");
1259 tbl.cell(0, 1).unwrap().shading("B22222");
1260 tbl.cell(1, 0).unwrap().set_text("Meridian Dynamics LLC");
1261 tbl.cell(1, 1).unwrap().set_text("Meridian Dynamics LLC");
1262 tbl.cell(2, 0).unwrap().set_text("456 Enterprise Blvd");
1263 tbl.cell(2, 1).unwrap().set_text("Attn: Server Room B");
1264 tbl.cell(3, 0).unwrap().set_text("Austin, TX 78701");
1265 tbl.cell(3, 1)
1266 .unwrap()
1267 .set_text("456 Enterprise Blvd, Austin, TX 78701");
1268 }
1269
1270 doc.add_paragraph("");
1271
1272 // ── Line Items ──
1273 {
1274 let mut tbl = doc
1275 .add_table(8, 5)
1276 .borders(BorderStyle::Single, 2, "B22222")
1277 .width_pct(100.0);
1278 let hdrs = ["Description", "Qty", "Unit Price", "Tax", "Amount"];
1279 for (c, h) in hdrs.iter().enumerate() {
1280 tbl.cell(0, c).unwrap().set_text(h);
1281 tbl.cell(0, c).unwrap().shading("B22222");
1282 }
1283 let items = [
1284 (
1285 "ML Infrastructure Setup — Phase 1",
1286 "1",
1287 "$45,000.00",
1288 "$3,881.25",
1289 "$48,881.25",
1290 ),
1291 (
1292 "Data Pipeline Development (160 hrs)",
1293 "160",
1294 "$225.00",
1295 "$3,105.00",
1296 "$39,105.00",
1297 ),
1298 (
1299 "GPU Cluster Configuration",
1300 "1",
1301 "$12,000.00",
1302 "$1,035.00",
1303 "$13,035.00",
1304 ),
1305 (
1306 "API Gateway Implementation",
1307 "1",
1308 "$18,500.00",
1309 "$1,595.63",
1310 "$20,095.63",
1311 ),
1312 (
1313 "Load Testing & QA (80 hrs)",
1314 "80",
1315 "$195.00",
1316 "$1,345.50",
1317 "$16,945.50",
1318 ),
1319 (
1320 "Documentation & Training",
1321 "1",
1322 "$8,000.00",
1323 "$690.00",
1324 "$8,690.00",
1325 ),
1326 (
1327 "Project Management (3 months)",
1328 "3",
1329 "$6,500.00",
1330 "$1,679.63",
1331 "$21,179.63",
1332 ),
1333 ];
1334 for (i, (desc, qty, unit, tax, amt)) in items.iter().enumerate() {
1335 let r = i + 1;
1336 tbl.cell(r, 0).unwrap().set_text(desc);
1337 tbl.cell(r, 1).unwrap().set_text(qty);
1338 tbl.cell(r, 2).unwrap().set_text(unit);
1339 tbl.cell(r, 3).unwrap().set_text(tax);
1340 tbl.cell(r, 4).unwrap().set_text(amt);
1341 if i % 2 == 0 {
1342 for c in 0..5 {
1343 tbl.cell(r, c).unwrap().shading("FAF0F0");
1344 }
1345 }
1346 }
1347 }
1348
1349 doc.add_paragraph("");
1350
1351 // ── Totals ──
1352 {
1353 let mut tbl = doc
1354 .add_table(4, 2)
1355 .borders(BorderStyle::Single, 2, "B22222")
1356 .width(Length::inches(3.0))
1357 .alignment(Alignment::Right);
1358 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1359 tbl.cell(0, 1).unwrap().set_text("$154,600.00");
1360 tbl.cell(1, 0).unwrap().set_text("Tax (8.625%)");
1361 tbl.cell(1, 1).unwrap().set_text("$13,334.25");
1362 tbl.cell(2, 0).unwrap().set_text("Discount (5%)");
1363 tbl.cell(2, 1).unwrap().set_text("-$7,730.00");
1364 tbl.cell(3, 0).unwrap().set_text("AMOUNT DUE");
1365 tbl.cell(3, 0).unwrap().shading("B22222");
1366 tbl.cell(3, 1).unwrap().set_text("$160,204.25");
1367 tbl.cell(3, 1).unwrap().shading("B22222");
1368 }
1369
1370 doc.add_paragraph("");
1371 doc.add_paragraph("");
1372
1373 // ── Payment Details ──
1374 {
1375 let mut p = doc.add_paragraph("");
1376 p.add_run("Payment Details")
1377 .bold(true)
1378 .size(14.0)
1379 .color("333333");
1380 }
1381 doc.add_paragraph("Bank: Silicon Valley Bank")
1382 .indent_left(Length::inches(0.3));
1383 doc.add_paragraph("Account: Tensorbee Inc. — 0847-2953-1120")
1384 .indent_left(Length::inches(0.3));
1385 doc.add_paragraph("Routing: 121140399")
1386 .indent_left(Length::inches(0.3));
1387 doc.add_paragraph("Swift: SVBKUS6S")
1388 .indent_left(Length::inches(0.3));
1389
1390 doc.add_paragraph("");
1391
1392 doc.add_paragraph("Please include invoice number INV-2026-0392 in the payment reference.")
1393 .shading("FAF0F0")
1394 .border_all(BorderStyle::Single, 2, "B22222");
1395
1396 doc
1397}
1398
1399// =============================================================================
1400// 5. REPORT — Forest green + earth tones, with images & hierarchical sections
1401// =============================================================================
1402fn generate_report(_samples_dir: &Path) -> Document {
1403 let mut doc = Document::new();
1404
1405 // Colors: Forest #2D5016, Sage #6B8E23, Earth #8B7355, Cream #FFFAF0
1406 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1407 doc.set_margins(
1408 Length::inches(1.0),
1409 Length::inches(1.0),
1410 Length::inches(1.0),
1411 Length::inches(1.0),
1412 );
1413 doc.set_title("Q4 2025 Environmental Impact Report");
1414 doc.set_author("Walter White");
1415 doc.set_subject("Quarterly Environmental Report");
1416 doc.set_keywords("environment, sustainability, report, Tensorbee");
1417
1418 doc.set_different_first_page(true);
1419 doc.set_first_page_header("");
1420 doc.set_header("Tensorbee — Q4 2025 Environmental Impact Report");
1421 doc.set_footer("CONFIDENTIAL — Page");
1422
1423 // ── Cover ──
1424 let cover_bg = create_sample_png(612, 792, [20, 50, 15]);
1425 doc.add_background_image(&cover_bg, "report_cover.png");
1426
1427 for _ in 0..5 {
1428 doc.add_paragraph("");
1429 }
1430 {
1431 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1432 p.add_run("Q4 2025")
1433 .bold(true)
1434 .size(48.0)
1435 .color("FFFFFF")
1436 .font("Georgia");
1437 }
1438 {
1439 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1440 p.add_run("Environmental Impact Report")
1441 .size(24.0)
1442 .color("90EE90")
1443 .font("Georgia")
1444 .italic(true);
1445 }
1446 doc.add_paragraph("");
1447 {
1448 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1449 p.add_run("Tensorbee — Sustainability Division")
1450 .size(14.0)
1451 .color("C0C0C0");
1452 }
1453 {
1454 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1455 p.add_run("Prepared by Walter White, Chief Sustainability Officer")
1456 .size(11.0)
1457 .color("AAAAAA");
1458 }
1459
1460 // ── TOC ──
1461 doc.add_paragraph("").page_break_before(true);
1462 doc.insert_toc(doc.content_count(), 3);
1463
1464 // ── Section 1: Executive Overview ──
1465 doc.add_paragraph("").page_break_before(true);
1466 doc.add_paragraph("Executive Overview").style("Heading1");
1467 doc.add_paragraph(
1468 "This report presents Tensorbee's environmental performance for Q4 2025. Our \
1469 sustainability initiatives have yielded a 34% reduction in carbon emissions compared \
1470 to Q4 2024, exceeding our target of 25%. Key achievements include the transition to \
1471 100% renewable energy in our primary data centers and the launch of our carbon offset \
1472 marketplace.",
1473 )
1474 .first_line_indent(Length::inches(0.3));
1475
1476 // Image: performance chart
1477 let chart_img = create_chart_png(400, 200);
1478 doc.add_paragraph("");
1479 doc.add_picture(
1480 &chart_img,
1481 "performance_chart.png",
1482 Length::inches(5.0),
1483 Length::inches(2.5),
1484 )
1485 .alignment(Alignment::Center);
1486 {
1487 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1488 p.add_run("Figure 1: Quarterly Carbon Emissions (tonnes CO2e)")
1489 .italic(true)
1490 .size(9.0)
1491 .color("666666");
1492 }
1493
1494 // ── Section 2: Energy Consumption ──
1495 doc.add_paragraph("").page_break_before(true);
1496 doc.add_paragraph("Energy Consumption").style("Heading1");
1497
1498 doc.add_paragraph("Data Center Operations")
1499 .style("Heading2");
1500 doc.add_paragraph(
1501 "Our three primary data centers consumed a combined 4.2 GWh in Q4 2025, \
1502 a 12% reduction from Q3 2025 driven by improved cooling efficiency and \
1503 server consolidation.",
1504 );
1505
1506 // Data table
1507 {
1508 let mut tbl = doc
1509 .add_table(5, 4)
1510 .borders(BorderStyle::Single, 4, "2D5016")
1511 .width_pct(100.0);
1512 let hdrs = ["Data Center", "Capacity (MW)", "Usage (GWh)", "PUE"];
1513 for (c, h) in hdrs.iter().enumerate() {
1514 tbl.cell(0, c).unwrap().set_text(h);
1515 tbl.cell(0, c).unwrap().shading("2D5016");
1516 }
1517 let rows = [
1518 ("San Francisco (Primary)", "3.2", "1.8", "1.12"),
1519 ("Dublin (EU)", "2.1", "1.2", "1.18"),
1520 ("Singapore (APAC)", "1.8", "1.2", "1.24"),
1521 ("TOTAL", "7.1", "4.2", "1.17 avg"),
1522 ];
1523 for (i, (dc, cap, use_, pue)) in rows.iter().enumerate() {
1524 tbl.cell(i + 1, 0).unwrap().set_text(dc);
1525 tbl.cell(i + 1, 1).unwrap().set_text(cap);
1526 tbl.cell(i + 1, 2).unwrap().set_text(use_);
1527 tbl.cell(i + 1, 3).unwrap().set_text(pue);
1528 if i == 3 {
1529 for c in 0..4 {
1530 tbl.cell(i + 1, c).unwrap().shading("E2EFDA");
1531 }
1532 }
1533 }
1534 }
1535
1536 doc.add_paragraph("");
1537
1538 doc.add_paragraph("Renewable Energy Mix").style("Heading2");
1539 doc.add_paragraph("Breakdown of energy sources across all facilities:");
1540
1541 doc.add_bullet_list_item("Solar PV: 42% (1.76 GWh)", 0);
1542 doc.add_bullet_list_item("Wind Power Purchase Agreements: 38% (1.60 GWh)", 0);
1543 doc.add_bullet_list_item("Hydroelectric: 12% (0.50 GWh)", 0);
1544 doc.add_bullet_list_item("Grid (non-renewable): 8% (0.34 GWh)", 0);
1545
1546 // ── Section 3: Water & Waste ──
1547 doc.add_paragraph("Water & Waste Management")
1548 .style("Heading1");
1549
1550 doc.add_paragraph("Water Usage").style("Heading2");
1551 doc.add_paragraph(
1552 "Total water consumption was 12.4 million gallons, a 15% reduction from Q3. \
1553 Our closed-loop cooling systems now recycle 78% of water used in cooling operations.",
1554 );
1555
1556 doc.add_paragraph("Waste Reduction").style("Heading2");
1557 doc.add_paragraph("E-waste management results for Q4:")
1558 .keep_with_next(true);
1559 {
1560 let mut tbl = doc
1561 .add_table(4, 3)
1562 .borders(BorderStyle::Single, 2, "6B8E23")
1563 .width_pct(80.0);
1564 tbl.cell(0, 0).unwrap().set_text("Category");
1565 tbl.cell(0, 0).unwrap().shading("6B8E23");
1566 tbl.cell(0, 1).unwrap().set_text("Weight (kg)");
1567 tbl.cell(0, 1).unwrap().shading("6B8E23");
1568 tbl.cell(0, 2).unwrap().set_text("Recycled %");
1569 tbl.cell(0, 2).unwrap().shading("6B8E23");
1570 let rows = [
1571 ("Server Hardware", "2,450", "94%"),
1572 ("Networking Equipment", "820", "91%"),
1573 ("Storage Media", "340", "99%"),
1574 ];
1575 for (i, (cat, wt, pct)) in rows.iter().enumerate() {
1576 tbl.cell(i + 1, 0).unwrap().set_text(cat);
1577 tbl.cell(i + 1, 1).unwrap().set_text(wt);
1578 tbl.cell(i + 1, 2).unwrap().set_text(pct);
1579 }
1580 }
1581
1582 // ── Section 4: Initiatives ──
1583 doc.add_paragraph("").page_break_before(true);
1584 doc.add_paragraph("Q1 2026 Initiatives").style("Heading1");
1585
1586 doc.add_paragraph("Planned Programs").style("Heading2");
1587 doc.add_numbered_list_item(
1588 "Deploy on-site battery storage at SF data center (2 MWh capacity)",
1589 0,
1590 );
1591 doc.add_numbered_list_item("Pilot immersion cooling in Dublin facility", 0);
1592 doc.add_numbered_list_item("Launch employee commute carbon offset program", 0);
1593 doc.add_numbered_list_item("Achieve ISO 14001 certification for Singapore facility", 0);
1594
1595 doc.add_paragraph("Investment Targets").style("Heading2");
1596 doc.add_paragraph("Sustainability CapEx allocation for FY2026:")
1597 .keep_with_next(true);
1598 {
1599 let mut tbl = doc
1600 .add_table(5, 2)
1601 .borders(BorderStyle::Single, 4, "2D5016");
1602 tbl.cell(0, 0).unwrap().set_text("Initiative");
1603 tbl.cell(0, 0).unwrap().shading("2D5016");
1604 tbl.cell(0, 1).unwrap().set_text("Budget");
1605 tbl.cell(0, 1).unwrap().shading("2D5016");
1606 let rows = [
1607 ("Battery Storage", "$1.2M"),
1608 ("Immersion Cooling Pilot", "$800K"),
1609 ("Solar Panel Expansion", "$2.1M"),
1610 ("Carbon Credits", "$500K"),
1611 ];
1612 for (i, (init, budget)) in rows.iter().enumerate() {
1613 tbl.cell(i + 1, 0).unwrap().set_text(init);
1614 tbl.cell(i + 1, 1).unwrap().set_text(budget);
1615 if i % 2 == 0 {
1616 tbl.cell(i + 1, 0).unwrap().shading("FFFAF0");
1617 tbl.cell(i + 1, 1).unwrap().shading("FFFAF0");
1618 }
1619 }
1620 }
1621
1622 // Image: sustainability roadmap
1623 let roadmap_img = create_sample_png(500, 100, [40, 80, 30]);
1624 doc.add_paragraph("");
1625 doc.add_picture(
1626 &roadmap_img,
1627 "roadmap.png",
1628 Length::inches(6.0),
1629 Length::inches(1.2),
1630 )
1631 .alignment(Alignment::Center);
1632 {
1633 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1634 p.add_run("Figure 2: 2026 Sustainability Roadmap")
1635 .italic(true)
1636 .size(9.0)
1637 .color("666666");
1638 }
1639
1640 doc.add_paragraph("");
1641 doc.add_paragraph(
1642 "For questions about this report, contact Walter White at sustainability@tensorbee.com.",
1643 )
1644 .shading("FFFAF0")
1645 .border_all(BorderStyle::Single, 2, "2D5016");
1646
1647 doc
1648}
1649
1650// =============================================================================
1651// 6. LETTER — Slate blue + silver scheme
1652// =============================================================================
1653fn generate_letter(_samples_dir: &Path) -> Document {
1654 let mut doc = Document::new();
1655
1656 // Colors: Slate #4A5568, Blue accent #3182CE
1657 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1658 doc.set_margins(
1659 Length::inches(1.25),
1660 Length::inches(1.25),
1661 Length::inches(1.25),
1662 Length::inches(1.25),
1663 );
1664 doc.set_title("Business Letter");
1665 doc.set_author("Walter White");
1666
1667 // Banner header using raw XML
1668 let logo_img = create_logo_png(220, 48);
1669 let banner = build_header_banner_xml(
1670 "rId1",
1671 &BannerOpts {
1672 bg_color: "4A5568",
1673 banner_width: 7772400,
1674 banner_height: 731520, // ~0.8"
1675 logo_width: 2011680,
1676 logo_height: 438912,
1677 logo_x_offset: 295125,
1678 logo_y_offset: 146304,
1679 },
1680 );
1681 doc.set_raw_header_with_images(
1682 banner,
1683 &[("rId1", &logo_img, "logo.png")],
1684 rdocx_oxml::header_footer::HdrFtrType::Default,
1685 );
1686 doc.set_margins(
1687 Length::twips(2000),
1688 Length::twips(1800),
1689 Length::twips(1440),
1690 Length::twips(1800),
1691 );
1692 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
1693
1694 // Footer with contact
1695 doc.set_footer(
1696 "Tensorbee | 123 Innovation Drive, Suite 400 | San Francisco, CA 94105 | tensorbee.com",
1697 );
1698
1699 // ── Sender Address ──
1700 doc.add_paragraph("Walter White");
1701 doc.add_paragraph("Chief Executive Officer");
1702 doc.add_paragraph("Tensorbee");
1703 doc.add_paragraph("123 Innovation Drive, Suite 400");
1704 doc.add_paragraph("San Francisco, CA 94105");
1705 doc.add_paragraph("");
1706 doc.add_paragraph("February 22, 2026");
1707
1708 doc.add_paragraph("");
1709
1710 // ── Recipient ──
1711 doc.add_paragraph("Ms. Sarah Chen");
1712 doc.add_paragraph("VP of Engineering");
1713 doc.add_paragraph("Meridian Dynamics LLC");
1714 doc.add_paragraph("456 Enterprise Boulevard");
1715 doc.add_paragraph("Austin, TX 78701");
1716
1717 doc.add_paragraph("");
1718
1719 // ── Subject ──
1720 {
1721 let mut p = doc.add_paragraph("");
1722 p.add_run("Re: Strategic Technology Partnership — Phase 2 Expansion")
1723 .bold(true);
1724 }
1725
1726 doc.add_paragraph("");
1727
1728 // ── Body ──
1729 doc.add_paragraph("Dear Ms. Chen,");
1730 doc.add_paragraph("");
1731
1732 doc.add_paragraph(
1733 "I am writing to express Tensorbee's enthusiasm for expanding our strategic technology \
1734 partnership with Meridian Dynamics. Following the successful completion of Phase 1, \
1735 which delivered a 40% improvement in your ML inference pipeline throughput, we believe \
1736 the foundation is firmly established for an ambitious Phase 2 engagement.",
1737 )
1738 .first_line_indent(Length::inches(0.5));
1739
1740 doc.add_paragraph(
1741 "During our recent executive review, your team highlighted three priority areas for \
1742 the next phase of collaboration:",
1743 )
1744 .first_line_indent(Length::inches(0.5));
1745
1746 doc.add_numbered_list_item(
1747 "Real-time anomaly detection for your financial transaction monitoring system, \
1748 targeting sub-100ms latency at 50,000 transactions per second.",
1749 0,
1750 );
1751 doc.add_numbered_list_item(
1752 "Federated learning infrastructure to enable model training across your distributed \
1753 data centers without centralizing sensitive financial data.",
1754 0,
1755 );
1756 doc.add_numbered_list_item(
1757 "MLOps automation to reduce your model deployment cycle from the current 5 days \
1758 to under 4 hours.",
1759 0,
1760 );
1761
1762 doc.add_paragraph(
1763 "Our engineering team has prepared a detailed technical proposal addressing each of \
1764 these areas. We have allocated a dedicated team of eight senior engineers, led by \
1765 our CTO, to ensure continuity with the Phase 1 team your organization has already \
1766 built a productive working relationship with.",
1767 )
1768 .first_line_indent(Length::inches(0.5));
1769
1770 doc.add_paragraph(
1771 "I would welcome the opportunity to present our Phase 2 proposal in person. My \
1772 assistant will reach out to coordinate a meeting at your convenience during the \
1773 first week of March.",
1774 )
1775 .first_line_indent(Length::inches(0.5));
1776
1777 doc.add_paragraph("");
1778
1779 doc.add_paragraph("Warm regards,");
1780 doc.add_paragraph("");
1781 doc.add_paragraph("");
1782
1783 // Signature line
1784 doc.add_paragraph("")
1785 .border_bottom(BorderStyle::Single, 4, "4A5568");
1786 {
1787 let mut p = doc.add_paragraph("");
1788 p.add_run("Walter White").bold(true).size(12.0);
1789 }
1790 {
1791 let mut p = doc.add_paragraph("");
1792 p.add_run("Chief Executive Officer, Tensorbee")
1793 .italic(true)
1794 .size(10.0)
1795 .color("666666");
1796 }
1797 {
1798 let mut p = doc.add_paragraph("");
1799 p.add_run("walter@tensorbee.com | +1 (415) 555-0199")
1800 .size(10.0)
1801 .color("888888");
1802 }
1803
1804 doc
1805}
1806
1807// =============================================================================
1808// 7. EMPLOYMENT CONTRACT — Purple + charcoal scheme
1809// =============================================================================
1810fn generate_contract(_samples_dir: &Path) -> Document {
1811 let mut doc = Document::new();
1812
1813 // Colors: Purple #5B2C6F, Plum #8E44AD, Gray #2C3E50
1814 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1815 doc.set_margins(
1816 Length::inches(1.25),
1817 Length::inches(1.0),
1818 Length::inches(1.0),
1819 Length::inches(1.0),
1820 );
1821 doc.set_title("Employment Agreement");
1822 doc.set_author("Tensorbee HR Department");
1823 doc.set_subject("Employment Contract — Walter White");
1824 doc.set_keywords("employment, contract, agreement, Tensorbee");
1825
1826 doc.set_header("TENSORBEE — Employment Agreement");
1827 doc.set_footer("Employment Agreement — Walter White — Page");
1828
1829 // ── Title Block ──
1830 doc.add_paragraph("")
1831 .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1832 {
1833 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1834 p.add_run("EMPLOYMENT AGREEMENT")
1835 .bold(true)
1836 .size(24.0)
1837 .color("5B2C6F")
1838 .font("Georgia");
1839 }
1840 doc.add_paragraph("")
1841 .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1842 doc.add_paragraph("");
1843
1844 // ── Parties ──
1845 doc.add_paragraph(
1846 "This Employment Agreement (\"Agreement\") is entered into as of February 22, 2026 \
1847 (\"Effective Date\"), by and between:",
1848 );
1849
1850 doc.add_paragraph("");
1851
1852 {
1853 let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1854 p.add_run("EMPLOYER: ").bold(true);
1855 p.add_run(
1856 "Tensorbee, Inc., a Delaware corporation with its principal place of business at \
1857 123 Innovation Drive, Suite 400, San Francisco, CA 94105 (\"Company\")",
1858 );
1859 }
1860 doc.add_paragraph("");
1861 {
1862 let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1863 p.add_run("EMPLOYEE: ").bold(true);
1864 p.add_run(
1865 "Walter White, residing at 308 Negra Arroyo Lane, Albuquerque, NM 87104 (\"Employee\")",
1866 );
1867 }
1868
1869 doc.add_paragraph("");
1870 doc.add_paragraph("The Company and Employee are collectively referred to as the \"Parties.\"");
1871 doc.add_paragraph("");
1872
1873 // ── Article 1: Position and Duties ──
1874 doc.add_paragraph("Article 1 — Position and Duties")
1875 .style("Heading1");
1876 {
1877 let mut p = doc.add_paragraph("");
1878 p.add_run("1.1 ").bold(true);
1879 p.add_run("The Company hereby employs the Employee as ");
1880 p.add_run("Chief Executive Officer (CEO)")
1881 .bold(true)
1882 .italic(true);
1883 p.add_run(", reporting directly to the Board of Directors.");
1884 }
1885 {
1886 let mut p = doc.add_paragraph("");
1887 p.add_run("1.2 ").bold(true);
1888 p.add_run("The Employee shall devote full working time, attention, and best efforts to \
1889 the performance of duties as reasonably assigned by the Board, including but not limited to:");
1890 }
1891 doc.add_bullet_list_item(
1892 "Setting and executing the Company's strategic vision and business plan",
1893 0,
1894 );
1895 doc.add_bullet_list_item(
1896 "Overseeing all operations, engineering, and commercial activities",
1897 0,
1898 );
1899 doc.add_bullet_list_item(
1900 "Representing the Company to investors, customers, and the public",
1901 0,
1902 );
1903 doc.add_bullet_list_item("Recruiting, developing, and retaining key talent", 0);
1904
1905 {
1906 let mut p = doc.add_paragraph("");
1907 p.add_run("1.3 ").bold(true);
1908 p.add_run(
1909 "The Employee's primary work location shall be the Company's San Francisco \
1910 headquarters, with reasonable travel as required by business needs.",
1911 );
1912 }
1913
1914 // ── Article 2: Compensation ──
1915 doc.add_paragraph("Article 2 — Compensation and Benefits")
1916 .style("Heading1");
1917 {
1918 let mut p = doc.add_paragraph("");
1919 p.add_run("2.1 Base Salary. ").bold(true);
1920 p.add_run("The Company shall pay the Employee an annual base salary of ");
1921 p.add_run("$375,000.00").bold(true);
1922 p.add_run(" (Three Hundred Seventy-Five Thousand Dollars), payable in accordance with the \
1923 Company's standard payroll schedule, less applicable withholdings and deductions.");
1924 }
1925 {
1926 let mut p = doc.add_paragraph("");
1927 p.add_run("2.2 Annual Bonus. ").bold(true);
1928 p.add_run("The Employee shall be eligible for an annual performance bonus of up to ");
1929 p.add_run("40%").bold(true);
1930 p.add_run(
1931 " of base salary, based on achievement of mutually agreed performance objectives.",
1932 );
1933 }
1934 {
1935 let mut p = doc.add_paragraph("");
1936 p.add_run("2.3 Equity. ").bold(true);
1937 p.add_run("Subject to Board approval, the Employee shall receive a stock option grant of ");
1938 p.add_run("500,000 shares").bold(true);
1939 p.add_run(
1940 " of the Company's common stock, vesting over four (4) years with a one-year cliff, \
1941 at an exercise price equal to the fair market value on the date of grant.",
1942 );
1943 }
1944
1945 // Compensation summary table
1946 doc.add_paragraph("");
1947 {
1948 let mut tbl = doc
1949 .add_table(5, 2)
1950 .borders(BorderStyle::Single, 4, "5B2C6F")
1951 .width_pct(70.0)
1952 .alignment(Alignment::Center);
1953 tbl.cell(0, 0).unwrap().set_text("Compensation Element");
1954 tbl.cell(0, 0).unwrap().shading("5B2C6F");
1955 tbl.cell(0, 1).unwrap().set_text("Value");
1956 tbl.cell(0, 1).unwrap().shading("5B2C6F");
1957 let items = [
1958 ("Base Salary", "$375,000/year"),
1959 ("Target Bonus", "Up to 40% ($150,000)"),
1960 ("Equity Grant", "500,000 shares (4yr vest)"),
1961 ("Total Target Comp", "$525,000 + equity"),
1962 ];
1963 for (i, (elem, val)) in items.iter().enumerate() {
1964 tbl.cell(i + 1, 0).unwrap().set_text(elem);
1965 tbl.cell(i + 1, 1).unwrap().set_text(val);
1966 if i == 3 {
1967 tbl.cell(i + 1, 0).unwrap().shading("E8DAEF");
1968 tbl.cell(i + 1, 1).unwrap().shading("E8DAEF");
1969 }
1970 }
1971 }
1972
1973 // ── Article 3: Benefits ──
1974 doc.add_paragraph("").page_break_before(true);
1975 doc.add_paragraph("Article 3 — Benefits").style("Heading1");
1976 {
1977 let mut p = doc.add_paragraph("");
1978 p.add_run("3.1 ").bold(true);
1979 p.add_run(
1980 "The Employee shall be entitled to participate in all benefit programs \
1981 generally available to senior executives, including:",
1982 );
1983 }
1984 doc.add_bullet_list_item(
1985 "Medical, dental, and vision insurance (100% premium coverage for employee and dependents)",
1986 0,
1987 );
1988 doc.add_bullet_list_item("401(k) retirement plan with 6% company match", 0);
1989 doc.add_bullet_list_item("Life insurance and long-term disability coverage", 0);
1990 doc.add_bullet_list_item("Annual professional development allowance of $10,000", 0);
1991
1992 {
1993 let mut p = doc.add_paragraph("");
1994 p.add_run("3.2 Paid Time Off. ").bold(true);
1995 p.add_run(
1996 "The Employee shall receive 25 days of paid vacation per year, plus Company holidays, \
1997 accruing on a monthly basis.",
1998 );
1999 }
2000
2001 // ── Article 4: Term and Termination ──
2002 doc.add_paragraph("Article 4 — Term and Termination")
2003 .style("Heading1");
2004 {
2005 let mut p = doc.add_paragraph("");
2006 p.add_run("4.1 At-Will Employment. ").bold(true);
2007 p.add_run(
2008 "This Agreement is for at-will employment and may be terminated by either Party \
2009 at any time, with or without cause, subject to the notice provisions herein.",
2010 );
2011 }
2012 {
2013 let mut p = doc.add_paragraph("");
2014 p.add_run("4.2 Notice Period. ").bold(true);
2015 p.add_run("Either Party shall provide ");
2016 p.add_run("ninety (90) days").bold(true);
2017 p.add_run(" written notice of termination, or pay in lieu thereof.");
2018 }
2019 {
2020 let mut p = doc.add_paragraph("");
2021 p.add_run("4.3 Severance. ").bold(true);
2022 p.add_run("In the event of termination by the Company without Cause, the Employee shall \
2023 receive (a) twelve (12) months of base salary continuation, (b) pro-rata bonus \
2024 for the year of termination, and (c) twelve (12) months of COBRA premium coverage.");
2025 }
2026
2027 // ── Article 5: Confidentiality ──
2028 doc.add_paragraph("Article 5 — Confidentiality and IP")
2029 .style("Heading1");
2030 {
2031 let mut p = doc.add_paragraph("");
2032 p.add_run("5.1 ").bold(true);
2033 p.add_run(
2034 "The Employee agrees to maintain strict confidentiality of all proprietary \
2035 information, trade secrets, and business plans of the Company during and after \
2036 employment.",
2037 );
2038 }
2039 {
2040 let mut p = doc.add_paragraph("");
2041 p.add_run("5.2 ").bold(true);
2042 p.add_run(
2043 "All intellectual property, inventions, and works of authorship created by the \
2044 Employee during the term of employment and within the scope of duties shall be \
2045 the sole and exclusive property of the Company.",
2046 );
2047 }
2048
2049 // ── Article 6: Non-Compete ──
2050 doc.add_paragraph("Article 6 — Non-Competition")
2051 .style("Heading1");
2052 {
2053 let mut p = doc.add_paragraph("");
2054 p.add_run("6.1 ").bold(true);
2055 p.add_run("For a period of twelve (12) months following termination, the Employee agrees \
2056 not to engage in any business that directly competes with the Company's core \
2057 business of AI infrastructure and ML operations services, within the United States.");
2058 }
2059
2060 // ── Article 7: General Provisions ──
2061 doc.add_paragraph("Article 7 — General Provisions")
2062 .style("Heading1");
2063 {
2064 let mut p = doc.add_paragraph("");
2065 p.add_run("7.1 Governing Law. ").bold(true);
2066 p.add_run(
2067 "This Agreement shall be governed by and construed in accordance with the laws of \
2068 the State of California.",
2069 );
2070 }
2071 {
2072 let mut p = doc.add_paragraph("");
2073 p.add_run("7.2 Entire Agreement. ").bold(true);
2074 p.add_run(
2075 "This Agreement constitutes the entire agreement between the Parties and supersedes \
2076 all prior negotiations, representations, and agreements.",
2077 );
2078 }
2079 {
2080 let mut p = doc.add_paragraph("");
2081 p.add_run("7.3 Amendment. ").bold(true);
2082 p.add_run(
2083 "This Agreement may only be amended by a written instrument signed by both Parties.",
2084 );
2085 }
2086
2087 // ── Signature Block ──
2088 doc.add_paragraph("").page_break_before(true);
2089 doc.add_paragraph("IN WITNESS WHEREOF, the Parties have executed this Employment Agreement as of the Effective Date.")
2090 .space_after(Length::pt(24.0));
2091
2092 // Signature table
2093 {
2094 let mut tbl = doc.add_table(6, 2).width_pct(100.0);
2095 tbl.cell(0, 0).unwrap().set_text("FOR THE COMPANY:");
2096 tbl.cell(0, 0).unwrap().shading("5B2C6F");
2097 tbl.cell(0, 1).unwrap().set_text("EMPLOYEE:");
2098 tbl.cell(0, 1).unwrap().shading("5B2C6F");
2099
2100 tbl.cell(1, 0).unwrap().set_text("");
2101 tbl.cell(1, 1).unwrap().set_text("");
2102 tbl.row(1).unwrap().height(Length::pt(40.0));
2103
2104 tbl.cell(2, 0)
2105 .unwrap()
2106 .set_text("Signature: ___________________________");
2107 tbl.cell(2, 1)
2108 .unwrap()
2109 .set_text("Signature: ___________________________");
2110 tbl.cell(3, 0)
2111 .unwrap()
2112 .set_text("Name: Board Representative");
2113 tbl.cell(3, 1).unwrap().set_text("Name: Walter White");
2114 tbl.cell(4, 0)
2115 .unwrap()
2116 .set_text("Title: Chair, Board of Directors");
2117 tbl.cell(4, 1)
2118 .unwrap()
2119 .set_text("Title: Chief Executive Officer");
2120 tbl.cell(5, 0).unwrap().set_text("Date: _______________");
2121 tbl.cell(5, 1).unwrap().set_text("Date: _______________");
2122 }
2123
2124 doc
2125}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?
34fn generate_feature_showcase(path: &Path) {
35 let mut doc = Document::new();
36
37 // =========================================================================
38 // PAGE SETUP & METADATA
39 // =========================================================================
40 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
41 doc.set_margins(
42 Length::inches(1.0), // top
43 Length::inches(1.0), // right
44 Length::inches(1.0), // bottom
45 Length::inches(1.0), // left
46 );
47 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
48 doc.set_gutter(Length::twips(0));
49
50 doc.set_title("rdocx Feature Showcase");
51 doc.set_author("rdocx Sample Generator");
52 doc.set_subject("Comprehensive feature demonstration");
53 doc.set_keywords("rdocx, docx, rust, sample");
54
55 // Header & Footer
56 doc.set_header("rdocx Feature Showcase");
57 doc.set_footer("Generated by rdocx — Page");
58
59 // Different first page header
60 doc.set_different_first_page(true);
61 doc.set_first_page_header("rdocx");
62 doc.set_first_page_footer("Feature Showcase — Cover Page");
63
64 // =========================================================================
65 // PAGE 1: COVER PAGE — background image, run formatting
66 // =========================================================================
67 let bg_cover = create_sample_png(612, 792, [30, 60, 120]);
68 doc.add_background_image(&bg_cover, "cover_bg.png");
69
70 doc.add_paragraph(""); // spacer
71 doc.add_paragraph(""); // spacer
72 doc.add_paragraph(""); // spacer
73
74 {
75 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
76 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
77 }
78 {
79 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
80 p.add_run("Feature Showcase")
81 .size(28.0)
82 .color("FFFFFF")
83 .italic(true);
84 }
85
86 doc.add_paragraph(""); // spacer
87
88 {
89 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
90 p.add_run("A comprehensive demonstration of every feature")
91 .size(14.0)
92 .color("CCDDFF");
93 }
94 {
95 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
96 p.add_run("provided by the rdocx Rust crate for DOCX generation.")
97 .size(14.0)
98 .color("CCDDFF");
99 }
100
101 // =========================================================================
102 // PAGE 2: TEXT FORMATTING
103 // =========================================================================
104 doc.add_paragraph("").page_break_before(true);
105
106 doc.add_paragraph("1. Text Formatting").style("Heading1");
107
108 doc.add_paragraph("This section demonstrates paragraph and run-level formatting options.");
109 doc.add_paragraph("");
110
111 // --- Paragraph alignment ---
112 doc.add_paragraph("Paragraph Alignment").style("Heading2");
113
114 doc.add_paragraph("This paragraph is left-aligned (the default).")
115 .alignment(Alignment::Left);
116 doc.add_paragraph("This paragraph is center-aligned.")
117 .alignment(Alignment::Center);
118 doc.add_paragraph("This paragraph is right-aligned.")
119 .alignment(Alignment::Right);
120 doc.add_paragraph(
121 "This paragraph is justified. To demonstrate justified text properly, it needs \
122 to be long enough to span multiple lines so the word spacing adjustment is visible \
123 across the full width of the text area on the page.",
124 )
125 .alignment(Alignment::Justify);
126
127 doc.add_paragraph("");
128
129 // --- Run formatting ---
130 doc.add_paragraph("Run Formatting").style("Heading2");
131
132 {
133 let mut p = doc.add_paragraph("");
134 p.add_run("Bold text").bold(true);
135 p.add_run(" | ");
136 p.add_run("Italic text").italic(true);
137 p.add_run(" | ");
138 p.add_run("Bold + Italic").bold(true).italic(true);
139 }
140 {
141 let mut p = doc.add_paragraph("");
142 p.add_run("Single underline").underline(true);
143 p.add_run(" | ");
144 p.add_run("Strikethrough").strike(true);
145 p.add_run(" | ");
146 p.add_run("Double strikethrough").double_strike(true);
147 }
148 {
149 let mut p = doc.add_paragraph("");
150 p.add_run("Red text").color("FF0000");
151 p.add_run(" | ");
152 p.add_run("Blue text").color("0000FF");
153 p.add_run(" | ");
154 p.add_run("Green text").color("00AA00");
155 p.add_run(" | ");
156 p.add_run("Highlighted").highlight("FFFF00");
157 }
158 {
159 let mut p = doc.add_paragraph("");
160 p.add_run("8pt small").size(8.0);
161 p.add_run(" | ");
162 p.add_run("11pt normal").size(11.0);
163 p.add_run(" | ");
164 p.add_run("16pt large").size(16.0);
165 p.add_run(" | ");
166 p.add_run("24pt extra-large").size(24.0);
167 }
168 {
169 let mut p = doc.add_paragraph("");
170 p.add_run("Arial font").font("Arial");
171 p.add_run(" | ");
172 p.add_run("Times New Roman font").font("Times New Roman");
173 p.add_run(" | ");
174 p.add_run("Courier New font").font("Courier New");
175 }
176 {
177 let mut p = doc.add_paragraph("");
178 p.add_run("Normal");
179 p.add_run(" H").size(11.0);
180 p.add_run("2").subscript();
181 p.add_run("O (subscript)").size(11.0);
182 p.add_run(" | E = mc").size(11.0);
183 p.add_run("2").superscript();
184 p.add_run(" (superscript)").size(11.0);
185 }
186 {
187 let mut p = doc.add_paragraph("");
188 p.add_run("ALL CAPS").all_caps(true);
189 p.add_run(" | ");
190 p.add_run("Small Caps").small_caps(true);
191 p.add_run(" | ");
192 p.add_run("Expanded spacing")
193 .character_spacing(Length::twips(40));
194 }
195
196 doc.add_paragraph("");
197
198 // --- Paragraph formatting ---
199 doc.add_paragraph("Paragraph Formatting").style("Heading2");
200
201 doc.add_paragraph("Paragraph with shading (light green background)")
202 .shading("E2EFDA");
203
204 doc.add_paragraph("Paragraph with bottom border")
205 .border_bottom(BorderStyle::Single, 6, "2E75B6");
206
207 doc.add_paragraph("Paragraph with all borders")
208 .border_all(BorderStyle::Single, 4, "FF0000");
209
210 doc.add_paragraph("Paragraph with 1-inch left indent and hanging indent")
211 .indent_left(Length::inches(1.0))
212 .hanging_indent(Length::inches(0.5));
213
214 doc.add_paragraph("Paragraph with first-line indent of 0.5 inches")
215 .first_line_indent(Length::inches(0.5));
216
217 doc.add_paragraph("Paragraph with extra space before (24pt) and after (12pt)")
218 .space_before(Length::pt(24.0))
219 .space_after(Length::pt(12.0));
220
221 doc.add_paragraph(
222 "Paragraph with double line spacing. This text should have extra vertical \
223 space between lines to demonstrate the line_spacing_multiple setting.",
224 )
225 .line_spacing_multiple(2.0);
226
227 doc.add_paragraph("Paragraph with keep-with-next (won't break from the next paragraph)")
228 .keep_with_next(true);
229 doc.add_paragraph("(This stays with the paragraph above.)");
230
231 // =========================================================================
232 // PAGE 3: LISTS & TAB STOPS
233 // =========================================================================
234 doc.add_paragraph("").page_break_before(true);
235
236 doc.add_paragraph("2. Lists").style("Heading1");
237
238 doc.add_paragraph("Bullet List").style("Heading2");
239
240 doc.add_bullet_list_item("First bullet item", 0);
241 doc.add_bullet_list_item("Second bullet item", 0);
242 doc.add_bullet_list_item("Nested level 1", 1);
243 doc.add_bullet_list_item("Nested level 2", 2);
244 doc.add_bullet_list_item("Back to level 1", 1);
245 doc.add_bullet_list_item("Third bullet item", 0);
246
247 doc.add_paragraph("");
248
249 doc.add_paragraph("Numbered List").style("Heading2");
250
251 doc.add_numbered_list_item("First numbered item", 0);
252 doc.add_numbered_list_item("Second numbered item", 0);
253 doc.add_numbered_list_item("Sub-item A", 1);
254 doc.add_numbered_list_item("Sub-item B", 1);
255 doc.add_numbered_list_item("Third numbered item", 0);
256
257 doc.add_paragraph("");
258
259 // --- Tab stops ---
260 doc.add_paragraph("Tab Stops").style("Heading2");
261
262 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
263 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
264 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
265 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
266 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
267
268 doc.add_paragraph("Item\t........\tPrice")
269 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
270 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
271 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
272
273 doc.add_paragraph("Widget A\t........\t$19.99")
274 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
275 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
276 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
277
278 doc.add_paragraph("Gadget B\t________\t$249.50")
279 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
280 .add_tab_stop_with_leader(
281 TabAlignment::Right,
282 Length::inches(4.0),
283 TabLeader::Underscore,
284 )
285 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
286
287 // =========================================================================
288 // PAGE 4: TABLES
289 // =========================================================================
290 doc.add_paragraph("").page_break_before(true);
291
292 doc.add_paragraph("3. Tables").style("Heading1");
293
294 // --- Basic table with borders ---
295 doc.add_paragraph("Basic Table with Borders")
296 .style("Heading2");
297
298 {
299 let mut tbl = doc.add_table(4, 3);
300 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
301
302 // Header row
303 for col in 0..3 {
304 tbl.cell(0, col).unwrap().shading("2E75B6");
305 }
306 tbl.cell(0, 0).unwrap().set_text("Name");
307 tbl.cell(0, 1).unwrap().set_text("Role");
308 tbl.cell(0, 2).unwrap().set_text("Location");
309
310 tbl.cell(1, 0).unwrap().set_text("Alice Johnson");
311 tbl.cell(1, 1).unwrap().set_text("Engineering Lead");
312 tbl.cell(1, 2).unwrap().set_text("New York");
313
314 tbl.cell(2, 0).unwrap().set_text("Bob Smith");
315 tbl.cell(2, 1).unwrap().set_text("Product Manager");
316 tbl.cell(2, 2).unwrap().set_text("San Francisco");
317
318 tbl.cell(3, 0).unwrap().set_text("Carol Davis");
319 tbl.cell(3, 1).unwrap().set_text("Designer");
320 tbl.cell(3, 2).unwrap().set_text("London");
321 }
322
323 doc.add_paragraph("");
324
325 // --- Table with cell merging ---
326 doc.add_paragraph("Table with Cell Merging & Vertical Alignment")
327 .style("Heading2");
328
329 {
330 let mut tbl = doc.add_table(4, 4);
331 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
332 tbl = tbl.width_pct(100.0);
333
334 // Header spanning all columns
335 tbl.cell(0, 0).unwrap().set_text("Quarterly Revenue Report");
336 tbl.cell(0, 0).unwrap().shading("1F4E79");
337 tbl.cell(0, 0).unwrap().grid_span(4);
338
339 // Sub-header
340 tbl.cell(1, 0).unwrap().set_text("Region");
341 tbl.cell(1, 0).unwrap().shading("D6E4F0");
342 tbl.cell(1, 1).unwrap().set_text("Q1");
343 tbl.cell(1, 1).unwrap().shading("D6E4F0");
344 tbl.cell(1, 2).unwrap().set_text("Q2");
345 tbl.cell(1, 2).unwrap().shading("D6E4F0");
346 tbl.cell(1, 3).unwrap().set_text("Total");
347 tbl.cell(1, 3).unwrap().shading("D6E4F0");
348
349 // Data
350 tbl.cell(2, 0).unwrap().set_text("North America");
351 tbl.cell(2, 1).unwrap().set_text("$2.4M");
352 tbl.cell(2, 2).unwrap().set_text("$2.7M");
353 tbl.cell(2, 3).unwrap().set_text("$5.1M");
354
355 tbl.cell(3, 0).unwrap().set_text("Europe");
356 tbl.cell(3, 1).unwrap().set_text("$1.8M");
357 tbl.cell(3, 2).unwrap().set_text("$2.0M");
358 tbl.cell(3, 3).unwrap().set_text("$3.8M");
359
360 // Vertical alignment on data cells
361 tbl.cell(2, 3)
362 .unwrap()
363 .vertical_alignment(VerticalAlignment::Center);
364 tbl.cell(3, 3)
365 .unwrap()
366 .vertical_alignment(VerticalAlignment::Bottom);
367 }
368
369 doc.add_paragraph("");
370
371 // --- Table with vertical merge ---
372 doc.add_paragraph("Table with Vertical Merge")
373 .style("Heading2");
374
375 {
376 let mut tbl = doc.add_table(4, 3);
377 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
378
379 tbl.cell(0, 0).unwrap().set_text("Category");
380 tbl.cell(0, 0).unwrap().shading("E2EFDA");
381 tbl.cell(0, 1).unwrap().set_text("Item");
382 tbl.cell(0, 1).unwrap().shading("E2EFDA");
383 tbl.cell(0, 2).unwrap().set_text("Price");
384 tbl.cell(0, 2).unwrap().shading("E2EFDA");
385
386 // "Hardware" spans rows 1-2
387 tbl.cell(1, 0).unwrap().set_text("Hardware");
388 tbl.cell(1, 0).unwrap().v_merge_restart();
389 tbl.cell(1, 1).unwrap().set_text("Laptop");
390 tbl.cell(1, 2).unwrap().set_text("$1,200");
391
392 tbl.cell(2, 0).unwrap().v_merge_continue();
393 tbl.cell(2, 1).unwrap().set_text("Monitor");
394 tbl.cell(2, 2).unwrap().set_text("$450");
395
396 // "Software" on row 3
397 tbl.cell(3, 0).unwrap().set_text("Software");
398 tbl.cell(3, 1).unwrap().set_text("IDE License");
399 tbl.cell(3, 2).unwrap().set_text("$200/yr");
400 }
401
402 doc.add_paragraph("");
403
404 // --- Nested table ---
405 doc.add_paragraph("Nested Table").style("Heading2");
406
407 {
408 let mut tbl = doc.add_table(2, 2);
409 tbl = tbl.borders(BorderStyle::Single, 6, "2E75B6");
410
411 tbl.cell(0, 0).unwrap().set_text("Outer Cell (0,0)");
412 tbl.cell(0, 1).unwrap().set_text("Outer Cell (0,1)");
413 tbl.cell(1, 0).unwrap().set_text("Outer Cell (1,0)");
414
415 // Nested table inside cell (1,1)
416 {
417 let mut cell = tbl.cell(1, 1).unwrap();
418 cell.set_text("Contains nested table:");
419 let mut nested = cell.add_table(2, 2);
420 nested = nested.borders(BorderStyle::Single, 2, "FF6600");
421 nested.cell(0, 0).unwrap().set_text("Inner A");
422 nested.cell(0, 1).unwrap().set_text("Inner B");
423 nested.cell(1, 0).unwrap().set_text("Inner C");
424 nested.cell(1, 1).unwrap().set_text("Inner D");
425 }
426 }
427
428 // =========================================================================
429 // PAGE 5: IMAGES
430 // =========================================================================
431 doc.add_paragraph("").page_break_before(true);
432
433 doc.add_paragraph("4. Images").style("Heading1");
434
435 doc.add_paragraph("Inline Image").style("Heading2");
436
437 doc.add_paragraph("Below is an inline image (200x50 pixels, blue gradient):");
438 let inline_img = create_sample_png(200, 50, [0, 80, 200]);
439 doc.add_picture(
440 &inline_img,
441 "inline_chart.png",
442 Length::inches(3.0),
443 Length::inches(0.75),
444 );
445
446 doc.add_paragraph("");
447
448 doc.add_paragraph("Header Image").style("Heading2");
449
450 // Replace the text-only header with an image header
451 let header_img = create_sample_png(400, 40, [40, 40, 40]);
452 doc.set_header_image(
453 &header_img,
454 "header_logo.png",
455 Length::inches(2.0),
456 Length::inches(0.2),
457 );
458
459 doc.add_paragraph(
460 "The document header has been replaced with an inline image. \
461 Check the header area at the top of this page.",
462 );
463
464 doc.add_paragraph("");
465 doc.add_paragraph(
466 "Note: The cover page uses a full-page background image behind the text, \
467 demonstrated on page 1 via add_background_image().",
468 );
469
470 // =========================================================================
471 // PAGE 6: CONTENT MANIPULATION — placeholder replacement, insertion
472 // =========================================================================
473 doc.add_paragraph("").page_break_before(true);
474
475 doc.add_paragraph("5. Content Manipulation")
476 .style("Heading1");
477
478 // --- Placeholder replacement ---
479 doc.add_paragraph("Placeholder Replacement")
480 .style("Heading2");
481
482 doc.add_paragraph(
483 "Before replacement, this document contained {{customer}} and {{date}} placeholders.",
484 );
485
486 {
487 let mut p = doc.add_paragraph("");
488 p.add_run("Customer: ").bold(true);
489 p.add_run("{{customer}}");
490 }
491 {
492 let mut p = doc.add_paragraph("");
493 p.add_run("Date: ").bold(true);
494 p.add_run("{{date}}");
495 }
496 doc.add_paragraph("Reference: {{ref_number}}");
497
498 // Table with placeholders
499 {
500 let mut tbl = doc.add_table(3, 2);
501 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
502 tbl.cell(0, 0).unwrap().set_text("Field");
503 tbl.cell(0, 0).unwrap().shading("D6E4F0");
504 tbl.cell(0, 1).unwrap().set_text("Value");
505 tbl.cell(0, 1).unwrap().shading("D6E4F0");
506 tbl.cell(1, 0).unwrap().set_text("Project");
507 tbl.cell(1, 1).unwrap().set_text("{{project}}");
508 tbl.cell(2, 0).unwrap().set_text("Status");
509 tbl.cell(2, 1).unwrap().set_text("{{status}}");
510 }
511
512 // Perform replacements
513 let mut replacements = HashMap::new();
514 replacements.insert("{{customer}}", "Acme Corporation");
515 replacements.insert("{{date}}", "February 22, 2026");
516 replacements.insert("{{ref_number}}", "REF-2026-001");
517 replacements.insert("{{project}}", "Infrastructure Upgrade");
518 replacements.insert("{{status}}", "In Progress");
519 let replace_count = doc.replace_all(&replacements);
520
521 doc.add_paragraph("");
522 doc.add_paragraph(&format!(
523 "(Replaced {} placeholders above — in body text and table cells)",
524 replace_count
525 ));
526
527 doc.add_paragraph("");
528
529 // --- Content insertion ---
530 doc.add_paragraph("Content Insertion").style("Heading2");
531
532 doc.add_paragraph("Section A: First section of content.");
533 doc.add_paragraph("Section C: Third section of content.");
534
535 // Insert "Section B" between A and C
536 if let Some(idx) = doc.find_content_index("Section C") {
537 doc.insert_paragraph(
538 idx,
539 "Section B: Inserted between A and C using find_content_index().",
540 );
541 }
542
543 doc.add_paragraph("");
544 doc.add_paragraph(
545 "The paragraph above ('Section B') was inserted at a specific position \
546 using find_content_index() + insert_paragraph().",
547 );
548
549 // =========================================================================
550 // PAGE 7: LANDSCAPE — section break, wide table
551 // =========================================================================
552 doc.add_paragraph("").section_break(SectionBreak::NextPage);
553
554 doc.add_paragraph("6. Mixed Page Orientation")
555 .style("Heading1");
556
557 doc.add_paragraph(
558 "This page is in LANDSCAPE orientation. It was created using a section break \
559 followed by section_landscape(). This is useful for wide tables or charts.",
560 );
561
562 doc.add_paragraph("");
563
564 // Wide table for landscape
565 {
566 let mut tbl = doc.add_table(4, 7);
567 tbl = tbl.borders(BorderStyle::Single, 4, "2E75B6");
568
569 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
570 for (col, h) in headers.iter().enumerate() {
571 tbl.cell(0, col).unwrap().set_text(h);
572 tbl.cell(0, col).unwrap().shading("2E75B6");
573 }
574
575 let data = [
576 [
577 "North America",
578 "$1.2M",
579 "$1.3M",
580 "$1.4M",
581 "$1.5M",
582 "$1.6M",
583 "$7.0M",
584 ],
585 [
586 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
587 ],
588 [
589 "Asia Pacific",
590 "$0.5M",
591 "$0.6M",
592 "$0.7M",
593 "$0.7M",
594 "$0.8M",
595 "$3.3M",
596 ],
597 ];
598 for (row_idx, row_data) in data.iter().enumerate() {
599 for (col, val) in row_data.iter().enumerate() {
600 tbl.cell(row_idx + 1, col).unwrap().set_text(val);
601 }
602 }
603 }
604
605 // End landscape, return to portrait
606 doc.add_paragraph("")
607 .section_break(SectionBreak::NextPage)
608 .section_landscape();
609
610 // =========================================================================
611 // PAGE 8: BACK TO PORTRAIT — styles, final notes
612 // =========================================================================
613 doc.add_paragraph("7. Custom Styles & Summary")
614 .style("Heading1");
615
616 doc.add_paragraph(
617 "This final page is back in portrait orientation after a section break. \
618 The document has demonstrated:",
619 );
620
621 doc.add_bullet_list_item(
622 "Page setup: size, margins, header/footer distance, gutter",
623 0,
624 );
625 doc.add_bullet_list_item("Document metadata: title, author, subject, keywords", 0);
626 doc.add_bullet_list_item("Headers and footers: text, images, different first page", 0);
627 doc.add_bullet_list_item("Background images: full-page behind text", 0);
628 doc.add_bullet_list_item(
629 "Text formatting: bold, italic, underline, strike, color, size, font",
630 0,
631 );
632 doc.add_bullet_list_item(
633 "Advanced run formatting: superscript, subscript, caps, spacing",
634 0,
635 );
636 doc.add_bullet_list_item(
637 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
638 0,
639 );
640 doc.add_bullet_list_item("Bullet and numbered lists with nesting levels", 0);
641 doc.add_bullet_list_item("Tab stops with dot/underscore leaders", 0);
642 doc.add_bullet_list_item(
643 "Tables: borders, shading, column spans, row spans, nesting",
644 0,
645 );
646 doc.add_bullet_list_item("Vertical alignment in table cells", 0);
647 doc.add_bullet_list_item("Inline images", 0);
648 doc.add_bullet_list_item("Placeholder replacement in body and table cells", 0);
649 doc.add_bullet_list_item("Content insertion at specific positions", 0);
650 doc.add_bullet_list_item(
651 "Section breaks with mixed portrait/landscape orientation",
652 0,
653 );
654
655 doc.add_paragraph("");
656 doc.add_paragraph("All features above were built entirely from scratch using the rdocx API.")
657 .alignment(Alignment::Center)
658 .shading("E2EFDA")
659 .border_all(BorderStyle::Single, 2, "00AA00");
660
661 doc.save(path).unwrap();
662}More examples
86fn generate_feature_showcase(_samples_dir: &Path) -> Document {
87 let mut doc = Document::new();
88
89 // ── Page Setup & Metadata ──
90 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
91 doc.set_margins(
92 Length::inches(1.0),
93 Length::inches(1.0),
94 Length::inches(1.0),
95 Length::inches(1.0),
96 );
97 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
98 doc.set_gutter(Length::twips(0));
99 doc.set_title("rdocx Feature Showcase");
100 doc.set_author("rdocx Sample Generator");
101 doc.set_subject("Comprehensive feature demonstration");
102 doc.set_keywords("rdocx, docx, rust, sample, showcase");
103
104 // Headers & Footers with different first page
105 doc.set_different_first_page(true);
106 doc.set_first_page_header("rdocx");
107 doc.set_first_page_footer("Feature Showcase — Cover Page");
108 doc.set_header("rdocx Feature Showcase");
109 doc.set_footer("Generated by rdocx");
110
111 // ── COVER PAGE ──
112 let bg = create_sample_png(612, 792, [20, 45, 90]);
113 doc.add_background_image(&bg, "cover_bg.png");
114
115 for _ in 0..3 {
116 doc.add_paragraph("");
117 }
118 {
119 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
120 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
121 }
122 {
123 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
124 p.add_run("Complete Feature Showcase")
125 .size(28.0)
126 .color("FFFFFF")
127 .italic(true);
128 }
129 doc.add_paragraph("");
130 {
131 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
132 p.add_run("Every feature of the rdocx Rust library")
133 .size(13.0)
134 .color("B0C4DE");
135 }
136 {
137 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
138 p.add_run("demonstrated in a single document.")
139 .size(13.0)
140 .color("B0C4DE");
141 }
142
143 // ── TABLE OF CONTENTS ──
144 doc.add_paragraph("").page_break_before(true);
145 doc.insert_toc(doc.content_count(), 3);
146
147 // ── SECTION 1: TEXT FORMATTING ──
148 doc.add_paragraph("").page_break_before(true);
149 doc.add_paragraph("1. Text Formatting").style("Heading1");
150
151 // Paragraph Alignment
152 doc.add_paragraph("1.1 Paragraph Alignment")
153 .style("Heading2");
154 doc.add_paragraph("Left-aligned paragraph (default).")
155 .alignment(Alignment::Left);
156 doc.add_paragraph("Center-aligned paragraph.")
157 .alignment(Alignment::Center);
158 doc.add_paragraph("Right-aligned paragraph.")
159 .alignment(Alignment::Right);
160 doc.add_paragraph(
161 "Justified paragraph — this text is long enough to demonstrate how justified alignment \
162 distributes extra space across word gaps so lines fill the full width of the text area.",
163 )
164 .alignment(Alignment::Justify);
165
166 doc.add_paragraph("");
167
168 // Run Formatting
169 doc.add_paragraph("1.2 Run Formatting").style("Heading2");
170 {
171 let mut p = doc.add_paragraph("");
172 p.add_run("Bold").bold(true);
173 p.add_run(" | ");
174 p.add_run("Italic").italic(true);
175 p.add_run(" | ");
176 p.add_run("Bold Italic").bold(true).italic(true);
177 p.add_run(" | ");
178 p.add_run("Underline").underline(true);
179 p.add_run(" | ");
180 p.add_run("Strikethrough").strike(true);
181 p.add_run(" | ");
182 p.add_run("Double Strike").double_strike(true);
183 }
184 {
185 let mut p = doc.add_paragraph("");
186 p.add_run("Red").color("FF0000");
187 p.add_run(" | ");
188 p.add_run("Blue").color("0000FF");
189 p.add_run(" | ");
190 p.add_run("Green").color("00AA00");
191 p.add_run(" | ");
192 p.add_run("Highlighted").highlight("FFFF00");
193 }
194 {
195 let mut p = doc.add_paragraph("");
196 p.add_run("8pt").size(8.0);
197 p.add_run(" | ");
198 p.add_run("11pt").size(11.0);
199 p.add_run(" | ");
200 p.add_run("16pt").size(16.0);
201 p.add_run(" | ");
202 p.add_run("24pt").size(24.0);
203 }
204 {
205 let mut p = doc.add_paragraph("");
206 p.add_run("Arial").font("Arial");
207 p.add_run(" | ");
208 p.add_run("Times New Roman").font("Times New Roman");
209 p.add_run(" | ");
210 p.add_run("Courier New").font("Courier New");
211 }
212 {
213 let mut p = doc.add_paragraph("");
214 p.add_run("H");
215 p.add_run("2").subscript();
216 p.add_run("O (subscript) | E=mc");
217 p.add_run("2").superscript();
218 p.add_run(" (superscript)");
219 }
220 {
221 let mut p = doc.add_paragraph("");
222 p.add_run("ALL CAPS").all_caps(true);
223 p.add_run(" | ");
224 p.add_run("Small Caps").small_caps(true);
225 p.add_run(" | ");
226 p.add_run("Expanded").character_spacing(Length::twips(40));
227 p.add_run(" | ");
228 p.add_run("Hidden text (not visible)").hidden(true);
229 }
230
231 doc.add_paragraph("");
232
233 // Underline Styles
234 doc.add_paragraph("1.3 Underline Styles").style("Heading2");
235 {
236 let mut p = doc.add_paragraph("");
237 p.add_run("Single ")
238 .underline_style(rdocx::UnderlineStyle::Single);
239 p.add_run("Double ")
240 .underline_style(rdocx::UnderlineStyle::Double);
241 p.add_run("Thick ")
242 .underline_style(rdocx::UnderlineStyle::Thick);
243 p.add_run("Dotted ")
244 .underline_style(rdocx::UnderlineStyle::Dotted);
245 p.add_run("Dash ")
246 .underline_style(rdocx::UnderlineStyle::Dash);
247 p.add_run("Wave ")
248 .underline_style(rdocx::UnderlineStyle::Wave);
249 }
250
251 // ── SECTION 2: PARAGRAPH FORMATTING ──
252 doc.add_paragraph("").page_break_before(true);
253 doc.add_paragraph("2. Paragraph Formatting")
254 .style("Heading1");
255
256 doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
257 doc.add_paragraph("Paragraph with light green shading")
258 .shading("E2EFDA");
259 doc.add_paragraph("Paragraph with bottom border")
260 .border_bottom(BorderStyle::Single, 6, "2E75B6");
261 doc.add_paragraph("Paragraph with all borders (red)")
262 .border_all(BorderStyle::Single, 4, "FF0000");
263
264 doc.add_paragraph("2.2 Indentation").style("Heading2");
265 doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
266 .indent_left(Length::inches(1.0))
267 .hanging_indent(Length::inches(0.5));
268 doc.add_paragraph("0.5-inch first-line indent on this paragraph")
269 .first_line_indent(Length::inches(0.5));
270 doc.add_paragraph("Both left and right indent (0.75 inches each)")
271 .indent_left(Length::inches(0.75))
272 .indent_right(Length::inches(0.75));
273
274 doc.add_paragraph("2.3 Spacing & Line Height")
275 .style("Heading2");
276 doc.add_paragraph("Extra space before (24pt) and after (12pt)")
277 .space_before(Length::pt(24.0))
278 .space_after(Length::pt(12.0));
279 doc.add_paragraph(
280 "Double line spacing paragraph. This text should have extra vertical space between \
281 lines to demonstrate line_spacing_multiple(2.0).",
282 )
283 .line_spacing_multiple(2.0);
284 doc.add_paragraph("Exact 20pt line spacing (fixed height).")
285 .line_spacing(20.0);
286
287 doc.add_paragraph("2.4 Pagination Controls")
288 .style("Heading2");
289 doc.add_paragraph("keep_with_next — this paragraph stays with the next")
290 .keep_with_next(true);
291 doc.add_paragraph("(This paragraph was kept with the one above.)");
292 doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
293 .keep_together(true);
294 doc.add_paragraph("widow_control — prevents widow/orphan lines")
295 .widow_control(true);
296
297 // ── SECTION 3: LISTS ──
298 doc.add_paragraph("").page_break_before(true);
299 doc.add_paragraph("3. Lists").style("Heading1");
300
301 doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
302 doc.add_bullet_list_item("First item", 0);
303 doc.add_bullet_list_item("Second item", 0);
304 doc.add_bullet_list_item("Nested level 1", 1);
305 doc.add_bullet_list_item("Nested level 2", 2);
306 doc.add_bullet_list_item("Back to level 1", 1);
307 doc.add_bullet_list_item("Third item", 0);
308
309 doc.add_paragraph("");
310
311 doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
312 doc.add_numbered_list_item("First numbered item", 0);
313 doc.add_numbered_list_item("Second numbered item", 0);
314 doc.add_numbered_list_item("Sub-item A", 1);
315 doc.add_numbered_list_item("Sub-item B", 1);
316 doc.add_numbered_list_item("Third numbered item", 0);
317
318 // ── SECTION 4: TAB STOPS ──
319 doc.add_paragraph("");
320 doc.add_paragraph("4. Tab Stops").style("Heading1");
321
322 doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
323 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
324 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
325 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
326 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
327 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
328
329 doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
330 doc.add_paragraph("Item\t\tPrice")
331 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
332 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
333 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
334 doc.add_paragraph("Widget\t\t$19.99")
335 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
336 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
337 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
338 doc.add_paragraph("Gadget\t\t$249.50")
339 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
340 .add_tab_stop_with_leader(
341 TabAlignment::Right,
342 Length::inches(4.0),
343 TabLeader::Underscore,
344 )
345 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
346
347 // ── SECTION 5: TABLES ──
348 doc.add_paragraph("").page_break_before(true);
349 doc.add_paragraph("5. Tables").style("Heading1");
350
351 doc.add_paragraph("5.1 Basic Table").style("Heading2");
352 {
353 let mut tbl = doc
354 .add_table(4, 3)
355 .borders(BorderStyle::Single, 4, "000000");
356 for col in 0..3 {
357 tbl.cell(0, col).unwrap().shading("2E75B6");
358 }
359 tbl.cell(0, 0).unwrap().set_text("Name");
360 tbl.cell(0, 1).unwrap().set_text("Role");
361 tbl.cell(0, 2).unwrap().set_text("Location");
362 tbl.cell(1, 0).unwrap().set_text("Walter White");
363 tbl.cell(1, 1).unwrap().set_text("CEO");
364 tbl.cell(1, 2).unwrap().set_text("Albuquerque");
365 tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
366 tbl.cell(2, 1).unwrap().set_text("CTO");
367 tbl.cell(2, 2).unwrap().set_text("Remote");
368 tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
369 tbl.cell(3, 1).unwrap().set_text("Security");
370 tbl.cell(3, 2).unwrap().set_text("Washington");
371 }
372
373 doc.add_paragraph("");
374
375 doc.add_paragraph("5.2 Column Span & Cell Shading")
376 .style("Heading2");
377 {
378 let mut tbl = doc
379 .add_table(3, 4)
380 .borders(BorderStyle::Single, 4, "000000")
381 .width_pct(100.0);
382 tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
383 tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
384 tbl.cell(1, 0).unwrap().set_text("Region");
385 tbl.cell(1, 0).unwrap().shading("D6E4F0");
386 tbl.cell(1, 1).unwrap().set_text("Q1");
387 tbl.cell(1, 1).unwrap().shading("D6E4F0");
388 tbl.cell(1, 2).unwrap().set_text("Q2");
389 tbl.cell(1, 2).unwrap().shading("D6E4F0");
390 tbl.cell(1, 3).unwrap().set_text("Total");
391 tbl.cell(1, 3).unwrap().shading("D6E4F0");
392 tbl.cell(2, 0).unwrap().set_text("Americas");
393 tbl.cell(2, 1).unwrap().set_text("$2.4M");
394 tbl.cell(2, 2).unwrap().set_text("$2.7M");
395 tbl.cell(2, 3).unwrap().set_text("$5.1M");
396 }
397
398 doc.add_paragraph("");
399
400 doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
401 {
402 let mut tbl = doc
403 .add_table(4, 3)
404 .borders(BorderStyle::Single, 4, "000000");
405 tbl.cell(0, 0).unwrap().set_text("Category");
406 tbl.cell(0, 0).unwrap().shading("E2EFDA");
407 tbl.cell(0, 1).unwrap().set_text("Item");
408 tbl.cell(0, 1).unwrap().shading("E2EFDA");
409 tbl.cell(0, 2).unwrap().set_text("Price");
410 tbl.cell(0, 2).unwrap().shading("E2EFDA");
411 tbl.cell(1, 0).unwrap().set_text("Hardware");
412 tbl.cell(1, 0).unwrap().v_merge_restart();
413 tbl.cell(1, 1).unwrap().set_text("Laptop");
414 tbl.cell(1, 2).unwrap().set_text("$1,200");
415 tbl.cell(2, 0).unwrap().v_merge_continue();
416 tbl.cell(2, 1).unwrap().set_text("Monitor");
417 tbl.cell(2, 2).unwrap().set_text("$450");
418 tbl.cell(3, 0).unwrap().set_text("Software");
419 tbl.cell(3, 1).unwrap().set_text("IDE License");
420 tbl.cell(3, 2).unwrap().set_text("$200/yr");
421 }
422
423 doc.add_paragraph("");
424
425 doc.add_paragraph("5.4 Nested Table").style("Heading2");
426 {
427 let mut tbl = doc
428 .add_table(2, 2)
429 .borders(BorderStyle::Single, 6, "2E75B6");
430 tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
431 tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
432 tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
433 {
434 let mut cell = tbl.cell(1, 1).unwrap();
435 cell.set_text("Nested table below:");
436 let mut nested = cell
437 .add_table(2, 2)
438 .borders(BorderStyle::Single, 2, "FF6600");
439 nested.cell(0, 0).unwrap().set_text("A");
440 nested.cell(0, 1).unwrap().set_text("B");
441 nested.cell(1, 0).unwrap().set_text("C");
442 nested.cell(1, 1).unwrap().set_text("D");
443 }
444 }
445
446 doc.add_paragraph("");
447
448 doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
449 .style("Heading2");
450 {
451 let mut tbl = doc
452 .add_table(2, 3)
453 .borders(BorderStyle::Single, 4, "666666");
454 tbl.row(0).unwrap().height(Length::pt(50.0)).header();
455 tbl.cell(0, 0).unwrap().set_text("Top");
456 tbl.cell(0, 0)
457 .unwrap()
458 .vertical_alignment(VerticalAlignment::Top)
459 .shading("FFF2CC");
460 tbl.cell(0, 1).unwrap().set_text("Center");
461 tbl.cell(0, 1)
462 .unwrap()
463 .vertical_alignment(VerticalAlignment::Center)
464 .shading("D9E2F3");
465 tbl.cell(0, 2).unwrap().set_text("Bottom");
466 tbl.cell(0, 2)
467 .unwrap()
468 .vertical_alignment(VerticalAlignment::Bottom)
469 .shading("E2EFDA");
470 tbl.row(1).unwrap().cant_split();
471 tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
472 tbl.cell(1, 0).unwrap().no_wrap();
473 tbl.cell(1, 1).unwrap().set_text("Fixed width");
474 tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
475 tbl.cell(1, 2).unwrap().set_text("Normal");
476 }
477
478 // ── SECTION 6: IMAGES ──
479 doc.add_paragraph("").page_break_before(true);
480 doc.add_paragraph("6. Images").style("Heading1");
481
482 doc.add_paragraph("6.1 Inline Image").style("Heading2");
483 doc.add_paragraph("A blue gradient image below:");
484 let img = create_sample_png(200, 50, [0, 80, 200]);
485 doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
486
487 doc.add_paragraph("");
488 doc.add_paragraph("6.2 Header Image").style("Heading2");
489 let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
490 doc.set_header_image(
491 &hdr_img,
492 "header_logo.png",
493 Length::inches(2.0),
494 Length::inches(0.2),
495 );
496 doc.add_paragraph("The header now contains an inline image (check the top of this page).");
497
498 doc.add_paragraph("");
499 doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
500
501 // ── SECTION 7: CONTENT MANIPULATION ──
502 doc.add_paragraph("").page_break_before(true);
503 doc.add_paragraph("7. Content Manipulation")
504 .style("Heading1");
505
506 doc.add_paragraph("7.1 Placeholder Replacement")
507 .style("Heading2");
508 doc.add_paragraph("Customer: {{customer}}");
509 doc.add_paragraph("Date: {{date}}");
510 doc.add_paragraph("Reference: {{ref_number}}");
511 {
512 let mut tbl = doc
513 .add_table(2, 2)
514 .borders(BorderStyle::Single, 4, "000000");
515 tbl.cell(0, 0).unwrap().set_text("Project");
516 tbl.cell(0, 0).unwrap().shading("D6E4F0");
517 tbl.cell(0, 1).unwrap().set_text("{{project}}");
518 tbl.cell(1, 0).unwrap().set_text("Status");
519 tbl.cell(1, 0).unwrap().shading("D6E4F0");
520 tbl.cell(1, 1).unwrap().set_text("{{status}}");
521 }
522
523 let mut replacements = HashMap::new();
524 replacements.insert("{{customer}}", "Tensorbee Inc.");
525 replacements.insert("{{date}}", "February 22, 2026");
526 replacements.insert("{{ref_number}}", "TB-2026-001");
527 replacements.insert("{{project}}", "Infrastructure Upgrade");
528 replacements.insert("{{status}}", "In Progress");
529 let n = doc.replace_all(&replacements);
530 doc.add_paragraph(&format!("({n} placeholders replaced above)"));
531
532 doc.add_paragraph("");
533
534 doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
535 doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
536 let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
537 doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
538
539 doc.add_paragraph("");
540
541 doc.add_paragraph("7.3 Content Insertion").style("Heading2");
542 doc.add_paragraph("Section A: First content.");
543 doc.add_paragraph("Section C: Third content.");
544 if let Some(idx) = doc.find_content_index("Section C") {
545 doc.insert_paragraph(
546 idx,
547 "Section B: Inserted between A and C via find_content_index().",
548 );
549 }
550
551 // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
552 doc.add_paragraph("").section_break(SectionBreak::NextPage);
553 doc.add_paragraph("8. Section Breaks & Orientation")
554 .style("Heading1");
555 doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
556
557 {
558 let mut tbl = doc
559 .add_table(3, 7)
560 .borders(BorderStyle::Single, 4, "2E75B6");
561 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
562 for (c, h) in headers.iter().enumerate() {
563 tbl.cell(0, c).unwrap().set_text(h);
564 tbl.cell(0, c).unwrap().shading("2E75B6");
565 }
566 let data = [
567 [
568 "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
569 ],
570 [
571 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
572 ],
573 ];
574 for (r, row) in data.iter().enumerate() {
575 for (c, v) in row.iter().enumerate() {
576 tbl.cell(r + 1, c).unwrap().set_text(v);
577 }
578 }
579 }
580
581 doc.add_paragraph("")
582 .section_break(SectionBreak::NextPage)
583 .section_landscape();
584
585 // ── SECTION 9: CUSTOM STYLES ──
586 doc.add_paragraph("9. Custom Styles").style("Heading1");
587 doc.add_style(
588 StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
589 .based_on("Normal")
590 .paragraph_properties({
591 let mut ppr = rdocx_oxml::properties::CT_PPr::default();
592 ppr.shading = Some(rdocx_oxml::properties::CT_Shd {
593 val: "clear".to_string(),
594 color: None,
595 fill: Some("FFF2CC".to_string()),
596 });
597 ppr
598 })
599 .run_properties({
600 let mut rpr = rdocx_oxml::properties::CT_RPr::default();
601 rpr.bold = Some(true);
602 rpr.color = Some("C45911".to_string());
603 rpr
604 }),
605 );
606 doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
607 .style("CustomHighlight");
608
609 doc.add_paragraph("");
610
611 // ── SECTION 10: DOCUMENT INTELLIGENCE ──
612 doc.add_paragraph("10. Document Intelligence API")
613 .style("Heading1");
614
615 let wc = doc.word_count();
616 let hc = doc.headings().len();
617 let ic = doc.images().len();
618 let lc = doc.links().len();
619 doc.add_paragraph(&format!("Word count: {wc}"));
620 doc.add_paragraph(&format!("Heading count: {hc}"));
621 doc.add_paragraph(&format!("Image count: {ic}"));
622 doc.add_paragraph(&format!("Link count: {lc}"));
623
624 let outline = doc.document_outline();
625 doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
626
627 let issues = doc.audit_accessibility();
628 doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
629 for issue in &issues {
630 doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
631 }
632
633 // ── SECTION 11: DOCUMENT MERGING ──
634 doc.add_paragraph("");
635 doc.add_paragraph("11. Document Merging").style("Heading1");
636 {
637 let mut other = Document::new();
638 other.add_paragraph("This paragraph was merged from another document using append().");
639 doc.append(&other);
640 }
641
642 doc.add_paragraph("");
643
644 // ── SUMMARY ──
645 doc.add_paragraph("Summary of Demonstrated Features")
646 .style("Heading1");
647 let features = [
648 "Page setup: size, margins, header/footer distance, gutter",
649 "Document metadata: title, author, subject, keywords",
650 "Headers and footers: text, images, different first page",
651 "Background images (full-page behind text)",
652 "Table of Contents generation with bookmarks",
653 "Text formatting: bold, italic, underline styles, strike, color, size, font",
654 "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
655 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
656 "Pagination controls: keep-with-next, keep-together, widow control",
657 "Bullet and numbered lists with nesting",
658 "Tab stops with dot/underscore/hyphen leaders",
659 "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
660 "Row properties: header rows, exact height, cant-split",
661 "Cell properties: width, no-wrap, vertical alignment",
662 "Inline images and header images",
663 "Placeholder replacement (single and batch)",
664 "Regex-based find and replace",
665 "Content insertion at specific positions",
666 "Section breaks with mixed portrait/landscape",
667 "Custom paragraph and character styles",
668 "Document intelligence: word count, headings, outline, images, links",
669 "Accessibility audit",
670 "Document merging (append)",
671 "Export: DOCX, PDF, HTML, Markdown",
672 ];
673 for f in &features {
674 doc.add_bullet_list_item(f, 0);
675 }
676 doc.add_paragraph("");
677 doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
678 .alignment(Alignment::Center)
679 .shading("E2EFDA")
680 .border_all(BorderStyle::Single, 2, "00AA00");
681
682 doc
683}
684
685// =============================================================================
686// 2. PROPOSAL DOCUMENT — Deep navy + gold scheme
687// =============================================================================
688fn generate_proposal(_samples_dir: &Path) -> Document {
689 let mut doc = Document::new();
690
691 // Colors: Navy #1B2A4A, Gold #C5922E, Light #F4F1EB
692 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
693 doc.set_margins(
694 Length::inches(1.0),
695 Length::inches(1.0),
696 Length::inches(1.0),
697 Length::inches(1.0),
698 );
699 doc.set_title("AI Platform Modernization Proposal");
700 doc.set_author("Walter White");
701 doc.set_subject("Technology Proposal");
702 doc.set_keywords("proposal, AI, modernization, Tensorbee");
703
704 // Banner header
705 let logo_img = create_logo_png(220, 48);
706 let banner = build_header_banner_xml(
707 "rId1",
708 &BannerOpts {
709 bg_color: "1B2A4A",
710 banner_width: 7772400,
711 banner_height: 914400,
712 logo_width: 2011680,
713 logo_height: 438912,
714 logo_x_offset: 295125,
715 logo_y_offset: 237744,
716 },
717 );
718 doc.set_raw_header_with_images(
719 banner,
720 &[("rId1", &logo_img, "logo.png")],
721 rdocx_oxml::header_footer::HdrFtrType::Default,
722 );
723 doc.set_different_first_page(true);
724 let cover_banner = build_header_banner_xml(
725 "rId1",
726 &BannerOpts {
727 bg_color: "C5922E",
728 banner_width: 7772400,
729 banner_height: 914400,
730 logo_width: 2011680,
731 logo_height: 438912,
732 logo_x_offset: 295125,
733 logo_y_offset: 237744,
734 },
735 );
736 doc.set_raw_header_with_images(
737 cover_banner,
738 &[("rId1", &logo_img, "logo.png")],
739 rdocx_oxml::header_footer::HdrFtrType::First,
740 );
741 doc.set_margins(
742 Length::twips(2292),
743 Length::twips(1440),
744 Length::twips(1440),
745 Length::twips(1440),
746 );
747 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
748 doc.set_footer("Tensorbee — Confidential");
749
750 // Custom styles
751 doc.add_style(
752 StyleBuilder::paragraph("ProposalTitle", "Proposal Title")
753 .based_on("Normal")
754 .run_properties({
755 let mut rpr = rdocx_oxml::properties::CT_RPr::default();
756 rpr.bold = Some(true);
757 rpr.font_ascii = Some("Georgia".to_string());
758 rpr.font_hansi = Some("Georgia".to_string());
759 rpr.sz = Some(rdocx_oxml::HalfPoint(56)); // half-points → 28pt
760 rpr.color = Some("1B2A4A".to_string());
761 rpr
762 }),
763 );
764
765 // ── Cover Page ──
766 for _ in 0..4 {
767 doc.add_paragraph("");
768 }
769 doc.add_paragraph("AI Platform Modernization")
770 .style("ProposalTitle")
771 .alignment(Alignment::Center);
772 doc.add_paragraph("")
773 .alignment(Alignment::Center)
774 .border_bottom(BorderStyle::Single, 8, "C5922E");
775 {
776 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
777 p.add_run("Prepared for Global Financial Corp.")
778 .size(14.0)
779 .color("666666")
780 .italic(true);
781 }
782 {
783 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
784 p.add_run("Prepared by: Walter White, CEO — Tensorbee")
785 .size(12.0)
786 .color("888888");
787 }
788 {
789 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
790 p.add_run("February 22, 2026").size(12.0).color("888888");
791 }
792
793 // ── TOC ──
794 doc.add_paragraph("").page_break_before(true);
795 doc.add_paragraph("Table of Contents").style("Heading1");
796 // We'll add a manual-style TOC since the insert_toc goes at a specific position
797 doc.insert_toc(doc.content_count(), 2);
798
799 // ── Executive Summary ──
800 doc.add_paragraph("").page_break_before(true);
801 doc.add_paragraph("Executive Summary").style("Heading1");
802 doc.add_paragraph(
803 "Tensorbee proposes a comprehensive modernization of Global Financial Corp's AI platform, \
804 transitioning from legacy batch-processing systems to a real-time inference architecture. \
805 This transformation will reduce model deployment time from weeks to hours, improve \
806 prediction accuracy by an estimated 23%, and deliver $4.2M in annual operational savings.",
807 )
808 .first_line_indent(Length::inches(0.3));
809 doc.add_paragraph(
810 "The project spans three phases over 18 months, with a total investment of $2.8M. \
811 Tensorbee brings deep expertise in ML infrastructure, having successfully completed \
812 similar transformations for three Fortune 500 financial institutions.",
813 )
814 .first_line_indent(Length::inches(0.3));
815
816 // Key metrics table
817 doc.add_paragraph("");
818 {
819 let mut tbl = doc
820 .add_table(5, 2)
821 .borders(BorderStyle::Single, 4, "1B2A4A")
822 .width_pct(80.0);
823 tbl.cell(0, 0).unwrap().set_text("Key Metric");
824 tbl.cell(0, 0).unwrap().shading("1B2A4A");
825 tbl.cell(0, 1).unwrap().set_text("Value");
826 tbl.cell(0, 1).unwrap().shading("1B2A4A");
827 let rows = [
828 ("Total Investment", "$2.8M"),
829 ("Annual Savings", "$4.2M"),
830 ("ROI (Year 1)", "150%"),
831 ("Timeline", "18 months"),
832 ];
833 for (i, (k, v)) in rows.iter().enumerate() {
834 tbl.cell(i + 1, 0).unwrap().set_text(k);
835 if i % 2 == 0 {
836 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
837 }
838 tbl.cell(i + 1, 1).unwrap().set_text(v);
839 if i % 2 == 0 {
840 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
841 }
842 }
843 }
844
845 // ── Problem Statement ──
846 doc.add_paragraph("").page_break_before(true);
847 doc.add_paragraph("Problem Statement").style("Heading1");
848 doc.add_paragraph(
849 "Global Financial Corp's current AI infrastructure faces three critical challenges:",
850 );
851 doc.add_numbered_list_item("Deployment Latency — New models take 3-4 weeks to deploy to production, compared to the industry benchmark of 2-3 days.", 0);
852 doc.add_numbered_list_item("Scalability Constraints — The batch processing architecture cannot handle real-time inference demands during peak trading hours.", 0);
853 doc.add_numbered_list_item("Technical Debt — Legacy Python 2.7 codebases and manual deployment scripts create reliability risks and slow iteration speed.", 0);
854
855 // ── Proposed Solution ──
856 doc.add_paragraph("").page_break_before(true);
857 doc.add_paragraph("Proposed Solution").style("Heading1");
858
859 doc.add_paragraph("Phase 1: Foundation (Months 1-6)")
860 .style("Heading2");
861 doc.add_bullet_list_item("Deploy Kubernetes-based ML serving infrastructure", 0);
862 doc.add_bullet_list_item("Implement CI/CD pipeline for model deployment", 0);
863 doc.add_bullet_list_item("Migrate top 5 production models to new platform", 0);
864
865 doc.add_paragraph("Phase 2: Expansion (Months 7-12)")
866 .style("Heading2");
867 doc.add_bullet_list_item(
868 "Real-time feature engineering pipeline (Apache Kafka + Flink)",
869 0,
870 );
871 doc.add_bullet_list_item("A/B testing framework for model variants", 0);
872 doc.add_bullet_list_item("Automated model monitoring and drift detection", 0);
873
874 doc.add_paragraph("Phase 3: Optimization (Months 13-18)")
875 .style("Heading2");
876 doc.add_bullet_list_item("GPU inference optimization (TensorRT/ONNX Runtime)", 0);
877 doc.add_bullet_list_item("Multi-region deployment for latency reduction", 0);
878 doc.add_bullet_list_item(
879 "Self-service model deployment portal for data science teams",
880 0,
881 );
882
883 // ── Budget ──
884 doc.add_paragraph("Budget Breakdown").style("Heading1");
885 {
886 let mut tbl = doc
887 .add_table(6, 3)
888 .borders(BorderStyle::Single, 4, "1B2A4A")
889 .width_pct(100.0);
890 tbl.cell(0, 0).unwrap().set_text("Category");
891 tbl.cell(0, 0).unwrap().shading("1B2A4A");
892 tbl.cell(0, 1).unwrap().set_text("Cost");
893 tbl.cell(0, 1).unwrap().shading("1B2A4A");
894 tbl.cell(0, 2).unwrap().set_text("Notes");
895 tbl.cell(0, 2).unwrap().shading("1B2A4A");
896 let items = [
897 (
898 "Infrastructure",
899 "$450,000",
900 "Cloud compute + GPU instances",
901 ),
902 ("Engineering Services", "$1,600,000", "12 FTE x 18 months"),
903 (
904 "Software Licenses",
905 "$350,000",
906 "Kafka, monitoring, CI/CD tools",
907 ),
908 (
909 "Training & Enablement",
910 "$200,000",
911 "Team training, documentation",
912 ),
913 ("Contingency (10%)", "$200,000", "Risk buffer"),
914 ];
915 for (i, (cat, cost, note)) in items.iter().enumerate() {
916 tbl.cell(i + 1, 0).unwrap().set_text(cat);
917 tbl.cell(i + 1, 1).unwrap().set_text(cost);
918 tbl.cell(i + 1, 2).unwrap().set_text(note);
919 if i % 2 == 0 {
920 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
921 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
922 tbl.cell(i + 1, 2).unwrap().shading("F4F1EB");
923 }
924 }
925 }
926
927 // ── Closing ──
928 doc.add_paragraph("");
929 doc.add_paragraph("Next Steps").style("Heading1");
930 doc.add_numbered_list_item(
931 "Schedule technical deep-dive with GFC engineering team (Week 1)",
932 0,
933 );
934 doc.add_numbered_list_item("Finalize scope and sign SOW (Week 2-3)", 0);
935 doc.add_numbered_list_item("Kick off Phase 1 with joint planning session (Week 4)", 0);
936
937 doc.add_paragraph("");
938 {
939 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
940 p.add_run("Walter White")
941 .bold(true)
942 .size(14.0)
943 .color("1B2A4A");
944 }
945 {
946 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
947 p.add_run("CEO, Tensorbee | walter@tensorbee.com")
948 .size(10.0)
949 .color("888888");
950 }
951
952 doc
953}
954
955// =============================================================================
956// 3. QUOTE / BILL OF MATERIALS — Teal + orange scheme
957// =============================================================================
958fn generate_quote(_samples_dir: &Path) -> Document {
959 let mut doc = Document::new();
960
961 // Colors: Teal #008B8B, Dark #1A3C3C, Orange #E07020, Light #F0F8F8
962 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
963 doc.set_margins(
964 Length::inches(0.75),
965 Length::inches(0.75),
966 Length::inches(0.75),
967 Length::inches(0.75),
968 );
969 doc.set_title("Quotation QT-2026-0147");
970 doc.set_author("Walter White");
971 doc.set_keywords("quote, BOM, Tensorbee");
972
973 doc.set_header("Tensorbee — Quotation");
974 doc.set_footer("QT-2026-0147 | Page");
975
976 // ── Header Block ──
977 {
978 let mut p = doc.add_paragraph("").alignment(Alignment::Left);
979 p.add_run("TENSORBEE")
980 .bold(true)
981 .size(28.0)
982 .color("008B8B")
983 .font("Helvetica");
984 }
985 doc.add_paragraph("123 Innovation Drive, Suite 400")
986 .alignment(Alignment::Left);
987 doc.add_paragraph("San Francisco, CA 94105 | +1 (415) 555-0199")
988 .alignment(Alignment::Left);
989 doc.add_paragraph("")
990 .border_bottom(BorderStyle::Single, 8, "008B8B");
991
992 // Quote meta
993 doc.add_paragraph("");
994 {
995 let mut tbl = doc.add_table(4, 4).width_pct(100.0);
996 tbl.cell(0, 0).unwrap().set_text("QUOTATION");
997 tbl.cell(0, 0).unwrap().shading("008B8B").grid_span(2);
998 tbl.cell(0, 2).unwrap().set_text("DATE");
999 tbl.cell(0, 2).unwrap().shading("008B8B");
1000 tbl.cell(0, 3).unwrap().set_text("VALID UNTIL");
1001 tbl.cell(0, 3).unwrap().shading("008B8B");
1002 tbl.cell(1, 0).unwrap().set_text("Quote #:");
1003 tbl.cell(1, 1).unwrap().set_text("QT-2026-0147");
1004 tbl.cell(1, 2).unwrap().set_text("Feb 22, 2026");
1005 tbl.cell(1, 3).unwrap().set_text("Mar 22, 2026");
1006 tbl.cell(2, 0).unwrap().set_text("Prepared by:");
1007 tbl.cell(2, 1).unwrap().set_text("Walter White");
1008 tbl.cell(2, 2).unwrap().set_text("Payment:");
1009 tbl.cell(2, 3).unwrap().set_text("Net 30");
1010 tbl.cell(3, 0).unwrap().set_text("Customer:");
1011 tbl.cell(3, 1).unwrap().set_text("Meridian Dynamics LLC");
1012 tbl.cell(3, 2).unwrap().set_text("Currency:");
1013 tbl.cell(3, 3).unwrap().set_text("USD");
1014 }
1015
1016 doc.add_paragraph("");
1017
1018 // ── Bill of Materials ──
1019 {
1020 let mut p = doc.add_paragraph("");
1021 p.add_run("Bill of Materials")
1022 .bold(true)
1023 .size(16.0)
1024 .color("1A3C3C");
1025 }
1026 doc.add_paragraph("");
1027
1028 {
1029 let mut tbl = doc
1030 .add_table(10, 6)
1031 .borders(BorderStyle::Single, 2, "008B8B")
1032 .width_pct(100.0)
1033 .layout_fixed();
1034 // Headers
1035 let hdrs = [
1036 "#",
1037 "Part Number",
1038 "Description",
1039 "Qty",
1040 "Unit Price",
1041 "Total",
1042 ];
1043 for (c, h) in hdrs.iter().enumerate() {
1044 tbl.cell(0, c).unwrap().set_text(h);
1045 tbl.cell(0, c).unwrap().shading("008B8B");
1046 }
1047
1048 let items: Vec<(&str, &str, &str, &str, &str)> = vec![
1049 (
1050 "TB-GPU-A100",
1051 "NVIDIA A100 80GB GPU",
1052 "4",
1053 "$12,500.00",
1054 "$50,000.00",
1055 ),
1056 (
1057 "TB-SRV-R750",
1058 "Dell R750xa Server Chassis",
1059 "2",
1060 "$8,200.00",
1061 "$16,400.00",
1062 ),
1063 (
1064 "TB-RAM-256G",
1065 "256GB DDR5 ECC Memory Module",
1066 "8",
1067 "$890.00",
1068 "$7,120.00",
1069 ),
1070 (
1071 "TB-SSD-3840",
1072 "3.84TB NVMe U.2 SSD",
1073 "8",
1074 "$1,150.00",
1075 "$9,200.00",
1076 ),
1077 (
1078 "TB-NET-CX7",
1079 "ConnectX-7 200GbE NIC",
1080 "4",
1081 "$1,800.00",
1082 "$7,200.00",
1083 ),
1084 (
1085 "TB-SW-48P",
1086 "48-Port 100GbE Switch",
1087 "1",
1088 "$22,000.00",
1089 "$22,000.00",
1090 ),
1091 (
1092 "TB-CAB-RACK",
1093 "42U Server Rack + PDU",
1094 "1",
1095 "$4,500.00",
1096 "$4,500.00",
1097 ),
1098 (
1099 "TB-SVC-INST",
1100 "Installation & Configuration",
1101 "1",
1102 "$8,500.00",
1103 "$8,500.00",
1104 ),
1105 (
1106 "TB-SVC-SUPP",
1107 "3-Year Premium Support",
1108 "1",
1109 "$15,000.00",
1110 "$15,000.00",
1111 ),
1112 ];
1113
1114 for (i, (pn, desc, qty, unit, total)) in items.iter().enumerate() {
1115 let row = i + 1;
1116 tbl.cell(row, 0).unwrap().set_text(&format!("{}", i + 1));
1117 tbl.cell(row, 1).unwrap().set_text(pn);
1118 tbl.cell(row, 2).unwrap().set_text(desc);
1119 tbl.cell(row, 3).unwrap().set_text(qty);
1120 tbl.cell(row, 4).unwrap().set_text(unit);
1121 tbl.cell(row, 5).unwrap().set_text(total);
1122 if i % 2 == 0 {
1123 for c in 0..6 {
1124 tbl.cell(row, c).unwrap().shading("F0F8F8");
1125 }
1126 }
1127 }
1128 }
1129
1130 doc.add_paragraph("");
1131
1132 // ── Totals ──
1133 {
1134 let mut tbl = doc
1135 .add_table(4, 2)
1136 .borders(BorderStyle::Single, 2, "008B8B")
1137 .width(Length::inches(3.5))
1138 .alignment(Alignment::Right);
1139 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1140 tbl.cell(0, 1).unwrap().set_text("$139,920.00");
1141 tbl.cell(1, 0).unwrap().set_text("Shipping & Handling");
1142 tbl.cell(1, 1).unwrap().set_text("$2,500.00");
1143 tbl.cell(2, 0).unwrap().set_text("Tax (8.625%)");
1144 tbl.cell(2, 1).unwrap().set_text("$12,068.10");
1145 tbl.cell(3, 0).unwrap().set_text("TOTAL");
1146 tbl.cell(3, 0).unwrap().shading("E07020");
1147 tbl.cell(3, 1).unwrap().set_text("$154,488.10");
1148 tbl.cell(3, 1).unwrap().shading("E07020");
1149 }
1150
1151 doc.add_paragraph("");
1152
1153 // ── Terms & Conditions ──
1154 {
1155 let mut p = doc.add_paragraph("");
1156 p.add_run("Terms & Conditions")
1157 .bold(true)
1158 .size(14.0)
1159 .color("1A3C3C");
1160 }
1161 doc.add_numbered_list_item(
1162 "This quotation is valid for 30 calendar days from the date of issue.",
1163 0,
1164 );
1165 doc.add_numbered_list_item(
1166 "All prices are in USD and exclusive of applicable taxes unless stated.",
1167 0,
1168 );
1169 doc.add_numbered_list_item("Standard lead time is 4-6 weeks from PO receipt.", 0);
1170 doc.add_numbered_list_item(
1171 "Payment terms: Net 30 from invoice date. 2% discount for payment within 10 days.",
1172 0,
1173 );
1174 doc.add_numbered_list_item(
1175 "Warranty: 3-year manufacturer warranty on all hardware components.",
1176 0,
1177 );
1178 doc.add_numbered_list_item(
1179 "Returns subject to 15% restocking fee if initiated after 14 days.",
1180 0,
1181 );
1182
1183 doc.add_paragraph("");
1184 doc.add_paragraph("")
1185 .border_bottom(BorderStyle::Single, 4, "008B8B");
1186 doc.add_paragraph("");
1187 {
1188 let mut p = doc.add_paragraph("");
1189 p.add_run("Accepted by: ").bold(true);
1190 p.add_run("___________________________ Date: ___________");
1191 }
1192 {
1193 let mut p = doc.add_paragraph("");
1194 p.add_run("Print Name: ").bold(true);
1195 p.add_run("___________________________ Title: ___________");
1196 }
1197
1198 doc
1199}
1200
1201// =============================================================================
1202// 4. INVOICE — Crimson + charcoal scheme
1203// =============================================================================
1204fn generate_invoice(_samples_dir: &Path) -> Document {
1205 let mut doc = Document::new();
1206
1207 // Colors: Crimson #B22222, Charcoal #333333, Light #FAF0F0
1208 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1209 doc.set_margins(
1210 Length::inches(0.75),
1211 Length::inches(0.75),
1212 Length::inches(0.75),
1213 Length::inches(0.75),
1214 );
1215 doc.set_title("Invoice INV-2026-0392");
1216 doc.set_author("Walter White");
1217 doc.set_keywords("invoice, Tensorbee");
1218
1219 doc.set_footer("Tensorbee — Thank you for your business!");
1220
1221 // ── Company & Invoice Header ──
1222 {
1223 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1224 // Company name left, INVOICE right
1225 {
1226 let mut cell = tbl.cell(0, 0).unwrap();
1227 let mut p = cell.add_paragraph("");
1228 p.add_run("TENSORBEE")
1229 .bold(true)
1230 .size(32.0)
1231 .color("B22222")
1232 .font("Helvetica");
1233 }
1234 {
1235 let mut cell = tbl.cell(0, 1).unwrap();
1236 let mut p = cell.add_paragraph("");
1237 p.add_run("INVOICE").bold(true).size(32.0).color("333333");
1238 }
1239 tbl.cell(1, 0)
1240 .unwrap()
1241 .set_text("123 Innovation Drive, Suite 400");
1242 tbl.cell(1, 1).unwrap().set_text("Invoice #: INV-2026-0392");
1243 tbl.cell(2, 0).unwrap().set_text("San Francisco, CA 94105");
1244 tbl.cell(2, 1).unwrap().set_text("Date: February 22, 2026");
1245 tbl.cell(3, 0).unwrap().set_text("walter@tensorbee.com");
1246 tbl.cell(3, 1).unwrap().set_text("Due Date: March 24, 2026");
1247 }
1248
1249 doc.add_paragraph("")
1250 .border_bottom(BorderStyle::Thick, 8, "B22222");
1251 doc.add_paragraph("");
1252
1253 // ── Bill To / Ship To ──
1254 {
1255 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1256 tbl.cell(0, 0).unwrap().set_text("BILL TO");
1257 tbl.cell(0, 0).unwrap().shading("B22222");
1258 tbl.cell(0, 1).unwrap().set_text("SHIP TO");
1259 tbl.cell(0, 1).unwrap().shading("B22222");
1260 tbl.cell(1, 0).unwrap().set_text("Meridian Dynamics LLC");
1261 tbl.cell(1, 1).unwrap().set_text("Meridian Dynamics LLC");
1262 tbl.cell(2, 0).unwrap().set_text("456 Enterprise Blvd");
1263 tbl.cell(2, 1).unwrap().set_text("Attn: Server Room B");
1264 tbl.cell(3, 0).unwrap().set_text("Austin, TX 78701");
1265 tbl.cell(3, 1)
1266 .unwrap()
1267 .set_text("456 Enterprise Blvd, Austin, TX 78701");
1268 }
1269
1270 doc.add_paragraph("");
1271
1272 // ── Line Items ──
1273 {
1274 let mut tbl = doc
1275 .add_table(8, 5)
1276 .borders(BorderStyle::Single, 2, "B22222")
1277 .width_pct(100.0);
1278 let hdrs = ["Description", "Qty", "Unit Price", "Tax", "Amount"];
1279 for (c, h) in hdrs.iter().enumerate() {
1280 tbl.cell(0, c).unwrap().set_text(h);
1281 tbl.cell(0, c).unwrap().shading("B22222");
1282 }
1283 let items = [
1284 (
1285 "ML Infrastructure Setup — Phase 1",
1286 "1",
1287 "$45,000.00",
1288 "$3,881.25",
1289 "$48,881.25",
1290 ),
1291 (
1292 "Data Pipeline Development (160 hrs)",
1293 "160",
1294 "$225.00",
1295 "$3,105.00",
1296 "$39,105.00",
1297 ),
1298 (
1299 "GPU Cluster Configuration",
1300 "1",
1301 "$12,000.00",
1302 "$1,035.00",
1303 "$13,035.00",
1304 ),
1305 (
1306 "API Gateway Implementation",
1307 "1",
1308 "$18,500.00",
1309 "$1,595.63",
1310 "$20,095.63",
1311 ),
1312 (
1313 "Load Testing & QA (80 hrs)",
1314 "80",
1315 "$195.00",
1316 "$1,345.50",
1317 "$16,945.50",
1318 ),
1319 (
1320 "Documentation & Training",
1321 "1",
1322 "$8,000.00",
1323 "$690.00",
1324 "$8,690.00",
1325 ),
1326 (
1327 "Project Management (3 months)",
1328 "3",
1329 "$6,500.00",
1330 "$1,679.63",
1331 "$21,179.63",
1332 ),
1333 ];
1334 for (i, (desc, qty, unit, tax, amt)) in items.iter().enumerate() {
1335 let r = i + 1;
1336 tbl.cell(r, 0).unwrap().set_text(desc);
1337 tbl.cell(r, 1).unwrap().set_text(qty);
1338 tbl.cell(r, 2).unwrap().set_text(unit);
1339 tbl.cell(r, 3).unwrap().set_text(tax);
1340 tbl.cell(r, 4).unwrap().set_text(amt);
1341 if i % 2 == 0 {
1342 for c in 0..5 {
1343 tbl.cell(r, c).unwrap().shading("FAF0F0");
1344 }
1345 }
1346 }
1347 }
1348
1349 doc.add_paragraph("");
1350
1351 // ── Totals ──
1352 {
1353 let mut tbl = doc
1354 .add_table(4, 2)
1355 .borders(BorderStyle::Single, 2, "B22222")
1356 .width(Length::inches(3.0))
1357 .alignment(Alignment::Right);
1358 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1359 tbl.cell(0, 1).unwrap().set_text("$154,600.00");
1360 tbl.cell(1, 0).unwrap().set_text("Tax (8.625%)");
1361 tbl.cell(1, 1).unwrap().set_text("$13,334.25");
1362 tbl.cell(2, 0).unwrap().set_text("Discount (5%)");
1363 tbl.cell(2, 1).unwrap().set_text("-$7,730.00");
1364 tbl.cell(3, 0).unwrap().set_text("AMOUNT DUE");
1365 tbl.cell(3, 0).unwrap().shading("B22222");
1366 tbl.cell(3, 1).unwrap().set_text("$160,204.25");
1367 tbl.cell(3, 1).unwrap().shading("B22222");
1368 }
1369
1370 doc.add_paragraph("");
1371 doc.add_paragraph("");
1372
1373 // ── Payment Details ──
1374 {
1375 let mut p = doc.add_paragraph("");
1376 p.add_run("Payment Details")
1377 .bold(true)
1378 .size(14.0)
1379 .color("333333");
1380 }
1381 doc.add_paragraph("Bank: Silicon Valley Bank")
1382 .indent_left(Length::inches(0.3));
1383 doc.add_paragraph("Account: Tensorbee Inc. — 0847-2953-1120")
1384 .indent_left(Length::inches(0.3));
1385 doc.add_paragraph("Routing: 121140399")
1386 .indent_left(Length::inches(0.3));
1387 doc.add_paragraph("Swift: SVBKUS6S")
1388 .indent_left(Length::inches(0.3));
1389
1390 doc.add_paragraph("");
1391
1392 doc.add_paragraph("Please include invoice number INV-2026-0392 in the payment reference.")
1393 .shading("FAF0F0")
1394 .border_all(BorderStyle::Single, 2, "B22222");
1395
1396 doc
1397}
1398
1399// =============================================================================
1400// 5. REPORT — Forest green + earth tones, with images & hierarchical sections
1401// =============================================================================
1402fn generate_report(_samples_dir: &Path) -> Document {
1403 let mut doc = Document::new();
1404
1405 // Colors: Forest #2D5016, Sage #6B8E23, Earth #8B7355, Cream #FFFAF0
1406 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1407 doc.set_margins(
1408 Length::inches(1.0),
1409 Length::inches(1.0),
1410 Length::inches(1.0),
1411 Length::inches(1.0),
1412 );
1413 doc.set_title("Q4 2025 Environmental Impact Report");
1414 doc.set_author("Walter White");
1415 doc.set_subject("Quarterly Environmental Report");
1416 doc.set_keywords("environment, sustainability, report, Tensorbee");
1417
1418 doc.set_different_first_page(true);
1419 doc.set_first_page_header("");
1420 doc.set_header("Tensorbee — Q4 2025 Environmental Impact Report");
1421 doc.set_footer("CONFIDENTIAL — Page");
1422
1423 // ── Cover ──
1424 let cover_bg = create_sample_png(612, 792, [20, 50, 15]);
1425 doc.add_background_image(&cover_bg, "report_cover.png");
1426
1427 for _ in 0..5 {
1428 doc.add_paragraph("");
1429 }
1430 {
1431 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1432 p.add_run("Q4 2025")
1433 .bold(true)
1434 .size(48.0)
1435 .color("FFFFFF")
1436 .font("Georgia");
1437 }
1438 {
1439 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1440 p.add_run("Environmental Impact Report")
1441 .size(24.0)
1442 .color("90EE90")
1443 .font("Georgia")
1444 .italic(true);
1445 }
1446 doc.add_paragraph("");
1447 {
1448 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1449 p.add_run("Tensorbee — Sustainability Division")
1450 .size(14.0)
1451 .color("C0C0C0");
1452 }
1453 {
1454 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1455 p.add_run("Prepared by Walter White, Chief Sustainability Officer")
1456 .size(11.0)
1457 .color("AAAAAA");
1458 }
1459
1460 // ── TOC ──
1461 doc.add_paragraph("").page_break_before(true);
1462 doc.insert_toc(doc.content_count(), 3);
1463
1464 // ── Section 1: Executive Overview ──
1465 doc.add_paragraph("").page_break_before(true);
1466 doc.add_paragraph("Executive Overview").style("Heading1");
1467 doc.add_paragraph(
1468 "This report presents Tensorbee's environmental performance for Q4 2025. Our \
1469 sustainability initiatives have yielded a 34% reduction in carbon emissions compared \
1470 to Q4 2024, exceeding our target of 25%. Key achievements include the transition to \
1471 100% renewable energy in our primary data centers and the launch of our carbon offset \
1472 marketplace.",
1473 )
1474 .first_line_indent(Length::inches(0.3));
1475
1476 // Image: performance chart
1477 let chart_img = create_chart_png(400, 200);
1478 doc.add_paragraph("");
1479 doc.add_picture(
1480 &chart_img,
1481 "performance_chart.png",
1482 Length::inches(5.0),
1483 Length::inches(2.5),
1484 )
1485 .alignment(Alignment::Center);
1486 {
1487 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1488 p.add_run("Figure 1: Quarterly Carbon Emissions (tonnes CO2e)")
1489 .italic(true)
1490 .size(9.0)
1491 .color("666666");
1492 }
1493
1494 // ── Section 2: Energy Consumption ──
1495 doc.add_paragraph("").page_break_before(true);
1496 doc.add_paragraph("Energy Consumption").style("Heading1");
1497
1498 doc.add_paragraph("Data Center Operations")
1499 .style("Heading2");
1500 doc.add_paragraph(
1501 "Our three primary data centers consumed a combined 4.2 GWh in Q4 2025, \
1502 a 12% reduction from Q3 2025 driven by improved cooling efficiency and \
1503 server consolidation.",
1504 );
1505
1506 // Data table
1507 {
1508 let mut tbl = doc
1509 .add_table(5, 4)
1510 .borders(BorderStyle::Single, 4, "2D5016")
1511 .width_pct(100.0);
1512 let hdrs = ["Data Center", "Capacity (MW)", "Usage (GWh)", "PUE"];
1513 for (c, h) in hdrs.iter().enumerate() {
1514 tbl.cell(0, c).unwrap().set_text(h);
1515 tbl.cell(0, c).unwrap().shading("2D5016");
1516 }
1517 let rows = [
1518 ("San Francisco (Primary)", "3.2", "1.8", "1.12"),
1519 ("Dublin (EU)", "2.1", "1.2", "1.18"),
1520 ("Singapore (APAC)", "1.8", "1.2", "1.24"),
1521 ("TOTAL", "7.1", "4.2", "1.17 avg"),
1522 ];
1523 for (i, (dc, cap, use_, pue)) in rows.iter().enumerate() {
1524 tbl.cell(i + 1, 0).unwrap().set_text(dc);
1525 tbl.cell(i + 1, 1).unwrap().set_text(cap);
1526 tbl.cell(i + 1, 2).unwrap().set_text(use_);
1527 tbl.cell(i + 1, 3).unwrap().set_text(pue);
1528 if i == 3 {
1529 for c in 0..4 {
1530 tbl.cell(i + 1, c).unwrap().shading("E2EFDA");
1531 }
1532 }
1533 }
1534 }
1535
1536 doc.add_paragraph("");
1537
1538 doc.add_paragraph("Renewable Energy Mix").style("Heading2");
1539 doc.add_paragraph("Breakdown of energy sources across all facilities:");
1540
1541 doc.add_bullet_list_item("Solar PV: 42% (1.76 GWh)", 0);
1542 doc.add_bullet_list_item("Wind Power Purchase Agreements: 38% (1.60 GWh)", 0);
1543 doc.add_bullet_list_item("Hydroelectric: 12% (0.50 GWh)", 0);
1544 doc.add_bullet_list_item("Grid (non-renewable): 8% (0.34 GWh)", 0);
1545
1546 // ── Section 3: Water & Waste ──
1547 doc.add_paragraph("Water & Waste Management")
1548 .style("Heading1");
1549
1550 doc.add_paragraph("Water Usage").style("Heading2");
1551 doc.add_paragraph(
1552 "Total water consumption was 12.4 million gallons, a 15% reduction from Q3. \
1553 Our closed-loop cooling systems now recycle 78% of water used in cooling operations.",
1554 );
1555
1556 doc.add_paragraph("Waste Reduction").style("Heading2");
1557 doc.add_paragraph("E-waste management results for Q4:")
1558 .keep_with_next(true);
1559 {
1560 let mut tbl = doc
1561 .add_table(4, 3)
1562 .borders(BorderStyle::Single, 2, "6B8E23")
1563 .width_pct(80.0);
1564 tbl.cell(0, 0).unwrap().set_text("Category");
1565 tbl.cell(0, 0).unwrap().shading("6B8E23");
1566 tbl.cell(0, 1).unwrap().set_text("Weight (kg)");
1567 tbl.cell(0, 1).unwrap().shading("6B8E23");
1568 tbl.cell(0, 2).unwrap().set_text("Recycled %");
1569 tbl.cell(0, 2).unwrap().shading("6B8E23");
1570 let rows = [
1571 ("Server Hardware", "2,450", "94%"),
1572 ("Networking Equipment", "820", "91%"),
1573 ("Storage Media", "340", "99%"),
1574 ];
1575 for (i, (cat, wt, pct)) in rows.iter().enumerate() {
1576 tbl.cell(i + 1, 0).unwrap().set_text(cat);
1577 tbl.cell(i + 1, 1).unwrap().set_text(wt);
1578 tbl.cell(i + 1, 2).unwrap().set_text(pct);
1579 }
1580 }
1581
1582 // ── Section 4: Initiatives ──
1583 doc.add_paragraph("").page_break_before(true);
1584 doc.add_paragraph("Q1 2026 Initiatives").style("Heading1");
1585
1586 doc.add_paragraph("Planned Programs").style("Heading2");
1587 doc.add_numbered_list_item(
1588 "Deploy on-site battery storage at SF data center (2 MWh capacity)",
1589 0,
1590 );
1591 doc.add_numbered_list_item("Pilot immersion cooling in Dublin facility", 0);
1592 doc.add_numbered_list_item("Launch employee commute carbon offset program", 0);
1593 doc.add_numbered_list_item("Achieve ISO 14001 certification for Singapore facility", 0);
1594
1595 doc.add_paragraph("Investment Targets").style("Heading2");
1596 doc.add_paragraph("Sustainability CapEx allocation for FY2026:")
1597 .keep_with_next(true);
1598 {
1599 let mut tbl = doc
1600 .add_table(5, 2)
1601 .borders(BorderStyle::Single, 4, "2D5016");
1602 tbl.cell(0, 0).unwrap().set_text("Initiative");
1603 tbl.cell(0, 0).unwrap().shading("2D5016");
1604 tbl.cell(0, 1).unwrap().set_text("Budget");
1605 tbl.cell(0, 1).unwrap().shading("2D5016");
1606 let rows = [
1607 ("Battery Storage", "$1.2M"),
1608 ("Immersion Cooling Pilot", "$800K"),
1609 ("Solar Panel Expansion", "$2.1M"),
1610 ("Carbon Credits", "$500K"),
1611 ];
1612 for (i, (init, budget)) in rows.iter().enumerate() {
1613 tbl.cell(i + 1, 0).unwrap().set_text(init);
1614 tbl.cell(i + 1, 1).unwrap().set_text(budget);
1615 if i % 2 == 0 {
1616 tbl.cell(i + 1, 0).unwrap().shading("FFFAF0");
1617 tbl.cell(i + 1, 1).unwrap().shading("FFFAF0");
1618 }
1619 }
1620 }
1621
1622 // Image: sustainability roadmap
1623 let roadmap_img = create_sample_png(500, 100, [40, 80, 30]);
1624 doc.add_paragraph("");
1625 doc.add_picture(
1626 &roadmap_img,
1627 "roadmap.png",
1628 Length::inches(6.0),
1629 Length::inches(1.2),
1630 )
1631 .alignment(Alignment::Center);
1632 {
1633 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1634 p.add_run("Figure 2: 2026 Sustainability Roadmap")
1635 .italic(true)
1636 .size(9.0)
1637 .color("666666");
1638 }
1639
1640 doc.add_paragraph("");
1641 doc.add_paragraph(
1642 "For questions about this report, contact Walter White at sustainability@tensorbee.com.",
1643 )
1644 .shading("FFFAF0")
1645 .border_all(BorderStyle::Single, 2, "2D5016");
1646
1647 doc
1648}Set the first-page footer text.
Examples found in repository?
34fn generate_feature_showcase(path: &Path) {
35 let mut doc = Document::new();
36
37 // =========================================================================
38 // PAGE SETUP & METADATA
39 // =========================================================================
40 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
41 doc.set_margins(
42 Length::inches(1.0), // top
43 Length::inches(1.0), // right
44 Length::inches(1.0), // bottom
45 Length::inches(1.0), // left
46 );
47 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
48 doc.set_gutter(Length::twips(0));
49
50 doc.set_title("rdocx Feature Showcase");
51 doc.set_author("rdocx Sample Generator");
52 doc.set_subject("Comprehensive feature demonstration");
53 doc.set_keywords("rdocx, docx, rust, sample");
54
55 // Header & Footer
56 doc.set_header("rdocx Feature Showcase");
57 doc.set_footer("Generated by rdocx — Page");
58
59 // Different first page header
60 doc.set_different_first_page(true);
61 doc.set_first_page_header("rdocx");
62 doc.set_first_page_footer("Feature Showcase — Cover Page");
63
64 // =========================================================================
65 // PAGE 1: COVER PAGE — background image, run formatting
66 // =========================================================================
67 let bg_cover = create_sample_png(612, 792, [30, 60, 120]);
68 doc.add_background_image(&bg_cover, "cover_bg.png");
69
70 doc.add_paragraph(""); // spacer
71 doc.add_paragraph(""); // spacer
72 doc.add_paragraph(""); // spacer
73
74 {
75 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
76 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
77 }
78 {
79 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
80 p.add_run("Feature Showcase")
81 .size(28.0)
82 .color("FFFFFF")
83 .italic(true);
84 }
85
86 doc.add_paragraph(""); // spacer
87
88 {
89 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
90 p.add_run("A comprehensive demonstration of every feature")
91 .size(14.0)
92 .color("CCDDFF");
93 }
94 {
95 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
96 p.add_run("provided by the rdocx Rust crate for DOCX generation.")
97 .size(14.0)
98 .color("CCDDFF");
99 }
100
101 // =========================================================================
102 // PAGE 2: TEXT FORMATTING
103 // =========================================================================
104 doc.add_paragraph("").page_break_before(true);
105
106 doc.add_paragraph("1. Text Formatting").style("Heading1");
107
108 doc.add_paragraph("This section demonstrates paragraph and run-level formatting options.");
109 doc.add_paragraph("");
110
111 // --- Paragraph alignment ---
112 doc.add_paragraph("Paragraph Alignment").style("Heading2");
113
114 doc.add_paragraph("This paragraph is left-aligned (the default).")
115 .alignment(Alignment::Left);
116 doc.add_paragraph("This paragraph is center-aligned.")
117 .alignment(Alignment::Center);
118 doc.add_paragraph("This paragraph is right-aligned.")
119 .alignment(Alignment::Right);
120 doc.add_paragraph(
121 "This paragraph is justified. To demonstrate justified text properly, it needs \
122 to be long enough to span multiple lines so the word spacing adjustment is visible \
123 across the full width of the text area on the page.",
124 )
125 .alignment(Alignment::Justify);
126
127 doc.add_paragraph("");
128
129 // --- Run formatting ---
130 doc.add_paragraph("Run Formatting").style("Heading2");
131
132 {
133 let mut p = doc.add_paragraph("");
134 p.add_run("Bold text").bold(true);
135 p.add_run(" | ");
136 p.add_run("Italic text").italic(true);
137 p.add_run(" | ");
138 p.add_run("Bold + Italic").bold(true).italic(true);
139 }
140 {
141 let mut p = doc.add_paragraph("");
142 p.add_run("Single underline").underline(true);
143 p.add_run(" | ");
144 p.add_run("Strikethrough").strike(true);
145 p.add_run(" | ");
146 p.add_run("Double strikethrough").double_strike(true);
147 }
148 {
149 let mut p = doc.add_paragraph("");
150 p.add_run("Red text").color("FF0000");
151 p.add_run(" | ");
152 p.add_run("Blue text").color("0000FF");
153 p.add_run(" | ");
154 p.add_run("Green text").color("00AA00");
155 p.add_run(" | ");
156 p.add_run("Highlighted").highlight("FFFF00");
157 }
158 {
159 let mut p = doc.add_paragraph("");
160 p.add_run("8pt small").size(8.0);
161 p.add_run(" | ");
162 p.add_run("11pt normal").size(11.0);
163 p.add_run(" | ");
164 p.add_run("16pt large").size(16.0);
165 p.add_run(" | ");
166 p.add_run("24pt extra-large").size(24.0);
167 }
168 {
169 let mut p = doc.add_paragraph("");
170 p.add_run("Arial font").font("Arial");
171 p.add_run(" | ");
172 p.add_run("Times New Roman font").font("Times New Roman");
173 p.add_run(" | ");
174 p.add_run("Courier New font").font("Courier New");
175 }
176 {
177 let mut p = doc.add_paragraph("");
178 p.add_run("Normal");
179 p.add_run(" H").size(11.0);
180 p.add_run("2").subscript();
181 p.add_run("O (subscript)").size(11.0);
182 p.add_run(" | E = mc").size(11.0);
183 p.add_run("2").superscript();
184 p.add_run(" (superscript)").size(11.0);
185 }
186 {
187 let mut p = doc.add_paragraph("");
188 p.add_run("ALL CAPS").all_caps(true);
189 p.add_run(" | ");
190 p.add_run("Small Caps").small_caps(true);
191 p.add_run(" | ");
192 p.add_run("Expanded spacing")
193 .character_spacing(Length::twips(40));
194 }
195
196 doc.add_paragraph("");
197
198 // --- Paragraph formatting ---
199 doc.add_paragraph("Paragraph Formatting").style("Heading2");
200
201 doc.add_paragraph("Paragraph with shading (light green background)")
202 .shading("E2EFDA");
203
204 doc.add_paragraph("Paragraph with bottom border")
205 .border_bottom(BorderStyle::Single, 6, "2E75B6");
206
207 doc.add_paragraph("Paragraph with all borders")
208 .border_all(BorderStyle::Single, 4, "FF0000");
209
210 doc.add_paragraph("Paragraph with 1-inch left indent and hanging indent")
211 .indent_left(Length::inches(1.0))
212 .hanging_indent(Length::inches(0.5));
213
214 doc.add_paragraph("Paragraph with first-line indent of 0.5 inches")
215 .first_line_indent(Length::inches(0.5));
216
217 doc.add_paragraph("Paragraph with extra space before (24pt) and after (12pt)")
218 .space_before(Length::pt(24.0))
219 .space_after(Length::pt(12.0));
220
221 doc.add_paragraph(
222 "Paragraph with double line spacing. This text should have extra vertical \
223 space between lines to demonstrate the line_spacing_multiple setting.",
224 )
225 .line_spacing_multiple(2.0);
226
227 doc.add_paragraph("Paragraph with keep-with-next (won't break from the next paragraph)")
228 .keep_with_next(true);
229 doc.add_paragraph("(This stays with the paragraph above.)");
230
231 // =========================================================================
232 // PAGE 3: LISTS & TAB STOPS
233 // =========================================================================
234 doc.add_paragraph("").page_break_before(true);
235
236 doc.add_paragraph("2. Lists").style("Heading1");
237
238 doc.add_paragraph("Bullet List").style("Heading2");
239
240 doc.add_bullet_list_item("First bullet item", 0);
241 doc.add_bullet_list_item("Second bullet item", 0);
242 doc.add_bullet_list_item("Nested level 1", 1);
243 doc.add_bullet_list_item("Nested level 2", 2);
244 doc.add_bullet_list_item("Back to level 1", 1);
245 doc.add_bullet_list_item("Third bullet item", 0);
246
247 doc.add_paragraph("");
248
249 doc.add_paragraph("Numbered List").style("Heading2");
250
251 doc.add_numbered_list_item("First numbered item", 0);
252 doc.add_numbered_list_item("Second numbered item", 0);
253 doc.add_numbered_list_item("Sub-item A", 1);
254 doc.add_numbered_list_item("Sub-item B", 1);
255 doc.add_numbered_list_item("Third numbered item", 0);
256
257 doc.add_paragraph("");
258
259 // --- Tab stops ---
260 doc.add_paragraph("Tab Stops").style("Heading2");
261
262 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
263 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
264 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
265 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
266 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
267
268 doc.add_paragraph("Item\t........\tPrice")
269 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
270 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
271 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
272
273 doc.add_paragraph("Widget A\t........\t$19.99")
274 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
275 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
276 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
277
278 doc.add_paragraph("Gadget B\t________\t$249.50")
279 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
280 .add_tab_stop_with_leader(
281 TabAlignment::Right,
282 Length::inches(4.0),
283 TabLeader::Underscore,
284 )
285 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
286
287 // =========================================================================
288 // PAGE 4: TABLES
289 // =========================================================================
290 doc.add_paragraph("").page_break_before(true);
291
292 doc.add_paragraph("3. Tables").style("Heading1");
293
294 // --- Basic table with borders ---
295 doc.add_paragraph("Basic Table with Borders")
296 .style("Heading2");
297
298 {
299 let mut tbl = doc.add_table(4, 3);
300 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
301
302 // Header row
303 for col in 0..3 {
304 tbl.cell(0, col).unwrap().shading("2E75B6");
305 }
306 tbl.cell(0, 0).unwrap().set_text("Name");
307 tbl.cell(0, 1).unwrap().set_text("Role");
308 tbl.cell(0, 2).unwrap().set_text("Location");
309
310 tbl.cell(1, 0).unwrap().set_text("Alice Johnson");
311 tbl.cell(1, 1).unwrap().set_text("Engineering Lead");
312 tbl.cell(1, 2).unwrap().set_text("New York");
313
314 tbl.cell(2, 0).unwrap().set_text("Bob Smith");
315 tbl.cell(2, 1).unwrap().set_text("Product Manager");
316 tbl.cell(2, 2).unwrap().set_text("San Francisco");
317
318 tbl.cell(3, 0).unwrap().set_text("Carol Davis");
319 tbl.cell(3, 1).unwrap().set_text("Designer");
320 tbl.cell(3, 2).unwrap().set_text("London");
321 }
322
323 doc.add_paragraph("");
324
325 // --- Table with cell merging ---
326 doc.add_paragraph("Table with Cell Merging & Vertical Alignment")
327 .style("Heading2");
328
329 {
330 let mut tbl = doc.add_table(4, 4);
331 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
332 tbl = tbl.width_pct(100.0);
333
334 // Header spanning all columns
335 tbl.cell(0, 0).unwrap().set_text("Quarterly Revenue Report");
336 tbl.cell(0, 0).unwrap().shading("1F4E79");
337 tbl.cell(0, 0).unwrap().grid_span(4);
338
339 // Sub-header
340 tbl.cell(1, 0).unwrap().set_text("Region");
341 tbl.cell(1, 0).unwrap().shading("D6E4F0");
342 tbl.cell(1, 1).unwrap().set_text("Q1");
343 tbl.cell(1, 1).unwrap().shading("D6E4F0");
344 tbl.cell(1, 2).unwrap().set_text("Q2");
345 tbl.cell(1, 2).unwrap().shading("D6E4F0");
346 tbl.cell(1, 3).unwrap().set_text("Total");
347 tbl.cell(1, 3).unwrap().shading("D6E4F0");
348
349 // Data
350 tbl.cell(2, 0).unwrap().set_text("North America");
351 tbl.cell(2, 1).unwrap().set_text("$2.4M");
352 tbl.cell(2, 2).unwrap().set_text("$2.7M");
353 tbl.cell(2, 3).unwrap().set_text("$5.1M");
354
355 tbl.cell(3, 0).unwrap().set_text("Europe");
356 tbl.cell(3, 1).unwrap().set_text("$1.8M");
357 tbl.cell(3, 2).unwrap().set_text("$2.0M");
358 tbl.cell(3, 3).unwrap().set_text("$3.8M");
359
360 // Vertical alignment on data cells
361 tbl.cell(2, 3)
362 .unwrap()
363 .vertical_alignment(VerticalAlignment::Center);
364 tbl.cell(3, 3)
365 .unwrap()
366 .vertical_alignment(VerticalAlignment::Bottom);
367 }
368
369 doc.add_paragraph("");
370
371 // --- Table with vertical merge ---
372 doc.add_paragraph("Table with Vertical Merge")
373 .style("Heading2");
374
375 {
376 let mut tbl = doc.add_table(4, 3);
377 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
378
379 tbl.cell(0, 0).unwrap().set_text("Category");
380 tbl.cell(0, 0).unwrap().shading("E2EFDA");
381 tbl.cell(0, 1).unwrap().set_text("Item");
382 tbl.cell(0, 1).unwrap().shading("E2EFDA");
383 tbl.cell(0, 2).unwrap().set_text("Price");
384 tbl.cell(0, 2).unwrap().shading("E2EFDA");
385
386 // "Hardware" spans rows 1-2
387 tbl.cell(1, 0).unwrap().set_text("Hardware");
388 tbl.cell(1, 0).unwrap().v_merge_restart();
389 tbl.cell(1, 1).unwrap().set_text("Laptop");
390 tbl.cell(1, 2).unwrap().set_text("$1,200");
391
392 tbl.cell(2, 0).unwrap().v_merge_continue();
393 tbl.cell(2, 1).unwrap().set_text("Monitor");
394 tbl.cell(2, 2).unwrap().set_text("$450");
395
396 // "Software" on row 3
397 tbl.cell(3, 0).unwrap().set_text("Software");
398 tbl.cell(3, 1).unwrap().set_text("IDE License");
399 tbl.cell(3, 2).unwrap().set_text("$200/yr");
400 }
401
402 doc.add_paragraph("");
403
404 // --- Nested table ---
405 doc.add_paragraph("Nested Table").style("Heading2");
406
407 {
408 let mut tbl = doc.add_table(2, 2);
409 tbl = tbl.borders(BorderStyle::Single, 6, "2E75B6");
410
411 tbl.cell(0, 0).unwrap().set_text("Outer Cell (0,0)");
412 tbl.cell(0, 1).unwrap().set_text("Outer Cell (0,1)");
413 tbl.cell(1, 0).unwrap().set_text("Outer Cell (1,0)");
414
415 // Nested table inside cell (1,1)
416 {
417 let mut cell = tbl.cell(1, 1).unwrap();
418 cell.set_text("Contains nested table:");
419 let mut nested = cell.add_table(2, 2);
420 nested = nested.borders(BorderStyle::Single, 2, "FF6600");
421 nested.cell(0, 0).unwrap().set_text("Inner A");
422 nested.cell(0, 1).unwrap().set_text("Inner B");
423 nested.cell(1, 0).unwrap().set_text("Inner C");
424 nested.cell(1, 1).unwrap().set_text("Inner D");
425 }
426 }
427
428 // =========================================================================
429 // PAGE 5: IMAGES
430 // =========================================================================
431 doc.add_paragraph("").page_break_before(true);
432
433 doc.add_paragraph("4. Images").style("Heading1");
434
435 doc.add_paragraph("Inline Image").style("Heading2");
436
437 doc.add_paragraph("Below is an inline image (200x50 pixels, blue gradient):");
438 let inline_img = create_sample_png(200, 50, [0, 80, 200]);
439 doc.add_picture(
440 &inline_img,
441 "inline_chart.png",
442 Length::inches(3.0),
443 Length::inches(0.75),
444 );
445
446 doc.add_paragraph("");
447
448 doc.add_paragraph("Header Image").style("Heading2");
449
450 // Replace the text-only header with an image header
451 let header_img = create_sample_png(400, 40, [40, 40, 40]);
452 doc.set_header_image(
453 &header_img,
454 "header_logo.png",
455 Length::inches(2.0),
456 Length::inches(0.2),
457 );
458
459 doc.add_paragraph(
460 "The document header has been replaced with an inline image. \
461 Check the header area at the top of this page.",
462 );
463
464 doc.add_paragraph("");
465 doc.add_paragraph(
466 "Note: The cover page uses a full-page background image behind the text, \
467 demonstrated on page 1 via add_background_image().",
468 );
469
470 // =========================================================================
471 // PAGE 6: CONTENT MANIPULATION — placeholder replacement, insertion
472 // =========================================================================
473 doc.add_paragraph("").page_break_before(true);
474
475 doc.add_paragraph("5. Content Manipulation")
476 .style("Heading1");
477
478 // --- Placeholder replacement ---
479 doc.add_paragraph("Placeholder Replacement")
480 .style("Heading2");
481
482 doc.add_paragraph(
483 "Before replacement, this document contained {{customer}} and {{date}} placeholders.",
484 );
485
486 {
487 let mut p = doc.add_paragraph("");
488 p.add_run("Customer: ").bold(true);
489 p.add_run("{{customer}}");
490 }
491 {
492 let mut p = doc.add_paragraph("");
493 p.add_run("Date: ").bold(true);
494 p.add_run("{{date}}");
495 }
496 doc.add_paragraph("Reference: {{ref_number}}");
497
498 // Table with placeholders
499 {
500 let mut tbl = doc.add_table(3, 2);
501 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
502 tbl.cell(0, 0).unwrap().set_text("Field");
503 tbl.cell(0, 0).unwrap().shading("D6E4F0");
504 tbl.cell(0, 1).unwrap().set_text("Value");
505 tbl.cell(0, 1).unwrap().shading("D6E4F0");
506 tbl.cell(1, 0).unwrap().set_text("Project");
507 tbl.cell(1, 1).unwrap().set_text("{{project}}");
508 tbl.cell(2, 0).unwrap().set_text("Status");
509 tbl.cell(2, 1).unwrap().set_text("{{status}}");
510 }
511
512 // Perform replacements
513 let mut replacements = HashMap::new();
514 replacements.insert("{{customer}}", "Acme Corporation");
515 replacements.insert("{{date}}", "February 22, 2026");
516 replacements.insert("{{ref_number}}", "REF-2026-001");
517 replacements.insert("{{project}}", "Infrastructure Upgrade");
518 replacements.insert("{{status}}", "In Progress");
519 let replace_count = doc.replace_all(&replacements);
520
521 doc.add_paragraph("");
522 doc.add_paragraph(&format!(
523 "(Replaced {} placeholders above — in body text and table cells)",
524 replace_count
525 ));
526
527 doc.add_paragraph("");
528
529 // --- Content insertion ---
530 doc.add_paragraph("Content Insertion").style("Heading2");
531
532 doc.add_paragraph("Section A: First section of content.");
533 doc.add_paragraph("Section C: Third section of content.");
534
535 // Insert "Section B" between A and C
536 if let Some(idx) = doc.find_content_index("Section C") {
537 doc.insert_paragraph(
538 idx,
539 "Section B: Inserted between A and C using find_content_index().",
540 );
541 }
542
543 doc.add_paragraph("");
544 doc.add_paragraph(
545 "The paragraph above ('Section B') was inserted at a specific position \
546 using find_content_index() + insert_paragraph().",
547 );
548
549 // =========================================================================
550 // PAGE 7: LANDSCAPE — section break, wide table
551 // =========================================================================
552 doc.add_paragraph("").section_break(SectionBreak::NextPage);
553
554 doc.add_paragraph("6. Mixed Page Orientation")
555 .style("Heading1");
556
557 doc.add_paragraph(
558 "This page is in LANDSCAPE orientation. It was created using a section break \
559 followed by section_landscape(). This is useful for wide tables or charts.",
560 );
561
562 doc.add_paragraph("");
563
564 // Wide table for landscape
565 {
566 let mut tbl = doc.add_table(4, 7);
567 tbl = tbl.borders(BorderStyle::Single, 4, "2E75B6");
568
569 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
570 for (col, h) in headers.iter().enumerate() {
571 tbl.cell(0, col).unwrap().set_text(h);
572 tbl.cell(0, col).unwrap().shading("2E75B6");
573 }
574
575 let data = [
576 [
577 "North America",
578 "$1.2M",
579 "$1.3M",
580 "$1.4M",
581 "$1.5M",
582 "$1.6M",
583 "$7.0M",
584 ],
585 [
586 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
587 ],
588 [
589 "Asia Pacific",
590 "$0.5M",
591 "$0.6M",
592 "$0.7M",
593 "$0.7M",
594 "$0.8M",
595 "$3.3M",
596 ],
597 ];
598 for (row_idx, row_data) in data.iter().enumerate() {
599 for (col, val) in row_data.iter().enumerate() {
600 tbl.cell(row_idx + 1, col).unwrap().set_text(val);
601 }
602 }
603 }
604
605 // End landscape, return to portrait
606 doc.add_paragraph("")
607 .section_break(SectionBreak::NextPage)
608 .section_landscape();
609
610 // =========================================================================
611 // PAGE 8: BACK TO PORTRAIT — styles, final notes
612 // =========================================================================
613 doc.add_paragraph("7. Custom Styles & Summary")
614 .style("Heading1");
615
616 doc.add_paragraph(
617 "This final page is back in portrait orientation after a section break. \
618 The document has demonstrated:",
619 );
620
621 doc.add_bullet_list_item(
622 "Page setup: size, margins, header/footer distance, gutter",
623 0,
624 );
625 doc.add_bullet_list_item("Document metadata: title, author, subject, keywords", 0);
626 doc.add_bullet_list_item("Headers and footers: text, images, different first page", 0);
627 doc.add_bullet_list_item("Background images: full-page behind text", 0);
628 doc.add_bullet_list_item(
629 "Text formatting: bold, italic, underline, strike, color, size, font",
630 0,
631 );
632 doc.add_bullet_list_item(
633 "Advanced run formatting: superscript, subscript, caps, spacing",
634 0,
635 );
636 doc.add_bullet_list_item(
637 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
638 0,
639 );
640 doc.add_bullet_list_item("Bullet and numbered lists with nesting levels", 0);
641 doc.add_bullet_list_item("Tab stops with dot/underscore leaders", 0);
642 doc.add_bullet_list_item(
643 "Tables: borders, shading, column spans, row spans, nesting",
644 0,
645 );
646 doc.add_bullet_list_item("Vertical alignment in table cells", 0);
647 doc.add_bullet_list_item("Inline images", 0);
648 doc.add_bullet_list_item("Placeholder replacement in body and table cells", 0);
649 doc.add_bullet_list_item("Content insertion at specific positions", 0);
650 doc.add_bullet_list_item(
651 "Section breaks with mixed portrait/landscape orientation",
652 0,
653 );
654
655 doc.add_paragraph("");
656 doc.add_paragraph("All features above were built entirely from scratch using the rdocx API.")
657 .alignment(Alignment::Center)
658 .shading("E2EFDA")
659 .border_all(BorderStyle::Single, 2, "00AA00");
660
661 doc.save(path).unwrap();
662}More examples
86fn generate_feature_showcase(_samples_dir: &Path) -> Document {
87 let mut doc = Document::new();
88
89 // ── Page Setup & Metadata ──
90 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
91 doc.set_margins(
92 Length::inches(1.0),
93 Length::inches(1.0),
94 Length::inches(1.0),
95 Length::inches(1.0),
96 );
97 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
98 doc.set_gutter(Length::twips(0));
99 doc.set_title("rdocx Feature Showcase");
100 doc.set_author("rdocx Sample Generator");
101 doc.set_subject("Comprehensive feature demonstration");
102 doc.set_keywords("rdocx, docx, rust, sample, showcase");
103
104 // Headers & Footers with different first page
105 doc.set_different_first_page(true);
106 doc.set_first_page_header("rdocx");
107 doc.set_first_page_footer("Feature Showcase — Cover Page");
108 doc.set_header("rdocx Feature Showcase");
109 doc.set_footer("Generated by rdocx");
110
111 // ── COVER PAGE ──
112 let bg = create_sample_png(612, 792, [20, 45, 90]);
113 doc.add_background_image(&bg, "cover_bg.png");
114
115 for _ in 0..3 {
116 doc.add_paragraph("");
117 }
118 {
119 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
120 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
121 }
122 {
123 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
124 p.add_run("Complete Feature Showcase")
125 .size(28.0)
126 .color("FFFFFF")
127 .italic(true);
128 }
129 doc.add_paragraph("");
130 {
131 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
132 p.add_run("Every feature of the rdocx Rust library")
133 .size(13.0)
134 .color("B0C4DE");
135 }
136 {
137 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
138 p.add_run("demonstrated in a single document.")
139 .size(13.0)
140 .color("B0C4DE");
141 }
142
143 // ── TABLE OF CONTENTS ──
144 doc.add_paragraph("").page_break_before(true);
145 doc.insert_toc(doc.content_count(), 3);
146
147 // ── SECTION 1: TEXT FORMATTING ──
148 doc.add_paragraph("").page_break_before(true);
149 doc.add_paragraph("1. Text Formatting").style("Heading1");
150
151 // Paragraph Alignment
152 doc.add_paragraph("1.1 Paragraph Alignment")
153 .style("Heading2");
154 doc.add_paragraph("Left-aligned paragraph (default).")
155 .alignment(Alignment::Left);
156 doc.add_paragraph("Center-aligned paragraph.")
157 .alignment(Alignment::Center);
158 doc.add_paragraph("Right-aligned paragraph.")
159 .alignment(Alignment::Right);
160 doc.add_paragraph(
161 "Justified paragraph — this text is long enough to demonstrate how justified alignment \
162 distributes extra space across word gaps so lines fill the full width of the text area.",
163 )
164 .alignment(Alignment::Justify);
165
166 doc.add_paragraph("");
167
168 // Run Formatting
169 doc.add_paragraph("1.2 Run Formatting").style("Heading2");
170 {
171 let mut p = doc.add_paragraph("");
172 p.add_run("Bold").bold(true);
173 p.add_run(" | ");
174 p.add_run("Italic").italic(true);
175 p.add_run(" | ");
176 p.add_run("Bold Italic").bold(true).italic(true);
177 p.add_run(" | ");
178 p.add_run("Underline").underline(true);
179 p.add_run(" | ");
180 p.add_run("Strikethrough").strike(true);
181 p.add_run(" | ");
182 p.add_run("Double Strike").double_strike(true);
183 }
184 {
185 let mut p = doc.add_paragraph("");
186 p.add_run("Red").color("FF0000");
187 p.add_run(" | ");
188 p.add_run("Blue").color("0000FF");
189 p.add_run(" | ");
190 p.add_run("Green").color("00AA00");
191 p.add_run(" | ");
192 p.add_run("Highlighted").highlight("FFFF00");
193 }
194 {
195 let mut p = doc.add_paragraph("");
196 p.add_run("8pt").size(8.0);
197 p.add_run(" | ");
198 p.add_run("11pt").size(11.0);
199 p.add_run(" | ");
200 p.add_run("16pt").size(16.0);
201 p.add_run(" | ");
202 p.add_run("24pt").size(24.0);
203 }
204 {
205 let mut p = doc.add_paragraph("");
206 p.add_run("Arial").font("Arial");
207 p.add_run(" | ");
208 p.add_run("Times New Roman").font("Times New Roman");
209 p.add_run(" | ");
210 p.add_run("Courier New").font("Courier New");
211 }
212 {
213 let mut p = doc.add_paragraph("");
214 p.add_run("H");
215 p.add_run("2").subscript();
216 p.add_run("O (subscript) | E=mc");
217 p.add_run("2").superscript();
218 p.add_run(" (superscript)");
219 }
220 {
221 let mut p = doc.add_paragraph("");
222 p.add_run("ALL CAPS").all_caps(true);
223 p.add_run(" | ");
224 p.add_run("Small Caps").small_caps(true);
225 p.add_run(" | ");
226 p.add_run("Expanded").character_spacing(Length::twips(40));
227 p.add_run(" | ");
228 p.add_run("Hidden text (not visible)").hidden(true);
229 }
230
231 doc.add_paragraph("");
232
233 // Underline Styles
234 doc.add_paragraph("1.3 Underline Styles").style("Heading2");
235 {
236 let mut p = doc.add_paragraph("");
237 p.add_run("Single ")
238 .underline_style(rdocx::UnderlineStyle::Single);
239 p.add_run("Double ")
240 .underline_style(rdocx::UnderlineStyle::Double);
241 p.add_run("Thick ")
242 .underline_style(rdocx::UnderlineStyle::Thick);
243 p.add_run("Dotted ")
244 .underline_style(rdocx::UnderlineStyle::Dotted);
245 p.add_run("Dash ")
246 .underline_style(rdocx::UnderlineStyle::Dash);
247 p.add_run("Wave ")
248 .underline_style(rdocx::UnderlineStyle::Wave);
249 }
250
251 // ── SECTION 2: PARAGRAPH FORMATTING ──
252 doc.add_paragraph("").page_break_before(true);
253 doc.add_paragraph("2. Paragraph Formatting")
254 .style("Heading1");
255
256 doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
257 doc.add_paragraph("Paragraph with light green shading")
258 .shading("E2EFDA");
259 doc.add_paragraph("Paragraph with bottom border")
260 .border_bottom(BorderStyle::Single, 6, "2E75B6");
261 doc.add_paragraph("Paragraph with all borders (red)")
262 .border_all(BorderStyle::Single, 4, "FF0000");
263
264 doc.add_paragraph("2.2 Indentation").style("Heading2");
265 doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
266 .indent_left(Length::inches(1.0))
267 .hanging_indent(Length::inches(0.5));
268 doc.add_paragraph("0.5-inch first-line indent on this paragraph")
269 .first_line_indent(Length::inches(0.5));
270 doc.add_paragraph("Both left and right indent (0.75 inches each)")
271 .indent_left(Length::inches(0.75))
272 .indent_right(Length::inches(0.75));
273
274 doc.add_paragraph("2.3 Spacing & Line Height")
275 .style("Heading2");
276 doc.add_paragraph("Extra space before (24pt) and after (12pt)")
277 .space_before(Length::pt(24.0))
278 .space_after(Length::pt(12.0));
279 doc.add_paragraph(
280 "Double line spacing paragraph. This text should have extra vertical space between \
281 lines to demonstrate line_spacing_multiple(2.0).",
282 )
283 .line_spacing_multiple(2.0);
284 doc.add_paragraph("Exact 20pt line spacing (fixed height).")
285 .line_spacing(20.0);
286
287 doc.add_paragraph("2.4 Pagination Controls")
288 .style("Heading2");
289 doc.add_paragraph("keep_with_next — this paragraph stays with the next")
290 .keep_with_next(true);
291 doc.add_paragraph("(This paragraph was kept with the one above.)");
292 doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
293 .keep_together(true);
294 doc.add_paragraph("widow_control — prevents widow/orphan lines")
295 .widow_control(true);
296
297 // ── SECTION 3: LISTS ──
298 doc.add_paragraph("").page_break_before(true);
299 doc.add_paragraph("3. Lists").style("Heading1");
300
301 doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
302 doc.add_bullet_list_item("First item", 0);
303 doc.add_bullet_list_item("Second item", 0);
304 doc.add_bullet_list_item("Nested level 1", 1);
305 doc.add_bullet_list_item("Nested level 2", 2);
306 doc.add_bullet_list_item("Back to level 1", 1);
307 doc.add_bullet_list_item("Third item", 0);
308
309 doc.add_paragraph("");
310
311 doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
312 doc.add_numbered_list_item("First numbered item", 0);
313 doc.add_numbered_list_item("Second numbered item", 0);
314 doc.add_numbered_list_item("Sub-item A", 1);
315 doc.add_numbered_list_item("Sub-item B", 1);
316 doc.add_numbered_list_item("Third numbered item", 0);
317
318 // ── SECTION 4: TAB STOPS ──
319 doc.add_paragraph("");
320 doc.add_paragraph("4. Tab Stops").style("Heading1");
321
322 doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
323 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
324 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
325 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
326 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
327 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
328
329 doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
330 doc.add_paragraph("Item\t\tPrice")
331 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
332 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
333 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
334 doc.add_paragraph("Widget\t\t$19.99")
335 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
336 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
337 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
338 doc.add_paragraph("Gadget\t\t$249.50")
339 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
340 .add_tab_stop_with_leader(
341 TabAlignment::Right,
342 Length::inches(4.0),
343 TabLeader::Underscore,
344 )
345 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
346
347 // ── SECTION 5: TABLES ──
348 doc.add_paragraph("").page_break_before(true);
349 doc.add_paragraph("5. Tables").style("Heading1");
350
351 doc.add_paragraph("5.1 Basic Table").style("Heading2");
352 {
353 let mut tbl = doc
354 .add_table(4, 3)
355 .borders(BorderStyle::Single, 4, "000000");
356 for col in 0..3 {
357 tbl.cell(0, col).unwrap().shading("2E75B6");
358 }
359 tbl.cell(0, 0).unwrap().set_text("Name");
360 tbl.cell(0, 1).unwrap().set_text("Role");
361 tbl.cell(0, 2).unwrap().set_text("Location");
362 tbl.cell(1, 0).unwrap().set_text("Walter White");
363 tbl.cell(1, 1).unwrap().set_text("CEO");
364 tbl.cell(1, 2).unwrap().set_text("Albuquerque");
365 tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
366 tbl.cell(2, 1).unwrap().set_text("CTO");
367 tbl.cell(2, 2).unwrap().set_text("Remote");
368 tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
369 tbl.cell(3, 1).unwrap().set_text("Security");
370 tbl.cell(3, 2).unwrap().set_text("Washington");
371 }
372
373 doc.add_paragraph("");
374
375 doc.add_paragraph("5.2 Column Span & Cell Shading")
376 .style("Heading2");
377 {
378 let mut tbl = doc
379 .add_table(3, 4)
380 .borders(BorderStyle::Single, 4, "000000")
381 .width_pct(100.0);
382 tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
383 tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
384 tbl.cell(1, 0).unwrap().set_text("Region");
385 tbl.cell(1, 0).unwrap().shading("D6E4F0");
386 tbl.cell(1, 1).unwrap().set_text("Q1");
387 tbl.cell(1, 1).unwrap().shading("D6E4F0");
388 tbl.cell(1, 2).unwrap().set_text("Q2");
389 tbl.cell(1, 2).unwrap().shading("D6E4F0");
390 tbl.cell(1, 3).unwrap().set_text("Total");
391 tbl.cell(1, 3).unwrap().shading("D6E4F0");
392 tbl.cell(2, 0).unwrap().set_text("Americas");
393 tbl.cell(2, 1).unwrap().set_text("$2.4M");
394 tbl.cell(2, 2).unwrap().set_text("$2.7M");
395 tbl.cell(2, 3).unwrap().set_text("$5.1M");
396 }
397
398 doc.add_paragraph("");
399
400 doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
401 {
402 let mut tbl = doc
403 .add_table(4, 3)
404 .borders(BorderStyle::Single, 4, "000000");
405 tbl.cell(0, 0).unwrap().set_text("Category");
406 tbl.cell(0, 0).unwrap().shading("E2EFDA");
407 tbl.cell(0, 1).unwrap().set_text("Item");
408 tbl.cell(0, 1).unwrap().shading("E2EFDA");
409 tbl.cell(0, 2).unwrap().set_text("Price");
410 tbl.cell(0, 2).unwrap().shading("E2EFDA");
411 tbl.cell(1, 0).unwrap().set_text("Hardware");
412 tbl.cell(1, 0).unwrap().v_merge_restart();
413 tbl.cell(1, 1).unwrap().set_text("Laptop");
414 tbl.cell(1, 2).unwrap().set_text("$1,200");
415 tbl.cell(2, 0).unwrap().v_merge_continue();
416 tbl.cell(2, 1).unwrap().set_text("Monitor");
417 tbl.cell(2, 2).unwrap().set_text("$450");
418 tbl.cell(3, 0).unwrap().set_text("Software");
419 tbl.cell(3, 1).unwrap().set_text("IDE License");
420 tbl.cell(3, 2).unwrap().set_text("$200/yr");
421 }
422
423 doc.add_paragraph("");
424
425 doc.add_paragraph("5.4 Nested Table").style("Heading2");
426 {
427 let mut tbl = doc
428 .add_table(2, 2)
429 .borders(BorderStyle::Single, 6, "2E75B6");
430 tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
431 tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
432 tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
433 {
434 let mut cell = tbl.cell(1, 1).unwrap();
435 cell.set_text("Nested table below:");
436 let mut nested = cell
437 .add_table(2, 2)
438 .borders(BorderStyle::Single, 2, "FF6600");
439 nested.cell(0, 0).unwrap().set_text("A");
440 nested.cell(0, 1).unwrap().set_text("B");
441 nested.cell(1, 0).unwrap().set_text("C");
442 nested.cell(1, 1).unwrap().set_text("D");
443 }
444 }
445
446 doc.add_paragraph("");
447
448 doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
449 .style("Heading2");
450 {
451 let mut tbl = doc
452 .add_table(2, 3)
453 .borders(BorderStyle::Single, 4, "666666");
454 tbl.row(0).unwrap().height(Length::pt(50.0)).header();
455 tbl.cell(0, 0).unwrap().set_text("Top");
456 tbl.cell(0, 0)
457 .unwrap()
458 .vertical_alignment(VerticalAlignment::Top)
459 .shading("FFF2CC");
460 tbl.cell(0, 1).unwrap().set_text("Center");
461 tbl.cell(0, 1)
462 .unwrap()
463 .vertical_alignment(VerticalAlignment::Center)
464 .shading("D9E2F3");
465 tbl.cell(0, 2).unwrap().set_text("Bottom");
466 tbl.cell(0, 2)
467 .unwrap()
468 .vertical_alignment(VerticalAlignment::Bottom)
469 .shading("E2EFDA");
470 tbl.row(1).unwrap().cant_split();
471 tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
472 tbl.cell(1, 0).unwrap().no_wrap();
473 tbl.cell(1, 1).unwrap().set_text("Fixed width");
474 tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
475 tbl.cell(1, 2).unwrap().set_text("Normal");
476 }
477
478 // ── SECTION 6: IMAGES ──
479 doc.add_paragraph("").page_break_before(true);
480 doc.add_paragraph("6. Images").style("Heading1");
481
482 doc.add_paragraph("6.1 Inline Image").style("Heading2");
483 doc.add_paragraph("A blue gradient image below:");
484 let img = create_sample_png(200, 50, [0, 80, 200]);
485 doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
486
487 doc.add_paragraph("");
488 doc.add_paragraph("6.2 Header Image").style("Heading2");
489 let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
490 doc.set_header_image(
491 &hdr_img,
492 "header_logo.png",
493 Length::inches(2.0),
494 Length::inches(0.2),
495 );
496 doc.add_paragraph("The header now contains an inline image (check the top of this page).");
497
498 doc.add_paragraph("");
499 doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
500
501 // ── SECTION 7: CONTENT MANIPULATION ──
502 doc.add_paragraph("").page_break_before(true);
503 doc.add_paragraph("7. Content Manipulation")
504 .style("Heading1");
505
506 doc.add_paragraph("7.1 Placeholder Replacement")
507 .style("Heading2");
508 doc.add_paragraph("Customer: {{customer}}");
509 doc.add_paragraph("Date: {{date}}");
510 doc.add_paragraph("Reference: {{ref_number}}");
511 {
512 let mut tbl = doc
513 .add_table(2, 2)
514 .borders(BorderStyle::Single, 4, "000000");
515 tbl.cell(0, 0).unwrap().set_text("Project");
516 tbl.cell(0, 0).unwrap().shading("D6E4F0");
517 tbl.cell(0, 1).unwrap().set_text("{{project}}");
518 tbl.cell(1, 0).unwrap().set_text("Status");
519 tbl.cell(1, 0).unwrap().shading("D6E4F0");
520 tbl.cell(1, 1).unwrap().set_text("{{status}}");
521 }
522
523 let mut replacements = HashMap::new();
524 replacements.insert("{{customer}}", "Tensorbee Inc.");
525 replacements.insert("{{date}}", "February 22, 2026");
526 replacements.insert("{{ref_number}}", "TB-2026-001");
527 replacements.insert("{{project}}", "Infrastructure Upgrade");
528 replacements.insert("{{status}}", "In Progress");
529 let n = doc.replace_all(&replacements);
530 doc.add_paragraph(&format!("({n} placeholders replaced above)"));
531
532 doc.add_paragraph("");
533
534 doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
535 doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
536 let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
537 doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
538
539 doc.add_paragraph("");
540
541 doc.add_paragraph("7.3 Content Insertion").style("Heading2");
542 doc.add_paragraph("Section A: First content.");
543 doc.add_paragraph("Section C: Third content.");
544 if let Some(idx) = doc.find_content_index("Section C") {
545 doc.insert_paragraph(
546 idx,
547 "Section B: Inserted between A and C via find_content_index().",
548 );
549 }
550
551 // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
552 doc.add_paragraph("").section_break(SectionBreak::NextPage);
553 doc.add_paragraph("8. Section Breaks & Orientation")
554 .style("Heading1");
555 doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
556
557 {
558 let mut tbl = doc
559 .add_table(3, 7)
560 .borders(BorderStyle::Single, 4, "2E75B6");
561 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
562 for (c, h) in headers.iter().enumerate() {
563 tbl.cell(0, c).unwrap().set_text(h);
564 tbl.cell(0, c).unwrap().shading("2E75B6");
565 }
566 let data = [
567 [
568 "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
569 ],
570 [
571 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
572 ],
573 ];
574 for (r, row) in data.iter().enumerate() {
575 for (c, v) in row.iter().enumerate() {
576 tbl.cell(r + 1, c).unwrap().set_text(v);
577 }
578 }
579 }
580
581 doc.add_paragraph("")
582 .section_break(SectionBreak::NextPage)
583 .section_landscape();
584
585 // ── SECTION 9: CUSTOM STYLES ──
586 doc.add_paragraph("9. Custom Styles").style("Heading1");
587 doc.add_style(
588 StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
589 .based_on("Normal")
590 .paragraph_properties({
591 let mut ppr = rdocx_oxml::properties::CT_PPr::default();
592 ppr.shading = Some(rdocx_oxml::properties::CT_Shd {
593 val: "clear".to_string(),
594 color: None,
595 fill: Some("FFF2CC".to_string()),
596 });
597 ppr
598 })
599 .run_properties({
600 let mut rpr = rdocx_oxml::properties::CT_RPr::default();
601 rpr.bold = Some(true);
602 rpr.color = Some("C45911".to_string());
603 rpr
604 }),
605 );
606 doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
607 .style("CustomHighlight");
608
609 doc.add_paragraph("");
610
611 // ── SECTION 10: DOCUMENT INTELLIGENCE ──
612 doc.add_paragraph("10. Document Intelligence API")
613 .style("Heading1");
614
615 let wc = doc.word_count();
616 let hc = doc.headings().len();
617 let ic = doc.images().len();
618 let lc = doc.links().len();
619 doc.add_paragraph(&format!("Word count: {wc}"));
620 doc.add_paragraph(&format!("Heading count: {hc}"));
621 doc.add_paragraph(&format!("Image count: {ic}"));
622 doc.add_paragraph(&format!("Link count: {lc}"));
623
624 let outline = doc.document_outline();
625 doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
626
627 let issues = doc.audit_accessibility();
628 doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
629 for issue in &issues {
630 doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
631 }
632
633 // ── SECTION 11: DOCUMENT MERGING ──
634 doc.add_paragraph("");
635 doc.add_paragraph("11. Document Merging").style("Heading1");
636 {
637 let mut other = Document::new();
638 other.add_paragraph("This paragraph was merged from another document using append().");
639 doc.append(&other);
640 }
641
642 doc.add_paragraph("");
643
644 // ── SUMMARY ──
645 doc.add_paragraph("Summary of Demonstrated Features")
646 .style("Heading1");
647 let features = [
648 "Page setup: size, margins, header/footer distance, gutter",
649 "Document metadata: title, author, subject, keywords",
650 "Headers and footers: text, images, different first page",
651 "Background images (full-page behind text)",
652 "Table of Contents generation with bookmarks",
653 "Text formatting: bold, italic, underline styles, strike, color, size, font",
654 "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
655 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
656 "Pagination controls: keep-with-next, keep-together, widow control",
657 "Bullet and numbered lists with nesting",
658 "Tab stops with dot/underscore/hyphen leaders",
659 "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
660 "Row properties: header rows, exact height, cant-split",
661 "Cell properties: width, no-wrap, vertical alignment",
662 "Inline images and header images",
663 "Placeholder replacement (single and batch)",
664 "Regex-based find and replace",
665 "Content insertion at specific positions",
666 "Section breaks with mixed portrait/landscape",
667 "Custom paragraph and character styles",
668 "Document intelligence: word count, headings, outline, images, links",
669 "Accessibility audit",
670 "Document merging (append)",
671 "Export: DOCX, PDF, HTML, Markdown",
672 ];
673 for f in &features {
674 doc.add_bullet_list_item(f, 0);
675 }
676 doc.add_paragraph("");
677 doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
678 .alignment(Alignment::Center)
679 .shading("E2EFDA")
680 .border_all(BorderStyle::Single, 2, "00AA00");
681
682 doc
683}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?
34fn generate_feature_showcase(path: &Path) {
35 let mut doc = Document::new();
36
37 // =========================================================================
38 // PAGE SETUP & METADATA
39 // =========================================================================
40 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
41 doc.set_margins(
42 Length::inches(1.0), // top
43 Length::inches(1.0), // right
44 Length::inches(1.0), // bottom
45 Length::inches(1.0), // left
46 );
47 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
48 doc.set_gutter(Length::twips(0));
49
50 doc.set_title("rdocx Feature Showcase");
51 doc.set_author("rdocx Sample Generator");
52 doc.set_subject("Comprehensive feature demonstration");
53 doc.set_keywords("rdocx, docx, rust, sample");
54
55 // Header & Footer
56 doc.set_header("rdocx Feature Showcase");
57 doc.set_footer("Generated by rdocx — Page");
58
59 // Different first page header
60 doc.set_different_first_page(true);
61 doc.set_first_page_header("rdocx");
62 doc.set_first_page_footer("Feature Showcase — Cover Page");
63
64 // =========================================================================
65 // PAGE 1: COVER PAGE — background image, run formatting
66 // =========================================================================
67 let bg_cover = create_sample_png(612, 792, [30, 60, 120]);
68 doc.add_background_image(&bg_cover, "cover_bg.png");
69
70 doc.add_paragraph(""); // spacer
71 doc.add_paragraph(""); // spacer
72 doc.add_paragraph(""); // spacer
73
74 {
75 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
76 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
77 }
78 {
79 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
80 p.add_run("Feature Showcase")
81 .size(28.0)
82 .color("FFFFFF")
83 .italic(true);
84 }
85
86 doc.add_paragraph(""); // spacer
87
88 {
89 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
90 p.add_run("A comprehensive demonstration of every feature")
91 .size(14.0)
92 .color("CCDDFF");
93 }
94 {
95 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
96 p.add_run("provided by the rdocx Rust crate for DOCX generation.")
97 .size(14.0)
98 .color("CCDDFF");
99 }
100
101 // =========================================================================
102 // PAGE 2: TEXT FORMATTING
103 // =========================================================================
104 doc.add_paragraph("").page_break_before(true);
105
106 doc.add_paragraph("1. Text Formatting").style("Heading1");
107
108 doc.add_paragraph("This section demonstrates paragraph and run-level formatting options.");
109 doc.add_paragraph("");
110
111 // --- Paragraph alignment ---
112 doc.add_paragraph("Paragraph Alignment").style("Heading2");
113
114 doc.add_paragraph("This paragraph is left-aligned (the default).")
115 .alignment(Alignment::Left);
116 doc.add_paragraph("This paragraph is center-aligned.")
117 .alignment(Alignment::Center);
118 doc.add_paragraph("This paragraph is right-aligned.")
119 .alignment(Alignment::Right);
120 doc.add_paragraph(
121 "This paragraph is justified. To demonstrate justified text properly, it needs \
122 to be long enough to span multiple lines so the word spacing adjustment is visible \
123 across the full width of the text area on the page.",
124 )
125 .alignment(Alignment::Justify);
126
127 doc.add_paragraph("");
128
129 // --- Run formatting ---
130 doc.add_paragraph("Run Formatting").style("Heading2");
131
132 {
133 let mut p = doc.add_paragraph("");
134 p.add_run("Bold text").bold(true);
135 p.add_run(" | ");
136 p.add_run("Italic text").italic(true);
137 p.add_run(" | ");
138 p.add_run("Bold + Italic").bold(true).italic(true);
139 }
140 {
141 let mut p = doc.add_paragraph("");
142 p.add_run("Single underline").underline(true);
143 p.add_run(" | ");
144 p.add_run("Strikethrough").strike(true);
145 p.add_run(" | ");
146 p.add_run("Double strikethrough").double_strike(true);
147 }
148 {
149 let mut p = doc.add_paragraph("");
150 p.add_run("Red text").color("FF0000");
151 p.add_run(" | ");
152 p.add_run("Blue text").color("0000FF");
153 p.add_run(" | ");
154 p.add_run("Green text").color("00AA00");
155 p.add_run(" | ");
156 p.add_run("Highlighted").highlight("FFFF00");
157 }
158 {
159 let mut p = doc.add_paragraph("");
160 p.add_run("8pt small").size(8.0);
161 p.add_run(" | ");
162 p.add_run("11pt normal").size(11.0);
163 p.add_run(" | ");
164 p.add_run("16pt large").size(16.0);
165 p.add_run(" | ");
166 p.add_run("24pt extra-large").size(24.0);
167 }
168 {
169 let mut p = doc.add_paragraph("");
170 p.add_run("Arial font").font("Arial");
171 p.add_run(" | ");
172 p.add_run("Times New Roman font").font("Times New Roman");
173 p.add_run(" | ");
174 p.add_run("Courier New font").font("Courier New");
175 }
176 {
177 let mut p = doc.add_paragraph("");
178 p.add_run("Normal");
179 p.add_run(" H").size(11.0);
180 p.add_run("2").subscript();
181 p.add_run("O (subscript)").size(11.0);
182 p.add_run(" | E = mc").size(11.0);
183 p.add_run("2").superscript();
184 p.add_run(" (superscript)").size(11.0);
185 }
186 {
187 let mut p = doc.add_paragraph("");
188 p.add_run("ALL CAPS").all_caps(true);
189 p.add_run(" | ");
190 p.add_run("Small Caps").small_caps(true);
191 p.add_run(" | ");
192 p.add_run("Expanded spacing")
193 .character_spacing(Length::twips(40));
194 }
195
196 doc.add_paragraph("");
197
198 // --- Paragraph formatting ---
199 doc.add_paragraph("Paragraph Formatting").style("Heading2");
200
201 doc.add_paragraph("Paragraph with shading (light green background)")
202 .shading("E2EFDA");
203
204 doc.add_paragraph("Paragraph with bottom border")
205 .border_bottom(BorderStyle::Single, 6, "2E75B6");
206
207 doc.add_paragraph("Paragraph with all borders")
208 .border_all(BorderStyle::Single, 4, "FF0000");
209
210 doc.add_paragraph("Paragraph with 1-inch left indent and hanging indent")
211 .indent_left(Length::inches(1.0))
212 .hanging_indent(Length::inches(0.5));
213
214 doc.add_paragraph("Paragraph with first-line indent of 0.5 inches")
215 .first_line_indent(Length::inches(0.5));
216
217 doc.add_paragraph("Paragraph with extra space before (24pt) and after (12pt)")
218 .space_before(Length::pt(24.0))
219 .space_after(Length::pt(12.0));
220
221 doc.add_paragraph(
222 "Paragraph with double line spacing. This text should have extra vertical \
223 space between lines to demonstrate the line_spacing_multiple setting.",
224 )
225 .line_spacing_multiple(2.0);
226
227 doc.add_paragraph("Paragraph with keep-with-next (won't break from the next paragraph)")
228 .keep_with_next(true);
229 doc.add_paragraph("(This stays with the paragraph above.)");
230
231 // =========================================================================
232 // PAGE 3: LISTS & TAB STOPS
233 // =========================================================================
234 doc.add_paragraph("").page_break_before(true);
235
236 doc.add_paragraph("2. Lists").style("Heading1");
237
238 doc.add_paragraph("Bullet List").style("Heading2");
239
240 doc.add_bullet_list_item("First bullet item", 0);
241 doc.add_bullet_list_item("Second bullet item", 0);
242 doc.add_bullet_list_item("Nested level 1", 1);
243 doc.add_bullet_list_item("Nested level 2", 2);
244 doc.add_bullet_list_item("Back to level 1", 1);
245 doc.add_bullet_list_item("Third bullet item", 0);
246
247 doc.add_paragraph("");
248
249 doc.add_paragraph("Numbered List").style("Heading2");
250
251 doc.add_numbered_list_item("First numbered item", 0);
252 doc.add_numbered_list_item("Second numbered item", 0);
253 doc.add_numbered_list_item("Sub-item A", 1);
254 doc.add_numbered_list_item("Sub-item B", 1);
255 doc.add_numbered_list_item("Third numbered item", 0);
256
257 doc.add_paragraph("");
258
259 // --- Tab stops ---
260 doc.add_paragraph("Tab Stops").style("Heading2");
261
262 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
263 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
264 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
265 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
266 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
267
268 doc.add_paragraph("Item\t........\tPrice")
269 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
270 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
271 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
272
273 doc.add_paragraph("Widget A\t........\t$19.99")
274 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
275 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
276 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
277
278 doc.add_paragraph("Gadget B\t________\t$249.50")
279 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
280 .add_tab_stop_with_leader(
281 TabAlignment::Right,
282 Length::inches(4.0),
283 TabLeader::Underscore,
284 )
285 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
286
287 // =========================================================================
288 // PAGE 4: TABLES
289 // =========================================================================
290 doc.add_paragraph("").page_break_before(true);
291
292 doc.add_paragraph("3. Tables").style("Heading1");
293
294 // --- Basic table with borders ---
295 doc.add_paragraph("Basic Table with Borders")
296 .style("Heading2");
297
298 {
299 let mut tbl = doc.add_table(4, 3);
300 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
301
302 // Header row
303 for col in 0..3 {
304 tbl.cell(0, col).unwrap().shading("2E75B6");
305 }
306 tbl.cell(0, 0).unwrap().set_text("Name");
307 tbl.cell(0, 1).unwrap().set_text("Role");
308 tbl.cell(0, 2).unwrap().set_text("Location");
309
310 tbl.cell(1, 0).unwrap().set_text("Alice Johnson");
311 tbl.cell(1, 1).unwrap().set_text("Engineering Lead");
312 tbl.cell(1, 2).unwrap().set_text("New York");
313
314 tbl.cell(2, 0).unwrap().set_text("Bob Smith");
315 tbl.cell(2, 1).unwrap().set_text("Product Manager");
316 tbl.cell(2, 2).unwrap().set_text("San Francisco");
317
318 tbl.cell(3, 0).unwrap().set_text("Carol Davis");
319 tbl.cell(3, 1).unwrap().set_text("Designer");
320 tbl.cell(3, 2).unwrap().set_text("London");
321 }
322
323 doc.add_paragraph("");
324
325 // --- Table with cell merging ---
326 doc.add_paragraph("Table with Cell Merging & Vertical Alignment")
327 .style("Heading2");
328
329 {
330 let mut tbl = doc.add_table(4, 4);
331 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
332 tbl = tbl.width_pct(100.0);
333
334 // Header spanning all columns
335 tbl.cell(0, 0).unwrap().set_text("Quarterly Revenue Report");
336 tbl.cell(0, 0).unwrap().shading("1F4E79");
337 tbl.cell(0, 0).unwrap().grid_span(4);
338
339 // Sub-header
340 tbl.cell(1, 0).unwrap().set_text("Region");
341 tbl.cell(1, 0).unwrap().shading("D6E4F0");
342 tbl.cell(1, 1).unwrap().set_text("Q1");
343 tbl.cell(1, 1).unwrap().shading("D6E4F0");
344 tbl.cell(1, 2).unwrap().set_text("Q2");
345 tbl.cell(1, 2).unwrap().shading("D6E4F0");
346 tbl.cell(1, 3).unwrap().set_text("Total");
347 tbl.cell(1, 3).unwrap().shading("D6E4F0");
348
349 // Data
350 tbl.cell(2, 0).unwrap().set_text("North America");
351 tbl.cell(2, 1).unwrap().set_text("$2.4M");
352 tbl.cell(2, 2).unwrap().set_text("$2.7M");
353 tbl.cell(2, 3).unwrap().set_text("$5.1M");
354
355 tbl.cell(3, 0).unwrap().set_text("Europe");
356 tbl.cell(3, 1).unwrap().set_text("$1.8M");
357 tbl.cell(3, 2).unwrap().set_text("$2.0M");
358 tbl.cell(3, 3).unwrap().set_text("$3.8M");
359
360 // Vertical alignment on data cells
361 tbl.cell(2, 3)
362 .unwrap()
363 .vertical_alignment(VerticalAlignment::Center);
364 tbl.cell(3, 3)
365 .unwrap()
366 .vertical_alignment(VerticalAlignment::Bottom);
367 }
368
369 doc.add_paragraph("");
370
371 // --- Table with vertical merge ---
372 doc.add_paragraph("Table with Vertical Merge")
373 .style("Heading2");
374
375 {
376 let mut tbl = doc.add_table(4, 3);
377 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
378
379 tbl.cell(0, 0).unwrap().set_text("Category");
380 tbl.cell(0, 0).unwrap().shading("E2EFDA");
381 tbl.cell(0, 1).unwrap().set_text("Item");
382 tbl.cell(0, 1).unwrap().shading("E2EFDA");
383 tbl.cell(0, 2).unwrap().set_text("Price");
384 tbl.cell(0, 2).unwrap().shading("E2EFDA");
385
386 // "Hardware" spans rows 1-2
387 tbl.cell(1, 0).unwrap().set_text("Hardware");
388 tbl.cell(1, 0).unwrap().v_merge_restart();
389 tbl.cell(1, 1).unwrap().set_text("Laptop");
390 tbl.cell(1, 2).unwrap().set_text("$1,200");
391
392 tbl.cell(2, 0).unwrap().v_merge_continue();
393 tbl.cell(2, 1).unwrap().set_text("Monitor");
394 tbl.cell(2, 2).unwrap().set_text("$450");
395
396 // "Software" on row 3
397 tbl.cell(3, 0).unwrap().set_text("Software");
398 tbl.cell(3, 1).unwrap().set_text("IDE License");
399 tbl.cell(3, 2).unwrap().set_text("$200/yr");
400 }
401
402 doc.add_paragraph("");
403
404 // --- Nested table ---
405 doc.add_paragraph("Nested Table").style("Heading2");
406
407 {
408 let mut tbl = doc.add_table(2, 2);
409 tbl = tbl.borders(BorderStyle::Single, 6, "2E75B6");
410
411 tbl.cell(0, 0).unwrap().set_text("Outer Cell (0,0)");
412 tbl.cell(0, 1).unwrap().set_text("Outer Cell (0,1)");
413 tbl.cell(1, 0).unwrap().set_text("Outer Cell (1,0)");
414
415 // Nested table inside cell (1,1)
416 {
417 let mut cell = tbl.cell(1, 1).unwrap();
418 cell.set_text("Contains nested table:");
419 let mut nested = cell.add_table(2, 2);
420 nested = nested.borders(BorderStyle::Single, 2, "FF6600");
421 nested.cell(0, 0).unwrap().set_text("Inner A");
422 nested.cell(0, 1).unwrap().set_text("Inner B");
423 nested.cell(1, 0).unwrap().set_text("Inner C");
424 nested.cell(1, 1).unwrap().set_text("Inner D");
425 }
426 }
427
428 // =========================================================================
429 // PAGE 5: IMAGES
430 // =========================================================================
431 doc.add_paragraph("").page_break_before(true);
432
433 doc.add_paragraph("4. Images").style("Heading1");
434
435 doc.add_paragraph("Inline Image").style("Heading2");
436
437 doc.add_paragraph("Below is an inline image (200x50 pixels, blue gradient):");
438 let inline_img = create_sample_png(200, 50, [0, 80, 200]);
439 doc.add_picture(
440 &inline_img,
441 "inline_chart.png",
442 Length::inches(3.0),
443 Length::inches(0.75),
444 );
445
446 doc.add_paragraph("");
447
448 doc.add_paragraph("Header Image").style("Heading2");
449
450 // Replace the text-only header with an image header
451 let header_img = create_sample_png(400, 40, [40, 40, 40]);
452 doc.set_header_image(
453 &header_img,
454 "header_logo.png",
455 Length::inches(2.0),
456 Length::inches(0.2),
457 );
458
459 doc.add_paragraph(
460 "The document header has been replaced with an inline image. \
461 Check the header area at the top of this page.",
462 );
463
464 doc.add_paragraph("");
465 doc.add_paragraph(
466 "Note: The cover page uses a full-page background image behind the text, \
467 demonstrated on page 1 via add_background_image().",
468 );
469
470 // =========================================================================
471 // PAGE 6: CONTENT MANIPULATION — placeholder replacement, insertion
472 // =========================================================================
473 doc.add_paragraph("").page_break_before(true);
474
475 doc.add_paragraph("5. Content Manipulation")
476 .style("Heading1");
477
478 // --- Placeholder replacement ---
479 doc.add_paragraph("Placeholder Replacement")
480 .style("Heading2");
481
482 doc.add_paragraph(
483 "Before replacement, this document contained {{customer}} and {{date}} placeholders.",
484 );
485
486 {
487 let mut p = doc.add_paragraph("");
488 p.add_run("Customer: ").bold(true);
489 p.add_run("{{customer}}");
490 }
491 {
492 let mut p = doc.add_paragraph("");
493 p.add_run("Date: ").bold(true);
494 p.add_run("{{date}}");
495 }
496 doc.add_paragraph("Reference: {{ref_number}}");
497
498 // Table with placeholders
499 {
500 let mut tbl = doc.add_table(3, 2);
501 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
502 tbl.cell(0, 0).unwrap().set_text("Field");
503 tbl.cell(0, 0).unwrap().shading("D6E4F0");
504 tbl.cell(0, 1).unwrap().set_text("Value");
505 tbl.cell(0, 1).unwrap().shading("D6E4F0");
506 tbl.cell(1, 0).unwrap().set_text("Project");
507 tbl.cell(1, 1).unwrap().set_text("{{project}}");
508 tbl.cell(2, 0).unwrap().set_text("Status");
509 tbl.cell(2, 1).unwrap().set_text("{{status}}");
510 }
511
512 // Perform replacements
513 let mut replacements = HashMap::new();
514 replacements.insert("{{customer}}", "Acme Corporation");
515 replacements.insert("{{date}}", "February 22, 2026");
516 replacements.insert("{{ref_number}}", "REF-2026-001");
517 replacements.insert("{{project}}", "Infrastructure Upgrade");
518 replacements.insert("{{status}}", "In Progress");
519 let replace_count = doc.replace_all(&replacements);
520
521 doc.add_paragraph("");
522 doc.add_paragraph(&format!(
523 "(Replaced {} placeholders above — in body text and table cells)",
524 replace_count
525 ));
526
527 doc.add_paragraph("");
528
529 // --- Content insertion ---
530 doc.add_paragraph("Content Insertion").style("Heading2");
531
532 doc.add_paragraph("Section A: First section of content.");
533 doc.add_paragraph("Section C: Third section of content.");
534
535 // Insert "Section B" between A and C
536 if let Some(idx) = doc.find_content_index("Section C") {
537 doc.insert_paragraph(
538 idx,
539 "Section B: Inserted between A and C using find_content_index().",
540 );
541 }
542
543 doc.add_paragraph("");
544 doc.add_paragraph(
545 "The paragraph above ('Section B') was inserted at a specific position \
546 using find_content_index() + insert_paragraph().",
547 );
548
549 // =========================================================================
550 // PAGE 7: LANDSCAPE — section break, wide table
551 // =========================================================================
552 doc.add_paragraph("").section_break(SectionBreak::NextPage);
553
554 doc.add_paragraph("6. Mixed Page Orientation")
555 .style("Heading1");
556
557 doc.add_paragraph(
558 "This page is in LANDSCAPE orientation. It was created using a section break \
559 followed by section_landscape(). This is useful for wide tables or charts.",
560 );
561
562 doc.add_paragraph("");
563
564 // Wide table for landscape
565 {
566 let mut tbl = doc.add_table(4, 7);
567 tbl = tbl.borders(BorderStyle::Single, 4, "2E75B6");
568
569 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
570 for (col, h) in headers.iter().enumerate() {
571 tbl.cell(0, col).unwrap().set_text(h);
572 tbl.cell(0, col).unwrap().shading("2E75B6");
573 }
574
575 let data = [
576 [
577 "North America",
578 "$1.2M",
579 "$1.3M",
580 "$1.4M",
581 "$1.5M",
582 "$1.6M",
583 "$7.0M",
584 ],
585 [
586 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
587 ],
588 [
589 "Asia Pacific",
590 "$0.5M",
591 "$0.6M",
592 "$0.7M",
593 "$0.7M",
594 "$0.8M",
595 "$3.3M",
596 ],
597 ];
598 for (row_idx, row_data) in data.iter().enumerate() {
599 for (col, val) in row_data.iter().enumerate() {
600 tbl.cell(row_idx + 1, col).unwrap().set_text(val);
601 }
602 }
603 }
604
605 // End landscape, return to portrait
606 doc.add_paragraph("")
607 .section_break(SectionBreak::NextPage)
608 .section_landscape();
609
610 // =========================================================================
611 // PAGE 8: BACK TO PORTRAIT — styles, final notes
612 // =========================================================================
613 doc.add_paragraph("7. Custom Styles & Summary")
614 .style("Heading1");
615
616 doc.add_paragraph(
617 "This final page is back in portrait orientation after a section break. \
618 The document has demonstrated:",
619 );
620
621 doc.add_bullet_list_item(
622 "Page setup: size, margins, header/footer distance, gutter",
623 0,
624 );
625 doc.add_bullet_list_item("Document metadata: title, author, subject, keywords", 0);
626 doc.add_bullet_list_item("Headers and footers: text, images, different first page", 0);
627 doc.add_bullet_list_item("Background images: full-page behind text", 0);
628 doc.add_bullet_list_item(
629 "Text formatting: bold, italic, underline, strike, color, size, font",
630 0,
631 );
632 doc.add_bullet_list_item(
633 "Advanced run formatting: superscript, subscript, caps, spacing",
634 0,
635 );
636 doc.add_bullet_list_item(
637 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
638 0,
639 );
640 doc.add_bullet_list_item("Bullet and numbered lists with nesting levels", 0);
641 doc.add_bullet_list_item("Tab stops with dot/underscore leaders", 0);
642 doc.add_bullet_list_item(
643 "Tables: borders, shading, column spans, row spans, nesting",
644 0,
645 );
646 doc.add_bullet_list_item("Vertical alignment in table cells", 0);
647 doc.add_bullet_list_item("Inline images", 0);
648 doc.add_bullet_list_item("Placeholder replacement in body and table cells", 0);
649 doc.add_bullet_list_item("Content insertion at specific positions", 0);
650 doc.add_bullet_list_item(
651 "Section breaks with mixed portrait/landscape orientation",
652 0,
653 );
654
655 doc.add_paragraph("");
656 doc.add_paragraph("All features above were built entirely from scratch using the rdocx API.")
657 .alignment(Alignment::Center)
658 .shading("E2EFDA")
659 .border_all(BorderStyle::Single, 2, "00AA00");
660
661 doc.save(path).unwrap();
662}More examples
86fn generate_feature_showcase(_samples_dir: &Path) -> Document {
87 let mut doc = Document::new();
88
89 // ── Page Setup & Metadata ──
90 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
91 doc.set_margins(
92 Length::inches(1.0),
93 Length::inches(1.0),
94 Length::inches(1.0),
95 Length::inches(1.0),
96 );
97 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
98 doc.set_gutter(Length::twips(0));
99 doc.set_title("rdocx Feature Showcase");
100 doc.set_author("rdocx Sample Generator");
101 doc.set_subject("Comprehensive feature demonstration");
102 doc.set_keywords("rdocx, docx, rust, sample, showcase");
103
104 // Headers & Footers with different first page
105 doc.set_different_first_page(true);
106 doc.set_first_page_header("rdocx");
107 doc.set_first_page_footer("Feature Showcase — Cover Page");
108 doc.set_header("rdocx Feature Showcase");
109 doc.set_footer("Generated by rdocx");
110
111 // ── COVER PAGE ──
112 let bg = create_sample_png(612, 792, [20, 45, 90]);
113 doc.add_background_image(&bg, "cover_bg.png");
114
115 for _ in 0..3 {
116 doc.add_paragraph("");
117 }
118 {
119 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
120 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
121 }
122 {
123 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
124 p.add_run("Complete Feature Showcase")
125 .size(28.0)
126 .color("FFFFFF")
127 .italic(true);
128 }
129 doc.add_paragraph("");
130 {
131 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
132 p.add_run("Every feature of the rdocx Rust library")
133 .size(13.0)
134 .color("B0C4DE");
135 }
136 {
137 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
138 p.add_run("demonstrated in a single document.")
139 .size(13.0)
140 .color("B0C4DE");
141 }
142
143 // ── TABLE OF CONTENTS ──
144 doc.add_paragraph("").page_break_before(true);
145 doc.insert_toc(doc.content_count(), 3);
146
147 // ── SECTION 1: TEXT FORMATTING ──
148 doc.add_paragraph("").page_break_before(true);
149 doc.add_paragraph("1. Text Formatting").style("Heading1");
150
151 // Paragraph Alignment
152 doc.add_paragraph("1.1 Paragraph Alignment")
153 .style("Heading2");
154 doc.add_paragraph("Left-aligned paragraph (default).")
155 .alignment(Alignment::Left);
156 doc.add_paragraph("Center-aligned paragraph.")
157 .alignment(Alignment::Center);
158 doc.add_paragraph("Right-aligned paragraph.")
159 .alignment(Alignment::Right);
160 doc.add_paragraph(
161 "Justified paragraph — this text is long enough to demonstrate how justified alignment \
162 distributes extra space across word gaps so lines fill the full width of the text area.",
163 )
164 .alignment(Alignment::Justify);
165
166 doc.add_paragraph("");
167
168 // Run Formatting
169 doc.add_paragraph("1.2 Run Formatting").style("Heading2");
170 {
171 let mut p = doc.add_paragraph("");
172 p.add_run("Bold").bold(true);
173 p.add_run(" | ");
174 p.add_run("Italic").italic(true);
175 p.add_run(" | ");
176 p.add_run("Bold Italic").bold(true).italic(true);
177 p.add_run(" | ");
178 p.add_run("Underline").underline(true);
179 p.add_run(" | ");
180 p.add_run("Strikethrough").strike(true);
181 p.add_run(" | ");
182 p.add_run("Double Strike").double_strike(true);
183 }
184 {
185 let mut p = doc.add_paragraph("");
186 p.add_run("Red").color("FF0000");
187 p.add_run(" | ");
188 p.add_run("Blue").color("0000FF");
189 p.add_run(" | ");
190 p.add_run("Green").color("00AA00");
191 p.add_run(" | ");
192 p.add_run("Highlighted").highlight("FFFF00");
193 }
194 {
195 let mut p = doc.add_paragraph("");
196 p.add_run("8pt").size(8.0);
197 p.add_run(" | ");
198 p.add_run("11pt").size(11.0);
199 p.add_run(" | ");
200 p.add_run("16pt").size(16.0);
201 p.add_run(" | ");
202 p.add_run("24pt").size(24.0);
203 }
204 {
205 let mut p = doc.add_paragraph("");
206 p.add_run("Arial").font("Arial");
207 p.add_run(" | ");
208 p.add_run("Times New Roman").font("Times New Roman");
209 p.add_run(" | ");
210 p.add_run("Courier New").font("Courier New");
211 }
212 {
213 let mut p = doc.add_paragraph("");
214 p.add_run("H");
215 p.add_run("2").subscript();
216 p.add_run("O (subscript) | E=mc");
217 p.add_run("2").superscript();
218 p.add_run(" (superscript)");
219 }
220 {
221 let mut p = doc.add_paragraph("");
222 p.add_run("ALL CAPS").all_caps(true);
223 p.add_run(" | ");
224 p.add_run("Small Caps").small_caps(true);
225 p.add_run(" | ");
226 p.add_run("Expanded").character_spacing(Length::twips(40));
227 p.add_run(" | ");
228 p.add_run("Hidden text (not visible)").hidden(true);
229 }
230
231 doc.add_paragraph("");
232
233 // Underline Styles
234 doc.add_paragraph("1.3 Underline Styles").style("Heading2");
235 {
236 let mut p = doc.add_paragraph("");
237 p.add_run("Single ")
238 .underline_style(rdocx::UnderlineStyle::Single);
239 p.add_run("Double ")
240 .underline_style(rdocx::UnderlineStyle::Double);
241 p.add_run("Thick ")
242 .underline_style(rdocx::UnderlineStyle::Thick);
243 p.add_run("Dotted ")
244 .underline_style(rdocx::UnderlineStyle::Dotted);
245 p.add_run("Dash ")
246 .underline_style(rdocx::UnderlineStyle::Dash);
247 p.add_run("Wave ")
248 .underline_style(rdocx::UnderlineStyle::Wave);
249 }
250
251 // ── SECTION 2: PARAGRAPH FORMATTING ──
252 doc.add_paragraph("").page_break_before(true);
253 doc.add_paragraph("2. Paragraph Formatting")
254 .style("Heading1");
255
256 doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
257 doc.add_paragraph("Paragraph with light green shading")
258 .shading("E2EFDA");
259 doc.add_paragraph("Paragraph with bottom border")
260 .border_bottom(BorderStyle::Single, 6, "2E75B6");
261 doc.add_paragraph("Paragraph with all borders (red)")
262 .border_all(BorderStyle::Single, 4, "FF0000");
263
264 doc.add_paragraph("2.2 Indentation").style("Heading2");
265 doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
266 .indent_left(Length::inches(1.0))
267 .hanging_indent(Length::inches(0.5));
268 doc.add_paragraph("0.5-inch first-line indent on this paragraph")
269 .first_line_indent(Length::inches(0.5));
270 doc.add_paragraph("Both left and right indent (0.75 inches each)")
271 .indent_left(Length::inches(0.75))
272 .indent_right(Length::inches(0.75));
273
274 doc.add_paragraph("2.3 Spacing & Line Height")
275 .style("Heading2");
276 doc.add_paragraph("Extra space before (24pt) and after (12pt)")
277 .space_before(Length::pt(24.0))
278 .space_after(Length::pt(12.0));
279 doc.add_paragraph(
280 "Double line spacing paragraph. This text should have extra vertical space between \
281 lines to demonstrate line_spacing_multiple(2.0).",
282 )
283 .line_spacing_multiple(2.0);
284 doc.add_paragraph("Exact 20pt line spacing (fixed height).")
285 .line_spacing(20.0);
286
287 doc.add_paragraph("2.4 Pagination Controls")
288 .style("Heading2");
289 doc.add_paragraph("keep_with_next — this paragraph stays with the next")
290 .keep_with_next(true);
291 doc.add_paragraph("(This paragraph was kept with the one above.)");
292 doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
293 .keep_together(true);
294 doc.add_paragraph("widow_control — prevents widow/orphan lines")
295 .widow_control(true);
296
297 // ── SECTION 3: LISTS ──
298 doc.add_paragraph("").page_break_before(true);
299 doc.add_paragraph("3. Lists").style("Heading1");
300
301 doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
302 doc.add_bullet_list_item("First item", 0);
303 doc.add_bullet_list_item("Second item", 0);
304 doc.add_bullet_list_item("Nested level 1", 1);
305 doc.add_bullet_list_item("Nested level 2", 2);
306 doc.add_bullet_list_item("Back to level 1", 1);
307 doc.add_bullet_list_item("Third item", 0);
308
309 doc.add_paragraph("");
310
311 doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
312 doc.add_numbered_list_item("First numbered item", 0);
313 doc.add_numbered_list_item("Second numbered item", 0);
314 doc.add_numbered_list_item("Sub-item A", 1);
315 doc.add_numbered_list_item("Sub-item B", 1);
316 doc.add_numbered_list_item("Third numbered item", 0);
317
318 // ── SECTION 4: TAB STOPS ──
319 doc.add_paragraph("");
320 doc.add_paragraph("4. Tab Stops").style("Heading1");
321
322 doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
323 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
324 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
325 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
326 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
327 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
328
329 doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
330 doc.add_paragraph("Item\t\tPrice")
331 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
332 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
333 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
334 doc.add_paragraph("Widget\t\t$19.99")
335 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
336 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
337 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
338 doc.add_paragraph("Gadget\t\t$249.50")
339 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
340 .add_tab_stop_with_leader(
341 TabAlignment::Right,
342 Length::inches(4.0),
343 TabLeader::Underscore,
344 )
345 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
346
347 // ── SECTION 5: TABLES ──
348 doc.add_paragraph("").page_break_before(true);
349 doc.add_paragraph("5. Tables").style("Heading1");
350
351 doc.add_paragraph("5.1 Basic Table").style("Heading2");
352 {
353 let mut tbl = doc
354 .add_table(4, 3)
355 .borders(BorderStyle::Single, 4, "000000");
356 for col in 0..3 {
357 tbl.cell(0, col).unwrap().shading("2E75B6");
358 }
359 tbl.cell(0, 0).unwrap().set_text("Name");
360 tbl.cell(0, 1).unwrap().set_text("Role");
361 tbl.cell(0, 2).unwrap().set_text("Location");
362 tbl.cell(1, 0).unwrap().set_text("Walter White");
363 tbl.cell(1, 1).unwrap().set_text("CEO");
364 tbl.cell(1, 2).unwrap().set_text("Albuquerque");
365 tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
366 tbl.cell(2, 1).unwrap().set_text("CTO");
367 tbl.cell(2, 2).unwrap().set_text("Remote");
368 tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
369 tbl.cell(3, 1).unwrap().set_text("Security");
370 tbl.cell(3, 2).unwrap().set_text("Washington");
371 }
372
373 doc.add_paragraph("");
374
375 doc.add_paragraph("5.2 Column Span & Cell Shading")
376 .style("Heading2");
377 {
378 let mut tbl = doc
379 .add_table(3, 4)
380 .borders(BorderStyle::Single, 4, "000000")
381 .width_pct(100.0);
382 tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
383 tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
384 tbl.cell(1, 0).unwrap().set_text("Region");
385 tbl.cell(1, 0).unwrap().shading("D6E4F0");
386 tbl.cell(1, 1).unwrap().set_text("Q1");
387 tbl.cell(1, 1).unwrap().shading("D6E4F0");
388 tbl.cell(1, 2).unwrap().set_text("Q2");
389 tbl.cell(1, 2).unwrap().shading("D6E4F0");
390 tbl.cell(1, 3).unwrap().set_text("Total");
391 tbl.cell(1, 3).unwrap().shading("D6E4F0");
392 tbl.cell(2, 0).unwrap().set_text("Americas");
393 tbl.cell(2, 1).unwrap().set_text("$2.4M");
394 tbl.cell(2, 2).unwrap().set_text("$2.7M");
395 tbl.cell(2, 3).unwrap().set_text("$5.1M");
396 }
397
398 doc.add_paragraph("");
399
400 doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
401 {
402 let mut tbl = doc
403 .add_table(4, 3)
404 .borders(BorderStyle::Single, 4, "000000");
405 tbl.cell(0, 0).unwrap().set_text("Category");
406 tbl.cell(0, 0).unwrap().shading("E2EFDA");
407 tbl.cell(0, 1).unwrap().set_text("Item");
408 tbl.cell(0, 1).unwrap().shading("E2EFDA");
409 tbl.cell(0, 2).unwrap().set_text("Price");
410 tbl.cell(0, 2).unwrap().shading("E2EFDA");
411 tbl.cell(1, 0).unwrap().set_text("Hardware");
412 tbl.cell(1, 0).unwrap().v_merge_restart();
413 tbl.cell(1, 1).unwrap().set_text("Laptop");
414 tbl.cell(1, 2).unwrap().set_text("$1,200");
415 tbl.cell(2, 0).unwrap().v_merge_continue();
416 tbl.cell(2, 1).unwrap().set_text("Monitor");
417 tbl.cell(2, 2).unwrap().set_text("$450");
418 tbl.cell(3, 0).unwrap().set_text("Software");
419 tbl.cell(3, 1).unwrap().set_text("IDE License");
420 tbl.cell(3, 2).unwrap().set_text("$200/yr");
421 }
422
423 doc.add_paragraph("");
424
425 doc.add_paragraph("5.4 Nested Table").style("Heading2");
426 {
427 let mut tbl = doc
428 .add_table(2, 2)
429 .borders(BorderStyle::Single, 6, "2E75B6");
430 tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
431 tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
432 tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
433 {
434 let mut cell = tbl.cell(1, 1).unwrap();
435 cell.set_text("Nested table below:");
436 let mut nested = cell
437 .add_table(2, 2)
438 .borders(BorderStyle::Single, 2, "FF6600");
439 nested.cell(0, 0).unwrap().set_text("A");
440 nested.cell(0, 1).unwrap().set_text("B");
441 nested.cell(1, 0).unwrap().set_text("C");
442 nested.cell(1, 1).unwrap().set_text("D");
443 }
444 }
445
446 doc.add_paragraph("");
447
448 doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
449 .style("Heading2");
450 {
451 let mut tbl = doc
452 .add_table(2, 3)
453 .borders(BorderStyle::Single, 4, "666666");
454 tbl.row(0).unwrap().height(Length::pt(50.0)).header();
455 tbl.cell(0, 0).unwrap().set_text("Top");
456 tbl.cell(0, 0)
457 .unwrap()
458 .vertical_alignment(VerticalAlignment::Top)
459 .shading("FFF2CC");
460 tbl.cell(0, 1).unwrap().set_text("Center");
461 tbl.cell(0, 1)
462 .unwrap()
463 .vertical_alignment(VerticalAlignment::Center)
464 .shading("D9E2F3");
465 tbl.cell(0, 2).unwrap().set_text("Bottom");
466 tbl.cell(0, 2)
467 .unwrap()
468 .vertical_alignment(VerticalAlignment::Bottom)
469 .shading("E2EFDA");
470 tbl.row(1).unwrap().cant_split();
471 tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
472 tbl.cell(1, 0).unwrap().no_wrap();
473 tbl.cell(1, 1).unwrap().set_text("Fixed width");
474 tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
475 tbl.cell(1, 2).unwrap().set_text("Normal");
476 }
477
478 // ── SECTION 6: IMAGES ──
479 doc.add_paragraph("").page_break_before(true);
480 doc.add_paragraph("6. Images").style("Heading1");
481
482 doc.add_paragraph("6.1 Inline Image").style("Heading2");
483 doc.add_paragraph("A blue gradient image below:");
484 let img = create_sample_png(200, 50, [0, 80, 200]);
485 doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
486
487 doc.add_paragraph("");
488 doc.add_paragraph("6.2 Header Image").style("Heading2");
489 let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
490 doc.set_header_image(
491 &hdr_img,
492 "header_logo.png",
493 Length::inches(2.0),
494 Length::inches(0.2),
495 );
496 doc.add_paragraph("The header now contains an inline image (check the top of this page).");
497
498 doc.add_paragraph("");
499 doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
500
501 // ── SECTION 7: CONTENT MANIPULATION ──
502 doc.add_paragraph("").page_break_before(true);
503 doc.add_paragraph("7. Content Manipulation")
504 .style("Heading1");
505
506 doc.add_paragraph("7.1 Placeholder Replacement")
507 .style("Heading2");
508 doc.add_paragraph("Customer: {{customer}}");
509 doc.add_paragraph("Date: {{date}}");
510 doc.add_paragraph("Reference: {{ref_number}}");
511 {
512 let mut tbl = doc
513 .add_table(2, 2)
514 .borders(BorderStyle::Single, 4, "000000");
515 tbl.cell(0, 0).unwrap().set_text("Project");
516 tbl.cell(0, 0).unwrap().shading("D6E4F0");
517 tbl.cell(0, 1).unwrap().set_text("{{project}}");
518 tbl.cell(1, 0).unwrap().set_text("Status");
519 tbl.cell(1, 0).unwrap().shading("D6E4F0");
520 tbl.cell(1, 1).unwrap().set_text("{{status}}");
521 }
522
523 let mut replacements = HashMap::new();
524 replacements.insert("{{customer}}", "Tensorbee Inc.");
525 replacements.insert("{{date}}", "February 22, 2026");
526 replacements.insert("{{ref_number}}", "TB-2026-001");
527 replacements.insert("{{project}}", "Infrastructure Upgrade");
528 replacements.insert("{{status}}", "In Progress");
529 let n = doc.replace_all(&replacements);
530 doc.add_paragraph(&format!("({n} placeholders replaced above)"));
531
532 doc.add_paragraph("");
533
534 doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
535 doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
536 let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
537 doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
538
539 doc.add_paragraph("");
540
541 doc.add_paragraph("7.3 Content Insertion").style("Heading2");
542 doc.add_paragraph("Section A: First content.");
543 doc.add_paragraph("Section C: Third content.");
544 if let Some(idx) = doc.find_content_index("Section C") {
545 doc.insert_paragraph(
546 idx,
547 "Section B: Inserted between A and C via find_content_index().",
548 );
549 }
550
551 // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
552 doc.add_paragraph("").section_break(SectionBreak::NextPage);
553 doc.add_paragraph("8. Section Breaks & Orientation")
554 .style("Heading1");
555 doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
556
557 {
558 let mut tbl = doc
559 .add_table(3, 7)
560 .borders(BorderStyle::Single, 4, "2E75B6");
561 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
562 for (c, h) in headers.iter().enumerate() {
563 tbl.cell(0, c).unwrap().set_text(h);
564 tbl.cell(0, c).unwrap().shading("2E75B6");
565 }
566 let data = [
567 [
568 "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
569 ],
570 [
571 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
572 ],
573 ];
574 for (r, row) in data.iter().enumerate() {
575 for (c, v) in row.iter().enumerate() {
576 tbl.cell(r + 1, c).unwrap().set_text(v);
577 }
578 }
579 }
580
581 doc.add_paragraph("")
582 .section_break(SectionBreak::NextPage)
583 .section_landscape();
584
585 // ── SECTION 9: CUSTOM STYLES ──
586 doc.add_paragraph("9. Custom Styles").style("Heading1");
587 doc.add_style(
588 StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
589 .based_on("Normal")
590 .paragraph_properties({
591 let mut ppr = rdocx_oxml::properties::CT_PPr::default();
592 ppr.shading = Some(rdocx_oxml::properties::CT_Shd {
593 val: "clear".to_string(),
594 color: None,
595 fill: Some("FFF2CC".to_string()),
596 });
597 ppr
598 })
599 .run_properties({
600 let mut rpr = rdocx_oxml::properties::CT_RPr::default();
601 rpr.bold = Some(true);
602 rpr.color = Some("C45911".to_string());
603 rpr
604 }),
605 );
606 doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
607 .style("CustomHighlight");
608
609 doc.add_paragraph("");
610
611 // ── SECTION 10: DOCUMENT INTELLIGENCE ──
612 doc.add_paragraph("10. Document Intelligence API")
613 .style("Heading1");
614
615 let wc = doc.word_count();
616 let hc = doc.headings().len();
617 let ic = doc.images().len();
618 let lc = doc.links().len();
619 doc.add_paragraph(&format!("Word count: {wc}"));
620 doc.add_paragraph(&format!("Heading count: {hc}"));
621 doc.add_paragraph(&format!("Image count: {ic}"));
622 doc.add_paragraph(&format!("Link count: {lc}"));
623
624 let outline = doc.document_outline();
625 doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
626
627 let issues = doc.audit_accessibility();
628 doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
629 for issue in &issues {
630 doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
631 }
632
633 // ── SECTION 11: DOCUMENT MERGING ──
634 doc.add_paragraph("");
635 doc.add_paragraph("11. Document Merging").style("Heading1");
636 {
637 let mut other = Document::new();
638 other.add_paragraph("This paragraph was merged from another document using append().");
639 doc.append(&other);
640 }
641
642 doc.add_paragraph("");
643
644 // ── SUMMARY ──
645 doc.add_paragraph("Summary of Demonstrated Features")
646 .style("Heading1");
647 let features = [
648 "Page setup: size, margins, header/footer distance, gutter",
649 "Document metadata: title, author, subject, keywords",
650 "Headers and footers: text, images, different first page",
651 "Background images (full-page behind text)",
652 "Table of Contents generation with bookmarks",
653 "Text formatting: bold, italic, underline styles, strike, color, size, font",
654 "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
655 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
656 "Pagination controls: keep-with-next, keep-together, widow control",
657 "Bullet and numbered lists with nesting",
658 "Tab stops with dot/underscore/hyphen leaders",
659 "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
660 "Row properties: header rows, exact height, cant-split",
661 "Cell properties: width, no-wrap, vertical alignment",
662 "Inline images and header images",
663 "Placeholder replacement (single and batch)",
664 "Regex-based find and replace",
665 "Content insertion at specific positions",
666 "Section breaks with mixed portrait/landscape",
667 "Custom paragraph and character styles",
668 "Document intelligence: word count, headings, outline, images, links",
669 "Accessibility audit",
670 "Document merging (append)",
671 "Export: DOCX, PDF, HTML, Markdown",
672 ];
673 for f in &features {
674 doc.add_bullet_list_item(f, 0);
675 }
676 doc.add_paragraph("");
677 doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
678 .alignment(Alignment::Center)
679 .shading("E2EFDA")
680 .border_all(BorderStyle::Single, 2, "00AA00");
681
682 doc
683}Set the default footer to an inline image.
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
688fn generate_proposal(_samples_dir: &Path) -> Document {
689 let mut doc = Document::new();
690
691 // Colors: Navy #1B2A4A, Gold #C5922E, Light #F4F1EB
692 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
693 doc.set_margins(
694 Length::inches(1.0),
695 Length::inches(1.0),
696 Length::inches(1.0),
697 Length::inches(1.0),
698 );
699 doc.set_title("AI Platform Modernization Proposal");
700 doc.set_author("Walter White");
701 doc.set_subject("Technology Proposal");
702 doc.set_keywords("proposal, AI, modernization, Tensorbee");
703
704 // Banner header
705 let logo_img = create_logo_png(220, 48);
706 let banner = build_header_banner_xml(
707 "rId1",
708 &BannerOpts {
709 bg_color: "1B2A4A",
710 banner_width: 7772400,
711 banner_height: 914400,
712 logo_width: 2011680,
713 logo_height: 438912,
714 logo_x_offset: 295125,
715 logo_y_offset: 237744,
716 },
717 );
718 doc.set_raw_header_with_images(
719 banner,
720 &[("rId1", &logo_img, "logo.png")],
721 rdocx_oxml::header_footer::HdrFtrType::Default,
722 );
723 doc.set_different_first_page(true);
724 let cover_banner = build_header_banner_xml(
725 "rId1",
726 &BannerOpts {
727 bg_color: "C5922E",
728 banner_width: 7772400,
729 banner_height: 914400,
730 logo_width: 2011680,
731 logo_height: 438912,
732 logo_x_offset: 295125,
733 logo_y_offset: 237744,
734 },
735 );
736 doc.set_raw_header_with_images(
737 cover_banner,
738 &[("rId1", &logo_img, "logo.png")],
739 rdocx_oxml::header_footer::HdrFtrType::First,
740 );
741 doc.set_margins(
742 Length::twips(2292),
743 Length::twips(1440),
744 Length::twips(1440),
745 Length::twips(1440),
746 );
747 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
748 doc.set_footer("Tensorbee — Confidential");
749
750 // Custom styles
751 doc.add_style(
752 StyleBuilder::paragraph("ProposalTitle", "Proposal Title")
753 .based_on("Normal")
754 .run_properties({
755 let mut rpr = rdocx_oxml::properties::CT_RPr::default();
756 rpr.bold = Some(true);
757 rpr.font_ascii = Some("Georgia".to_string());
758 rpr.font_hansi = Some("Georgia".to_string());
759 rpr.sz = Some(rdocx_oxml::HalfPoint(56)); // half-points → 28pt
760 rpr.color = Some("1B2A4A".to_string());
761 rpr
762 }),
763 );
764
765 // ── Cover Page ──
766 for _ in 0..4 {
767 doc.add_paragraph("");
768 }
769 doc.add_paragraph("AI Platform Modernization")
770 .style("ProposalTitle")
771 .alignment(Alignment::Center);
772 doc.add_paragraph("")
773 .alignment(Alignment::Center)
774 .border_bottom(BorderStyle::Single, 8, "C5922E");
775 {
776 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
777 p.add_run("Prepared for Global Financial Corp.")
778 .size(14.0)
779 .color("666666")
780 .italic(true);
781 }
782 {
783 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
784 p.add_run("Prepared by: Walter White, CEO — Tensorbee")
785 .size(12.0)
786 .color("888888");
787 }
788 {
789 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
790 p.add_run("February 22, 2026").size(12.0).color("888888");
791 }
792
793 // ── TOC ──
794 doc.add_paragraph("").page_break_before(true);
795 doc.add_paragraph("Table of Contents").style("Heading1");
796 // We'll add a manual-style TOC since the insert_toc goes at a specific position
797 doc.insert_toc(doc.content_count(), 2);
798
799 // ── Executive Summary ──
800 doc.add_paragraph("").page_break_before(true);
801 doc.add_paragraph("Executive Summary").style("Heading1");
802 doc.add_paragraph(
803 "Tensorbee proposes a comprehensive modernization of Global Financial Corp's AI platform, \
804 transitioning from legacy batch-processing systems to a real-time inference architecture. \
805 This transformation will reduce model deployment time from weeks to hours, improve \
806 prediction accuracy by an estimated 23%, and deliver $4.2M in annual operational savings.",
807 )
808 .first_line_indent(Length::inches(0.3));
809 doc.add_paragraph(
810 "The project spans three phases over 18 months, with a total investment of $2.8M. \
811 Tensorbee brings deep expertise in ML infrastructure, having successfully completed \
812 similar transformations for three Fortune 500 financial institutions.",
813 )
814 .first_line_indent(Length::inches(0.3));
815
816 // Key metrics table
817 doc.add_paragraph("");
818 {
819 let mut tbl = doc
820 .add_table(5, 2)
821 .borders(BorderStyle::Single, 4, "1B2A4A")
822 .width_pct(80.0);
823 tbl.cell(0, 0).unwrap().set_text("Key Metric");
824 tbl.cell(0, 0).unwrap().shading("1B2A4A");
825 tbl.cell(0, 1).unwrap().set_text("Value");
826 tbl.cell(0, 1).unwrap().shading("1B2A4A");
827 let rows = [
828 ("Total Investment", "$2.8M"),
829 ("Annual Savings", "$4.2M"),
830 ("ROI (Year 1)", "150%"),
831 ("Timeline", "18 months"),
832 ];
833 for (i, (k, v)) in rows.iter().enumerate() {
834 tbl.cell(i + 1, 0).unwrap().set_text(k);
835 if i % 2 == 0 {
836 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
837 }
838 tbl.cell(i + 1, 1).unwrap().set_text(v);
839 if i % 2 == 0 {
840 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
841 }
842 }
843 }
844
845 // ── Problem Statement ──
846 doc.add_paragraph("").page_break_before(true);
847 doc.add_paragraph("Problem Statement").style("Heading1");
848 doc.add_paragraph(
849 "Global Financial Corp's current AI infrastructure faces three critical challenges:",
850 );
851 doc.add_numbered_list_item("Deployment Latency — New models take 3-4 weeks to deploy to production, compared to the industry benchmark of 2-3 days.", 0);
852 doc.add_numbered_list_item("Scalability Constraints — The batch processing architecture cannot handle real-time inference demands during peak trading hours.", 0);
853 doc.add_numbered_list_item("Technical Debt — Legacy Python 2.7 codebases and manual deployment scripts create reliability risks and slow iteration speed.", 0);
854
855 // ── Proposed Solution ──
856 doc.add_paragraph("").page_break_before(true);
857 doc.add_paragraph("Proposed Solution").style("Heading1");
858
859 doc.add_paragraph("Phase 1: Foundation (Months 1-6)")
860 .style("Heading2");
861 doc.add_bullet_list_item("Deploy Kubernetes-based ML serving infrastructure", 0);
862 doc.add_bullet_list_item("Implement CI/CD pipeline for model deployment", 0);
863 doc.add_bullet_list_item("Migrate top 5 production models to new platform", 0);
864
865 doc.add_paragraph("Phase 2: Expansion (Months 7-12)")
866 .style("Heading2");
867 doc.add_bullet_list_item(
868 "Real-time feature engineering pipeline (Apache Kafka + Flink)",
869 0,
870 );
871 doc.add_bullet_list_item("A/B testing framework for model variants", 0);
872 doc.add_bullet_list_item("Automated model monitoring and drift detection", 0);
873
874 doc.add_paragraph("Phase 3: Optimization (Months 13-18)")
875 .style("Heading2");
876 doc.add_bullet_list_item("GPU inference optimization (TensorRT/ONNX Runtime)", 0);
877 doc.add_bullet_list_item("Multi-region deployment for latency reduction", 0);
878 doc.add_bullet_list_item(
879 "Self-service model deployment portal for data science teams",
880 0,
881 );
882
883 // ── Budget ──
884 doc.add_paragraph("Budget Breakdown").style("Heading1");
885 {
886 let mut tbl = doc
887 .add_table(6, 3)
888 .borders(BorderStyle::Single, 4, "1B2A4A")
889 .width_pct(100.0);
890 tbl.cell(0, 0).unwrap().set_text("Category");
891 tbl.cell(0, 0).unwrap().shading("1B2A4A");
892 tbl.cell(0, 1).unwrap().set_text("Cost");
893 tbl.cell(0, 1).unwrap().shading("1B2A4A");
894 tbl.cell(0, 2).unwrap().set_text("Notes");
895 tbl.cell(0, 2).unwrap().shading("1B2A4A");
896 let items = [
897 (
898 "Infrastructure",
899 "$450,000",
900 "Cloud compute + GPU instances",
901 ),
902 ("Engineering Services", "$1,600,000", "12 FTE x 18 months"),
903 (
904 "Software Licenses",
905 "$350,000",
906 "Kafka, monitoring, CI/CD tools",
907 ),
908 (
909 "Training & Enablement",
910 "$200,000",
911 "Team training, documentation",
912 ),
913 ("Contingency (10%)", "$200,000", "Risk buffer"),
914 ];
915 for (i, (cat, cost, note)) in items.iter().enumerate() {
916 tbl.cell(i + 1, 0).unwrap().set_text(cat);
917 tbl.cell(i + 1, 1).unwrap().set_text(cost);
918 tbl.cell(i + 1, 2).unwrap().set_text(note);
919 if i % 2 == 0 {
920 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
921 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
922 tbl.cell(i + 1, 2).unwrap().shading("F4F1EB");
923 }
924 }
925 }
926
927 // ── Closing ──
928 doc.add_paragraph("");
929 doc.add_paragraph("Next Steps").style("Heading1");
930 doc.add_numbered_list_item(
931 "Schedule technical deep-dive with GFC engineering team (Week 1)",
932 0,
933 );
934 doc.add_numbered_list_item("Finalize scope and sign SOW (Week 2-3)", 0);
935 doc.add_numbered_list_item("Kick off Phase 1 with joint planning session (Week 4)", 0);
936
937 doc.add_paragraph("");
938 {
939 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
940 p.add_run("Walter White")
941 .bold(true)
942 .size(14.0)
943 .color("1B2A4A");
944 }
945 {
946 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
947 p.add_run("CEO, Tensorbee | walter@tensorbee.com")
948 .size(10.0)
949 .color("888888");
950 }
951
952 doc
953}
954
955// =============================================================================
956// 3. QUOTE / BILL OF MATERIALS — Teal + orange scheme
957// =============================================================================
958fn generate_quote(_samples_dir: &Path) -> Document {
959 let mut doc = Document::new();
960
961 // Colors: Teal #008B8B, Dark #1A3C3C, Orange #E07020, Light #F0F8F8
962 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
963 doc.set_margins(
964 Length::inches(0.75),
965 Length::inches(0.75),
966 Length::inches(0.75),
967 Length::inches(0.75),
968 );
969 doc.set_title("Quotation QT-2026-0147");
970 doc.set_author("Walter White");
971 doc.set_keywords("quote, BOM, Tensorbee");
972
973 doc.set_header("Tensorbee — Quotation");
974 doc.set_footer("QT-2026-0147 | Page");
975
976 // ── Header Block ──
977 {
978 let mut p = doc.add_paragraph("").alignment(Alignment::Left);
979 p.add_run("TENSORBEE")
980 .bold(true)
981 .size(28.0)
982 .color("008B8B")
983 .font("Helvetica");
984 }
985 doc.add_paragraph("123 Innovation Drive, Suite 400")
986 .alignment(Alignment::Left);
987 doc.add_paragraph("San Francisco, CA 94105 | +1 (415) 555-0199")
988 .alignment(Alignment::Left);
989 doc.add_paragraph("")
990 .border_bottom(BorderStyle::Single, 8, "008B8B");
991
992 // Quote meta
993 doc.add_paragraph("");
994 {
995 let mut tbl = doc.add_table(4, 4).width_pct(100.0);
996 tbl.cell(0, 0).unwrap().set_text("QUOTATION");
997 tbl.cell(0, 0).unwrap().shading("008B8B").grid_span(2);
998 tbl.cell(0, 2).unwrap().set_text("DATE");
999 tbl.cell(0, 2).unwrap().shading("008B8B");
1000 tbl.cell(0, 3).unwrap().set_text("VALID UNTIL");
1001 tbl.cell(0, 3).unwrap().shading("008B8B");
1002 tbl.cell(1, 0).unwrap().set_text("Quote #:");
1003 tbl.cell(1, 1).unwrap().set_text("QT-2026-0147");
1004 tbl.cell(1, 2).unwrap().set_text("Feb 22, 2026");
1005 tbl.cell(1, 3).unwrap().set_text("Mar 22, 2026");
1006 tbl.cell(2, 0).unwrap().set_text("Prepared by:");
1007 tbl.cell(2, 1).unwrap().set_text("Walter White");
1008 tbl.cell(2, 2).unwrap().set_text("Payment:");
1009 tbl.cell(2, 3).unwrap().set_text("Net 30");
1010 tbl.cell(3, 0).unwrap().set_text("Customer:");
1011 tbl.cell(3, 1).unwrap().set_text("Meridian Dynamics LLC");
1012 tbl.cell(3, 2).unwrap().set_text("Currency:");
1013 tbl.cell(3, 3).unwrap().set_text("USD");
1014 }
1015
1016 doc.add_paragraph("");
1017
1018 // ── Bill of Materials ──
1019 {
1020 let mut p = doc.add_paragraph("");
1021 p.add_run("Bill of Materials")
1022 .bold(true)
1023 .size(16.0)
1024 .color("1A3C3C");
1025 }
1026 doc.add_paragraph("");
1027
1028 {
1029 let mut tbl = doc
1030 .add_table(10, 6)
1031 .borders(BorderStyle::Single, 2, "008B8B")
1032 .width_pct(100.0)
1033 .layout_fixed();
1034 // Headers
1035 let hdrs = [
1036 "#",
1037 "Part Number",
1038 "Description",
1039 "Qty",
1040 "Unit Price",
1041 "Total",
1042 ];
1043 for (c, h) in hdrs.iter().enumerate() {
1044 tbl.cell(0, c).unwrap().set_text(h);
1045 tbl.cell(0, c).unwrap().shading("008B8B");
1046 }
1047
1048 let items: Vec<(&str, &str, &str, &str, &str)> = vec![
1049 (
1050 "TB-GPU-A100",
1051 "NVIDIA A100 80GB GPU",
1052 "4",
1053 "$12,500.00",
1054 "$50,000.00",
1055 ),
1056 (
1057 "TB-SRV-R750",
1058 "Dell R750xa Server Chassis",
1059 "2",
1060 "$8,200.00",
1061 "$16,400.00",
1062 ),
1063 (
1064 "TB-RAM-256G",
1065 "256GB DDR5 ECC Memory Module",
1066 "8",
1067 "$890.00",
1068 "$7,120.00",
1069 ),
1070 (
1071 "TB-SSD-3840",
1072 "3.84TB NVMe U.2 SSD",
1073 "8",
1074 "$1,150.00",
1075 "$9,200.00",
1076 ),
1077 (
1078 "TB-NET-CX7",
1079 "ConnectX-7 200GbE NIC",
1080 "4",
1081 "$1,800.00",
1082 "$7,200.00",
1083 ),
1084 (
1085 "TB-SW-48P",
1086 "48-Port 100GbE Switch",
1087 "1",
1088 "$22,000.00",
1089 "$22,000.00",
1090 ),
1091 (
1092 "TB-CAB-RACK",
1093 "42U Server Rack + PDU",
1094 "1",
1095 "$4,500.00",
1096 "$4,500.00",
1097 ),
1098 (
1099 "TB-SVC-INST",
1100 "Installation & Configuration",
1101 "1",
1102 "$8,500.00",
1103 "$8,500.00",
1104 ),
1105 (
1106 "TB-SVC-SUPP",
1107 "3-Year Premium Support",
1108 "1",
1109 "$15,000.00",
1110 "$15,000.00",
1111 ),
1112 ];
1113
1114 for (i, (pn, desc, qty, unit, total)) in items.iter().enumerate() {
1115 let row = i + 1;
1116 tbl.cell(row, 0).unwrap().set_text(&format!("{}", i + 1));
1117 tbl.cell(row, 1).unwrap().set_text(pn);
1118 tbl.cell(row, 2).unwrap().set_text(desc);
1119 tbl.cell(row, 3).unwrap().set_text(qty);
1120 tbl.cell(row, 4).unwrap().set_text(unit);
1121 tbl.cell(row, 5).unwrap().set_text(total);
1122 if i % 2 == 0 {
1123 for c in 0..6 {
1124 tbl.cell(row, c).unwrap().shading("F0F8F8");
1125 }
1126 }
1127 }
1128 }
1129
1130 doc.add_paragraph("");
1131
1132 // ── Totals ──
1133 {
1134 let mut tbl = doc
1135 .add_table(4, 2)
1136 .borders(BorderStyle::Single, 2, "008B8B")
1137 .width(Length::inches(3.5))
1138 .alignment(Alignment::Right);
1139 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1140 tbl.cell(0, 1).unwrap().set_text("$139,920.00");
1141 tbl.cell(1, 0).unwrap().set_text("Shipping & Handling");
1142 tbl.cell(1, 1).unwrap().set_text("$2,500.00");
1143 tbl.cell(2, 0).unwrap().set_text("Tax (8.625%)");
1144 tbl.cell(2, 1).unwrap().set_text("$12,068.10");
1145 tbl.cell(3, 0).unwrap().set_text("TOTAL");
1146 tbl.cell(3, 0).unwrap().shading("E07020");
1147 tbl.cell(3, 1).unwrap().set_text("$154,488.10");
1148 tbl.cell(3, 1).unwrap().shading("E07020");
1149 }
1150
1151 doc.add_paragraph("");
1152
1153 // ── Terms & Conditions ──
1154 {
1155 let mut p = doc.add_paragraph("");
1156 p.add_run("Terms & Conditions")
1157 .bold(true)
1158 .size(14.0)
1159 .color("1A3C3C");
1160 }
1161 doc.add_numbered_list_item(
1162 "This quotation is valid for 30 calendar days from the date of issue.",
1163 0,
1164 );
1165 doc.add_numbered_list_item(
1166 "All prices are in USD and exclusive of applicable taxes unless stated.",
1167 0,
1168 );
1169 doc.add_numbered_list_item("Standard lead time is 4-6 weeks from PO receipt.", 0);
1170 doc.add_numbered_list_item(
1171 "Payment terms: Net 30 from invoice date. 2% discount for payment within 10 days.",
1172 0,
1173 );
1174 doc.add_numbered_list_item(
1175 "Warranty: 3-year manufacturer warranty on all hardware components.",
1176 0,
1177 );
1178 doc.add_numbered_list_item(
1179 "Returns subject to 15% restocking fee if initiated after 14 days.",
1180 0,
1181 );
1182
1183 doc.add_paragraph("");
1184 doc.add_paragraph("")
1185 .border_bottom(BorderStyle::Single, 4, "008B8B");
1186 doc.add_paragraph("");
1187 {
1188 let mut p = doc.add_paragraph("");
1189 p.add_run("Accepted by: ").bold(true);
1190 p.add_run("___________________________ Date: ___________");
1191 }
1192 {
1193 let mut p = doc.add_paragraph("");
1194 p.add_run("Print Name: ").bold(true);
1195 p.add_run("___________________________ Title: ___________");
1196 }
1197
1198 doc
1199}
1200
1201// =============================================================================
1202// 4. INVOICE — Crimson + charcoal scheme
1203// =============================================================================
1204fn generate_invoice(_samples_dir: &Path) -> Document {
1205 let mut doc = Document::new();
1206
1207 // Colors: Crimson #B22222, Charcoal #333333, Light #FAF0F0
1208 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1209 doc.set_margins(
1210 Length::inches(0.75),
1211 Length::inches(0.75),
1212 Length::inches(0.75),
1213 Length::inches(0.75),
1214 );
1215 doc.set_title("Invoice INV-2026-0392");
1216 doc.set_author("Walter White");
1217 doc.set_keywords("invoice, Tensorbee");
1218
1219 doc.set_footer("Tensorbee — Thank you for your business!");
1220
1221 // ── Company & Invoice Header ──
1222 {
1223 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1224 // Company name left, INVOICE right
1225 {
1226 let mut cell = tbl.cell(0, 0).unwrap();
1227 let mut p = cell.add_paragraph("");
1228 p.add_run("TENSORBEE")
1229 .bold(true)
1230 .size(32.0)
1231 .color("B22222")
1232 .font("Helvetica");
1233 }
1234 {
1235 let mut cell = tbl.cell(0, 1).unwrap();
1236 let mut p = cell.add_paragraph("");
1237 p.add_run("INVOICE").bold(true).size(32.0).color("333333");
1238 }
1239 tbl.cell(1, 0)
1240 .unwrap()
1241 .set_text("123 Innovation Drive, Suite 400");
1242 tbl.cell(1, 1).unwrap().set_text("Invoice #: INV-2026-0392");
1243 tbl.cell(2, 0).unwrap().set_text("San Francisco, CA 94105");
1244 tbl.cell(2, 1).unwrap().set_text("Date: February 22, 2026");
1245 tbl.cell(3, 0).unwrap().set_text("walter@tensorbee.com");
1246 tbl.cell(3, 1).unwrap().set_text("Due Date: March 24, 2026");
1247 }
1248
1249 doc.add_paragraph("")
1250 .border_bottom(BorderStyle::Thick, 8, "B22222");
1251 doc.add_paragraph("");
1252
1253 // ── Bill To / Ship To ──
1254 {
1255 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1256 tbl.cell(0, 0).unwrap().set_text("BILL TO");
1257 tbl.cell(0, 0).unwrap().shading("B22222");
1258 tbl.cell(0, 1).unwrap().set_text("SHIP TO");
1259 tbl.cell(0, 1).unwrap().shading("B22222");
1260 tbl.cell(1, 0).unwrap().set_text("Meridian Dynamics LLC");
1261 tbl.cell(1, 1).unwrap().set_text("Meridian Dynamics LLC");
1262 tbl.cell(2, 0).unwrap().set_text("456 Enterprise Blvd");
1263 tbl.cell(2, 1).unwrap().set_text("Attn: Server Room B");
1264 tbl.cell(3, 0).unwrap().set_text("Austin, TX 78701");
1265 tbl.cell(3, 1)
1266 .unwrap()
1267 .set_text("456 Enterprise Blvd, Austin, TX 78701");
1268 }
1269
1270 doc.add_paragraph("");
1271
1272 // ── Line Items ──
1273 {
1274 let mut tbl = doc
1275 .add_table(8, 5)
1276 .borders(BorderStyle::Single, 2, "B22222")
1277 .width_pct(100.0);
1278 let hdrs = ["Description", "Qty", "Unit Price", "Tax", "Amount"];
1279 for (c, h) in hdrs.iter().enumerate() {
1280 tbl.cell(0, c).unwrap().set_text(h);
1281 tbl.cell(0, c).unwrap().shading("B22222");
1282 }
1283 let items = [
1284 (
1285 "ML Infrastructure Setup — Phase 1",
1286 "1",
1287 "$45,000.00",
1288 "$3,881.25",
1289 "$48,881.25",
1290 ),
1291 (
1292 "Data Pipeline Development (160 hrs)",
1293 "160",
1294 "$225.00",
1295 "$3,105.00",
1296 "$39,105.00",
1297 ),
1298 (
1299 "GPU Cluster Configuration",
1300 "1",
1301 "$12,000.00",
1302 "$1,035.00",
1303 "$13,035.00",
1304 ),
1305 (
1306 "API Gateway Implementation",
1307 "1",
1308 "$18,500.00",
1309 "$1,595.63",
1310 "$20,095.63",
1311 ),
1312 (
1313 "Load Testing & QA (80 hrs)",
1314 "80",
1315 "$195.00",
1316 "$1,345.50",
1317 "$16,945.50",
1318 ),
1319 (
1320 "Documentation & Training",
1321 "1",
1322 "$8,000.00",
1323 "$690.00",
1324 "$8,690.00",
1325 ),
1326 (
1327 "Project Management (3 months)",
1328 "3",
1329 "$6,500.00",
1330 "$1,679.63",
1331 "$21,179.63",
1332 ),
1333 ];
1334 for (i, (desc, qty, unit, tax, amt)) in items.iter().enumerate() {
1335 let r = i + 1;
1336 tbl.cell(r, 0).unwrap().set_text(desc);
1337 tbl.cell(r, 1).unwrap().set_text(qty);
1338 tbl.cell(r, 2).unwrap().set_text(unit);
1339 tbl.cell(r, 3).unwrap().set_text(tax);
1340 tbl.cell(r, 4).unwrap().set_text(amt);
1341 if i % 2 == 0 {
1342 for c in 0..5 {
1343 tbl.cell(r, c).unwrap().shading("FAF0F0");
1344 }
1345 }
1346 }
1347 }
1348
1349 doc.add_paragraph("");
1350
1351 // ── Totals ──
1352 {
1353 let mut tbl = doc
1354 .add_table(4, 2)
1355 .borders(BorderStyle::Single, 2, "B22222")
1356 .width(Length::inches(3.0))
1357 .alignment(Alignment::Right);
1358 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1359 tbl.cell(0, 1).unwrap().set_text("$154,600.00");
1360 tbl.cell(1, 0).unwrap().set_text("Tax (8.625%)");
1361 tbl.cell(1, 1).unwrap().set_text("$13,334.25");
1362 tbl.cell(2, 0).unwrap().set_text("Discount (5%)");
1363 tbl.cell(2, 1).unwrap().set_text("-$7,730.00");
1364 tbl.cell(3, 0).unwrap().set_text("AMOUNT DUE");
1365 tbl.cell(3, 0).unwrap().shading("B22222");
1366 tbl.cell(3, 1).unwrap().set_text("$160,204.25");
1367 tbl.cell(3, 1).unwrap().shading("B22222");
1368 }
1369
1370 doc.add_paragraph("");
1371 doc.add_paragraph("");
1372
1373 // ── Payment Details ──
1374 {
1375 let mut p = doc.add_paragraph("");
1376 p.add_run("Payment Details")
1377 .bold(true)
1378 .size(14.0)
1379 .color("333333");
1380 }
1381 doc.add_paragraph("Bank: Silicon Valley Bank")
1382 .indent_left(Length::inches(0.3));
1383 doc.add_paragraph("Account: Tensorbee Inc. — 0847-2953-1120")
1384 .indent_left(Length::inches(0.3));
1385 doc.add_paragraph("Routing: 121140399")
1386 .indent_left(Length::inches(0.3));
1387 doc.add_paragraph("Swift: SVBKUS6S")
1388 .indent_left(Length::inches(0.3));
1389
1390 doc.add_paragraph("");
1391
1392 doc.add_paragraph("Please include invoice number INV-2026-0392 in the payment reference.")
1393 .shading("FAF0F0")
1394 .border_all(BorderStyle::Single, 2, "B22222");
1395
1396 doc
1397}
1398
1399// =============================================================================
1400// 5. REPORT — Forest green + earth tones, with images & hierarchical sections
1401// =============================================================================
1402fn generate_report(_samples_dir: &Path) -> Document {
1403 let mut doc = Document::new();
1404
1405 // Colors: Forest #2D5016, Sage #6B8E23, Earth #8B7355, Cream #FFFAF0
1406 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1407 doc.set_margins(
1408 Length::inches(1.0),
1409 Length::inches(1.0),
1410 Length::inches(1.0),
1411 Length::inches(1.0),
1412 );
1413 doc.set_title("Q4 2025 Environmental Impact Report");
1414 doc.set_author("Walter White");
1415 doc.set_subject("Quarterly Environmental Report");
1416 doc.set_keywords("environment, sustainability, report, Tensorbee");
1417
1418 doc.set_different_first_page(true);
1419 doc.set_first_page_header("");
1420 doc.set_header("Tensorbee — Q4 2025 Environmental Impact Report");
1421 doc.set_footer("CONFIDENTIAL — Page");
1422
1423 // ── Cover ──
1424 let cover_bg = create_sample_png(612, 792, [20, 50, 15]);
1425 doc.add_background_image(&cover_bg, "report_cover.png");
1426
1427 for _ in 0..5 {
1428 doc.add_paragraph("");
1429 }
1430 {
1431 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1432 p.add_run("Q4 2025")
1433 .bold(true)
1434 .size(48.0)
1435 .color("FFFFFF")
1436 .font("Georgia");
1437 }
1438 {
1439 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1440 p.add_run("Environmental Impact Report")
1441 .size(24.0)
1442 .color("90EE90")
1443 .font("Georgia")
1444 .italic(true);
1445 }
1446 doc.add_paragraph("");
1447 {
1448 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1449 p.add_run("Tensorbee — Sustainability Division")
1450 .size(14.0)
1451 .color("C0C0C0");
1452 }
1453 {
1454 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1455 p.add_run("Prepared by Walter White, Chief Sustainability Officer")
1456 .size(11.0)
1457 .color("AAAAAA");
1458 }
1459
1460 // ── TOC ──
1461 doc.add_paragraph("").page_break_before(true);
1462 doc.insert_toc(doc.content_count(), 3);
1463
1464 // ── Section 1: Executive Overview ──
1465 doc.add_paragraph("").page_break_before(true);
1466 doc.add_paragraph("Executive Overview").style("Heading1");
1467 doc.add_paragraph(
1468 "This report presents Tensorbee's environmental performance for Q4 2025. Our \
1469 sustainability initiatives have yielded a 34% reduction in carbon emissions compared \
1470 to Q4 2024, exceeding our target of 25%. Key achievements include the transition to \
1471 100% renewable energy in our primary data centers and the launch of our carbon offset \
1472 marketplace.",
1473 )
1474 .first_line_indent(Length::inches(0.3));
1475
1476 // Image: performance chart
1477 let chart_img = create_chart_png(400, 200);
1478 doc.add_paragraph("");
1479 doc.add_picture(
1480 &chart_img,
1481 "performance_chart.png",
1482 Length::inches(5.0),
1483 Length::inches(2.5),
1484 )
1485 .alignment(Alignment::Center);
1486 {
1487 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1488 p.add_run("Figure 1: Quarterly Carbon Emissions (tonnes CO2e)")
1489 .italic(true)
1490 .size(9.0)
1491 .color("666666");
1492 }
1493
1494 // ── Section 2: Energy Consumption ──
1495 doc.add_paragraph("").page_break_before(true);
1496 doc.add_paragraph("Energy Consumption").style("Heading1");
1497
1498 doc.add_paragraph("Data Center Operations")
1499 .style("Heading2");
1500 doc.add_paragraph(
1501 "Our three primary data centers consumed a combined 4.2 GWh in Q4 2025, \
1502 a 12% reduction from Q3 2025 driven by improved cooling efficiency and \
1503 server consolidation.",
1504 );
1505
1506 // Data table
1507 {
1508 let mut tbl = doc
1509 .add_table(5, 4)
1510 .borders(BorderStyle::Single, 4, "2D5016")
1511 .width_pct(100.0);
1512 let hdrs = ["Data Center", "Capacity (MW)", "Usage (GWh)", "PUE"];
1513 for (c, h) in hdrs.iter().enumerate() {
1514 tbl.cell(0, c).unwrap().set_text(h);
1515 tbl.cell(0, c).unwrap().shading("2D5016");
1516 }
1517 let rows = [
1518 ("San Francisco (Primary)", "3.2", "1.8", "1.12"),
1519 ("Dublin (EU)", "2.1", "1.2", "1.18"),
1520 ("Singapore (APAC)", "1.8", "1.2", "1.24"),
1521 ("TOTAL", "7.1", "4.2", "1.17 avg"),
1522 ];
1523 for (i, (dc, cap, use_, pue)) in rows.iter().enumerate() {
1524 tbl.cell(i + 1, 0).unwrap().set_text(dc);
1525 tbl.cell(i + 1, 1).unwrap().set_text(cap);
1526 tbl.cell(i + 1, 2).unwrap().set_text(use_);
1527 tbl.cell(i + 1, 3).unwrap().set_text(pue);
1528 if i == 3 {
1529 for c in 0..4 {
1530 tbl.cell(i + 1, c).unwrap().shading("E2EFDA");
1531 }
1532 }
1533 }
1534 }
1535
1536 doc.add_paragraph("");
1537
1538 doc.add_paragraph("Renewable Energy Mix").style("Heading2");
1539 doc.add_paragraph("Breakdown of energy sources across all facilities:");
1540
1541 doc.add_bullet_list_item("Solar PV: 42% (1.76 GWh)", 0);
1542 doc.add_bullet_list_item("Wind Power Purchase Agreements: 38% (1.60 GWh)", 0);
1543 doc.add_bullet_list_item("Hydroelectric: 12% (0.50 GWh)", 0);
1544 doc.add_bullet_list_item("Grid (non-renewable): 8% (0.34 GWh)", 0);
1545
1546 // ── Section 3: Water & Waste ──
1547 doc.add_paragraph("Water & Waste Management")
1548 .style("Heading1");
1549
1550 doc.add_paragraph("Water Usage").style("Heading2");
1551 doc.add_paragraph(
1552 "Total water consumption was 12.4 million gallons, a 15% reduction from Q3. \
1553 Our closed-loop cooling systems now recycle 78% of water used in cooling operations.",
1554 );
1555
1556 doc.add_paragraph("Waste Reduction").style("Heading2");
1557 doc.add_paragraph("E-waste management results for Q4:")
1558 .keep_with_next(true);
1559 {
1560 let mut tbl = doc
1561 .add_table(4, 3)
1562 .borders(BorderStyle::Single, 2, "6B8E23")
1563 .width_pct(80.0);
1564 tbl.cell(0, 0).unwrap().set_text("Category");
1565 tbl.cell(0, 0).unwrap().shading("6B8E23");
1566 tbl.cell(0, 1).unwrap().set_text("Weight (kg)");
1567 tbl.cell(0, 1).unwrap().shading("6B8E23");
1568 tbl.cell(0, 2).unwrap().set_text("Recycled %");
1569 tbl.cell(0, 2).unwrap().shading("6B8E23");
1570 let rows = [
1571 ("Server Hardware", "2,450", "94%"),
1572 ("Networking Equipment", "820", "91%"),
1573 ("Storage Media", "340", "99%"),
1574 ];
1575 for (i, (cat, wt, pct)) in rows.iter().enumerate() {
1576 tbl.cell(i + 1, 0).unwrap().set_text(cat);
1577 tbl.cell(i + 1, 1).unwrap().set_text(wt);
1578 tbl.cell(i + 1, 2).unwrap().set_text(pct);
1579 }
1580 }
1581
1582 // ── Section 4: Initiatives ──
1583 doc.add_paragraph("").page_break_before(true);
1584 doc.add_paragraph("Q1 2026 Initiatives").style("Heading1");
1585
1586 doc.add_paragraph("Planned Programs").style("Heading2");
1587 doc.add_numbered_list_item(
1588 "Deploy on-site battery storage at SF data center (2 MWh capacity)",
1589 0,
1590 );
1591 doc.add_numbered_list_item("Pilot immersion cooling in Dublin facility", 0);
1592 doc.add_numbered_list_item("Launch employee commute carbon offset program", 0);
1593 doc.add_numbered_list_item("Achieve ISO 14001 certification for Singapore facility", 0);
1594
1595 doc.add_paragraph("Investment Targets").style("Heading2");
1596 doc.add_paragraph("Sustainability CapEx allocation for FY2026:")
1597 .keep_with_next(true);
1598 {
1599 let mut tbl = doc
1600 .add_table(5, 2)
1601 .borders(BorderStyle::Single, 4, "2D5016");
1602 tbl.cell(0, 0).unwrap().set_text("Initiative");
1603 tbl.cell(0, 0).unwrap().shading("2D5016");
1604 tbl.cell(0, 1).unwrap().set_text("Budget");
1605 tbl.cell(0, 1).unwrap().shading("2D5016");
1606 let rows = [
1607 ("Battery Storage", "$1.2M"),
1608 ("Immersion Cooling Pilot", "$800K"),
1609 ("Solar Panel Expansion", "$2.1M"),
1610 ("Carbon Credits", "$500K"),
1611 ];
1612 for (i, (init, budget)) in rows.iter().enumerate() {
1613 tbl.cell(i + 1, 0).unwrap().set_text(init);
1614 tbl.cell(i + 1, 1).unwrap().set_text(budget);
1615 if i % 2 == 0 {
1616 tbl.cell(i + 1, 0).unwrap().shading("FFFAF0");
1617 tbl.cell(i + 1, 1).unwrap().shading("FFFAF0");
1618 }
1619 }
1620 }
1621
1622 // Image: sustainability roadmap
1623 let roadmap_img = create_sample_png(500, 100, [40, 80, 30]);
1624 doc.add_paragraph("");
1625 doc.add_picture(
1626 &roadmap_img,
1627 "roadmap.png",
1628 Length::inches(6.0),
1629 Length::inches(1.2),
1630 )
1631 .alignment(Alignment::Center);
1632 {
1633 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1634 p.add_run("Figure 2: 2026 Sustainability Roadmap")
1635 .italic(true)
1636 .size(9.0)
1637 .color("666666");
1638 }
1639
1640 doc.add_paragraph("");
1641 doc.add_paragraph(
1642 "For questions about this report, contact Walter White at sustainability@tensorbee.com.",
1643 )
1644 .shading("FFFAF0")
1645 .border_all(BorderStyle::Single, 2, "2D5016");
1646
1647 doc
1648}
1649
1650// =============================================================================
1651// 6. LETTER — Slate blue + silver scheme
1652// =============================================================================
1653fn generate_letter(_samples_dir: &Path) -> Document {
1654 let mut doc = Document::new();
1655
1656 // Colors: Slate #4A5568, Blue accent #3182CE
1657 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1658 doc.set_margins(
1659 Length::inches(1.25),
1660 Length::inches(1.25),
1661 Length::inches(1.25),
1662 Length::inches(1.25),
1663 );
1664 doc.set_title("Business Letter");
1665 doc.set_author("Walter White");
1666
1667 // Banner header using raw XML
1668 let logo_img = create_logo_png(220, 48);
1669 let banner = build_header_banner_xml(
1670 "rId1",
1671 &BannerOpts {
1672 bg_color: "4A5568",
1673 banner_width: 7772400,
1674 banner_height: 731520, // ~0.8"
1675 logo_width: 2011680,
1676 logo_height: 438912,
1677 logo_x_offset: 295125,
1678 logo_y_offset: 146304,
1679 },
1680 );
1681 doc.set_raw_header_with_images(
1682 banner,
1683 &[("rId1", &logo_img, "logo.png")],
1684 rdocx_oxml::header_footer::HdrFtrType::Default,
1685 );
1686 doc.set_margins(
1687 Length::twips(2000),
1688 Length::twips(1800),
1689 Length::twips(1440),
1690 Length::twips(1800),
1691 );
1692 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
1693
1694 // Footer with contact
1695 doc.set_footer(
1696 "Tensorbee | 123 Innovation Drive, Suite 400 | San Francisco, CA 94105 | tensorbee.com",
1697 );
1698
1699 // ── Sender Address ──
1700 doc.add_paragraph("Walter White");
1701 doc.add_paragraph("Chief Executive Officer");
1702 doc.add_paragraph("Tensorbee");
1703 doc.add_paragraph("123 Innovation Drive, Suite 400");
1704 doc.add_paragraph("San Francisco, CA 94105");
1705 doc.add_paragraph("");
1706 doc.add_paragraph("February 22, 2026");
1707
1708 doc.add_paragraph("");
1709
1710 // ── Recipient ──
1711 doc.add_paragraph("Ms. Sarah Chen");
1712 doc.add_paragraph("VP of Engineering");
1713 doc.add_paragraph("Meridian Dynamics LLC");
1714 doc.add_paragraph("456 Enterprise Boulevard");
1715 doc.add_paragraph("Austin, TX 78701");
1716
1717 doc.add_paragraph("");
1718
1719 // ── Subject ──
1720 {
1721 let mut p = doc.add_paragraph("");
1722 p.add_run("Re: Strategic Technology Partnership — Phase 2 Expansion")
1723 .bold(true);
1724 }
1725
1726 doc.add_paragraph("");
1727
1728 // ── Body ──
1729 doc.add_paragraph("Dear Ms. Chen,");
1730 doc.add_paragraph("");
1731
1732 doc.add_paragraph(
1733 "I am writing to express Tensorbee's enthusiasm for expanding our strategic technology \
1734 partnership with Meridian Dynamics. Following the successful completion of Phase 1, \
1735 which delivered a 40% improvement in your ML inference pipeline throughput, we believe \
1736 the foundation is firmly established for an ambitious Phase 2 engagement.",
1737 )
1738 .first_line_indent(Length::inches(0.5));
1739
1740 doc.add_paragraph(
1741 "During our recent executive review, your team highlighted three priority areas for \
1742 the next phase of collaboration:",
1743 )
1744 .first_line_indent(Length::inches(0.5));
1745
1746 doc.add_numbered_list_item(
1747 "Real-time anomaly detection for your financial transaction monitoring system, \
1748 targeting sub-100ms latency at 50,000 transactions per second.",
1749 0,
1750 );
1751 doc.add_numbered_list_item(
1752 "Federated learning infrastructure to enable model training across your distributed \
1753 data centers without centralizing sensitive financial data.",
1754 0,
1755 );
1756 doc.add_numbered_list_item(
1757 "MLOps automation to reduce your model deployment cycle from the current 5 days \
1758 to under 4 hours.",
1759 0,
1760 );
1761
1762 doc.add_paragraph(
1763 "Our engineering team has prepared a detailed technical proposal addressing each of \
1764 these areas. We have allocated a dedicated team of eight senior engineers, led by \
1765 our CTO, to ensure continuity with the Phase 1 team your organization has already \
1766 built a productive working relationship with.",
1767 )
1768 .first_line_indent(Length::inches(0.5));
1769
1770 doc.add_paragraph(
1771 "I would welcome the opportunity to present our Phase 2 proposal in person. My \
1772 assistant will reach out to coordinate a meeting at your convenience during the \
1773 first week of March.",
1774 )
1775 .first_line_indent(Length::inches(0.5));
1776
1777 doc.add_paragraph("");
1778
1779 doc.add_paragraph("Warm regards,");
1780 doc.add_paragraph("");
1781 doc.add_paragraph("");
1782
1783 // Signature line
1784 doc.add_paragraph("")
1785 .border_bottom(BorderStyle::Single, 4, "4A5568");
1786 {
1787 let mut p = doc.add_paragraph("");
1788 p.add_run("Walter White").bold(true).size(12.0);
1789 }
1790 {
1791 let mut p = doc.add_paragraph("");
1792 p.add_run("Chief Executive Officer, Tensorbee")
1793 .italic(true)
1794 .size(10.0)
1795 .color("666666");
1796 }
1797 {
1798 let mut p = doc.add_paragraph("");
1799 p.add_run("walter@tensorbee.com | +1 (415) 555-0199")
1800 .size(10.0)
1801 .color("888888");
1802 }
1803
1804 doc
1805}Set a footer from raw XML bytes with associated images.
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
34fn generate_feature_showcase(path: &Path) {
35 let mut doc = Document::new();
36
37 // =========================================================================
38 // PAGE SETUP & METADATA
39 // =========================================================================
40 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
41 doc.set_margins(
42 Length::inches(1.0), // top
43 Length::inches(1.0), // right
44 Length::inches(1.0), // bottom
45 Length::inches(1.0), // left
46 );
47 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
48 doc.set_gutter(Length::twips(0));
49
50 doc.set_title("rdocx Feature Showcase");
51 doc.set_author("rdocx Sample Generator");
52 doc.set_subject("Comprehensive feature demonstration");
53 doc.set_keywords("rdocx, docx, rust, sample");
54
55 // Header & Footer
56 doc.set_header("rdocx Feature Showcase");
57 doc.set_footer("Generated by rdocx — Page");
58
59 // Different first page header
60 doc.set_different_first_page(true);
61 doc.set_first_page_header("rdocx");
62 doc.set_first_page_footer("Feature Showcase — Cover Page");
63
64 // =========================================================================
65 // PAGE 1: COVER PAGE — background image, run formatting
66 // =========================================================================
67 let bg_cover = create_sample_png(612, 792, [30, 60, 120]);
68 doc.add_background_image(&bg_cover, "cover_bg.png");
69
70 doc.add_paragraph(""); // spacer
71 doc.add_paragraph(""); // spacer
72 doc.add_paragraph(""); // spacer
73
74 {
75 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
76 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
77 }
78 {
79 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
80 p.add_run("Feature Showcase")
81 .size(28.0)
82 .color("FFFFFF")
83 .italic(true);
84 }
85
86 doc.add_paragraph(""); // spacer
87
88 {
89 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
90 p.add_run("A comprehensive demonstration of every feature")
91 .size(14.0)
92 .color("CCDDFF");
93 }
94 {
95 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
96 p.add_run("provided by the rdocx Rust crate for DOCX generation.")
97 .size(14.0)
98 .color("CCDDFF");
99 }
100
101 // =========================================================================
102 // PAGE 2: TEXT FORMATTING
103 // =========================================================================
104 doc.add_paragraph("").page_break_before(true);
105
106 doc.add_paragraph("1. Text Formatting").style("Heading1");
107
108 doc.add_paragraph("This section demonstrates paragraph and run-level formatting options.");
109 doc.add_paragraph("");
110
111 // --- Paragraph alignment ---
112 doc.add_paragraph("Paragraph Alignment").style("Heading2");
113
114 doc.add_paragraph("This paragraph is left-aligned (the default).")
115 .alignment(Alignment::Left);
116 doc.add_paragraph("This paragraph is center-aligned.")
117 .alignment(Alignment::Center);
118 doc.add_paragraph("This paragraph is right-aligned.")
119 .alignment(Alignment::Right);
120 doc.add_paragraph(
121 "This paragraph is justified. To demonstrate justified text properly, it needs \
122 to be long enough to span multiple lines so the word spacing adjustment is visible \
123 across the full width of the text area on the page.",
124 )
125 .alignment(Alignment::Justify);
126
127 doc.add_paragraph("");
128
129 // --- Run formatting ---
130 doc.add_paragraph("Run Formatting").style("Heading2");
131
132 {
133 let mut p = doc.add_paragraph("");
134 p.add_run("Bold text").bold(true);
135 p.add_run(" | ");
136 p.add_run("Italic text").italic(true);
137 p.add_run(" | ");
138 p.add_run("Bold + Italic").bold(true).italic(true);
139 }
140 {
141 let mut p = doc.add_paragraph("");
142 p.add_run("Single underline").underline(true);
143 p.add_run(" | ");
144 p.add_run("Strikethrough").strike(true);
145 p.add_run(" | ");
146 p.add_run("Double strikethrough").double_strike(true);
147 }
148 {
149 let mut p = doc.add_paragraph("");
150 p.add_run("Red text").color("FF0000");
151 p.add_run(" | ");
152 p.add_run("Blue text").color("0000FF");
153 p.add_run(" | ");
154 p.add_run("Green text").color("00AA00");
155 p.add_run(" | ");
156 p.add_run("Highlighted").highlight("FFFF00");
157 }
158 {
159 let mut p = doc.add_paragraph("");
160 p.add_run("8pt small").size(8.0);
161 p.add_run(" | ");
162 p.add_run("11pt normal").size(11.0);
163 p.add_run(" | ");
164 p.add_run("16pt large").size(16.0);
165 p.add_run(" | ");
166 p.add_run("24pt extra-large").size(24.0);
167 }
168 {
169 let mut p = doc.add_paragraph("");
170 p.add_run("Arial font").font("Arial");
171 p.add_run(" | ");
172 p.add_run("Times New Roman font").font("Times New Roman");
173 p.add_run(" | ");
174 p.add_run("Courier New font").font("Courier New");
175 }
176 {
177 let mut p = doc.add_paragraph("");
178 p.add_run("Normal");
179 p.add_run(" H").size(11.0);
180 p.add_run("2").subscript();
181 p.add_run("O (subscript)").size(11.0);
182 p.add_run(" | E = mc").size(11.0);
183 p.add_run("2").superscript();
184 p.add_run(" (superscript)").size(11.0);
185 }
186 {
187 let mut p = doc.add_paragraph("");
188 p.add_run("ALL CAPS").all_caps(true);
189 p.add_run(" | ");
190 p.add_run("Small Caps").small_caps(true);
191 p.add_run(" | ");
192 p.add_run("Expanded spacing")
193 .character_spacing(Length::twips(40));
194 }
195
196 doc.add_paragraph("");
197
198 // --- Paragraph formatting ---
199 doc.add_paragraph("Paragraph Formatting").style("Heading2");
200
201 doc.add_paragraph("Paragraph with shading (light green background)")
202 .shading("E2EFDA");
203
204 doc.add_paragraph("Paragraph with bottom border")
205 .border_bottom(BorderStyle::Single, 6, "2E75B6");
206
207 doc.add_paragraph("Paragraph with all borders")
208 .border_all(BorderStyle::Single, 4, "FF0000");
209
210 doc.add_paragraph("Paragraph with 1-inch left indent and hanging indent")
211 .indent_left(Length::inches(1.0))
212 .hanging_indent(Length::inches(0.5));
213
214 doc.add_paragraph("Paragraph with first-line indent of 0.5 inches")
215 .first_line_indent(Length::inches(0.5));
216
217 doc.add_paragraph("Paragraph with extra space before (24pt) and after (12pt)")
218 .space_before(Length::pt(24.0))
219 .space_after(Length::pt(12.0));
220
221 doc.add_paragraph(
222 "Paragraph with double line spacing. This text should have extra vertical \
223 space between lines to demonstrate the line_spacing_multiple setting.",
224 )
225 .line_spacing_multiple(2.0);
226
227 doc.add_paragraph("Paragraph with keep-with-next (won't break from the next paragraph)")
228 .keep_with_next(true);
229 doc.add_paragraph("(This stays with the paragraph above.)");
230
231 // =========================================================================
232 // PAGE 3: LISTS & TAB STOPS
233 // =========================================================================
234 doc.add_paragraph("").page_break_before(true);
235
236 doc.add_paragraph("2. Lists").style("Heading1");
237
238 doc.add_paragraph("Bullet List").style("Heading2");
239
240 doc.add_bullet_list_item("First bullet item", 0);
241 doc.add_bullet_list_item("Second bullet item", 0);
242 doc.add_bullet_list_item("Nested level 1", 1);
243 doc.add_bullet_list_item("Nested level 2", 2);
244 doc.add_bullet_list_item("Back to level 1", 1);
245 doc.add_bullet_list_item("Third bullet item", 0);
246
247 doc.add_paragraph("");
248
249 doc.add_paragraph("Numbered List").style("Heading2");
250
251 doc.add_numbered_list_item("First numbered item", 0);
252 doc.add_numbered_list_item("Second numbered item", 0);
253 doc.add_numbered_list_item("Sub-item A", 1);
254 doc.add_numbered_list_item("Sub-item B", 1);
255 doc.add_numbered_list_item("Third numbered item", 0);
256
257 doc.add_paragraph("");
258
259 // --- Tab stops ---
260 doc.add_paragraph("Tab Stops").style("Heading2");
261
262 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
263 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
264 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
265 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
266 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
267
268 doc.add_paragraph("Item\t........\tPrice")
269 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
270 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
271 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
272
273 doc.add_paragraph("Widget A\t........\t$19.99")
274 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
275 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
276 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
277
278 doc.add_paragraph("Gadget B\t________\t$249.50")
279 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
280 .add_tab_stop_with_leader(
281 TabAlignment::Right,
282 Length::inches(4.0),
283 TabLeader::Underscore,
284 )
285 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
286
287 // =========================================================================
288 // PAGE 4: TABLES
289 // =========================================================================
290 doc.add_paragraph("").page_break_before(true);
291
292 doc.add_paragraph("3. Tables").style("Heading1");
293
294 // --- Basic table with borders ---
295 doc.add_paragraph("Basic Table with Borders")
296 .style("Heading2");
297
298 {
299 let mut tbl = doc.add_table(4, 3);
300 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
301
302 // Header row
303 for col in 0..3 {
304 tbl.cell(0, col).unwrap().shading("2E75B6");
305 }
306 tbl.cell(0, 0).unwrap().set_text("Name");
307 tbl.cell(0, 1).unwrap().set_text("Role");
308 tbl.cell(0, 2).unwrap().set_text("Location");
309
310 tbl.cell(1, 0).unwrap().set_text("Alice Johnson");
311 tbl.cell(1, 1).unwrap().set_text("Engineering Lead");
312 tbl.cell(1, 2).unwrap().set_text("New York");
313
314 tbl.cell(2, 0).unwrap().set_text("Bob Smith");
315 tbl.cell(2, 1).unwrap().set_text("Product Manager");
316 tbl.cell(2, 2).unwrap().set_text("San Francisco");
317
318 tbl.cell(3, 0).unwrap().set_text("Carol Davis");
319 tbl.cell(3, 1).unwrap().set_text("Designer");
320 tbl.cell(3, 2).unwrap().set_text("London");
321 }
322
323 doc.add_paragraph("");
324
325 // --- Table with cell merging ---
326 doc.add_paragraph("Table with Cell Merging & Vertical Alignment")
327 .style("Heading2");
328
329 {
330 let mut tbl = doc.add_table(4, 4);
331 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
332 tbl = tbl.width_pct(100.0);
333
334 // Header spanning all columns
335 tbl.cell(0, 0).unwrap().set_text("Quarterly Revenue Report");
336 tbl.cell(0, 0).unwrap().shading("1F4E79");
337 tbl.cell(0, 0).unwrap().grid_span(4);
338
339 // Sub-header
340 tbl.cell(1, 0).unwrap().set_text("Region");
341 tbl.cell(1, 0).unwrap().shading("D6E4F0");
342 tbl.cell(1, 1).unwrap().set_text("Q1");
343 tbl.cell(1, 1).unwrap().shading("D6E4F0");
344 tbl.cell(1, 2).unwrap().set_text("Q2");
345 tbl.cell(1, 2).unwrap().shading("D6E4F0");
346 tbl.cell(1, 3).unwrap().set_text("Total");
347 tbl.cell(1, 3).unwrap().shading("D6E4F0");
348
349 // Data
350 tbl.cell(2, 0).unwrap().set_text("North America");
351 tbl.cell(2, 1).unwrap().set_text("$2.4M");
352 tbl.cell(2, 2).unwrap().set_text("$2.7M");
353 tbl.cell(2, 3).unwrap().set_text("$5.1M");
354
355 tbl.cell(3, 0).unwrap().set_text("Europe");
356 tbl.cell(3, 1).unwrap().set_text("$1.8M");
357 tbl.cell(3, 2).unwrap().set_text("$2.0M");
358 tbl.cell(3, 3).unwrap().set_text("$3.8M");
359
360 // Vertical alignment on data cells
361 tbl.cell(2, 3)
362 .unwrap()
363 .vertical_alignment(VerticalAlignment::Center);
364 tbl.cell(3, 3)
365 .unwrap()
366 .vertical_alignment(VerticalAlignment::Bottom);
367 }
368
369 doc.add_paragraph("");
370
371 // --- Table with vertical merge ---
372 doc.add_paragraph("Table with Vertical Merge")
373 .style("Heading2");
374
375 {
376 let mut tbl = doc.add_table(4, 3);
377 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
378
379 tbl.cell(0, 0).unwrap().set_text("Category");
380 tbl.cell(0, 0).unwrap().shading("E2EFDA");
381 tbl.cell(0, 1).unwrap().set_text("Item");
382 tbl.cell(0, 1).unwrap().shading("E2EFDA");
383 tbl.cell(0, 2).unwrap().set_text("Price");
384 tbl.cell(0, 2).unwrap().shading("E2EFDA");
385
386 // "Hardware" spans rows 1-2
387 tbl.cell(1, 0).unwrap().set_text("Hardware");
388 tbl.cell(1, 0).unwrap().v_merge_restart();
389 tbl.cell(1, 1).unwrap().set_text("Laptop");
390 tbl.cell(1, 2).unwrap().set_text("$1,200");
391
392 tbl.cell(2, 0).unwrap().v_merge_continue();
393 tbl.cell(2, 1).unwrap().set_text("Monitor");
394 tbl.cell(2, 2).unwrap().set_text("$450");
395
396 // "Software" on row 3
397 tbl.cell(3, 0).unwrap().set_text("Software");
398 tbl.cell(3, 1).unwrap().set_text("IDE License");
399 tbl.cell(3, 2).unwrap().set_text("$200/yr");
400 }
401
402 doc.add_paragraph("");
403
404 // --- Nested table ---
405 doc.add_paragraph("Nested Table").style("Heading2");
406
407 {
408 let mut tbl = doc.add_table(2, 2);
409 tbl = tbl.borders(BorderStyle::Single, 6, "2E75B6");
410
411 tbl.cell(0, 0).unwrap().set_text("Outer Cell (0,0)");
412 tbl.cell(0, 1).unwrap().set_text("Outer Cell (0,1)");
413 tbl.cell(1, 0).unwrap().set_text("Outer Cell (1,0)");
414
415 // Nested table inside cell (1,1)
416 {
417 let mut cell = tbl.cell(1, 1).unwrap();
418 cell.set_text("Contains nested table:");
419 let mut nested = cell.add_table(2, 2);
420 nested = nested.borders(BorderStyle::Single, 2, "FF6600");
421 nested.cell(0, 0).unwrap().set_text("Inner A");
422 nested.cell(0, 1).unwrap().set_text("Inner B");
423 nested.cell(1, 0).unwrap().set_text("Inner C");
424 nested.cell(1, 1).unwrap().set_text("Inner D");
425 }
426 }
427
428 // =========================================================================
429 // PAGE 5: IMAGES
430 // =========================================================================
431 doc.add_paragraph("").page_break_before(true);
432
433 doc.add_paragraph("4. Images").style("Heading1");
434
435 doc.add_paragraph("Inline Image").style("Heading2");
436
437 doc.add_paragraph("Below is an inline image (200x50 pixels, blue gradient):");
438 let inline_img = create_sample_png(200, 50, [0, 80, 200]);
439 doc.add_picture(
440 &inline_img,
441 "inline_chart.png",
442 Length::inches(3.0),
443 Length::inches(0.75),
444 );
445
446 doc.add_paragraph("");
447
448 doc.add_paragraph("Header Image").style("Heading2");
449
450 // Replace the text-only header with an image header
451 let header_img = create_sample_png(400, 40, [40, 40, 40]);
452 doc.set_header_image(
453 &header_img,
454 "header_logo.png",
455 Length::inches(2.0),
456 Length::inches(0.2),
457 );
458
459 doc.add_paragraph(
460 "The document header has been replaced with an inline image. \
461 Check the header area at the top of this page.",
462 );
463
464 doc.add_paragraph("");
465 doc.add_paragraph(
466 "Note: The cover page uses a full-page background image behind the text, \
467 demonstrated on page 1 via add_background_image().",
468 );
469
470 // =========================================================================
471 // PAGE 6: CONTENT MANIPULATION — placeholder replacement, insertion
472 // =========================================================================
473 doc.add_paragraph("").page_break_before(true);
474
475 doc.add_paragraph("5. Content Manipulation")
476 .style("Heading1");
477
478 // --- Placeholder replacement ---
479 doc.add_paragraph("Placeholder Replacement")
480 .style("Heading2");
481
482 doc.add_paragraph(
483 "Before replacement, this document contained {{customer}} and {{date}} placeholders.",
484 );
485
486 {
487 let mut p = doc.add_paragraph("");
488 p.add_run("Customer: ").bold(true);
489 p.add_run("{{customer}}");
490 }
491 {
492 let mut p = doc.add_paragraph("");
493 p.add_run("Date: ").bold(true);
494 p.add_run("{{date}}");
495 }
496 doc.add_paragraph("Reference: {{ref_number}}");
497
498 // Table with placeholders
499 {
500 let mut tbl = doc.add_table(3, 2);
501 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
502 tbl.cell(0, 0).unwrap().set_text("Field");
503 tbl.cell(0, 0).unwrap().shading("D6E4F0");
504 tbl.cell(0, 1).unwrap().set_text("Value");
505 tbl.cell(0, 1).unwrap().shading("D6E4F0");
506 tbl.cell(1, 0).unwrap().set_text("Project");
507 tbl.cell(1, 1).unwrap().set_text("{{project}}");
508 tbl.cell(2, 0).unwrap().set_text("Status");
509 tbl.cell(2, 1).unwrap().set_text("{{status}}");
510 }
511
512 // Perform replacements
513 let mut replacements = HashMap::new();
514 replacements.insert("{{customer}}", "Acme Corporation");
515 replacements.insert("{{date}}", "February 22, 2026");
516 replacements.insert("{{ref_number}}", "REF-2026-001");
517 replacements.insert("{{project}}", "Infrastructure Upgrade");
518 replacements.insert("{{status}}", "In Progress");
519 let replace_count = doc.replace_all(&replacements);
520
521 doc.add_paragraph("");
522 doc.add_paragraph(&format!(
523 "(Replaced {} placeholders above — in body text and table cells)",
524 replace_count
525 ));
526
527 doc.add_paragraph("");
528
529 // --- Content insertion ---
530 doc.add_paragraph("Content Insertion").style("Heading2");
531
532 doc.add_paragraph("Section A: First section of content.");
533 doc.add_paragraph("Section C: Third section of content.");
534
535 // Insert "Section B" between A and C
536 if let Some(idx) = doc.find_content_index("Section C") {
537 doc.insert_paragraph(
538 idx,
539 "Section B: Inserted between A and C using find_content_index().",
540 );
541 }
542
543 doc.add_paragraph("");
544 doc.add_paragraph(
545 "The paragraph above ('Section B') was inserted at a specific position \
546 using find_content_index() + insert_paragraph().",
547 );
548
549 // =========================================================================
550 // PAGE 7: LANDSCAPE — section break, wide table
551 // =========================================================================
552 doc.add_paragraph("").section_break(SectionBreak::NextPage);
553
554 doc.add_paragraph("6. Mixed Page Orientation")
555 .style("Heading1");
556
557 doc.add_paragraph(
558 "This page is in LANDSCAPE orientation. It was created using a section break \
559 followed by section_landscape(). This is useful for wide tables or charts.",
560 );
561
562 doc.add_paragraph("");
563
564 // Wide table for landscape
565 {
566 let mut tbl = doc.add_table(4, 7);
567 tbl = tbl.borders(BorderStyle::Single, 4, "2E75B6");
568
569 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
570 for (col, h) in headers.iter().enumerate() {
571 tbl.cell(0, col).unwrap().set_text(h);
572 tbl.cell(0, col).unwrap().shading("2E75B6");
573 }
574
575 let data = [
576 [
577 "North America",
578 "$1.2M",
579 "$1.3M",
580 "$1.4M",
581 "$1.5M",
582 "$1.6M",
583 "$7.0M",
584 ],
585 [
586 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
587 ],
588 [
589 "Asia Pacific",
590 "$0.5M",
591 "$0.6M",
592 "$0.7M",
593 "$0.7M",
594 "$0.8M",
595 "$3.3M",
596 ],
597 ];
598 for (row_idx, row_data) in data.iter().enumerate() {
599 for (col, val) in row_data.iter().enumerate() {
600 tbl.cell(row_idx + 1, col).unwrap().set_text(val);
601 }
602 }
603 }
604
605 // End landscape, return to portrait
606 doc.add_paragraph("")
607 .section_break(SectionBreak::NextPage)
608 .section_landscape();
609
610 // =========================================================================
611 // PAGE 8: BACK TO PORTRAIT — styles, final notes
612 // =========================================================================
613 doc.add_paragraph("7. Custom Styles & Summary")
614 .style("Heading1");
615
616 doc.add_paragraph(
617 "This final page is back in portrait orientation after a section break. \
618 The document has demonstrated:",
619 );
620
621 doc.add_bullet_list_item(
622 "Page setup: size, margins, header/footer distance, gutter",
623 0,
624 );
625 doc.add_bullet_list_item("Document metadata: title, author, subject, keywords", 0);
626 doc.add_bullet_list_item("Headers and footers: text, images, different first page", 0);
627 doc.add_bullet_list_item("Background images: full-page behind text", 0);
628 doc.add_bullet_list_item(
629 "Text formatting: bold, italic, underline, strike, color, size, font",
630 0,
631 );
632 doc.add_bullet_list_item(
633 "Advanced run formatting: superscript, subscript, caps, spacing",
634 0,
635 );
636 doc.add_bullet_list_item(
637 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
638 0,
639 );
640 doc.add_bullet_list_item("Bullet and numbered lists with nesting levels", 0);
641 doc.add_bullet_list_item("Tab stops with dot/underscore leaders", 0);
642 doc.add_bullet_list_item(
643 "Tables: borders, shading, column spans, row spans, nesting",
644 0,
645 );
646 doc.add_bullet_list_item("Vertical alignment in table cells", 0);
647 doc.add_bullet_list_item("Inline images", 0);
648 doc.add_bullet_list_item("Placeholder replacement in body and table cells", 0);
649 doc.add_bullet_list_item("Content insertion at specific positions", 0);
650 doc.add_bullet_list_item(
651 "Section breaks with mixed portrait/landscape orientation",
652 0,
653 );
654
655 doc.add_paragraph("");
656 doc.add_paragraph("All features above were built entirely from scratch using the rdocx API.")
657 .alignment(Alignment::Center)
658 .shading("E2EFDA")
659 .border_all(BorderStyle::Single, 2, "00AA00");
660
661 doc.save(path).unwrap();
662}86fn generate_feature_showcase(_samples_dir: &Path) -> Document {
87 let mut doc = Document::new();
88
89 // ── Page Setup & Metadata ──
90 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
91 doc.set_margins(
92 Length::inches(1.0),
93 Length::inches(1.0),
94 Length::inches(1.0),
95 Length::inches(1.0),
96 );
97 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
98 doc.set_gutter(Length::twips(0));
99 doc.set_title("rdocx Feature Showcase");
100 doc.set_author("rdocx Sample Generator");
101 doc.set_subject("Comprehensive feature demonstration");
102 doc.set_keywords("rdocx, docx, rust, sample, showcase");
103
104 // Headers & Footers with different first page
105 doc.set_different_first_page(true);
106 doc.set_first_page_header("rdocx");
107 doc.set_first_page_footer("Feature Showcase — Cover Page");
108 doc.set_header("rdocx Feature Showcase");
109 doc.set_footer("Generated by rdocx");
110
111 // ── COVER PAGE ──
112 let bg = create_sample_png(612, 792, [20, 45, 90]);
113 doc.add_background_image(&bg, "cover_bg.png");
114
115 for _ in 0..3 {
116 doc.add_paragraph("");
117 }
118 {
119 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
120 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
121 }
122 {
123 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
124 p.add_run("Complete Feature Showcase")
125 .size(28.0)
126 .color("FFFFFF")
127 .italic(true);
128 }
129 doc.add_paragraph("");
130 {
131 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
132 p.add_run("Every feature of the rdocx Rust library")
133 .size(13.0)
134 .color("B0C4DE");
135 }
136 {
137 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
138 p.add_run("demonstrated in a single document.")
139 .size(13.0)
140 .color("B0C4DE");
141 }
142
143 // ── TABLE OF CONTENTS ──
144 doc.add_paragraph("").page_break_before(true);
145 doc.insert_toc(doc.content_count(), 3);
146
147 // ── SECTION 1: TEXT FORMATTING ──
148 doc.add_paragraph("").page_break_before(true);
149 doc.add_paragraph("1. Text Formatting").style("Heading1");
150
151 // Paragraph Alignment
152 doc.add_paragraph("1.1 Paragraph Alignment")
153 .style("Heading2");
154 doc.add_paragraph("Left-aligned paragraph (default).")
155 .alignment(Alignment::Left);
156 doc.add_paragraph("Center-aligned paragraph.")
157 .alignment(Alignment::Center);
158 doc.add_paragraph("Right-aligned paragraph.")
159 .alignment(Alignment::Right);
160 doc.add_paragraph(
161 "Justified paragraph — this text is long enough to demonstrate how justified alignment \
162 distributes extra space across word gaps so lines fill the full width of the text area.",
163 )
164 .alignment(Alignment::Justify);
165
166 doc.add_paragraph("");
167
168 // Run Formatting
169 doc.add_paragraph("1.2 Run Formatting").style("Heading2");
170 {
171 let mut p = doc.add_paragraph("");
172 p.add_run("Bold").bold(true);
173 p.add_run(" | ");
174 p.add_run("Italic").italic(true);
175 p.add_run(" | ");
176 p.add_run("Bold Italic").bold(true).italic(true);
177 p.add_run(" | ");
178 p.add_run("Underline").underline(true);
179 p.add_run(" | ");
180 p.add_run("Strikethrough").strike(true);
181 p.add_run(" | ");
182 p.add_run("Double Strike").double_strike(true);
183 }
184 {
185 let mut p = doc.add_paragraph("");
186 p.add_run("Red").color("FF0000");
187 p.add_run(" | ");
188 p.add_run("Blue").color("0000FF");
189 p.add_run(" | ");
190 p.add_run("Green").color("00AA00");
191 p.add_run(" | ");
192 p.add_run("Highlighted").highlight("FFFF00");
193 }
194 {
195 let mut p = doc.add_paragraph("");
196 p.add_run("8pt").size(8.0);
197 p.add_run(" | ");
198 p.add_run("11pt").size(11.0);
199 p.add_run(" | ");
200 p.add_run("16pt").size(16.0);
201 p.add_run(" | ");
202 p.add_run("24pt").size(24.0);
203 }
204 {
205 let mut p = doc.add_paragraph("");
206 p.add_run("Arial").font("Arial");
207 p.add_run(" | ");
208 p.add_run("Times New Roman").font("Times New Roman");
209 p.add_run(" | ");
210 p.add_run("Courier New").font("Courier New");
211 }
212 {
213 let mut p = doc.add_paragraph("");
214 p.add_run("H");
215 p.add_run("2").subscript();
216 p.add_run("O (subscript) | E=mc");
217 p.add_run("2").superscript();
218 p.add_run(" (superscript)");
219 }
220 {
221 let mut p = doc.add_paragraph("");
222 p.add_run("ALL CAPS").all_caps(true);
223 p.add_run(" | ");
224 p.add_run("Small Caps").small_caps(true);
225 p.add_run(" | ");
226 p.add_run("Expanded").character_spacing(Length::twips(40));
227 p.add_run(" | ");
228 p.add_run("Hidden text (not visible)").hidden(true);
229 }
230
231 doc.add_paragraph("");
232
233 // Underline Styles
234 doc.add_paragraph("1.3 Underline Styles").style("Heading2");
235 {
236 let mut p = doc.add_paragraph("");
237 p.add_run("Single ")
238 .underline_style(rdocx::UnderlineStyle::Single);
239 p.add_run("Double ")
240 .underline_style(rdocx::UnderlineStyle::Double);
241 p.add_run("Thick ")
242 .underline_style(rdocx::UnderlineStyle::Thick);
243 p.add_run("Dotted ")
244 .underline_style(rdocx::UnderlineStyle::Dotted);
245 p.add_run("Dash ")
246 .underline_style(rdocx::UnderlineStyle::Dash);
247 p.add_run("Wave ")
248 .underline_style(rdocx::UnderlineStyle::Wave);
249 }
250
251 // ── SECTION 2: PARAGRAPH FORMATTING ──
252 doc.add_paragraph("").page_break_before(true);
253 doc.add_paragraph("2. Paragraph Formatting")
254 .style("Heading1");
255
256 doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
257 doc.add_paragraph("Paragraph with light green shading")
258 .shading("E2EFDA");
259 doc.add_paragraph("Paragraph with bottom border")
260 .border_bottom(BorderStyle::Single, 6, "2E75B6");
261 doc.add_paragraph("Paragraph with all borders (red)")
262 .border_all(BorderStyle::Single, 4, "FF0000");
263
264 doc.add_paragraph("2.2 Indentation").style("Heading2");
265 doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
266 .indent_left(Length::inches(1.0))
267 .hanging_indent(Length::inches(0.5));
268 doc.add_paragraph("0.5-inch first-line indent on this paragraph")
269 .first_line_indent(Length::inches(0.5));
270 doc.add_paragraph("Both left and right indent (0.75 inches each)")
271 .indent_left(Length::inches(0.75))
272 .indent_right(Length::inches(0.75));
273
274 doc.add_paragraph("2.3 Spacing & Line Height")
275 .style("Heading2");
276 doc.add_paragraph("Extra space before (24pt) and after (12pt)")
277 .space_before(Length::pt(24.0))
278 .space_after(Length::pt(12.0));
279 doc.add_paragraph(
280 "Double line spacing paragraph. This text should have extra vertical space between \
281 lines to demonstrate line_spacing_multiple(2.0).",
282 )
283 .line_spacing_multiple(2.0);
284 doc.add_paragraph("Exact 20pt line spacing (fixed height).")
285 .line_spacing(20.0);
286
287 doc.add_paragraph("2.4 Pagination Controls")
288 .style("Heading2");
289 doc.add_paragraph("keep_with_next — this paragraph stays with the next")
290 .keep_with_next(true);
291 doc.add_paragraph("(This paragraph was kept with the one above.)");
292 doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
293 .keep_together(true);
294 doc.add_paragraph("widow_control — prevents widow/orphan lines")
295 .widow_control(true);
296
297 // ── SECTION 3: LISTS ──
298 doc.add_paragraph("").page_break_before(true);
299 doc.add_paragraph("3. Lists").style("Heading1");
300
301 doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
302 doc.add_bullet_list_item("First item", 0);
303 doc.add_bullet_list_item("Second item", 0);
304 doc.add_bullet_list_item("Nested level 1", 1);
305 doc.add_bullet_list_item("Nested level 2", 2);
306 doc.add_bullet_list_item("Back to level 1", 1);
307 doc.add_bullet_list_item("Third item", 0);
308
309 doc.add_paragraph("");
310
311 doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
312 doc.add_numbered_list_item("First numbered item", 0);
313 doc.add_numbered_list_item("Second numbered item", 0);
314 doc.add_numbered_list_item("Sub-item A", 1);
315 doc.add_numbered_list_item("Sub-item B", 1);
316 doc.add_numbered_list_item("Third numbered item", 0);
317
318 // ── SECTION 4: TAB STOPS ──
319 doc.add_paragraph("");
320 doc.add_paragraph("4. Tab Stops").style("Heading1");
321
322 doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
323 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
324 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
325 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
326 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
327 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
328
329 doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
330 doc.add_paragraph("Item\t\tPrice")
331 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
332 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
333 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
334 doc.add_paragraph("Widget\t\t$19.99")
335 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
336 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
337 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
338 doc.add_paragraph("Gadget\t\t$249.50")
339 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
340 .add_tab_stop_with_leader(
341 TabAlignment::Right,
342 Length::inches(4.0),
343 TabLeader::Underscore,
344 )
345 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
346
347 // ── SECTION 5: TABLES ──
348 doc.add_paragraph("").page_break_before(true);
349 doc.add_paragraph("5. Tables").style("Heading1");
350
351 doc.add_paragraph("5.1 Basic Table").style("Heading2");
352 {
353 let mut tbl = doc
354 .add_table(4, 3)
355 .borders(BorderStyle::Single, 4, "000000");
356 for col in 0..3 {
357 tbl.cell(0, col).unwrap().shading("2E75B6");
358 }
359 tbl.cell(0, 0).unwrap().set_text("Name");
360 tbl.cell(0, 1).unwrap().set_text("Role");
361 tbl.cell(0, 2).unwrap().set_text("Location");
362 tbl.cell(1, 0).unwrap().set_text("Walter White");
363 tbl.cell(1, 1).unwrap().set_text("CEO");
364 tbl.cell(1, 2).unwrap().set_text("Albuquerque");
365 tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
366 tbl.cell(2, 1).unwrap().set_text("CTO");
367 tbl.cell(2, 2).unwrap().set_text("Remote");
368 tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
369 tbl.cell(3, 1).unwrap().set_text("Security");
370 tbl.cell(3, 2).unwrap().set_text("Washington");
371 }
372
373 doc.add_paragraph("");
374
375 doc.add_paragraph("5.2 Column Span & Cell Shading")
376 .style("Heading2");
377 {
378 let mut tbl = doc
379 .add_table(3, 4)
380 .borders(BorderStyle::Single, 4, "000000")
381 .width_pct(100.0);
382 tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
383 tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
384 tbl.cell(1, 0).unwrap().set_text("Region");
385 tbl.cell(1, 0).unwrap().shading("D6E4F0");
386 tbl.cell(1, 1).unwrap().set_text("Q1");
387 tbl.cell(1, 1).unwrap().shading("D6E4F0");
388 tbl.cell(1, 2).unwrap().set_text("Q2");
389 tbl.cell(1, 2).unwrap().shading("D6E4F0");
390 tbl.cell(1, 3).unwrap().set_text("Total");
391 tbl.cell(1, 3).unwrap().shading("D6E4F0");
392 tbl.cell(2, 0).unwrap().set_text("Americas");
393 tbl.cell(2, 1).unwrap().set_text("$2.4M");
394 tbl.cell(2, 2).unwrap().set_text("$2.7M");
395 tbl.cell(2, 3).unwrap().set_text("$5.1M");
396 }
397
398 doc.add_paragraph("");
399
400 doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
401 {
402 let mut tbl = doc
403 .add_table(4, 3)
404 .borders(BorderStyle::Single, 4, "000000");
405 tbl.cell(0, 0).unwrap().set_text("Category");
406 tbl.cell(0, 0).unwrap().shading("E2EFDA");
407 tbl.cell(0, 1).unwrap().set_text("Item");
408 tbl.cell(0, 1).unwrap().shading("E2EFDA");
409 tbl.cell(0, 2).unwrap().set_text("Price");
410 tbl.cell(0, 2).unwrap().shading("E2EFDA");
411 tbl.cell(1, 0).unwrap().set_text("Hardware");
412 tbl.cell(1, 0).unwrap().v_merge_restart();
413 tbl.cell(1, 1).unwrap().set_text("Laptop");
414 tbl.cell(1, 2).unwrap().set_text("$1,200");
415 tbl.cell(2, 0).unwrap().v_merge_continue();
416 tbl.cell(2, 1).unwrap().set_text("Monitor");
417 tbl.cell(2, 2).unwrap().set_text("$450");
418 tbl.cell(3, 0).unwrap().set_text("Software");
419 tbl.cell(3, 1).unwrap().set_text("IDE License");
420 tbl.cell(3, 2).unwrap().set_text("$200/yr");
421 }
422
423 doc.add_paragraph("");
424
425 doc.add_paragraph("5.4 Nested Table").style("Heading2");
426 {
427 let mut tbl = doc
428 .add_table(2, 2)
429 .borders(BorderStyle::Single, 6, "2E75B6");
430 tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
431 tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
432 tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
433 {
434 let mut cell = tbl.cell(1, 1).unwrap();
435 cell.set_text("Nested table below:");
436 let mut nested = cell
437 .add_table(2, 2)
438 .borders(BorderStyle::Single, 2, "FF6600");
439 nested.cell(0, 0).unwrap().set_text("A");
440 nested.cell(0, 1).unwrap().set_text("B");
441 nested.cell(1, 0).unwrap().set_text("C");
442 nested.cell(1, 1).unwrap().set_text("D");
443 }
444 }
445
446 doc.add_paragraph("");
447
448 doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
449 .style("Heading2");
450 {
451 let mut tbl = doc
452 .add_table(2, 3)
453 .borders(BorderStyle::Single, 4, "666666");
454 tbl.row(0).unwrap().height(Length::pt(50.0)).header();
455 tbl.cell(0, 0).unwrap().set_text("Top");
456 tbl.cell(0, 0)
457 .unwrap()
458 .vertical_alignment(VerticalAlignment::Top)
459 .shading("FFF2CC");
460 tbl.cell(0, 1).unwrap().set_text("Center");
461 tbl.cell(0, 1)
462 .unwrap()
463 .vertical_alignment(VerticalAlignment::Center)
464 .shading("D9E2F3");
465 tbl.cell(0, 2).unwrap().set_text("Bottom");
466 tbl.cell(0, 2)
467 .unwrap()
468 .vertical_alignment(VerticalAlignment::Bottom)
469 .shading("E2EFDA");
470 tbl.row(1).unwrap().cant_split();
471 tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
472 tbl.cell(1, 0).unwrap().no_wrap();
473 tbl.cell(1, 1).unwrap().set_text("Fixed width");
474 tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
475 tbl.cell(1, 2).unwrap().set_text("Normal");
476 }
477
478 // ── SECTION 6: IMAGES ──
479 doc.add_paragraph("").page_break_before(true);
480 doc.add_paragraph("6. Images").style("Heading1");
481
482 doc.add_paragraph("6.1 Inline Image").style("Heading2");
483 doc.add_paragraph("A blue gradient image below:");
484 let img = create_sample_png(200, 50, [0, 80, 200]);
485 doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
486
487 doc.add_paragraph("");
488 doc.add_paragraph("6.2 Header Image").style("Heading2");
489 let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
490 doc.set_header_image(
491 &hdr_img,
492 "header_logo.png",
493 Length::inches(2.0),
494 Length::inches(0.2),
495 );
496 doc.add_paragraph("The header now contains an inline image (check the top of this page).");
497
498 doc.add_paragraph("");
499 doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
500
501 // ── SECTION 7: CONTENT MANIPULATION ──
502 doc.add_paragraph("").page_break_before(true);
503 doc.add_paragraph("7. Content Manipulation")
504 .style("Heading1");
505
506 doc.add_paragraph("7.1 Placeholder Replacement")
507 .style("Heading2");
508 doc.add_paragraph("Customer: {{customer}}");
509 doc.add_paragraph("Date: {{date}}");
510 doc.add_paragraph("Reference: {{ref_number}}");
511 {
512 let mut tbl = doc
513 .add_table(2, 2)
514 .borders(BorderStyle::Single, 4, "000000");
515 tbl.cell(0, 0).unwrap().set_text("Project");
516 tbl.cell(0, 0).unwrap().shading("D6E4F0");
517 tbl.cell(0, 1).unwrap().set_text("{{project}}");
518 tbl.cell(1, 0).unwrap().set_text("Status");
519 tbl.cell(1, 0).unwrap().shading("D6E4F0");
520 tbl.cell(1, 1).unwrap().set_text("{{status}}");
521 }
522
523 let mut replacements = HashMap::new();
524 replacements.insert("{{customer}}", "Tensorbee Inc.");
525 replacements.insert("{{date}}", "February 22, 2026");
526 replacements.insert("{{ref_number}}", "TB-2026-001");
527 replacements.insert("{{project}}", "Infrastructure Upgrade");
528 replacements.insert("{{status}}", "In Progress");
529 let n = doc.replace_all(&replacements);
530 doc.add_paragraph(&format!("({n} placeholders replaced above)"));
531
532 doc.add_paragraph("");
533
534 doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
535 doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
536 let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
537 doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
538
539 doc.add_paragraph("");
540
541 doc.add_paragraph("7.3 Content Insertion").style("Heading2");
542 doc.add_paragraph("Section A: First content.");
543 doc.add_paragraph("Section C: Third content.");
544 if let Some(idx) = doc.find_content_index("Section C") {
545 doc.insert_paragraph(
546 idx,
547 "Section B: Inserted between A and C via find_content_index().",
548 );
549 }
550
551 // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
552 doc.add_paragraph("").section_break(SectionBreak::NextPage);
553 doc.add_paragraph("8. Section Breaks & Orientation")
554 .style("Heading1");
555 doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
556
557 {
558 let mut tbl = doc
559 .add_table(3, 7)
560 .borders(BorderStyle::Single, 4, "2E75B6");
561 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
562 for (c, h) in headers.iter().enumerate() {
563 tbl.cell(0, c).unwrap().set_text(h);
564 tbl.cell(0, c).unwrap().shading("2E75B6");
565 }
566 let data = [
567 [
568 "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
569 ],
570 [
571 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
572 ],
573 ];
574 for (r, row) in data.iter().enumerate() {
575 for (c, v) in row.iter().enumerate() {
576 tbl.cell(r + 1, c).unwrap().set_text(v);
577 }
578 }
579 }
580
581 doc.add_paragraph("")
582 .section_break(SectionBreak::NextPage)
583 .section_landscape();
584
585 // ── SECTION 9: CUSTOM STYLES ──
586 doc.add_paragraph("9. Custom Styles").style("Heading1");
587 doc.add_style(
588 StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
589 .based_on("Normal")
590 .paragraph_properties({
591 let mut ppr = rdocx_oxml::properties::CT_PPr::default();
592 ppr.shading = Some(rdocx_oxml::properties::CT_Shd {
593 val: "clear".to_string(),
594 color: None,
595 fill: Some("FFF2CC".to_string()),
596 });
597 ppr
598 })
599 .run_properties({
600 let mut rpr = rdocx_oxml::properties::CT_RPr::default();
601 rpr.bold = Some(true);
602 rpr.color = Some("C45911".to_string());
603 rpr
604 }),
605 );
606 doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
607 .style("CustomHighlight");
608
609 doc.add_paragraph("");
610
611 // ── SECTION 10: DOCUMENT INTELLIGENCE ──
612 doc.add_paragraph("10. Document Intelligence API")
613 .style("Heading1");
614
615 let wc = doc.word_count();
616 let hc = doc.headings().len();
617 let ic = doc.images().len();
618 let lc = doc.links().len();
619 doc.add_paragraph(&format!("Word count: {wc}"));
620 doc.add_paragraph(&format!("Heading count: {hc}"));
621 doc.add_paragraph(&format!("Image count: {ic}"));
622 doc.add_paragraph(&format!("Link count: {lc}"));
623
624 let outline = doc.document_outline();
625 doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
626
627 let issues = doc.audit_accessibility();
628 doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
629 for issue in &issues {
630 doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
631 }
632
633 // ── SECTION 11: DOCUMENT MERGING ──
634 doc.add_paragraph("");
635 doc.add_paragraph("11. Document Merging").style("Heading1");
636 {
637 let mut other = Document::new();
638 other.add_paragraph("This paragraph was merged from another document using append().");
639 doc.append(&other);
640 }
641
642 doc.add_paragraph("");
643
644 // ── SUMMARY ──
645 doc.add_paragraph("Summary of Demonstrated Features")
646 .style("Heading1");
647 let features = [
648 "Page setup: size, margins, header/footer distance, gutter",
649 "Document metadata: title, author, subject, keywords",
650 "Headers and footers: text, images, different first page",
651 "Background images (full-page behind text)",
652 "Table of Contents generation with bookmarks",
653 "Text formatting: bold, italic, underline styles, strike, color, size, font",
654 "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
655 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
656 "Pagination controls: keep-with-next, keep-together, widow control",
657 "Bullet and numbered lists with nesting",
658 "Tab stops with dot/underscore/hyphen leaders",
659 "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
660 "Row properties: header rows, exact height, cant-split",
661 "Cell properties: width, no-wrap, vertical alignment",
662 "Inline images and header images",
663 "Placeholder replacement (single and batch)",
664 "Regex-based find and replace",
665 "Content insertion at specific positions",
666 "Section breaks with mixed portrait/landscape",
667 "Custom paragraph and character styles",
668 "Document intelligence: word count, headings, outline, images, links",
669 "Accessibility audit",
670 "Document merging (append)",
671 "Export: DOCX, PDF, HTML, Markdown",
672 ];
673 for f in &features {
674 doc.add_bullet_list_item(f, 0);
675 }
676 doc.add_paragraph("");
677 doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
678 .alignment(Alignment::Center)
679 .shading("E2EFDA")
680 .border_all(BorderStyle::Single, 2, "00AA00");
681
682 doc
683}
684
685// =============================================================================
686// 2. PROPOSAL DOCUMENT — Deep navy + gold scheme
687// =============================================================================
688fn generate_proposal(_samples_dir: &Path) -> Document {
689 let mut doc = Document::new();
690
691 // Colors: Navy #1B2A4A, Gold #C5922E, Light #F4F1EB
692 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
693 doc.set_margins(
694 Length::inches(1.0),
695 Length::inches(1.0),
696 Length::inches(1.0),
697 Length::inches(1.0),
698 );
699 doc.set_title("AI Platform Modernization Proposal");
700 doc.set_author("Walter White");
701 doc.set_subject("Technology Proposal");
702 doc.set_keywords("proposal, AI, modernization, Tensorbee");
703
704 // Banner header
705 let logo_img = create_logo_png(220, 48);
706 let banner = build_header_banner_xml(
707 "rId1",
708 &BannerOpts {
709 bg_color: "1B2A4A",
710 banner_width: 7772400,
711 banner_height: 914400,
712 logo_width: 2011680,
713 logo_height: 438912,
714 logo_x_offset: 295125,
715 logo_y_offset: 237744,
716 },
717 );
718 doc.set_raw_header_with_images(
719 banner,
720 &[("rId1", &logo_img, "logo.png")],
721 rdocx_oxml::header_footer::HdrFtrType::Default,
722 );
723 doc.set_different_first_page(true);
724 let cover_banner = build_header_banner_xml(
725 "rId1",
726 &BannerOpts {
727 bg_color: "C5922E",
728 banner_width: 7772400,
729 banner_height: 914400,
730 logo_width: 2011680,
731 logo_height: 438912,
732 logo_x_offset: 295125,
733 logo_y_offset: 237744,
734 },
735 );
736 doc.set_raw_header_with_images(
737 cover_banner,
738 &[("rId1", &logo_img, "logo.png")],
739 rdocx_oxml::header_footer::HdrFtrType::First,
740 );
741 doc.set_margins(
742 Length::twips(2292),
743 Length::twips(1440),
744 Length::twips(1440),
745 Length::twips(1440),
746 );
747 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
748 doc.set_footer("Tensorbee — Confidential");
749
750 // Custom styles
751 doc.add_style(
752 StyleBuilder::paragraph("ProposalTitle", "Proposal Title")
753 .based_on("Normal")
754 .run_properties({
755 let mut rpr = rdocx_oxml::properties::CT_RPr::default();
756 rpr.bold = Some(true);
757 rpr.font_ascii = Some("Georgia".to_string());
758 rpr.font_hansi = Some("Georgia".to_string());
759 rpr.sz = Some(rdocx_oxml::HalfPoint(56)); // half-points → 28pt
760 rpr.color = Some("1B2A4A".to_string());
761 rpr
762 }),
763 );
764
765 // ── Cover Page ──
766 for _ in 0..4 {
767 doc.add_paragraph("");
768 }
769 doc.add_paragraph("AI Platform Modernization")
770 .style("ProposalTitle")
771 .alignment(Alignment::Center);
772 doc.add_paragraph("")
773 .alignment(Alignment::Center)
774 .border_bottom(BorderStyle::Single, 8, "C5922E");
775 {
776 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
777 p.add_run("Prepared for Global Financial Corp.")
778 .size(14.0)
779 .color("666666")
780 .italic(true);
781 }
782 {
783 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
784 p.add_run("Prepared by: Walter White, CEO — Tensorbee")
785 .size(12.0)
786 .color("888888");
787 }
788 {
789 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
790 p.add_run("February 22, 2026").size(12.0).color("888888");
791 }
792
793 // ── TOC ──
794 doc.add_paragraph("").page_break_before(true);
795 doc.add_paragraph("Table of Contents").style("Heading1");
796 // We'll add a manual-style TOC since the insert_toc goes at a specific position
797 doc.insert_toc(doc.content_count(), 2);
798
799 // ── Executive Summary ──
800 doc.add_paragraph("").page_break_before(true);
801 doc.add_paragraph("Executive Summary").style("Heading1");
802 doc.add_paragraph(
803 "Tensorbee proposes a comprehensive modernization of Global Financial Corp's AI platform, \
804 transitioning from legacy batch-processing systems to a real-time inference architecture. \
805 This transformation will reduce model deployment time from weeks to hours, improve \
806 prediction accuracy by an estimated 23%, and deliver $4.2M in annual operational savings.",
807 )
808 .first_line_indent(Length::inches(0.3));
809 doc.add_paragraph(
810 "The project spans three phases over 18 months, with a total investment of $2.8M. \
811 Tensorbee brings deep expertise in ML infrastructure, having successfully completed \
812 similar transformations for three Fortune 500 financial institutions.",
813 )
814 .first_line_indent(Length::inches(0.3));
815
816 // Key metrics table
817 doc.add_paragraph("");
818 {
819 let mut tbl = doc
820 .add_table(5, 2)
821 .borders(BorderStyle::Single, 4, "1B2A4A")
822 .width_pct(80.0);
823 tbl.cell(0, 0).unwrap().set_text("Key Metric");
824 tbl.cell(0, 0).unwrap().shading("1B2A4A");
825 tbl.cell(0, 1).unwrap().set_text("Value");
826 tbl.cell(0, 1).unwrap().shading("1B2A4A");
827 let rows = [
828 ("Total Investment", "$2.8M"),
829 ("Annual Savings", "$4.2M"),
830 ("ROI (Year 1)", "150%"),
831 ("Timeline", "18 months"),
832 ];
833 for (i, (k, v)) in rows.iter().enumerate() {
834 tbl.cell(i + 1, 0).unwrap().set_text(k);
835 if i % 2 == 0 {
836 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
837 }
838 tbl.cell(i + 1, 1).unwrap().set_text(v);
839 if i % 2 == 0 {
840 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
841 }
842 }
843 }
844
845 // ── Problem Statement ──
846 doc.add_paragraph("").page_break_before(true);
847 doc.add_paragraph("Problem Statement").style("Heading1");
848 doc.add_paragraph(
849 "Global Financial Corp's current AI infrastructure faces three critical challenges:",
850 );
851 doc.add_numbered_list_item("Deployment Latency — New models take 3-4 weeks to deploy to production, compared to the industry benchmark of 2-3 days.", 0);
852 doc.add_numbered_list_item("Scalability Constraints — The batch processing architecture cannot handle real-time inference demands during peak trading hours.", 0);
853 doc.add_numbered_list_item("Technical Debt — Legacy Python 2.7 codebases and manual deployment scripts create reliability risks and slow iteration speed.", 0);
854
855 // ── Proposed Solution ──
856 doc.add_paragraph("").page_break_before(true);
857 doc.add_paragraph("Proposed Solution").style("Heading1");
858
859 doc.add_paragraph("Phase 1: Foundation (Months 1-6)")
860 .style("Heading2");
861 doc.add_bullet_list_item("Deploy Kubernetes-based ML serving infrastructure", 0);
862 doc.add_bullet_list_item("Implement CI/CD pipeline for model deployment", 0);
863 doc.add_bullet_list_item("Migrate top 5 production models to new platform", 0);
864
865 doc.add_paragraph("Phase 2: Expansion (Months 7-12)")
866 .style("Heading2");
867 doc.add_bullet_list_item(
868 "Real-time feature engineering pipeline (Apache Kafka + Flink)",
869 0,
870 );
871 doc.add_bullet_list_item("A/B testing framework for model variants", 0);
872 doc.add_bullet_list_item("Automated model monitoring and drift detection", 0);
873
874 doc.add_paragraph("Phase 3: Optimization (Months 13-18)")
875 .style("Heading2");
876 doc.add_bullet_list_item("GPU inference optimization (TensorRT/ONNX Runtime)", 0);
877 doc.add_bullet_list_item("Multi-region deployment for latency reduction", 0);
878 doc.add_bullet_list_item(
879 "Self-service model deployment portal for data science teams",
880 0,
881 );
882
883 // ── Budget ──
884 doc.add_paragraph("Budget Breakdown").style("Heading1");
885 {
886 let mut tbl = doc
887 .add_table(6, 3)
888 .borders(BorderStyle::Single, 4, "1B2A4A")
889 .width_pct(100.0);
890 tbl.cell(0, 0).unwrap().set_text("Category");
891 tbl.cell(0, 0).unwrap().shading("1B2A4A");
892 tbl.cell(0, 1).unwrap().set_text("Cost");
893 tbl.cell(0, 1).unwrap().shading("1B2A4A");
894 tbl.cell(0, 2).unwrap().set_text("Notes");
895 tbl.cell(0, 2).unwrap().shading("1B2A4A");
896 let items = [
897 (
898 "Infrastructure",
899 "$450,000",
900 "Cloud compute + GPU instances",
901 ),
902 ("Engineering Services", "$1,600,000", "12 FTE x 18 months"),
903 (
904 "Software Licenses",
905 "$350,000",
906 "Kafka, monitoring, CI/CD tools",
907 ),
908 (
909 "Training & Enablement",
910 "$200,000",
911 "Team training, documentation",
912 ),
913 ("Contingency (10%)", "$200,000", "Risk buffer"),
914 ];
915 for (i, (cat, cost, note)) in items.iter().enumerate() {
916 tbl.cell(i + 1, 0).unwrap().set_text(cat);
917 tbl.cell(i + 1, 1).unwrap().set_text(cost);
918 tbl.cell(i + 1, 2).unwrap().set_text(note);
919 if i % 2 == 0 {
920 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
921 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
922 tbl.cell(i + 1, 2).unwrap().shading("F4F1EB");
923 }
924 }
925 }
926
927 // ── Closing ──
928 doc.add_paragraph("");
929 doc.add_paragraph("Next Steps").style("Heading1");
930 doc.add_numbered_list_item(
931 "Schedule technical deep-dive with GFC engineering team (Week 1)",
932 0,
933 );
934 doc.add_numbered_list_item("Finalize scope and sign SOW (Week 2-3)", 0);
935 doc.add_numbered_list_item("Kick off Phase 1 with joint planning session (Week 4)", 0);
936
937 doc.add_paragraph("");
938 {
939 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
940 p.add_run("Walter White")
941 .bold(true)
942 .size(14.0)
943 .color("1B2A4A");
944 }
945 {
946 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
947 p.add_run("CEO, Tensorbee | walter@tensorbee.com")
948 .size(10.0)
949 .color("888888");
950 }
951
952 doc
953}
954
955// =============================================================================
956// 3. QUOTE / BILL OF MATERIALS — Teal + orange scheme
957// =============================================================================
958fn generate_quote(_samples_dir: &Path) -> Document {
959 let mut doc = Document::new();
960
961 // Colors: Teal #008B8B, Dark #1A3C3C, Orange #E07020, Light #F0F8F8
962 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
963 doc.set_margins(
964 Length::inches(0.75),
965 Length::inches(0.75),
966 Length::inches(0.75),
967 Length::inches(0.75),
968 );
969 doc.set_title("Quotation QT-2026-0147");
970 doc.set_author("Walter White");
971 doc.set_keywords("quote, BOM, Tensorbee");
972
973 doc.set_header("Tensorbee — Quotation");
974 doc.set_footer("QT-2026-0147 | Page");
975
976 // ── Header Block ──
977 {
978 let mut p = doc.add_paragraph("").alignment(Alignment::Left);
979 p.add_run("TENSORBEE")
980 .bold(true)
981 .size(28.0)
982 .color("008B8B")
983 .font("Helvetica");
984 }
985 doc.add_paragraph("123 Innovation Drive, Suite 400")
986 .alignment(Alignment::Left);
987 doc.add_paragraph("San Francisco, CA 94105 | +1 (415) 555-0199")
988 .alignment(Alignment::Left);
989 doc.add_paragraph("")
990 .border_bottom(BorderStyle::Single, 8, "008B8B");
991
992 // Quote meta
993 doc.add_paragraph("");
994 {
995 let mut tbl = doc.add_table(4, 4).width_pct(100.0);
996 tbl.cell(0, 0).unwrap().set_text("QUOTATION");
997 tbl.cell(0, 0).unwrap().shading("008B8B").grid_span(2);
998 tbl.cell(0, 2).unwrap().set_text("DATE");
999 tbl.cell(0, 2).unwrap().shading("008B8B");
1000 tbl.cell(0, 3).unwrap().set_text("VALID UNTIL");
1001 tbl.cell(0, 3).unwrap().shading("008B8B");
1002 tbl.cell(1, 0).unwrap().set_text("Quote #:");
1003 tbl.cell(1, 1).unwrap().set_text("QT-2026-0147");
1004 tbl.cell(1, 2).unwrap().set_text("Feb 22, 2026");
1005 tbl.cell(1, 3).unwrap().set_text("Mar 22, 2026");
1006 tbl.cell(2, 0).unwrap().set_text("Prepared by:");
1007 tbl.cell(2, 1).unwrap().set_text("Walter White");
1008 tbl.cell(2, 2).unwrap().set_text("Payment:");
1009 tbl.cell(2, 3).unwrap().set_text("Net 30");
1010 tbl.cell(3, 0).unwrap().set_text("Customer:");
1011 tbl.cell(3, 1).unwrap().set_text("Meridian Dynamics LLC");
1012 tbl.cell(3, 2).unwrap().set_text("Currency:");
1013 tbl.cell(3, 3).unwrap().set_text("USD");
1014 }
1015
1016 doc.add_paragraph("");
1017
1018 // ── Bill of Materials ──
1019 {
1020 let mut p = doc.add_paragraph("");
1021 p.add_run("Bill of Materials")
1022 .bold(true)
1023 .size(16.0)
1024 .color("1A3C3C");
1025 }
1026 doc.add_paragraph("");
1027
1028 {
1029 let mut tbl = doc
1030 .add_table(10, 6)
1031 .borders(BorderStyle::Single, 2, "008B8B")
1032 .width_pct(100.0)
1033 .layout_fixed();
1034 // Headers
1035 let hdrs = [
1036 "#",
1037 "Part Number",
1038 "Description",
1039 "Qty",
1040 "Unit Price",
1041 "Total",
1042 ];
1043 for (c, h) in hdrs.iter().enumerate() {
1044 tbl.cell(0, c).unwrap().set_text(h);
1045 tbl.cell(0, c).unwrap().shading("008B8B");
1046 }
1047
1048 let items: Vec<(&str, &str, &str, &str, &str)> = vec![
1049 (
1050 "TB-GPU-A100",
1051 "NVIDIA A100 80GB GPU",
1052 "4",
1053 "$12,500.00",
1054 "$50,000.00",
1055 ),
1056 (
1057 "TB-SRV-R750",
1058 "Dell R750xa Server Chassis",
1059 "2",
1060 "$8,200.00",
1061 "$16,400.00",
1062 ),
1063 (
1064 "TB-RAM-256G",
1065 "256GB DDR5 ECC Memory Module",
1066 "8",
1067 "$890.00",
1068 "$7,120.00",
1069 ),
1070 (
1071 "TB-SSD-3840",
1072 "3.84TB NVMe U.2 SSD",
1073 "8",
1074 "$1,150.00",
1075 "$9,200.00",
1076 ),
1077 (
1078 "TB-NET-CX7",
1079 "ConnectX-7 200GbE NIC",
1080 "4",
1081 "$1,800.00",
1082 "$7,200.00",
1083 ),
1084 (
1085 "TB-SW-48P",
1086 "48-Port 100GbE Switch",
1087 "1",
1088 "$22,000.00",
1089 "$22,000.00",
1090 ),
1091 (
1092 "TB-CAB-RACK",
1093 "42U Server Rack + PDU",
1094 "1",
1095 "$4,500.00",
1096 "$4,500.00",
1097 ),
1098 (
1099 "TB-SVC-INST",
1100 "Installation & Configuration",
1101 "1",
1102 "$8,500.00",
1103 "$8,500.00",
1104 ),
1105 (
1106 "TB-SVC-SUPP",
1107 "3-Year Premium Support",
1108 "1",
1109 "$15,000.00",
1110 "$15,000.00",
1111 ),
1112 ];
1113
1114 for (i, (pn, desc, qty, unit, total)) in items.iter().enumerate() {
1115 let row = i + 1;
1116 tbl.cell(row, 0).unwrap().set_text(&format!("{}", i + 1));
1117 tbl.cell(row, 1).unwrap().set_text(pn);
1118 tbl.cell(row, 2).unwrap().set_text(desc);
1119 tbl.cell(row, 3).unwrap().set_text(qty);
1120 tbl.cell(row, 4).unwrap().set_text(unit);
1121 tbl.cell(row, 5).unwrap().set_text(total);
1122 if i % 2 == 0 {
1123 for c in 0..6 {
1124 tbl.cell(row, c).unwrap().shading("F0F8F8");
1125 }
1126 }
1127 }
1128 }
1129
1130 doc.add_paragraph("");
1131
1132 // ── Totals ──
1133 {
1134 let mut tbl = doc
1135 .add_table(4, 2)
1136 .borders(BorderStyle::Single, 2, "008B8B")
1137 .width(Length::inches(3.5))
1138 .alignment(Alignment::Right);
1139 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1140 tbl.cell(0, 1).unwrap().set_text("$139,920.00");
1141 tbl.cell(1, 0).unwrap().set_text("Shipping & Handling");
1142 tbl.cell(1, 1).unwrap().set_text("$2,500.00");
1143 tbl.cell(2, 0).unwrap().set_text("Tax (8.625%)");
1144 tbl.cell(2, 1).unwrap().set_text("$12,068.10");
1145 tbl.cell(3, 0).unwrap().set_text("TOTAL");
1146 tbl.cell(3, 0).unwrap().shading("E07020");
1147 tbl.cell(3, 1).unwrap().set_text("$154,488.10");
1148 tbl.cell(3, 1).unwrap().shading("E07020");
1149 }
1150
1151 doc.add_paragraph("");
1152
1153 // ── Terms & Conditions ──
1154 {
1155 let mut p = doc.add_paragraph("");
1156 p.add_run("Terms & Conditions")
1157 .bold(true)
1158 .size(14.0)
1159 .color("1A3C3C");
1160 }
1161 doc.add_numbered_list_item(
1162 "This quotation is valid for 30 calendar days from the date of issue.",
1163 0,
1164 );
1165 doc.add_numbered_list_item(
1166 "All prices are in USD and exclusive of applicable taxes unless stated.",
1167 0,
1168 );
1169 doc.add_numbered_list_item("Standard lead time is 4-6 weeks from PO receipt.", 0);
1170 doc.add_numbered_list_item(
1171 "Payment terms: Net 30 from invoice date. 2% discount for payment within 10 days.",
1172 0,
1173 );
1174 doc.add_numbered_list_item(
1175 "Warranty: 3-year manufacturer warranty on all hardware components.",
1176 0,
1177 );
1178 doc.add_numbered_list_item(
1179 "Returns subject to 15% restocking fee if initiated after 14 days.",
1180 0,
1181 );
1182
1183 doc.add_paragraph("");
1184 doc.add_paragraph("")
1185 .border_bottom(BorderStyle::Single, 4, "008B8B");
1186 doc.add_paragraph("");
1187 {
1188 let mut p = doc.add_paragraph("");
1189 p.add_run("Accepted by: ").bold(true);
1190 p.add_run("___________________________ Date: ___________");
1191 }
1192 {
1193 let mut p = doc.add_paragraph("");
1194 p.add_run("Print Name: ").bold(true);
1195 p.add_run("___________________________ Title: ___________");
1196 }
1197
1198 doc
1199}
1200
1201// =============================================================================
1202// 4. INVOICE — Crimson + charcoal scheme
1203// =============================================================================
1204fn generate_invoice(_samples_dir: &Path) -> Document {
1205 let mut doc = Document::new();
1206
1207 // Colors: Crimson #B22222, Charcoal #333333, Light #FAF0F0
1208 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1209 doc.set_margins(
1210 Length::inches(0.75),
1211 Length::inches(0.75),
1212 Length::inches(0.75),
1213 Length::inches(0.75),
1214 );
1215 doc.set_title("Invoice INV-2026-0392");
1216 doc.set_author("Walter White");
1217 doc.set_keywords("invoice, Tensorbee");
1218
1219 doc.set_footer("Tensorbee — Thank you for your business!");
1220
1221 // ── Company & Invoice Header ──
1222 {
1223 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1224 // Company name left, INVOICE right
1225 {
1226 let mut cell = tbl.cell(0, 0).unwrap();
1227 let mut p = cell.add_paragraph("");
1228 p.add_run("TENSORBEE")
1229 .bold(true)
1230 .size(32.0)
1231 .color("B22222")
1232 .font("Helvetica");
1233 }
1234 {
1235 let mut cell = tbl.cell(0, 1).unwrap();
1236 let mut p = cell.add_paragraph("");
1237 p.add_run("INVOICE").bold(true).size(32.0).color("333333");
1238 }
1239 tbl.cell(1, 0)
1240 .unwrap()
1241 .set_text("123 Innovation Drive, Suite 400");
1242 tbl.cell(1, 1).unwrap().set_text("Invoice #: INV-2026-0392");
1243 tbl.cell(2, 0).unwrap().set_text("San Francisco, CA 94105");
1244 tbl.cell(2, 1).unwrap().set_text("Date: February 22, 2026");
1245 tbl.cell(3, 0).unwrap().set_text("walter@tensorbee.com");
1246 tbl.cell(3, 1).unwrap().set_text("Due Date: March 24, 2026");
1247 }
1248
1249 doc.add_paragraph("")
1250 .border_bottom(BorderStyle::Thick, 8, "B22222");
1251 doc.add_paragraph("");
1252
1253 // ── Bill To / Ship To ──
1254 {
1255 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1256 tbl.cell(0, 0).unwrap().set_text("BILL TO");
1257 tbl.cell(0, 0).unwrap().shading("B22222");
1258 tbl.cell(0, 1).unwrap().set_text("SHIP TO");
1259 tbl.cell(0, 1).unwrap().shading("B22222");
1260 tbl.cell(1, 0).unwrap().set_text("Meridian Dynamics LLC");
1261 tbl.cell(1, 1).unwrap().set_text("Meridian Dynamics LLC");
1262 tbl.cell(2, 0).unwrap().set_text("456 Enterprise Blvd");
1263 tbl.cell(2, 1).unwrap().set_text("Attn: Server Room B");
1264 tbl.cell(3, 0).unwrap().set_text("Austin, TX 78701");
1265 tbl.cell(3, 1)
1266 .unwrap()
1267 .set_text("456 Enterprise Blvd, Austin, TX 78701");
1268 }
1269
1270 doc.add_paragraph("");
1271
1272 // ── Line Items ──
1273 {
1274 let mut tbl = doc
1275 .add_table(8, 5)
1276 .borders(BorderStyle::Single, 2, "B22222")
1277 .width_pct(100.0);
1278 let hdrs = ["Description", "Qty", "Unit Price", "Tax", "Amount"];
1279 for (c, h) in hdrs.iter().enumerate() {
1280 tbl.cell(0, c).unwrap().set_text(h);
1281 tbl.cell(0, c).unwrap().shading("B22222");
1282 }
1283 let items = [
1284 (
1285 "ML Infrastructure Setup — Phase 1",
1286 "1",
1287 "$45,000.00",
1288 "$3,881.25",
1289 "$48,881.25",
1290 ),
1291 (
1292 "Data Pipeline Development (160 hrs)",
1293 "160",
1294 "$225.00",
1295 "$3,105.00",
1296 "$39,105.00",
1297 ),
1298 (
1299 "GPU Cluster Configuration",
1300 "1",
1301 "$12,000.00",
1302 "$1,035.00",
1303 "$13,035.00",
1304 ),
1305 (
1306 "API Gateway Implementation",
1307 "1",
1308 "$18,500.00",
1309 "$1,595.63",
1310 "$20,095.63",
1311 ),
1312 (
1313 "Load Testing & QA (80 hrs)",
1314 "80",
1315 "$195.00",
1316 "$1,345.50",
1317 "$16,945.50",
1318 ),
1319 (
1320 "Documentation & Training",
1321 "1",
1322 "$8,000.00",
1323 "$690.00",
1324 "$8,690.00",
1325 ),
1326 (
1327 "Project Management (3 months)",
1328 "3",
1329 "$6,500.00",
1330 "$1,679.63",
1331 "$21,179.63",
1332 ),
1333 ];
1334 for (i, (desc, qty, unit, tax, amt)) in items.iter().enumerate() {
1335 let r = i + 1;
1336 tbl.cell(r, 0).unwrap().set_text(desc);
1337 tbl.cell(r, 1).unwrap().set_text(qty);
1338 tbl.cell(r, 2).unwrap().set_text(unit);
1339 tbl.cell(r, 3).unwrap().set_text(tax);
1340 tbl.cell(r, 4).unwrap().set_text(amt);
1341 if i % 2 == 0 {
1342 for c in 0..5 {
1343 tbl.cell(r, c).unwrap().shading("FAF0F0");
1344 }
1345 }
1346 }
1347 }
1348
1349 doc.add_paragraph("");
1350
1351 // ── Totals ──
1352 {
1353 let mut tbl = doc
1354 .add_table(4, 2)
1355 .borders(BorderStyle::Single, 2, "B22222")
1356 .width(Length::inches(3.0))
1357 .alignment(Alignment::Right);
1358 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1359 tbl.cell(0, 1).unwrap().set_text("$154,600.00");
1360 tbl.cell(1, 0).unwrap().set_text("Tax (8.625%)");
1361 tbl.cell(1, 1).unwrap().set_text("$13,334.25");
1362 tbl.cell(2, 0).unwrap().set_text("Discount (5%)");
1363 tbl.cell(2, 1).unwrap().set_text("-$7,730.00");
1364 tbl.cell(3, 0).unwrap().set_text("AMOUNT DUE");
1365 tbl.cell(3, 0).unwrap().shading("B22222");
1366 tbl.cell(3, 1).unwrap().set_text("$160,204.25");
1367 tbl.cell(3, 1).unwrap().shading("B22222");
1368 }
1369
1370 doc.add_paragraph("");
1371 doc.add_paragraph("");
1372
1373 // ── Payment Details ──
1374 {
1375 let mut p = doc.add_paragraph("");
1376 p.add_run("Payment Details")
1377 .bold(true)
1378 .size(14.0)
1379 .color("333333");
1380 }
1381 doc.add_paragraph("Bank: Silicon Valley Bank")
1382 .indent_left(Length::inches(0.3));
1383 doc.add_paragraph("Account: Tensorbee Inc. — 0847-2953-1120")
1384 .indent_left(Length::inches(0.3));
1385 doc.add_paragraph("Routing: 121140399")
1386 .indent_left(Length::inches(0.3));
1387 doc.add_paragraph("Swift: SVBKUS6S")
1388 .indent_left(Length::inches(0.3));
1389
1390 doc.add_paragraph("");
1391
1392 doc.add_paragraph("Please include invoice number INV-2026-0392 in the payment reference.")
1393 .shading("FAF0F0")
1394 .border_all(BorderStyle::Single, 2, "B22222");
1395
1396 doc
1397}
1398
1399// =============================================================================
1400// 5. REPORT — Forest green + earth tones, with images & hierarchical sections
1401// =============================================================================
1402fn generate_report(_samples_dir: &Path) -> Document {
1403 let mut doc = Document::new();
1404
1405 // Colors: Forest #2D5016, Sage #6B8E23, Earth #8B7355, Cream #FFFAF0
1406 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1407 doc.set_margins(
1408 Length::inches(1.0),
1409 Length::inches(1.0),
1410 Length::inches(1.0),
1411 Length::inches(1.0),
1412 );
1413 doc.set_title("Q4 2025 Environmental Impact Report");
1414 doc.set_author("Walter White");
1415 doc.set_subject("Quarterly Environmental Report");
1416 doc.set_keywords("environment, sustainability, report, Tensorbee");
1417
1418 doc.set_different_first_page(true);
1419 doc.set_first_page_header("");
1420 doc.set_header("Tensorbee — Q4 2025 Environmental Impact Report");
1421 doc.set_footer("CONFIDENTIAL — Page");
1422
1423 // ── Cover ──
1424 let cover_bg = create_sample_png(612, 792, [20, 50, 15]);
1425 doc.add_background_image(&cover_bg, "report_cover.png");
1426
1427 for _ in 0..5 {
1428 doc.add_paragraph("");
1429 }
1430 {
1431 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1432 p.add_run("Q4 2025")
1433 .bold(true)
1434 .size(48.0)
1435 .color("FFFFFF")
1436 .font("Georgia");
1437 }
1438 {
1439 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1440 p.add_run("Environmental Impact Report")
1441 .size(24.0)
1442 .color("90EE90")
1443 .font("Georgia")
1444 .italic(true);
1445 }
1446 doc.add_paragraph("");
1447 {
1448 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1449 p.add_run("Tensorbee — Sustainability Division")
1450 .size(14.0)
1451 .color("C0C0C0");
1452 }
1453 {
1454 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1455 p.add_run("Prepared by Walter White, Chief Sustainability Officer")
1456 .size(11.0)
1457 .color("AAAAAA");
1458 }
1459
1460 // ── TOC ──
1461 doc.add_paragraph("").page_break_before(true);
1462 doc.insert_toc(doc.content_count(), 3);
1463
1464 // ── Section 1: Executive Overview ──
1465 doc.add_paragraph("").page_break_before(true);
1466 doc.add_paragraph("Executive Overview").style("Heading1");
1467 doc.add_paragraph(
1468 "This report presents Tensorbee's environmental performance for Q4 2025. Our \
1469 sustainability initiatives have yielded a 34% reduction in carbon emissions compared \
1470 to Q4 2024, exceeding our target of 25%. Key achievements include the transition to \
1471 100% renewable energy in our primary data centers and the launch of our carbon offset \
1472 marketplace.",
1473 )
1474 .first_line_indent(Length::inches(0.3));
1475
1476 // Image: performance chart
1477 let chart_img = create_chart_png(400, 200);
1478 doc.add_paragraph("");
1479 doc.add_picture(
1480 &chart_img,
1481 "performance_chart.png",
1482 Length::inches(5.0),
1483 Length::inches(2.5),
1484 )
1485 .alignment(Alignment::Center);
1486 {
1487 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1488 p.add_run("Figure 1: Quarterly Carbon Emissions (tonnes CO2e)")
1489 .italic(true)
1490 .size(9.0)
1491 .color("666666");
1492 }
1493
1494 // ── Section 2: Energy Consumption ──
1495 doc.add_paragraph("").page_break_before(true);
1496 doc.add_paragraph("Energy Consumption").style("Heading1");
1497
1498 doc.add_paragraph("Data Center Operations")
1499 .style("Heading2");
1500 doc.add_paragraph(
1501 "Our three primary data centers consumed a combined 4.2 GWh in Q4 2025, \
1502 a 12% reduction from Q3 2025 driven by improved cooling efficiency and \
1503 server consolidation.",
1504 );
1505
1506 // Data table
1507 {
1508 let mut tbl = doc
1509 .add_table(5, 4)
1510 .borders(BorderStyle::Single, 4, "2D5016")
1511 .width_pct(100.0);
1512 let hdrs = ["Data Center", "Capacity (MW)", "Usage (GWh)", "PUE"];
1513 for (c, h) in hdrs.iter().enumerate() {
1514 tbl.cell(0, c).unwrap().set_text(h);
1515 tbl.cell(0, c).unwrap().shading("2D5016");
1516 }
1517 let rows = [
1518 ("San Francisco (Primary)", "3.2", "1.8", "1.12"),
1519 ("Dublin (EU)", "2.1", "1.2", "1.18"),
1520 ("Singapore (APAC)", "1.8", "1.2", "1.24"),
1521 ("TOTAL", "7.1", "4.2", "1.17 avg"),
1522 ];
1523 for (i, (dc, cap, use_, pue)) in rows.iter().enumerate() {
1524 tbl.cell(i + 1, 0).unwrap().set_text(dc);
1525 tbl.cell(i + 1, 1).unwrap().set_text(cap);
1526 tbl.cell(i + 1, 2).unwrap().set_text(use_);
1527 tbl.cell(i + 1, 3).unwrap().set_text(pue);
1528 if i == 3 {
1529 for c in 0..4 {
1530 tbl.cell(i + 1, c).unwrap().shading("E2EFDA");
1531 }
1532 }
1533 }
1534 }
1535
1536 doc.add_paragraph("");
1537
1538 doc.add_paragraph("Renewable Energy Mix").style("Heading2");
1539 doc.add_paragraph("Breakdown of energy sources across all facilities:");
1540
1541 doc.add_bullet_list_item("Solar PV: 42% (1.76 GWh)", 0);
1542 doc.add_bullet_list_item("Wind Power Purchase Agreements: 38% (1.60 GWh)", 0);
1543 doc.add_bullet_list_item("Hydroelectric: 12% (0.50 GWh)", 0);
1544 doc.add_bullet_list_item("Grid (non-renewable): 8% (0.34 GWh)", 0);
1545
1546 // ── Section 3: Water & Waste ──
1547 doc.add_paragraph("Water & Waste Management")
1548 .style("Heading1");
1549
1550 doc.add_paragraph("Water Usage").style("Heading2");
1551 doc.add_paragraph(
1552 "Total water consumption was 12.4 million gallons, a 15% reduction from Q3. \
1553 Our closed-loop cooling systems now recycle 78% of water used in cooling operations.",
1554 );
1555
1556 doc.add_paragraph("Waste Reduction").style("Heading2");
1557 doc.add_paragraph("E-waste management results for Q4:")
1558 .keep_with_next(true);
1559 {
1560 let mut tbl = doc
1561 .add_table(4, 3)
1562 .borders(BorderStyle::Single, 2, "6B8E23")
1563 .width_pct(80.0);
1564 tbl.cell(0, 0).unwrap().set_text("Category");
1565 tbl.cell(0, 0).unwrap().shading("6B8E23");
1566 tbl.cell(0, 1).unwrap().set_text("Weight (kg)");
1567 tbl.cell(0, 1).unwrap().shading("6B8E23");
1568 tbl.cell(0, 2).unwrap().set_text("Recycled %");
1569 tbl.cell(0, 2).unwrap().shading("6B8E23");
1570 let rows = [
1571 ("Server Hardware", "2,450", "94%"),
1572 ("Networking Equipment", "820", "91%"),
1573 ("Storage Media", "340", "99%"),
1574 ];
1575 for (i, (cat, wt, pct)) in rows.iter().enumerate() {
1576 tbl.cell(i + 1, 0).unwrap().set_text(cat);
1577 tbl.cell(i + 1, 1).unwrap().set_text(wt);
1578 tbl.cell(i + 1, 2).unwrap().set_text(pct);
1579 }
1580 }
1581
1582 // ── Section 4: Initiatives ──
1583 doc.add_paragraph("").page_break_before(true);
1584 doc.add_paragraph("Q1 2026 Initiatives").style("Heading1");
1585
1586 doc.add_paragraph("Planned Programs").style("Heading2");
1587 doc.add_numbered_list_item(
1588 "Deploy on-site battery storage at SF data center (2 MWh capacity)",
1589 0,
1590 );
1591 doc.add_numbered_list_item("Pilot immersion cooling in Dublin facility", 0);
1592 doc.add_numbered_list_item("Launch employee commute carbon offset program", 0);
1593 doc.add_numbered_list_item("Achieve ISO 14001 certification for Singapore facility", 0);
1594
1595 doc.add_paragraph("Investment Targets").style("Heading2");
1596 doc.add_paragraph("Sustainability CapEx allocation for FY2026:")
1597 .keep_with_next(true);
1598 {
1599 let mut tbl = doc
1600 .add_table(5, 2)
1601 .borders(BorderStyle::Single, 4, "2D5016");
1602 tbl.cell(0, 0).unwrap().set_text("Initiative");
1603 tbl.cell(0, 0).unwrap().shading("2D5016");
1604 tbl.cell(0, 1).unwrap().set_text("Budget");
1605 tbl.cell(0, 1).unwrap().shading("2D5016");
1606 let rows = [
1607 ("Battery Storage", "$1.2M"),
1608 ("Immersion Cooling Pilot", "$800K"),
1609 ("Solar Panel Expansion", "$2.1M"),
1610 ("Carbon Credits", "$500K"),
1611 ];
1612 for (i, (init, budget)) in rows.iter().enumerate() {
1613 tbl.cell(i + 1, 0).unwrap().set_text(init);
1614 tbl.cell(i + 1, 1).unwrap().set_text(budget);
1615 if i % 2 == 0 {
1616 tbl.cell(i + 1, 0).unwrap().shading("FFFAF0");
1617 tbl.cell(i + 1, 1).unwrap().shading("FFFAF0");
1618 }
1619 }
1620 }
1621
1622 // Image: sustainability roadmap
1623 let roadmap_img = create_sample_png(500, 100, [40, 80, 30]);
1624 doc.add_paragraph("");
1625 doc.add_picture(
1626 &roadmap_img,
1627 "roadmap.png",
1628 Length::inches(6.0),
1629 Length::inches(1.2),
1630 )
1631 .alignment(Alignment::Center);
1632 {
1633 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1634 p.add_run("Figure 2: 2026 Sustainability Roadmap")
1635 .italic(true)
1636 .size(9.0)
1637 .color("666666");
1638 }
1639
1640 doc.add_paragraph("");
1641 doc.add_paragraph(
1642 "For questions about this report, contact Walter White at sustainability@tensorbee.com.",
1643 )
1644 .shading("FFFAF0")
1645 .border_all(BorderStyle::Single, 2, "2D5016");
1646
1647 doc
1648}
1649
1650// =============================================================================
1651// 6. LETTER — Slate blue + silver scheme
1652// =============================================================================
1653fn generate_letter(_samples_dir: &Path) -> Document {
1654 let mut doc = Document::new();
1655
1656 // Colors: Slate #4A5568, Blue accent #3182CE
1657 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1658 doc.set_margins(
1659 Length::inches(1.25),
1660 Length::inches(1.25),
1661 Length::inches(1.25),
1662 Length::inches(1.25),
1663 );
1664 doc.set_title("Business Letter");
1665 doc.set_author("Walter White");
1666
1667 // Banner header using raw XML
1668 let logo_img = create_logo_png(220, 48);
1669 let banner = build_header_banner_xml(
1670 "rId1",
1671 &BannerOpts {
1672 bg_color: "4A5568",
1673 banner_width: 7772400,
1674 banner_height: 731520, // ~0.8"
1675 logo_width: 2011680,
1676 logo_height: 438912,
1677 logo_x_offset: 295125,
1678 logo_y_offset: 146304,
1679 },
1680 );
1681 doc.set_raw_header_with_images(
1682 banner,
1683 &[("rId1", &logo_img, "logo.png")],
1684 rdocx_oxml::header_footer::HdrFtrType::Default,
1685 );
1686 doc.set_margins(
1687 Length::twips(2000),
1688 Length::twips(1800),
1689 Length::twips(1440),
1690 Length::twips(1800),
1691 );
1692 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
1693
1694 // Footer with contact
1695 doc.set_footer(
1696 "Tensorbee | 123 Innovation Drive, Suite 400 | San Francisco, CA 94105 | tensorbee.com",
1697 );
1698
1699 // ── Sender Address ──
1700 doc.add_paragraph("Walter White");
1701 doc.add_paragraph("Chief Executive Officer");
1702 doc.add_paragraph("Tensorbee");
1703 doc.add_paragraph("123 Innovation Drive, Suite 400");
1704 doc.add_paragraph("San Francisco, CA 94105");
1705 doc.add_paragraph("");
1706 doc.add_paragraph("February 22, 2026");
1707
1708 doc.add_paragraph("");
1709
1710 // ── Recipient ──
1711 doc.add_paragraph("Ms. Sarah Chen");
1712 doc.add_paragraph("VP of Engineering");
1713 doc.add_paragraph("Meridian Dynamics LLC");
1714 doc.add_paragraph("456 Enterprise Boulevard");
1715 doc.add_paragraph("Austin, TX 78701");
1716
1717 doc.add_paragraph("");
1718
1719 // ── Subject ──
1720 {
1721 let mut p = doc.add_paragraph("");
1722 p.add_run("Re: Strategic Technology Partnership — Phase 2 Expansion")
1723 .bold(true);
1724 }
1725
1726 doc.add_paragraph("");
1727
1728 // ── Body ──
1729 doc.add_paragraph("Dear Ms. Chen,");
1730 doc.add_paragraph("");
1731
1732 doc.add_paragraph(
1733 "I am writing to express Tensorbee's enthusiasm for expanding our strategic technology \
1734 partnership with Meridian Dynamics. Following the successful completion of Phase 1, \
1735 which delivered a 40% improvement in your ML inference pipeline throughput, we believe \
1736 the foundation is firmly established for an ambitious Phase 2 engagement.",
1737 )
1738 .first_line_indent(Length::inches(0.5));
1739
1740 doc.add_paragraph(
1741 "During our recent executive review, your team highlighted three priority areas for \
1742 the next phase of collaboration:",
1743 )
1744 .first_line_indent(Length::inches(0.5));
1745
1746 doc.add_numbered_list_item(
1747 "Real-time anomaly detection for your financial transaction monitoring system, \
1748 targeting sub-100ms latency at 50,000 transactions per second.",
1749 0,
1750 );
1751 doc.add_numbered_list_item(
1752 "Federated learning infrastructure to enable model training across your distributed \
1753 data centers without centralizing sensitive financial data.",
1754 0,
1755 );
1756 doc.add_numbered_list_item(
1757 "MLOps automation to reduce your model deployment cycle from the current 5 days \
1758 to under 4 hours.",
1759 0,
1760 );
1761
1762 doc.add_paragraph(
1763 "Our engineering team has prepared a detailed technical proposal addressing each of \
1764 these areas. We have allocated a dedicated team of eight senior engineers, led by \
1765 our CTO, to ensure continuity with the Phase 1 team your organization has already \
1766 built a productive working relationship with.",
1767 )
1768 .first_line_indent(Length::inches(0.5));
1769
1770 doc.add_paragraph(
1771 "I would welcome the opportunity to present our Phase 2 proposal in person. My \
1772 assistant will reach out to coordinate a meeting at your convenience during the \
1773 first week of March.",
1774 )
1775 .first_line_indent(Length::inches(0.5));
1776
1777 doc.add_paragraph("");
1778
1779 doc.add_paragraph("Warm regards,");
1780 doc.add_paragraph("");
1781 doc.add_paragraph("");
1782
1783 // Signature line
1784 doc.add_paragraph("")
1785 .border_bottom(BorderStyle::Single, 4, "4A5568");
1786 {
1787 let mut p = doc.add_paragraph("");
1788 p.add_run("Walter White").bold(true).size(12.0);
1789 }
1790 {
1791 let mut p = doc.add_paragraph("");
1792 p.add_run("Chief Executive Officer, Tensorbee")
1793 .italic(true)
1794 .size(10.0)
1795 .color("666666");
1796 }
1797 {
1798 let mut p = doc.add_paragraph("");
1799 p.add_run("walter@tensorbee.com | +1 (415) 555-0199")
1800 .size(10.0)
1801 .color("888888");
1802 }
1803
1804 doc
1805}
1806
1807// =============================================================================
1808// 7. EMPLOYMENT CONTRACT — Purple + charcoal scheme
1809// =============================================================================
1810fn generate_contract(_samples_dir: &Path) -> Document {
1811 let mut doc = Document::new();
1812
1813 // Colors: Purple #5B2C6F, Plum #8E44AD, Gray #2C3E50
1814 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1815 doc.set_margins(
1816 Length::inches(1.25),
1817 Length::inches(1.0),
1818 Length::inches(1.0),
1819 Length::inches(1.0),
1820 );
1821 doc.set_title("Employment Agreement");
1822 doc.set_author("Tensorbee HR Department");
1823 doc.set_subject("Employment Contract — Walter White");
1824 doc.set_keywords("employment, contract, agreement, Tensorbee");
1825
1826 doc.set_header("TENSORBEE — Employment Agreement");
1827 doc.set_footer("Employment Agreement — Walter White — Page");
1828
1829 // ── Title Block ──
1830 doc.add_paragraph("")
1831 .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1832 {
1833 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1834 p.add_run("EMPLOYMENT AGREEMENT")
1835 .bold(true)
1836 .size(24.0)
1837 .color("5B2C6F")
1838 .font("Georgia");
1839 }
1840 doc.add_paragraph("")
1841 .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1842 doc.add_paragraph("");
1843
1844 // ── Parties ──
1845 doc.add_paragraph(
1846 "This Employment Agreement (\"Agreement\") is entered into as of February 22, 2026 \
1847 (\"Effective Date\"), by and between:",
1848 );
1849
1850 doc.add_paragraph("");
1851
1852 {
1853 let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1854 p.add_run("EMPLOYER: ").bold(true);
1855 p.add_run(
1856 "Tensorbee, Inc., a Delaware corporation with its principal place of business at \
1857 123 Innovation Drive, Suite 400, San Francisco, CA 94105 (\"Company\")",
1858 );
1859 }
1860 doc.add_paragraph("");
1861 {
1862 let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1863 p.add_run("EMPLOYEE: ").bold(true);
1864 p.add_run(
1865 "Walter White, residing at 308 Negra Arroyo Lane, Albuquerque, NM 87104 (\"Employee\")",
1866 );
1867 }
1868
1869 doc.add_paragraph("");
1870 doc.add_paragraph("The Company and Employee are collectively referred to as the \"Parties.\"");
1871 doc.add_paragraph("");
1872
1873 // ── Article 1: Position and Duties ──
1874 doc.add_paragraph("Article 1 — Position and Duties")
1875 .style("Heading1");
1876 {
1877 let mut p = doc.add_paragraph("");
1878 p.add_run("1.1 ").bold(true);
1879 p.add_run("The Company hereby employs the Employee as ");
1880 p.add_run("Chief Executive Officer (CEO)")
1881 .bold(true)
1882 .italic(true);
1883 p.add_run(", reporting directly to the Board of Directors.");
1884 }
1885 {
1886 let mut p = doc.add_paragraph("");
1887 p.add_run("1.2 ").bold(true);
1888 p.add_run("The Employee shall devote full working time, attention, and best efforts to \
1889 the performance of duties as reasonably assigned by the Board, including but not limited to:");
1890 }
1891 doc.add_bullet_list_item(
1892 "Setting and executing the Company's strategic vision and business plan",
1893 0,
1894 );
1895 doc.add_bullet_list_item(
1896 "Overseeing all operations, engineering, and commercial activities",
1897 0,
1898 );
1899 doc.add_bullet_list_item(
1900 "Representing the Company to investors, customers, and the public",
1901 0,
1902 );
1903 doc.add_bullet_list_item("Recruiting, developing, and retaining key talent", 0);
1904
1905 {
1906 let mut p = doc.add_paragraph("");
1907 p.add_run("1.3 ").bold(true);
1908 p.add_run(
1909 "The Employee's primary work location shall be the Company's San Francisco \
1910 headquarters, with reasonable travel as required by business needs.",
1911 );
1912 }
1913
1914 // ── Article 2: Compensation ──
1915 doc.add_paragraph("Article 2 — Compensation and Benefits")
1916 .style("Heading1");
1917 {
1918 let mut p = doc.add_paragraph("");
1919 p.add_run("2.1 Base Salary. ").bold(true);
1920 p.add_run("The Company shall pay the Employee an annual base salary of ");
1921 p.add_run("$375,000.00").bold(true);
1922 p.add_run(" (Three Hundred Seventy-Five Thousand Dollars), payable in accordance with the \
1923 Company's standard payroll schedule, less applicable withholdings and deductions.");
1924 }
1925 {
1926 let mut p = doc.add_paragraph("");
1927 p.add_run("2.2 Annual Bonus. ").bold(true);
1928 p.add_run("The Employee shall be eligible for an annual performance bonus of up to ");
1929 p.add_run("40%").bold(true);
1930 p.add_run(
1931 " of base salary, based on achievement of mutually agreed performance objectives.",
1932 );
1933 }
1934 {
1935 let mut p = doc.add_paragraph("");
1936 p.add_run("2.3 Equity. ").bold(true);
1937 p.add_run("Subject to Board approval, the Employee shall receive a stock option grant of ");
1938 p.add_run("500,000 shares").bold(true);
1939 p.add_run(
1940 " of the Company's common stock, vesting over four (4) years with a one-year cliff, \
1941 at an exercise price equal to the fair market value on the date of grant.",
1942 );
1943 }
1944
1945 // Compensation summary table
1946 doc.add_paragraph("");
1947 {
1948 let mut tbl = doc
1949 .add_table(5, 2)
1950 .borders(BorderStyle::Single, 4, "5B2C6F")
1951 .width_pct(70.0)
1952 .alignment(Alignment::Center);
1953 tbl.cell(0, 0).unwrap().set_text("Compensation Element");
1954 tbl.cell(0, 0).unwrap().shading("5B2C6F");
1955 tbl.cell(0, 1).unwrap().set_text("Value");
1956 tbl.cell(0, 1).unwrap().shading("5B2C6F");
1957 let items = [
1958 ("Base Salary", "$375,000/year"),
1959 ("Target Bonus", "Up to 40% ($150,000)"),
1960 ("Equity Grant", "500,000 shares (4yr vest)"),
1961 ("Total Target Comp", "$525,000 + equity"),
1962 ];
1963 for (i, (elem, val)) in items.iter().enumerate() {
1964 tbl.cell(i + 1, 0).unwrap().set_text(elem);
1965 tbl.cell(i + 1, 1).unwrap().set_text(val);
1966 if i == 3 {
1967 tbl.cell(i + 1, 0).unwrap().shading("E8DAEF");
1968 tbl.cell(i + 1, 1).unwrap().shading("E8DAEF");
1969 }
1970 }
1971 }
1972
1973 // ── Article 3: Benefits ──
1974 doc.add_paragraph("").page_break_before(true);
1975 doc.add_paragraph("Article 3 — Benefits").style("Heading1");
1976 {
1977 let mut p = doc.add_paragraph("");
1978 p.add_run("3.1 ").bold(true);
1979 p.add_run(
1980 "The Employee shall be entitled to participate in all benefit programs \
1981 generally available to senior executives, including:",
1982 );
1983 }
1984 doc.add_bullet_list_item(
1985 "Medical, dental, and vision insurance (100% premium coverage for employee and dependents)",
1986 0,
1987 );
1988 doc.add_bullet_list_item("401(k) retirement plan with 6% company match", 0);
1989 doc.add_bullet_list_item("Life insurance and long-term disability coverage", 0);
1990 doc.add_bullet_list_item("Annual professional development allowance of $10,000", 0);
1991
1992 {
1993 let mut p = doc.add_paragraph("");
1994 p.add_run("3.2 Paid Time Off. ").bold(true);
1995 p.add_run(
1996 "The Employee shall receive 25 days of paid vacation per year, plus Company holidays, \
1997 accruing on a monthly basis.",
1998 );
1999 }
2000
2001 // ── Article 4: Term and Termination ──
2002 doc.add_paragraph("Article 4 — Term and Termination")
2003 .style("Heading1");
2004 {
2005 let mut p = doc.add_paragraph("");
2006 p.add_run("4.1 At-Will Employment. ").bold(true);
2007 p.add_run(
2008 "This Agreement is for at-will employment and may be terminated by either Party \
2009 at any time, with or without cause, subject to the notice provisions herein.",
2010 );
2011 }
2012 {
2013 let mut p = doc.add_paragraph("");
2014 p.add_run("4.2 Notice Period. ").bold(true);
2015 p.add_run("Either Party shall provide ");
2016 p.add_run("ninety (90) days").bold(true);
2017 p.add_run(" written notice of termination, or pay in lieu thereof.");
2018 }
2019 {
2020 let mut p = doc.add_paragraph("");
2021 p.add_run("4.3 Severance. ").bold(true);
2022 p.add_run("In the event of termination by the Company without Cause, the Employee shall \
2023 receive (a) twelve (12) months of base salary continuation, (b) pro-rata bonus \
2024 for the year of termination, and (c) twelve (12) months of COBRA premium coverage.");
2025 }
2026
2027 // ── Article 5: Confidentiality ──
2028 doc.add_paragraph("Article 5 — Confidentiality and IP")
2029 .style("Heading1");
2030 {
2031 let mut p = doc.add_paragraph("");
2032 p.add_run("5.1 ").bold(true);
2033 p.add_run(
2034 "The Employee agrees to maintain strict confidentiality of all proprietary \
2035 information, trade secrets, and business plans of the Company during and after \
2036 employment.",
2037 );
2038 }
2039 {
2040 let mut p = doc.add_paragraph("");
2041 p.add_run("5.2 ").bold(true);
2042 p.add_run(
2043 "All intellectual property, inventions, and works of authorship created by the \
2044 Employee during the term of employment and within the scope of duties shall be \
2045 the sole and exclusive property of the Company.",
2046 );
2047 }
2048
2049 // ── Article 6: Non-Compete ──
2050 doc.add_paragraph("Article 6 — Non-Competition")
2051 .style("Heading1");
2052 {
2053 let mut p = doc.add_paragraph("");
2054 p.add_run("6.1 ").bold(true);
2055 p.add_run("For a period of twelve (12) months following termination, the Employee agrees \
2056 not to engage in any business that directly competes with the Company's core \
2057 business of AI infrastructure and ML operations services, within the United States.");
2058 }
2059
2060 // ── Article 7: General Provisions ──
2061 doc.add_paragraph("Article 7 — General Provisions")
2062 .style("Heading1");
2063 {
2064 let mut p = doc.add_paragraph("");
2065 p.add_run("7.1 Governing Law. ").bold(true);
2066 p.add_run(
2067 "This Agreement shall be governed by and construed in accordance with the laws of \
2068 the State of California.",
2069 );
2070 }
2071 {
2072 let mut p = doc.add_paragraph("");
2073 p.add_run("7.2 Entire Agreement. ").bold(true);
2074 p.add_run(
2075 "This Agreement constitutes the entire agreement between the Parties and supersedes \
2076 all prior negotiations, representations, and agreements.",
2077 );
2078 }
2079 {
2080 let mut p = doc.add_paragraph("");
2081 p.add_run("7.3 Amendment. ").bold(true);
2082 p.add_run(
2083 "This Agreement may only be amended by a written instrument signed by both Parties.",
2084 );
2085 }
2086
2087 // ── Signature Block ──
2088 doc.add_paragraph("").page_break_before(true);
2089 doc.add_paragraph("IN WITNESS WHEREOF, the Parties have executed this Employment Agreement as of the Effective Date.")
2090 .space_after(Length::pt(24.0));
2091
2092 // Signature table
2093 {
2094 let mut tbl = doc.add_table(6, 2).width_pct(100.0);
2095 tbl.cell(0, 0).unwrap().set_text("FOR THE COMPANY:");
2096 tbl.cell(0, 0).unwrap().shading("5B2C6F");
2097 tbl.cell(0, 1).unwrap().set_text("EMPLOYEE:");
2098 tbl.cell(0, 1).unwrap().shading("5B2C6F");
2099
2100 tbl.cell(1, 0).unwrap().set_text("");
2101 tbl.cell(1, 1).unwrap().set_text("");
2102 tbl.row(1).unwrap().height(Length::pt(40.0));
2103
2104 tbl.cell(2, 0)
2105 .unwrap()
2106 .set_text("Signature: ___________________________");
2107 tbl.cell(2, 1)
2108 .unwrap()
2109 .set_text("Signature: ___________________________");
2110 tbl.cell(3, 0)
2111 .unwrap()
2112 .set_text("Name: Board Representative");
2113 tbl.cell(3, 1).unwrap().set_text("Name: Walter White");
2114 tbl.cell(4, 0)
2115 .unwrap()
2116 .set_text("Title: Chair, Board of Directors");
2117 tbl.cell(4, 1)
2118 .unwrap()
2119 .set_text("Title: Chief Executive Officer");
2120 tbl.cell(5, 0).unwrap().set_text("Date: _______________");
2121 tbl.cell(5, 1).unwrap().set_text("Date: _______________");
2122 }
2123
2124 doc
2125}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?
34fn generate_feature_showcase(path: &Path) {
35 let mut doc = Document::new();
36
37 // =========================================================================
38 // PAGE SETUP & METADATA
39 // =========================================================================
40 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
41 doc.set_margins(
42 Length::inches(1.0), // top
43 Length::inches(1.0), // right
44 Length::inches(1.0), // bottom
45 Length::inches(1.0), // left
46 );
47 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
48 doc.set_gutter(Length::twips(0));
49
50 doc.set_title("rdocx Feature Showcase");
51 doc.set_author("rdocx Sample Generator");
52 doc.set_subject("Comprehensive feature demonstration");
53 doc.set_keywords("rdocx, docx, rust, sample");
54
55 // Header & Footer
56 doc.set_header("rdocx Feature Showcase");
57 doc.set_footer("Generated by rdocx — Page");
58
59 // Different first page header
60 doc.set_different_first_page(true);
61 doc.set_first_page_header("rdocx");
62 doc.set_first_page_footer("Feature Showcase — Cover Page");
63
64 // =========================================================================
65 // PAGE 1: COVER PAGE — background image, run formatting
66 // =========================================================================
67 let bg_cover = create_sample_png(612, 792, [30, 60, 120]);
68 doc.add_background_image(&bg_cover, "cover_bg.png");
69
70 doc.add_paragraph(""); // spacer
71 doc.add_paragraph(""); // spacer
72 doc.add_paragraph(""); // spacer
73
74 {
75 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
76 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
77 }
78 {
79 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
80 p.add_run("Feature Showcase")
81 .size(28.0)
82 .color("FFFFFF")
83 .italic(true);
84 }
85
86 doc.add_paragraph(""); // spacer
87
88 {
89 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
90 p.add_run("A comprehensive demonstration of every feature")
91 .size(14.0)
92 .color("CCDDFF");
93 }
94 {
95 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
96 p.add_run("provided by the rdocx Rust crate for DOCX generation.")
97 .size(14.0)
98 .color("CCDDFF");
99 }
100
101 // =========================================================================
102 // PAGE 2: TEXT FORMATTING
103 // =========================================================================
104 doc.add_paragraph("").page_break_before(true);
105
106 doc.add_paragraph("1. Text Formatting").style("Heading1");
107
108 doc.add_paragraph("This section demonstrates paragraph and run-level formatting options.");
109 doc.add_paragraph("");
110
111 // --- Paragraph alignment ---
112 doc.add_paragraph("Paragraph Alignment").style("Heading2");
113
114 doc.add_paragraph("This paragraph is left-aligned (the default).")
115 .alignment(Alignment::Left);
116 doc.add_paragraph("This paragraph is center-aligned.")
117 .alignment(Alignment::Center);
118 doc.add_paragraph("This paragraph is right-aligned.")
119 .alignment(Alignment::Right);
120 doc.add_paragraph(
121 "This paragraph is justified. To demonstrate justified text properly, it needs \
122 to be long enough to span multiple lines so the word spacing adjustment is visible \
123 across the full width of the text area on the page.",
124 )
125 .alignment(Alignment::Justify);
126
127 doc.add_paragraph("");
128
129 // --- Run formatting ---
130 doc.add_paragraph("Run Formatting").style("Heading2");
131
132 {
133 let mut p = doc.add_paragraph("");
134 p.add_run("Bold text").bold(true);
135 p.add_run(" | ");
136 p.add_run("Italic text").italic(true);
137 p.add_run(" | ");
138 p.add_run("Bold + Italic").bold(true).italic(true);
139 }
140 {
141 let mut p = doc.add_paragraph("");
142 p.add_run("Single underline").underline(true);
143 p.add_run(" | ");
144 p.add_run("Strikethrough").strike(true);
145 p.add_run(" | ");
146 p.add_run("Double strikethrough").double_strike(true);
147 }
148 {
149 let mut p = doc.add_paragraph("");
150 p.add_run("Red text").color("FF0000");
151 p.add_run(" | ");
152 p.add_run("Blue text").color("0000FF");
153 p.add_run(" | ");
154 p.add_run("Green text").color("00AA00");
155 p.add_run(" | ");
156 p.add_run("Highlighted").highlight("FFFF00");
157 }
158 {
159 let mut p = doc.add_paragraph("");
160 p.add_run("8pt small").size(8.0);
161 p.add_run(" | ");
162 p.add_run("11pt normal").size(11.0);
163 p.add_run(" | ");
164 p.add_run("16pt large").size(16.0);
165 p.add_run(" | ");
166 p.add_run("24pt extra-large").size(24.0);
167 }
168 {
169 let mut p = doc.add_paragraph("");
170 p.add_run("Arial font").font("Arial");
171 p.add_run(" | ");
172 p.add_run("Times New Roman font").font("Times New Roman");
173 p.add_run(" | ");
174 p.add_run("Courier New font").font("Courier New");
175 }
176 {
177 let mut p = doc.add_paragraph("");
178 p.add_run("Normal");
179 p.add_run(" H").size(11.0);
180 p.add_run("2").subscript();
181 p.add_run("O (subscript)").size(11.0);
182 p.add_run(" | E = mc").size(11.0);
183 p.add_run("2").superscript();
184 p.add_run(" (superscript)").size(11.0);
185 }
186 {
187 let mut p = doc.add_paragraph("");
188 p.add_run("ALL CAPS").all_caps(true);
189 p.add_run(" | ");
190 p.add_run("Small Caps").small_caps(true);
191 p.add_run(" | ");
192 p.add_run("Expanded spacing")
193 .character_spacing(Length::twips(40));
194 }
195
196 doc.add_paragraph("");
197
198 // --- Paragraph formatting ---
199 doc.add_paragraph("Paragraph Formatting").style("Heading2");
200
201 doc.add_paragraph("Paragraph with shading (light green background)")
202 .shading("E2EFDA");
203
204 doc.add_paragraph("Paragraph with bottom border")
205 .border_bottom(BorderStyle::Single, 6, "2E75B6");
206
207 doc.add_paragraph("Paragraph with all borders")
208 .border_all(BorderStyle::Single, 4, "FF0000");
209
210 doc.add_paragraph("Paragraph with 1-inch left indent and hanging indent")
211 .indent_left(Length::inches(1.0))
212 .hanging_indent(Length::inches(0.5));
213
214 doc.add_paragraph("Paragraph with first-line indent of 0.5 inches")
215 .first_line_indent(Length::inches(0.5));
216
217 doc.add_paragraph("Paragraph with extra space before (24pt) and after (12pt)")
218 .space_before(Length::pt(24.0))
219 .space_after(Length::pt(12.0));
220
221 doc.add_paragraph(
222 "Paragraph with double line spacing. This text should have extra vertical \
223 space between lines to demonstrate the line_spacing_multiple setting.",
224 )
225 .line_spacing_multiple(2.0);
226
227 doc.add_paragraph("Paragraph with keep-with-next (won't break from the next paragraph)")
228 .keep_with_next(true);
229 doc.add_paragraph("(This stays with the paragraph above.)");
230
231 // =========================================================================
232 // PAGE 3: LISTS & TAB STOPS
233 // =========================================================================
234 doc.add_paragraph("").page_break_before(true);
235
236 doc.add_paragraph("2. Lists").style("Heading1");
237
238 doc.add_paragraph("Bullet List").style("Heading2");
239
240 doc.add_bullet_list_item("First bullet item", 0);
241 doc.add_bullet_list_item("Second bullet item", 0);
242 doc.add_bullet_list_item("Nested level 1", 1);
243 doc.add_bullet_list_item("Nested level 2", 2);
244 doc.add_bullet_list_item("Back to level 1", 1);
245 doc.add_bullet_list_item("Third bullet item", 0);
246
247 doc.add_paragraph("");
248
249 doc.add_paragraph("Numbered List").style("Heading2");
250
251 doc.add_numbered_list_item("First numbered item", 0);
252 doc.add_numbered_list_item("Second numbered item", 0);
253 doc.add_numbered_list_item("Sub-item A", 1);
254 doc.add_numbered_list_item("Sub-item B", 1);
255 doc.add_numbered_list_item("Third numbered item", 0);
256
257 doc.add_paragraph("");
258
259 // --- Tab stops ---
260 doc.add_paragraph("Tab Stops").style("Heading2");
261
262 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
263 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
264 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
265 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
266 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
267
268 doc.add_paragraph("Item\t........\tPrice")
269 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
270 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
271 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
272
273 doc.add_paragraph("Widget A\t........\t$19.99")
274 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
275 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
276 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
277
278 doc.add_paragraph("Gadget B\t________\t$249.50")
279 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
280 .add_tab_stop_with_leader(
281 TabAlignment::Right,
282 Length::inches(4.0),
283 TabLeader::Underscore,
284 )
285 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
286
287 // =========================================================================
288 // PAGE 4: TABLES
289 // =========================================================================
290 doc.add_paragraph("").page_break_before(true);
291
292 doc.add_paragraph("3. Tables").style("Heading1");
293
294 // --- Basic table with borders ---
295 doc.add_paragraph("Basic Table with Borders")
296 .style("Heading2");
297
298 {
299 let mut tbl = doc.add_table(4, 3);
300 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
301
302 // Header row
303 for col in 0..3 {
304 tbl.cell(0, col).unwrap().shading("2E75B6");
305 }
306 tbl.cell(0, 0).unwrap().set_text("Name");
307 tbl.cell(0, 1).unwrap().set_text("Role");
308 tbl.cell(0, 2).unwrap().set_text("Location");
309
310 tbl.cell(1, 0).unwrap().set_text("Alice Johnson");
311 tbl.cell(1, 1).unwrap().set_text("Engineering Lead");
312 tbl.cell(1, 2).unwrap().set_text("New York");
313
314 tbl.cell(2, 0).unwrap().set_text("Bob Smith");
315 tbl.cell(2, 1).unwrap().set_text("Product Manager");
316 tbl.cell(2, 2).unwrap().set_text("San Francisco");
317
318 tbl.cell(3, 0).unwrap().set_text("Carol Davis");
319 tbl.cell(3, 1).unwrap().set_text("Designer");
320 tbl.cell(3, 2).unwrap().set_text("London");
321 }
322
323 doc.add_paragraph("");
324
325 // --- Table with cell merging ---
326 doc.add_paragraph("Table with Cell Merging & Vertical Alignment")
327 .style("Heading2");
328
329 {
330 let mut tbl = doc.add_table(4, 4);
331 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
332 tbl = tbl.width_pct(100.0);
333
334 // Header spanning all columns
335 tbl.cell(0, 0).unwrap().set_text("Quarterly Revenue Report");
336 tbl.cell(0, 0).unwrap().shading("1F4E79");
337 tbl.cell(0, 0).unwrap().grid_span(4);
338
339 // Sub-header
340 tbl.cell(1, 0).unwrap().set_text("Region");
341 tbl.cell(1, 0).unwrap().shading("D6E4F0");
342 tbl.cell(1, 1).unwrap().set_text("Q1");
343 tbl.cell(1, 1).unwrap().shading("D6E4F0");
344 tbl.cell(1, 2).unwrap().set_text("Q2");
345 tbl.cell(1, 2).unwrap().shading("D6E4F0");
346 tbl.cell(1, 3).unwrap().set_text("Total");
347 tbl.cell(1, 3).unwrap().shading("D6E4F0");
348
349 // Data
350 tbl.cell(2, 0).unwrap().set_text("North America");
351 tbl.cell(2, 1).unwrap().set_text("$2.4M");
352 tbl.cell(2, 2).unwrap().set_text("$2.7M");
353 tbl.cell(2, 3).unwrap().set_text("$5.1M");
354
355 tbl.cell(3, 0).unwrap().set_text("Europe");
356 tbl.cell(3, 1).unwrap().set_text("$1.8M");
357 tbl.cell(3, 2).unwrap().set_text("$2.0M");
358 tbl.cell(3, 3).unwrap().set_text("$3.8M");
359
360 // Vertical alignment on data cells
361 tbl.cell(2, 3)
362 .unwrap()
363 .vertical_alignment(VerticalAlignment::Center);
364 tbl.cell(3, 3)
365 .unwrap()
366 .vertical_alignment(VerticalAlignment::Bottom);
367 }
368
369 doc.add_paragraph("");
370
371 // --- Table with vertical merge ---
372 doc.add_paragraph("Table with Vertical Merge")
373 .style("Heading2");
374
375 {
376 let mut tbl = doc.add_table(4, 3);
377 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
378
379 tbl.cell(0, 0).unwrap().set_text("Category");
380 tbl.cell(0, 0).unwrap().shading("E2EFDA");
381 tbl.cell(0, 1).unwrap().set_text("Item");
382 tbl.cell(0, 1).unwrap().shading("E2EFDA");
383 tbl.cell(0, 2).unwrap().set_text("Price");
384 tbl.cell(0, 2).unwrap().shading("E2EFDA");
385
386 // "Hardware" spans rows 1-2
387 tbl.cell(1, 0).unwrap().set_text("Hardware");
388 tbl.cell(1, 0).unwrap().v_merge_restart();
389 tbl.cell(1, 1).unwrap().set_text("Laptop");
390 tbl.cell(1, 2).unwrap().set_text("$1,200");
391
392 tbl.cell(2, 0).unwrap().v_merge_continue();
393 tbl.cell(2, 1).unwrap().set_text("Monitor");
394 tbl.cell(2, 2).unwrap().set_text("$450");
395
396 // "Software" on row 3
397 tbl.cell(3, 0).unwrap().set_text("Software");
398 tbl.cell(3, 1).unwrap().set_text("IDE License");
399 tbl.cell(3, 2).unwrap().set_text("$200/yr");
400 }
401
402 doc.add_paragraph("");
403
404 // --- Nested table ---
405 doc.add_paragraph("Nested Table").style("Heading2");
406
407 {
408 let mut tbl = doc.add_table(2, 2);
409 tbl = tbl.borders(BorderStyle::Single, 6, "2E75B6");
410
411 tbl.cell(0, 0).unwrap().set_text("Outer Cell (0,0)");
412 tbl.cell(0, 1).unwrap().set_text("Outer Cell (0,1)");
413 tbl.cell(1, 0).unwrap().set_text("Outer Cell (1,0)");
414
415 // Nested table inside cell (1,1)
416 {
417 let mut cell = tbl.cell(1, 1).unwrap();
418 cell.set_text("Contains nested table:");
419 let mut nested = cell.add_table(2, 2);
420 nested = nested.borders(BorderStyle::Single, 2, "FF6600");
421 nested.cell(0, 0).unwrap().set_text("Inner A");
422 nested.cell(0, 1).unwrap().set_text("Inner B");
423 nested.cell(1, 0).unwrap().set_text("Inner C");
424 nested.cell(1, 1).unwrap().set_text("Inner D");
425 }
426 }
427
428 // =========================================================================
429 // PAGE 5: IMAGES
430 // =========================================================================
431 doc.add_paragraph("").page_break_before(true);
432
433 doc.add_paragraph("4. Images").style("Heading1");
434
435 doc.add_paragraph("Inline Image").style("Heading2");
436
437 doc.add_paragraph("Below is an inline image (200x50 pixels, blue gradient):");
438 let inline_img = create_sample_png(200, 50, [0, 80, 200]);
439 doc.add_picture(
440 &inline_img,
441 "inline_chart.png",
442 Length::inches(3.0),
443 Length::inches(0.75),
444 );
445
446 doc.add_paragraph("");
447
448 doc.add_paragraph("Header Image").style("Heading2");
449
450 // Replace the text-only header with an image header
451 let header_img = create_sample_png(400, 40, [40, 40, 40]);
452 doc.set_header_image(
453 &header_img,
454 "header_logo.png",
455 Length::inches(2.0),
456 Length::inches(0.2),
457 );
458
459 doc.add_paragraph(
460 "The document header has been replaced with an inline image. \
461 Check the header area at the top of this page.",
462 );
463
464 doc.add_paragraph("");
465 doc.add_paragraph(
466 "Note: The cover page uses a full-page background image behind the text, \
467 demonstrated on page 1 via add_background_image().",
468 );
469
470 // =========================================================================
471 // PAGE 6: CONTENT MANIPULATION — placeholder replacement, insertion
472 // =========================================================================
473 doc.add_paragraph("").page_break_before(true);
474
475 doc.add_paragraph("5. Content Manipulation")
476 .style("Heading1");
477
478 // --- Placeholder replacement ---
479 doc.add_paragraph("Placeholder Replacement")
480 .style("Heading2");
481
482 doc.add_paragraph(
483 "Before replacement, this document contained {{customer}} and {{date}} placeholders.",
484 );
485
486 {
487 let mut p = doc.add_paragraph("");
488 p.add_run("Customer: ").bold(true);
489 p.add_run("{{customer}}");
490 }
491 {
492 let mut p = doc.add_paragraph("");
493 p.add_run("Date: ").bold(true);
494 p.add_run("{{date}}");
495 }
496 doc.add_paragraph("Reference: {{ref_number}}");
497
498 // Table with placeholders
499 {
500 let mut tbl = doc.add_table(3, 2);
501 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
502 tbl.cell(0, 0).unwrap().set_text("Field");
503 tbl.cell(0, 0).unwrap().shading("D6E4F0");
504 tbl.cell(0, 1).unwrap().set_text("Value");
505 tbl.cell(0, 1).unwrap().shading("D6E4F0");
506 tbl.cell(1, 0).unwrap().set_text("Project");
507 tbl.cell(1, 1).unwrap().set_text("{{project}}");
508 tbl.cell(2, 0).unwrap().set_text("Status");
509 tbl.cell(2, 1).unwrap().set_text("{{status}}");
510 }
511
512 // Perform replacements
513 let mut replacements = HashMap::new();
514 replacements.insert("{{customer}}", "Acme Corporation");
515 replacements.insert("{{date}}", "February 22, 2026");
516 replacements.insert("{{ref_number}}", "REF-2026-001");
517 replacements.insert("{{project}}", "Infrastructure Upgrade");
518 replacements.insert("{{status}}", "In Progress");
519 let replace_count = doc.replace_all(&replacements);
520
521 doc.add_paragraph("");
522 doc.add_paragraph(&format!(
523 "(Replaced {} placeholders above — in body text and table cells)",
524 replace_count
525 ));
526
527 doc.add_paragraph("");
528
529 // --- Content insertion ---
530 doc.add_paragraph("Content Insertion").style("Heading2");
531
532 doc.add_paragraph("Section A: First section of content.");
533 doc.add_paragraph("Section C: Third section of content.");
534
535 // Insert "Section B" between A and C
536 if let Some(idx) = doc.find_content_index("Section C") {
537 doc.insert_paragraph(
538 idx,
539 "Section B: Inserted between A and C using find_content_index().",
540 );
541 }
542
543 doc.add_paragraph("");
544 doc.add_paragraph(
545 "The paragraph above ('Section B') was inserted at a specific position \
546 using find_content_index() + insert_paragraph().",
547 );
548
549 // =========================================================================
550 // PAGE 7: LANDSCAPE — section break, wide table
551 // =========================================================================
552 doc.add_paragraph("").section_break(SectionBreak::NextPage);
553
554 doc.add_paragraph("6. Mixed Page Orientation")
555 .style("Heading1");
556
557 doc.add_paragraph(
558 "This page is in LANDSCAPE orientation. It was created using a section break \
559 followed by section_landscape(). This is useful for wide tables or charts.",
560 );
561
562 doc.add_paragraph("");
563
564 // Wide table for landscape
565 {
566 let mut tbl = doc.add_table(4, 7);
567 tbl = tbl.borders(BorderStyle::Single, 4, "2E75B6");
568
569 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
570 for (col, h) in headers.iter().enumerate() {
571 tbl.cell(0, col).unwrap().set_text(h);
572 tbl.cell(0, col).unwrap().shading("2E75B6");
573 }
574
575 let data = [
576 [
577 "North America",
578 "$1.2M",
579 "$1.3M",
580 "$1.4M",
581 "$1.5M",
582 "$1.6M",
583 "$7.0M",
584 ],
585 [
586 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
587 ],
588 [
589 "Asia Pacific",
590 "$0.5M",
591 "$0.6M",
592 "$0.7M",
593 "$0.7M",
594 "$0.8M",
595 "$3.3M",
596 ],
597 ];
598 for (row_idx, row_data) in data.iter().enumerate() {
599 for (col, val) in row_data.iter().enumerate() {
600 tbl.cell(row_idx + 1, col).unwrap().set_text(val);
601 }
602 }
603 }
604
605 // End landscape, return to portrait
606 doc.add_paragraph("")
607 .section_break(SectionBreak::NextPage)
608 .section_landscape();
609
610 // =========================================================================
611 // PAGE 8: BACK TO PORTRAIT — styles, final notes
612 // =========================================================================
613 doc.add_paragraph("7. Custom Styles & Summary")
614 .style("Heading1");
615
616 doc.add_paragraph(
617 "This final page is back in portrait orientation after a section break. \
618 The document has demonstrated:",
619 );
620
621 doc.add_bullet_list_item(
622 "Page setup: size, margins, header/footer distance, gutter",
623 0,
624 );
625 doc.add_bullet_list_item("Document metadata: title, author, subject, keywords", 0);
626 doc.add_bullet_list_item("Headers and footers: text, images, different first page", 0);
627 doc.add_bullet_list_item("Background images: full-page behind text", 0);
628 doc.add_bullet_list_item(
629 "Text formatting: bold, italic, underline, strike, color, size, font",
630 0,
631 );
632 doc.add_bullet_list_item(
633 "Advanced run formatting: superscript, subscript, caps, spacing",
634 0,
635 );
636 doc.add_bullet_list_item(
637 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
638 0,
639 );
640 doc.add_bullet_list_item("Bullet and numbered lists with nesting levels", 0);
641 doc.add_bullet_list_item("Tab stops with dot/underscore leaders", 0);
642 doc.add_bullet_list_item(
643 "Tables: borders, shading, column spans, row spans, nesting",
644 0,
645 );
646 doc.add_bullet_list_item("Vertical alignment in table cells", 0);
647 doc.add_bullet_list_item("Inline images", 0);
648 doc.add_bullet_list_item("Placeholder replacement in body and table cells", 0);
649 doc.add_bullet_list_item("Content insertion at specific positions", 0);
650 doc.add_bullet_list_item(
651 "Section breaks with mixed portrait/landscape orientation",
652 0,
653 );
654
655 doc.add_paragraph("");
656 doc.add_paragraph("All features above were built entirely from scratch using the rdocx API.")
657 .alignment(Alignment::Center)
658 .shading("E2EFDA")
659 .border_all(BorderStyle::Single, 2, "00AA00");
660
661 doc.save(path).unwrap();
662}More examples
86fn generate_feature_showcase(_samples_dir: &Path) -> Document {
87 let mut doc = Document::new();
88
89 // ── Page Setup & Metadata ──
90 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
91 doc.set_margins(
92 Length::inches(1.0),
93 Length::inches(1.0),
94 Length::inches(1.0),
95 Length::inches(1.0),
96 );
97 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
98 doc.set_gutter(Length::twips(0));
99 doc.set_title("rdocx Feature Showcase");
100 doc.set_author("rdocx Sample Generator");
101 doc.set_subject("Comprehensive feature demonstration");
102 doc.set_keywords("rdocx, docx, rust, sample, showcase");
103
104 // Headers & Footers with different first page
105 doc.set_different_first_page(true);
106 doc.set_first_page_header("rdocx");
107 doc.set_first_page_footer("Feature Showcase — Cover Page");
108 doc.set_header("rdocx Feature Showcase");
109 doc.set_footer("Generated by rdocx");
110
111 // ── COVER PAGE ──
112 let bg = create_sample_png(612, 792, [20, 45, 90]);
113 doc.add_background_image(&bg, "cover_bg.png");
114
115 for _ in 0..3 {
116 doc.add_paragraph("");
117 }
118 {
119 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
120 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
121 }
122 {
123 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
124 p.add_run("Complete Feature Showcase")
125 .size(28.0)
126 .color("FFFFFF")
127 .italic(true);
128 }
129 doc.add_paragraph("");
130 {
131 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
132 p.add_run("Every feature of the rdocx Rust library")
133 .size(13.0)
134 .color("B0C4DE");
135 }
136 {
137 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
138 p.add_run("demonstrated in a single document.")
139 .size(13.0)
140 .color("B0C4DE");
141 }
142
143 // ── TABLE OF CONTENTS ──
144 doc.add_paragraph("").page_break_before(true);
145 doc.insert_toc(doc.content_count(), 3);
146
147 // ── SECTION 1: TEXT FORMATTING ──
148 doc.add_paragraph("").page_break_before(true);
149 doc.add_paragraph("1. Text Formatting").style("Heading1");
150
151 // Paragraph Alignment
152 doc.add_paragraph("1.1 Paragraph Alignment")
153 .style("Heading2");
154 doc.add_paragraph("Left-aligned paragraph (default).")
155 .alignment(Alignment::Left);
156 doc.add_paragraph("Center-aligned paragraph.")
157 .alignment(Alignment::Center);
158 doc.add_paragraph("Right-aligned paragraph.")
159 .alignment(Alignment::Right);
160 doc.add_paragraph(
161 "Justified paragraph — this text is long enough to demonstrate how justified alignment \
162 distributes extra space across word gaps so lines fill the full width of the text area.",
163 )
164 .alignment(Alignment::Justify);
165
166 doc.add_paragraph("");
167
168 // Run Formatting
169 doc.add_paragraph("1.2 Run Formatting").style("Heading2");
170 {
171 let mut p = doc.add_paragraph("");
172 p.add_run("Bold").bold(true);
173 p.add_run(" | ");
174 p.add_run("Italic").italic(true);
175 p.add_run(" | ");
176 p.add_run("Bold Italic").bold(true).italic(true);
177 p.add_run(" | ");
178 p.add_run("Underline").underline(true);
179 p.add_run(" | ");
180 p.add_run("Strikethrough").strike(true);
181 p.add_run(" | ");
182 p.add_run("Double Strike").double_strike(true);
183 }
184 {
185 let mut p = doc.add_paragraph("");
186 p.add_run("Red").color("FF0000");
187 p.add_run(" | ");
188 p.add_run("Blue").color("0000FF");
189 p.add_run(" | ");
190 p.add_run("Green").color("00AA00");
191 p.add_run(" | ");
192 p.add_run("Highlighted").highlight("FFFF00");
193 }
194 {
195 let mut p = doc.add_paragraph("");
196 p.add_run("8pt").size(8.0);
197 p.add_run(" | ");
198 p.add_run("11pt").size(11.0);
199 p.add_run(" | ");
200 p.add_run("16pt").size(16.0);
201 p.add_run(" | ");
202 p.add_run("24pt").size(24.0);
203 }
204 {
205 let mut p = doc.add_paragraph("");
206 p.add_run("Arial").font("Arial");
207 p.add_run(" | ");
208 p.add_run("Times New Roman").font("Times New Roman");
209 p.add_run(" | ");
210 p.add_run("Courier New").font("Courier New");
211 }
212 {
213 let mut p = doc.add_paragraph("");
214 p.add_run("H");
215 p.add_run("2").subscript();
216 p.add_run("O (subscript) | E=mc");
217 p.add_run("2").superscript();
218 p.add_run(" (superscript)");
219 }
220 {
221 let mut p = doc.add_paragraph("");
222 p.add_run("ALL CAPS").all_caps(true);
223 p.add_run(" | ");
224 p.add_run("Small Caps").small_caps(true);
225 p.add_run(" | ");
226 p.add_run("Expanded").character_spacing(Length::twips(40));
227 p.add_run(" | ");
228 p.add_run("Hidden text (not visible)").hidden(true);
229 }
230
231 doc.add_paragraph("");
232
233 // Underline Styles
234 doc.add_paragraph("1.3 Underline Styles").style("Heading2");
235 {
236 let mut p = doc.add_paragraph("");
237 p.add_run("Single ")
238 .underline_style(rdocx::UnderlineStyle::Single);
239 p.add_run("Double ")
240 .underline_style(rdocx::UnderlineStyle::Double);
241 p.add_run("Thick ")
242 .underline_style(rdocx::UnderlineStyle::Thick);
243 p.add_run("Dotted ")
244 .underline_style(rdocx::UnderlineStyle::Dotted);
245 p.add_run("Dash ")
246 .underline_style(rdocx::UnderlineStyle::Dash);
247 p.add_run("Wave ")
248 .underline_style(rdocx::UnderlineStyle::Wave);
249 }
250
251 // ── SECTION 2: PARAGRAPH FORMATTING ──
252 doc.add_paragraph("").page_break_before(true);
253 doc.add_paragraph("2. Paragraph Formatting")
254 .style("Heading1");
255
256 doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
257 doc.add_paragraph("Paragraph with light green shading")
258 .shading("E2EFDA");
259 doc.add_paragraph("Paragraph with bottom border")
260 .border_bottom(BorderStyle::Single, 6, "2E75B6");
261 doc.add_paragraph("Paragraph with all borders (red)")
262 .border_all(BorderStyle::Single, 4, "FF0000");
263
264 doc.add_paragraph("2.2 Indentation").style("Heading2");
265 doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
266 .indent_left(Length::inches(1.0))
267 .hanging_indent(Length::inches(0.5));
268 doc.add_paragraph("0.5-inch first-line indent on this paragraph")
269 .first_line_indent(Length::inches(0.5));
270 doc.add_paragraph("Both left and right indent (0.75 inches each)")
271 .indent_left(Length::inches(0.75))
272 .indent_right(Length::inches(0.75));
273
274 doc.add_paragraph("2.3 Spacing & Line Height")
275 .style("Heading2");
276 doc.add_paragraph("Extra space before (24pt) and after (12pt)")
277 .space_before(Length::pt(24.0))
278 .space_after(Length::pt(12.0));
279 doc.add_paragraph(
280 "Double line spacing paragraph. This text should have extra vertical space between \
281 lines to demonstrate line_spacing_multiple(2.0).",
282 )
283 .line_spacing_multiple(2.0);
284 doc.add_paragraph("Exact 20pt line spacing (fixed height).")
285 .line_spacing(20.0);
286
287 doc.add_paragraph("2.4 Pagination Controls")
288 .style("Heading2");
289 doc.add_paragraph("keep_with_next — this paragraph stays with the next")
290 .keep_with_next(true);
291 doc.add_paragraph("(This paragraph was kept with the one above.)");
292 doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
293 .keep_together(true);
294 doc.add_paragraph("widow_control — prevents widow/orphan lines")
295 .widow_control(true);
296
297 // ── SECTION 3: LISTS ──
298 doc.add_paragraph("").page_break_before(true);
299 doc.add_paragraph("3. Lists").style("Heading1");
300
301 doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
302 doc.add_bullet_list_item("First item", 0);
303 doc.add_bullet_list_item("Second item", 0);
304 doc.add_bullet_list_item("Nested level 1", 1);
305 doc.add_bullet_list_item("Nested level 2", 2);
306 doc.add_bullet_list_item("Back to level 1", 1);
307 doc.add_bullet_list_item("Third item", 0);
308
309 doc.add_paragraph("");
310
311 doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
312 doc.add_numbered_list_item("First numbered item", 0);
313 doc.add_numbered_list_item("Second numbered item", 0);
314 doc.add_numbered_list_item("Sub-item A", 1);
315 doc.add_numbered_list_item("Sub-item B", 1);
316 doc.add_numbered_list_item("Third numbered item", 0);
317
318 // ── SECTION 4: TAB STOPS ──
319 doc.add_paragraph("");
320 doc.add_paragraph("4. Tab Stops").style("Heading1");
321
322 doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
323 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
324 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
325 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
326 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
327 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
328
329 doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
330 doc.add_paragraph("Item\t\tPrice")
331 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
332 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
333 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
334 doc.add_paragraph("Widget\t\t$19.99")
335 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
336 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
337 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
338 doc.add_paragraph("Gadget\t\t$249.50")
339 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
340 .add_tab_stop_with_leader(
341 TabAlignment::Right,
342 Length::inches(4.0),
343 TabLeader::Underscore,
344 )
345 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
346
347 // ── SECTION 5: TABLES ──
348 doc.add_paragraph("").page_break_before(true);
349 doc.add_paragraph("5. Tables").style("Heading1");
350
351 doc.add_paragraph("5.1 Basic Table").style("Heading2");
352 {
353 let mut tbl = doc
354 .add_table(4, 3)
355 .borders(BorderStyle::Single, 4, "000000");
356 for col in 0..3 {
357 tbl.cell(0, col).unwrap().shading("2E75B6");
358 }
359 tbl.cell(0, 0).unwrap().set_text("Name");
360 tbl.cell(0, 1).unwrap().set_text("Role");
361 tbl.cell(0, 2).unwrap().set_text("Location");
362 tbl.cell(1, 0).unwrap().set_text("Walter White");
363 tbl.cell(1, 1).unwrap().set_text("CEO");
364 tbl.cell(1, 2).unwrap().set_text("Albuquerque");
365 tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
366 tbl.cell(2, 1).unwrap().set_text("CTO");
367 tbl.cell(2, 2).unwrap().set_text("Remote");
368 tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
369 tbl.cell(3, 1).unwrap().set_text("Security");
370 tbl.cell(3, 2).unwrap().set_text("Washington");
371 }
372
373 doc.add_paragraph("");
374
375 doc.add_paragraph("5.2 Column Span & Cell Shading")
376 .style("Heading2");
377 {
378 let mut tbl = doc
379 .add_table(3, 4)
380 .borders(BorderStyle::Single, 4, "000000")
381 .width_pct(100.0);
382 tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
383 tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
384 tbl.cell(1, 0).unwrap().set_text("Region");
385 tbl.cell(1, 0).unwrap().shading("D6E4F0");
386 tbl.cell(1, 1).unwrap().set_text("Q1");
387 tbl.cell(1, 1).unwrap().shading("D6E4F0");
388 tbl.cell(1, 2).unwrap().set_text("Q2");
389 tbl.cell(1, 2).unwrap().shading("D6E4F0");
390 tbl.cell(1, 3).unwrap().set_text("Total");
391 tbl.cell(1, 3).unwrap().shading("D6E4F0");
392 tbl.cell(2, 0).unwrap().set_text("Americas");
393 tbl.cell(2, 1).unwrap().set_text("$2.4M");
394 tbl.cell(2, 2).unwrap().set_text("$2.7M");
395 tbl.cell(2, 3).unwrap().set_text("$5.1M");
396 }
397
398 doc.add_paragraph("");
399
400 doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
401 {
402 let mut tbl = doc
403 .add_table(4, 3)
404 .borders(BorderStyle::Single, 4, "000000");
405 tbl.cell(0, 0).unwrap().set_text("Category");
406 tbl.cell(0, 0).unwrap().shading("E2EFDA");
407 tbl.cell(0, 1).unwrap().set_text("Item");
408 tbl.cell(0, 1).unwrap().shading("E2EFDA");
409 tbl.cell(0, 2).unwrap().set_text("Price");
410 tbl.cell(0, 2).unwrap().shading("E2EFDA");
411 tbl.cell(1, 0).unwrap().set_text("Hardware");
412 tbl.cell(1, 0).unwrap().v_merge_restart();
413 tbl.cell(1, 1).unwrap().set_text("Laptop");
414 tbl.cell(1, 2).unwrap().set_text("$1,200");
415 tbl.cell(2, 0).unwrap().v_merge_continue();
416 tbl.cell(2, 1).unwrap().set_text("Monitor");
417 tbl.cell(2, 2).unwrap().set_text("$450");
418 tbl.cell(3, 0).unwrap().set_text("Software");
419 tbl.cell(3, 1).unwrap().set_text("IDE License");
420 tbl.cell(3, 2).unwrap().set_text("$200/yr");
421 }
422
423 doc.add_paragraph("");
424
425 doc.add_paragraph("5.4 Nested Table").style("Heading2");
426 {
427 let mut tbl = doc
428 .add_table(2, 2)
429 .borders(BorderStyle::Single, 6, "2E75B6");
430 tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
431 tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
432 tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
433 {
434 let mut cell = tbl.cell(1, 1).unwrap();
435 cell.set_text("Nested table below:");
436 let mut nested = cell
437 .add_table(2, 2)
438 .borders(BorderStyle::Single, 2, "FF6600");
439 nested.cell(0, 0).unwrap().set_text("A");
440 nested.cell(0, 1).unwrap().set_text("B");
441 nested.cell(1, 0).unwrap().set_text("C");
442 nested.cell(1, 1).unwrap().set_text("D");
443 }
444 }
445
446 doc.add_paragraph("");
447
448 doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
449 .style("Heading2");
450 {
451 let mut tbl = doc
452 .add_table(2, 3)
453 .borders(BorderStyle::Single, 4, "666666");
454 tbl.row(0).unwrap().height(Length::pt(50.0)).header();
455 tbl.cell(0, 0).unwrap().set_text("Top");
456 tbl.cell(0, 0)
457 .unwrap()
458 .vertical_alignment(VerticalAlignment::Top)
459 .shading("FFF2CC");
460 tbl.cell(0, 1).unwrap().set_text("Center");
461 tbl.cell(0, 1)
462 .unwrap()
463 .vertical_alignment(VerticalAlignment::Center)
464 .shading("D9E2F3");
465 tbl.cell(0, 2).unwrap().set_text("Bottom");
466 tbl.cell(0, 2)
467 .unwrap()
468 .vertical_alignment(VerticalAlignment::Bottom)
469 .shading("E2EFDA");
470 tbl.row(1).unwrap().cant_split();
471 tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
472 tbl.cell(1, 0).unwrap().no_wrap();
473 tbl.cell(1, 1).unwrap().set_text("Fixed width");
474 tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
475 tbl.cell(1, 2).unwrap().set_text("Normal");
476 }
477
478 // ── SECTION 6: IMAGES ──
479 doc.add_paragraph("").page_break_before(true);
480 doc.add_paragraph("6. Images").style("Heading1");
481
482 doc.add_paragraph("6.1 Inline Image").style("Heading2");
483 doc.add_paragraph("A blue gradient image below:");
484 let img = create_sample_png(200, 50, [0, 80, 200]);
485 doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
486
487 doc.add_paragraph("");
488 doc.add_paragraph("6.2 Header Image").style("Heading2");
489 let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
490 doc.set_header_image(
491 &hdr_img,
492 "header_logo.png",
493 Length::inches(2.0),
494 Length::inches(0.2),
495 );
496 doc.add_paragraph("The header now contains an inline image (check the top of this page).");
497
498 doc.add_paragraph("");
499 doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
500
501 // ── SECTION 7: CONTENT MANIPULATION ──
502 doc.add_paragraph("").page_break_before(true);
503 doc.add_paragraph("7. Content Manipulation")
504 .style("Heading1");
505
506 doc.add_paragraph("7.1 Placeholder Replacement")
507 .style("Heading2");
508 doc.add_paragraph("Customer: {{customer}}");
509 doc.add_paragraph("Date: {{date}}");
510 doc.add_paragraph("Reference: {{ref_number}}");
511 {
512 let mut tbl = doc
513 .add_table(2, 2)
514 .borders(BorderStyle::Single, 4, "000000");
515 tbl.cell(0, 0).unwrap().set_text("Project");
516 tbl.cell(0, 0).unwrap().shading("D6E4F0");
517 tbl.cell(0, 1).unwrap().set_text("{{project}}");
518 tbl.cell(1, 0).unwrap().set_text("Status");
519 tbl.cell(1, 0).unwrap().shading("D6E4F0");
520 tbl.cell(1, 1).unwrap().set_text("{{status}}");
521 }
522
523 let mut replacements = HashMap::new();
524 replacements.insert("{{customer}}", "Tensorbee Inc.");
525 replacements.insert("{{date}}", "February 22, 2026");
526 replacements.insert("{{ref_number}}", "TB-2026-001");
527 replacements.insert("{{project}}", "Infrastructure Upgrade");
528 replacements.insert("{{status}}", "In Progress");
529 let n = doc.replace_all(&replacements);
530 doc.add_paragraph(&format!("({n} placeholders replaced above)"));
531
532 doc.add_paragraph("");
533
534 doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
535 doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
536 let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
537 doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
538
539 doc.add_paragraph("");
540
541 doc.add_paragraph("7.3 Content Insertion").style("Heading2");
542 doc.add_paragraph("Section A: First content.");
543 doc.add_paragraph("Section C: Third content.");
544 if let Some(idx) = doc.find_content_index("Section C") {
545 doc.insert_paragraph(
546 idx,
547 "Section B: Inserted between A and C via find_content_index().",
548 );
549 }
550
551 // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
552 doc.add_paragraph("").section_break(SectionBreak::NextPage);
553 doc.add_paragraph("8. Section Breaks & Orientation")
554 .style("Heading1");
555 doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
556
557 {
558 let mut tbl = doc
559 .add_table(3, 7)
560 .borders(BorderStyle::Single, 4, "2E75B6");
561 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
562 for (c, h) in headers.iter().enumerate() {
563 tbl.cell(0, c).unwrap().set_text(h);
564 tbl.cell(0, c).unwrap().shading("2E75B6");
565 }
566 let data = [
567 [
568 "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
569 ],
570 [
571 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
572 ],
573 ];
574 for (r, row) in data.iter().enumerate() {
575 for (c, v) in row.iter().enumerate() {
576 tbl.cell(r + 1, c).unwrap().set_text(v);
577 }
578 }
579 }
580
581 doc.add_paragraph("")
582 .section_break(SectionBreak::NextPage)
583 .section_landscape();
584
585 // ── SECTION 9: CUSTOM STYLES ──
586 doc.add_paragraph("9. Custom Styles").style("Heading1");
587 doc.add_style(
588 StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
589 .based_on("Normal")
590 .paragraph_properties({
591 let mut ppr = rdocx_oxml::properties::CT_PPr::default();
592 ppr.shading = Some(rdocx_oxml::properties::CT_Shd {
593 val: "clear".to_string(),
594 color: None,
595 fill: Some("FFF2CC".to_string()),
596 });
597 ppr
598 })
599 .run_properties({
600 let mut rpr = rdocx_oxml::properties::CT_RPr::default();
601 rpr.bold = Some(true);
602 rpr.color = Some("C45911".to_string());
603 rpr
604 }),
605 );
606 doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
607 .style("CustomHighlight");
608
609 doc.add_paragraph("");
610
611 // ── SECTION 10: DOCUMENT INTELLIGENCE ──
612 doc.add_paragraph("10. Document Intelligence API")
613 .style("Heading1");
614
615 let wc = doc.word_count();
616 let hc = doc.headings().len();
617 let ic = doc.images().len();
618 let lc = doc.links().len();
619 doc.add_paragraph(&format!("Word count: {wc}"));
620 doc.add_paragraph(&format!("Heading count: {hc}"));
621 doc.add_paragraph(&format!("Image count: {ic}"));
622 doc.add_paragraph(&format!("Link count: {lc}"));
623
624 let outline = doc.document_outline();
625 doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
626
627 let issues = doc.audit_accessibility();
628 doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
629 for issue in &issues {
630 doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
631 }
632
633 // ── SECTION 11: DOCUMENT MERGING ──
634 doc.add_paragraph("");
635 doc.add_paragraph("11. Document Merging").style("Heading1");
636 {
637 let mut other = Document::new();
638 other.add_paragraph("This paragraph was merged from another document using append().");
639 doc.append(&other);
640 }
641
642 doc.add_paragraph("");
643
644 // ── SUMMARY ──
645 doc.add_paragraph("Summary of Demonstrated Features")
646 .style("Heading1");
647 let features = [
648 "Page setup: size, margins, header/footer distance, gutter",
649 "Document metadata: title, author, subject, keywords",
650 "Headers and footers: text, images, different first page",
651 "Background images (full-page behind text)",
652 "Table of Contents generation with bookmarks",
653 "Text formatting: bold, italic, underline styles, strike, color, size, font",
654 "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
655 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
656 "Pagination controls: keep-with-next, keep-together, widow control",
657 "Bullet and numbered lists with nesting",
658 "Tab stops with dot/underscore/hyphen leaders",
659 "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
660 "Row properties: header rows, exact height, cant-split",
661 "Cell properties: width, no-wrap, vertical alignment",
662 "Inline images and header images",
663 "Placeholder replacement (single and batch)",
664 "Regex-based find and replace",
665 "Content insertion at specific positions",
666 "Section breaks with mixed portrait/landscape",
667 "Custom paragraph and character styles",
668 "Document intelligence: word count, headings, outline, images, links",
669 "Accessibility audit",
670 "Document merging (append)",
671 "Export: DOCX, PDF, HTML, Markdown",
672 ];
673 for f in &features {
674 doc.add_bullet_list_item(f, 0);
675 }
676 doc.add_paragraph("");
677 doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
678 .alignment(Alignment::Center)
679 .shading("E2EFDA")
680 .border_all(BorderStyle::Single, 2, "00AA00");
681
682 doc
683}
684
685// =============================================================================
686// 2. PROPOSAL DOCUMENT — Deep navy + gold scheme
687// =============================================================================
688fn generate_proposal(_samples_dir: &Path) -> Document {
689 let mut doc = Document::new();
690
691 // Colors: Navy #1B2A4A, Gold #C5922E, Light #F4F1EB
692 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
693 doc.set_margins(
694 Length::inches(1.0),
695 Length::inches(1.0),
696 Length::inches(1.0),
697 Length::inches(1.0),
698 );
699 doc.set_title("AI Platform Modernization Proposal");
700 doc.set_author("Walter White");
701 doc.set_subject("Technology Proposal");
702 doc.set_keywords("proposal, AI, modernization, Tensorbee");
703
704 // Banner header
705 let logo_img = create_logo_png(220, 48);
706 let banner = build_header_banner_xml(
707 "rId1",
708 &BannerOpts {
709 bg_color: "1B2A4A",
710 banner_width: 7772400,
711 banner_height: 914400,
712 logo_width: 2011680,
713 logo_height: 438912,
714 logo_x_offset: 295125,
715 logo_y_offset: 237744,
716 },
717 );
718 doc.set_raw_header_with_images(
719 banner,
720 &[("rId1", &logo_img, "logo.png")],
721 rdocx_oxml::header_footer::HdrFtrType::Default,
722 );
723 doc.set_different_first_page(true);
724 let cover_banner = build_header_banner_xml(
725 "rId1",
726 &BannerOpts {
727 bg_color: "C5922E",
728 banner_width: 7772400,
729 banner_height: 914400,
730 logo_width: 2011680,
731 logo_height: 438912,
732 logo_x_offset: 295125,
733 logo_y_offset: 237744,
734 },
735 );
736 doc.set_raw_header_with_images(
737 cover_banner,
738 &[("rId1", &logo_img, "logo.png")],
739 rdocx_oxml::header_footer::HdrFtrType::First,
740 );
741 doc.set_margins(
742 Length::twips(2292),
743 Length::twips(1440),
744 Length::twips(1440),
745 Length::twips(1440),
746 );
747 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
748 doc.set_footer("Tensorbee — Confidential");
749
750 // Custom styles
751 doc.add_style(
752 StyleBuilder::paragraph("ProposalTitle", "Proposal Title")
753 .based_on("Normal")
754 .run_properties({
755 let mut rpr = rdocx_oxml::properties::CT_RPr::default();
756 rpr.bold = Some(true);
757 rpr.font_ascii = Some("Georgia".to_string());
758 rpr.font_hansi = Some("Georgia".to_string());
759 rpr.sz = Some(rdocx_oxml::HalfPoint(56)); // half-points → 28pt
760 rpr.color = Some("1B2A4A".to_string());
761 rpr
762 }),
763 );
764
765 // ── Cover Page ──
766 for _ in 0..4 {
767 doc.add_paragraph("");
768 }
769 doc.add_paragraph("AI Platform Modernization")
770 .style("ProposalTitle")
771 .alignment(Alignment::Center);
772 doc.add_paragraph("")
773 .alignment(Alignment::Center)
774 .border_bottom(BorderStyle::Single, 8, "C5922E");
775 {
776 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
777 p.add_run("Prepared for Global Financial Corp.")
778 .size(14.0)
779 .color("666666")
780 .italic(true);
781 }
782 {
783 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
784 p.add_run("Prepared by: Walter White, CEO — Tensorbee")
785 .size(12.0)
786 .color("888888");
787 }
788 {
789 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
790 p.add_run("February 22, 2026").size(12.0).color("888888");
791 }
792
793 // ── TOC ──
794 doc.add_paragraph("").page_break_before(true);
795 doc.add_paragraph("Table of Contents").style("Heading1");
796 // We'll add a manual-style TOC since the insert_toc goes at a specific position
797 doc.insert_toc(doc.content_count(), 2);
798
799 // ── Executive Summary ──
800 doc.add_paragraph("").page_break_before(true);
801 doc.add_paragraph("Executive Summary").style("Heading1");
802 doc.add_paragraph(
803 "Tensorbee proposes a comprehensive modernization of Global Financial Corp's AI platform, \
804 transitioning from legacy batch-processing systems to a real-time inference architecture. \
805 This transformation will reduce model deployment time from weeks to hours, improve \
806 prediction accuracy by an estimated 23%, and deliver $4.2M in annual operational savings.",
807 )
808 .first_line_indent(Length::inches(0.3));
809 doc.add_paragraph(
810 "The project spans three phases over 18 months, with a total investment of $2.8M. \
811 Tensorbee brings deep expertise in ML infrastructure, having successfully completed \
812 similar transformations for three Fortune 500 financial institutions.",
813 )
814 .first_line_indent(Length::inches(0.3));
815
816 // Key metrics table
817 doc.add_paragraph("");
818 {
819 let mut tbl = doc
820 .add_table(5, 2)
821 .borders(BorderStyle::Single, 4, "1B2A4A")
822 .width_pct(80.0);
823 tbl.cell(0, 0).unwrap().set_text("Key Metric");
824 tbl.cell(0, 0).unwrap().shading("1B2A4A");
825 tbl.cell(0, 1).unwrap().set_text("Value");
826 tbl.cell(0, 1).unwrap().shading("1B2A4A");
827 let rows = [
828 ("Total Investment", "$2.8M"),
829 ("Annual Savings", "$4.2M"),
830 ("ROI (Year 1)", "150%"),
831 ("Timeline", "18 months"),
832 ];
833 for (i, (k, v)) in rows.iter().enumerate() {
834 tbl.cell(i + 1, 0).unwrap().set_text(k);
835 if i % 2 == 0 {
836 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
837 }
838 tbl.cell(i + 1, 1).unwrap().set_text(v);
839 if i % 2 == 0 {
840 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
841 }
842 }
843 }
844
845 // ── Problem Statement ──
846 doc.add_paragraph("").page_break_before(true);
847 doc.add_paragraph("Problem Statement").style("Heading1");
848 doc.add_paragraph(
849 "Global Financial Corp's current AI infrastructure faces three critical challenges:",
850 );
851 doc.add_numbered_list_item("Deployment Latency — New models take 3-4 weeks to deploy to production, compared to the industry benchmark of 2-3 days.", 0);
852 doc.add_numbered_list_item("Scalability Constraints — The batch processing architecture cannot handle real-time inference demands during peak trading hours.", 0);
853 doc.add_numbered_list_item("Technical Debt — Legacy Python 2.7 codebases and manual deployment scripts create reliability risks and slow iteration speed.", 0);
854
855 // ── Proposed Solution ──
856 doc.add_paragraph("").page_break_before(true);
857 doc.add_paragraph("Proposed Solution").style("Heading1");
858
859 doc.add_paragraph("Phase 1: Foundation (Months 1-6)")
860 .style("Heading2");
861 doc.add_bullet_list_item("Deploy Kubernetes-based ML serving infrastructure", 0);
862 doc.add_bullet_list_item("Implement CI/CD pipeline for model deployment", 0);
863 doc.add_bullet_list_item("Migrate top 5 production models to new platform", 0);
864
865 doc.add_paragraph("Phase 2: Expansion (Months 7-12)")
866 .style("Heading2");
867 doc.add_bullet_list_item(
868 "Real-time feature engineering pipeline (Apache Kafka + Flink)",
869 0,
870 );
871 doc.add_bullet_list_item("A/B testing framework for model variants", 0);
872 doc.add_bullet_list_item("Automated model monitoring and drift detection", 0);
873
874 doc.add_paragraph("Phase 3: Optimization (Months 13-18)")
875 .style("Heading2");
876 doc.add_bullet_list_item("GPU inference optimization (TensorRT/ONNX Runtime)", 0);
877 doc.add_bullet_list_item("Multi-region deployment for latency reduction", 0);
878 doc.add_bullet_list_item(
879 "Self-service model deployment portal for data science teams",
880 0,
881 );
882
883 // ── Budget ──
884 doc.add_paragraph("Budget Breakdown").style("Heading1");
885 {
886 let mut tbl = doc
887 .add_table(6, 3)
888 .borders(BorderStyle::Single, 4, "1B2A4A")
889 .width_pct(100.0);
890 tbl.cell(0, 0).unwrap().set_text("Category");
891 tbl.cell(0, 0).unwrap().shading("1B2A4A");
892 tbl.cell(0, 1).unwrap().set_text("Cost");
893 tbl.cell(0, 1).unwrap().shading("1B2A4A");
894 tbl.cell(0, 2).unwrap().set_text("Notes");
895 tbl.cell(0, 2).unwrap().shading("1B2A4A");
896 let items = [
897 (
898 "Infrastructure",
899 "$450,000",
900 "Cloud compute + GPU instances",
901 ),
902 ("Engineering Services", "$1,600,000", "12 FTE x 18 months"),
903 (
904 "Software Licenses",
905 "$350,000",
906 "Kafka, monitoring, CI/CD tools",
907 ),
908 (
909 "Training & Enablement",
910 "$200,000",
911 "Team training, documentation",
912 ),
913 ("Contingency (10%)", "$200,000", "Risk buffer"),
914 ];
915 for (i, (cat, cost, note)) in items.iter().enumerate() {
916 tbl.cell(i + 1, 0).unwrap().set_text(cat);
917 tbl.cell(i + 1, 1).unwrap().set_text(cost);
918 tbl.cell(i + 1, 2).unwrap().set_text(note);
919 if i % 2 == 0 {
920 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
921 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
922 tbl.cell(i + 1, 2).unwrap().shading("F4F1EB");
923 }
924 }
925 }
926
927 // ── Closing ──
928 doc.add_paragraph("");
929 doc.add_paragraph("Next Steps").style("Heading1");
930 doc.add_numbered_list_item(
931 "Schedule technical deep-dive with GFC engineering team (Week 1)",
932 0,
933 );
934 doc.add_numbered_list_item("Finalize scope and sign SOW (Week 2-3)", 0);
935 doc.add_numbered_list_item("Kick off Phase 1 with joint planning session (Week 4)", 0);
936
937 doc.add_paragraph("");
938 {
939 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
940 p.add_run("Walter White")
941 .bold(true)
942 .size(14.0)
943 .color("1B2A4A");
944 }
945 {
946 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
947 p.add_run("CEO, Tensorbee | walter@tensorbee.com")
948 .size(10.0)
949 .color("888888");
950 }
951
952 doc
953}
954
955// =============================================================================
956// 3. QUOTE / BILL OF MATERIALS — Teal + orange scheme
957// =============================================================================
958fn generate_quote(_samples_dir: &Path) -> Document {
959 let mut doc = Document::new();
960
961 // Colors: Teal #008B8B, Dark #1A3C3C, Orange #E07020, Light #F0F8F8
962 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
963 doc.set_margins(
964 Length::inches(0.75),
965 Length::inches(0.75),
966 Length::inches(0.75),
967 Length::inches(0.75),
968 );
969 doc.set_title("Quotation QT-2026-0147");
970 doc.set_author("Walter White");
971 doc.set_keywords("quote, BOM, Tensorbee");
972
973 doc.set_header("Tensorbee — Quotation");
974 doc.set_footer("QT-2026-0147 | Page");
975
976 // ── Header Block ──
977 {
978 let mut p = doc.add_paragraph("").alignment(Alignment::Left);
979 p.add_run("TENSORBEE")
980 .bold(true)
981 .size(28.0)
982 .color("008B8B")
983 .font("Helvetica");
984 }
985 doc.add_paragraph("123 Innovation Drive, Suite 400")
986 .alignment(Alignment::Left);
987 doc.add_paragraph("San Francisco, CA 94105 | +1 (415) 555-0199")
988 .alignment(Alignment::Left);
989 doc.add_paragraph("")
990 .border_bottom(BorderStyle::Single, 8, "008B8B");
991
992 // Quote meta
993 doc.add_paragraph("");
994 {
995 let mut tbl = doc.add_table(4, 4).width_pct(100.0);
996 tbl.cell(0, 0).unwrap().set_text("QUOTATION");
997 tbl.cell(0, 0).unwrap().shading("008B8B").grid_span(2);
998 tbl.cell(0, 2).unwrap().set_text("DATE");
999 tbl.cell(0, 2).unwrap().shading("008B8B");
1000 tbl.cell(0, 3).unwrap().set_text("VALID UNTIL");
1001 tbl.cell(0, 3).unwrap().shading("008B8B");
1002 tbl.cell(1, 0).unwrap().set_text("Quote #:");
1003 tbl.cell(1, 1).unwrap().set_text("QT-2026-0147");
1004 tbl.cell(1, 2).unwrap().set_text("Feb 22, 2026");
1005 tbl.cell(1, 3).unwrap().set_text("Mar 22, 2026");
1006 tbl.cell(2, 0).unwrap().set_text("Prepared by:");
1007 tbl.cell(2, 1).unwrap().set_text("Walter White");
1008 tbl.cell(2, 2).unwrap().set_text("Payment:");
1009 tbl.cell(2, 3).unwrap().set_text("Net 30");
1010 tbl.cell(3, 0).unwrap().set_text("Customer:");
1011 tbl.cell(3, 1).unwrap().set_text("Meridian Dynamics LLC");
1012 tbl.cell(3, 2).unwrap().set_text("Currency:");
1013 tbl.cell(3, 3).unwrap().set_text("USD");
1014 }
1015
1016 doc.add_paragraph("");
1017
1018 // ── Bill of Materials ──
1019 {
1020 let mut p = doc.add_paragraph("");
1021 p.add_run("Bill of Materials")
1022 .bold(true)
1023 .size(16.0)
1024 .color("1A3C3C");
1025 }
1026 doc.add_paragraph("");
1027
1028 {
1029 let mut tbl = doc
1030 .add_table(10, 6)
1031 .borders(BorderStyle::Single, 2, "008B8B")
1032 .width_pct(100.0)
1033 .layout_fixed();
1034 // Headers
1035 let hdrs = [
1036 "#",
1037 "Part Number",
1038 "Description",
1039 "Qty",
1040 "Unit Price",
1041 "Total",
1042 ];
1043 for (c, h) in hdrs.iter().enumerate() {
1044 tbl.cell(0, c).unwrap().set_text(h);
1045 tbl.cell(0, c).unwrap().shading("008B8B");
1046 }
1047
1048 let items: Vec<(&str, &str, &str, &str, &str)> = vec![
1049 (
1050 "TB-GPU-A100",
1051 "NVIDIA A100 80GB GPU",
1052 "4",
1053 "$12,500.00",
1054 "$50,000.00",
1055 ),
1056 (
1057 "TB-SRV-R750",
1058 "Dell R750xa Server Chassis",
1059 "2",
1060 "$8,200.00",
1061 "$16,400.00",
1062 ),
1063 (
1064 "TB-RAM-256G",
1065 "256GB DDR5 ECC Memory Module",
1066 "8",
1067 "$890.00",
1068 "$7,120.00",
1069 ),
1070 (
1071 "TB-SSD-3840",
1072 "3.84TB NVMe U.2 SSD",
1073 "8",
1074 "$1,150.00",
1075 "$9,200.00",
1076 ),
1077 (
1078 "TB-NET-CX7",
1079 "ConnectX-7 200GbE NIC",
1080 "4",
1081 "$1,800.00",
1082 "$7,200.00",
1083 ),
1084 (
1085 "TB-SW-48P",
1086 "48-Port 100GbE Switch",
1087 "1",
1088 "$22,000.00",
1089 "$22,000.00",
1090 ),
1091 (
1092 "TB-CAB-RACK",
1093 "42U Server Rack + PDU",
1094 "1",
1095 "$4,500.00",
1096 "$4,500.00",
1097 ),
1098 (
1099 "TB-SVC-INST",
1100 "Installation & Configuration",
1101 "1",
1102 "$8,500.00",
1103 "$8,500.00",
1104 ),
1105 (
1106 "TB-SVC-SUPP",
1107 "3-Year Premium Support",
1108 "1",
1109 "$15,000.00",
1110 "$15,000.00",
1111 ),
1112 ];
1113
1114 for (i, (pn, desc, qty, unit, total)) in items.iter().enumerate() {
1115 let row = i + 1;
1116 tbl.cell(row, 0).unwrap().set_text(&format!("{}", i + 1));
1117 tbl.cell(row, 1).unwrap().set_text(pn);
1118 tbl.cell(row, 2).unwrap().set_text(desc);
1119 tbl.cell(row, 3).unwrap().set_text(qty);
1120 tbl.cell(row, 4).unwrap().set_text(unit);
1121 tbl.cell(row, 5).unwrap().set_text(total);
1122 if i % 2 == 0 {
1123 for c in 0..6 {
1124 tbl.cell(row, c).unwrap().shading("F0F8F8");
1125 }
1126 }
1127 }
1128 }
1129
1130 doc.add_paragraph("");
1131
1132 // ── Totals ──
1133 {
1134 let mut tbl = doc
1135 .add_table(4, 2)
1136 .borders(BorderStyle::Single, 2, "008B8B")
1137 .width(Length::inches(3.5))
1138 .alignment(Alignment::Right);
1139 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1140 tbl.cell(0, 1).unwrap().set_text("$139,920.00");
1141 tbl.cell(1, 0).unwrap().set_text("Shipping & Handling");
1142 tbl.cell(1, 1).unwrap().set_text("$2,500.00");
1143 tbl.cell(2, 0).unwrap().set_text("Tax (8.625%)");
1144 tbl.cell(2, 1).unwrap().set_text("$12,068.10");
1145 tbl.cell(3, 0).unwrap().set_text("TOTAL");
1146 tbl.cell(3, 0).unwrap().shading("E07020");
1147 tbl.cell(3, 1).unwrap().set_text("$154,488.10");
1148 tbl.cell(3, 1).unwrap().shading("E07020");
1149 }
1150
1151 doc.add_paragraph("");
1152
1153 // ── Terms & Conditions ──
1154 {
1155 let mut p = doc.add_paragraph("");
1156 p.add_run("Terms & Conditions")
1157 .bold(true)
1158 .size(14.0)
1159 .color("1A3C3C");
1160 }
1161 doc.add_numbered_list_item(
1162 "This quotation is valid for 30 calendar days from the date of issue.",
1163 0,
1164 );
1165 doc.add_numbered_list_item(
1166 "All prices are in USD and exclusive of applicable taxes unless stated.",
1167 0,
1168 );
1169 doc.add_numbered_list_item("Standard lead time is 4-6 weeks from PO receipt.", 0);
1170 doc.add_numbered_list_item(
1171 "Payment terms: Net 30 from invoice date. 2% discount for payment within 10 days.",
1172 0,
1173 );
1174 doc.add_numbered_list_item(
1175 "Warranty: 3-year manufacturer warranty on all hardware components.",
1176 0,
1177 );
1178 doc.add_numbered_list_item(
1179 "Returns subject to 15% restocking fee if initiated after 14 days.",
1180 0,
1181 );
1182
1183 doc.add_paragraph("");
1184 doc.add_paragraph("")
1185 .border_bottom(BorderStyle::Single, 4, "008B8B");
1186 doc.add_paragraph("");
1187 {
1188 let mut p = doc.add_paragraph("");
1189 p.add_run("Accepted by: ").bold(true);
1190 p.add_run("___________________________ Date: ___________");
1191 }
1192 {
1193 let mut p = doc.add_paragraph("");
1194 p.add_run("Print Name: ").bold(true);
1195 p.add_run("___________________________ Title: ___________");
1196 }
1197
1198 doc
1199}
1200
1201// =============================================================================
1202// 4. INVOICE — Crimson + charcoal scheme
1203// =============================================================================
1204fn generate_invoice(_samples_dir: &Path) -> Document {
1205 let mut doc = Document::new();
1206
1207 // Colors: Crimson #B22222, Charcoal #333333, Light #FAF0F0
1208 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1209 doc.set_margins(
1210 Length::inches(0.75),
1211 Length::inches(0.75),
1212 Length::inches(0.75),
1213 Length::inches(0.75),
1214 );
1215 doc.set_title("Invoice INV-2026-0392");
1216 doc.set_author("Walter White");
1217 doc.set_keywords("invoice, Tensorbee");
1218
1219 doc.set_footer("Tensorbee — Thank you for your business!");
1220
1221 // ── Company & Invoice Header ──
1222 {
1223 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1224 // Company name left, INVOICE right
1225 {
1226 let mut cell = tbl.cell(0, 0).unwrap();
1227 let mut p = cell.add_paragraph("");
1228 p.add_run("TENSORBEE")
1229 .bold(true)
1230 .size(32.0)
1231 .color("B22222")
1232 .font("Helvetica");
1233 }
1234 {
1235 let mut cell = tbl.cell(0, 1).unwrap();
1236 let mut p = cell.add_paragraph("");
1237 p.add_run("INVOICE").bold(true).size(32.0).color("333333");
1238 }
1239 tbl.cell(1, 0)
1240 .unwrap()
1241 .set_text("123 Innovation Drive, Suite 400");
1242 tbl.cell(1, 1).unwrap().set_text("Invoice #: INV-2026-0392");
1243 tbl.cell(2, 0).unwrap().set_text("San Francisco, CA 94105");
1244 tbl.cell(2, 1).unwrap().set_text("Date: February 22, 2026");
1245 tbl.cell(3, 0).unwrap().set_text("walter@tensorbee.com");
1246 tbl.cell(3, 1).unwrap().set_text("Due Date: March 24, 2026");
1247 }
1248
1249 doc.add_paragraph("")
1250 .border_bottom(BorderStyle::Thick, 8, "B22222");
1251 doc.add_paragraph("");
1252
1253 // ── Bill To / Ship To ──
1254 {
1255 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1256 tbl.cell(0, 0).unwrap().set_text("BILL TO");
1257 tbl.cell(0, 0).unwrap().shading("B22222");
1258 tbl.cell(0, 1).unwrap().set_text("SHIP TO");
1259 tbl.cell(0, 1).unwrap().shading("B22222");
1260 tbl.cell(1, 0).unwrap().set_text("Meridian Dynamics LLC");
1261 tbl.cell(1, 1).unwrap().set_text("Meridian Dynamics LLC");
1262 tbl.cell(2, 0).unwrap().set_text("456 Enterprise Blvd");
1263 tbl.cell(2, 1).unwrap().set_text("Attn: Server Room B");
1264 tbl.cell(3, 0).unwrap().set_text("Austin, TX 78701");
1265 tbl.cell(3, 1)
1266 .unwrap()
1267 .set_text("456 Enterprise Blvd, Austin, TX 78701");
1268 }
1269
1270 doc.add_paragraph("");
1271
1272 // ── Line Items ──
1273 {
1274 let mut tbl = doc
1275 .add_table(8, 5)
1276 .borders(BorderStyle::Single, 2, "B22222")
1277 .width_pct(100.0);
1278 let hdrs = ["Description", "Qty", "Unit Price", "Tax", "Amount"];
1279 for (c, h) in hdrs.iter().enumerate() {
1280 tbl.cell(0, c).unwrap().set_text(h);
1281 tbl.cell(0, c).unwrap().shading("B22222");
1282 }
1283 let items = [
1284 (
1285 "ML Infrastructure Setup — Phase 1",
1286 "1",
1287 "$45,000.00",
1288 "$3,881.25",
1289 "$48,881.25",
1290 ),
1291 (
1292 "Data Pipeline Development (160 hrs)",
1293 "160",
1294 "$225.00",
1295 "$3,105.00",
1296 "$39,105.00",
1297 ),
1298 (
1299 "GPU Cluster Configuration",
1300 "1",
1301 "$12,000.00",
1302 "$1,035.00",
1303 "$13,035.00",
1304 ),
1305 (
1306 "API Gateway Implementation",
1307 "1",
1308 "$18,500.00",
1309 "$1,595.63",
1310 "$20,095.63",
1311 ),
1312 (
1313 "Load Testing & QA (80 hrs)",
1314 "80",
1315 "$195.00",
1316 "$1,345.50",
1317 "$16,945.50",
1318 ),
1319 (
1320 "Documentation & Training",
1321 "1",
1322 "$8,000.00",
1323 "$690.00",
1324 "$8,690.00",
1325 ),
1326 (
1327 "Project Management (3 months)",
1328 "3",
1329 "$6,500.00",
1330 "$1,679.63",
1331 "$21,179.63",
1332 ),
1333 ];
1334 for (i, (desc, qty, unit, tax, amt)) in items.iter().enumerate() {
1335 let r = i + 1;
1336 tbl.cell(r, 0).unwrap().set_text(desc);
1337 tbl.cell(r, 1).unwrap().set_text(qty);
1338 tbl.cell(r, 2).unwrap().set_text(unit);
1339 tbl.cell(r, 3).unwrap().set_text(tax);
1340 tbl.cell(r, 4).unwrap().set_text(amt);
1341 if i % 2 == 0 {
1342 for c in 0..5 {
1343 tbl.cell(r, c).unwrap().shading("FAF0F0");
1344 }
1345 }
1346 }
1347 }
1348
1349 doc.add_paragraph("");
1350
1351 // ── Totals ──
1352 {
1353 let mut tbl = doc
1354 .add_table(4, 2)
1355 .borders(BorderStyle::Single, 2, "B22222")
1356 .width(Length::inches(3.0))
1357 .alignment(Alignment::Right);
1358 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1359 tbl.cell(0, 1).unwrap().set_text("$154,600.00");
1360 tbl.cell(1, 0).unwrap().set_text("Tax (8.625%)");
1361 tbl.cell(1, 1).unwrap().set_text("$13,334.25");
1362 tbl.cell(2, 0).unwrap().set_text("Discount (5%)");
1363 tbl.cell(2, 1).unwrap().set_text("-$7,730.00");
1364 tbl.cell(3, 0).unwrap().set_text("AMOUNT DUE");
1365 tbl.cell(3, 0).unwrap().shading("B22222");
1366 tbl.cell(3, 1).unwrap().set_text("$160,204.25");
1367 tbl.cell(3, 1).unwrap().shading("B22222");
1368 }
1369
1370 doc.add_paragraph("");
1371 doc.add_paragraph("");
1372
1373 // ── Payment Details ──
1374 {
1375 let mut p = doc.add_paragraph("");
1376 p.add_run("Payment Details")
1377 .bold(true)
1378 .size(14.0)
1379 .color("333333");
1380 }
1381 doc.add_paragraph("Bank: Silicon Valley Bank")
1382 .indent_left(Length::inches(0.3));
1383 doc.add_paragraph("Account: Tensorbee Inc. — 0847-2953-1120")
1384 .indent_left(Length::inches(0.3));
1385 doc.add_paragraph("Routing: 121140399")
1386 .indent_left(Length::inches(0.3));
1387 doc.add_paragraph("Swift: SVBKUS6S")
1388 .indent_left(Length::inches(0.3));
1389
1390 doc.add_paragraph("");
1391
1392 doc.add_paragraph("Please include invoice number INV-2026-0392 in the payment reference.")
1393 .shading("FAF0F0")
1394 .border_all(BorderStyle::Single, 2, "B22222");
1395
1396 doc
1397}
1398
1399// =============================================================================
1400// 5. REPORT — Forest green + earth tones, with images & hierarchical sections
1401// =============================================================================
1402fn generate_report(_samples_dir: &Path) -> Document {
1403 let mut doc = Document::new();
1404
1405 // Colors: Forest #2D5016, Sage #6B8E23, Earth #8B7355, Cream #FFFAF0
1406 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1407 doc.set_margins(
1408 Length::inches(1.0),
1409 Length::inches(1.0),
1410 Length::inches(1.0),
1411 Length::inches(1.0),
1412 );
1413 doc.set_title("Q4 2025 Environmental Impact Report");
1414 doc.set_author("Walter White");
1415 doc.set_subject("Quarterly Environmental Report");
1416 doc.set_keywords("environment, sustainability, report, Tensorbee");
1417
1418 doc.set_different_first_page(true);
1419 doc.set_first_page_header("");
1420 doc.set_header("Tensorbee — Q4 2025 Environmental Impact Report");
1421 doc.set_footer("CONFIDENTIAL — Page");
1422
1423 // ── Cover ──
1424 let cover_bg = create_sample_png(612, 792, [20, 50, 15]);
1425 doc.add_background_image(&cover_bg, "report_cover.png");
1426
1427 for _ in 0..5 {
1428 doc.add_paragraph("");
1429 }
1430 {
1431 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1432 p.add_run("Q4 2025")
1433 .bold(true)
1434 .size(48.0)
1435 .color("FFFFFF")
1436 .font("Georgia");
1437 }
1438 {
1439 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1440 p.add_run("Environmental Impact Report")
1441 .size(24.0)
1442 .color("90EE90")
1443 .font("Georgia")
1444 .italic(true);
1445 }
1446 doc.add_paragraph("");
1447 {
1448 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1449 p.add_run("Tensorbee — Sustainability Division")
1450 .size(14.0)
1451 .color("C0C0C0");
1452 }
1453 {
1454 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1455 p.add_run("Prepared by Walter White, Chief Sustainability Officer")
1456 .size(11.0)
1457 .color("AAAAAA");
1458 }
1459
1460 // ── TOC ──
1461 doc.add_paragraph("").page_break_before(true);
1462 doc.insert_toc(doc.content_count(), 3);
1463
1464 // ── Section 1: Executive Overview ──
1465 doc.add_paragraph("").page_break_before(true);
1466 doc.add_paragraph("Executive Overview").style("Heading1");
1467 doc.add_paragraph(
1468 "This report presents Tensorbee's environmental performance for Q4 2025. Our \
1469 sustainability initiatives have yielded a 34% reduction in carbon emissions compared \
1470 to Q4 2024, exceeding our target of 25%. Key achievements include the transition to \
1471 100% renewable energy in our primary data centers and the launch of our carbon offset \
1472 marketplace.",
1473 )
1474 .first_line_indent(Length::inches(0.3));
1475
1476 // Image: performance chart
1477 let chart_img = create_chart_png(400, 200);
1478 doc.add_paragraph("");
1479 doc.add_picture(
1480 &chart_img,
1481 "performance_chart.png",
1482 Length::inches(5.0),
1483 Length::inches(2.5),
1484 )
1485 .alignment(Alignment::Center);
1486 {
1487 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1488 p.add_run("Figure 1: Quarterly Carbon Emissions (tonnes CO2e)")
1489 .italic(true)
1490 .size(9.0)
1491 .color("666666");
1492 }
1493
1494 // ── Section 2: Energy Consumption ──
1495 doc.add_paragraph("").page_break_before(true);
1496 doc.add_paragraph("Energy Consumption").style("Heading1");
1497
1498 doc.add_paragraph("Data Center Operations")
1499 .style("Heading2");
1500 doc.add_paragraph(
1501 "Our three primary data centers consumed a combined 4.2 GWh in Q4 2025, \
1502 a 12% reduction from Q3 2025 driven by improved cooling efficiency and \
1503 server consolidation.",
1504 );
1505
1506 // Data table
1507 {
1508 let mut tbl = doc
1509 .add_table(5, 4)
1510 .borders(BorderStyle::Single, 4, "2D5016")
1511 .width_pct(100.0);
1512 let hdrs = ["Data Center", "Capacity (MW)", "Usage (GWh)", "PUE"];
1513 for (c, h) in hdrs.iter().enumerate() {
1514 tbl.cell(0, c).unwrap().set_text(h);
1515 tbl.cell(0, c).unwrap().shading("2D5016");
1516 }
1517 let rows = [
1518 ("San Francisco (Primary)", "3.2", "1.8", "1.12"),
1519 ("Dublin (EU)", "2.1", "1.2", "1.18"),
1520 ("Singapore (APAC)", "1.8", "1.2", "1.24"),
1521 ("TOTAL", "7.1", "4.2", "1.17 avg"),
1522 ];
1523 for (i, (dc, cap, use_, pue)) in rows.iter().enumerate() {
1524 tbl.cell(i + 1, 0).unwrap().set_text(dc);
1525 tbl.cell(i + 1, 1).unwrap().set_text(cap);
1526 tbl.cell(i + 1, 2).unwrap().set_text(use_);
1527 tbl.cell(i + 1, 3).unwrap().set_text(pue);
1528 if i == 3 {
1529 for c in 0..4 {
1530 tbl.cell(i + 1, c).unwrap().shading("E2EFDA");
1531 }
1532 }
1533 }
1534 }
1535
1536 doc.add_paragraph("");
1537
1538 doc.add_paragraph("Renewable Energy Mix").style("Heading2");
1539 doc.add_paragraph("Breakdown of energy sources across all facilities:");
1540
1541 doc.add_bullet_list_item("Solar PV: 42% (1.76 GWh)", 0);
1542 doc.add_bullet_list_item("Wind Power Purchase Agreements: 38% (1.60 GWh)", 0);
1543 doc.add_bullet_list_item("Hydroelectric: 12% (0.50 GWh)", 0);
1544 doc.add_bullet_list_item("Grid (non-renewable): 8% (0.34 GWh)", 0);
1545
1546 // ── Section 3: Water & Waste ──
1547 doc.add_paragraph("Water & Waste Management")
1548 .style("Heading1");
1549
1550 doc.add_paragraph("Water Usage").style("Heading2");
1551 doc.add_paragraph(
1552 "Total water consumption was 12.4 million gallons, a 15% reduction from Q3. \
1553 Our closed-loop cooling systems now recycle 78% of water used in cooling operations.",
1554 );
1555
1556 doc.add_paragraph("Waste Reduction").style("Heading2");
1557 doc.add_paragraph("E-waste management results for Q4:")
1558 .keep_with_next(true);
1559 {
1560 let mut tbl = doc
1561 .add_table(4, 3)
1562 .borders(BorderStyle::Single, 2, "6B8E23")
1563 .width_pct(80.0);
1564 tbl.cell(0, 0).unwrap().set_text("Category");
1565 tbl.cell(0, 0).unwrap().shading("6B8E23");
1566 tbl.cell(0, 1).unwrap().set_text("Weight (kg)");
1567 tbl.cell(0, 1).unwrap().shading("6B8E23");
1568 tbl.cell(0, 2).unwrap().set_text("Recycled %");
1569 tbl.cell(0, 2).unwrap().shading("6B8E23");
1570 let rows = [
1571 ("Server Hardware", "2,450", "94%"),
1572 ("Networking Equipment", "820", "91%"),
1573 ("Storage Media", "340", "99%"),
1574 ];
1575 for (i, (cat, wt, pct)) in rows.iter().enumerate() {
1576 tbl.cell(i + 1, 0).unwrap().set_text(cat);
1577 tbl.cell(i + 1, 1).unwrap().set_text(wt);
1578 tbl.cell(i + 1, 2).unwrap().set_text(pct);
1579 }
1580 }
1581
1582 // ── Section 4: Initiatives ──
1583 doc.add_paragraph("").page_break_before(true);
1584 doc.add_paragraph("Q1 2026 Initiatives").style("Heading1");
1585
1586 doc.add_paragraph("Planned Programs").style("Heading2");
1587 doc.add_numbered_list_item(
1588 "Deploy on-site battery storage at SF data center (2 MWh capacity)",
1589 0,
1590 );
1591 doc.add_numbered_list_item("Pilot immersion cooling in Dublin facility", 0);
1592 doc.add_numbered_list_item("Launch employee commute carbon offset program", 0);
1593 doc.add_numbered_list_item("Achieve ISO 14001 certification for Singapore facility", 0);
1594
1595 doc.add_paragraph("Investment Targets").style("Heading2");
1596 doc.add_paragraph("Sustainability CapEx allocation for FY2026:")
1597 .keep_with_next(true);
1598 {
1599 let mut tbl = doc
1600 .add_table(5, 2)
1601 .borders(BorderStyle::Single, 4, "2D5016");
1602 tbl.cell(0, 0).unwrap().set_text("Initiative");
1603 tbl.cell(0, 0).unwrap().shading("2D5016");
1604 tbl.cell(0, 1).unwrap().set_text("Budget");
1605 tbl.cell(0, 1).unwrap().shading("2D5016");
1606 let rows = [
1607 ("Battery Storage", "$1.2M"),
1608 ("Immersion Cooling Pilot", "$800K"),
1609 ("Solar Panel Expansion", "$2.1M"),
1610 ("Carbon Credits", "$500K"),
1611 ];
1612 for (i, (init, budget)) in rows.iter().enumerate() {
1613 tbl.cell(i + 1, 0).unwrap().set_text(init);
1614 tbl.cell(i + 1, 1).unwrap().set_text(budget);
1615 if i % 2 == 0 {
1616 tbl.cell(i + 1, 0).unwrap().shading("FFFAF0");
1617 tbl.cell(i + 1, 1).unwrap().shading("FFFAF0");
1618 }
1619 }
1620 }
1621
1622 // Image: sustainability roadmap
1623 let roadmap_img = create_sample_png(500, 100, [40, 80, 30]);
1624 doc.add_paragraph("");
1625 doc.add_picture(
1626 &roadmap_img,
1627 "roadmap.png",
1628 Length::inches(6.0),
1629 Length::inches(1.2),
1630 )
1631 .alignment(Alignment::Center);
1632 {
1633 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1634 p.add_run("Figure 2: 2026 Sustainability Roadmap")
1635 .italic(true)
1636 .size(9.0)
1637 .color("666666");
1638 }
1639
1640 doc.add_paragraph("");
1641 doc.add_paragraph(
1642 "For questions about this report, contact Walter White at sustainability@tensorbee.com.",
1643 )
1644 .shading("FFFAF0")
1645 .border_all(BorderStyle::Single, 2, "2D5016");
1646
1647 doc
1648}
1649
1650// =============================================================================
1651// 6. LETTER — Slate blue + silver scheme
1652// =============================================================================
1653fn generate_letter(_samples_dir: &Path) -> Document {
1654 let mut doc = Document::new();
1655
1656 // Colors: Slate #4A5568, Blue accent #3182CE
1657 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1658 doc.set_margins(
1659 Length::inches(1.25),
1660 Length::inches(1.25),
1661 Length::inches(1.25),
1662 Length::inches(1.25),
1663 );
1664 doc.set_title("Business Letter");
1665 doc.set_author("Walter White");
1666
1667 // Banner header using raw XML
1668 let logo_img = create_logo_png(220, 48);
1669 let banner = build_header_banner_xml(
1670 "rId1",
1671 &BannerOpts {
1672 bg_color: "4A5568",
1673 banner_width: 7772400,
1674 banner_height: 731520, // ~0.8"
1675 logo_width: 2011680,
1676 logo_height: 438912,
1677 logo_x_offset: 295125,
1678 logo_y_offset: 146304,
1679 },
1680 );
1681 doc.set_raw_header_with_images(
1682 banner,
1683 &[("rId1", &logo_img, "logo.png")],
1684 rdocx_oxml::header_footer::HdrFtrType::Default,
1685 );
1686 doc.set_margins(
1687 Length::twips(2000),
1688 Length::twips(1800),
1689 Length::twips(1440),
1690 Length::twips(1800),
1691 );
1692 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
1693
1694 // Footer with contact
1695 doc.set_footer(
1696 "Tensorbee | 123 Innovation Drive, Suite 400 | San Francisco, CA 94105 | tensorbee.com",
1697 );
1698
1699 // ── Sender Address ──
1700 doc.add_paragraph("Walter White");
1701 doc.add_paragraph("Chief Executive Officer");
1702 doc.add_paragraph("Tensorbee");
1703 doc.add_paragraph("123 Innovation Drive, Suite 400");
1704 doc.add_paragraph("San Francisco, CA 94105");
1705 doc.add_paragraph("");
1706 doc.add_paragraph("February 22, 2026");
1707
1708 doc.add_paragraph("");
1709
1710 // ── Recipient ──
1711 doc.add_paragraph("Ms. Sarah Chen");
1712 doc.add_paragraph("VP of Engineering");
1713 doc.add_paragraph("Meridian Dynamics LLC");
1714 doc.add_paragraph("456 Enterprise Boulevard");
1715 doc.add_paragraph("Austin, TX 78701");
1716
1717 doc.add_paragraph("");
1718
1719 // ── Subject ──
1720 {
1721 let mut p = doc.add_paragraph("");
1722 p.add_run("Re: Strategic Technology Partnership — Phase 2 Expansion")
1723 .bold(true);
1724 }
1725
1726 doc.add_paragraph("");
1727
1728 // ── Body ──
1729 doc.add_paragraph("Dear Ms. Chen,");
1730 doc.add_paragraph("");
1731
1732 doc.add_paragraph(
1733 "I am writing to express Tensorbee's enthusiasm for expanding our strategic technology \
1734 partnership with Meridian Dynamics. Following the successful completion of Phase 1, \
1735 which delivered a 40% improvement in your ML inference pipeline throughput, we believe \
1736 the foundation is firmly established for an ambitious Phase 2 engagement.",
1737 )
1738 .first_line_indent(Length::inches(0.5));
1739
1740 doc.add_paragraph(
1741 "During our recent executive review, your team highlighted three priority areas for \
1742 the next phase of collaboration:",
1743 )
1744 .first_line_indent(Length::inches(0.5));
1745
1746 doc.add_numbered_list_item(
1747 "Real-time anomaly detection for your financial transaction monitoring system, \
1748 targeting sub-100ms latency at 50,000 transactions per second.",
1749 0,
1750 );
1751 doc.add_numbered_list_item(
1752 "Federated learning infrastructure to enable model training across your distributed \
1753 data centers without centralizing sensitive financial data.",
1754 0,
1755 );
1756 doc.add_numbered_list_item(
1757 "MLOps automation to reduce your model deployment cycle from the current 5 days \
1758 to under 4 hours.",
1759 0,
1760 );
1761
1762 doc.add_paragraph(
1763 "Our engineering team has prepared a detailed technical proposal addressing each of \
1764 these areas. We have allocated a dedicated team of eight senior engineers, led by \
1765 our CTO, to ensure continuity with the Phase 1 team your organization has already \
1766 built a productive working relationship with.",
1767 )
1768 .first_line_indent(Length::inches(0.5));
1769
1770 doc.add_paragraph(
1771 "I would welcome the opportunity to present our Phase 2 proposal in person. My \
1772 assistant will reach out to coordinate a meeting at your convenience during the \
1773 first week of March.",
1774 )
1775 .first_line_indent(Length::inches(0.5));
1776
1777 doc.add_paragraph("");
1778
1779 doc.add_paragraph("Warm regards,");
1780 doc.add_paragraph("");
1781 doc.add_paragraph("");
1782
1783 // Signature line
1784 doc.add_paragraph("")
1785 .border_bottom(BorderStyle::Single, 4, "4A5568");
1786 {
1787 let mut p = doc.add_paragraph("");
1788 p.add_run("Walter White").bold(true).size(12.0);
1789 }
1790 {
1791 let mut p = doc.add_paragraph("");
1792 p.add_run("Chief Executive Officer, Tensorbee")
1793 .italic(true)
1794 .size(10.0)
1795 .color("666666");
1796 }
1797 {
1798 let mut p = doc.add_paragraph("");
1799 p.add_run("walter@tensorbee.com | +1 (415) 555-0199")
1800 .size(10.0)
1801 .color("888888");
1802 }
1803
1804 doc
1805}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?
86fn generate_feature_showcase(_samples_dir: &Path) -> Document {
87 let mut doc = Document::new();
88
89 // ── Page Setup & Metadata ──
90 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
91 doc.set_margins(
92 Length::inches(1.0),
93 Length::inches(1.0),
94 Length::inches(1.0),
95 Length::inches(1.0),
96 );
97 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
98 doc.set_gutter(Length::twips(0));
99 doc.set_title("rdocx Feature Showcase");
100 doc.set_author("rdocx Sample Generator");
101 doc.set_subject("Comprehensive feature demonstration");
102 doc.set_keywords("rdocx, docx, rust, sample, showcase");
103
104 // Headers & Footers with different first page
105 doc.set_different_first_page(true);
106 doc.set_first_page_header("rdocx");
107 doc.set_first_page_footer("Feature Showcase — Cover Page");
108 doc.set_header("rdocx Feature Showcase");
109 doc.set_footer("Generated by rdocx");
110
111 // ── COVER PAGE ──
112 let bg = create_sample_png(612, 792, [20, 45, 90]);
113 doc.add_background_image(&bg, "cover_bg.png");
114
115 for _ in 0..3 {
116 doc.add_paragraph("");
117 }
118 {
119 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
120 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
121 }
122 {
123 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
124 p.add_run("Complete Feature Showcase")
125 .size(28.0)
126 .color("FFFFFF")
127 .italic(true);
128 }
129 doc.add_paragraph("");
130 {
131 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
132 p.add_run("Every feature of the rdocx Rust library")
133 .size(13.0)
134 .color("B0C4DE");
135 }
136 {
137 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
138 p.add_run("demonstrated in a single document.")
139 .size(13.0)
140 .color("B0C4DE");
141 }
142
143 // ── TABLE OF CONTENTS ──
144 doc.add_paragraph("").page_break_before(true);
145 doc.insert_toc(doc.content_count(), 3);
146
147 // ── SECTION 1: TEXT FORMATTING ──
148 doc.add_paragraph("").page_break_before(true);
149 doc.add_paragraph("1. Text Formatting").style("Heading1");
150
151 // Paragraph Alignment
152 doc.add_paragraph("1.1 Paragraph Alignment")
153 .style("Heading2");
154 doc.add_paragraph("Left-aligned paragraph (default).")
155 .alignment(Alignment::Left);
156 doc.add_paragraph("Center-aligned paragraph.")
157 .alignment(Alignment::Center);
158 doc.add_paragraph("Right-aligned paragraph.")
159 .alignment(Alignment::Right);
160 doc.add_paragraph(
161 "Justified paragraph — this text is long enough to demonstrate how justified alignment \
162 distributes extra space across word gaps so lines fill the full width of the text area.",
163 )
164 .alignment(Alignment::Justify);
165
166 doc.add_paragraph("");
167
168 // Run Formatting
169 doc.add_paragraph("1.2 Run Formatting").style("Heading2");
170 {
171 let mut p = doc.add_paragraph("");
172 p.add_run("Bold").bold(true);
173 p.add_run(" | ");
174 p.add_run("Italic").italic(true);
175 p.add_run(" | ");
176 p.add_run("Bold Italic").bold(true).italic(true);
177 p.add_run(" | ");
178 p.add_run("Underline").underline(true);
179 p.add_run(" | ");
180 p.add_run("Strikethrough").strike(true);
181 p.add_run(" | ");
182 p.add_run("Double Strike").double_strike(true);
183 }
184 {
185 let mut p = doc.add_paragraph("");
186 p.add_run("Red").color("FF0000");
187 p.add_run(" | ");
188 p.add_run("Blue").color("0000FF");
189 p.add_run(" | ");
190 p.add_run("Green").color("00AA00");
191 p.add_run(" | ");
192 p.add_run("Highlighted").highlight("FFFF00");
193 }
194 {
195 let mut p = doc.add_paragraph("");
196 p.add_run("8pt").size(8.0);
197 p.add_run(" | ");
198 p.add_run("11pt").size(11.0);
199 p.add_run(" | ");
200 p.add_run("16pt").size(16.0);
201 p.add_run(" | ");
202 p.add_run("24pt").size(24.0);
203 }
204 {
205 let mut p = doc.add_paragraph("");
206 p.add_run("Arial").font("Arial");
207 p.add_run(" | ");
208 p.add_run("Times New Roman").font("Times New Roman");
209 p.add_run(" | ");
210 p.add_run("Courier New").font("Courier New");
211 }
212 {
213 let mut p = doc.add_paragraph("");
214 p.add_run("H");
215 p.add_run("2").subscript();
216 p.add_run("O (subscript) | E=mc");
217 p.add_run("2").superscript();
218 p.add_run(" (superscript)");
219 }
220 {
221 let mut p = doc.add_paragraph("");
222 p.add_run("ALL CAPS").all_caps(true);
223 p.add_run(" | ");
224 p.add_run("Small Caps").small_caps(true);
225 p.add_run(" | ");
226 p.add_run("Expanded").character_spacing(Length::twips(40));
227 p.add_run(" | ");
228 p.add_run("Hidden text (not visible)").hidden(true);
229 }
230
231 doc.add_paragraph("");
232
233 // Underline Styles
234 doc.add_paragraph("1.3 Underline Styles").style("Heading2");
235 {
236 let mut p = doc.add_paragraph("");
237 p.add_run("Single ")
238 .underline_style(rdocx::UnderlineStyle::Single);
239 p.add_run("Double ")
240 .underline_style(rdocx::UnderlineStyle::Double);
241 p.add_run("Thick ")
242 .underline_style(rdocx::UnderlineStyle::Thick);
243 p.add_run("Dotted ")
244 .underline_style(rdocx::UnderlineStyle::Dotted);
245 p.add_run("Dash ")
246 .underline_style(rdocx::UnderlineStyle::Dash);
247 p.add_run("Wave ")
248 .underline_style(rdocx::UnderlineStyle::Wave);
249 }
250
251 // ── SECTION 2: PARAGRAPH FORMATTING ──
252 doc.add_paragraph("").page_break_before(true);
253 doc.add_paragraph("2. Paragraph Formatting")
254 .style("Heading1");
255
256 doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
257 doc.add_paragraph("Paragraph with light green shading")
258 .shading("E2EFDA");
259 doc.add_paragraph("Paragraph with bottom border")
260 .border_bottom(BorderStyle::Single, 6, "2E75B6");
261 doc.add_paragraph("Paragraph with all borders (red)")
262 .border_all(BorderStyle::Single, 4, "FF0000");
263
264 doc.add_paragraph("2.2 Indentation").style("Heading2");
265 doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
266 .indent_left(Length::inches(1.0))
267 .hanging_indent(Length::inches(0.5));
268 doc.add_paragraph("0.5-inch first-line indent on this paragraph")
269 .first_line_indent(Length::inches(0.5));
270 doc.add_paragraph("Both left and right indent (0.75 inches each)")
271 .indent_left(Length::inches(0.75))
272 .indent_right(Length::inches(0.75));
273
274 doc.add_paragraph("2.3 Spacing & Line Height")
275 .style("Heading2");
276 doc.add_paragraph("Extra space before (24pt) and after (12pt)")
277 .space_before(Length::pt(24.0))
278 .space_after(Length::pt(12.0));
279 doc.add_paragraph(
280 "Double line spacing paragraph. This text should have extra vertical space between \
281 lines to demonstrate line_spacing_multiple(2.0).",
282 )
283 .line_spacing_multiple(2.0);
284 doc.add_paragraph("Exact 20pt line spacing (fixed height).")
285 .line_spacing(20.0);
286
287 doc.add_paragraph("2.4 Pagination Controls")
288 .style("Heading2");
289 doc.add_paragraph("keep_with_next — this paragraph stays with the next")
290 .keep_with_next(true);
291 doc.add_paragraph("(This paragraph was kept with the one above.)");
292 doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
293 .keep_together(true);
294 doc.add_paragraph("widow_control — prevents widow/orphan lines")
295 .widow_control(true);
296
297 // ── SECTION 3: LISTS ──
298 doc.add_paragraph("").page_break_before(true);
299 doc.add_paragraph("3. Lists").style("Heading1");
300
301 doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
302 doc.add_bullet_list_item("First item", 0);
303 doc.add_bullet_list_item("Second item", 0);
304 doc.add_bullet_list_item("Nested level 1", 1);
305 doc.add_bullet_list_item("Nested level 2", 2);
306 doc.add_bullet_list_item("Back to level 1", 1);
307 doc.add_bullet_list_item("Third item", 0);
308
309 doc.add_paragraph("");
310
311 doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
312 doc.add_numbered_list_item("First numbered item", 0);
313 doc.add_numbered_list_item("Second numbered item", 0);
314 doc.add_numbered_list_item("Sub-item A", 1);
315 doc.add_numbered_list_item("Sub-item B", 1);
316 doc.add_numbered_list_item("Third numbered item", 0);
317
318 // ── SECTION 4: TAB STOPS ──
319 doc.add_paragraph("");
320 doc.add_paragraph("4. Tab Stops").style("Heading1");
321
322 doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
323 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
324 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
325 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
326 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
327 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
328
329 doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
330 doc.add_paragraph("Item\t\tPrice")
331 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
332 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
333 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
334 doc.add_paragraph("Widget\t\t$19.99")
335 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
336 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
337 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
338 doc.add_paragraph("Gadget\t\t$249.50")
339 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
340 .add_tab_stop_with_leader(
341 TabAlignment::Right,
342 Length::inches(4.0),
343 TabLeader::Underscore,
344 )
345 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
346
347 // ── SECTION 5: TABLES ──
348 doc.add_paragraph("").page_break_before(true);
349 doc.add_paragraph("5. Tables").style("Heading1");
350
351 doc.add_paragraph("5.1 Basic Table").style("Heading2");
352 {
353 let mut tbl = doc
354 .add_table(4, 3)
355 .borders(BorderStyle::Single, 4, "000000");
356 for col in 0..3 {
357 tbl.cell(0, col).unwrap().shading("2E75B6");
358 }
359 tbl.cell(0, 0).unwrap().set_text("Name");
360 tbl.cell(0, 1).unwrap().set_text("Role");
361 tbl.cell(0, 2).unwrap().set_text("Location");
362 tbl.cell(1, 0).unwrap().set_text("Walter White");
363 tbl.cell(1, 1).unwrap().set_text("CEO");
364 tbl.cell(1, 2).unwrap().set_text("Albuquerque");
365 tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
366 tbl.cell(2, 1).unwrap().set_text("CTO");
367 tbl.cell(2, 2).unwrap().set_text("Remote");
368 tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
369 tbl.cell(3, 1).unwrap().set_text("Security");
370 tbl.cell(3, 2).unwrap().set_text("Washington");
371 }
372
373 doc.add_paragraph("");
374
375 doc.add_paragraph("5.2 Column Span & Cell Shading")
376 .style("Heading2");
377 {
378 let mut tbl = doc
379 .add_table(3, 4)
380 .borders(BorderStyle::Single, 4, "000000")
381 .width_pct(100.0);
382 tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
383 tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
384 tbl.cell(1, 0).unwrap().set_text("Region");
385 tbl.cell(1, 0).unwrap().shading("D6E4F0");
386 tbl.cell(1, 1).unwrap().set_text("Q1");
387 tbl.cell(1, 1).unwrap().shading("D6E4F0");
388 tbl.cell(1, 2).unwrap().set_text("Q2");
389 tbl.cell(1, 2).unwrap().shading("D6E4F0");
390 tbl.cell(1, 3).unwrap().set_text("Total");
391 tbl.cell(1, 3).unwrap().shading("D6E4F0");
392 tbl.cell(2, 0).unwrap().set_text("Americas");
393 tbl.cell(2, 1).unwrap().set_text("$2.4M");
394 tbl.cell(2, 2).unwrap().set_text("$2.7M");
395 tbl.cell(2, 3).unwrap().set_text("$5.1M");
396 }
397
398 doc.add_paragraph("");
399
400 doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
401 {
402 let mut tbl = doc
403 .add_table(4, 3)
404 .borders(BorderStyle::Single, 4, "000000");
405 tbl.cell(0, 0).unwrap().set_text("Category");
406 tbl.cell(0, 0).unwrap().shading("E2EFDA");
407 tbl.cell(0, 1).unwrap().set_text("Item");
408 tbl.cell(0, 1).unwrap().shading("E2EFDA");
409 tbl.cell(0, 2).unwrap().set_text("Price");
410 tbl.cell(0, 2).unwrap().shading("E2EFDA");
411 tbl.cell(1, 0).unwrap().set_text("Hardware");
412 tbl.cell(1, 0).unwrap().v_merge_restart();
413 tbl.cell(1, 1).unwrap().set_text("Laptop");
414 tbl.cell(1, 2).unwrap().set_text("$1,200");
415 tbl.cell(2, 0).unwrap().v_merge_continue();
416 tbl.cell(2, 1).unwrap().set_text("Monitor");
417 tbl.cell(2, 2).unwrap().set_text("$450");
418 tbl.cell(3, 0).unwrap().set_text("Software");
419 tbl.cell(3, 1).unwrap().set_text("IDE License");
420 tbl.cell(3, 2).unwrap().set_text("$200/yr");
421 }
422
423 doc.add_paragraph("");
424
425 doc.add_paragraph("5.4 Nested Table").style("Heading2");
426 {
427 let mut tbl = doc
428 .add_table(2, 2)
429 .borders(BorderStyle::Single, 6, "2E75B6");
430 tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
431 tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
432 tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
433 {
434 let mut cell = tbl.cell(1, 1).unwrap();
435 cell.set_text("Nested table below:");
436 let mut nested = cell
437 .add_table(2, 2)
438 .borders(BorderStyle::Single, 2, "FF6600");
439 nested.cell(0, 0).unwrap().set_text("A");
440 nested.cell(0, 1).unwrap().set_text("B");
441 nested.cell(1, 0).unwrap().set_text("C");
442 nested.cell(1, 1).unwrap().set_text("D");
443 }
444 }
445
446 doc.add_paragraph("");
447
448 doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
449 .style("Heading2");
450 {
451 let mut tbl = doc
452 .add_table(2, 3)
453 .borders(BorderStyle::Single, 4, "666666");
454 tbl.row(0).unwrap().height(Length::pt(50.0)).header();
455 tbl.cell(0, 0).unwrap().set_text("Top");
456 tbl.cell(0, 0)
457 .unwrap()
458 .vertical_alignment(VerticalAlignment::Top)
459 .shading("FFF2CC");
460 tbl.cell(0, 1).unwrap().set_text("Center");
461 tbl.cell(0, 1)
462 .unwrap()
463 .vertical_alignment(VerticalAlignment::Center)
464 .shading("D9E2F3");
465 tbl.cell(0, 2).unwrap().set_text("Bottom");
466 tbl.cell(0, 2)
467 .unwrap()
468 .vertical_alignment(VerticalAlignment::Bottom)
469 .shading("E2EFDA");
470 tbl.row(1).unwrap().cant_split();
471 tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
472 tbl.cell(1, 0).unwrap().no_wrap();
473 tbl.cell(1, 1).unwrap().set_text("Fixed width");
474 tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
475 tbl.cell(1, 2).unwrap().set_text("Normal");
476 }
477
478 // ── SECTION 6: IMAGES ──
479 doc.add_paragraph("").page_break_before(true);
480 doc.add_paragraph("6. Images").style("Heading1");
481
482 doc.add_paragraph("6.1 Inline Image").style("Heading2");
483 doc.add_paragraph("A blue gradient image below:");
484 let img = create_sample_png(200, 50, [0, 80, 200]);
485 doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
486
487 doc.add_paragraph("");
488 doc.add_paragraph("6.2 Header Image").style("Heading2");
489 let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
490 doc.set_header_image(
491 &hdr_img,
492 "header_logo.png",
493 Length::inches(2.0),
494 Length::inches(0.2),
495 );
496 doc.add_paragraph("The header now contains an inline image (check the top of this page).");
497
498 doc.add_paragraph("");
499 doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
500
501 // ── SECTION 7: CONTENT MANIPULATION ──
502 doc.add_paragraph("").page_break_before(true);
503 doc.add_paragraph("7. Content Manipulation")
504 .style("Heading1");
505
506 doc.add_paragraph("7.1 Placeholder Replacement")
507 .style("Heading2");
508 doc.add_paragraph("Customer: {{customer}}");
509 doc.add_paragraph("Date: {{date}}");
510 doc.add_paragraph("Reference: {{ref_number}}");
511 {
512 let mut tbl = doc
513 .add_table(2, 2)
514 .borders(BorderStyle::Single, 4, "000000");
515 tbl.cell(0, 0).unwrap().set_text("Project");
516 tbl.cell(0, 0).unwrap().shading("D6E4F0");
517 tbl.cell(0, 1).unwrap().set_text("{{project}}");
518 tbl.cell(1, 0).unwrap().set_text("Status");
519 tbl.cell(1, 0).unwrap().shading("D6E4F0");
520 tbl.cell(1, 1).unwrap().set_text("{{status}}");
521 }
522
523 let mut replacements = HashMap::new();
524 replacements.insert("{{customer}}", "Tensorbee Inc.");
525 replacements.insert("{{date}}", "February 22, 2026");
526 replacements.insert("{{ref_number}}", "TB-2026-001");
527 replacements.insert("{{project}}", "Infrastructure Upgrade");
528 replacements.insert("{{status}}", "In Progress");
529 let n = doc.replace_all(&replacements);
530 doc.add_paragraph(&format!("({n} placeholders replaced above)"));
531
532 doc.add_paragraph("");
533
534 doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
535 doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
536 let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
537 doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
538
539 doc.add_paragraph("");
540
541 doc.add_paragraph("7.3 Content Insertion").style("Heading2");
542 doc.add_paragraph("Section A: First content.");
543 doc.add_paragraph("Section C: Third content.");
544 if let Some(idx) = doc.find_content_index("Section C") {
545 doc.insert_paragraph(
546 idx,
547 "Section B: Inserted between A and C via find_content_index().",
548 );
549 }
550
551 // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
552 doc.add_paragraph("").section_break(SectionBreak::NextPage);
553 doc.add_paragraph("8. Section Breaks & Orientation")
554 .style("Heading1");
555 doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
556
557 {
558 let mut tbl = doc
559 .add_table(3, 7)
560 .borders(BorderStyle::Single, 4, "2E75B6");
561 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
562 for (c, h) in headers.iter().enumerate() {
563 tbl.cell(0, c).unwrap().set_text(h);
564 tbl.cell(0, c).unwrap().shading("2E75B6");
565 }
566 let data = [
567 [
568 "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
569 ],
570 [
571 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
572 ],
573 ];
574 for (r, row) in data.iter().enumerate() {
575 for (c, v) in row.iter().enumerate() {
576 tbl.cell(r + 1, c).unwrap().set_text(v);
577 }
578 }
579 }
580
581 doc.add_paragraph("")
582 .section_break(SectionBreak::NextPage)
583 .section_landscape();
584
585 // ── SECTION 9: CUSTOM STYLES ──
586 doc.add_paragraph("9. Custom Styles").style("Heading1");
587 doc.add_style(
588 StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
589 .based_on("Normal")
590 .paragraph_properties({
591 let mut ppr = rdocx_oxml::properties::CT_PPr::default();
592 ppr.shading = Some(rdocx_oxml::properties::CT_Shd {
593 val: "clear".to_string(),
594 color: None,
595 fill: Some("FFF2CC".to_string()),
596 });
597 ppr
598 })
599 .run_properties({
600 let mut rpr = rdocx_oxml::properties::CT_RPr::default();
601 rpr.bold = Some(true);
602 rpr.color = Some("C45911".to_string());
603 rpr
604 }),
605 );
606 doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
607 .style("CustomHighlight");
608
609 doc.add_paragraph("");
610
611 // ── SECTION 10: DOCUMENT INTELLIGENCE ──
612 doc.add_paragraph("10. Document Intelligence API")
613 .style("Heading1");
614
615 let wc = doc.word_count();
616 let hc = doc.headings().len();
617 let ic = doc.images().len();
618 let lc = doc.links().len();
619 doc.add_paragraph(&format!("Word count: {wc}"));
620 doc.add_paragraph(&format!("Heading count: {hc}"));
621 doc.add_paragraph(&format!("Image count: {ic}"));
622 doc.add_paragraph(&format!("Link count: {lc}"));
623
624 let outline = doc.document_outline();
625 doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
626
627 let issues = doc.audit_accessibility();
628 doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
629 for issue in &issues {
630 doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
631 }
632
633 // ── SECTION 11: DOCUMENT MERGING ──
634 doc.add_paragraph("");
635 doc.add_paragraph("11. Document Merging").style("Heading1");
636 {
637 let mut other = Document::new();
638 other.add_paragraph("This paragraph was merged from another document using append().");
639 doc.append(&other);
640 }
641
642 doc.add_paragraph("");
643
644 // ── SUMMARY ──
645 doc.add_paragraph("Summary of Demonstrated Features")
646 .style("Heading1");
647 let features = [
648 "Page setup: size, margins, header/footer distance, gutter",
649 "Document metadata: title, author, subject, keywords",
650 "Headers and footers: text, images, different first page",
651 "Background images (full-page behind text)",
652 "Table of Contents generation with bookmarks",
653 "Text formatting: bold, italic, underline styles, strike, color, size, font",
654 "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
655 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
656 "Pagination controls: keep-with-next, keep-together, widow control",
657 "Bullet and numbered lists with nesting",
658 "Tab stops with dot/underscore/hyphen leaders",
659 "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
660 "Row properties: header rows, exact height, cant-split",
661 "Cell properties: width, no-wrap, vertical alignment",
662 "Inline images and header images",
663 "Placeholder replacement (single and batch)",
664 "Regex-based find and replace",
665 "Content insertion at specific positions",
666 "Section breaks with mixed portrait/landscape",
667 "Custom paragraph and character styles",
668 "Document intelligence: word count, headings, outline, images, links",
669 "Accessibility audit",
670 "Document merging (append)",
671 "Export: DOCX, PDF, HTML, Markdown",
672 ];
673 for f in &features {
674 doc.add_bullet_list_item(f, 0);
675 }
676 doc.add_paragraph("");
677 doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
678 .alignment(Alignment::Center)
679 .shading("E2EFDA")
680 .border_all(BorderStyle::Single, 2, "00AA00");
681
682 doc
683}
684
685// =============================================================================
686// 2. PROPOSAL DOCUMENT — Deep navy + gold scheme
687// =============================================================================
688fn generate_proposal(_samples_dir: &Path) -> Document {
689 let mut doc = Document::new();
690
691 // Colors: Navy #1B2A4A, Gold #C5922E, Light #F4F1EB
692 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
693 doc.set_margins(
694 Length::inches(1.0),
695 Length::inches(1.0),
696 Length::inches(1.0),
697 Length::inches(1.0),
698 );
699 doc.set_title("AI Platform Modernization Proposal");
700 doc.set_author("Walter White");
701 doc.set_subject("Technology Proposal");
702 doc.set_keywords("proposal, AI, modernization, Tensorbee");
703
704 // Banner header
705 let logo_img = create_logo_png(220, 48);
706 let banner = build_header_banner_xml(
707 "rId1",
708 &BannerOpts {
709 bg_color: "1B2A4A",
710 banner_width: 7772400,
711 banner_height: 914400,
712 logo_width: 2011680,
713 logo_height: 438912,
714 logo_x_offset: 295125,
715 logo_y_offset: 237744,
716 },
717 );
718 doc.set_raw_header_with_images(
719 banner,
720 &[("rId1", &logo_img, "logo.png")],
721 rdocx_oxml::header_footer::HdrFtrType::Default,
722 );
723 doc.set_different_first_page(true);
724 let cover_banner = build_header_banner_xml(
725 "rId1",
726 &BannerOpts {
727 bg_color: "C5922E",
728 banner_width: 7772400,
729 banner_height: 914400,
730 logo_width: 2011680,
731 logo_height: 438912,
732 logo_x_offset: 295125,
733 logo_y_offset: 237744,
734 },
735 );
736 doc.set_raw_header_with_images(
737 cover_banner,
738 &[("rId1", &logo_img, "logo.png")],
739 rdocx_oxml::header_footer::HdrFtrType::First,
740 );
741 doc.set_margins(
742 Length::twips(2292),
743 Length::twips(1440),
744 Length::twips(1440),
745 Length::twips(1440),
746 );
747 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
748 doc.set_footer("Tensorbee — Confidential");
749
750 // Custom styles
751 doc.add_style(
752 StyleBuilder::paragraph("ProposalTitle", "Proposal Title")
753 .based_on("Normal")
754 .run_properties({
755 let mut rpr = rdocx_oxml::properties::CT_RPr::default();
756 rpr.bold = Some(true);
757 rpr.font_ascii = Some("Georgia".to_string());
758 rpr.font_hansi = Some("Georgia".to_string());
759 rpr.sz = Some(rdocx_oxml::HalfPoint(56)); // half-points → 28pt
760 rpr.color = Some("1B2A4A".to_string());
761 rpr
762 }),
763 );
764
765 // ── Cover Page ──
766 for _ in 0..4 {
767 doc.add_paragraph("");
768 }
769 doc.add_paragraph("AI Platform Modernization")
770 .style("ProposalTitle")
771 .alignment(Alignment::Center);
772 doc.add_paragraph("")
773 .alignment(Alignment::Center)
774 .border_bottom(BorderStyle::Single, 8, "C5922E");
775 {
776 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
777 p.add_run("Prepared for Global Financial Corp.")
778 .size(14.0)
779 .color("666666")
780 .italic(true);
781 }
782 {
783 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
784 p.add_run("Prepared by: Walter White, CEO — Tensorbee")
785 .size(12.0)
786 .color("888888");
787 }
788 {
789 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
790 p.add_run("February 22, 2026").size(12.0).color("888888");
791 }
792
793 // ── TOC ──
794 doc.add_paragraph("").page_break_before(true);
795 doc.add_paragraph("Table of Contents").style("Heading1");
796 // We'll add a manual-style TOC since the insert_toc goes at a specific position
797 doc.insert_toc(doc.content_count(), 2);
798
799 // ── Executive Summary ──
800 doc.add_paragraph("").page_break_before(true);
801 doc.add_paragraph("Executive Summary").style("Heading1");
802 doc.add_paragraph(
803 "Tensorbee proposes a comprehensive modernization of Global Financial Corp's AI platform, \
804 transitioning from legacy batch-processing systems to a real-time inference architecture. \
805 This transformation will reduce model deployment time from weeks to hours, improve \
806 prediction accuracy by an estimated 23%, and deliver $4.2M in annual operational savings.",
807 )
808 .first_line_indent(Length::inches(0.3));
809 doc.add_paragraph(
810 "The project spans three phases over 18 months, with a total investment of $2.8M. \
811 Tensorbee brings deep expertise in ML infrastructure, having successfully completed \
812 similar transformations for three Fortune 500 financial institutions.",
813 )
814 .first_line_indent(Length::inches(0.3));
815
816 // Key metrics table
817 doc.add_paragraph("");
818 {
819 let mut tbl = doc
820 .add_table(5, 2)
821 .borders(BorderStyle::Single, 4, "1B2A4A")
822 .width_pct(80.0);
823 tbl.cell(0, 0).unwrap().set_text("Key Metric");
824 tbl.cell(0, 0).unwrap().shading("1B2A4A");
825 tbl.cell(0, 1).unwrap().set_text("Value");
826 tbl.cell(0, 1).unwrap().shading("1B2A4A");
827 let rows = [
828 ("Total Investment", "$2.8M"),
829 ("Annual Savings", "$4.2M"),
830 ("ROI (Year 1)", "150%"),
831 ("Timeline", "18 months"),
832 ];
833 for (i, (k, v)) in rows.iter().enumerate() {
834 tbl.cell(i + 1, 0).unwrap().set_text(k);
835 if i % 2 == 0 {
836 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
837 }
838 tbl.cell(i + 1, 1).unwrap().set_text(v);
839 if i % 2 == 0 {
840 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
841 }
842 }
843 }
844
845 // ── Problem Statement ──
846 doc.add_paragraph("").page_break_before(true);
847 doc.add_paragraph("Problem Statement").style("Heading1");
848 doc.add_paragraph(
849 "Global Financial Corp's current AI infrastructure faces three critical challenges:",
850 );
851 doc.add_numbered_list_item("Deployment Latency — New models take 3-4 weeks to deploy to production, compared to the industry benchmark of 2-3 days.", 0);
852 doc.add_numbered_list_item("Scalability Constraints — The batch processing architecture cannot handle real-time inference demands during peak trading hours.", 0);
853 doc.add_numbered_list_item("Technical Debt — Legacy Python 2.7 codebases and manual deployment scripts create reliability risks and slow iteration speed.", 0);
854
855 // ── Proposed Solution ──
856 doc.add_paragraph("").page_break_before(true);
857 doc.add_paragraph("Proposed Solution").style("Heading1");
858
859 doc.add_paragraph("Phase 1: Foundation (Months 1-6)")
860 .style("Heading2");
861 doc.add_bullet_list_item("Deploy Kubernetes-based ML serving infrastructure", 0);
862 doc.add_bullet_list_item("Implement CI/CD pipeline for model deployment", 0);
863 doc.add_bullet_list_item("Migrate top 5 production models to new platform", 0);
864
865 doc.add_paragraph("Phase 2: Expansion (Months 7-12)")
866 .style("Heading2");
867 doc.add_bullet_list_item(
868 "Real-time feature engineering pipeline (Apache Kafka + Flink)",
869 0,
870 );
871 doc.add_bullet_list_item("A/B testing framework for model variants", 0);
872 doc.add_bullet_list_item("Automated model monitoring and drift detection", 0);
873
874 doc.add_paragraph("Phase 3: Optimization (Months 13-18)")
875 .style("Heading2");
876 doc.add_bullet_list_item("GPU inference optimization (TensorRT/ONNX Runtime)", 0);
877 doc.add_bullet_list_item("Multi-region deployment for latency reduction", 0);
878 doc.add_bullet_list_item(
879 "Self-service model deployment portal for data science teams",
880 0,
881 );
882
883 // ── Budget ──
884 doc.add_paragraph("Budget Breakdown").style("Heading1");
885 {
886 let mut tbl = doc
887 .add_table(6, 3)
888 .borders(BorderStyle::Single, 4, "1B2A4A")
889 .width_pct(100.0);
890 tbl.cell(0, 0).unwrap().set_text("Category");
891 tbl.cell(0, 0).unwrap().shading("1B2A4A");
892 tbl.cell(0, 1).unwrap().set_text("Cost");
893 tbl.cell(0, 1).unwrap().shading("1B2A4A");
894 tbl.cell(0, 2).unwrap().set_text("Notes");
895 tbl.cell(0, 2).unwrap().shading("1B2A4A");
896 let items = [
897 (
898 "Infrastructure",
899 "$450,000",
900 "Cloud compute + GPU instances",
901 ),
902 ("Engineering Services", "$1,600,000", "12 FTE x 18 months"),
903 (
904 "Software Licenses",
905 "$350,000",
906 "Kafka, monitoring, CI/CD tools",
907 ),
908 (
909 "Training & Enablement",
910 "$200,000",
911 "Team training, documentation",
912 ),
913 ("Contingency (10%)", "$200,000", "Risk buffer"),
914 ];
915 for (i, (cat, cost, note)) in items.iter().enumerate() {
916 tbl.cell(i + 1, 0).unwrap().set_text(cat);
917 tbl.cell(i + 1, 1).unwrap().set_text(cost);
918 tbl.cell(i + 1, 2).unwrap().set_text(note);
919 if i % 2 == 0 {
920 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
921 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
922 tbl.cell(i + 1, 2).unwrap().shading("F4F1EB");
923 }
924 }
925 }
926
927 // ── Closing ──
928 doc.add_paragraph("");
929 doc.add_paragraph("Next Steps").style("Heading1");
930 doc.add_numbered_list_item(
931 "Schedule technical deep-dive with GFC engineering team (Week 1)",
932 0,
933 );
934 doc.add_numbered_list_item("Finalize scope and sign SOW (Week 2-3)", 0);
935 doc.add_numbered_list_item("Kick off Phase 1 with joint planning session (Week 4)", 0);
936
937 doc.add_paragraph("");
938 {
939 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
940 p.add_run("Walter White")
941 .bold(true)
942 .size(14.0)
943 .color("1B2A4A");
944 }
945 {
946 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
947 p.add_run("CEO, Tensorbee | walter@tensorbee.com")
948 .size(10.0)
949 .color("888888");
950 }
951
952 doc
953}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}34fn generate_feature_showcase(path: &Path) {
35 let mut doc = Document::new();
36
37 // =========================================================================
38 // PAGE SETUP & METADATA
39 // =========================================================================
40 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
41 doc.set_margins(
42 Length::inches(1.0), // top
43 Length::inches(1.0), // right
44 Length::inches(1.0), // bottom
45 Length::inches(1.0), // left
46 );
47 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
48 doc.set_gutter(Length::twips(0));
49
50 doc.set_title("rdocx Feature Showcase");
51 doc.set_author("rdocx Sample Generator");
52 doc.set_subject("Comprehensive feature demonstration");
53 doc.set_keywords("rdocx, docx, rust, sample");
54
55 // Header & Footer
56 doc.set_header("rdocx Feature Showcase");
57 doc.set_footer("Generated by rdocx — Page");
58
59 // Different first page header
60 doc.set_different_first_page(true);
61 doc.set_first_page_header("rdocx");
62 doc.set_first_page_footer("Feature Showcase — Cover Page");
63
64 // =========================================================================
65 // PAGE 1: COVER PAGE — background image, run formatting
66 // =========================================================================
67 let bg_cover = create_sample_png(612, 792, [30, 60, 120]);
68 doc.add_background_image(&bg_cover, "cover_bg.png");
69
70 doc.add_paragraph(""); // spacer
71 doc.add_paragraph(""); // spacer
72 doc.add_paragraph(""); // spacer
73
74 {
75 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
76 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
77 }
78 {
79 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
80 p.add_run("Feature Showcase")
81 .size(28.0)
82 .color("FFFFFF")
83 .italic(true);
84 }
85
86 doc.add_paragraph(""); // spacer
87
88 {
89 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
90 p.add_run("A comprehensive demonstration of every feature")
91 .size(14.0)
92 .color("CCDDFF");
93 }
94 {
95 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
96 p.add_run("provided by the rdocx Rust crate for DOCX generation.")
97 .size(14.0)
98 .color("CCDDFF");
99 }
100
101 // =========================================================================
102 // PAGE 2: TEXT FORMATTING
103 // =========================================================================
104 doc.add_paragraph("").page_break_before(true);
105
106 doc.add_paragraph("1. Text Formatting").style("Heading1");
107
108 doc.add_paragraph("This section demonstrates paragraph and run-level formatting options.");
109 doc.add_paragraph("");
110
111 // --- Paragraph alignment ---
112 doc.add_paragraph("Paragraph Alignment").style("Heading2");
113
114 doc.add_paragraph("This paragraph is left-aligned (the default).")
115 .alignment(Alignment::Left);
116 doc.add_paragraph("This paragraph is center-aligned.")
117 .alignment(Alignment::Center);
118 doc.add_paragraph("This paragraph is right-aligned.")
119 .alignment(Alignment::Right);
120 doc.add_paragraph(
121 "This paragraph is justified. To demonstrate justified text properly, it needs \
122 to be long enough to span multiple lines so the word spacing adjustment is visible \
123 across the full width of the text area on the page.",
124 )
125 .alignment(Alignment::Justify);
126
127 doc.add_paragraph("");
128
129 // --- Run formatting ---
130 doc.add_paragraph("Run Formatting").style("Heading2");
131
132 {
133 let mut p = doc.add_paragraph("");
134 p.add_run("Bold text").bold(true);
135 p.add_run(" | ");
136 p.add_run("Italic text").italic(true);
137 p.add_run(" | ");
138 p.add_run("Bold + Italic").bold(true).italic(true);
139 }
140 {
141 let mut p = doc.add_paragraph("");
142 p.add_run("Single underline").underline(true);
143 p.add_run(" | ");
144 p.add_run("Strikethrough").strike(true);
145 p.add_run(" | ");
146 p.add_run("Double strikethrough").double_strike(true);
147 }
148 {
149 let mut p = doc.add_paragraph("");
150 p.add_run("Red text").color("FF0000");
151 p.add_run(" | ");
152 p.add_run("Blue text").color("0000FF");
153 p.add_run(" | ");
154 p.add_run("Green text").color("00AA00");
155 p.add_run(" | ");
156 p.add_run("Highlighted").highlight("FFFF00");
157 }
158 {
159 let mut p = doc.add_paragraph("");
160 p.add_run("8pt small").size(8.0);
161 p.add_run(" | ");
162 p.add_run("11pt normal").size(11.0);
163 p.add_run(" | ");
164 p.add_run("16pt large").size(16.0);
165 p.add_run(" | ");
166 p.add_run("24pt extra-large").size(24.0);
167 }
168 {
169 let mut p = doc.add_paragraph("");
170 p.add_run("Arial font").font("Arial");
171 p.add_run(" | ");
172 p.add_run("Times New Roman font").font("Times New Roman");
173 p.add_run(" | ");
174 p.add_run("Courier New font").font("Courier New");
175 }
176 {
177 let mut p = doc.add_paragraph("");
178 p.add_run("Normal");
179 p.add_run(" H").size(11.0);
180 p.add_run("2").subscript();
181 p.add_run("O (subscript)").size(11.0);
182 p.add_run(" | E = mc").size(11.0);
183 p.add_run("2").superscript();
184 p.add_run(" (superscript)").size(11.0);
185 }
186 {
187 let mut p = doc.add_paragraph("");
188 p.add_run("ALL CAPS").all_caps(true);
189 p.add_run(" | ");
190 p.add_run("Small Caps").small_caps(true);
191 p.add_run(" | ");
192 p.add_run("Expanded spacing")
193 .character_spacing(Length::twips(40));
194 }
195
196 doc.add_paragraph("");
197
198 // --- Paragraph formatting ---
199 doc.add_paragraph("Paragraph Formatting").style("Heading2");
200
201 doc.add_paragraph("Paragraph with shading (light green background)")
202 .shading("E2EFDA");
203
204 doc.add_paragraph("Paragraph with bottom border")
205 .border_bottom(BorderStyle::Single, 6, "2E75B6");
206
207 doc.add_paragraph("Paragraph with all borders")
208 .border_all(BorderStyle::Single, 4, "FF0000");
209
210 doc.add_paragraph("Paragraph with 1-inch left indent and hanging indent")
211 .indent_left(Length::inches(1.0))
212 .hanging_indent(Length::inches(0.5));
213
214 doc.add_paragraph("Paragraph with first-line indent of 0.5 inches")
215 .first_line_indent(Length::inches(0.5));
216
217 doc.add_paragraph("Paragraph with extra space before (24pt) and after (12pt)")
218 .space_before(Length::pt(24.0))
219 .space_after(Length::pt(12.0));
220
221 doc.add_paragraph(
222 "Paragraph with double line spacing. This text should have extra vertical \
223 space between lines to demonstrate the line_spacing_multiple setting.",
224 )
225 .line_spacing_multiple(2.0);
226
227 doc.add_paragraph("Paragraph with keep-with-next (won't break from the next paragraph)")
228 .keep_with_next(true);
229 doc.add_paragraph("(This stays with the paragraph above.)");
230
231 // =========================================================================
232 // PAGE 3: LISTS & TAB STOPS
233 // =========================================================================
234 doc.add_paragraph("").page_break_before(true);
235
236 doc.add_paragraph("2. Lists").style("Heading1");
237
238 doc.add_paragraph("Bullet List").style("Heading2");
239
240 doc.add_bullet_list_item("First bullet item", 0);
241 doc.add_bullet_list_item("Second bullet item", 0);
242 doc.add_bullet_list_item("Nested level 1", 1);
243 doc.add_bullet_list_item("Nested level 2", 2);
244 doc.add_bullet_list_item("Back to level 1", 1);
245 doc.add_bullet_list_item("Third bullet item", 0);
246
247 doc.add_paragraph("");
248
249 doc.add_paragraph("Numbered List").style("Heading2");
250
251 doc.add_numbered_list_item("First numbered item", 0);
252 doc.add_numbered_list_item("Second numbered item", 0);
253 doc.add_numbered_list_item("Sub-item A", 1);
254 doc.add_numbered_list_item("Sub-item B", 1);
255 doc.add_numbered_list_item("Third numbered item", 0);
256
257 doc.add_paragraph("");
258
259 // --- Tab stops ---
260 doc.add_paragraph("Tab Stops").style("Heading2");
261
262 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
263 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
264 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
265 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
266 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
267
268 doc.add_paragraph("Item\t........\tPrice")
269 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
270 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
271 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
272
273 doc.add_paragraph("Widget A\t........\t$19.99")
274 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
275 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
276 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
277
278 doc.add_paragraph("Gadget B\t________\t$249.50")
279 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
280 .add_tab_stop_with_leader(
281 TabAlignment::Right,
282 Length::inches(4.0),
283 TabLeader::Underscore,
284 )
285 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
286
287 // =========================================================================
288 // PAGE 4: TABLES
289 // =========================================================================
290 doc.add_paragraph("").page_break_before(true);
291
292 doc.add_paragraph("3. Tables").style("Heading1");
293
294 // --- Basic table with borders ---
295 doc.add_paragraph("Basic Table with Borders")
296 .style("Heading2");
297
298 {
299 let mut tbl = doc.add_table(4, 3);
300 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
301
302 // Header row
303 for col in 0..3 {
304 tbl.cell(0, col).unwrap().shading("2E75B6");
305 }
306 tbl.cell(0, 0).unwrap().set_text("Name");
307 tbl.cell(0, 1).unwrap().set_text("Role");
308 tbl.cell(0, 2).unwrap().set_text("Location");
309
310 tbl.cell(1, 0).unwrap().set_text("Alice Johnson");
311 tbl.cell(1, 1).unwrap().set_text("Engineering Lead");
312 tbl.cell(1, 2).unwrap().set_text("New York");
313
314 tbl.cell(2, 0).unwrap().set_text("Bob Smith");
315 tbl.cell(2, 1).unwrap().set_text("Product Manager");
316 tbl.cell(2, 2).unwrap().set_text("San Francisco");
317
318 tbl.cell(3, 0).unwrap().set_text("Carol Davis");
319 tbl.cell(3, 1).unwrap().set_text("Designer");
320 tbl.cell(3, 2).unwrap().set_text("London");
321 }
322
323 doc.add_paragraph("");
324
325 // --- Table with cell merging ---
326 doc.add_paragraph("Table with Cell Merging & Vertical Alignment")
327 .style("Heading2");
328
329 {
330 let mut tbl = doc.add_table(4, 4);
331 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
332 tbl = tbl.width_pct(100.0);
333
334 // Header spanning all columns
335 tbl.cell(0, 0).unwrap().set_text("Quarterly Revenue Report");
336 tbl.cell(0, 0).unwrap().shading("1F4E79");
337 tbl.cell(0, 0).unwrap().grid_span(4);
338
339 // Sub-header
340 tbl.cell(1, 0).unwrap().set_text("Region");
341 tbl.cell(1, 0).unwrap().shading("D6E4F0");
342 tbl.cell(1, 1).unwrap().set_text("Q1");
343 tbl.cell(1, 1).unwrap().shading("D6E4F0");
344 tbl.cell(1, 2).unwrap().set_text("Q2");
345 tbl.cell(1, 2).unwrap().shading("D6E4F0");
346 tbl.cell(1, 3).unwrap().set_text("Total");
347 tbl.cell(1, 3).unwrap().shading("D6E4F0");
348
349 // Data
350 tbl.cell(2, 0).unwrap().set_text("North America");
351 tbl.cell(2, 1).unwrap().set_text("$2.4M");
352 tbl.cell(2, 2).unwrap().set_text("$2.7M");
353 tbl.cell(2, 3).unwrap().set_text("$5.1M");
354
355 tbl.cell(3, 0).unwrap().set_text("Europe");
356 tbl.cell(3, 1).unwrap().set_text("$1.8M");
357 tbl.cell(3, 2).unwrap().set_text("$2.0M");
358 tbl.cell(3, 3).unwrap().set_text("$3.8M");
359
360 // Vertical alignment on data cells
361 tbl.cell(2, 3)
362 .unwrap()
363 .vertical_alignment(VerticalAlignment::Center);
364 tbl.cell(3, 3)
365 .unwrap()
366 .vertical_alignment(VerticalAlignment::Bottom);
367 }
368
369 doc.add_paragraph("");
370
371 // --- Table with vertical merge ---
372 doc.add_paragraph("Table with Vertical Merge")
373 .style("Heading2");
374
375 {
376 let mut tbl = doc.add_table(4, 3);
377 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
378
379 tbl.cell(0, 0).unwrap().set_text("Category");
380 tbl.cell(0, 0).unwrap().shading("E2EFDA");
381 tbl.cell(0, 1).unwrap().set_text("Item");
382 tbl.cell(0, 1).unwrap().shading("E2EFDA");
383 tbl.cell(0, 2).unwrap().set_text("Price");
384 tbl.cell(0, 2).unwrap().shading("E2EFDA");
385
386 // "Hardware" spans rows 1-2
387 tbl.cell(1, 0).unwrap().set_text("Hardware");
388 tbl.cell(1, 0).unwrap().v_merge_restart();
389 tbl.cell(1, 1).unwrap().set_text("Laptop");
390 tbl.cell(1, 2).unwrap().set_text("$1,200");
391
392 tbl.cell(2, 0).unwrap().v_merge_continue();
393 tbl.cell(2, 1).unwrap().set_text("Monitor");
394 tbl.cell(2, 2).unwrap().set_text("$450");
395
396 // "Software" on row 3
397 tbl.cell(3, 0).unwrap().set_text("Software");
398 tbl.cell(3, 1).unwrap().set_text("IDE License");
399 tbl.cell(3, 2).unwrap().set_text("$200/yr");
400 }
401
402 doc.add_paragraph("");
403
404 // --- Nested table ---
405 doc.add_paragraph("Nested Table").style("Heading2");
406
407 {
408 let mut tbl = doc.add_table(2, 2);
409 tbl = tbl.borders(BorderStyle::Single, 6, "2E75B6");
410
411 tbl.cell(0, 0).unwrap().set_text("Outer Cell (0,0)");
412 tbl.cell(0, 1).unwrap().set_text("Outer Cell (0,1)");
413 tbl.cell(1, 0).unwrap().set_text("Outer Cell (1,0)");
414
415 // Nested table inside cell (1,1)
416 {
417 let mut cell = tbl.cell(1, 1).unwrap();
418 cell.set_text("Contains nested table:");
419 let mut nested = cell.add_table(2, 2);
420 nested = nested.borders(BorderStyle::Single, 2, "FF6600");
421 nested.cell(0, 0).unwrap().set_text("Inner A");
422 nested.cell(0, 1).unwrap().set_text("Inner B");
423 nested.cell(1, 0).unwrap().set_text("Inner C");
424 nested.cell(1, 1).unwrap().set_text("Inner D");
425 }
426 }
427
428 // =========================================================================
429 // PAGE 5: IMAGES
430 // =========================================================================
431 doc.add_paragraph("").page_break_before(true);
432
433 doc.add_paragraph("4. Images").style("Heading1");
434
435 doc.add_paragraph("Inline Image").style("Heading2");
436
437 doc.add_paragraph("Below is an inline image (200x50 pixels, blue gradient):");
438 let inline_img = create_sample_png(200, 50, [0, 80, 200]);
439 doc.add_picture(
440 &inline_img,
441 "inline_chart.png",
442 Length::inches(3.0),
443 Length::inches(0.75),
444 );
445
446 doc.add_paragraph("");
447
448 doc.add_paragraph("Header Image").style("Heading2");
449
450 // Replace the text-only header with an image header
451 let header_img = create_sample_png(400, 40, [40, 40, 40]);
452 doc.set_header_image(
453 &header_img,
454 "header_logo.png",
455 Length::inches(2.0),
456 Length::inches(0.2),
457 );
458
459 doc.add_paragraph(
460 "The document header has been replaced with an inline image. \
461 Check the header area at the top of this page.",
462 );
463
464 doc.add_paragraph("");
465 doc.add_paragraph(
466 "Note: The cover page uses a full-page background image behind the text, \
467 demonstrated on page 1 via add_background_image().",
468 );
469
470 // =========================================================================
471 // PAGE 6: CONTENT MANIPULATION — placeholder replacement, insertion
472 // =========================================================================
473 doc.add_paragraph("").page_break_before(true);
474
475 doc.add_paragraph("5. Content Manipulation")
476 .style("Heading1");
477
478 // --- Placeholder replacement ---
479 doc.add_paragraph("Placeholder Replacement")
480 .style("Heading2");
481
482 doc.add_paragraph(
483 "Before replacement, this document contained {{customer}} and {{date}} placeholders.",
484 );
485
486 {
487 let mut p = doc.add_paragraph("");
488 p.add_run("Customer: ").bold(true);
489 p.add_run("{{customer}}");
490 }
491 {
492 let mut p = doc.add_paragraph("");
493 p.add_run("Date: ").bold(true);
494 p.add_run("{{date}}");
495 }
496 doc.add_paragraph("Reference: {{ref_number}}");
497
498 // Table with placeholders
499 {
500 let mut tbl = doc.add_table(3, 2);
501 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
502 tbl.cell(0, 0).unwrap().set_text("Field");
503 tbl.cell(0, 0).unwrap().shading("D6E4F0");
504 tbl.cell(0, 1).unwrap().set_text("Value");
505 tbl.cell(0, 1).unwrap().shading("D6E4F0");
506 tbl.cell(1, 0).unwrap().set_text("Project");
507 tbl.cell(1, 1).unwrap().set_text("{{project}}");
508 tbl.cell(2, 0).unwrap().set_text("Status");
509 tbl.cell(2, 1).unwrap().set_text("{{status}}");
510 }
511
512 // Perform replacements
513 let mut replacements = HashMap::new();
514 replacements.insert("{{customer}}", "Acme Corporation");
515 replacements.insert("{{date}}", "February 22, 2026");
516 replacements.insert("{{ref_number}}", "REF-2026-001");
517 replacements.insert("{{project}}", "Infrastructure Upgrade");
518 replacements.insert("{{status}}", "In Progress");
519 let replace_count = doc.replace_all(&replacements);
520
521 doc.add_paragraph("");
522 doc.add_paragraph(&format!(
523 "(Replaced {} placeholders above — in body text and table cells)",
524 replace_count
525 ));
526
527 doc.add_paragraph("");
528
529 // --- Content insertion ---
530 doc.add_paragraph("Content Insertion").style("Heading2");
531
532 doc.add_paragraph("Section A: First section of content.");
533 doc.add_paragraph("Section C: Third section of content.");
534
535 // Insert "Section B" between A and C
536 if let Some(idx) = doc.find_content_index("Section C") {
537 doc.insert_paragraph(
538 idx,
539 "Section B: Inserted between A and C using find_content_index().",
540 );
541 }
542
543 doc.add_paragraph("");
544 doc.add_paragraph(
545 "The paragraph above ('Section B') was inserted at a specific position \
546 using find_content_index() + insert_paragraph().",
547 );
548
549 // =========================================================================
550 // PAGE 7: LANDSCAPE — section break, wide table
551 // =========================================================================
552 doc.add_paragraph("").section_break(SectionBreak::NextPage);
553
554 doc.add_paragraph("6. Mixed Page Orientation")
555 .style("Heading1");
556
557 doc.add_paragraph(
558 "This page is in LANDSCAPE orientation. It was created using a section break \
559 followed by section_landscape(). This is useful for wide tables or charts.",
560 );
561
562 doc.add_paragraph("");
563
564 // Wide table for landscape
565 {
566 let mut tbl = doc.add_table(4, 7);
567 tbl = tbl.borders(BorderStyle::Single, 4, "2E75B6");
568
569 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
570 for (col, h) in headers.iter().enumerate() {
571 tbl.cell(0, col).unwrap().set_text(h);
572 tbl.cell(0, col).unwrap().shading("2E75B6");
573 }
574
575 let data = [
576 [
577 "North America",
578 "$1.2M",
579 "$1.3M",
580 "$1.4M",
581 "$1.5M",
582 "$1.6M",
583 "$7.0M",
584 ],
585 [
586 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
587 ],
588 [
589 "Asia Pacific",
590 "$0.5M",
591 "$0.6M",
592 "$0.7M",
593 "$0.7M",
594 "$0.8M",
595 "$3.3M",
596 ],
597 ];
598 for (row_idx, row_data) in data.iter().enumerate() {
599 for (col, val) in row_data.iter().enumerate() {
600 tbl.cell(row_idx + 1, col).unwrap().set_text(val);
601 }
602 }
603 }
604
605 // End landscape, return to portrait
606 doc.add_paragraph("")
607 .section_break(SectionBreak::NextPage)
608 .section_landscape();
609
610 // =========================================================================
611 // PAGE 8: BACK TO PORTRAIT — styles, final notes
612 // =========================================================================
613 doc.add_paragraph("7. Custom Styles & Summary")
614 .style("Heading1");
615
616 doc.add_paragraph(
617 "This final page is back in portrait orientation after a section break. \
618 The document has demonstrated:",
619 );
620
621 doc.add_bullet_list_item(
622 "Page setup: size, margins, header/footer distance, gutter",
623 0,
624 );
625 doc.add_bullet_list_item("Document metadata: title, author, subject, keywords", 0);
626 doc.add_bullet_list_item("Headers and footers: text, images, different first page", 0);
627 doc.add_bullet_list_item("Background images: full-page behind text", 0);
628 doc.add_bullet_list_item(
629 "Text formatting: bold, italic, underline, strike, color, size, font",
630 0,
631 );
632 doc.add_bullet_list_item(
633 "Advanced run formatting: superscript, subscript, caps, spacing",
634 0,
635 );
636 doc.add_bullet_list_item(
637 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
638 0,
639 );
640 doc.add_bullet_list_item("Bullet and numbered lists with nesting levels", 0);
641 doc.add_bullet_list_item("Tab stops with dot/underscore leaders", 0);
642 doc.add_bullet_list_item(
643 "Tables: borders, shading, column spans, row spans, nesting",
644 0,
645 );
646 doc.add_bullet_list_item("Vertical alignment in table cells", 0);
647 doc.add_bullet_list_item("Inline images", 0);
648 doc.add_bullet_list_item("Placeholder replacement in body and table cells", 0);
649 doc.add_bullet_list_item("Content insertion at specific positions", 0);
650 doc.add_bullet_list_item(
651 "Section breaks with mixed portrait/landscape orientation",
652 0,
653 );
654
655 doc.add_paragraph("");
656 doc.add_paragraph("All features above were built entirely from scratch using the rdocx API.")
657 .alignment(Alignment::Center)
658 .shading("E2EFDA")
659 .border_all(BorderStyle::Single, 2, "00AA00");
660
661 doc.save(path).unwrap();
662}86fn generate_feature_showcase(_samples_dir: &Path) -> Document {
87 let mut doc = Document::new();
88
89 // ── Page Setup & Metadata ──
90 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
91 doc.set_margins(
92 Length::inches(1.0),
93 Length::inches(1.0),
94 Length::inches(1.0),
95 Length::inches(1.0),
96 );
97 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
98 doc.set_gutter(Length::twips(0));
99 doc.set_title("rdocx Feature Showcase");
100 doc.set_author("rdocx Sample Generator");
101 doc.set_subject("Comprehensive feature demonstration");
102 doc.set_keywords("rdocx, docx, rust, sample, showcase");
103
104 // Headers & Footers with different first page
105 doc.set_different_first_page(true);
106 doc.set_first_page_header("rdocx");
107 doc.set_first_page_footer("Feature Showcase — Cover Page");
108 doc.set_header("rdocx Feature Showcase");
109 doc.set_footer("Generated by rdocx");
110
111 // ── COVER PAGE ──
112 let bg = create_sample_png(612, 792, [20, 45, 90]);
113 doc.add_background_image(&bg, "cover_bg.png");
114
115 for _ in 0..3 {
116 doc.add_paragraph("");
117 }
118 {
119 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
120 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
121 }
122 {
123 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
124 p.add_run("Complete Feature Showcase")
125 .size(28.0)
126 .color("FFFFFF")
127 .italic(true);
128 }
129 doc.add_paragraph("");
130 {
131 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
132 p.add_run("Every feature of the rdocx Rust library")
133 .size(13.0)
134 .color("B0C4DE");
135 }
136 {
137 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
138 p.add_run("demonstrated in a single document.")
139 .size(13.0)
140 .color("B0C4DE");
141 }
142
143 // ── TABLE OF CONTENTS ──
144 doc.add_paragraph("").page_break_before(true);
145 doc.insert_toc(doc.content_count(), 3);
146
147 // ── SECTION 1: TEXT FORMATTING ──
148 doc.add_paragraph("").page_break_before(true);
149 doc.add_paragraph("1. Text Formatting").style("Heading1");
150
151 // Paragraph Alignment
152 doc.add_paragraph("1.1 Paragraph Alignment")
153 .style("Heading2");
154 doc.add_paragraph("Left-aligned paragraph (default).")
155 .alignment(Alignment::Left);
156 doc.add_paragraph("Center-aligned paragraph.")
157 .alignment(Alignment::Center);
158 doc.add_paragraph("Right-aligned paragraph.")
159 .alignment(Alignment::Right);
160 doc.add_paragraph(
161 "Justified paragraph — this text is long enough to demonstrate how justified alignment \
162 distributes extra space across word gaps so lines fill the full width of the text area.",
163 )
164 .alignment(Alignment::Justify);
165
166 doc.add_paragraph("");
167
168 // Run Formatting
169 doc.add_paragraph("1.2 Run Formatting").style("Heading2");
170 {
171 let mut p = doc.add_paragraph("");
172 p.add_run("Bold").bold(true);
173 p.add_run(" | ");
174 p.add_run("Italic").italic(true);
175 p.add_run(" | ");
176 p.add_run("Bold Italic").bold(true).italic(true);
177 p.add_run(" | ");
178 p.add_run("Underline").underline(true);
179 p.add_run(" | ");
180 p.add_run("Strikethrough").strike(true);
181 p.add_run(" | ");
182 p.add_run("Double Strike").double_strike(true);
183 }
184 {
185 let mut p = doc.add_paragraph("");
186 p.add_run("Red").color("FF0000");
187 p.add_run(" | ");
188 p.add_run("Blue").color("0000FF");
189 p.add_run(" | ");
190 p.add_run("Green").color("00AA00");
191 p.add_run(" | ");
192 p.add_run("Highlighted").highlight("FFFF00");
193 }
194 {
195 let mut p = doc.add_paragraph("");
196 p.add_run("8pt").size(8.0);
197 p.add_run(" | ");
198 p.add_run("11pt").size(11.0);
199 p.add_run(" | ");
200 p.add_run("16pt").size(16.0);
201 p.add_run(" | ");
202 p.add_run("24pt").size(24.0);
203 }
204 {
205 let mut p = doc.add_paragraph("");
206 p.add_run("Arial").font("Arial");
207 p.add_run(" | ");
208 p.add_run("Times New Roman").font("Times New Roman");
209 p.add_run(" | ");
210 p.add_run("Courier New").font("Courier New");
211 }
212 {
213 let mut p = doc.add_paragraph("");
214 p.add_run("H");
215 p.add_run("2").subscript();
216 p.add_run("O (subscript) | E=mc");
217 p.add_run("2").superscript();
218 p.add_run(" (superscript)");
219 }
220 {
221 let mut p = doc.add_paragraph("");
222 p.add_run("ALL CAPS").all_caps(true);
223 p.add_run(" | ");
224 p.add_run("Small Caps").small_caps(true);
225 p.add_run(" | ");
226 p.add_run("Expanded").character_spacing(Length::twips(40));
227 p.add_run(" | ");
228 p.add_run("Hidden text (not visible)").hidden(true);
229 }
230
231 doc.add_paragraph("");
232
233 // Underline Styles
234 doc.add_paragraph("1.3 Underline Styles").style("Heading2");
235 {
236 let mut p = doc.add_paragraph("");
237 p.add_run("Single ")
238 .underline_style(rdocx::UnderlineStyle::Single);
239 p.add_run("Double ")
240 .underline_style(rdocx::UnderlineStyle::Double);
241 p.add_run("Thick ")
242 .underline_style(rdocx::UnderlineStyle::Thick);
243 p.add_run("Dotted ")
244 .underline_style(rdocx::UnderlineStyle::Dotted);
245 p.add_run("Dash ")
246 .underline_style(rdocx::UnderlineStyle::Dash);
247 p.add_run("Wave ")
248 .underline_style(rdocx::UnderlineStyle::Wave);
249 }
250
251 // ── SECTION 2: PARAGRAPH FORMATTING ──
252 doc.add_paragraph("").page_break_before(true);
253 doc.add_paragraph("2. Paragraph Formatting")
254 .style("Heading1");
255
256 doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
257 doc.add_paragraph("Paragraph with light green shading")
258 .shading("E2EFDA");
259 doc.add_paragraph("Paragraph with bottom border")
260 .border_bottom(BorderStyle::Single, 6, "2E75B6");
261 doc.add_paragraph("Paragraph with all borders (red)")
262 .border_all(BorderStyle::Single, 4, "FF0000");
263
264 doc.add_paragraph("2.2 Indentation").style("Heading2");
265 doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
266 .indent_left(Length::inches(1.0))
267 .hanging_indent(Length::inches(0.5));
268 doc.add_paragraph("0.5-inch first-line indent on this paragraph")
269 .first_line_indent(Length::inches(0.5));
270 doc.add_paragraph("Both left and right indent (0.75 inches each)")
271 .indent_left(Length::inches(0.75))
272 .indent_right(Length::inches(0.75));
273
274 doc.add_paragraph("2.3 Spacing & Line Height")
275 .style("Heading2");
276 doc.add_paragraph("Extra space before (24pt) and after (12pt)")
277 .space_before(Length::pt(24.0))
278 .space_after(Length::pt(12.0));
279 doc.add_paragraph(
280 "Double line spacing paragraph. This text should have extra vertical space between \
281 lines to demonstrate line_spacing_multiple(2.0).",
282 )
283 .line_spacing_multiple(2.0);
284 doc.add_paragraph("Exact 20pt line spacing (fixed height).")
285 .line_spacing(20.0);
286
287 doc.add_paragraph("2.4 Pagination Controls")
288 .style("Heading2");
289 doc.add_paragraph("keep_with_next — this paragraph stays with the next")
290 .keep_with_next(true);
291 doc.add_paragraph("(This paragraph was kept with the one above.)");
292 doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
293 .keep_together(true);
294 doc.add_paragraph("widow_control — prevents widow/orphan lines")
295 .widow_control(true);
296
297 // ── SECTION 3: LISTS ──
298 doc.add_paragraph("").page_break_before(true);
299 doc.add_paragraph("3. Lists").style("Heading1");
300
301 doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
302 doc.add_bullet_list_item("First item", 0);
303 doc.add_bullet_list_item("Second item", 0);
304 doc.add_bullet_list_item("Nested level 1", 1);
305 doc.add_bullet_list_item("Nested level 2", 2);
306 doc.add_bullet_list_item("Back to level 1", 1);
307 doc.add_bullet_list_item("Third item", 0);
308
309 doc.add_paragraph("");
310
311 doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
312 doc.add_numbered_list_item("First numbered item", 0);
313 doc.add_numbered_list_item("Second numbered item", 0);
314 doc.add_numbered_list_item("Sub-item A", 1);
315 doc.add_numbered_list_item("Sub-item B", 1);
316 doc.add_numbered_list_item("Third numbered item", 0);
317
318 // ── SECTION 4: TAB STOPS ──
319 doc.add_paragraph("");
320 doc.add_paragraph("4. Tab Stops").style("Heading1");
321
322 doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
323 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
324 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
325 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
326 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
327 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
328
329 doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
330 doc.add_paragraph("Item\t\tPrice")
331 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
332 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
333 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
334 doc.add_paragraph("Widget\t\t$19.99")
335 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
336 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
337 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
338 doc.add_paragraph("Gadget\t\t$249.50")
339 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
340 .add_tab_stop_with_leader(
341 TabAlignment::Right,
342 Length::inches(4.0),
343 TabLeader::Underscore,
344 )
345 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
346
347 // ── SECTION 5: TABLES ──
348 doc.add_paragraph("").page_break_before(true);
349 doc.add_paragraph("5. Tables").style("Heading1");
350
351 doc.add_paragraph("5.1 Basic Table").style("Heading2");
352 {
353 let mut tbl = doc
354 .add_table(4, 3)
355 .borders(BorderStyle::Single, 4, "000000");
356 for col in 0..3 {
357 tbl.cell(0, col).unwrap().shading("2E75B6");
358 }
359 tbl.cell(0, 0).unwrap().set_text("Name");
360 tbl.cell(0, 1).unwrap().set_text("Role");
361 tbl.cell(0, 2).unwrap().set_text("Location");
362 tbl.cell(1, 0).unwrap().set_text("Walter White");
363 tbl.cell(1, 1).unwrap().set_text("CEO");
364 tbl.cell(1, 2).unwrap().set_text("Albuquerque");
365 tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
366 tbl.cell(2, 1).unwrap().set_text("CTO");
367 tbl.cell(2, 2).unwrap().set_text("Remote");
368 tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
369 tbl.cell(3, 1).unwrap().set_text("Security");
370 tbl.cell(3, 2).unwrap().set_text("Washington");
371 }
372
373 doc.add_paragraph("");
374
375 doc.add_paragraph("5.2 Column Span & Cell Shading")
376 .style("Heading2");
377 {
378 let mut tbl = doc
379 .add_table(3, 4)
380 .borders(BorderStyle::Single, 4, "000000")
381 .width_pct(100.0);
382 tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
383 tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
384 tbl.cell(1, 0).unwrap().set_text("Region");
385 tbl.cell(1, 0).unwrap().shading("D6E4F0");
386 tbl.cell(1, 1).unwrap().set_text("Q1");
387 tbl.cell(1, 1).unwrap().shading("D6E4F0");
388 tbl.cell(1, 2).unwrap().set_text("Q2");
389 tbl.cell(1, 2).unwrap().shading("D6E4F0");
390 tbl.cell(1, 3).unwrap().set_text("Total");
391 tbl.cell(1, 3).unwrap().shading("D6E4F0");
392 tbl.cell(2, 0).unwrap().set_text("Americas");
393 tbl.cell(2, 1).unwrap().set_text("$2.4M");
394 tbl.cell(2, 2).unwrap().set_text("$2.7M");
395 tbl.cell(2, 3).unwrap().set_text("$5.1M");
396 }
397
398 doc.add_paragraph("");
399
400 doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
401 {
402 let mut tbl = doc
403 .add_table(4, 3)
404 .borders(BorderStyle::Single, 4, "000000");
405 tbl.cell(0, 0).unwrap().set_text("Category");
406 tbl.cell(0, 0).unwrap().shading("E2EFDA");
407 tbl.cell(0, 1).unwrap().set_text("Item");
408 tbl.cell(0, 1).unwrap().shading("E2EFDA");
409 tbl.cell(0, 2).unwrap().set_text("Price");
410 tbl.cell(0, 2).unwrap().shading("E2EFDA");
411 tbl.cell(1, 0).unwrap().set_text("Hardware");
412 tbl.cell(1, 0).unwrap().v_merge_restart();
413 tbl.cell(1, 1).unwrap().set_text("Laptop");
414 tbl.cell(1, 2).unwrap().set_text("$1,200");
415 tbl.cell(2, 0).unwrap().v_merge_continue();
416 tbl.cell(2, 1).unwrap().set_text("Monitor");
417 tbl.cell(2, 2).unwrap().set_text("$450");
418 tbl.cell(3, 0).unwrap().set_text("Software");
419 tbl.cell(3, 1).unwrap().set_text("IDE License");
420 tbl.cell(3, 2).unwrap().set_text("$200/yr");
421 }
422
423 doc.add_paragraph("");
424
425 doc.add_paragraph("5.4 Nested Table").style("Heading2");
426 {
427 let mut tbl = doc
428 .add_table(2, 2)
429 .borders(BorderStyle::Single, 6, "2E75B6");
430 tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
431 tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
432 tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
433 {
434 let mut cell = tbl.cell(1, 1).unwrap();
435 cell.set_text("Nested table below:");
436 let mut nested = cell
437 .add_table(2, 2)
438 .borders(BorderStyle::Single, 2, "FF6600");
439 nested.cell(0, 0).unwrap().set_text("A");
440 nested.cell(0, 1).unwrap().set_text("B");
441 nested.cell(1, 0).unwrap().set_text("C");
442 nested.cell(1, 1).unwrap().set_text("D");
443 }
444 }
445
446 doc.add_paragraph("");
447
448 doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
449 .style("Heading2");
450 {
451 let mut tbl = doc
452 .add_table(2, 3)
453 .borders(BorderStyle::Single, 4, "666666");
454 tbl.row(0).unwrap().height(Length::pt(50.0)).header();
455 tbl.cell(0, 0).unwrap().set_text("Top");
456 tbl.cell(0, 0)
457 .unwrap()
458 .vertical_alignment(VerticalAlignment::Top)
459 .shading("FFF2CC");
460 tbl.cell(0, 1).unwrap().set_text("Center");
461 tbl.cell(0, 1)
462 .unwrap()
463 .vertical_alignment(VerticalAlignment::Center)
464 .shading("D9E2F3");
465 tbl.cell(0, 2).unwrap().set_text("Bottom");
466 tbl.cell(0, 2)
467 .unwrap()
468 .vertical_alignment(VerticalAlignment::Bottom)
469 .shading("E2EFDA");
470 tbl.row(1).unwrap().cant_split();
471 tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
472 tbl.cell(1, 0).unwrap().no_wrap();
473 tbl.cell(1, 1).unwrap().set_text("Fixed width");
474 tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
475 tbl.cell(1, 2).unwrap().set_text("Normal");
476 }
477
478 // ── SECTION 6: IMAGES ──
479 doc.add_paragraph("").page_break_before(true);
480 doc.add_paragraph("6. Images").style("Heading1");
481
482 doc.add_paragraph("6.1 Inline Image").style("Heading2");
483 doc.add_paragraph("A blue gradient image below:");
484 let img = create_sample_png(200, 50, [0, 80, 200]);
485 doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
486
487 doc.add_paragraph("");
488 doc.add_paragraph("6.2 Header Image").style("Heading2");
489 let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
490 doc.set_header_image(
491 &hdr_img,
492 "header_logo.png",
493 Length::inches(2.0),
494 Length::inches(0.2),
495 );
496 doc.add_paragraph("The header now contains an inline image (check the top of this page).");
497
498 doc.add_paragraph("");
499 doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
500
501 // ── SECTION 7: CONTENT MANIPULATION ──
502 doc.add_paragraph("").page_break_before(true);
503 doc.add_paragraph("7. Content Manipulation")
504 .style("Heading1");
505
506 doc.add_paragraph("7.1 Placeholder Replacement")
507 .style("Heading2");
508 doc.add_paragraph("Customer: {{customer}}");
509 doc.add_paragraph("Date: {{date}}");
510 doc.add_paragraph("Reference: {{ref_number}}");
511 {
512 let mut tbl = doc
513 .add_table(2, 2)
514 .borders(BorderStyle::Single, 4, "000000");
515 tbl.cell(0, 0).unwrap().set_text("Project");
516 tbl.cell(0, 0).unwrap().shading("D6E4F0");
517 tbl.cell(0, 1).unwrap().set_text("{{project}}");
518 tbl.cell(1, 0).unwrap().set_text("Status");
519 tbl.cell(1, 0).unwrap().shading("D6E4F0");
520 tbl.cell(1, 1).unwrap().set_text("{{status}}");
521 }
522
523 let mut replacements = HashMap::new();
524 replacements.insert("{{customer}}", "Tensorbee Inc.");
525 replacements.insert("{{date}}", "February 22, 2026");
526 replacements.insert("{{ref_number}}", "TB-2026-001");
527 replacements.insert("{{project}}", "Infrastructure Upgrade");
528 replacements.insert("{{status}}", "In Progress");
529 let n = doc.replace_all(&replacements);
530 doc.add_paragraph(&format!("({n} placeholders replaced above)"));
531
532 doc.add_paragraph("");
533
534 doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
535 doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
536 let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
537 doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
538
539 doc.add_paragraph("");
540
541 doc.add_paragraph("7.3 Content Insertion").style("Heading2");
542 doc.add_paragraph("Section A: First content.");
543 doc.add_paragraph("Section C: Third content.");
544 if let Some(idx) = doc.find_content_index("Section C") {
545 doc.insert_paragraph(
546 idx,
547 "Section B: Inserted between A and C via find_content_index().",
548 );
549 }
550
551 // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
552 doc.add_paragraph("").section_break(SectionBreak::NextPage);
553 doc.add_paragraph("8. Section Breaks & Orientation")
554 .style("Heading1");
555 doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
556
557 {
558 let mut tbl = doc
559 .add_table(3, 7)
560 .borders(BorderStyle::Single, 4, "2E75B6");
561 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
562 for (c, h) in headers.iter().enumerate() {
563 tbl.cell(0, c).unwrap().set_text(h);
564 tbl.cell(0, c).unwrap().shading("2E75B6");
565 }
566 let data = [
567 [
568 "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
569 ],
570 [
571 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
572 ],
573 ];
574 for (r, row) in data.iter().enumerate() {
575 for (c, v) in row.iter().enumerate() {
576 tbl.cell(r + 1, c).unwrap().set_text(v);
577 }
578 }
579 }
580
581 doc.add_paragraph("")
582 .section_break(SectionBreak::NextPage)
583 .section_landscape();
584
585 // ── SECTION 9: CUSTOM STYLES ──
586 doc.add_paragraph("9. Custom Styles").style("Heading1");
587 doc.add_style(
588 StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
589 .based_on("Normal")
590 .paragraph_properties({
591 let mut ppr = rdocx_oxml::properties::CT_PPr::default();
592 ppr.shading = Some(rdocx_oxml::properties::CT_Shd {
593 val: "clear".to_string(),
594 color: None,
595 fill: Some("FFF2CC".to_string()),
596 });
597 ppr
598 })
599 .run_properties({
600 let mut rpr = rdocx_oxml::properties::CT_RPr::default();
601 rpr.bold = Some(true);
602 rpr.color = Some("C45911".to_string());
603 rpr
604 }),
605 );
606 doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
607 .style("CustomHighlight");
608
609 doc.add_paragraph("");
610
611 // ── SECTION 10: DOCUMENT INTELLIGENCE ──
612 doc.add_paragraph("10. Document Intelligence API")
613 .style("Heading1");
614
615 let wc = doc.word_count();
616 let hc = doc.headings().len();
617 let ic = doc.images().len();
618 let lc = doc.links().len();
619 doc.add_paragraph(&format!("Word count: {wc}"));
620 doc.add_paragraph(&format!("Heading count: {hc}"));
621 doc.add_paragraph(&format!("Image count: {ic}"));
622 doc.add_paragraph(&format!("Link count: {lc}"));
623
624 let outline = doc.document_outline();
625 doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
626
627 let issues = doc.audit_accessibility();
628 doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
629 for issue in &issues {
630 doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
631 }
632
633 // ── SECTION 11: DOCUMENT MERGING ──
634 doc.add_paragraph("");
635 doc.add_paragraph("11. Document Merging").style("Heading1");
636 {
637 let mut other = Document::new();
638 other.add_paragraph("This paragraph was merged from another document using append().");
639 doc.append(&other);
640 }
641
642 doc.add_paragraph("");
643
644 // ── SUMMARY ──
645 doc.add_paragraph("Summary of Demonstrated Features")
646 .style("Heading1");
647 let features = [
648 "Page setup: size, margins, header/footer distance, gutter",
649 "Document metadata: title, author, subject, keywords",
650 "Headers and footers: text, images, different first page",
651 "Background images (full-page behind text)",
652 "Table of Contents generation with bookmarks",
653 "Text formatting: bold, italic, underline styles, strike, color, size, font",
654 "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
655 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
656 "Pagination controls: keep-with-next, keep-together, widow control",
657 "Bullet and numbered lists with nesting",
658 "Tab stops with dot/underscore/hyphen leaders",
659 "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
660 "Row properties: header rows, exact height, cant-split",
661 "Cell properties: width, no-wrap, vertical alignment",
662 "Inline images and header images",
663 "Placeholder replacement (single and batch)",
664 "Regex-based find and replace",
665 "Content insertion at specific positions",
666 "Section breaks with mixed portrait/landscape",
667 "Custom paragraph and character styles",
668 "Document intelligence: word count, headings, outline, images, links",
669 "Accessibility audit",
670 "Document merging (append)",
671 "Export: DOCX, PDF, HTML, Markdown",
672 ];
673 for f in &features {
674 doc.add_bullet_list_item(f, 0);
675 }
676 doc.add_paragraph("");
677 doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
678 .alignment(Alignment::Center)
679 .shading("E2EFDA")
680 .border_all(BorderStyle::Single, 2, "00AA00");
681
682 doc
683}
684
685// =============================================================================
686// 2. PROPOSAL DOCUMENT — Deep navy + gold scheme
687// =============================================================================
688fn generate_proposal(_samples_dir: &Path) -> Document {
689 let mut doc = Document::new();
690
691 // Colors: Navy #1B2A4A, Gold #C5922E, Light #F4F1EB
692 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
693 doc.set_margins(
694 Length::inches(1.0),
695 Length::inches(1.0),
696 Length::inches(1.0),
697 Length::inches(1.0),
698 );
699 doc.set_title("AI Platform Modernization Proposal");
700 doc.set_author("Walter White");
701 doc.set_subject("Technology Proposal");
702 doc.set_keywords("proposal, AI, modernization, Tensorbee");
703
704 // Banner header
705 let logo_img = create_logo_png(220, 48);
706 let banner = build_header_banner_xml(
707 "rId1",
708 &BannerOpts {
709 bg_color: "1B2A4A",
710 banner_width: 7772400,
711 banner_height: 914400,
712 logo_width: 2011680,
713 logo_height: 438912,
714 logo_x_offset: 295125,
715 logo_y_offset: 237744,
716 },
717 );
718 doc.set_raw_header_with_images(
719 banner,
720 &[("rId1", &logo_img, "logo.png")],
721 rdocx_oxml::header_footer::HdrFtrType::Default,
722 );
723 doc.set_different_first_page(true);
724 let cover_banner = build_header_banner_xml(
725 "rId1",
726 &BannerOpts {
727 bg_color: "C5922E",
728 banner_width: 7772400,
729 banner_height: 914400,
730 logo_width: 2011680,
731 logo_height: 438912,
732 logo_x_offset: 295125,
733 logo_y_offset: 237744,
734 },
735 );
736 doc.set_raw_header_with_images(
737 cover_banner,
738 &[("rId1", &logo_img, "logo.png")],
739 rdocx_oxml::header_footer::HdrFtrType::First,
740 );
741 doc.set_margins(
742 Length::twips(2292),
743 Length::twips(1440),
744 Length::twips(1440),
745 Length::twips(1440),
746 );
747 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
748 doc.set_footer("Tensorbee — Confidential");
749
750 // Custom styles
751 doc.add_style(
752 StyleBuilder::paragraph("ProposalTitle", "Proposal Title")
753 .based_on("Normal")
754 .run_properties({
755 let mut rpr = rdocx_oxml::properties::CT_RPr::default();
756 rpr.bold = Some(true);
757 rpr.font_ascii = Some("Georgia".to_string());
758 rpr.font_hansi = Some("Georgia".to_string());
759 rpr.sz = Some(rdocx_oxml::HalfPoint(56)); // half-points → 28pt
760 rpr.color = Some("1B2A4A".to_string());
761 rpr
762 }),
763 );
764
765 // ── Cover Page ──
766 for _ in 0..4 {
767 doc.add_paragraph("");
768 }
769 doc.add_paragraph("AI Platform Modernization")
770 .style("ProposalTitle")
771 .alignment(Alignment::Center);
772 doc.add_paragraph("")
773 .alignment(Alignment::Center)
774 .border_bottom(BorderStyle::Single, 8, "C5922E");
775 {
776 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
777 p.add_run("Prepared for Global Financial Corp.")
778 .size(14.0)
779 .color("666666")
780 .italic(true);
781 }
782 {
783 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
784 p.add_run("Prepared by: Walter White, CEO — Tensorbee")
785 .size(12.0)
786 .color("888888");
787 }
788 {
789 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
790 p.add_run("February 22, 2026").size(12.0).color("888888");
791 }
792
793 // ── TOC ──
794 doc.add_paragraph("").page_break_before(true);
795 doc.add_paragraph("Table of Contents").style("Heading1");
796 // We'll add a manual-style TOC since the insert_toc goes at a specific position
797 doc.insert_toc(doc.content_count(), 2);
798
799 // ── Executive Summary ──
800 doc.add_paragraph("").page_break_before(true);
801 doc.add_paragraph("Executive Summary").style("Heading1");
802 doc.add_paragraph(
803 "Tensorbee proposes a comprehensive modernization of Global Financial Corp's AI platform, \
804 transitioning from legacy batch-processing systems to a real-time inference architecture. \
805 This transformation will reduce model deployment time from weeks to hours, improve \
806 prediction accuracy by an estimated 23%, and deliver $4.2M in annual operational savings.",
807 )
808 .first_line_indent(Length::inches(0.3));
809 doc.add_paragraph(
810 "The project spans three phases over 18 months, with a total investment of $2.8M. \
811 Tensorbee brings deep expertise in ML infrastructure, having successfully completed \
812 similar transformations for three Fortune 500 financial institutions.",
813 )
814 .first_line_indent(Length::inches(0.3));
815
816 // Key metrics table
817 doc.add_paragraph("");
818 {
819 let mut tbl = doc
820 .add_table(5, 2)
821 .borders(BorderStyle::Single, 4, "1B2A4A")
822 .width_pct(80.0);
823 tbl.cell(0, 0).unwrap().set_text("Key Metric");
824 tbl.cell(0, 0).unwrap().shading("1B2A4A");
825 tbl.cell(0, 1).unwrap().set_text("Value");
826 tbl.cell(0, 1).unwrap().shading("1B2A4A");
827 let rows = [
828 ("Total Investment", "$2.8M"),
829 ("Annual Savings", "$4.2M"),
830 ("ROI (Year 1)", "150%"),
831 ("Timeline", "18 months"),
832 ];
833 for (i, (k, v)) in rows.iter().enumerate() {
834 tbl.cell(i + 1, 0).unwrap().set_text(k);
835 if i % 2 == 0 {
836 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
837 }
838 tbl.cell(i + 1, 1).unwrap().set_text(v);
839 if i % 2 == 0 {
840 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
841 }
842 }
843 }
844
845 // ── Problem Statement ──
846 doc.add_paragraph("").page_break_before(true);
847 doc.add_paragraph("Problem Statement").style("Heading1");
848 doc.add_paragraph(
849 "Global Financial Corp's current AI infrastructure faces three critical challenges:",
850 );
851 doc.add_numbered_list_item("Deployment Latency — New models take 3-4 weeks to deploy to production, compared to the industry benchmark of 2-3 days.", 0);
852 doc.add_numbered_list_item("Scalability Constraints — The batch processing architecture cannot handle real-time inference demands during peak trading hours.", 0);
853 doc.add_numbered_list_item("Technical Debt — Legacy Python 2.7 codebases and manual deployment scripts create reliability risks and slow iteration speed.", 0);
854
855 // ── Proposed Solution ──
856 doc.add_paragraph("").page_break_before(true);
857 doc.add_paragraph("Proposed Solution").style("Heading1");
858
859 doc.add_paragraph("Phase 1: Foundation (Months 1-6)")
860 .style("Heading2");
861 doc.add_bullet_list_item("Deploy Kubernetes-based ML serving infrastructure", 0);
862 doc.add_bullet_list_item("Implement CI/CD pipeline for model deployment", 0);
863 doc.add_bullet_list_item("Migrate top 5 production models to new platform", 0);
864
865 doc.add_paragraph("Phase 2: Expansion (Months 7-12)")
866 .style("Heading2");
867 doc.add_bullet_list_item(
868 "Real-time feature engineering pipeline (Apache Kafka + Flink)",
869 0,
870 );
871 doc.add_bullet_list_item("A/B testing framework for model variants", 0);
872 doc.add_bullet_list_item("Automated model monitoring and drift detection", 0);
873
874 doc.add_paragraph("Phase 3: Optimization (Months 13-18)")
875 .style("Heading2");
876 doc.add_bullet_list_item("GPU inference optimization (TensorRT/ONNX Runtime)", 0);
877 doc.add_bullet_list_item("Multi-region deployment for latency reduction", 0);
878 doc.add_bullet_list_item(
879 "Self-service model deployment portal for data science teams",
880 0,
881 );
882
883 // ── Budget ──
884 doc.add_paragraph("Budget Breakdown").style("Heading1");
885 {
886 let mut tbl = doc
887 .add_table(6, 3)
888 .borders(BorderStyle::Single, 4, "1B2A4A")
889 .width_pct(100.0);
890 tbl.cell(0, 0).unwrap().set_text("Category");
891 tbl.cell(0, 0).unwrap().shading("1B2A4A");
892 tbl.cell(0, 1).unwrap().set_text("Cost");
893 tbl.cell(0, 1).unwrap().shading("1B2A4A");
894 tbl.cell(0, 2).unwrap().set_text("Notes");
895 tbl.cell(0, 2).unwrap().shading("1B2A4A");
896 let items = [
897 (
898 "Infrastructure",
899 "$450,000",
900 "Cloud compute + GPU instances",
901 ),
902 ("Engineering Services", "$1,600,000", "12 FTE x 18 months"),
903 (
904 "Software Licenses",
905 "$350,000",
906 "Kafka, monitoring, CI/CD tools",
907 ),
908 (
909 "Training & Enablement",
910 "$200,000",
911 "Team training, documentation",
912 ),
913 ("Contingency (10%)", "$200,000", "Risk buffer"),
914 ];
915 for (i, (cat, cost, note)) in items.iter().enumerate() {
916 tbl.cell(i + 1, 0).unwrap().set_text(cat);
917 tbl.cell(i + 1, 1).unwrap().set_text(cost);
918 tbl.cell(i + 1, 2).unwrap().set_text(note);
919 if i % 2 == 0 {
920 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
921 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
922 tbl.cell(i + 1, 2).unwrap().shading("F4F1EB");
923 }
924 }
925 }
926
927 // ── Closing ──
928 doc.add_paragraph("");
929 doc.add_paragraph("Next Steps").style("Heading1");
930 doc.add_numbered_list_item(
931 "Schedule technical deep-dive with GFC engineering team (Week 1)",
932 0,
933 );
934 doc.add_numbered_list_item("Finalize scope and sign SOW (Week 2-3)", 0);
935 doc.add_numbered_list_item("Kick off Phase 1 with joint planning session (Week 4)", 0);
936
937 doc.add_paragraph("");
938 {
939 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
940 p.add_run("Walter White")
941 .bold(true)
942 .size(14.0)
943 .color("1B2A4A");
944 }
945 {
946 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
947 p.add_run("CEO, Tensorbee | walter@tensorbee.com")
948 .size(10.0)
949 .color("888888");
950 }
951
952 doc
953}
954
955// =============================================================================
956// 3. QUOTE / BILL OF MATERIALS — Teal + orange scheme
957// =============================================================================
958fn generate_quote(_samples_dir: &Path) -> Document {
959 let mut doc = Document::new();
960
961 // Colors: Teal #008B8B, Dark #1A3C3C, Orange #E07020, Light #F0F8F8
962 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
963 doc.set_margins(
964 Length::inches(0.75),
965 Length::inches(0.75),
966 Length::inches(0.75),
967 Length::inches(0.75),
968 );
969 doc.set_title("Quotation QT-2026-0147");
970 doc.set_author("Walter White");
971 doc.set_keywords("quote, BOM, Tensorbee");
972
973 doc.set_header("Tensorbee — Quotation");
974 doc.set_footer("QT-2026-0147 | Page");
975
976 // ── Header Block ──
977 {
978 let mut p = doc.add_paragraph("").alignment(Alignment::Left);
979 p.add_run("TENSORBEE")
980 .bold(true)
981 .size(28.0)
982 .color("008B8B")
983 .font("Helvetica");
984 }
985 doc.add_paragraph("123 Innovation Drive, Suite 400")
986 .alignment(Alignment::Left);
987 doc.add_paragraph("San Francisco, CA 94105 | +1 (415) 555-0199")
988 .alignment(Alignment::Left);
989 doc.add_paragraph("")
990 .border_bottom(BorderStyle::Single, 8, "008B8B");
991
992 // Quote meta
993 doc.add_paragraph("");
994 {
995 let mut tbl = doc.add_table(4, 4).width_pct(100.0);
996 tbl.cell(0, 0).unwrap().set_text("QUOTATION");
997 tbl.cell(0, 0).unwrap().shading("008B8B").grid_span(2);
998 tbl.cell(0, 2).unwrap().set_text("DATE");
999 tbl.cell(0, 2).unwrap().shading("008B8B");
1000 tbl.cell(0, 3).unwrap().set_text("VALID UNTIL");
1001 tbl.cell(0, 3).unwrap().shading("008B8B");
1002 tbl.cell(1, 0).unwrap().set_text("Quote #:");
1003 tbl.cell(1, 1).unwrap().set_text("QT-2026-0147");
1004 tbl.cell(1, 2).unwrap().set_text("Feb 22, 2026");
1005 tbl.cell(1, 3).unwrap().set_text("Mar 22, 2026");
1006 tbl.cell(2, 0).unwrap().set_text("Prepared by:");
1007 tbl.cell(2, 1).unwrap().set_text("Walter White");
1008 tbl.cell(2, 2).unwrap().set_text("Payment:");
1009 tbl.cell(2, 3).unwrap().set_text("Net 30");
1010 tbl.cell(3, 0).unwrap().set_text("Customer:");
1011 tbl.cell(3, 1).unwrap().set_text("Meridian Dynamics LLC");
1012 tbl.cell(3, 2).unwrap().set_text("Currency:");
1013 tbl.cell(3, 3).unwrap().set_text("USD");
1014 }
1015
1016 doc.add_paragraph("");
1017
1018 // ── Bill of Materials ──
1019 {
1020 let mut p = doc.add_paragraph("");
1021 p.add_run("Bill of Materials")
1022 .bold(true)
1023 .size(16.0)
1024 .color("1A3C3C");
1025 }
1026 doc.add_paragraph("");
1027
1028 {
1029 let mut tbl = doc
1030 .add_table(10, 6)
1031 .borders(BorderStyle::Single, 2, "008B8B")
1032 .width_pct(100.0)
1033 .layout_fixed();
1034 // Headers
1035 let hdrs = [
1036 "#",
1037 "Part Number",
1038 "Description",
1039 "Qty",
1040 "Unit Price",
1041 "Total",
1042 ];
1043 for (c, h) in hdrs.iter().enumerate() {
1044 tbl.cell(0, c).unwrap().set_text(h);
1045 tbl.cell(0, c).unwrap().shading("008B8B");
1046 }
1047
1048 let items: Vec<(&str, &str, &str, &str, &str)> = vec![
1049 (
1050 "TB-GPU-A100",
1051 "NVIDIA A100 80GB GPU",
1052 "4",
1053 "$12,500.00",
1054 "$50,000.00",
1055 ),
1056 (
1057 "TB-SRV-R750",
1058 "Dell R750xa Server Chassis",
1059 "2",
1060 "$8,200.00",
1061 "$16,400.00",
1062 ),
1063 (
1064 "TB-RAM-256G",
1065 "256GB DDR5 ECC Memory Module",
1066 "8",
1067 "$890.00",
1068 "$7,120.00",
1069 ),
1070 (
1071 "TB-SSD-3840",
1072 "3.84TB NVMe U.2 SSD",
1073 "8",
1074 "$1,150.00",
1075 "$9,200.00",
1076 ),
1077 (
1078 "TB-NET-CX7",
1079 "ConnectX-7 200GbE NIC",
1080 "4",
1081 "$1,800.00",
1082 "$7,200.00",
1083 ),
1084 (
1085 "TB-SW-48P",
1086 "48-Port 100GbE Switch",
1087 "1",
1088 "$22,000.00",
1089 "$22,000.00",
1090 ),
1091 (
1092 "TB-CAB-RACK",
1093 "42U Server Rack + PDU",
1094 "1",
1095 "$4,500.00",
1096 "$4,500.00",
1097 ),
1098 (
1099 "TB-SVC-INST",
1100 "Installation & Configuration",
1101 "1",
1102 "$8,500.00",
1103 "$8,500.00",
1104 ),
1105 (
1106 "TB-SVC-SUPP",
1107 "3-Year Premium Support",
1108 "1",
1109 "$15,000.00",
1110 "$15,000.00",
1111 ),
1112 ];
1113
1114 for (i, (pn, desc, qty, unit, total)) in items.iter().enumerate() {
1115 let row = i + 1;
1116 tbl.cell(row, 0).unwrap().set_text(&format!("{}", i + 1));
1117 tbl.cell(row, 1).unwrap().set_text(pn);
1118 tbl.cell(row, 2).unwrap().set_text(desc);
1119 tbl.cell(row, 3).unwrap().set_text(qty);
1120 tbl.cell(row, 4).unwrap().set_text(unit);
1121 tbl.cell(row, 5).unwrap().set_text(total);
1122 if i % 2 == 0 {
1123 for c in 0..6 {
1124 tbl.cell(row, c).unwrap().shading("F0F8F8");
1125 }
1126 }
1127 }
1128 }
1129
1130 doc.add_paragraph("");
1131
1132 // ── Totals ──
1133 {
1134 let mut tbl = doc
1135 .add_table(4, 2)
1136 .borders(BorderStyle::Single, 2, "008B8B")
1137 .width(Length::inches(3.5))
1138 .alignment(Alignment::Right);
1139 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1140 tbl.cell(0, 1).unwrap().set_text("$139,920.00");
1141 tbl.cell(1, 0).unwrap().set_text("Shipping & Handling");
1142 tbl.cell(1, 1).unwrap().set_text("$2,500.00");
1143 tbl.cell(2, 0).unwrap().set_text("Tax (8.625%)");
1144 tbl.cell(2, 1).unwrap().set_text("$12,068.10");
1145 tbl.cell(3, 0).unwrap().set_text("TOTAL");
1146 tbl.cell(3, 0).unwrap().shading("E07020");
1147 tbl.cell(3, 1).unwrap().set_text("$154,488.10");
1148 tbl.cell(3, 1).unwrap().shading("E07020");
1149 }
1150
1151 doc.add_paragraph("");
1152
1153 // ── Terms & Conditions ──
1154 {
1155 let mut p = doc.add_paragraph("");
1156 p.add_run("Terms & Conditions")
1157 .bold(true)
1158 .size(14.0)
1159 .color("1A3C3C");
1160 }
1161 doc.add_numbered_list_item(
1162 "This quotation is valid for 30 calendar days from the date of issue.",
1163 0,
1164 );
1165 doc.add_numbered_list_item(
1166 "All prices are in USD and exclusive of applicable taxes unless stated.",
1167 0,
1168 );
1169 doc.add_numbered_list_item("Standard lead time is 4-6 weeks from PO receipt.", 0);
1170 doc.add_numbered_list_item(
1171 "Payment terms: Net 30 from invoice date. 2% discount for payment within 10 days.",
1172 0,
1173 );
1174 doc.add_numbered_list_item(
1175 "Warranty: 3-year manufacturer warranty on all hardware components.",
1176 0,
1177 );
1178 doc.add_numbered_list_item(
1179 "Returns subject to 15% restocking fee if initiated after 14 days.",
1180 0,
1181 );
1182
1183 doc.add_paragraph("");
1184 doc.add_paragraph("")
1185 .border_bottom(BorderStyle::Single, 4, "008B8B");
1186 doc.add_paragraph("");
1187 {
1188 let mut p = doc.add_paragraph("");
1189 p.add_run("Accepted by: ").bold(true);
1190 p.add_run("___________________________ Date: ___________");
1191 }
1192 {
1193 let mut p = doc.add_paragraph("");
1194 p.add_run("Print Name: ").bold(true);
1195 p.add_run("___________________________ Title: ___________");
1196 }
1197
1198 doc
1199}
1200
1201// =============================================================================
1202// 4. INVOICE — Crimson + charcoal scheme
1203// =============================================================================
1204fn generate_invoice(_samples_dir: &Path) -> Document {
1205 let mut doc = Document::new();
1206
1207 // Colors: Crimson #B22222, Charcoal #333333, Light #FAF0F0
1208 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1209 doc.set_margins(
1210 Length::inches(0.75),
1211 Length::inches(0.75),
1212 Length::inches(0.75),
1213 Length::inches(0.75),
1214 );
1215 doc.set_title("Invoice INV-2026-0392");
1216 doc.set_author("Walter White");
1217 doc.set_keywords("invoice, Tensorbee");
1218
1219 doc.set_footer("Tensorbee — Thank you for your business!");
1220
1221 // ── Company & Invoice Header ──
1222 {
1223 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1224 // Company name left, INVOICE right
1225 {
1226 let mut cell = tbl.cell(0, 0).unwrap();
1227 let mut p = cell.add_paragraph("");
1228 p.add_run("TENSORBEE")
1229 .bold(true)
1230 .size(32.0)
1231 .color("B22222")
1232 .font("Helvetica");
1233 }
1234 {
1235 let mut cell = tbl.cell(0, 1).unwrap();
1236 let mut p = cell.add_paragraph("");
1237 p.add_run("INVOICE").bold(true).size(32.0).color("333333");
1238 }
1239 tbl.cell(1, 0)
1240 .unwrap()
1241 .set_text("123 Innovation Drive, Suite 400");
1242 tbl.cell(1, 1).unwrap().set_text("Invoice #: INV-2026-0392");
1243 tbl.cell(2, 0).unwrap().set_text("San Francisco, CA 94105");
1244 tbl.cell(2, 1).unwrap().set_text("Date: February 22, 2026");
1245 tbl.cell(3, 0).unwrap().set_text("walter@tensorbee.com");
1246 tbl.cell(3, 1).unwrap().set_text("Due Date: March 24, 2026");
1247 }
1248
1249 doc.add_paragraph("")
1250 .border_bottom(BorderStyle::Thick, 8, "B22222");
1251 doc.add_paragraph("");
1252
1253 // ── Bill To / Ship To ──
1254 {
1255 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1256 tbl.cell(0, 0).unwrap().set_text("BILL TO");
1257 tbl.cell(0, 0).unwrap().shading("B22222");
1258 tbl.cell(0, 1).unwrap().set_text("SHIP TO");
1259 tbl.cell(0, 1).unwrap().shading("B22222");
1260 tbl.cell(1, 0).unwrap().set_text("Meridian Dynamics LLC");
1261 tbl.cell(1, 1).unwrap().set_text("Meridian Dynamics LLC");
1262 tbl.cell(2, 0).unwrap().set_text("456 Enterprise Blvd");
1263 tbl.cell(2, 1).unwrap().set_text("Attn: Server Room B");
1264 tbl.cell(3, 0).unwrap().set_text("Austin, TX 78701");
1265 tbl.cell(3, 1)
1266 .unwrap()
1267 .set_text("456 Enterprise Blvd, Austin, TX 78701");
1268 }
1269
1270 doc.add_paragraph("");
1271
1272 // ── Line Items ──
1273 {
1274 let mut tbl = doc
1275 .add_table(8, 5)
1276 .borders(BorderStyle::Single, 2, "B22222")
1277 .width_pct(100.0);
1278 let hdrs = ["Description", "Qty", "Unit Price", "Tax", "Amount"];
1279 for (c, h) in hdrs.iter().enumerate() {
1280 tbl.cell(0, c).unwrap().set_text(h);
1281 tbl.cell(0, c).unwrap().shading("B22222");
1282 }
1283 let items = [
1284 (
1285 "ML Infrastructure Setup — Phase 1",
1286 "1",
1287 "$45,000.00",
1288 "$3,881.25",
1289 "$48,881.25",
1290 ),
1291 (
1292 "Data Pipeline Development (160 hrs)",
1293 "160",
1294 "$225.00",
1295 "$3,105.00",
1296 "$39,105.00",
1297 ),
1298 (
1299 "GPU Cluster Configuration",
1300 "1",
1301 "$12,000.00",
1302 "$1,035.00",
1303 "$13,035.00",
1304 ),
1305 (
1306 "API Gateway Implementation",
1307 "1",
1308 "$18,500.00",
1309 "$1,595.63",
1310 "$20,095.63",
1311 ),
1312 (
1313 "Load Testing & QA (80 hrs)",
1314 "80",
1315 "$195.00",
1316 "$1,345.50",
1317 "$16,945.50",
1318 ),
1319 (
1320 "Documentation & Training",
1321 "1",
1322 "$8,000.00",
1323 "$690.00",
1324 "$8,690.00",
1325 ),
1326 (
1327 "Project Management (3 months)",
1328 "3",
1329 "$6,500.00",
1330 "$1,679.63",
1331 "$21,179.63",
1332 ),
1333 ];
1334 for (i, (desc, qty, unit, tax, amt)) in items.iter().enumerate() {
1335 let r = i + 1;
1336 tbl.cell(r, 0).unwrap().set_text(desc);
1337 tbl.cell(r, 1).unwrap().set_text(qty);
1338 tbl.cell(r, 2).unwrap().set_text(unit);
1339 tbl.cell(r, 3).unwrap().set_text(tax);
1340 tbl.cell(r, 4).unwrap().set_text(amt);
1341 if i % 2 == 0 {
1342 for c in 0..5 {
1343 tbl.cell(r, c).unwrap().shading("FAF0F0");
1344 }
1345 }
1346 }
1347 }
1348
1349 doc.add_paragraph("");
1350
1351 // ── Totals ──
1352 {
1353 let mut tbl = doc
1354 .add_table(4, 2)
1355 .borders(BorderStyle::Single, 2, "B22222")
1356 .width(Length::inches(3.0))
1357 .alignment(Alignment::Right);
1358 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1359 tbl.cell(0, 1).unwrap().set_text("$154,600.00");
1360 tbl.cell(1, 0).unwrap().set_text("Tax (8.625%)");
1361 tbl.cell(1, 1).unwrap().set_text("$13,334.25");
1362 tbl.cell(2, 0).unwrap().set_text("Discount (5%)");
1363 tbl.cell(2, 1).unwrap().set_text("-$7,730.00");
1364 tbl.cell(3, 0).unwrap().set_text("AMOUNT DUE");
1365 tbl.cell(3, 0).unwrap().shading("B22222");
1366 tbl.cell(3, 1).unwrap().set_text("$160,204.25");
1367 tbl.cell(3, 1).unwrap().shading("B22222");
1368 }
1369
1370 doc.add_paragraph("");
1371 doc.add_paragraph("");
1372
1373 // ── Payment Details ──
1374 {
1375 let mut p = doc.add_paragraph("");
1376 p.add_run("Payment Details")
1377 .bold(true)
1378 .size(14.0)
1379 .color("333333");
1380 }
1381 doc.add_paragraph("Bank: Silicon Valley Bank")
1382 .indent_left(Length::inches(0.3));
1383 doc.add_paragraph("Account: Tensorbee Inc. — 0847-2953-1120")
1384 .indent_left(Length::inches(0.3));
1385 doc.add_paragraph("Routing: 121140399")
1386 .indent_left(Length::inches(0.3));
1387 doc.add_paragraph("Swift: SVBKUS6S")
1388 .indent_left(Length::inches(0.3));
1389
1390 doc.add_paragraph("");
1391
1392 doc.add_paragraph("Please include invoice number INV-2026-0392 in the payment reference.")
1393 .shading("FAF0F0")
1394 .border_all(BorderStyle::Single, 2, "B22222");
1395
1396 doc
1397}
1398
1399// =============================================================================
1400// 5. REPORT — Forest green + earth tones, with images & hierarchical sections
1401// =============================================================================
1402fn generate_report(_samples_dir: &Path) -> Document {
1403 let mut doc = Document::new();
1404
1405 // Colors: Forest #2D5016, Sage #6B8E23, Earth #8B7355, Cream #FFFAF0
1406 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1407 doc.set_margins(
1408 Length::inches(1.0),
1409 Length::inches(1.0),
1410 Length::inches(1.0),
1411 Length::inches(1.0),
1412 );
1413 doc.set_title("Q4 2025 Environmental Impact Report");
1414 doc.set_author("Walter White");
1415 doc.set_subject("Quarterly Environmental Report");
1416 doc.set_keywords("environment, sustainability, report, Tensorbee");
1417
1418 doc.set_different_first_page(true);
1419 doc.set_first_page_header("");
1420 doc.set_header("Tensorbee — Q4 2025 Environmental Impact Report");
1421 doc.set_footer("CONFIDENTIAL — Page");
1422
1423 // ── Cover ──
1424 let cover_bg = create_sample_png(612, 792, [20, 50, 15]);
1425 doc.add_background_image(&cover_bg, "report_cover.png");
1426
1427 for _ in 0..5 {
1428 doc.add_paragraph("");
1429 }
1430 {
1431 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1432 p.add_run("Q4 2025")
1433 .bold(true)
1434 .size(48.0)
1435 .color("FFFFFF")
1436 .font("Georgia");
1437 }
1438 {
1439 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1440 p.add_run("Environmental Impact Report")
1441 .size(24.0)
1442 .color("90EE90")
1443 .font("Georgia")
1444 .italic(true);
1445 }
1446 doc.add_paragraph("");
1447 {
1448 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1449 p.add_run("Tensorbee — Sustainability Division")
1450 .size(14.0)
1451 .color("C0C0C0");
1452 }
1453 {
1454 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1455 p.add_run("Prepared by Walter White, Chief Sustainability Officer")
1456 .size(11.0)
1457 .color("AAAAAA");
1458 }
1459
1460 // ── TOC ──
1461 doc.add_paragraph("").page_break_before(true);
1462 doc.insert_toc(doc.content_count(), 3);
1463
1464 // ── Section 1: Executive Overview ──
1465 doc.add_paragraph("").page_break_before(true);
1466 doc.add_paragraph("Executive Overview").style("Heading1");
1467 doc.add_paragraph(
1468 "This report presents Tensorbee's environmental performance for Q4 2025. Our \
1469 sustainability initiatives have yielded a 34% reduction in carbon emissions compared \
1470 to Q4 2024, exceeding our target of 25%. Key achievements include the transition to \
1471 100% renewable energy in our primary data centers and the launch of our carbon offset \
1472 marketplace.",
1473 )
1474 .first_line_indent(Length::inches(0.3));
1475
1476 // Image: performance chart
1477 let chart_img = create_chart_png(400, 200);
1478 doc.add_paragraph("");
1479 doc.add_picture(
1480 &chart_img,
1481 "performance_chart.png",
1482 Length::inches(5.0),
1483 Length::inches(2.5),
1484 )
1485 .alignment(Alignment::Center);
1486 {
1487 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1488 p.add_run("Figure 1: Quarterly Carbon Emissions (tonnes CO2e)")
1489 .italic(true)
1490 .size(9.0)
1491 .color("666666");
1492 }
1493
1494 // ── Section 2: Energy Consumption ──
1495 doc.add_paragraph("").page_break_before(true);
1496 doc.add_paragraph("Energy Consumption").style("Heading1");
1497
1498 doc.add_paragraph("Data Center Operations")
1499 .style("Heading2");
1500 doc.add_paragraph(
1501 "Our three primary data centers consumed a combined 4.2 GWh in Q4 2025, \
1502 a 12% reduction from Q3 2025 driven by improved cooling efficiency and \
1503 server consolidation.",
1504 );
1505
1506 // Data table
1507 {
1508 let mut tbl = doc
1509 .add_table(5, 4)
1510 .borders(BorderStyle::Single, 4, "2D5016")
1511 .width_pct(100.0);
1512 let hdrs = ["Data Center", "Capacity (MW)", "Usage (GWh)", "PUE"];
1513 for (c, h) in hdrs.iter().enumerate() {
1514 tbl.cell(0, c).unwrap().set_text(h);
1515 tbl.cell(0, c).unwrap().shading("2D5016");
1516 }
1517 let rows = [
1518 ("San Francisco (Primary)", "3.2", "1.8", "1.12"),
1519 ("Dublin (EU)", "2.1", "1.2", "1.18"),
1520 ("Singapore (APAC)", "1.8", "1.2", "1.24"),
1521 ("TOTAL", "7.1", "4.2", "1.17 avg"),
1522 ];
1523 for (i, (dc, cap, use_, pue)) in rows.iter().enumerate() {
1524 tbl.cell(i + 1, 0).unwrap().set_text(dc);
1525 tbl.cell(i + 1, 1).unwrap().set_text(cap);
1526 tbl.cell(i + 1, 2).unwrap().set_text(use_);
1527 tbl.cell(i + 1, 3).unwrap().set_text(pue);
1528 if i == 3 {
1529 for c in 0..4 {
1530 tbl.cell(i + 1, c).unwrap().shading("E2EFDA");
1531 }
1532 }
1533 }
1534 }
1535
1536 doc.add_paragraph("");
1537
1538 doc.add_paragraph("Renewable Energy Mix").style("Heading2");
1539 doc.add_paragraph("Breakdown of energy sources across all facilities:");
1540
1541 doc.add_bullet_list_item("Solar PV: 42% (1.76 GWh)", 0);
1542 doc.add_bullet_list_item("Wind Power Purchase Agreements: 38% (1.60 GWh)", 0);
1543 doc.add_bullet_list_item("Hydroelectric: 12% (0.50 GWh)", 0);
1544 doc.add_bullet_list_item("Grid (non-renewable): 8% (0.34 GWh)", 0);
1545
1546 // ── Section 3: Water & Waste ──
1547 doc.add_paragraph("Water & Waste Management")
1548 .style("Heading1");
1549
1550 doc.add_paragraph("Water Usage").style("Heading2");
1551 doc.add_paragraph(
1552 "Total water consumption was 12.4 million gallons, a 15% reduction from Q3. \
1553 Our closed-loop cooling systems now recycle 78% of water used in cooling operations.",
1554 );
1555
1556 doc.add_paragraph("Waste Reduction").style("Heading2");
1557 doc.add_paragraph("E-waste management results for Q4:")
1558 .keep_with_next(true);
1559 {
1560 let mut tbl = doc
1561 .add_table(4, 3)
1562 .borders(BorderStyle::Single, 2, "6B8E23")
1563 .width_pct(80.0);
1564 tbl.cell(0, 0).unwrap().set_text("Category");
1565 tbl.cell(0, 0).unwrap().shading("6B8E23");
1566 tbl.cell(0, 1).unwrap().set_text("Weight (kg)");
1567 tbl.cell(0, 1).unwrap().shading("6B8E23");
1568 tbl.cell(0, 2).unwrap().set_text("Recycled %");
1569 tbl.cell(0, 2).unwrap().shading("6B8E23");
1570 let rows = [
1571 ("Server Hardware", "2,450", "94%"),
1572 ("Networking Equipment", "820", "91%"),
1573 ("Storage Media", "340", "99%"),
1574 ];
1575 for (i, (cat, wt, pct)) in rows.iter().enumerate() {
1576 tbl.cell(i + 1, 0).unwrap().set_text(cat);
1577 tbl.cell(i + 1, 1).unwrap().set_text(wt);
1578 tbl.cell(i + 1, 2).unwrap().set_text(pct);
1579 }
1580 }
1581
1582 // ── Section 4: Initiatives ──
1583 doc.add_paragraph("").page_break_before(true);
1584 doc.add_paragraph("Q1 2026 Initiatives").style("Heading1");
1585
1586 doc.add_paragraph("Planned Programs").style("Heading2");
1587 doc.add_numbered_list_item(
1588 "Deploy on-site battery storage at SF data center (2 MWh capacity)",
1589 0,
1590 );
1591 doc.add_numbered_list_item("Pilot immersion cooling in Dublin facility", 0);
1592 doc.add_numbered_list_item("Launch employee commute carbon offset program", 0);
1593 doc.add_numbered_list_item("Achieve ISO 14001 certification for Singapore facility", 0);
1594
1595 doc.add_paragraph("Investment Targets").style("Heading2");
1596 doc.add_paragraph("Sustainability CapEx allocation for FY2026:")
1597 .keep_with_next(true);
1598 {
1599 let mut tbl = doc
1600 .add_table(5, 2)
1601 .borders(BorderStyle::Single, 4, "2D5016");
1602 tbl.cell(0, 0).unwrap().set_text("Initiative");
1603 tbl.cell(0, 0).unwrap().shading("2D5016");
1604 tbl.cell(0, 1).unwrap().set_text("Budget");
1605 tbl.cell(0, 1).unwrap().shading("2D5016");
1606 let rows = [
1607 ("Battery Storage", "$1.2M"),
1608 ("Immersion Cooling Pilot", "$800K"),
1609 ("Solar Panel Expansion", "$2.1M"),
1610 ("Carbon Credits", "$500K"),
1611 ];
1612 for (i, (init, budget)) in rows.iter().enumerate() {
1613 tbl.cell(i + 1, 0).unwrap().set_text(init);
1614 tbl.cell(i + 1, 1).unwrap().set_text(budget);
1615 if i % 2 == 0 {
1616 tbl.cell(i + 1, 0).unwrap().shading("FFFAF0");
1617 tbl.cell(i + 1, 1).unwrap().shading("FFFAF0");
1618 }
1619 }
1620 }
1621
1622 // Image: sustainability roadmap
1623 let roadmap_img = create_sample_png(500, 100, [40, 80, 30]);
1624 doc.add_paragraph("");
1625 doc.add_picture(
1626 &roadmap_img,
1627 "roadmap.png",
1628 Length::inches(6.0),
1629 Length::inches(1.2),
1630 )
1631 .alignment(Alignment::Center);
1632 {
1633 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1634 p.add_run("Figure 2: 2026 Sustainability Roadmap")
1635 .italic(true)
1636 .size(9.0)
1637 .color("666666");
1638 }
1639
1640 doc.add_paragraph("");
1641 doc.add_paragraph(
1642 "For questions about this report, contact Walter White at sustainability@tensorbee.com.",
1643 )
1644 .shading("FFFAF0")
1645 .border_all(BorderStyle::Single, 2, "2D5016");
1646
1647 doc
1648}
1649
1650// =============================================================================
1651// 6. LETTER — Slate blue + silver scheme
1652// =============================================================================
1653fn generate_letter(_samples_dir: &Path) -> Document {
1654 let mut doc = Document::new();
1655
1656 // Colors: Slate #4A5568, Blue accent #3182CE
1657 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1658 doc.set_margins(
1659 Length::inches(1.25),
1660 Length::inches(1.25),
1661 Length::inches(1.25),
1662 Length::inches(1.25),
1663 );
1664 doc.set_title("Business Letter");
1665 doc.set_author("Walter White");
1666
1667 // Banner header using raw XML
1668 let logo_img = create_logo_png(220, 48);
1669 let banner = build_header_banner_xml(
1670 "rId1",
1671 &BannerOpts {
1672 bg_color: "4A5568",
1673 banner_width: 7772400,
1674 banner_height: 731520, // ~0.8"
1675 logo_width: 2011680,
1676 logo_height: 438912,
1677 logo_x_offset: 295125,
1678 logo_y_offset: 146304,
1679 },
1680 );
1681 doc.set_raw_header_with_images(
1682 banner,
1683 &[("rId1", &logo_img, "logo.png")],
1684 rdocx_oxml::header_footer::HdrFtrType::Default,
1685 );
1686 doc.set_margins(
1687 Length::twips(2000),
1688 Length::twips(1800),
1689 Length::twips(1440),
1690 Length::twips(1800),
1691 );
1692 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
1693
1694 // Footer with contact
1695 doc.set_footer(
1696 "Tensorbee | 123 Innovation Drive, Suite 400 | San Francisco, CA 94105 | tensorbee.com",
1697 );
1698
1699 // ── Sender Address ──
1700 doc.add_paragraph("Walter White");
1701 doc.add_paragraph("Chief Executive Officer");
1702 doc.add_paragraph("Tensorbee");
1703 doc.add_paragraph("123 Innovation Drive, Suite 400");
1704 doc.add_paragraph("San Francisco, CA 94105");
1705 doc.add_paragraph("");
1706 doc.add_paragraph("February 22, 2026");
1707
1708 doc.add_paragraph("");
1709
1710 // ── Recipient ──
1711 doc.add_paragraph("Ms. Sarah Chen");
1712 doc.add_paragraph("VP of Engineering");
1713 doc.add_paragraph("Meridian Dynamics LLC");
1714 doc.add_paragraph("456 Enterprise Boulevard");
1715 doc.add_paragraph("Austin, TX 78701");
1716
1717 doc.add_paragraph("");
1718
1719 // ── Subject ──
1720 {
1721 let mut p = doc.add_paragraph("");
1722 p.add_run("Re: Strategic Technology Partnership — Phase 2 Expansion")
1723 .bold(true);
1724 }
1725
1726 doc.add_paragraph("");
1727
1728 // ── Body ──
1729 doc.add_paragraph("Dear Ms. Chen,");
1730 doc.add_paragraph("");
1731
1732 doc.add_paragraph(
1733 "I am writing to express Tensorbee's enthusiasm for expanding our strategic technology \
1734 partnership with Meridian Dynamics. Following the successful completion of Phase 1, \
1735 which delivered a 40% improvement in your ML inference pipeline throughput, we believe \
1736 the foundation is firmly established for an ambitious Phase 2 engagement.",
1737 )
1738 .first_line_indent(Length::inches(0.5));
1739
1740 doc.add_paragraph(
1741 "During our recent executive review, your team highlighted three priority areas for \
1742 the next phase of collaboration:",
1743 )
1744 .first_line_indent(Length::inches(0.5));
1745
1746 doc.add_numbered_list_item(
1747 "Real-time anomaly detection for your financial transaction monitoring system, \
1748 targeting sub-100ms latency at 50,000 transactions per second.",
1749 0,
1750 );
1751 doc.add_numbered_list_item(
1752 "Federated learning infrastructure to enable model training across your distributed \
1753 data centers without centralizing sensitive financial data.",
1754 0,
1755 );
1756 doc.add_numbered_list_item(
1757 "MLOps automation to reduce your model deployment cycle from the current 5 days \
1758 to under 4 hours.",
1759 0,
1760 );
1761
1762 doc.add_paragraph(
1763 "Our engineering team has prepared a detailed technical proposal addressing each of \
1764 these areas. We have allocated a dedicated team of eight senior engineers, led by \
1765 our CTO, to ensure continuity with the Phase 1 team your organization has already \
1766 built a productive working relationship with.",
1767 )
1768 .first_line_indent(Length::inches(0.5));
1769
1770 doc.add_paragraph(
1771 "I would welcome the opportunity to present our Phase 2 proposal in person. My \
1772 assistant will reach out to coordinate a meeting at your convenience during the \
1773 first week of March.",
1774 )
1775 .first_line_indent(Length::inches(0.5));
1776
1777 doc.add_paragraph("");
1778
1779 doc.add_paragraph("Warm regards,");
1780 doc.add_paragraph("");
1781 doc.add_paragraph("");
1782
1783 // Signature line
1784 doc.add_paragraph("")
1785 .border_bottom(BorderStyle::Single, 4, "4A5568");
1786 {
1787 let mut p = doc.add_paragraph("");
1788 p.add_run("Walter White").bold(true).size(12.0);
1789 }
1790 {
1791 let mut p = doc.add_paragraph("");
1792 p.add_run("Chief Executive Officer, Tensorbee")
1793 .italic(true)
1794 .size(10.0)
1795 .color("666666");
1796 }
1797 {
1798 let mut p = doc.add_paragraph("");
1799 p.add_run("walter@tensorbee.com | +1 (415) 555-0199")
1800 .size(10.0)
1801 .color("888888");
1802 }
1803
1804 doc
1805}
1806
1807// =============================================================================
1808// 7. EMPLOYMENT CONTRACT — Purple + charcoal scheme
1809// =============================================================================
1810fn generate_contract(_samples_dir: &Path) -> Document {
1811 let mut doc = Document::new();
1812
1813 // Colors: Purple #5B2C6F, Plum #8E44AD, Gray #2C3E50
1814 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1815 doc.set_margins(
1816 Length::inches(1.25),
1817 Length::inches(1.0),
1818 Length::inches(1.0),
1819 Length::inches(1.0),
1820 );
1821 doc.set_title("Employment Agreement");
1822 doc.set_author("Tensorbee HR Department");
1823 doc.set_subject("Employment Contract — Walter White");
1824 doc.set_keywords("employment, contract, agreement, Tensorbee");
1825
1826 doc.set_header("TENSORBEE — Employment Agreement");
1827 doc.set_footer("Employment Agreement — Walter White — Page");
1828
1829 // ── Title Block ──
1830 doc.add_paragraph("")
1831 .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1832 {
1833 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1834 p.add_run("EMPLOYMENT AGREEMENT")
1835 .bold(true)
1836 .size(24.0)
1837 .color("5B2C6F")
1838 .font("Georgia");
1839 }
1840 doc.add_paragraph("")
1841 .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1842 doc.add_paragraph("");
1843
1844 // ── Parties ──
1845 doc.add_paragraph(
1846 "This Employment Agreement (\"Agreement\") is entered into as of February 22, 2026 \
1847 (\"Effective Date\"), by and between:",
1848 );
1849
1850 doc.add_paragraph("");
1851
1852 {
1853 let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1854 p.add_run("EMPLOYER: ").bold(true);
1855 p.add_run(
1856 "Tensorbee, Inc., a Delaware corporation with its principal place of business at \
1857 123 Innovation Drive, Suite 400, San Francisco, CA 94105 (\"Company\")",
1858 );
1859 }
1860 doc.add_paragraph("");
1861 {
1862 let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1863 p.add_run("EMPLOYEE: ").bold(true);
1864 p.add_run(
1865 "Walter White, residing at 308 Negra Arroyo Lane, Albuquerque, NM 87104 (\"Employee\")",
1866 );
1867 }
1868
1869 doc.add_paragraph("");
1870 doc.add_paragraph("The Company and Employee are collectively referred to as the \"Parties.\"");
1871 doc.add_paragraph("");
1872
1873 // ── Article 1: Position and Duties ──
1874 doc.add_paragraph("Article 1 — Position and Duties")
1875 .style("Heading1");
1876 {
1877 let mut p = doc.add_paragraph("");
1878 p.add_run("1.1 ").bold(true);
1879 p.add_run("The Company hereby employs the Employee as ");
1880 p.add_run("Chief Executive Officer (CEO)")
1881 .bold(true)
1882 .italic(true);
1883 p.add_run(", reporting directly to the Board of Directors.");
1884 }
1885 {
1886 let mut p = doc.add_paragraph("");
1887 p.add_run("1.2 ").bold(true);
1888 p.add_run("The Employee shall devote full working time, attention, and best efforts to \
1889 the performance of duties as reasonably assigned by the Board, including but not limited to:");
1890 }
1891 doc.add_bullet_list_item(
1892 "Setting and executing the Company's strategic vision and business plan",
1893 0,
1894 );
1895 doc.add_bullet_list_item(
1896 "Overseeing all operations, engineering, and commercial activities",
1897 0,
1898 );
1899 doc.add_bullet_list_item(
1900 "Representing the Company to investors, customers, and the public",
1901 0,
1902 );
1903 doc.add_bullet_list_item("Recruiting, developing, and retaining key talent", 0);
1904
1905 {
1906 let mut p = doc.add_paragraph("");
1907 p.add_run("1.3 ").bold(true);
1908 p.add_run(
1909 "The Employee's primary work location shall be the Company's San Francisco \
1910 headquarters, with reasonable travel as required by business needs.",
1911 );
1912 }
1913
1914 // ── Article 2: Compensation ──
1915 doc.add_paragraph("Article 2 — Compensation and Benefits")
1916 .style("Heading1");
1917 {
1918 let mut p = doc.add_paragraph("");
1919 p.add_run("2.1 Base Salary. ").bold(true);
1920 p.add_run("The Company shall pay the Employee an annual base salary of ");
1921 p.add_run("$375,000.00").bold(true);
1922 p.add_run(" (Three Hundred Seventy-Five Thousand Dollars), payable in accordance with the \
1923 Company's standard payroll schedule, less applicable withholdings and deductions.");
1924 }
1925 {
1926 let mut p = doc.add_paragraph("");
1927 p.add_run("2.2 Annual Bonus. ").bold(true);
1928 p.add_run("The Employee shall be eligible for an annual performance bonus of up to ");
1929 p.add_run("40%").bold(true);
1930 p.add_run(
1931 " of base salary, based on achievement of mutually agreed performance objectives.",
1932 );
1933 }
1934 {
1935 let mut p = doc.add_paragraph("");
1936 p.add_run("2.3 Equity. ").bold(true);
1937 p.add_run("Subject to Board approval, the Employee shall receive a stock option grant of ");
1938 p.add_run("500,000 shares").bold(true);
1939 p.add_run(
1940 " of the Company's common stock, vesting over four (4) years with a one-year cliff, \
1941 at an exercise price equal to the fair market value on the date of grant.",
1942 );
1943 }
1944
1945 // Compensation summary table
1946 doc.add_paragraph("");
1947 {
1948 let mut tbl = doc
1949 .add_table(5, 2)
1950 .borders(BorderStyle::Single, 4, "5B2C6F")
1951 .width_pct(70.0)
1952 .alignment(Alignment::Center);
1953 tbl.cell(0, 0).unwrap().set_text("Compensation Element");
1954 tbl.cell(0, 0).unwrap().shading("5B2C6F");
1955 tbl.cell(0, 1).unwrap().set_text("Value");
1956 tbl.cell(0, 1).unwrap().shading("5B2C6F");
1957 let items = [
1958 ("Base Salary", "$375,000/year"),
1959 ("Target Bonus", "Up to 40% ($150,000)"),
1960 ("Equity Grant", "500,000 shares (4yr vest)"),
1961 ("Total Target Comp", "$525,000 + equity"),
1962 ];
1963 for (i, (elem, val)) in items.iter().enumerate() {
1964 tbl.cell(i + 1, 0).unwrap().set_text(elem);
1965 tbl.cell(i + 1, 1).unwrap().set_text(val);
1966 if i == 3 {
1967 tbl.cell(i + 1, 0).unwrap().shading("E8DAEF");
1968 tbl.cell(i + 1, 1).unwrap().shading("E8DAEF");
1969 }
1970 }
1971 }
1972
1973 // ── Article 3: Benefits ──
1974 doc.add_paragraph("").page_break_before(true);
1975 doc.add_paragraph("Article 3 — Benefits").style("Heading1");
1976 {
1977 let mut p = doc.add_paragraph("");
1978 p.add_run("3.1 ").bold(true);
1979 p.add_run(
1980 "The Employee shall be entitled to participate in all benefit programs \
1981 generally available to senior executives, including:",
1982 );
1983 }
1984 doc.add_bullet_list_item(
1985 "Medical, dental, and vision insurance (100% premium coverage for employee and dependents)",
1986 0,
1987 );
1988 doc.add_bullet_list_item("401(k) retirement plan with 6% company match", 0);
1989 doc.add_bullet_list_item("Life insurance and long-term disability coverage", 0);
1990 doc.add_bullet_list_item("Annual professional development allowance of $10,000", 0);
1991
1992 {
1993 let mut p = doc.add_paragraph("");
1994 p.add_run("3.2 Paid Time Off. ").bold(true);
1995 p.add_run(
1996 "The Employee shall receive 25 days of paid vacation per year, plus Company holidays, \
1997 accruing on a monthly basis.",
1998 );
1999 }
2000
2001 // ── Article 4: Term and Termination ──
2002 doc.add_paragraph("Article 4 — Term and Termination")
2003 .style("Heading1");
2004 {
2005 let mut p = doc.add_paragraph("");
2006 p.add_run("4.1 At-Will Employment. ").bold(true);
2007 p.add_run(
2008 "This Agreement is for at-will employment and may be terminated by either Party \
2009 at any time, with or without cause, subject to the notice provisions herein.",
2010 );
2011 }
2012 {
2013 let mut p = doc.add_paragraph("");
2014 p.add_run("4.2 Notice Period. ").bold(true);
2015 p.add_run("Either Party shall provide ");
2016 p.add_run("ninety (90) days").bold(true);
2017 p.add_run(" written notice of termination, or pay in lieu thereof.");
2018 }
2019 {
2020 let mut p = doc.add_paragraph("");
2021 p.add_run("4.3 Severance. ").bold(true);
2022 p.add_run("In the event of termination by the Company without Cause, the Employee shall \
2023 receive (a) twelve (12) months of base salary continuation, (b) pro-rata bonus \
2024 for the year of termination, and (c) twelve (12) months of COBRA premium coverage.");
2025 }
2026
2027 // ── Article 5: Confidentiality ──
2028 doc.add_paragraph("Article 5 — Confidentiality and IP")
2029 .style("Heading1");
2030 {
2031 let mut p = doc.add_paragraph("");
2032 p.add_run("5.1 ").bold(true);
2033 p.add_run(
2034 "The Employee agrees to maintain strict confidentiality of all proprietary \
2035 information, trade secrets, and business plans of the Company during and after \
2036 employment.",
2037 );
2038 }
2039 {
2040 let mut p = doc.add_paragraph("");
2041 p.add_run("5.2 ").bold(true);
2042 p.add_run(
2043 "All intellectual property, inventions, and works of authorship created by the \
2044 Employee during the term of employment and within the scope of duties shall be \
2045 the sole and exclusive property of the Company.",
2046 );
2047 }
2048
2049 // ── Article 6: Non-Compete ──
2050 doc.add_paragraph("Article 6 — Non-Competition")
2051 .style("Heading1");
2052 {
2053 let mut p = doc.add_paragraph("");
2054 p.add_run("6.1 ").bold(true);
2055 p.add_run("For a period of twelve (12) months following termination, the Employee agrees \
2056 not to engage in any business that directly competes with the Company's core \
2057 business of AI infrastructure and ML operations services, within the United States.");
2058 }
2059
2060 // ── Article 7: General Provisions ──
2061 doc.add_paragraph("Article 7 — General Provisions")
2062 .style("Heading1");
2063 {
2064 let mut p = doc.add_paragraph("");
2065 p.add_run("7.1 Governing Law. ").bold(true);
2066 p.add_run(
2067 "This Agreement shall be governed by and construed in accordance with the laws of \
2068 the State of California.",
2069 );
2070 }
2071 {
2072 let mut p = doc.add_paragraph("");
2073 p.add_run("7.2 Entire Agreement. ").bold(true);
2074 p.add_run(
2075 "This Agreement constitutes the entire agreement between the Parties and supersedes \
2076 all prior negotiations, representations, and agreements.",
2077 );
2078 }
2079 {
2080 let mut p = doc.add_paragraph("");
2081 p.add_run("7.3 Amendment. ").bold(true);
2082 p.add_run(
2083 "This Agreement may only be amended by a written instrument signed by both Parties.",
2084 );
2085 }
2086
2087 // ── Signature Block ──
2088 doc.add_paragraph("").page_break_before(true);
2089 doc.add_paragraph("IN WITNESS WHEREOF, the Parties have executed this Employment Agreement as of the Effective Date.")
2090 .space_after(Length::pt(24.0));
2091
2092 // Signature table
2093 {
2094 let mut tbl = doc.add_table(6, 2).width_pct(100.0);
2095 tbl.cell(0, 0).unwrap().set_text("FOR THE COMPANY:");
2096 tbl.cell(0, 0).unwrap().shading("5B2C6F");
2097 tbl.cell(0, 1).unwrap().set_text("EMPLOYEE:");
2098 tbl.cell(0, 1).unwrap().shading("5B2C6F");
2099
2100 tbl.cell(1, 0).unwrap().set_text("");
2101 tbl.cell(1, 1).unwrap().set_text("");
2102 tbl.row(1).unwrap().height(Length::pt(40.0));
2103
2104 tbl.cell(2, 0)
2105 .unwrap()
2106 .set_text("Signature: ___________________________");
2107 tbl.cell(2, 1)
2108 .unwrap()
2109 .set_text("Signature: ___________________________");
2110 tbl.cell(3, 0)
2111 .unwrap()
2112 .set_text("Name: Board Representative");
2113 tbl.cell(3, 1).unwrap().set_text("Name: Walter White");
2114 tbl.cell(4, 0)
2115 .unwrap()
2116 .set_text("Title: Chair, Board of Directors");
2117 tbl.cell(4, 1)
2118 .unwrap()
2119 .set_text("Title: Chief Executive Officer");
2120 tbl.cell(5, 0).unwrap().set_text("Date: _______________");
2121 tbl.cell(5, 1).unwrap().set_text("Date: _______________");
2122 }
2123
2124 doc
2125}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}34fn generate_feature_showcase(path: &Path) {
35 let mut doc = Document::new();
36
37 // =========================================================================
38 // PAGE SETUP & METADATA
39 // =========================================================================
40 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
41 doc.set_margins(
42 Length::inches(1.0), // top
43 Length::inches(1.0), // right
44 Length::inches(1.0), // bottom
45 Length::inches(1.0), // left
46 );
47 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
48 doc.set_gutter(Length::twips(0));
49
50 doc.set_title("rdocx Feature Showcase");
51 doc.set_author("rdocx Sample Generator");
52 doc.set_subject("Comprehensive feature demonstration");
53 doc.set_keywords("rdocx, docx, rust, sample");
54
55 // Header & Footer
56 doc.set_header("rdocx Feature Showcase");
57 doc.set_footer("Generated by rdocx — Page");
58
59 // Different first page header
60 doc.set_different_first_page(true);
61 doc.set_first_page_header("rdocx");
62 doc.set_first_page_footer("Feature Showcase — Cover Page");
63
64 // =========================================================================
65 // PAGE 1: COVER PAGE — background image, run formatting
66 // =========================================================================
67 let bg_cover = create_sample_png(612, 792, [30, 60, 120]);
68 doc.add_background_image(&bg_cover, "cover_bg.png");
69
70 doc.add_paragraph(""); // spacer
71 doc.add_paragraph(""); // spacer
72 doc.add_paragraph(""); // spacer
73
74 {
75 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
76 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
77 }
78 {
79 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
80 p.add_run("Feature Showcase")
81 .size(28.0)
82 .color("FFFFFF")
83 .italic(true);
84 }
85
86 doc.add_paragraph(""); // spacer
87
88 {
89 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
90 p.add_run("A comprehensive demonstration of every feature")
91 .size(14.0)
92 .color("CCDDFF");
93 }
94 {
95 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
96 p.add_run("provided by the rdocx Rust crate for DOCX generation.")
97 .size(14.0)
98 .color("CCDDFF");
99 }
100
101 // =========================================================================
102 // PAGE 2: TEXT FORMATTING
103 // =========================================================================
104 doc.add_paragraph("").page_break_before(true);
105
106 doc.add_paragraph("1. Text Formatting").style("Heading1");
107
108 doc.add_paragraph("This section demonstrates paragraph and run-level formatting options.");
109 doc.add_paragraph("");
110
111 // --- Paragraph alignment ---
112 doc.add_paragraph("Paragraph Alignment").style("Heading2");
113
114 doc.add_paragraph("This paragraph is left-aligned (the default).")
115 .alignment(Alignment::Left);
116 doc.add_paragraph("This paragraph is center-aligned.")
117 .alignment(Alignment::Center);
118 doc.add_paragraph("This paragraph is right-aligned.")
119 .alignment(Alignment::Right);
120 doc.add_paragraph(
121 "This paragraph is justified. To demonstrate justified text properly, it needs \
122 to be long enough to span multiple lines so the word spacing adjustment is visible \
123 across the full width of the text area on the page.",
124 )
125 .alignment(Alignment::Justify);
126
127 doc.add_paragraph("");
128
129 // --- Run formatting ---
130 doc.add_paragraph("Run Formatting").style("Heading2");
131
132 {
133 let mut p = doc.add_paragraph("");
134 p.add_run("Bold text").bold(true);
135 p.add_run(" | ");
136 p.add_run("Italic text").italic(true);
137 p.add_run(" | ");
138 p.add_run("Bold + Italic").bold(true).italic(true);
139 }
140 {
141 let mut p = doc.add_paragraph("");
142 p.add_run("Single underline").underline(true);
143 p.add_run(" | ");
144 p.add_run("Strikethrough").strike(true);
145 p.add_run(" | ");
146 p.add_run("Double strikethrough").double_strike(true);
147 }
148 {
149 let mut p = doc.add_paragraph("");
150 p.add_run("Red text").color("FF0000");
151 p.add_run(" | ");
152 p.add_run("Blue text").color("0000FF");
153 p.add_run(" | ");
154 p.add_run("Green text").color("00AA00");
155 p.add_run(" | ");
156 p.add_run("Highlighted").highlight("FFFF00");
157 }
158 {
159 let mut p = doc.add_paragraph("");
160 p.add_run("8pt small").size(8.0);
161 p.add_run(" | ");
162 p.add_run("11pt normal").size(11.0);
163 p.add_run(" | ");
164 p.add_run("16pt large").size(16.0);
165 p.add_run(" | ");
166 p.add_run("24pt extra-large").size(24.0);
167 }
168 {
169 let mut p = doc.add_paragraph("");
170 p.add_run("Arial font").font("Arial");
171 p.add_run(" | ");
172 p.add_run("Times New Roman font").font("Times New Roman");
173 p.add_run(" | ");
174 p.add_run("Courier New font").font("Courier New");
175 }
176 {
177 let mut p = doc.add_paragraph("");
178 p.add_run("Normal");
179 p.add_run(" H").size(11.0);
180 p.add_run("2").subscript();
181 p.add_run("O (subscript)").size(11.0);
182 p.add_run(" | E = mc").size(11.0);
183 p.add_run("2").superscript();
184 p.add_run(" (superscript)").size(11.0);
185 }
186 {
187 let mut p = doc.add_paragraph("");
188 p.add_run("ALL CAPS").all_caps(true);
189 p.add_run(" | ");
190 p.add_run("Small Caps").small_caps(true);
191 p.add_run(" | ");
192 p.add_run("Expanded spacing")
193 .character_spacing(Length::twips(40));
194 }
195
196 doc.add_paragraph("");
197
198 // --- Paragraph formatting ---
199 doc.add_paragraph("Paragraph Formatting").style("Heading2");
200
201 doc.add_paragraph("Paragraph with shading (light green background)")
202 .shading("E2EFDA");
203
204 doc.add_paragraph("Paragraph with bottom border")
205 .border_bottom(BorderStyle::Single, 6, "2E75B6");
206
207 doc.add_paragraph("Paragraph with all borders")
208 .border_all(BorderStyle::Single, 4, "FF0000");
209
210 doc.add_paragraph("Paragraph with 1-inch left indent and hanging indent")
211 .indent_left(Length::inches(1.0))
212 .hanging_indent(Length::inches(0.5));
213
214 doc.add_paragraph("Paragraph with first-line indent of 0.5 inches")
215 .first_line_indent(Length::inches(0.5));
216
217 doc.add_paragraph("Paragraph with extra space before (24pt) and after (12pt)")
218 .space_before(Length::pt(24.0))
219 .space_after(Length::pt(12.0));
220
221 doc.add_paragraph(
222 "Paragraph with double line spacing. This text should have extra vertical \
223 space between lines to demonstrate the line_spacing_multiple setting.",
224 )
225 .line_spacing_multiple(2.0);
226
227 doc.add_paragraph("Paragraph with keep-with-next (won't break from the next paragraph)")
228 .keep_with_next(true);
229 doc.add_paragraph("(This stays with the paragraph above.)");
230
231 // =========================================================================
232 // PAGE 3: LISTS & TAB STOPS
233 // =========================================================================
234 doc.add_paragraph("").page_break_before(true);
235
236 doc.add_paragraph("2. Lists").style("Heading1");
237
238 doc.add_paragraph("Bullet List").style("Heading2");
239
240 doc.add_bullet_list_item("First bullet item", 0);
241 doc.add_bullet_list_item("Second bullet item", 0);
242 doc.add_bullet_list_item("Nested level 1", 1);
243 doc.add_bullet_list_item("Nested level 2", 2);
244 doc.add_bullet_list_item("Back to level 1", 1);
245 doc.add_bullet_list_item("Third bullet item", 0);
246
247 doc.add_paragraph("");
248
249 doc.add_paragraph("Numbered List").style("Heading2");
250
251 doc.add_numbered_list_item("First numbered item", 0);
252 doc.add_numbered_list_item("Second numbered item", 0);
253 doc.add_numbered_list_item("Sub-item A", 1);
254 doc.add_numbered_list_item("Sub-item B", 1);
255 doc.add_numbered_list_item("Third numbered item", 0);
256
257 doc.add_paragraph("");
258
259 // --- Tab stops ---
260 doc.add_paragraph("Tab Stops").style("Heading2");
261
262 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
263 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
264 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
265 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
266 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
267
268 doc.add_paragraph("Item\t........\tPrice")
269 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
270 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
271 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
272
273 doc.add_paragraph("Widget A\t........\t$19.99")
274 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
275 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
276 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
277
278 doc.add_paragraph("Gadget B\t________\t$249.50")
279 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
280 .add_tab_stop_with_leader(
281 TabAlignment::Right,
282 Length::inches(4.0),
283 TabLeader::Underscore,
284 )
285 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
286
287 // =========================================================================
288 // PAGE 4: TABLES
289 // =========================================================================
290 doc.add_paragraph("").page_break_before(true);
291
292 doc.add_paragraph("3. Tables").style("Heading1");
293
294 // --- Basic table with borders ---
295 doc.add_paragraph("Basic Table with Borders")
296 .style("Heading2");
297
298 {
299 let mut tbl = doc.add_table(4, 3);
300 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
301
302 // Header row
303 for col in 0..3 {
304 tbl.cell(0, col).unwrap().shading("2E75B6");
305 }
306 tbl.cell(0, 0).unwrap().set_text("Name");
307 tbl.cell(0, 1).unwrap().set_text("Role");
308 tbl.cell(0, 2).unwrap().set_text("Location");
309
310 tbl.cell(1, 0).unwrap().set_text("Alice Johnson");
311 tbl.cell(1, 1).unwrap().set_text("Engineering Lead");
312 tbl.cell(1, 2).unwrap().set_text("New York");
313
314 tbl.cell(2, 0).unwrap().set_text("Bob Smith");
315 tbl.cell(2, 1).unwrap().set_text("Product Manager");
316 tbl.cell(2, 2).unwrap().set_text("San Francisco");
317
318 tbl.cell(3, 0).unwrap().set_text("Carol Davis");
319 tbl.cell(3, 1).unwrap().set_text("Designer");
320 tbl.cell(3, 2).unwrap().set_text("London");
321 }
322
323 doc.add_paragraph("");
324
325 // --- Table with cell merging ---
326 doc.add_paragraph("Table with Cell Merging & Vertical Alignment")
327 .style("Heading2");
328
329 {
330 let mut tbl = doc.add_table(4, 4);
331 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
332 tbl = tbl.width_pct(100.0);
333
334 // Header spanning all columns
335 tbl.cell(0, 0).unwrap().set_text("Quarterly Revenue Report");
336 tbl.cell(0, 0).unwrap().shading("1F4E79");
337 tbl.cell(0, 0).unwrap().grid_span(4);
338
339 // Sub-header
340 tbl.cell(1, 0).unwrap().set_text("Region");
341 tbl.cell(1, 0).unwrap().shading("D6E4F0");
342 tbl.cell(1, 1).unwrap().set_text("Q1");
343 tbl.cell(1, 1).unwrap().shading("D6E4F0");
344 tbl.cell(1, 2).unwrap().set_text("Q2");
345 tbl.cell(1, 2).unwrap().shading("D6E4F0");
346 tbl.cell(1, 3).unwrap().set_text("Total");
347 tbl.cell(1, 3).unwrap().shading("D6E4F0");
348
349 // Data
350 tbl.cell(2, 0).unwrap().set_text("North America");
351 tbl.cell(2, 1).unwrap().set_text("$2.4M");
352 tbl.cell(2, 2).unwrap().set_text("$2.7M");
353 tbl.cell(2, 3).unwrap().set_text("$5.1M");
354
355 tbl.cell(3, 0).unwrap().set_text("Europe");
356 tbl.cell(3, 1).unwrap().set_text("$1.8M");
357 tbl.cell(3, 2).unwrap().set_text("$2.0M");
358 tbl.cell(3, 3).unwrap().set_text("$3.8M");
359
360 // Vertical alignment on data cells
361 tbl.cell(2, 3)
362 .unwrap()
363 .vertical_alignment(VerticalAlignment::Center);
364 tbl.cell(3, 3)
365 .unwrap()
366 .vertical_alignment(VerticalAlignment::Bottom);
367 }
368
369 doc.add_paragraph("");
370
371 // --- Table with vertical merge ---
372 doc.add_paragraph("Table with Vertical Merge")
373 .style("Heading2");
374
375 {
376 let mut tbl = doc.add_table(4, 3);
377 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
378
379 tbl.cell(0, 0).unwrap().set_text("Category");
380 tbl.cell(0, 0).unwrap().shading("E2EFDA");
381 tbl.cell(0, 1).unwrap().set_text("Item");
382 tbl.cell(0, 1).unwrap().shading("E2EFDA");
383 tbl.cell(0, 2).unwrap().set_text("Price");
384 tbl.cell(0, 2).unwrap().shading("E2EFDA");
385
386 // "Hardware" spans rows 1-2
387 tbl.cell(1, 0).unwrap().set_text("Hardware");
388 tbl.cell(1, 0).unwrap().v_merge_restart();
389 tbl.cell(1, 1).unwrap().set_text("Laptop");
390 tbl.cell(1, 2).unwrap().set_text("$1,200");
391
392 tbl.cell(2, 0).unwrap().v_merge_continue();
393 tbl.cell(2, 1).unwrap().set_text("Monitor");
394 tbl.cell(2, 2).unwrap().set_text("$450");
395
396 // "Software" on row 3
397 tbl.cell(3, 0).unwrap().set_text("Software");
398 tbl.cell(3, 1).unwrap().set_text("IDE License");
399 tbl.cell(3, 2).unwrap().set_text("$200/yr");
400 }
401
402 doc.add_paragraph("");
403
404 // --- Nested table ---
405 doc.add_paragraph("Nested Table").style("Heading2");
406
407 {
408 let mut tbl = doc.add_table(2, 2);
409 tbl = tbl.borders(BorderStyle::Single, 6, "2E75B6");
410
411 tbl.cell(0, 0).unwrap().set_text("Outer Cell (0,0)");
412 tbl.cell(0, 1).unwrap().set_text("Outer Cell (0,1)");
413 tbl.cell(1, 0).unwrap().set_text("Outer Cell (1,0)");
414
415 // Nested table inside cell (1,1)
416 {
417 let mut cell = tbl.cell(1, 1).unwrap();
418 cell.set_text("Contains nested table:");
419 let mut nested = cell.add_table(2, 2);
420 nested = nested.borders(BorderStyle::Single, 2, "FF6600");
421 nested.cell(0, 0).unwrap().set_text("Inner A");
422 nested.cell(0, 1).unwrap().set_text("Inner B");
423 nested.cell(1, 0).unwrap().set_text("Inner C");
424 nested.cell(1, 1).unwrap().set_text("Inner D");
425 }
426 }
427
428 // =========================================================================
429 // PAGE 5: IMAGES
430 // =========================================================================
431 doc.add_paragraph("").page_break_before(true);
432
433 doc.add_paragraph("4. Images").style("Heading1");
434
435 doc.add_paragraph("Inline Image").style("Heading2");
436
437 doc.add_paragraph("Below is an inline image (200x50 pixels, blue gradient):");
438 let inline_img = create_sample_png(200, 50, [0, 80, 200]);
439 doc.add_picture(
440 &inline_img,
441 "inline_chart.png",
442 Length::inches(3.0),
443 Length::inches(0.75),
444 );
445
446 doc.add_paragraph("");
447
448 doc.add_paragraph("Header Image").style("Heading2");
449
450 // Replace the text-only header with an image header
451 let header_img = create_sample_png(400, 40, [40, 40, 40]);
452 doc.set_header_image(
453 &header_img,
454 "header_logo.png",
455 Length::inches(2.0),
456 Length::inches(0.2),
457 );
458
459 doc.add_paragraph(
460 "The document header has been replaced with an inline image. \
461 Check the header area at the top of this page.",
462 );
463
464 doc.add_paragraph("");
465 doc.add_paragraph(
466 "Note: The cover page uses a full-page background image behind the text, \
467 demonstrated on page 1 via add_background_image().",
468 );
469
470 // =========================================================================
471 // PAGE 6: CONTENT MANIPULATION — placeholder replacement, insertion
472 // =========================================================================
473 doc.add_paragraph("").page_break_before(true);
474
475 doc.add_paragraph("5. Content Manipulation")
476 .style("Heading1");
477
478 // --- Placeholder replacement ---
479 doc.add_paragraph("Placeholder Replacement")
480 .style("Heading2");
481
482 doc.add_paragraph(
483 "Before replacement, this document contained {{customer}} and {{date}} placeholders.",
484 );
485
486 {
487 let mut p = doc.add_paragraph("");
488 p.add_run("Customer: ").bold(true);
489 p.add_run("{{customer}}");
490 }
491 {
492 let mut p = doc.add_paragraph("");
493 p.add_run("Date: ").bold(true);
494 p.add_run("{{date}}");
495 }
496 doc.add_paragraph("Reference: {{ref_number}}");
497
498 // Table with placeholders
499 {
500 let mut tbl = doc.add_table(3, 2);
501 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
502 tbl.cell(0, 0).unwrap().set_text("Field");
503 tbl.cell(0, 0).unwrap().shading("D6E4F0");
504 tbl.cell(0, 1).unwrap().set_text("Value");
505 tbl.cell(0, 1).unwrap().shading("D6E4F0");
506 tbl.cell(1, 0).unwrap().set_text("Project");
507 tbl.cell(1, 1).unwrap().set_text("{{project}}");
508 tbl.cell(2, 0).unwrap().set_text("Status");
509 tbl.cell(2, 1).unwrap().set_text("{{status}}");
510 }
511
512 // Perform replacements
513 let mut replacements = HashMap::new();
514 replacements.insert("{{customer}}", "Acme Corporation");
515 replacements.insert("{{date}}", "February 22, 2026");
516 replacements.insert("{{ref_number}}", "REF-2026-001");
517 replacements.insert("{{project}}", "Infrastructure Upgrade");
518 replacements.insert("{{status}}", "In Progress");
519 let replace_count = doc.replace_all(&replacements);
520
521 doc.add_paragraph("");
522 doc.add_paragraph(&format!(
523 "(Replaced {} placeholders above — in body text and table cells)",
524 replace_count
525 ));
526
527 doc.add_paragraph("");
528
529 // --- Content insertion ---
530 doc.add_paragraph("Content Insertion").style("Heading2");
531
532 doc.add_paragraph("Section A: First section of content.");
533 doc.add_paragraph("Section C: Third section of content.");
534
535 // Insert "Section B" between A and C
536 if let Some(idx) = doc.find_content_index("Section C") {
537 doc.insert_paragraph(
538 idx,
539 "Section B: Inserted between A and C using find_content_index().",
540 );
541 }
542
543 doc.add_paragraph("");
544 doc.add_paragraph(
545 "The paragraph above ('Section B') was inserted at a specific position \
546 using find_content_index() + insert_paragraph().",
547 );
548
549 // =========================================================================
550 // PAGE 7: LANDSCAPE — section break, wide table
551 // =========================================================================
552 doc.add_paragraph("").section_break(SectionBreak::NextPage);
553
554 doc.add_paragraph("6. Mixed Page Orientation")
555 .style("Heading1");
556
557 doc.add_paragraph(
558 "This page is in LANDSCAPE orientation. It was created using a section break \
559 followed by section_landscape(). This is useful for wide tables or charts.",
560 );
561
562 doc.add_paragraph("");
563
564 // Wide table for landscape
565 {
566 let mut tbl = doc.add_table(4, 7);
567 tbl = tbl.borders(BorderStyle::Single, 4, "2E75B6");
568
569 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
570 for (col, h) in headers.iter().enumerate() {
571 tbl.cell(0, col).unwrap().set_text(h);
572 tbl.cell(0, col).unwrap().shading("2E75B6");
573 }
574
575 let data = [
576 [
577 "North America",
578 "$1.2M",
579 "$1.3M",
580 "$1.4M",
581 "$1.5M",
582 "$1.6M",
583 "$7.0M",
584 ],
585 [
586 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
587 ],
588 [
589 "Asia Pacific",
590 "$0.5M",
591 "$0.6M",
592 "$0.7M",
593 "$0.7M",
594 "$0.8M",
595 "$3.3M",
596 ],
597 ];
598 for (row_idx, row_data) in data.iter().enumerate() {
599 for (col, val) in row_data.iter().enumerate() {
600 tbl.cell(row_idx + 1, col).unwrap().set_text(val);
601 }
602 }
603 }
604
605 // End landscape, return to portrait
606 doc.add_paragraph("")
607 .section_break(SectionBreak::NextPage)
608 .section_landscape();
609
610 // =========================================================================
611 // PAGE 8: BACK TO PORTRAIT — styles, final notes
612 // =========================================================================
613 doc.add_paragraph("7. Custom Styles & Summary")
614 .style("Heading1");
615
616 doc.add_paragraph(
617 "This final page is back in portrait orientation after a section break. \
618 The document has demonstrated:",
619 );
620
621 doc.add_bullet_list_item(
622 "Page setup: size, margins, header/footer distance, gutter",
623 0,
624 );
625 doc.add_bullet_list_item("Document metadata: title, author, subject, keywords", 0);
626 doc.add_bullet_list_item("Headers and footers: text, images, different first page", 0);
627 doc.add_bullet_list_item("Background images: full-page behind text", 0);
628 doc.add_bullet_list_item(
629 "Text formatting: bold, italic, underline, strike, color, size, font",
630 0,
631 );
632 doc.add_bullet_list_item(
633 "Advanced run formatting: superscript, subscript, caps, spacing",
634 0,
635 );
636 doc.add_bullet_list_item(
637 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
638 0,
639 );
640 doc.add_bullet_list_item("Bullet and numbered lists with nesting levels", 0);
641 doc.add_bullet_list_item("Tab stops with dot/underscore leaders", 0);
642 doc.add_bullet_list_item(
643 "Tables: borders, shading, column spans, row spans, nesting",
644 0,
645 );
646 doc.add_bullet_list_item("Vertical alignment in table cells", 0);
647 doc.add_bullet_list_item("Inline images", 0);
648 doc.add_bullet_list_item("Placeholder replacement in body and table cells", 0);
649 doc.add_bullet_list_item("Content insertion at specific positions", 0);
650 doc.add_bullet_list_item(
651 "Section breaks with mixed portrait/landscape orientation",
652 0,
653 );
654
655 doc.add_paragraph("");
656 doc.add_paragraph("All features above were built entirely from scratch using the rdocx API.")
657 .alignment(Alignment::Center)
658 .shading("E2EFDA")
659 .border_all(BorderStyle::Single, 2, "00AA00");
660
661 doc.save(path).unwrap();
662}86fn generate_feature_showcase(_samples_dir: &Path) -> Document {
87 let mut doc = Document::new();
88
89 // ── Page Setup & Metadata ──
90 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
91 doc.set_margins(
92 Length::inches(1.0),
93 Length::inches(1.0),
94 Length::inches(1.0),
95 Length::inches(1.0),
96 );
97 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
98 doc.set_gutter(Length::twips(0));
99 doc.set_title("rdocx Feature Showcase");
100 doc.set_author("rdocx Sample Generator");
101 doc.set_subject("Comprehensive feature demonstration");
102 doc.set_keywords("rdocx, docx, rust, sample, showcase");
103
104 // Headers & Footers with different first page
105 doc.set_different_first_page(true);
106 doc.set_first_page_header("rdocx");
107 doc.set_first_page_footer("Feature Showcase — Cover Page");
108 doc.set_header("rdocx Feature Showcase");
109 doc.set_footer("Generated by rdocx");
110
111 // ── COVER PAGE ──
112 let bg = create_sample_png(612, 792, [20, 45, 90]);
113 doc.add_background_image(&bg, "cover_bg.png");
114
115 for _ in 0..3 {
116 doc.add_paragraph("");
117 }
118 {
119 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
120 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
121 }
122 {
123 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
124 p.add_run("Complete Feature Showcase")
125 .size(28.0)
126 .color("FFFFFF")
127 .italic(true);
128 }
129 doc.add_paragraph("");
130 {
131 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
132 p.add_run("Every feature of the rdocx Rust library")
133 .size(13.0)
134 .color("B0C4DE");
135 }
136 {
137 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
138 p.add_run("demonstrated in a single document.")
139 .size(13.0)
140 .color("B0C4DE");
141 }
142
143 // ── TABLE OF CONTENTS ──
144 doc.add_paragraph("").page_break_before(true);
145 doc.insert_toc(doc.content_count(), 3);
146
147 // ── SECTION 1: TEXT FORMATTING ──
148 doc.add_paragraph("").page_break_before(true);
149 doc.add_paragraph("1. Text Formatting").style("Heading1");
150
151 // Paragraph Alignment
152 doc.add_paragraph("1.1 Paragraph Alignment")
153 .style("Heading2");
154 doc.add_paragraph("Left-aligned paragraph (default).")
155 .alignment(Alignment::Left);
156 doc.add_paragraph("Center-aligned paragraph.")
157 .alignment(Alignment::Center);
158 doc.add_paragraph("Right-aligned paragraph.")
159 .alignment(Alignment::Right);
160 doc.add_paragraph(
161 "Justified paragraph — this text is long enough to demonstrate how justified alignment \
162 distributes extra space across word gaps so lines fill the full width of the text area.",
163 )
164 .alignment(Alignment::Justify);
165
166 doc.add_paragraph("");
167
168 // Run Formatting
169 doc.add_paragraph("1.2 Run Formatting").style("Heading2");
170 {
171 let mut p = doc.add_paragraph("");
172 p.add_run("Bold").bold(true);
173 p.add_run(" | ");
174 p.add_run("Italic").italic(true);
175 p.add_run(" | ");
176 p.add_run("Bold Italic").bold(true).italic(true);
177 p.add_run(" | ");
178 p.add_run("Underline").underline(true);
179 p.add_run(" | ");
180 p.add_run("Strikethrough").strike(true);
181 p.add_run(" | ");
182 p.add_run("Double Strike").double_strike(true);
183 }
184 {
185 let mut p = doc.add_paragraph("");
186 p.add_run("Red").color("FF0000");
187 p.add_run(" | ");
188 p.add_run("Blue").color("0000FF");
189 p.add_run(" | ");
190 p.add_run("Green").color("00AA00");
191 p.add_run(" | ");
192 p.add_run("Highlighted").highlight("FFFF00");
193 }
194 {
195 let mut p = doc.add_paragraph("");
196 p.add_run("8pt").size(8.0);
197 p.add_run(" | ");
198 p.add_run("11pt").size(11.0);
199 p.add_run(" | ");
200 p.add_run("16pt").size(16.0);
201 p.add_run(" | ");
202 p.add_run("24pt").size(24.0);
203 }
204 {
205 let mut p = doc.add_paragraph("");
206 p.add_run("Arial").font("Arial");
207 p.add_run(" | ");
208 p.add_run("Times New Roman").font("Times New Roman");
209 p.add_run(" | ");
210 p.add_run("Courier New").font("Courier New");
211 }
212 {
213 let mut p = doc.add_paragraph("");
214 p.add_run("H");
215 p.add_run("2").subscript();
216 p.add_run("O (subscript) | E=mc");
217 p.add_run("2").superscript();
218 p.add_run(" (superscript)");
219 }
220 {
221 let mut p = doc.add_paragraph("");
222 p.add_run("ALL CAPS").all_caps(true);
223 p.add_run(" | ");
224 p.add_run("Small Caps").small_caps(true);
225 p.add_run(" | ");
226 p.add_run("Expanded").character_spacing(Length::twips(40));
227 p.add_run(" | ");
228 p.add_run("Hidden text (not visible)").hidden(true);
229 }
230
231 doc.add_paragraph("");
232
233 // Underline Styles
234 doc.add_paragraph("1.3 Underline Styles").style("Heading2");
235 {
236 let mut p = doc.add_paragraph("");
237 p.add_run("Single ")
238 .underline_style(rdocx::UnderlineStyle::Single);
239 p.add_run("Double ")
240 .underline_style(rdocx::UnderlineStyle::Double);
241 p.add_run("Thick ")
242 .underline_style(rdocx::UnderlineStyle::Thick);
243 p.add_run("Dotted ")
244 .underline_style(rdocx::UnderlineStyle::Dotted);
245 p.add_run("Dash ")
246 .underline_style(rdocx::UnderlineStyle::Dash);
247 p.add_run("Wave ")
248 .underline_style(rdocx::UnderlineStyle::Wave);
249 }
250
251 // ── SECTION 2: PARAGRAPH FORMATTING ──
252 doc.add_paragraph("").page_break_before(true);
253 doc.add_paragraph("2. Paragraph Formatting")
254 .style("Heading1");
255
256 doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
257 doc.add_paragraph("Paragraph with light green shading")
258 .shading("E2EFDA");
259 doc.add_paragraph("Paragraph with bottom border")
260 .border_bottom(BorderStyle::Single, 6, "2E75B6");
261 doc.add_paragraph("Paragraph with all borders (red)")
262 .border_all(BorderStyle::Single, 4, "FF0000");
263
264 doc.add_paragraph("2.2 Indentation").style("Heading2");
265 doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
266 .indent_left(Length::inches(1.0))
267 .hanging_indent(Length::inches(0.5));
268 doc.add_paragraph("0.5-inch first-line indent on this paragraph")
269 .first_line_indent(Length::inches(0.5));
270 doc.add_paragraph("Both left and right indent (0.75 inches each)")
271 .indent_left(Length::inches(0.75))
272 .indent_right(Length::inches(0.75));
273
274 doc.add_paragraph("2.3 Spacing & Line Height")
275 .style("Heading2");
276 doc.add_paragraph("Extra space before (24pt) and after (12pt)")
277 .space_before(Length::pt(24.0))
278 .space_after(Length::pt(12.0));
279 doc.add_paragraph(
280 "Double line spacing paragraph. This text should have extra vertical space between \
281 lines to demonstrate line_spacing_multiple(2.0).",
282 )
283 .line_spacing_multiple(2.0);
284 doc.add_paragraph("Exact 20pt line spacing (fixed height).")
285 .line_spacing(20.0);
286
287 doc.add_paragraph("2.4 Pagination Controls")
288 .style("Heading2");
289 doc.add_paragraph("keep_with_next — this paragraph stays with the next")
290 .keep_with_next(true);
291 doc.add_paragraph("(This paragraph was kept with the one above.)");
292 doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
293 .keep_together(true);
294 doc.add_paragraph("widow_control — prevents widow/orphan lines")
295 .widow_control(true);
296
297 // ── SECTION 3: LISTS ──
298 doc.add_paragraph("").page_break_before(true);
299 doc.add_paragraph("3. Lists").style("Heading1");
300
301 doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
302 doc.add_bullet_list_item("First item", 0);
303 doc.add_bullet_list_item("Second item", 0);
304 doc.add_bullet_list_item("Nested level 1", 1);
305 doc.add_bullet_list_item("Nested level 2", 2);
306 doc.add_bullet_list_item("Back to level 1", 1);
307 doc.add_bullet_list_item("Third item", 0);
308
309 doc.add_paragraph("");
310
311 doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
312 doc.add_numbered_list_item("First numbered item", 0);
313 doc.add_numbered_list_item("Second numbered item", 0);
314 doc.add_numbered_list_item("Sub-item A", 1);
315 doc.add_numbered_list_item("Sub-item B", 1);
316 doc.add_numbered_list_item("Third numbered item", 0);
317
318 // ── SECTION 4: TAB STOPS ──
319 doc.add_paragraph("");
320 doc.add_paragraph("4. Tab Stops").style("Heading1");
321
322 doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
323 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
324 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
325 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
326 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
327 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
328
329 doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
330 doc.add_paragraph("Item\t\tPrice")
331 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
332 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
333 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
334 doc.add_paragraph("Widget\t\t$19.99")
335 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
336 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
337 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
338 doc.add_paragraph("Gadget\t\t$249.50")
339 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
340 .add_tab_stop_with_leader(
341 TabAlignment::Right,
342 Length::inches(4.0),
343 TabLeader::Underscore,
344 )
345 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
346
347 // ── SECTION 5: TABLES ──
348 doc.add_paragraph("").page_break_before(true);
349 doc.add_paragraph("5. Tables").style("Heading1");
350
351 doc.add_paragraph("5.1 Basic Table").style("Heading2");
352 {
353 let mut tbl = doc
354 .add_table(4, 3)
355 .borders(BorderStyle::Single, 4, "000000");
356 for col in 0..3 {
357 tbl.cell(0, col).unwrap().shading("2E75B6");
358 }
359 tbl.cell(0, 0).unwrap().set_text("Name");
360 tbl.cell(0, 1).unwrap().set_text("Role");
361 tbl.cell(0, 2).unwrap().set_text("Location");
362 tbl.cell(1, 0).unwrap().set_text("Walter White");
363 tbl.cell(1, 1).unwrap().set_text("CEO");
364 tbl.cell(1, 2).unwrap().set_text("Albuquerque");
365 tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
366 tbl.cell(2, 1).unwrap().set_text("CTO");
367 tbl.cell(2, 2).unwrap().set_text("Remote");
368 tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
369 tbl.cell(3, 1).unwrap().set_text("Security");
370 tbl.cell(3, 2).unwrap().set_text("Washington");
371 }
372
373 doc.add_paragraph("");
374
375 doc.add_paragraph("5.2 Column Span & Cell Shading")
376 .style("Heading2");
377 {
378 let mut tbl = doc
379 .add_table(3, 4)
380 .borders(BorderStyle::Single, 4, "000000")
381 .width_pct(100.0);
382 tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
383 tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
384 tbl.cell(1, 0).unwrap().set_text("Region");
385 tbl.cell(1, 0).unwrap().shading("D6E4F0");
386 tbl.cell(1, 1).unwrap().set_text("Q1");
387 tbl.cell(1, 1).unwrap().shading("D6E4F0");
388 tbl.cell(1, 2).unwrap().set_text("Q2");
389 tbl.cell(1, 2).unwrap().shading("D6E4F0");
390 tbl.cell(1, 3).unwrap().set_text("Total");
391 tbl.cell(1, 3).unwrap().shading("D6E4F0");
392 tbl.cell(2, 0).unwrap().set_text("Americas");
393 tbl.cell(2, 1).unwrap().set_text("$2.4M");
394 tbl.cell(2, 2).unwrap().set_text("$2.7M");
395 tbl.cell(2, 3).unwrap().set_text("$5.1M");
396 }
397
398 doc.add_paragraph("");
399
400 doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
401 {
402 let mut tbl = doc
403 .add_table(4, 3)
404 .borders(BorderStyle::Single, 4, "000000");
405 tbl.cell(0, 0).unwrap().set_text("Category");
406 tbl.cell(0, 0).unwrap().shading("E2EFDA");
407 tbl.cell(0, 1).unwrap().set_text("Item");
408 tbl.cell(0, 1).unwrap().shading("E2EFDA");
409 tbl.cell(0, 2).unwrap().set_text("Price");
410 tbl.cell(0, 2).unwrap().shading("E2EFDA");
411 tbl.cell(1, 0).unwrap().set_text("Hardware");
412 tbl.cell(1, 0).unwrap().v_merge_restart();
413 tbl.cell(1, 1).unwrap().set_text("Laptop");
414 tbl.cell(1, 2).unwrap().set_text("$1,200");
415 tbl.cell(2, 0).unwrap().v_merge_continue();
416 tbl.cell(2, 1).unwrap().set_text("Monitor");
417 tbl.cell(2, 2).unwrap().set_text("$450");
418 tbl.cell(3, 0).unwrap().set_text("Software");
419 tbl.cell(3, 1).unwrap().set_text("IDE License");
420 tbl.cell(3, 2).unwrap().set_text("$200/yr");
421 }
422
423 doc.add_paragraph("");
424
425 doc.add_paragraph("5.4 Nested Table").style("Heading2");
426 {
427 let mut tbl = doc
428 .add_table(2, 2)
429 .borders(BorderStyle::Single, 6, "2E75B6");
430 tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
431 tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
432 tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
433 {
434 let mut cell = tbl.cell(1, 1).unwrap();
435 cell.set_text("Nested table below:");
436 let mut nested = cell
437 .add_table(2, 2)
438 .borders(BorderStyle::Single, 2, "FF6600");
439 nested.cell(0, 0).unwrap().set_text("A");
440 nested.cell(0, 1).unwrap().set_text("B");
441 nested.cell(1, 0).unwrap().set_text("C");
442 nested.cell(1, 1).unwrap().set_text("D");
443 }
444 }
445
446 doc.add_paragraph("");
447
448 doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
449 .style("Heading2");
450 {
451 let mut tbl = doc
452 .add_table(2, 3)
453 .borders(BorderStyle::Single, 4, "666666");
454 tbl.row(0).unwrap().height(Length::pt(50.0)).header();
455 tbl.cell(0, 0).unwrap().set_text("Top");
456 tbl.cell(0, 0)
457 .unwrap()
458 .vertical_alignment(VerticalAlignment::Top)
459 .shading("FFF2CC");
460 tbl.cell(0, 1).unwrap().set_text("Center");
461 tbl.cell(0, 1)
462 .unwrap()
463 .vertical_alignment(VerticalAlignment::Center)
464 .shading("D9E2F3");
465 tbl.cell(0, 2).unwrap().set_text("Bottom");
466 tbl.cell(0, 2)
467 .unwrap()
468 .vertical_alignment(VerticalAlignment::Bottom)
469 .shading("E2EFDA");
470 tbl.row(1).unwrap().cant_split();
471 tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
472 tbl.cell(1, 0).unwrap().no_wrap();
473 tbl.cell(1, 1).unwrap().set_text("Fixed width");
474 tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
475 tbl.cell(1, 2).unwrap().set_text("Normal");
476 }
477
478 // ── SECTION 6: IMAGES ──
479 doc.add_paragraph("").page_break_before(true);
480 doc.add_paragraph("6. Images").style("Heading1");
481
482 doc.add_paragraph("6.1 Inline Image").style("Heading2");
483 doc.add_paragraph("A blue gradient image below:");
484 let img = create_sample_png(200, 50, [0, 80, 200]);
485 doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
486
487 doc.add_paragraph("");
488 doc.add_paragraph("6.2 Header Image").style("Heading2");
489 let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
490 doc.set_header_image(
491 &hdr_img,
492 "header_logo.png",
493 Length::inches(2.0),
494 Length::inches(0.2),
495 );
496 doc.add_paragraph("The header now contains an inline image (check the top of this page).");
497
498 doc.add_paragraph("");
499 doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
500
501 // ── SECTION 7: CONTENT MANIPULATION ──
502 doc.add_paragraph("").page_break_before(true);
503 doc.add_paragraph("7. Content Manipulation")
504 .style("Heading1");
505
506 doc.add_paragraph("7.1 Placeholder Replacement")
507 .style("Heading2");
508 doc.add_paragraph("Customer: {{customer}}");
509 doc.add_paragraph("Date: {{date}}");
510 doc.add_paragraph("Reference: {{ref_number}}");
511 {
512 let mut tbl = doc
513 .add_table(2, 2)
514 .borders(BorderStyle::Single, 4, "000000");
515 tbl.cell(0, 0).unwrap().set_text("Project");
516 tbl.cell(0, 0).unwrap().shading("D6E4F0");
517 tbl.cell(0, 1).unwrap().set_text("{{project}}");
518 tbl.cell(1, 0).unwrap().set_text("Status");
519 tbl.cell(1, 0).unwrap().shading("D6E4F0");
520 tbl.cell(1, 1).unwrap().set_text("{{status}}");
521 }
522
523 let mut replacements = HashMap::new();
524 replacements.insert("{{customer}}", "Tensorbee Inc.");
525 replacements.insert("{{date}}", "February 22, 2026");
526 replacements.insert("{{ref_number}}", "TB-2026-001");
527 replacements.insert("{{project}}", "Infrastructure Upgrade");
528 replacements.insert("{{status}}", "In Progress");
529 let n = doc.replace_all(&replacements);
530 doc.add_paragraph(&format!("({n} placeholders replaced above)"));
531
532 doc.add_paragraph("");
533
534 doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
535 doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
536 let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
537 doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
538
539 doc.add_paragraph("");
540
541 doc.add_paragraph("7.3 Content Insertion").style("Heading2");
542 doc.add_paragraph("Section A: First content.");
543 doc.add_paragraph("Section C: Third content.");
544 if let Some(idx) = doc.find_content_index("Section C") {
545 doc.insert_paragraph(
546 idx,
547 "Section B: Inserted between A and C via find_content_index().",
548 );
549 }
550
551 // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
552 doc.add_paragraph("").section_break(SectionBreak::NextPage);
553 doc.add_paragraph("8. Section Breaks & Orientation")
554 .style("Heading1");
555 doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
556
557 {
558 let mut tbl = doc
559 .add_table(3, 7)
560 .borders(BorderStyle::Single, 4, "2E75B6");
561 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
562 for (c, h) in headers.iter().enumerate() {
563 tbl.cell(0, c).unwrap().set_text(h);
564 tbl.cell(0, c).unwrap().shading("2E75B6");
565 }
566 let data = [
567 [
568 "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
569 ],
570 [
571 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
572 ],
573 ];
574 for (r, row) in data.iter().enumerate() {
575 for (c, v) in row.iter().enumerate() {
576 tbl.cell(r + 1, c).unwrap().set_text(v);
577 }
578 }
579 }
580
581 doc.add_paragraph("")
582 .section_break(SectionBreak::NextPage)
583 .section_landscape();
584
585 // ── SECTION 9: CUSTOM STYLES ──
586 doc.add_paragraph("9. Custom Styles").style("Heading1");
587 doc.add_style(
588 StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
589 .based_on("Normal")
590 .paragraph_properties({
591 let mut ppr = rdocx_oxml::properties::CT_PPr::default();
592 ppr.shading = Some(rdocx_oxml::properties::CT_Shd {
593 val: "clear".to_string(),
594 color: None,
595 fill: Some("FFF2CC".to_string()),
596 });
597 ppr
598 })
599 .run_properties({
600 let mut rpr = rdocx_oxml::properties::CT_RPr::default();
601 rpr.bold = Some(true);
602 rpr.color = Some("C45911".to_string());
603 rpr
604 }),
605 );
606 doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
607 .style("CustomHighlight");
608
609 doc.add_paragraph("");
610
611 // ── SECTION 10: DOCUMENT INTELLIGENCE ──
612 doc.add_paragraph("10. Document Intelligence API")
613 .style("Heading1");
614
615 let wc = doc.word_count();
616 let hc = doc.headings().len();
617 let ic = doc.images().len();
618 let lc = doc.links().len();
619 doc.add_paragraph(&format!("Word count: {wc}"));
620 doc.add_paragraph(&format!("Heading count: {hc}"));
621 doc.add_paragraph(&format!("Image count: {ic}"));
622 doc.add_paragraph(&format!("Link count: {lc}"));
623
624 let outline = doc.document_outline();
625 doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
626
627 let issues = doc.audit_accessibility();
628 doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
629 for issue in &issues {
630 doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
631 }
632
633 // ── SECTION 11: DOCUMENT MERGING ──
634 doc.add_paragraph("");
635 doc.add_paragraph("11. Document Merging").style("Heading1");
636 {
637 let mut other = Document::new();
638 other.add_paragraph("This paragraph was merged from another document using append().");
639 doc.append(&other);
640 }
641
642 doc.add_paragraph("");
643
644 // ── SUMMARY ──
645 doc.add_paragraph("Summary of Demonstrated Features")
646 .style("Heading1");
647 let features = [
648 "Page setup: size, margins, header/footer distance, gutter",
649 "Document metadata: title, author, subject, keywords",
650 "Headers and footers: text, images, different first page",
651 "Background images (full-page behind text)",
652 "Table of Contents generation with bookmarks",
653 "Text formatting: bold, italic, underline styles, strike, color, size, font",
654 "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
655 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
656 "Pagination controls: keep-with-next, keep-together, widow control",
657 "Bullet and numbered lists with nesting",
658 "Tab stops with dot/underscore/hyphen leaders",
659 "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
660 "Row properties: header rows, exact height, cant-split",
661 "Cell properties: width, no-wrap, vertical alignment",
662 "Inline images and header images",
663 "Placeholder replacement (single and batch)",
664 "Regex-based find and replace",
665 "Content insertion at specific positions",
666 "Section breaks with mixed portrait/landscape",
667 "Custom paragraph and character styles",
668 "Document intelligence: word count, headings, outline, images, links",
669 "Accessibility audit",
670 "Document merging (append)",
671 "Export: DOCX, PDF, HTML, Markdown",
672 ];
673 for f in &features {
674 doc.add_bullet_list_item(f, 0);
675 }
676 doc.add_paragraph("");
677 doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
678 .alignment(Alignment::Center)
679 .shading("E2EFDA")
680 .border_all(BorderStyle::Single, 2, "00AA00");
681
682 doc
683}
684
685// =============================================================================
686// 2. PROPOSAL DOCUMENT — Deep navy + gold scheme
687// =============================================================================
688fn generate_proposal(_samples_dir: &Path) -> Document {
689 let mut doc = Document::new();
690
691 // Colors: Navy #1B2A4A, Gold #C5922E, Light #F4F1EB
692 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
693 doc.set_margins(
694 Length::inches(1.0),
695 Length::inches(1.0),
696 Length::inches(1.0),
697 Length::inches(1.0),
698 );
699 doc.set_title("AI Platform Modernization Proposal");
700 doc.set_author("Walter White");
701 doc.set_subject("Technology Proposal");
702 doc.set_keywords("proposal, AI, modernization, Tensorbee");
703
704 // Banner header
705 let logo_img = create_logo_png(220, 48);
706 let banner = build_header_banner_xml(
707 "rId1",
708 &BannerOpts {
709 bg_color: "1B2A4A",
710 banner_width: 7772400,
711 banner_height: 914400,
712 logo_width: 2011680,
713 logo_height: 438912,
714 logo_x_offset: 295125,
715 logo_y_offset: 237744,
716 },
717 );
718 doc.set_raw_header_with_images(
719 banner,
720 &[("rId1", &logo_img, "logo.png")],
721 rdocx_oxml::header_footer::HdrFtrType::Default,
722 );
723 doc.set_different_first_page(true);
724 let cover_banner = build_header_banner_xml(
725 "rId1",
726 &BannerOpts {
727 bg_color: "C5922E",
728 banner_width: 7772400,
729 banner_height: 914400,
730 logo_width: 2011680,
731 logo_height: 438912,
732 logo_x_offset: 295125,
733 logo_y_offset: 237744,
734 },
735 );
736 doc.set_raw_header_with_images(
737 cover_banner,
738 &[("rId1", &logo_img, "logo.png")],
739 rdocx_oxml::header_footer::HdrFtrType::First,
740 );
741 doc.set_margins(
742 Length::twips(2292),
743 Length::twips(1440),
744 Length::twips(1440),
745 Length::twips(1440),
746 );
747 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
748 doc.set_footer("Tensorbee — Confidential");
749
750 // Custom styles
751 doc.add_style(
752 StyleBuilder::paragraph("ProposalTitle", "Proposal Title")
753 .based_on("Normal")
754 .run_properties({
755 let mut rpr = rdocx_oxml::properties::CT_RPr::default();
756 rpr.bold = Some(true);
757 rpr.font_ascii = Some("Georgia".to_string());
758 rpr.font_hansi = Some("Georgia".to_string());
759 rpr.sz = Some(rdocx_oxml::HalfPoint(56)); // half-points → 28pt
760 rpr.color = Some("1B2A4A".to_string());
761 rpr
762 }),
763 );
764
765 // ── Cover Page ──
766 for _ in 0..4 {
767 doc.add_paragraph("");
768 }
769 doc.add_paragraph("AI Platform Modernization")
770 .style("ProposalTitle")
771 .alignment(Alignment::Center);
772 doc.add_paragraph("")
773 .alignment(Alignment::Center)
774 .border_bottom(BorderStyle::Single, 8, "C5922E");
775 {
776 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
777 p.add_run("Prepared for Global Financial Corp.")
778 .size(14.0)
779 .color("666666")
780 .italic(true);
781 }
782 {
783 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
784 p.add_run("Prepared by: Walter White, CEO — Tensorbee")
785 .size(12.0)
786 .color("888888");
787 }
788 {
789 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
790 p.add_run("February 22, 2026").size(12.0).color("888888");
791 }
792
793 // ── TOC ──
794 doc.add_paragraph("").page_break_before(true);
795 doc.add_paragraph("Table of Contents").style("Heading1");
796 // We'll add a manual-style TOC since the insert_toc goes at a specific position
797 doc.insert_toc(doc.content_count(), 2);
798
799 // ── Executive Summary ──
800 doc.add_paragraph("").page_break_before(true);
801 doc.add_paragraph("Executive Summary").style("Heading1");
802 doc.add_paragraph(
803 "Tensorbee proposes a comprehensive modernization of Global Financial Corp's AI platform, \
804 transitioning from legacy batch-processing systems to a real-time inference architecture. \
805 This transformation will reduce model deployment time from weeks to hours, improve \
806 prediction accuracy by an estimated 23%, and deliver $4.2M in annual operational savings.",
807 )
808 .first_line_indent(Length::inches(0.3));
809 doc.add_paragraph(
810 "The project spans three phases over 18 months, with a total investment of $2.8M. \
811 Tensorbee brings deep expertise in ML infrastructure, having successfully completed \
812 similar transformations for three Fortune 500 financial institutions.",
813 )
814 .first_line_indent(Length::inches(0.3));
815
816 // Key metrics table
817 doc.add_paragraph("");
818 {
819 let mut tbl = doc
820 .add_table(5, 2)
821 .borders(BorderStyle::Single, 4, "1B2A4A")
822 .width_pct(80.0);
823 tbl.cell(0, 0).unwrap().set_text("Key Metric");
824 tbl.cell(0, 0).unwrap().shading("1B2A4A");
825 tbl.cell(0, 1).unwrap().set_text("Value");
826 tbl.cell(0, 1).unwrap().shading("1B2A4A");
827 let rows = [
828 ("Total Investment", "$2.8M"),
829 ("Annual Savings", "$4.2M"),
830 ("ROI (Year 1)", "150%"),
831 ("Timeline", "18 months"),
832 ];
833 for (i, (k, v)) in rows.iter().enumerate() {
834 tbl.cell(i + 1, 0).unwrap().set_text(k);
835 if i % 2 == 0 {
836 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
837 }
838 tbl.cell(i + 1, 1).unwrap().set_text(v);
839 if i % 2 == 0 {
840 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
841 }
842 }
843 }
844
845 // ── Problem Statement ──
846 doc.add_paragraph("").page_break_before(true);
847 doc.add_paragraph("Problem Statement").style("Heading1");
848 doc.add_paragraph(
849 "Global Financial Corp's current AI infrastructure faces three critical challenges:",
850 );
851 doc.add_numbered_list_item("Deployment Latency — New models take 3-4 weeks to deploy to production, compared to the industry benchmark of 2-3 days.", 0);
852 doc.add_numbered_list_item("Scalability Constraints — The batch processing architecture cannot handle real-time inference demands during peak trading hours.", 0);
853 doc.add_numbered_list_item("Technical Debt — Legacy Python 2.7 codebases and manual deployment scripts create reliability risks and slow iteration speed.", 0);
854
855 // ── Proposed Solution ──
856 doc.add_paragraph("").page_break_before(true);
857 doc.add_paragraph("Proposed Solution").style("Heading1");
858
859 doc.add_paragraph("Phase 1: Foundation (Months 1-6)")
860 .style("Heading2");
861 doc.add_bullet_list_item("Deploy Kubernetes-based ML serving infrastructure", 0);
862 doc.add_bullet_list_item("Implement CI/CD pipeline for model deployment", 0);
863 doc.add_bullet_list_item("Migrate top 5 production models to new platform", 0);
864
865 doc.add_paragraph("Phase 2: Expansion (Months 7-12)")
866 .style("Heading2");
867 doc.add_bullet_list_item(
868 "Real-time feature engineering pipeline (Apache Kafka + Flink)",
869 0,
870 );
871 doc.add_bullet_list_item("A/B testing framework for model variants", 0);
872 doc.add_bullet_list_item("Automated model monitoring and drift detection", 0);
873
874 doc.add_paragraph("Phase 3: Optimization (Months 13-18)")
875 .style("Heading2");
876 doc.add_bullet_list_item("GPU inference optimization (TensorRT/ONNX Runtime)", 0);
877 doc.add_bullet_list_item("Multi-region deployment for latency reduction", 0);
878 doc.add_bullet_list_item(
879 "Self-service model deployment portal for data science teams",
880 0,
881 );
882
883 // ── Budget ──
884 doc.add_paragraph("Budget Breakdown").style("Heading1");
885 {
886 let mut tbl = doc
887 .add_table(6, 3)
888 .borders(BorderStyle::Single, 4, "1B2A4A")
889 .width_pct(100.0);
890 tbl.cell(0, 0).unwrap().set_text("Category");
891 tbl.cell(0, 0).unwrap().shading("1B2A4A");
892 tbl.cell(0, 1).unwrap().set_text("Cost");
893 tbl.cell(0, 1).unwrap().shading("1B2A4A");
894 tbl.cell(0, 2).unwrap().set_text("Notes");
895 tbl.cell(0, 2).unwrap().shading("1B2A4A");
896 let items = [
897 (
898 "Infrastructure",
899 "$450,000",
900 "Cloud compute + GPU instances",
901 ),
902 ("Engineering Services", "$1,600,000", "12 FTE x 18 months"),
903 (
904 "Software Licenses",
905 "$350,000",
906 "Kafka, monitoring, CI/CD tools",
907 ),
908 (
909 "Training & Enablement",
910 "$200,000",
911 "Team training, documentation",
912 ),
913 ("Contingency (10%)", "$200,000", "Risk buffer"),
914 ];
915 for (i, (cat, cost, note)) in items.iter().enumerate() {
916 tbl.cell(i + 1, 0).unwrap().set_text(cat);
917 tbl.cell(i + 1, 1).unwrap().set_text(cost);
918 tbl.cell(i + 1, 2).unwrap().set_text(note);
919 if i % 2 == 0 {
920 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
921 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
922 tbl.cell(i + 1, 2).unwrap().shading("F4F1EB");
923 }
924 }
925 }
926
927 // ── Closing ──
928 doc.add_paragraph("");
929 doc.add_paragraph("Next Steps").style("Heading1");
930 doc.add_numbered_list_item(
931 "Schedule technical deep-dive with GFC engineering team (Week 1)",
932 0,
933 );
934 doc.add_numbered_list_item("Finalize scope and sign SOW (Week 2-3)", 0);
935 doc.add_numbered_list_item("Kick off Phase 1 with joint planning session (Week 4)", 0);
936
937 doc.add_paragraph("");
938 {
939 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
940 p.add_run("Walter White")
941 .bold(true)
942 .size(14.0)
943 .color("1B2A4A");
944 }
945 {
946 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
947 p.add_run("CEO, Tensorbee | walter@tensorbee.com")
948 .size(10.0)
949 .color("888888");
950 }
951
952 doc
953}
954
955// =============================================================================
956// 3. QUOTE / BILL OF MATERIALS — Teal + orange scheme
957// =============================================================================
958fn generate_quote(_samples_dir: &Path) -> Document {
959 let mut doc = Document::new();
960
961 // Colors: Teal #008B8B, Dark #1A3C3C, Orange #E07020, Light #F0F8F8
962 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
963 doc.set_margins(
964 Length::inches(0.75),
965 Length::inches(0.75),
966 Length::inches(0.75),
967 Length::inches(0.75),
968 );
969 doc.set_title("Quotation QT-2026-0147");
970 doc.set_author("Walter White");
971 doc.set_keywords("quote, BOM, Tensorbee");
972
973 doc.set_header("Tensorbee — Quotation");
974 doc.set_footer("QT-2026-0147 | Page");
975
976 // ── Header Block ──
977 {
978 let mut p = doc.add_paragraph("").alignment(Alignment::Left);
979 p.add_run("TENSORBEE")
980 .bold(true)
981 .size(28.0)
982 .color("008B8B")
983 .font("Helvetica");
984 }
985 doc.add_paragraph("123 Innovation Drive, Suite 400")
986 .alignment(Alignment::Left);
987 doc.add_paragraph("San Francisco, CA 94105 | +1 (415) 555-0199")
988 .alignment(Alignment::Left);
989 doc.add_paragraph("")
990 .border_bottom(BorderStyle::Single, 8, "008B8B");
991
992 // Quote meta
993 doc.add_paragraph("");
994 {
995 let mut tbl = doc.add_table(4, 4).width_pct(100.0);
996 tbl.cell(0, 0).unwrap().set_text("QUOTATION");
997 tbl.cell(0, 0).unwrap().shading("008B8B").grid_span(2);
998 tbl.cell(0, 2).unwrap().set_text("DATE");
999 tbl.cell(0, 2).unwrap().shading("008B8B");
1000 tbl.cell(0, 3).unwrap().set_text("VALID UNTIL");
1001 tbl.cell(0, 3).unwrap().shading("008B8B");
1002 tbl.cell(1, 0).unwrap().set_text("Quote #:");
1003 tbl.cell(1, 1).unwrap().set_text("QT-2026-0147");
1004 tbl.cell(1, 2).unwrap().set_text("Feb 22, 2026");
1005 tbl.cell(1, 3).unwrap().set_text("Mar 22, 2026");
1006 tbl.cell(2, 0).unwrap().set_text("Prepared by:");
1007 tbl.cell(2, 1).unwrap().set_text("Walter White");
1008 tbl.cell(2, 2).unwrap().set_text("Payment:");
1009 tbl.cell(2, 3).unwrap().set_text("Net 30");
1010 tbl.cell(3, 0).unwrap().set_text("Customer:");
1011 tbl.cell(3, 1).unwrap().set_text("Meridian Dynamics LLC");
1012 tbl.cell(3, 2).unwrap().set_text("Currency:");
1013 tbl.cell(3, 3).unwrap().set_text("USD");
1014 }
1015
1016 doc.add_paragraph("");
1017
1018 // ── Bill of Materials ──
1019 {
1020 let mut p = doc.add_paragraph("");
1021 p.add_run("Bill of Materials")
1022 .bold(true)
1023 .size(16.0)
1024 .color("1A3C3C");
1025 }
1026 doc.add_paragraph("");
1027
1028 {
1029 let mut tbl = doc
1030 .add_table(10, 6)
1031 .borders(BorderStyle::Single, 2, "008B8B")
1032 .width_pct(100.0)
1033 .layout_fixed();
1034 // Headers
1035 let hdrs = [
1036 "#",
1037 "Part Number",
1038 "Description",
1039 "Qty",
1040 "Unit Price",
1041 "Total",
1042 ];
1043 for (c, h) in hdrs.iter().enumerate() {
1044 tbl.cell(0, c).unwrap().set_text(h);
1045 tbl.cell(0, c).unwrap().shading("008B8B");
1046 }
1047
1048 let items: Vec<(&str, &str, &str, &str, &str)> = vec![
1049 (
1050 "TB-GPU-A100",
1051 "NVIDIA A100 80GB GPU",
1052 "4",
1053 "$12,500.00",
1054 "$50,000.00",
1055 ),
1056 (
1057 "TB-SRV-R750",
1058 "Dell R750xa Server Chassis",
1059 "2",
1060 "$8,200.00",
1061 "$16,400.00",
1062 ),
1063 (
1064 "TB-RAM-256G",
1065 "256GB DDR5 ECC Memory Module",
1066 "8",
1067 "$890.00",
1068 "$7,120.00",
1069 ),
1070 (
1071 "TB-SSD-3840",
1072 "3.84TB NVMe U.2 SSD",
1073 "8",
1074 "$1,150.00",
1075 "$9,200.00",
1076 ),
1077 (
1078 "TB-NET-CX7",
1079 "ConnectX-7 200GbE NIC",
1080 "4",
1081 "$1,800.00",
1082 "$7,200.00",
1083 ),
1084 (
1085 "TB-SW-48P",
1086 "48-Port 100GbE Switch",
1087 "1",
1088 "$22,000.00",
1089 "$22,000.00",
1090 ),
1091 (
1092 "TB-CAB-RACK",
1093 "42U Server Rack + PDU",
1094 "1",
1095 "$4,500.00",
1096 "$4,500.00",
1097 ),
1098 (
1099 "TB-SVC-INST",
1100 "Installation & Configuration",
1101 "1",
1102 "$8,500.00",
1103 "$8,500.00",
1104 ),
1105 (
1106 "TB-SVC-SUPP",
1107 "3-Year Premium Support",
1108 "1",
1109 "$15,000.00",
1110 "$15,000.00",
1111 ),
1112 ];
1113
1114 for (i, (pn, desc, qty, unit, total)) in items.iter().enumerate() {
1115 let row = i + 1;
1116 tbl.cell(row, 0).unwrap().set_text(&format!("{}", i + 1));
1117 tbl.cell(row, 1).unwrap().set_text(pn);
1118 tbl.cell(row, 2).unwrap().set_text(desc);
1119 tbl.cell(row, 3).unwrap().set_text(qty);
1120 tbl.cell(row, 4).unwrap().set_text(unit);
1121 tbl.cell(row, 5).unwrap().set_text(total);
1122 if i % 2 == 0 {
1123 for c in 0..6 {
1124 tbl.cell(row, c).unwrap().shading("F0F8F8");
1125 }
1126 }
1127 }
1128 }
1129
1130 doc.add_paragraph("");
1131
1132 // ── Totals ──
1133 {
1134 let mut tbl = doc
1135 .add_table(4, 2)
1136 .borders(BorderStyle::Single, 2, "008B8B")
1137 .width(Length::inches(3.5))
1138 .alignment(Alignment::Right);
1139 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1140 tbl.cell(0, 1).unwrap().set_text("$139,920.00");
1141 tbl.cell(1, 0).unwrap().set_text("Shipping & Handling");
1142 tbl.cell(1, 1).unwrap().set_text("$2,500.00");
1143 tbl.cell(2, 0).unwrap().set_text("Tax (8.625%)");
1144 tbl.cell(2, 1).unwrap().set_text("$12,068.10");
1145 tbl.cell(3, 0).unwrap().set_text("TOTAL");
1146 tbl.cell(3, 0).unwrap().shading("E07020");
1147 tbl.cell(3, 1).unwrap().set_text("$154,488.10");
1148 tbl.cell(3, 1).unwrap().shading("E07020");
1149 }
1150
1151 doc.add_paragraph("");
1152
1153 // ── Terms & Conditions ──
1154 {
1155 let mut p = doc.add_paragraph("");
1156 p.add_run("Terms & Conditions")
1157 .bold(true)
1158 .size(14.0)
1159 .color("1A3C3C");
1160 }
1161 doc.add_numbered_list_item(
1162 "This quotation is valid for 30 calendar days from the date of issue.",
1163 0,
1164 );
1165 doc.add_numbered_list_item(
1166 "All prices are in USD and exclusive of applicable taxes unless stated.",
1167 0,
1168 );
1169 doc.add_numbered_list_item("Standard lead time is 4-6 weeks from PO receipt.", 0);
1170 doc.add_numbered_list_item(
1171 "Payment terms: Net 30 from invoice date. 2% discount for payment within 10 days.",
1172 0,
1173 );
1174 doc.add_numbered_list_item(
1175 "Warranty: 3-year manufacturer warranty on all hardware components.",
1176 0,
1177 );
1178 doc.add_numbered_list_item(
1179 "Returns subject to 15% restocking fee if initiated after 14 days.",
1180 0,
1181 );
1182
1183 doc.add_paragraph("");
1184 doc.add_paragraph("")
1185 .border_bottom(BorderStyle::Single, 4, "008B8B");
1186 doc.add_paragraph("");
1187 {
1188 let mut p = doc.add_paragraph("");
1189 p.add_run("Accepted by: ").bold(true);
1190 p.add_run("___________________________ Date: ___________");
1191 }
1192 {
1193 let mut p = doc.add_paragraph("");
1194 p.add_run("Print Name: ").bold(true);
1195 p.add_run("___________________________ Title: ___________");
1196 }
1197
1198 doc
1199}
1200
1201// =============================================================================
1202// 4. INVOICE — Crimson + charcoal scheme
1203// =============================================================================
1204fn generate_invoice(_samples_dir: &Path) -> Document {
1205 let mut doc = Document::new();
1206
1207 // Colors: Crimson #B22222, Charcoal #333333, Light #FAF0F0
1208 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1209 doc.set_margins(
1210 Length::inches(0.75),
1211 Length::inches(0.75),
1212 Length::inches(0.75),
1213 Length::inches(0.75),
1214 );
1215 doc.set_title("Invoice INV-2026-0392");
1216 doc.set_author("Walter White");
1217 doc.set_keywords("invoice, Tensorbee");
1218
1219 doc.set_footer("Tensorbee — Thank you for your business!");
1220
1221 // ── Company & Invoice Header ──
1222 {
1223 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1224 // Company name left, INVOICE right
1225 {
1226 let mut cell = tbl.cell(0, 0).unwrap();
1227 let mut p = cell.add_paragraph("");
1228 p.add_run("TENSORBEE")
1229 .bold(true)
1230 .size(32.0)
1231 .color("B22222")
1232 .font("Helvetica");
1233 }
1234 {
1235 let mut cell = tbl.cell(0, 1).unwrap();
1236 let mut p = cell.add_paragraph("");
1237 p.add_run("INVOICE").bold(true).size(32.0).color("333333");
1238 }
1239 tbl.cell(1, 0)
1240 .unwrap()
1241 .set_text("123 Innovation Drive, Suite 400");
1242 tbl.cell(1, 1).unwrap().set_text("Invoice #: INV-2026-0392");
1243 tbl.cell(2, 0).unwrap().set_text("San Francisco, CA 94105");
1244 tbl.cell(2, 1).unwrap().set_text("Date: February 22, 2026");
1245 tbl.cell(3, 0).unwrap().set_text("walter@tensorbee.com");
1246 tbl.cell(3, 1).unwrap().set_text("Due Date: March 24, 2026");
1247 }
1248
1249 doc.add_paragraph("")
1250 .border_bottom(BorderStyle::Thick, 8, "B22222");
1251 doc.add_paragraph("");
1252
1253 // ── Bill To / Ship To ──
1254 {
1255 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1256 tbl.cell(0, 0).unwrap().set_text("BILL TO");
1257 tbl.cell(0, 0).unwrap().shading("B22222");
1258 tbl.cell(0, 1).unwrap().set_text("SHIP TO");
1259 tbl.cell(0, 1).unwrap().shading("B22222");
1260 tbl.cell(1, 0).unwrap().set_text("Meridian Dynamics LLC");
1261 tbl.cell(1, 1).unwrap().set_text("Meridian Dynamics LLC");
1262 tbl.cell(2, 0).unwrap().set_text("456 Enterprise Blvd");
1263 tbl.cell(2, 1).unwrap().set_text("Attn: Server Room B");
1264 tbl.cell(3, 0).unwrap().set_text("Austin, TX 78701");
1265 tbl.cell(3, 1)
1266 .unwrap()
1267 .set_text("456 Enterprise Blvd, Austin, TX 78701");
1268 }
1269
1270 doc.add_paragraph("");
1271
1272 // ── Line Items ──
1273 {
1274 let mut tbl = doc
1275 .add_table(8, 5)
1276 .borders(BorderStyle::Single, 2, "B22222")
1277 .width_pct(100.0);
1278 let hdrs = ["Description", "Qty", "Unit Price", "Tax", "Amount"];
1279 for (c, h) in hdrs.iter().enumerate() {
1280 tbl.cell(0, c).unwrap().set_text(h);
1281 tbl.cell(0, c).unwrap().shading("B22222");
1282 }
1283 let items = [
1284 (
1285 "ML Infrastructure Setup — Phase 1",
1286 "1",
1287 "$45,000.00",
1288 "$3,881.25",
1289 "$48,881.25",
1290 ),
1291 (
1292 "Data Pipeline Development (160 hrs)",
1293 "160",
1294 "$225.00",
1295 "$3,105.00",
1296 "$39,105.00",
1297 ),
1298 (
1299 "GPU Cluster Configuration",
1300 "1",
1301 "$12,000.00",
1302 "$1,035.00",
1303 "$13,035.00",
1304 ),
1305 (
1306 "API Gateway Implementation",
1307 "1",
1308 "$18,500.00",
1309 "$1,595.63",
1310 "$20,095.63",
1311 ),
1312 (
1313 "Load Testing & QA (80 hrs)",
1314 "80",
1315 "$195.00",
1316 "$1,345.50",
1317 "$16,945.50",
1318 ),
1319 (
1320 "Documentation & Training",
1321 "1",
1322 "$8,000.00",
1323 "$690.00",
1324 "$8,690.00",
1325 ),
1326 (
1327 "Project Management (3 months)",
1328 "3",
1329 "$6,500.00",
1330 "$1,679.63",
1331 "$21,179.63",
1332 ),
1333 ];
1334 for (i, (desc, qty, unit, tax, amt)) in items.iter().enumerate() {
1335 let r = i + 1;
1336 tbl.cell(r, 0).unwrap().set_text(desc);
1337 tbl.cell(r, 1).unwrap().set_text(qty);
1338 tbl.cell(r, 2).unwrap().set_text(unit);
1339 tbl.cell(r, 3).unwrap().set_text(tax);
1340 tbl.cell(r, 4).unwrap().set_text(amt);
1341 if i % 2 == 0 {
1342 for c in 0..5 {
1343 tbl.cell(r, c).unwrap().shading("FAF0F0");
1344 }
1345 }
1346 }
1347 }
1348
1349 doc.add_paragraph("");
1350
1351 // ── Totals ──
1352 {
1353 let mut tbl = doc
1354 .add_table(4, 2)
1355 .borders(BorderStyle::Single, 2, "B22222")
1356 .width(Length::inches(3.0))
1357 .alignment(Alignment::Right);
1358 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1359 tbl.cell(0, 1).unwrap().set_text("$154,600.00");
1360 tbl.cell(1, 0).unwrap().set_text("Tax (8.625%)");
1361 tbl.cell(1, 1).unwrap().set_text("$13,334.25");
1362 tbl.cell(2, 0).unwrap().set_text("Discount (5%)");
1363 tbl.cell(2, 1).unwrap().set_text("-$7,730.00");
1364 tbl.cell(3, 0).unwrap().set_text("AMOUNT DUE");
1365 tbl.cell(3, 0).unwrap().shading("B22222");
1366 tbl.cell(3, 1).unwrap().set_text("$160,204.25");
1367 tbl.cell(3, 1).unwrap().shading("B22222");
1368 }
1369
1370 doc.add_paragraph("");
1371 doc.add_paragraph("");
1372
1373 // ── Payment Details ──
1374 {
1375 let mut p = doc.add_paragraph("");
1376 p.add_run("Payment Details")
1377 .bold(true)
1378 .size(14.0)
1379 .color("333333");
1380 }
1381 doc.add_paragraph("Bank: Silicon Valley Bank")
1382 .indent_left(Length::inches(0.3));
1383 doc.add_paragraph("Account: Tensorbee Inc. — 0847-2953-1120")
1384 .indent_left(Length::inches(0.3));
1385 doc.add_paragraph("Routing: 121140399")
1386 .indent_left(Length::inches(0.3));
1387 doc.add_paragraph("Swift: SVBKUS6S")
1388 .indent_left(Length::inches(0.3));
1389
1390 doc.add_paragraph("");
1391
1392 doc.add_paragraph("Please include invoice number INV-2026-0392 in the payment reference.")
1393 .shading("FAF0F0")
1394 .border_all(BorderStyle::Single, 2, "B22222");
1395
1396 doc
1397}
1398
1399// =============================================================================
1400// 5. REPORT — Forest green + earth tones, with images & hierarchical sections
1401// =============================================================================
1402fn generate_report(_samples_dir: &Path) -> Document {
1403 let mut doc = Document::new();
1404
1405 // Colors: Forest #2D5016, Sage #6B8E23, Earth #8B7355, Cream #FFFAF0
1406 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1407 doc.set_margins(
1408 Length::inches(1.0),
1409 Length::inches(1.0),
1410 Length::inches(1.0),
1411 Length::inches(1.0),
1412 );
1413 doc.set_title("Q4 2025 Environmental Impact Report");
1414 doc.set_author("Walter White");
1415 doc.set_subject("Quarterly Environmental Report");
1416 doc.set_keywords("environment, sustainability, report, Tensorbee");
1417
1418 doc.set_different_first_page(true);
1419 doc.set_first_page_header("");
1420 doc.set_header("Tensorbee — Q4 2025 Environmental Impact Report");
1421 doc.set_footer("CONFIDENTIAL — Page");
1422
1423 // ── Cover ──
1424 let cover_bg = create_sample_png(612, 792, [20, 50, 15]);
1425 doc.add_background_image(&cover_bg, "report_cover.png");
1426
1427 for _ in 0..5 {
1428 doc.add_paragraph("");
1429 }
1430 {
1431 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1432 p.add_run("Q4 2025")
1433 .bold(true)
1434 .size(48.0)
1435 .color("FFFFFF")
1436 .font("Georgia");
1437 }
1438 {
1439 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1440 p.add_run("Environmental Impact Report")
1441 .size(24.0)
1442 .color("90EE90")
1443 .font("Georgia")
1444 .italic(true);
1445 }
1446 doc.add_paragraph("");
1447 {
1448 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1449 p.add_run("Tensorbee — Sustainability Division")
1450 .size(14.0)
1451 .color("C0C0C0");
1452 }
1453 {
1454 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1455 p.add_run("Prepared by Walter White, Chief Sustainability Officer")
1456 .size(11.0)
1457 .color("AAAAAA");
1458 }
1459
1460 // ── TOC ──
1461 doc.add_paragraph("").page_break_before(true);
1462 doc.insert_toc(doc.content_count(), 3);
1463
1464 // ── Section 1: Executive Overview ──
1465 doc.add_paragraph("").page_break_before(true);
1466 doc.add_paragraph("Executive Overview").style("Heading1");
1467 doc.add_paragraph(
1468 "This report presents Tensorbee's environmental performance for Q4 2025. Our \
1469 sustainability initiatives have yielded a 34% reduction in carbon emissions compared \
1470 to Q4 2024, exceeding our target of 25%. Key achievements include the transition to \
1471 100% renewable energy in our primary data centers and the launch of our carbon offset \
1472 marketplace.",
1473 )
1474 .first_line_indent(Length::inches(0.3));
1475
1476 // Image: performance chart
1477 let chart_img = create_chart_png(400, 200);
1478 doc.add_paragraph("");
1479 doc.add_picture(
1480 &chart_img,
1481 "performance_chart.png",
1482 Length::inches(5.0),
1483 Length::inches(2.5),
1484 )
1485 .alignment(Alignment::Center);
1486 {
1487 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1488 p.add_run("Figure 1: Quarterly Carbon Emissions (tonnes CO2e)")
1489 .italic(true)
1490 .size(9.0)
1491 .color("666666");
1492 }
1493
1494 // ── Section 2: Energy Consumption ──
1495 doc.add_paragraph("").page_break_before(true);
1496 doc.add_paragraph("Energy Consumption").style("Heading1");
1497
1498 doc.add_paragraph("Data Center Operations")
1499 .style("Heading2");
1500 doc.add_paragraph(
1501 "Our three primary data centers consumed a combined 4.2 GWh in Q4 2025, \
1502 a 12% reduction from Q3 2025 driven by improved cooling efficiency and \
1503 server consolidation.",
1504 );
1505
1506 // Data table
1507 {
1508 let mut tbl = doc
1509 .add_table(5, 4)
1510 .borders(BorderStyle::Single, 4, "2D5016")
1511 .width_pct(100.0);
1512 let hdrs = ["Data Center", "Capacity (MW)", "Usage (GWh)", "PUE"];
1513 for (c, h) in hdrs.iter().enumerate() {
1514 tbl.cell(0, c).unwrap().set_text(h);
1515 tbl.cell(0, c).unwrap().shading("2D5016");
1516 }
1517 let rows = [
1518 ("San Francisco (Primary)", "3.2", "1.8", "1.12"),
1519 ("Dublin (EU)", "2.1", "1.2", "1.18"),
1520 ("Singapore (APAC)", "1.8", "1.2", "1.24"),
1521 ("TOTAL", "7.1", "4.2", "1.17 avg"),
1522 ];
1523 for (i, (dc, cap, use_, pue)) in rows.iter().enumerate() {
1524 tbl.cell(i + 1, 0).unwrap().set_text(dc);
1525 tbl.cell(i + 1, 1).unwrap().set_text(cap);
1526 tbl.cell(i + 1, 2).unwrap().set_text(use_);
1527 tbl.cell(i + 1, 3).unwrap().set_text(pue);
1528 if i == 3 {
1529 for c in 0..4 {
1530 tbl.cell(i + 1, c).unwrap().shading("E2EFDA");
1531 }
1532 }
1533 }
1534 }
1535
1536 doc.add_paragraph("");
1537
1538 doc.add_paragraph("Renewable Energy Mix").style("Heading2");
1539 doc.add_paragraph("Breakdown of energy sources across all facilities:");
1540
1541 doc.add_bullet_list_item("Solar PV: 42% (1.76 GWh)", 0);
1542 doc.add_bullet_list_item("Wind Power Purchase Agreements: 38% (1.60 GWh)", 0);
1543 doc.add_bullet_list_item("Hydroelectric: 12% (0.50 GWh)", 0);
1544 doc.add_bullet_list_item("Grid (non-renewable): 8% (0.34 GWh)", 0);
1545
1546 // ── Section 3: Water & Waste ──
1547 doc.add_paragraph("Water & Waste Management")
1548 .style("Heading1");
1549
1550 doc.add_paragraph("Water Usage").style("Heading2");
1551 doc.add_paragraph(
1552 "Total water consumption was 12.4 million gallons, a 15% reduction from Q3. \
1553 Our closed-loop cooling systems now recycle 78% of water used in cooling operations.",
1554 );
1555
1556 doc.add_paragraph("Waste Reduction").style("Heading2");
1557 doc.add_paragraph("E-waste management results for Q4:")
1558 .keep_with_next(true);
1559 {
1560 let mut tbl = doc
1561 .add_table(4, 3)
1562 .borders(BorderStyle::Single, 2, "6B8E23")
1563 .width_pct(80.0);
1564 tbl.cell(0, 0).unwrap().set_text("Category");
1565 tbl.cell(0, 0).unwrap().shading("6B8E23");
1566 tbl.cell(0, 1).unwrap().set_text("Weight (kg)");
1567 tbl.cell(0, 1).unwrap().shading("6B8E23");
1568 tbl.cell(0, 2).unwrap().set_text("Recycled %");
1569 tbl.cell(0, 2).unwrap().shading("6B8E23");
1570 let rows = [
1571 ("Server Hardware", "2,450", "94%"),
1572 ("Networking Equipment", "820", "91%"),
1573 ("Storage Media", "340", "99%"),
1574 ];
1575 for (i, (cat, wt, pct)) in rows.iter().enumerate() {
1576 tbl.cell(i + 1, 0).unwrap().set_text(cat);
1577 tbl.cell(i + 1, 1).unwrap().set_text(wt);
1578 tbl.cell(i + 1, 2).unwrap().set_text(pct);
1579 }
1580 }
1581
1582 // ── Section 4: Initiatives ──
1583 doc.add_paragraph("").page_break_before(true);
1584 doc.add_paragraph("Q1 2026 Initiatives").style("Heading1");
1585
1586 doc.add_paragraph("Planned Programs").style("Heading2");
1587 doc.add_numbered_list_item(
1588 "Deploy on-site battery storage at SF data center (2 MWh capacity)",
1589 0,
1590 );
1591 doc.add_numbered_list_item("Pilot immersion cooling in Dublin facility", 0);
1592 doc.add_numbered_list_item("Launch employee commute carbon offset program", 0);
1593 doc.add_numbered_list_item("Achieve ISO 14001 certification for Singapore facility", 0);
1594
1595 doc.add_paragraph("Investment Targets").style("Heading2");
1596 doc.add_paragraph("Sustainability CapEx allocation for FY2026:")
1597 .keep_with_next(true);
1598 {
1599 let mut tbl = doc
1600 .add_table(5, 2)
1601 .borders(BorderStyle::Single, 4, "2D5016");
1602 tbl.cell(0, 0).unwrap().set_text("Initiative");
1603 tbl.cell(0, 0).unwrap().shading("2D5016");
1604 tbl.cell(0, 1).unwrap().set_text("Budget");
1605 tbl.cell(0, 1).unwrap().shading("2D5016");
1606 let rows = [
1607 ("Battery Storage", "$1.2M"),
1608 ("Immersion Cooling Pilot", "$800K"),
1609 ("Solar Panel Expansion", "$2.1M"),
1610 ("Carbon Credits", "$500K"),
1611 ];
1612 for (i, (init, budget)) in rows.iter().enumerate() {
1613 tbl.cell(i + 1, 0).unwrap().set_text(init);
1614 tbl.cell(i + 1, 1).unwrap().set_text(budget);
1615 if i % 2 == 0 {
1616 tbl.cell(i + 1, 0).unwrap().shading("FFFAF0");
1617 tbl.cell(i + 1, 1).unwrap().shading("FFFAF0");
1618 }
1619 }
1620 }
1621
1622 // Image: sustainability roadmap
1623 let roadmap_img = create_sample_png(500, 100, [40, 80, 30]);
1624 doc.add_paragraph("");
1625 doc.add_picture(
1626 &roadmap_img,
1627 "roadmap.png",
1628 Length::inches(6.0),
1629 Length::inches(1.2),
1630 )
1631 .alignment(Alignment::Center);
1632 {
1633 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1634 p.add_run("Figure 2: 2026 Sustainability Roadmap")
1635 .italic(true)
1636 .size(9.0)
1637 .color("666666");
1638 }
1639
1640 doc.add_paragraph("");
1641 doc.add_paragraph(
1642 "For questions about this report, contact Walter White at sustainability@tensorbee.com.",
1643 )
1644 .shading("FFFAF0")
1645 .border_all(BorderStyle::Single, 2, "2D5016");
1646
1647 doc
1648}
1649
1650// =============================================================================
1651// 6. LETTER — Slate blue + silver scheme
1652// =============================================================================
1653fn generate_letter(_samples_dir: &Path) -> Document {
1654 let mut doc = Document::new();
1655
1656 // Colors: Slate #4A5568, Blue accent #3182CE
1657 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1658 doc.set_margins(
1659 Length::inches(1.25),
1660 Length::inches(1.25),
1661 Length::inches(1.25),
1662 Length::inches(1.25),
1663 );
1664 doc.set_title("Business Letter");
1665 doc.set_author("Walter White");
1666
1667 // Banner header using raw XML
1668 let logo_img = create_logo_png(220, 48);
1669 let banner = build_header_banner_xml(
1670 "rId1",
1671 &BannerOpts {
1672 bg_color: "4A5568",
1673 banner_width: 7772400,
1674 banner_height: 731520, // ~0.8"
1675 logo_width: 2011680,
1676 logo_height: 438912,
1677 logo_x_offset: 295125,
1678 logo_y_offset: 146304,
1679 },
1680 );
1681 doc.set_raw_header_with_images(
1682 banner,
1683 &[("rId1", &logo_img, "logo.png")],
1684 rdocx_oxml::header_footer::HdrFtrType::Default,
1685 );
1686 doc.set_margins(
1687 Length::twips(2000),
1688 Length::twips(1800),
1689 Length::twips(1440),
1690 Length::twips(1800),
1691 );
1692 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
1693
1694 // Footer with contact
1695 doc.set_footer(
1696 "Tensorbee | 123 Innovation Drive, Suite 400 | San Francisco, CA 94105 | tensorbee.com",
1697 );
1698
1699 // ── Sender Address ──
1700 doc.add_paragraph("Walter White");
1701 doc.add_paragraph("Chief Executive Officer");
1702 doc.add_paragraph("Tensorbee");
1703 doc.add_paragraph("123 Innovation Drive, Suite 400");
1704 doc.add_paragraph("San Francisco, CA 94105");
1705 doc.add_paragraph("");
1706 doc.add_paragraph("February 22, 2026");
1707
1708 doc.add_paragraph("");
1709
1710 // ── Recipient ──
1711 doc.add_paragraph("Ms. Sarah Chen");
1712 doc.add_paragraph("VP of Engineering");
1713 doc.add_paragraph("Meridian Dynamics LLC");
1714 doc.add_paragraph("456 Enterprise Boulevard");
1715 doc.add_paragraph("Austin, TX 78701");
1716
1717 doc.add_paragraph("");
1718
1719 // ── Subject ──
1720 {
1721 let mut p = doc.add_paragraph("");
1722 p.add_run("Re: Strategic Technology Partnership — Phase 2 Expansion")
1723 .bold(true);
1724 }
1725
1726 doc.add_paragraph("");
1727
1728 // ── Body ──
1729 doc.add_paragraph("Dear Ms. Chen,");
1730 doc.add_paragraph("");
1731
1732 doc.add_paragraph(
1733 "I am writing to express Tensorbee's enthusiasm for expanding our strategic technology \
1734 partnership with Meridian Dynamics. Following the successful completion of Phase 1, \
1735 which delivered a 40% improvement in your ML inference pipeline throughput, we believe \
1736 the foundation is firmly established for an ambitious Phase 2 engagement.",
1737 )
1738 .first_line_indent(Length::inches(0.5));
1739
1740 doc.add_paragraph(
1741 "During our recent executive review, your team highlighted three priority areas for \
1742 the next phase of collaboration:",
1743 )
1744 .first_line_indent(Length::inches(0.5));
1745
1746 doc.add_numbered_list_item(
1747 "Real-time anomaly detection for your financial transaction monitoring system, \
1748 targeting sub-100ms latency at 50,000 transactions per second.",
1749 0,
1750 );
1751 doc.add_numbered_list_item(
1752 "Federated learning infrastructure to enable model training across your distributed \
1753 data centers without centralizing sensitive financial data.",
1754 0,
1755 );
1756 doc.add_numbered_list_item(
1757 "MLOps automation to reduce your model deployment cycle from the current 5 days \
1758 to under 4 hours.",
1759 0,
1760 );
1761
1762 doc.add_paragraph(
1763 "Our engineering team has prepared a detailed technical proposal addressing each of \
1764 these areas. We have allocated a dedicated team of eight senior engineers, led by \
1765 our CTO, to ensure continuity with the Phase 1 team your organization has already \
1766 built a productive working relationship with.",
1767 )
1768 .first_line_indent(Length::inches(0.5));
1769
1770 doc.add_paragraph(
1771 "I would welcome the opportunity to present our Phase 2 proposal in person. My \
1772 assistant will reach out to coordinate a meeting at your convenience during the \
1773 first week of March.",
1774 )
1775 .first_line_indent(Length::inches(0.5));
1776
1777 doc.add_paragraph("");
1778
1779 doc.add_paragraph("Warm regards,");
1780 doc.add_paragraph("");
1781 doc.add_paragraph("");
1782
1783 // Signature line
1784 doc.add_paragraph("")
1785 .border_bottom(BorderStyle::Single, 4, "4A5568");
1786 {
1787 let mut p = doc.add_paragraph("");
1788 p.add_run("Walter White").bold(true).size(12.0);
1789 }
1790 {
1791 let mut p = doc.add_paragraph("");
1792 p.add_run("Chief Executive Officer, Tensorbee")
1793 .italic(true)
1794 .size(10.0)
1795 .color("666666");
1796 }
1797 {
1798 let mut p = doc.add_paragraph("");
1799 p.add_run("walter@tensorbee.com | +1 (415) 555-0199")
1800 .size(10.0)
1801 .color("888888");
1802 }
1803
1804 doc
1805}
1806
1807// =============================================================================
1808// 7. EMPLOYMENT CONTRACT — Purple + charcoal scheme
1809// =============================================================================
1810fn generate_contract(_samples_dir: &Path) -> Document {
1811 let mut doc = Document::new();
1812
1813 // Colors: Purple #5B2C6F, Plum #8E44AD, Gray #2C3E50
1814 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1815 doc.set_margins(
1816 Length::inches(1.25),
1817 Length::inches(1.0),
1818 Length::inches(1.0),
1819 Length::inches(1.0),
1820 );
1821 doc.set_title("Employment Agreement");
1822 doc.set_author("Tensorbee HR Department");
1823 doc.set_subject("Employment Contract — Walter White");
1824 doc.set_keywords("employment, contract, agreement, Tensorbee");
1825
1826 doc.set_header("TENSORBEE — Employment Agreement");
1827 doc.set_footer("Employment Agreement — Walter White — Page");
1828
1829 // ── Title Block ──
1830 doc.add_paragraph("")
1831 .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1832 {
1833 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1834 p.add_run("EMPLOYMENT AGREEMENT")
1835 .bold(true)
1836 .size(24.0)
1837 .color("5B2C6F")
1838 .font("Georgia");
1839 }
1840 doc.add_paragraph("")
1841 .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1842 doc.add_paragraph("");
1843
1844 // ── Parties ──
1845 doc.add_paragraph(
1846 "This Employment Agreement (\"Agreement\") is entered into as of February 22, 2026 \
1847 (\"Effective Date\"), by and between:",
1848 );
1849
1850 doc.add_paragraph("");
1851
1852 {
1853 let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1854 p.add_run("EMPLOYER: ").bold(true);
1855 p.add_run(
1856 "Tensorbee, Inc., a Delaware corporation with its principal place of business at \
1857 123 Innovation Drive, Suite 400, San Francisco, CA 94105 (\"Company\")",
1858 );
1859 }
1860 doc.add_paragraph("");
1861 {
1862 let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1863 p.add_run("EMPLOYEE: ").bold(true);
1864 p.add_run(
1865 "Walter White, residing at 308 Negra Arroyo Lane, Albuquerque, NM 87104 (\"Employee\")",
1866 );
1867 }
1868
1869 doc.add_paragraph("");
1870 doc.add_paragraph("The Company and Employee are collectively referred to as the \"Parties.\"");
1871 doc.add_paragraph("");
1872
1873 // ── Article 1: Position and Duties ──
1874 doc.add_paragraph("Article 1 — Position and Duties")
1875 .style("Heading1");
1876 {
1877 let mut p = doc.add_paragraph("");
1878 p.add_run("1.1 ").bold(true);
1879 p.add_run("The Company hereby employs the Employee as ");
1880 p.add_run("Chief Executive Officer (CEO)")
1881 .bold(true)
1882 .italic(true);
1883 p.add_run(", reporting directly to the Board of Directors.");
1884 }
1885 {
1886 let mut p = doc.add_paragraph("");
1887 p.add_run("1.2 ").bold(true);
1888 p.add_run("The Employee shall devote full working time, attention, and best efforts to \
1889 the performance of duties as reasonably assigned by the Board, including but not limited to:");
1890 }
1891 doc.add_bullet_list_item(
1892 "Setting and executing the Company's strategic vision and business plan",
1893 0,
1894 );
1895 doc.add_bullet_list_item(
1896 "Overseeing all operations, engineering, and commercial activities",
1897 0,
1898 );
1899 doc.add_bullet_list_item(
1900 "Representing the Company to investors, customers, and the public",
1901 0,
1902 );
1903 doc.add_bullet_list_item("Recruiting, developing, and retaining key talent", 0);
1904
1905 {
1906 let mut p = doc.add_paragraph("");
1907 p.add_run("1.3 ").bold(true);
1908 p.add_run(
1909 "The Employee's primary work location shall be the Company's San Francisco \
1910 headquarters, with reasonable travel as required by business needs.",
1911 );
1912 }
1913
1914 // ── Article 2: Compensation ──
1915 doc.add_paragraph("Article 2 — Compensation and Benefits")
1916 .style("Heading1");
1917 {
1918 let mut p = doc.add_paragraph("");
1919 p.add_run("2.1 Base Salary. ").bold(true);
1920 p.add_run("The Company shall pay the Employee an annual base salary of ");
1921 p.add_run("$375,000.00").bold(true);
1922 p.add_run(" (Three Hundred Seventy-Five Thousand Dollars), payable in accordance with the \
1923 Company's standard payroll schedule, less applicable withholdings and deductions.");
1924 }
1925 {
1926 let mut p = doc.add_paragraph("");
1927 p.add_run("2.2 Annual Bonus. ").bold(true);
1928 p.add_run("The Employee shall be eligible for an annual performance bonus of up to ");
1929 p.add_run("40%").bold(true);
1930 p.add_run(
1931 " of base salary, based on achievement of mutually agreed performance objectives.",
1932 );
1933 }
1934 {
1935 let mut p = doc.add_paragraph("");
1936 p.add_run("2.3 Equity. ").bold(true);
1937 p.add_run("Subject to Board approval, the Employee shall receive a stock option grant of ");
1938 p.add_run("500,000 shares").bold(true);
1939 p.add_run(
1940 " of the Company's common stock, vesting over four (4) years with a one-year cliff, \
1941 at an exercise price equal to the fair market value on the date of grant.",
1942 );
1943 }
1944
1945 // Compensation summary table
1946 doc.add_paragraph("");
1947 {
1948 let mut tbl = doc
1949 .add_table(5, 2)
1950 .borders(BorderStyle::Single, 4, "5B2C6F")
1951 .width_pct(70.0)
1952 .alignment(Alignment::Center);
1953 tbl.cell(0, 0).unwrap().set_text("Compensation Element");
1954 tbl.cell(0, 0).unwrap().shading("5B2C6F");
1955 tbl.cell(0, 1).unwrap().set_text("Value");
1956 tbl.cell(0, 1).unwrap().shading("5B2C6F");
1957 let items = [
1958 ("Base Salary", "$375,000/year"),
1959 ("Target Bonus", "Up to 40% ($150,000)"),
1960 ("Equity Grant", "500,000 shares (4yr vest)"),
1961 ("Total Target Comp", "$525,000 + equity"),
1962 ];
1963 for (i, (elem, val)) in items.iter().enumerate() {
1964 tbl.cell(i + 1, 0).unwrap().set_text(elem);
1965 tbl.cell(i + 1, 1).unwrap().set_text(val);
1966 if i == 3 {
1967 tbl.cell(i + 1, 0).unwrap().shading("E8DAEF");
1968 tbl.cell(i + 1, 1).unwrap().shading("E8DAEF");
1969 }
1970 }
1971 }
1972
1973 // ── Article 3: Benefits ──
1974 doc.add_paragraph("").page_break_before(true);
1975 doc.add_paragraph("Article 3 — Benefits").style("Heading1");
1976 {
1977 let mut p = doc.add_paragraph("");
1978 p.add_run("3.1 ").bold(true);
1979 p.add_run(
1980 "The Employee shall be entitled to participate in all benefit programs \
1981 generally available to senior executives, including:",
1982 );
1983 }
1984 doc.add_bullet_list_item(
1985 "Medical, dental, and vision insurance (100% premium coverage for employee and dependents)",
1986 0,
1987 );
1988 doc.add_bullet_list_item("401(k) retirement plan with 6% company match", 0);
1989 doc.add_bullet_list_item("Life insurance and long-term disability coverage", 0);
1990 doc.add_bullet_list_item("Annual professional development allowance of $10,000", 0);
1991
1992 {
1993 let mut p = doc.add_paragraph("");
1994 p.add_run("3.2 Paid Time Off. ").bold(true);
1995 p.add_run(
1996 "The Employee shall receive 25 days of paid vacation per year, plus Company holidays, \
1997 accruing on a monthly basis.",
1998 );
1999 }
2000
2001 // ── Article 4: Term and Termination ──
2002 doc.add_paragraph("Article 4 — Term and Termination")
2003 .style("Heading1");
2004 {
2005 let mut p = doc.add_paragraph("");
2006 p.add_run("4.1 At-Will Employment. ").bold(true);
2007 p.add_run(
2008 "This Agreement is for at-will employment and may be terminated by either Party \
2009 at any time, with or without cause, subject to the notice provisions herein.",
2010 );
2011 }
2012 {
2013 let mut p = doc.add_paragraph("");
2014 p.add_run("4.2 Notice Period. ").bold(true);
2015 p.add_run("Either Party shall provide ");
2016 p.add_run("ninety (90) days").bold(true);
2017 p.add_run(" written notice of termination, or pay in lieu thereof.");
2018 }
2019 {
2020 let mut p = doc.add_paragraph("");
2021 p.add_run("4.3 Severance. ").bold(true);
2022 p.add_run("In the event of termination by the Company without Cause, the Employee shall \
2023 receive (a) twelve (12) months of base salary continuation, (b) pro-rata bonus \
2024 for the year of termination, and (c) twelve (12) months of COBRA premium coverage.");
2025 }
2026
2027 // ── Article 5: Confidentiality ──
2028 doc.add_paragraph("Article 5 — Confidentiality and IP")
2029 .style("Heading1");
2030 {
2031 let mut p = doc.add_paragraph("");
2032 p.add_run("5.1 ").bold(true);
2033 p.add_run(
2034 "The Employee agrees to maintain strict confidentiality of all proprietary \
2035 information, trade secrets, and business plans of the Company during and after \
2036 employment.",
2037 );
2038 }
2039 {
2040 let mut p = doc.add_paragraph("");
2041 p.add_run("5.2 ").bold(true);
2042 p.add_run(
2043 "All intellectual property, inventions, and works of authorship created by the \
2044 Employee during the term of employment and within the scope of duties shall be \
2045 the sole and exclusive property of the Company.",
2046 );
2047 }
2048
2049 // ── Article 6: Non-Compete ──
2050 doc.add_paragraph("Article 6 — Non-Competition")
2051 .style("Heading1");
2052 {
2053 let mut p = doc.add_paragraph("");
2054 p.add_run("6.1 ").bold(true);
2055 p.add_run("For a period of twelve (12) months following termination, the Employee agrees \
2056 not to engage in any business that directly competes with the Company's core \
2057 business of AI infrastructure and ML operations services, within the United States.");
2058 }
2059
2060 // ── Article 7: General Provisions ──
2061 doc.add_paragraph("Article 7 — General Provisions")
2062 .style("Heading1");
2063 {
2064 let mut p = doc.add_paragraph("");
2065 p.add_run("7.1 Governing Law. ").bold(true);
2066 p.add_run(
2067 "This Agreement shall be governed by and construed in accordance with the laws of \
2068 the State of California.",
2069 );
2070 }
2071 {
2072 let mut p = doc.add_paragraph("");
2073 p.add_run("7.2 Entire Agreement. ").bold(true);
2074 p.add_run(
2075 "This Agreement constitutes the entire agreement between the Parties and supersedes \
2076 all prior negotiations, representations, and agreements.",
2077 );
2078 }
2079 {
2080 let mut p = doc.add_paragraph("");
2081 p.add_run("7.3 Amendment. ").bold(true);
2082 p.add_run(
2083 "This Agreement may only be amended by a written instrument signed by both Parties.",
2084 );
2085 }
2086
2087 // ── Signature Block ──
2088 doc.add_paragraph("").page_break_before(true);
2089 doc.add_paragraph("IN WITNESS WHEREOF, the Parties have executed this Employment Agreement as of the Effective Date.")
2090 .space_after(Length::pt(24.0));
2091
2092 // Signature table
2093 {
2094 let mut tbl = doc.add_table(6, 2).width_pct(100.0);
2095 tbl.cell(0, 0).unwrap().set_text("FOR THE COMPANY:");
2096 tbl.cell(0, 0).unwrap().shading("5B2C6F");
2097 tbl.cell(0, 1).unwrap().set_text("EMPLOYEE:");
2098 tbl.cell(0, 1).unwrap().shading("5B2C6F");
2099
2100 tbl.cell(1, 0).unwrap().set_text("");
2101 tbl.cell(1, 1).unwrap().set_text("");
2102 tbl.row(1).unwrap().height(Length::pt(40.0));
2103
2104 tbl.cell(2, 0)
2105 .unwrap()
2106 .set_text("Signature: ___________________________");
2107 tbl.cell(2, 1)
2108 .unwrap()
2109 .set_text("Signature: ___________________________");
2110 tbl.cell(3, 0)
2111 .unwrap()
2112 .set_text("Name: Board Representative");
2113 tbl.cell(3, 1).unwrap().set_text("Name: Walter White");
2114 tbl.cell(4, 0)
2115 .unwrap()
2116 .set_text("Title: Chair, Board of Directors");
2117 tbl.cell(4, 1)
2118 .unwrap()
2119 .set_text("Title: Chief Executive Officer");
2120 tbl.cell(5, 0).unwrap().set_text("Date: _______________");
2121 tbl.cell(5, 1).unwrap().set_text("Date: _______________");
2122 }
2123
2124 doc
2125}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
34fn generate_feature_showcase(path: &Path) {
35 let mut doc = Document::new();
36
37 // =========================================================================
38 // PAGE SETUP & METADATA
39 // =========================================================================
40 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
41 doc.set_margins(
42 Length::inches(1.0), // top
43 Length::inches(1.0), // right
44 Length::inches(1.0), // bottom
45 Length::inches(1.0), // left
46 );
47 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
48 doc.set_gutter(Length::twips(0));
49
50 doc.set_title("rdocx Feature Showcase");
51 doc.set_author("rdocx Sample Generator");
52 doc.set_subject("Comprehensive feature demonstration");
53 doc.set_keywords("rdocx, docx, rust, sample");
54
55 // Header & Footer
56 doc.set_header("rdocx Feature Showcase");
57 doc.set_footer("Generated by rdocx — Page");
58
59 // Different first page header
60 doc.set_different_first_page(true);
61 doc.set_first_page_header("rdocx");
62 doc.set_first_page_footer("Feature Showcase — Cover Page");
63
64 // =========================================================================
65 // PAGE 1: COVER PAGE — background image, run formatting
66 // =========================================================================
67 let bg_cover = create_sample_png(612, 792, [30, 60, 120]);
68 doc.add_background_image(&bg_cover, "cover_bg.png");
69
70 doc.add_paragraph(""); // spacer
71 doc.add_paragraph(""); // spacer
72 doc.add_paragraph(""); // spacer
73
74 {
75 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
76 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
77 }
78 {
79 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
80 p.add_run("Feature Showcase")
81 .size(28.0)
82 .color("FFFFFF")
83 .italic(true);
84 }
85
86 doc.add_paragraph(""); // spacer
87
88 {
89 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
90 p.add_run("A comprehensive demonstration of every feature")
91 .size(14.0)
92 .color("CCDDFF");
93 }
94 {
95 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
96 p.add_run("provided by the rdocx Rust crate for DOCX generation.")
97 .size(14.0)
98 .color("CCDDFF");
99 }
100
101 // =========================================================================
102 // PAGE 2: TEXT FORMATTING
103 // =========================================================================
104 doc.add_paragraph("").page_break_before(true);
105
106 doc.add_paragraph("1. Text Formatting").style("Heading1");
107
108 doc.add_paragraph("This section demonstrates paragraph and run-level formatting options.");
109 doc.add_paragraph("");
110
111 // --- Paragraph alignment ---
112 doc.add_paragraph("Paragraph Alignment").style("Heading2");
113
114 doc.add_paragraph("This paragraph is left-aligned (the default).")
115 .alignment(Alignment::Left);
116 doc.add_paragraph("This paragraph is center-aligned.")
117 .alignment(Alignment::Center);
118 doc.add_paragraph("This paragraph is right-aligned.")
119 .alignment(Alignment::Right);
120 doc.add_paragraph(
121 "This paragraph is justified. To demonstrate justified text properly, it needs \
122 to be long enough to span multiple lines so the word spacing adjustment is visible \
123 across the full width of the text area on the page.",
124 )
125 .alignment(Alignment::Justify);
126
127 doc.add_paragraph("");
128
129 // --- Run formatting ---
130 doc.add_paragraph("Run Formatting").style("Heading2");
131
132 {
133 let mut p = doc.add_paragraph("");
134 p.add_run("Bold text").bold(true);
135 p.add_run(" | ");
136 p.add_run("Italic text").italic(true);
137 p.add_run(" | ");
138 p.add_run("Bold + Italic").bold(true).italic(true);
139 }
140 {
141 let mut p = doc.add_paragraph("");
142 p.add_run("Single underline").underline(true);
143 p.add_run(" | ");
144 p.add_run("Strikethrough").strike(true);
145 p.add_run(" | ");
146 p.add_run("Double strikethrough").double_strike(true);
147 }
148 {
149 let mut p = doc.add_paragraph("");
150 p.add_run("Red text").color("FF0000");
151 p.add_run(" | ");
152 p.add_run("Blue text").color("0000FF");
153 p.add_run(" | ");
154 p.add_run("Green text").color("00AA00");
155 p.add_run(" | ");
156 p.add_run("Highlighted").highlight("FFFF00");
157 }
158 {
159 let mut p = doc.add_paragraph("");
160 p.add_run("8pt small").size(8.0);
161 p.add_run(" | ");
162 p.add_run("11pt normal").size(11.0);
163 p.add_run(" | ");
164 p.add_run("16pt large").size(16.0);
165 p.add_run(" | ");
166 p.add_run("24pt extra-large").size(24.0);
167 }
168 {
169 let mut p = doc.add_paragraph("");
170 p.add_run("Arial font").font("Arial");
171 p.add_run(" | ");
172 p.add_run("Times New Roman font").font("Times New Roman");
173 p.add_run(" | ");
174 p.add_run("Courier New font").font("Courier New");
175 }
176 {
177 let mut p = doc.add_paragraph("");
178 p.add_run("Normal");
179 p.add_run(" H").size(11.0);
180 p.add_run("2").subscript();
181 p.add_run("O (subscript)").size(11.0);
182 p.add_run(" | E = mc").size(11.0);
183 p.add_run("2").superscript();
184 p.add_run(" (superscript)").size(11.0);
185 }
186 {
187 let mut p = doc.add_paragraph("");
188 p.add_run("ALL CAPS").all_caps(true);
189 p.add_run(" | ");
190 p.add_run("Small Caps").small_caps(true);
191 p.add_run(" | ");
192 p.add_run("Expanded spacing")
193 .character_spacing(Length::twips(40));
194 }
195
196 doc.add_paragraph("");
197
198 // --- Paragraph formatting ---
199 doc.add_paragraph("Paragraph Formatting").style("Heading2");
200
201 doc.add_paragraph("Paragraph with shading (light green background)")
202 .shading("E2EFDA");
203
204 doc.add_paragraph("Paragraph with bottom border")
205 .border_bottom(BorderStyle::Single, 6, "2E75B6");
206
207 doc.add_paragraph("Paragraph with all borders")
208 .border_all(BorderStyle::Single, 4, "FF0000");
209
210 doc.add_paragraph("Paragraph with 1-inch left indent and hanging indent")
211 .indent_left(Length::inches(1.0))
212 .hanging_indent(Length::inches(0.5));
213
214 doc.add_paragraph("Paragraph with first-line indent of 0.5 inches")
215 .first_line_indent(Length::inches(0.5));
216
217 doc.add_paragraph("Paragraph with extra space before (24pt) and after (12pt)")
218 .space_before(Length::pt(24.0))
219 .space_after(Length::pt(12.0));
220
221 doc.add_paragraph(
222 "Paragraph with double line spacing. This text should have extra vertical \
223 space between lines to demonstrate the line_spacing_multiple setting.",
224 )
225 .line_spacing_multiple(2.0);
226
227 doc.add_paragraph("Paragraph with keep-with-next (won't break from the next paragraph)")
228 .keep_with_next(true);
229 doc.add_paragraph("(This stays with the paragraph above.)");
230
231 // =========================================================================
232 // PAGE 3: LISTS & TAB STOPS
233 // =========================================================================
234 doc.add_paragraph("").page_break_before(true);
235
236 doc.add_paragraph("2. Lists").style("Heading1");
237
238 doc.add_paragraph("Bullet List").style("Heading2");
239
240 doc.add_bullet_list_item("First bullet item", 0);
241 doc.add_bullet_list_item("Second bullet item", 0);
242 doc.add_bullet_list_item("Nested level 1", 1);
243 doc.add_bullet_list_item("Nested level 2", 2);
244 doc.add_bullet_list_item("Back to level 1", 1);
245 doc.add_bullet_list_item("Third bullet item", 0);
246
247 doc.add_paragraph("");
248
249 doc.add_paragraph("Numbered List").style("Heading2");
250
251 doc.add_numbered_list_item("First numbered item", 0);
252 doc.add_numbered_list_item("Second numbered item", 0);
253 doc.add_numbered_list_item("Sub-item A", 1);
254 doc.add_numbered_list_item("Sub-item B", 1);
255 doc.add_numbered_list_item("Third numbered item", 0);
256
257 doc.add_paragraph("");
258
259 // --- Tab stops ---
260 doc.add_paragraph("Tab Stops").style("Heading2");
261
262 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
263 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
264 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
265 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
266 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
267
268 doc.add_paragraph("Item\t........\tPrice")
269 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
270 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
271 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
272
273 doc.add_paragraph("Widget A\t........\t$19.99")
274 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
275 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
276 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
277
278 doc.add_paragraph("Gadget B\t________\t$249.50")
279 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
280 .add_tab_stop_with_leader(
281 TabAlignment::Right,
282 Length::inches(4.0),
283 TabLeader::Underscore,
284 )
285 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
286
287 // =========================================================================
288 // PAGE 4: TABLES
289 // =========================================================================
290 doc.add_paragraph("").page_break_before(true);
291
292 doc.add_paragraph("3. Tables").style("Heading1");
293
294 // --- Basic table with borders ---
295 doc.add_paragraph("Basic Table with Borders")
296 .style("Heading2");
297
298 {
299 let mut tbl = doc.add_table(4, 3);
300 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
301
302 // Header row
303 for col in 0..3 {
304 tbl.cell(0, col).unwrap().shading("2E75B6");
305 }
306 tbl.cell(0, 0).unwrap().set_text("Name");
307 tbl.cell(0, 1).unwrap().set_text("Role");
308 tbl.cell(0, 2).unwrap().set_text("Location");
309
310 tbl.cell(1, 0).unwrap().set_text("Alice Johnson");
311 tbl.cell(1, 1).unwrap().set_text("Engineering Lead");
312 tbl.cell(1, 2).unwrap().set_text("New York");
313
314 tbl.cell(2, 0).unwrap().set_text("Bob Smith");
315 tbl.cell(2, 1).unwrap().set_text("Product Manager");
316 tbl.cell(2, 2).unwrap().set_text("San Francisco");
317
318 tbl.cell(3, 0).unwrap().set_text("Carol Davis");
319 tbl.cell(3, 1).unwrap().set_text("Designer");
320 tbl.cell(3, 2).unwrap().set_text("London");
321 }
322
323 doc.add_paragraph("");
324
325 // --- Table with cell merging ---
326 doc.add_paragraph("Table with Cell Merging & Vertical Alignment")
327 .style("Heading2");
328
329 {
330 let mut tbl = doc.add_table(4, 4);
331 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
332 tbl = tbl.width_pct(100.0);
333
334 // Header spanning all columns
335 tbl.cell(0, 0).unwrap().set_text("Quarterly Revenue Report");
336 tbl.cell(0, 0).unwrap().shading("1F4E79");
337 tbl.cell(0, 0).unwrap().grid_span(4);
338
339 // Sub-header
340 tbl.cell(1, 0).unwrap().set_text("Region");
341 tbl.cell(1, 0).unwrap().shading("D6E4F0");
342 tbl.cell(1, 1).unwrap().set_text("Q1");
343 tbl.cell(1, 1).unwrap().shading("D6E4F0");
344 tbl.cell(1, 2).unwrap().set_text("Q2");
345 tbl.cell(1, 2).unwrap().shading("D6E4F0");
346 tbl.cell(1, 3).unwrap().set_text("Total");
347 tbl.cell(1, 3).unwrap().shading("D6E4F0");
348
349 // Data
350 tbl.cell(2, 0).unwrap().set_text("North America");
351 tbl.cell(2, 1).unwrap().set_text("$2.4M");
352 tbl.cell(2, 2).unwrap().set_text("$2.7M");
353 tbl.cell(2, 3).unwrap().set_text("$5.1M");
354
355 tbl.cell(3, 0).unwrap().set_text("Europe");
356 tbl.cell(3, 1).unwrap().set_text("$1.8M");
357 tbl.cell(3, 2).unwrap().set_text("$2.0M");
358 tbl.cell(3, 3).unwrap().set_text("$3.8M");
359
360 // Vertical alignment on data cells
361 tbl.cell(2, 3)
362 .unwrap()
363 .vertical_alignment(VerticalAlignment::Center);
364 tbl.cell(3, 3)
365 .unwrap()
366 .vertical_alignment(VerticalAlignment::Bottom);
367 }
368
369 doc.add_paragraph("");
370
371 // --- Table with vertical merge ---
372 doc.add_paragraph("Table with Vertical Merge")
373 .style("Heading2");
374
375 {
376 let mut tbl = doc.add_table(4, 3);
377 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
378
379 tbl.cell(0, 0).unwrap().set_text("Category");
380 tbl.cell(0, 0).unwrap().shading("E2EFDA");
381 tbl.cell(0, 1).unwrap().set_text("Item");
382 tbl.cell(0, 1).unwrap().shading("E2EFDA");
383 tbl.cell(0, 2).unwrap().set_text("Price");
384 tbl.cell(0, 2).unwrap().shading("E2EFDA");
385
386 // "Hardware" spans rows 1-2
387 tbl.cell(1, 0).unwrap().set_text("Hardware");
388 tbl.cell(1, 0).unwrap().v_merge_restart();
389 tbl.cell(1, 1).unwrap().set_text("Laptop");
390 tbl.cell(1, 2).unwrap().set_text("$1,200");
391
392 tbl.cell(2, 0).unwrap().v_merge_continue();
393 tbl.cell(2, 1).unwrap().set_text("Monitor");
394 tbl.cell(2, 2).unwrap().set_text("$450");
395
396 // "Software" on row 3
397 tbl.cell(3, 0).unwrap().set_text("Software");
398 tbl.cell(3, 1).unwrap().set_text("IDE License");
399 tbl.cell(3, 2).unwrap().set_text("$200/yr");
400 }
401
402 doc.add_paragraph("");
403
404 // --- Nested table ---
405 doc.add_paragraph("Nested Table").style("Heading2");
406
407 {
408 let mut tbl = doc.add_table(2, 2);
409 tbl = tbl.borders(BorderStyle::Single, 6, "2E75B6");
410
411 tbl.cell(0, 0).unwrap().set_text("Outer Cell (0,0)");
412 tbl.cell(0, 1).unwrap().set_text("Outer Cell (0,1)");
413 tbl.cell(1, 0).unwrap().set_text("Outer Cell (1,0)");
414
415 // Nested table inside cell (1,1)
416 {
417 let mut cell = tbl.cell(1, 1).unwrap();
418 cell.set_text("Contains nested table:");
419 let mut nested = cell.add_table(2, 2);
420 nested = nested.borders(BorderStyle::Single, 2, "FF6600");
421 nested.cell(0, 0).unwrap().set_text("Inner A");
422 nested.cell(0, 1).unwrap().set_text("Inner B");
423 nested.cell(1, 0).unwrap().set_text("Inner C");
424 nested.cell(1, 1).unwrap().set_text("Inner D");
425 }
426 }
427
428 // =========================================================================
429 // PAGE 5: IMAGES
430 // =========================================================================
431 doc.add_paragraph("").page_break_before(true);
432
433 doc.add_paragraph("4. Images").style("Heading1");
434
435 doc.add_paragraph("Inline Image").style("Heading2");
436
437 doc.add_paragraph("Below is an inline image (200x50 pixels, blue gradient):");
438 let inline_img = create_sample_png(200, 50, [0, 80, 200]);
439 doc.add_picture(
440 &inline_img,
441 "inline_chart.png",
442 Length::inches(3.0),
443 Length::inches(0.75),
444 );
445
446 doc.add_paragraph("");
447
448 doc.add_paragraph("Header Image").style("Heading2");
449
450 // Replace the text-only header with an image header
451 let header_img = create_sample_png(400, 40, [40, 40, 40]);
452 doc.set_header_image(
453 &header_img,
454 "header_logo.png",
455 Length::inches(2.0),
456 Length::inches(0.2),
457 );
458
459 doc.add_paragraph(
460 "The document header has been replaced with an inline image. \
461 Check the header area at the top of this page.",
462 );
463
464 doc.add_paragraph("");
465 doc.add_paragraph(
466 "Note: The cover page uses a full-page background image behind the text, \
467 demonstrated on page 1 via add_background_image().",
468 );
469
470 // =========================================================================
471 // PAGE 6: CONTENT MANIPULATION — placeholder replacement, insertion
472 // =========================================================================
473 doc.add_paragraph("").page_break_before(true);
474
475 doc.add_paragraph("5. Content Manipulation")
476 .style("Heading1");
477
478 // --- Placeholder replacement ---
479 doc.add_paragraph("Placeholder Replacement")
480 .style("Heading2");
481
482 doc.add_paragraph(
483 "Before replacement, this document contained {{customer}} and {{date}} placeholders.",
484 );
485
486 {
487 let mut p = doc.add_paragraph("");
488 p.add_run("Customer: ").bold(true);
489 p.add_run("{{customer}}");
490 }
491 {
492 let mut p = doc.add_paragraph("");
493 p.add_run("Date: ").bold(true);
494 p.add_run("{{date}}");
495 }
496 doc.add_paragraph("Reference: {{ref_number}}");
497
498 // Table with placeholders
499 {
500 let mut tbl = doc.add_table(3, 2);
501 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
502 tbl.cell(0, 0).unwrap().set_text("Field");
503 tbl.cell(0, 0).unwrap().shading("D6E4F0");
504 tbl.cell(0, 1).unwrap().set_text("Value");
505 tbl.cell(0, 1).unwrap().shading("D6E4F0");
506 tbl.cell(1, 0).unwrap().set_text("Project");
507 tbl.cell(1, 1).unwrap().set_text("{{project}}");
508 tbl.cell(2, 0).unwrap().set_text("Status");
509 tbl.cell(2, 1).unwrap().set_text("{{status}}");
510 }
511
512 // Perform replacements
513 let mut replacements = HashMap::new();
514 replacements.insert("{{customer}}", "Acme Corporation");
515 replacements.insert("{{date}}", "February 22, 2026");
516 replacements.insert("{{ref_number}}", "REF-2026-001");
517 replacements.insert("{{project}}", "Infrastructure Upgrade");
518 replacements.insert("{{status}}", "In Progress");
519 let replace_count = doc.replace_all(&replacements);
520
521 doc.add_paragraph("");
522 doc.add_paragraph(&format!(
523 "(Replaced {} placeholders above — in body text and table cells)",
524 replace_count
525 ));
526
527 doc.add_paragraph("");
528
529 // --- Content insertion ---
530 doc.add_paragraph("Content Insertion").style("Heading2");
531
532 doc.add_paragraph("Section A: First section of content.");
533 doc.add_paragraph("Section C: Third section of content.");
534
535 // Insert "Section B" between A and C
536 if let Some(idx) = doc.find_content_index("Section C") {
537 doc.insert_paragraph(
538 idx,
539 "Section B: Inserted between A and C using find_content_index().",
540 );
541 }
542
543 doc.add_paragraph("");
544 doc.add_paragraph(
545 "The paragraph above ('Section B') was inserted at a specific position \
546 using find_content_index() + insert_paragraph().",
547 );
548
549 // =========================================================================
550 // PAGE 7: LANDSCAPE — section break, wide table
551 // =========================================================================
552 doc.add_paragraph("").section_break(SectionBreak::NextPage);
553
554 doc.add_paragraph("6. Mixed Page Orientation")
555 .style("Heading1");
556
557 doc.add_paragraph(
558 "This page is in LANDSCAPE orientation. It was created using a section break \
559 followed by section_landscape(). This is useful for wide tables or charts.",
560 );
561
562 doc.add_paragraph("");
563
564 // Wide table for landscape
565 {
566 let mut tbl = doc.add_table(4, 7);
567 tbl = tbl.borders(BorderStyle::Single, 4, "2E75B6");
568
569 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
570 for (col, h) in headers.iter().enumerate() {
571 tbl.cell(0, col).unwrap().set_text(h);
572 tbl.cell(0, col).unwrap().shading("2E75B6");
573 }
574
575 let data = [
576 [
577 "North America",
578 "$1.2M",
579 "$1.3M",
580 "$1.4M",
581 "$1.5M",
582 "$1.6M",
583 "$7.0M",
584 ],
585 [
586 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
587 ],
588 [
589 "Asia Pacific",
590 "$0.5M",
591 "$0.6M",
592 "$0.7M",
593 "$0.7M",
594 "$0.8M",
595 "$3.3M",
596 ],
597 ];
598 for (row_idx, row_data) in data.iter().enumerate() {
599 for (col, val) in row_data.iter().enumerate() {
600 tbl.cell(row_idx + 1, col).unwrap().set_text(val);
601 }
602 }
603 }
604
605 // End landscape, return to portrait
606 doc.add_paragraph("")
607 .section_break(SectionBreak::NextPage)
608 .section_landscape();
609
610 // =========================================================================
611 // PAGE 8: BACK TO PORTRAIT — styles, final notes
612 // =========================================================================
613 doc.add_paragraph("7. Custom Styles & Summary")
614 .style("Heading1");
615
616 doc.add_paragraph(
617 "This final page is back in portrait orientation after a section break. \
618 The document has demonstrated:",
619 );
620
621 doc.add_bullet_list_item(
622 "Page setup: size, margins, header/footer distance, gutter",
623 0,
624 );
625 doc.add_bullet_list_item("Document metadata: title, author, subject, keywords", 0);
626 doc.add_bullet_list_item("Headers and footers: text, images, different first page", 0);
627 doc.add_bullet_list_item("Background images: full-page behind text", 0);
628 doc.add_bullet_list_item(
629 "Text formatting: bold, italic, underline, strike, color, size, font",
630 0,
631 );
632 doc.add_bullet_list_item(
633 "Advanced run formatting: superscript, subscript, caps, spacing",
634 0,
635 );
636 doc.add_bullet_list_item(
637 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
638 0,
639 );
640 doc.add_bullet_list_item("Bullet and numbered lists with nesting levels", 0);
641 doc.add_bullet_list_item("Tab stops with dot/underscore leaders", 0);
642 doc.add_bullet_list_item(
643 "Tables: borders, shading, column spans, row spans, nesting",
644 0,
645 );
646 doc.add_bullet_list_item("Vertical alignment in table cells", 0);
647 doc.add_bullet_list_item("Inline images", 0);
648 doc.add_bullet_list_item("Placeholder replacement in body and table cells", 0);
649 doc.add_bullet_list_item("Content insertion at specific positions", 0);
650 doc.add_bullet_list_item(
651 "Section breaks with mixed portrait/landscape orientation",
652 0,
653 );
654
655 doc.add_paragraph("");
656 doc.add_paragraph("All features above were built entirely from scratch using the rdocx API.")
657 .alignment(Alignment::Center)
658 .shading("E2EFDA")
659 .border_all(BorderStyle::Single, 2, "00AA00");
660
661 doc.save(path).unwrap();
662}86fn generate_feature_showcase(_samples_dir: &Path) -> Document {
87 let mut doc = Document::new();
88
89 // ── Page Setup & Metadata ──
90 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
91 doc.set_margins(
92 Length::inches(1.0),
93 Length::inches(1.0),
94 Length::inches(1.0),
95 Length::inches(1.0),
96 );
97 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
98 doc.set_gutter(Length::twips(0));
99 doc.set_title("rdocx Feature Showcase");
100 doc.set_author("rdocx Sample Generator");
101 doc.set_subject("Comprehensive feature demonstration");
102 doc.set_keywords("rdocx, docx, rust, sample, showcase");
103
104 // Headers & Footers with different first page
105 doc.set_different_first_page(true);
106 doc.set_first_page_header("rdocx");
107 doc.set_first_page_footer("Feature Showcase — Cover Page");
108 doc.set_header("rdocx Feature Showcase");
109 doc.set_footer("Generated by rdocx");
110
111 // ── COVER PAGE ──
112 let bg = create_sample_png(612, 792, [20, 45, 90]);
113 doc.add_background_image(&bg, "cover_bg.png");
114
115 for _ in 0..3 {
116 doc.add_paragraph("");
117 }
118 {
119 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
120 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
121 }
122 {
123 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
124 p.add_run("Complete Feature Showcase")
125 .size(28.0)
126 .color("FFFFFF")
127 .italic(true);
128 }
129 doc.add_paragraph("");
130 {
131 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
132 p.add_run("Every feature of the rdocx Rust library")
133 .size(13.0)
134 .color("B0C4DE");
135 }
136 {
137 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
138 p.add_run("demonstrated in a single document.")
139 .size(13.0)
140 .color("B0C4DE");
141 }
142
143 // ── TABLE OF CONTENTS ──
144 doc.add_paragraph("").page_break_before(true);
145 doc.insert_toc(doc.content_count(), 3);
146
147 // ── SECTION 1: TEXT FORMATTING ──
148 doc.add_paragraph("").page_break_before(true);
149 doc.add_paragraph("1. Text Formatting").style("Heading1");
150
151 // Paragraph Alignment
152 doc.add_paragraph("1.1 Paragraph Alignment")
153 .style("Heading2");
154 doc.add_paragraph("Left-aligned paragraph (default).")
155 .alignment(Alignment::Left);
156 doc.add_paragraph("Center-aligned paragraph.")
157 .alignment(Alignment::Center);
158 doc.add_paragraph("Right-aligned paragraph.")
159 .alignment(Alignment::Right);
160 doc.add_paragraph(
161 "Justified paragraph — this text is long enough to demonstrate how justified alignment \
162 distributes extra space across word gaps so lines fill the full width of the text area.",
163 )
164 .alignment(Alignment::Justify);
165
166 doc.add_paragraph("");
167
168 // Run Formatting
169 doc.add_paragraph("1.2 Run Formatting").style("Heading2");
170 {
171 let mut p = doc.add_paragraph("");
172 p.add_run("Bold").bold(true);
173 p.add_run(" | ");
174 p.add_run("Italic").italic(true);
175 p.add_run(" | ");
176 p.add_run("Bold Italic").bold(true).italic(true);
177 p.add_run(" | ");
178 p.add_run("Underline").underline(true);
179 p.add_run(" | ");
180 p.add_run("Strikethrough").strike(true);
181 p.add_run(" | ");
182 p.add_run("Double Strike").double_strike(true);
183 }
184 {
185 let mut p = doc.add_paragraph("");
186 p.add_run("Red").color("FF0000");
187 p.add_run(" | ");
188 p.add_run("Blue").color("0000FF");
189 p.add_run(" | ");
190 p.add_run("Green").color("00AA00");
191 p.add_run(" | ");
192 p.add_run("Highlighted").highlight("FFFF00");
193 }
194 {
195 let mut p = doc.add_paragraph("");
196 p.add_run("8pt").size(8.0);
197 p.add_run(" | ");
198 p.add_run("11pt").size(11.0);
199 p.add_run(" | ");
200 p.add_run("16pt").size(16.0);
201 p.add_run(" | ");
202 p.add_run("24pt").size(24.0);
203 }
204 {
205 let mut p = doc.add_paragraph("");
206 p.add_run("Arial").font("Arial");
207 p.add_run(" | ");
208 p.add_run("Times New Roman").font("Times New Roman");
209 p.add_run(" | ");
210 p.add_run("Courier New").font("Courier New");
211 }
212 {
213 let mut p = doc.add_paragraph("");
214 p.add_run("H");
215 p.add_run("2").subscript();
216 p.add_run("O (subscript) | E=mc");
217 p.add_run("2").superscript();
218 p.add_run(" (superscript)");
219 }
220 {
221 let mut p = doc.add_paragraph("");
222 p.add_run("ALL CAPS").all_caps(true);
223 p.add_run(" | ");
224 p.add_run("Small Caps").small_caps(true);
225 p.add_run(" | ");
226 p.add_run("Expanded").character_spacing(Length::twips(40));
227 p.add_run(" | ");
228 p.add_run("Hidden text (not visible)").hidden(true);
229 }
230
231 doc.add_paragraph("");
232
233 // Underline Styles
234 doc.add_paragraph("1.3 Underline Styles").style("Heading2");
235 {
236 let mut p = doc.add_paragraph("");
237 p.add_run("Single ")
238 .underline_style(rdocx::UnderlineStyle::Single);
239 p.add_run("Double ")
240 .underline_style(rdocx::UnderlineStyle::Double);
241 p.add_run("Thick ")
242 .underline_style(rdocx::UnderlineStyle::Thick);
243 p.add_run("Dotted ")
244 .underline_style(rdocx::UnderlineStyle::Dotted);
245 p.add_run("Dash ")
246 .underline_style(rdocx::UnderlineStyle::Dash);
247 p.add_run("Wave ")
248 .underline_style(rdocx::UnderlineStyle::Wave);
249 }
250
251 // ── SECTION 2: PARAGRAPH FORMATTING ──
252 doc.add_paragraph("").page_break_before(true);
253 doc.add_paragraph("2. Paragraph Formatting")
254 .style("Heading1");
255
256 doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
257 doc.add_paragraph("Paragraph with light green shading")
258 .shading("E2EFDA");
259 doc.add_paragraph("Paragraph with bottom border")
260 .border_bottom(BorderStyle::Single, 6, "2E75B6");
261 doc.add_paragraph("Paragraph with all borders (red)")
262 .border_all(BorderStyle::Single, 4, "FF0000");
263
264 doc.add_paragraph("2.2 Indentation").style("Heading2");
265 doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
266 .indent_left(Length::inches(1.0))
267 .hanging_indent(Length::inches(0.5));
268 doc.add_paragraph("0.5-inch first-line indent on this paragraph")
269 .first_line_indent(Length::inches(0.5));
270 doc.add_paragraph("Both left and right indent (0.75 inches each)")
271 .indent_left(Length::inches(0.75))
272 .indent_right(Length::inches(0.75));
273
274 doc.add_paragraph("2.3 Spacing & Line Height")
275 .style("Heading2");
276 doc.add_paragraph("Extra space before (24pt) and after (12pt)")
277 .space_before(Length::pt(24.0))
278 .space_after(Length::pt(12.0));
279 doc.add_paragraph(
280 "Double line spacing paragraph. This text should have extra vertical space between \
281 lines to demonstrate line_spacing_multiple(2.0).",
282 )
283 .line_spacing_multiple(2.0);
284 doc.add_paragraph("Exact 20pt line spacing (fixed height).")
285 .line_spacing(20.0);
286
287 doc.add_paragraph("2.4 Pagination Controls")
288 .style("Heading2");
289 doc.add_paragraph("keep_with_next — this paragraph stays with the next")
290 .keep_with_next(true);
291 doc.add_paragraph("(This paragraph was kept with the one above.)");
292 doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
293 .keep_together(true);
294 doc.add_paragraph("widow_control — prevents widow/orphan lines")
295 .widow_control(true);
296
297 // ── SECTION 3: LISTS ──
298 doc.add_paragraph("").page_break_before(true);
299 doc.add_paragraph("3. Lists").style("Heading1");
300
301 doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
302 doc.add_bullet_list_item("First item", 0);
303 doc.add_bullet_list_item("Second item", 0);
304 doc.add_bullet_list_item("Nested level 1", 1);
305 doc.add_bullet_list_item("Nested level 2", 2);
306 doc.add_bullet_list_item("Back to level 1", 1);
307 doc.add_bullet_list_item("Third item", 0);
308
309 doc.add_paragraph("");
310
311 doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
312 doc.add_numbered_list_item("First numbered item", 0);
313 doc.add_numbered_list_item("Second numbered item", 0);
314 doc.add_numbered_list_item("Sub-item A", 1);
315 doc.add_numbered_list_item("Sub-item B", 1);
316 doc.add_numbered_list_item("Third numbered item", 0);
317
318 // ── SECTION 4: TAB STOPS ──
319 doc.add_paragraph("");
320 doc.add_paragraph("4. Tab Stops").style("Heading1");
321
322 doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
323 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
324 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
325 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
326 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
327 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
328
329 doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
330 doc.add_paragraph("Item\t\tPrice")
331 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
332 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
333 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
334 doc.add_paragraph("Widget\t\t$19.99")
335 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
336 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
337 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
338 doc.add_paragraph("Gadget\t\t$249.50")
339 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
340 .add_tab_stop_with_leader(
341 TabAlignment::Right,
342 Length::inches(4.0),
343 TabLeader::Underscore,
344 )
345 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
346
347 // ── SECTION 5: TABLES ──
348 doc.add_paragraph("").page_break_before(true);
349 doc.add_paragraph("5. Tables").style("Heading1");
350
351 doc.add_paragraph("5.1 Basic Table").style("Heading2");
352 {
353 let mut tbl = doc
354 .add_table(4, 3)
355 .borders(BorderStyle::Single, 4, "000000");
356 for col in 0..3 {
357 tbl.cell(0, col).unwrap().shading("2E75B6");
358 }
359 tbl.cell(0, 0).unwrap().set_text("Name");
360 tbl.cell(0, 1).unwrap().set_text("Role");
361 tbl.cell(0, 2).unwrap().set_text("Location");
362 tbl.cell(1, 0).unwrap().set_text("Walter White");
363 tbl.cell(1, 1).unwrap().set_text("CEO");
364 tbl.cell(1, 2).unwrap().set_text("Albuquerque");
365 tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
366 tbl.cell(2, 1).unwrap().set_text("CTO");
367 tbl.cell(2, 2).unwrap().set_text("Remote");
368 tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
369 tbl.cell(3, 1).unwrap().set_text("Security");
370 tbl.cell(3, 2).unwrap().set_text("Washington");
371 }
372
373 doc.add_paragraph("");
374
375 doc.add_paragraph("5.2 Column Span & Cell Shading")
376 .style("Heading2");
377 {
378 let mut tbl = doc
379 .add_table(3, 4)
380 .borders(BorderStyle::Single, 4, "000000")
381 .width_pct(100.0);
382 tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
383 tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
384 tbl.cell(1, 0).unwrap().set_text("Region");
385 tbl.cell(1, 0).unwrap().shading("D6E4F0");
386 tbl.cell(1, 1).unwrap().set_text("Q1");
387 tbl.cell(1, 1).unwrap().shading("D6E4F0");
388 tbl.cell(1, 2).unwrap().set_text("Q2");
389 tbl.cell(1, 2).unwrap().shading("D6E4F0");
390 tbl.cell(1, 3).unwrap().set_text("Total");
391 tbl.cell(1, 3).unwrap().shading("D6E4F0");
392 tbl.cell(2, 0).unwrap().set_text("Americas");
393 tbl.cell(2, 1).unwrap().set_text("$2.4M");
394 tbl.cell(2, 2).unwrap().set_text("$2.7M");
395 tbl.cell(2, 3).unwrap().set_text("$5.1M");
396 }
397
398 doc.add_paragraph("");
399
400 doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
401 {
402 let mut tbl = doc
403 .add_table(4, 3)
404 .borders(BorderStyle::Single, 4, "000000");
405 tbl.cell(0, 0).unwrap().set_text("Category");
406 tbl.cell(0, 0).unwrap().shading("E2EFDA");
407 tbl.cell(0, 1).unwrap().set_text("Item");
408 tbl.cell(0, 1).unwrap().shading("E2EFDA");
409 tbl.cell(0, 2).unwrap().set_text("Price");
410 tbl.cell(0, 2).unwrap().shading("E2EFDA");
411 tbl.cell(1, 0).unwrap().set_text("Hardware");
412 tbl.cell(1, 0).unwrap().v_merge_restart();
413 tbl.cell(1, 1).unwrap().set_text("Laptop");
414 tbl.cell(1, 2).unwrap().set_text("$1,200");
415 tbl.cell(2, 0).unwrap().v_merge_continue();
416 tbl.cell(2, 1).unwrap().set_text("Monitor");
417 tbl.cell(2, 2).unwrap().set_text("$450");
418 tbl.cell(3, 0).unwrap().set_text("Software");
419 tbl.cell(3, 1).unwrap().set_text("IDE License");
420 tbl.cell(3, 2).unwrap().set_text("$200/yr");
421 }
422
423 doc.add_paragraph("");
424
425 doc.add_paragraph("5.4 Nested Table").style("Heading2");
426 {
427 let mut tbl = doc
428 .add_table(2, 2)
429 .borders(BorderStyle::Single, 6, "2E75B6");
430 tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
431 tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
432 tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
433 {
434 let mut cell = tbl.cell(1, 1).unwrap();
435 cell.set_text("Nested table below:");
436 let mut nested = cell
437 .add_table(2, 2)
438 .borders(BorderStyle::Single, 2, "FF6600");
439 nested.cell(0, 0).unwrap().set_text("A");
440 nested.cell(0, 1).unwrap().set_text("B");
441 nested.cell(1, 0).unwrap().set_text("C");
442 nested.cell(1, 1).unwrap().set_text("D");
443 }
444 }
445
446 doc.add_paragraph("");
447
448 doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
449 .style("Heading2");
450 {
451 let mut tbl = doc
452 .add_table(2, 3)
453 .borders(BorderStyle::Single, 4, "666666");
454 tbl.row(0).unwrap().height(Length::pt(50.0)).header();
455 tbl.cell(0, 0).unwrap().set_text("Top");
456 tbl.cell(0, 0)
457 .unwrap()
458 .vertical_alignment(VerticalAlignment::Top)
459 .shading("FFF2CC");
460 tbl.cell(0, 1).unwrap().set_text("Center");
461 tbl.cell(0, 1)
462 .unwrap()
463 .vertical_alignment(VerticalAlignment::Center)
464 .shading("D9E2F3");
465 tbl.cell(0, 2).unwrap().set_text("Bottom");
466 tbl.cell(0, 2)
467 .unwrap()
468 .vertical_alignment(VerticalAlignment::Bottom)
469 .shading("E2EFDA");
470 tbl.row(1).unwrap().cant_split();
471 tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
472 tbl.cell(1, 0).unwrap().no_wrap();
473 tbl.cell(1, 1).unwrap().set_text("Fixed width");
474 tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
475 tbl.cell(1, 2).unwrap().set_text("Normal");
476 }
477
478 // ── SECTION 6: IMAGES ──
479 doc.add_paragraph("").page_break_before(true);
480 doc.add_paragraph("6. Images").style("Heading1");
481
482 doc.add_paragraph("6.1 Inline Image").style("Heading2");
483 doc.add_paragraph("A blue gradient image below:");
484 let img = create_sample_png(200, 50, [0, 80, 200]);
485 doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
486
487 doc.add_paragraph("");
488 doc.add_paragraph("6.2 Header Image").style("Heading2");
489 let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
490 doc.set_header_image(
491 &hdr_img,
492 "header_logo.png",
493 Length::inches(2.0),
494 Length::inches(0.2),
495 );
496 doc.add_paragraph("The header now contains an inline image (check the top of this page).");
497
498 doc.add_paragraph("");
499 doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
500
501 // ── SECTION 7: CONTENT MANIPULATION ──
502 doc.add_paragraph("").page_break_before(true);
503 doc.add_paragraph("7. Content Manipulation")
504 .style("Heading1");
505
506 doc.add_paragraph("7.1 Placeholder Replacement")
507 .style("Heading2");
508 doc.add_paragraph("Customer: {{customer}}");
509 doc.add_paragraph("Date: {{date}}");
510 doc.add_paragraph("Reference: {{ref_number}}");
511 {
512 let mut tbl = doc
513 .add_table(2, 2)
514 .borders(BorderStyle::Single, 4, "000000");
515 tbl.cell(0, 0).unwrap().set_text("Project");
516 tbl.cell(0, 0).unwrap().shading("D6E4F0");
517 tbl.cell(0, 1).unwrap().set_text("{{project}}");
518 tbl.cell(1, 0).unwrap().set_text("Status");
519 tbl.cell(1, 0).unwrap().shading("D6E4F0");
520 tbl.cell(1, 1).unwrap().set_text("{{status}}");
521 }
522
523 let mut replacements = HashMap::new();
524 replacements.insert("{{customer}}", "Tensorbee Inc.");
525 replacements.insert("{{date}}", "February 22, 2026");
526 replacements.insert("{{ref_number}}", "TB-2026-001");
527 replacements.insert("{{project}}", "Infrastructure Upgrade");
528 replacements.insert("{{status}}", "In Progress");
529 let n = doc.replace_all(&replacements);
530 doc.add_paragraph(&format!("({n} placeholders replaced above)"));
531
532 doc.add_paragraph("");
533
534 doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
535 doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
536 let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
537 doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
538
539 doc.add_paragraph("");
540
541 doc.add_paragraph("7.3 Content Insertion").style("Heading2");
542 doc.add_paragraph("Section A: First content.");
543 doc.add_paragraph("Section C: Third content.");
544 if let Some(idx) = doc.find_content_index("Section C") {
545 doc.insert_paragraph(
546 idx,
547 "Section B: Inserted between A and C via find_content_index().",
548 );
549 }
550
551 // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
552 doc.add_paragraph("").section_break(SectionBreak::NextPage);
553 doc.add_paragraph("8. Section Breaks & Orientation")
554 .style("Heading1");
555 doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
556
557 {
558 let mut tbl = doc
559 .add_table(3, 7)
560 .borders(BorderStyle::Single, 4, "2E75B6");
561 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
562 for (c, h) in headers.iter().enumerate() {
563 tbl.cell(0, c).unwrap().set_text(h);
564 tbl.cell(0, c).unwrap().shading("2E75B6");
565 }
566 let data = [
567 [
568 "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
569 ],
570 [
571 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
572 ],
573 ];
574 for (r, row) in data.iter().enumerate() {
575 for (c, v) in row.iter().enumerate() {
576 tbl.cell(r + 1, c).unwrap().set_text(v);
577 }
578 }
579 }
580
581 doc.add_paragraph("")
582 .section_break(SectionBreak::NextPage)
583 .section_landscape();
584
585 // ── SECTION 9: CUSTOM STYLES ──
586 doc.add_paragraph("9. Custom Styles").style("Heading1");
587 doc.add_style(
588 StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
589 .based_on("Normal")
590 .paragraph_properties({
591 let mut ppr = rdocx_oxml::properties::CT_PPr::default();
592 ppr.shading = Some(rdocx_oxml::properties::CT_Shd {
593 val: "clear".to_string(),
594 color: None,
595 fill: Some("FFF2CC".to_string()),
596 });
597 ppr
598 })
599 .run_properties({
600 let mut rpr = rdocx_oxml::properties::CT_RPr::default();
601 rpr.bold = Some(true);
602 rpr.color = Some("C45911".to_string());
603 rpr
604 }),
605 );
606 doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
607 .style("CustomHighlight");
608
609 doc.add_paragraph("");
610
611 // ── SECTION 10: DOCUMENT INTELLIGENCE ──
612 doc.add_paragraph("10. Document Intelligence API")
613 .style("Heading1");
614
615 let wc = doc.word_count();
616 let hc = doc.headings().len();
617 let ic = doc.images().len();
618 let lc = doc.links().len();
619 doc.add_paragraph(&format!("Word count: {wc}"));
620 doc.add_paragraph(&format!("Heading count: {hc}"));
621 doc.add_paragraph(&format!("Image count: {ic}"));
622 doc.add_paragraph(&format!("Link count: {lc}"));
623
624 let outline = doc.document_outline();
625 doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
626
627 let issues = doc.audit_accessibility();
628 doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
629 for issue in &issues {
630 doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
631 }
632
633 // ── SECTION 11: DOCUMENT MERGING ──
634 doc.add_paragraph("");
635 doc.add_paragraph("11. Document Merging").style("Heading1");
636 {
637 let mut other = Document::new();
638 other.add_paragraph("This paragraph was merged from another document using append().");
639 doc.append(&other);
640 }
641
642 doc.add_paragraph("");
643
644 // ── SUMMARY ──
645 doc.add_paragraph("Summary of Demonstrated Features")
646 .style("Heading1");
647 let features = [
648 "Page setup: size, margins, header/footer distance, gutter",
649 "Document metadata: title, author, subject, keywords",
650 "Headers and footers: text, images, different first page",
651 "Background images (full-page behind text)",
652 "Table of Contents generation with bookmarks",
653 "Text formatting: bold, italic, underline styles, strike, color, size, font",
654 "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
655 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
656 "Pagination controls: keep-with-next, keep-together, widow control",
657 "Bullet and numbered lists with nesting",
658 "Tab stops with dot/underscore/hyphen leaders",
659 "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
660 "Row properties: header rows, exact height, cant-split",
661 "Cell properties: width, no-wrap, vertical alignment",
662 "Inline images and header images",
663 "Placeholder replacement (single and batch)",
664 "Regex-based find and replace",
665 "Content insertion at specific positions",
666 "Section breaks with mixed portrait/landscape",
667 "Custom paragraph and character styles",
668 "Document intelligence: word count, headings, outline, images, links",
669 "Accessibility audit",
670 "Document merging (append)",
671 "Export: DOCX, PDF, HTML, Markdown",
672 ];
673 for f in &features {
674 doc.add_bullet_list_item(f, 0);
675 }
676 doc.add_paragraph("");
677 doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
678 .alignment(Alignment::Center)
679 .shading("E2EFDA")
680 .border_all(BorderStyle::Single, 2, "00AA00");
681
682 doc
683}
684
685// =============================================================================
686// 2. PROPOSAL DOCUMENT — Deep navy + gold scheme
687// =============================================================================
688fn generate_proposal(_samples_dir: &Path) -> Document {
689 let mut doc = Document::new();
690
691 // Colors: Navy #1B2A4A, Gold #C5922E, Light #F4F1EB
692 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
693 doc.set_margins(
694 Length::inches(1.0),
695 Length::inches(1.0),
696 Length::inches(1.0),
697 Length::inches(1.0),
698 );
699 doc.set_title("AI Platform Modernization Proposal");
700 doc.set_author("Walter White");
701 doc.set_subject("Technology Proposal");
702 doc.set_keywords("proposal, AI, modernization, Tensorbee");
703
704 // Banner header
705 let logo_img = create_logo_png(220, 48);
706 let banner = build_header_banner_xml(
707 "rId1",
708 &BannerOpts {
709 bg_color: "1B2A4A",
710 banner_width: 7772400,
711 banner_height: 914400,
712 logo_width: 2011680,
713 logo_height: 438912,
714 logo_x_offset: 295125,
715 logo_y_offset: 237744,
716 },
717 );
718 doc.set_raw_header_with_images(
719 banner,
720 &[("rId1", &logo_img, "logo.png")],
721 rdocx_oxml::header_footer::HdrFtrType::Default,
722 );
723 doc.set_different_first_page(true);
724 let cover_banner = build_header_banner_xml(
725 "rId1",
726 &BannerOpts {
727 bg_color: "C5922E",
728 banner_width: 7772400,
729 banner_height: 914400,
730 logo_width: 2011680,
731 logo_height: 438912,
732 logo_x_offset: 295125,
733 logo_y_offset: 237744,
734 },
735 );
736 doc.set_raw_header_with_images(
737 cover_banner,
738 &[("rId1", &logo_img, "logo.png")],
739 rdocx_oxml::header_footer::HdrFtrType::First,
740 );
741 doc.set_margins(
742 Length::twips(2292),
743 Length::twips(1440),
744 Length::twips(1440),
745 Length::twips(1440),
746 );
747 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
748 doc.set_footer("Tensorbee — Confidential");
749
750 // Custom styles
751 doc.add_style(
752 StyleBuilder::paragraph("ProposalTitle", "Proposal Title")
753 .based_on("Normal")
754 .run_properties({
755 let mut rpr = rdocx_oxml::properties::CT_RPr::default();
756 rpr.bold = Some(true);
757 rpr.font_ascii = Some("Georgia".to_string());
758 rpr.font_hansi = Some("Georgia".to_string());
759 rpr.sz = Some(rdocx_oxml::HalfPoint(56)); // half-points → 28pt
760 rpr.color = Some("1B2A4A".to_string());
761 rpr
762 }),
763 );
764
765 // ── Cover Page ──
766 for _ in 0..4 {
767 doc.add_paragraph("");
768 }
769 doc.add_paragraph("AI Platform Modernization")
770 .style("ProposalTitle")
771 .alignment(Alignment::Center);
772 doc.add_paragraph("")
773 .alignment(Alignment::Center)
774 .border_bottom(BorderStyle::Single, 8, "C5922E");
775 {
776 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
777 p.add_run("Prepared for Global Financial Corp.")
778 .size(14.0)
779 .color("666666")
780 .italic(true);
781 }
782 {
783 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
784 p.add_run("Prepared by: Walter White, CEO — Tensorbee")
785 .size(12.0)
786 .color("888888");
787 }
788 {
789 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
790 p.add_run("February 22, 2026").size(12.0).color("888888");
791 }
792
793 // ── TOC ──
794 doc.add_paragraph("").page_break_before(true);
795 doc.add_paragraph("Table of Contents").style("Heading1");
796 // We'll add a manual-style TOC since the insert_toc goes at a specific position
797 doc.insert_toc(doc.content_count(), 2);
798
799 // ── Executive Summary ──
800 doc.add_paragraph("").page_break_before(true);
801 doc.add_paragraph("Executive Summary").style("Heading1");
802 doc.add_paragraph(
803 "Tensorbee proposes a comprehensive modernization of Global Financial Corp's AI platform, \
804 transitioning from legacy batch-processing systems to a real-time inference architecture. \
805 This transformation will reduce model deployment time from weeks to hours, improve \
806 prediction accuracy by an estimated 23%, and deliver $4.2M in annual operational savings.",
807 )
808 .first_line_indent(Length::inches(0.3));
809 doc.add_paragraph(
810 "The project spans three phases over 18 months, with a total investment of $2.8M. \
811 Tensorbee brings deep expertise in ML infrastructure, having successfully completed \
812 similar transformations for three Fortune 500 financial institutions.",
813 )
814 .first_line_indent(Length::inches(0.3));
815
816 // Key metrics table
817 doc.add_paragraph("");
818 {
819 let mut tbl = doc
820 .add_table(5, 2)
821 .borders(BorderStyle::Single, 4, "1B2A4A")
822 .width_pct(80.0);
823 tbl.cell(0, 0).unwrap().set_text("Key Metric");
824 tbl.cell(0, 0).unwrap().shading("1B2A4A");
825 tbl.cell(0, 1).unwrap().set_text("Value");
826 tbl.cell(0, 1).unwrap().shading("1B2A4A");
827 let rows = [
828 ("Total Investment", "$2.8M"),
829 ("Annual Savings", "$4.2M"),
830 ("ROI (Year 1)", "150%"),
831 ("Timeline", "18 months"),
832 ];
833 for (i, (k, v)) in rows.iter().enumerate() {
834 tbl.cell(i + 1, 0).unwrap().set_text(k);
835 if i % 2 == 0 {
836 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
837 }
838 tbl.cell(i + 1, 1).unwrap().set_text(v);
839 if i % 2 == 0 {
840 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
841 }
842 }
843 }
844
845 // ── Problem Statement ──
846 doc.add_paragraph("").page_break_before(true);
847 doc.add_paragraph("Problem Statement").style("Heading1");
848 doc.add_paragraph(
849 "Global Financial Corp's current AI infrastructure faces three critical challenges:",
850 );
851 doc.add_numbered_list_item("Deployment Latency — New models take 3-4 weeks to deploy to production, compared to the industry benchmark of 2-3 days.", 0);
852 doc.add_numbered_list_item("Scalability Constraints — The batch processing architecture cannot handle real-time inference demands during peak trading hours.", 0);
853 doc.add_numbered_list_item("Technical Debt — Legacy Python 2.7 codebases and manual deployment scripts create reliability risks and slow iteration speed.", 0);
854
855 // ── Proposed Solution ──
856 doc.add_paragraph("").page_break_before(true);
857 doc.add_paragraph("Proposed Solution").style("Heading1");
858
859 doc.add_paragraph("Phase 1: Foundation (Months 1-6)")
860 .style("Heading2");
861 doc.add_bullet_list_item("Deploy Kubernetes-based ML serving infrastructure", 0);
862 doc.add_bullet_list_item("Implement CI/CD pipeline for model deployment", 0);
863 doc.add_bullet_list_item("Migrate top 5 production models to new platform", 0);
864
865 doc.add_paragraph("Phase 2: Expansion (Months 7-12)")
866 .style("Heading2");
867 doc.add_bullet_list_item(
868 "Real-time feature engineering pipeline (Apache Kafka + Flink)",
869 0,
870 );
871 doc.add_bullet_list_item("A/B testing framework for model variants", 0);
872 doc.add_bullet_list_item("Automated model monitoring and drift detection", 0);
873
874 doc.add_paragraph("Phase 3: Optimization (Months 13-18)")
875 .style("Heading2");
876 doc.add_bullet_list_item("GPU inference optimization (TensorRT/ONNX Runtime)", 0);
877 doc.add_bullet_list_item("Multi-region deployment for latency reduction", 0);
878 doc.add_bullet_list_item(
879 "Self-service model deployment portal for data science teams",
880 0,
881 );
882
883 // ── Budget ──
884 doc.add_paragraph("Budget Breakdown").style("Heading1");
885 {
886 let mut tbl = doc
887 .add_table(6, 3)
888 .borders(BorderStyle::Single, 4, "1B2A4A")
889 .width_pct(100.0);
890 tbl.cell(0, 0).unwrap().set_text("Category");
891 tbl.cell(0, 0).unwrap().shading("1B2A4A");
892 tbl.cell(0, 1).unwrap().set_text("Cost");
893 tbl.cell(0, 1).unwrap().shading("1B2A4A");
894 tbl.cell(0, 2).unwrap().set_text("Notes");
895 tbl.cell(0, 2).unwrap().shading("1B2A4A");
896 let items = [
897 (
898 "Infrastructure",
899 "$450,000",
900 "Cloud compute + GPU instances",
901 ),
902 ("Engineering Services", "$1,600,000", "12 FTE x 18 months"),
903 (
904 "Software Licenses",
905 "$350,000",
906 "Kafka, monitoring, CI/CD tools",
907 ),
908 (
909 "Training & Enablement",
910 "$200,000",
911 "Team training, documentation",
912 ),
913 ("Contingency (10%)", "$200,000", "Risk buffer"),
914 ];
915 for (i, (cat, cost, note)) in items.iter().enumerate() {
916 tbl.cell(i + 1, 0).unwrap().set_text(cat);
917 tbl.cell(i + 1, 1).unwrap().set_text(cost);
918 tbl.cell(i + 1, 2).unwrap().set_text(note);
919 if i % 2 == 0 {
920 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
921 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
922 tbl.cell(i + 1, 2).unwrap().shading("F4F1EB");
923 }
924 }
925 }
926
927 // ── Closing ──
928 doc.add_paragraph("");
929 doc.add_paragraph("Next Steps").style("Heading1");
930 doc.add_numbered_list_item(
931 "Schedule technical deep-dive with GFC engineering team (Week 1)",
932 0,
933 );
934 doc.add_numbered_list_item("Finalize scope and sign SOW (Week 2-3)", 0);
935 doc.add_numbered_list_item("Kick off Phase 1 with joint planning session (Week 4)", 0);
936
937 doc.add_paragraph("");
938 {
939 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
940 p.add_run("Walter White")
941 .bold(true)
942 .size(14.0)
943 .color("1B2A4A");
944 }
945 {
946 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
947 p.add_run("CEO, Tensorbee | walter@tensorbee.com")
948 .size(10.0)
949 .color("888888");
950 }
951
952 doc
953}
954
955// =============================================================================
956// 3. QUOTE / BILL OF MATERIALS — Teal + orange scheme
957// =============================================================================
958fn generate_quote(_samples_dir: &Path) -> Document {
959 let mut doc = Document::new();
960
961 // Colors: Teal #008B8B, Dark #1A3C3C, Orange #E07020, Light #F0F8F8
962 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
963 doc.set_margins(
964 Length::inches(0.75),
965 Length::inches(0.75),
966 Length::inches(0.75),
967 Length::inches(0.75),
968 );
969 doc.set_title("Quotation QT-2026-0147");
970 doc.set_author("Walter White");
971 doc.set_keywords("quote, BOM, Tensorbee");
972
973 doc.set_header("Tensorbee — Quotation");
974 doc.set_footer("QT-2026-0147 | Page");
975
976 // ── Header Block ──
977 {
978 let mut p = doc.add_paragraph("").alignment(Alignment::Left);
979 p.add_run("TENSORBEE")
980 .bold(true)
981 .size(28.0)
982 .color("008B8B")
983 .font("Helvetica");
984 }
985 doc.add_paragraph("123 Innovation Drive, Suite 400")
986 .alignment(Alignment::Left);
987 doc.add_paragraph("San Francisco, CA 94105 | +1 (415) 555-0199")
988 .alignment(Alignment::Left);
989 doc.add_paragraph("")
990 .border_bottom(BorderStyle::Single, 8, "008B8B");
991
992 // Quote meta
993 doc.add_paragraph("");
994 {
995 let mut tbl = doc.add_table(4, 4).width_pct(100.0);
996 tbl.cell(0, 0).unwrap().set_text("QUOTATION");
997 tbl.cell(0, 0).unwrap().shading("008B8B").grid_span(2);
998 tbl.cell(0, 2).unwrap().set_text("DATE");
999 tbl.cell(0, 2).unwrap().shading("008B8B");
1000 tbl.cell(0, 3).unwrap().set_text("VALID UNTIL");
1001 tbl.cell(0, 3).unwrap().shading("008B8B");
1002 tbl.cell(1, 0).unwrap().set_text("Quote #:");
1003 tbl.cell(1, 1).unwrap().set_text("QT-2026-0147");
1004 tbl.cell(1, 2).unwrap().set_text("Feb 22, 2026");
1005 tbl.cell(1, 3).unwrap().set_text("Mar 22, 2026");
1006 tbl.cell(2, 0).unwrap().set_text("Prepared by:");
1007 tbl.cell(2, 1).unwrap().set_text("Walter White");
1008 tbl.cell(2, 2).unwrap().set_text("Payment:");
1009 tbl.cell(2, 3).unwrap().set_text("Net 30");
1010 tbl.cell(3, 0).unwrap().set_text("Customer:");
1011 tbl.cell(3, 1).unwrap().set_text("Meridian Dynamics LLC");
1012 tbl.cell(3, 2).unwrap().set_text("Currency:");
1013 tbl.cell(3, 3).unwrap().set_text("USD");
1014 }
1015
1016 doc.add_paragraph("");
1017
1018 // ── Bill of Materials ──
1019 {
1020 let mut p = doc.add_paragraph("");
1021 p.add_run("Bill of Materials")
1022 .bold(true)
1023 .size(16.0)
1024 .color("1A3C3C");
1025 }
1026 doc.add_paragraph("");
1027
1028 {
1029 let mut tbl = doc
1030 .add_table(10, 6)
1031 .borders(BorderStyle::Single, 2, "008B8B")
1032 .width_pct(100.0)
1033 .layout_fixed();
1034 // Headers
1035 let hdrs = [
1036 "#",
1037 "Part Number",
1038 "Description",
1039 "Qty",
1040 "Unit Price",
1041 "Total",
1042 ];
1043 for (c, h) in hdrs.iter().enumerate() {
1044 tbl.cell(0, c).unwrap().set_text(h);
1045 tbl.cell(0, c).unwrap().shading("008B8B");
1046 }
1047
1048 let items: Vec<(&str, &str, &str, &str, &str)> = vec![
1049 (
1050 "TB-GPU-A100",
1051 "NVIDIA A100 80GB GPU",
1052 "4",
1053 "$12,500.00",
1054 "$50,000.00",
1055 ),
1056 (
1057 "TB-SRV-R750",
1058 "Dell R750xa Server Chassis",
1059 "2",
1060 "$8,200.00",
1061 "$16,400.00",
1062 ),
1063 (
1064 "TB-RAM-256G",
1065 "256GB DDR5 ECC Memory Module",
1066 "8",
1067 "$890.00",
1068 "$7,120.00",
1069 ),
1070 (
1071 "TB-SSD-3840",
1072 "3.84TB NVMe U.2 SSD",
1073 "8",
1074 "$1,150.00",
1075 "$9,200.00",
1076 ),
1077 (
1078 "TB-NET-CX7",
1079 "ConnectX-7 200GbE NIC",
1080 "4",
1081 "$1,800.00",
1082 "$7,200.00",
1083 ),
1084 (
1085 "TB-SW-48P",
1086 "48-Port 100GbE Switch",
1087 "1",
1088 "$22,000.00",
1089 "$22,000.00",
1090 ),
1091 (
1092 "TB-CAB-RACK",
1093 "42U Server Rack + PDU",
1094 "1",
1095 "$4,500.00",
1096 "$4,500.00",
1097 ),
1098 (
1099 "TB-SVC-INST",
1100 "Installation & Configuration",
1101 "1",
1102 "$8,500.00",
1103 "$8,500.00",
1104 ),
1105 (
1106 "TB-SVC-SUPP",
1107 "3-Year Premium Support",
1108 "1",
1109 "$15,000.00",
1110 "$15,000.00",
1111 ),
1112 ];
1113
1114 for (i, (pn, desc, qty, unit, total)) in items.iter().enumerate() {
1115 let row = i + 1;
1116 tbl.cell(row, 0).unwrap().set_text(&format!("{}", i + 1));
1117 tbl.cell(row, 1).unwrap().set_text(pn);
1118 tbl.cell(row, 2).unwrap().set_text(desc);
1119 tbl.cell(row, 3).unwrap().set_text(qty);
1120 tbl.cell(row, 4).unwrap().set_text(unit);
1121 tbl.cell(row, 5).unwrap().set_text(total);
1122 if i % 2 == 0 {
1123 for c in 0..6 {
1124 tbl.cell(row, c).unwrap().shading("F0F8F8");
1125 }
1126 }
1127 }
1128 }
1129
1130 doc.add_paragraph("");
1131
1132 // ── Totals ──
1133 {
1134 let mut tbl = doc
1135 .add_table(4, 2)
1136 .borders(BorderStyle::Single, 2, "008B8B")
1137 .width(Length::inches(3.5))
1138 .alignment(Alignment::Right);
1139 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1140 tbl.cell(0, 1).unwrap().set_text("$139,920.00");
1141 tbl.cell(1, 0).unwrap().set_text("Shipping & Handling");
1142 tbl.cell(1, 1).unwrap().set_text("$2,500.00");
1143 tbl.cell(2, 0).unwrap().set_text("Tax (8.625%)");
1144 tbl.cell(2, 1).unwrap().set_text("$12,068.10");
1145 tbl.cell(3, 0).unwrap().set_text("TOTAL");
1146 tbl.cell(3, 0).unwrap().shading("E07020");
1147 tbl.cell(3, 1).unwrap().set_text("$154,488.10");
1148 tbl.cell(3, 1).unwrap().shading("E07020");
1149 }
1150
1151 doc.add_paragraph("");
1152
1153 // ── Terms & Conditions ──
1154 {
1155 let mut p = doc.add_paragraph("");
1156 p.add_run("Terms & Conditions")
1157 .bold(true)
1158 .size(14.0)
1159 .color("1A3C3C");
1160 }
1161 doc.add_numbered_list_item(
1162 "This quotation is valid for 30 calendar days from the date of issue.",
1163 0,
1164 );
1165 doc.add_numbered_list_item(
1166 "All prices are in USD and exclusive of applicable taxes unless stated.",
1167 0,
1168 );
1169 doc.add_numbered_list_item("Standard lead time is 4-6 weeks from PO receipt.", 0);
1170 doc.add_numbered_list_item(
1171 "Payment terms: Net 30 from invoice date. 2% discount for payment within 10 days.",
1172 0,
1173 );
1174 doc.add_numbered_list_item(
1175 "Warranty: 3-year manufacturer warranty on all hardware components.",
1176 0,
1177 );
1178 doc.add_numbered_list_item(
1179 "Returns subject to 15% restocking fee if initiated after 14 days.",
1180 0,
1181 );
1182
1183 doc.add_paragraph("");
1184 doc.add_paragraph("")
1185 .border_bottom(BorderStyle::Single, 4, "008B8B");
1186 doc.add_paragraph("");
1187 {
1188 let mut p = doc.add_paragraph("");
1189 p.add_run("Accepted by: ").bold(true);
1190 p.add_run("___________________________ Date: ___________");
1191 }
1192 {
1193 let mut p = doc.add_paragraph("");
1194 p.add_run("Print Name: ").bold(true);
1195 p.add_run("___________________________ Title: ___________");
1196 }
1197
1198 doc
1199}
1200
1201// =============================================================================
1202// 4. INVOICE — Crimson + charcoal scheme
1203// =============================================================================
1204fn generate_invoice(_samples_dir: &Path) -> Document {
1205 let mut doc = Document::new();
1206
1207 // Colors: Crimson #B22222, Charcoal #333333, Light #FAF0F0
1208 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1209 doc.set_margins(
1210 Length::inches(0.75),
1211 Length::inches(0.75),
1212 Length::inches(0.75),
1213 Length::inches(0.75),
1214 );
1215 doc.set_title("Invoice INV-2026-0392");
1216 doc.set_author("Walter White");
1217 doc.set_keywords("invoice, Tensorbee");
1218
1219 doc.set_footer("Tensorbee — Thank you for your business!");
1220
1221 // ── Company & Invoice Header ──
1222 {
1223 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1224 // Company name left, INVOICE right
1225 {
1226 let mut cell = tbl.cell(0, 0).unwrap();
1227 let mut p = cell.add_paragraph("");
1228 p.add_run("TENSORBEE")
1229 .bold(true)
1230 .size(32.0)
1231 .color("B22222")
1232 .font("Helvetica");
1233 }
1234 {
1235 let mut cell = tbl.cell(0, 1).unwrap();
1236 let mut p = cell.add_paragraph("");
1237 p.add_run("INVOICE").bold(true).size(32.0).color("333333");
1238 }
1239 tbl.cell(1, 0)
1240 .unwrap()
1241 .set_text("123 Innovation Drive, Suite 400");
1242 tbl.cell(1, 1).unwrap().set_text("Invoice #: INV-2026-0392");
1243 tbl.cell(2, 0).unwrap().set_text("San Francisco, CA 94105");
1244 tbl.cell(2, 1).unwrap().set_text("Date: February 22, 2026");
1245 tbl.cell(3, 0).unwrap().set_text("walter@tensorbee.com");
1246 tbl.cell(3, 1).unwrap().set_text("Due Date: March 24, 2026");
1247 }
1248
1249 doc.add_paragraph("")
1250 .border_bottom(BorderStyle::Thick, 8, "B22222");
1251 doc.add_paragraph("");
1252
1253 // ── Bill To / Ship To ──
1254 {
1255 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1256 tbl.cell(0, 0).unwrap().set_text("BILL TO");
1257 tbl.cell(0, 0).unwrap().shading("B22222");
1258 tbl.cell(0, 1).unwrap().set_text("SHIP TO");
1259 tbl.cell(0, 1).unwrap().shading("B22222");
1260 tbl.cell(1, 0).unwrap().set_text("Meridian Dynamics LLC");
1261 tbl.cell(1, 1).unwrap().set_text("Meridian Dynamics LLC");
1262 tbl.cell(2, 0).unwrap().set_text("456 Enterprise Blvd");
1263 tbl.cell(2, 1).unwrap().set_text("Attn: Server Room B");
1264 tbl.cell(3, 0).unwrap().set_text("Austin, TX 78701");
1265 tbl.cell(3, 1)
1266 .unwrap()
1267 .set_text("456 Enterprise Blvd, Austin, TX 78701");
1268 }
1269
1270 doc.add_paragraph("");
1271
1272 // ── Line Items ──
1273 {
1274 let mut tbl = doc
1275 .add_table(8, 5)
1276 .borders(BorderStyle::Single, 2, "B22222")
1277 .width_pct(100.0);
1278 let hdrs = ["Description", "Qty", "Unit Price", "Tax", "Amount"];
1279 for (c, h) in hdrs.iter().enumerate() {
1280 tbl.cell(0, c).unwrap().set_text(h);
1281 tbl.cell(0, c).unwrap().shading("B22222");
1282 }
1283 let items = [
1284 (
1285 "ML Infrastructure Setup — Phase 1",
1286 "1",
1287 "$45,000.00",
1288 "$3,881.25",
1289 "$48,881.25",
1290 ),
1291 (
1292 "Data Pipeline Development (160 hrs)",
1293 "160",
1294 "$225.00",
1295 "$3,105.00",
1296 "$39,105.00",
1297 ),
1298 (
1299 "GPU Cluster Configuration",
1300 "1",
1301 "$12,000.00",
1302 "$1,035.00",
1303 "$13,035.00",
1304 ),
1305 (
1306 "API Gateway Implementation",
1307 "1",
1308 "$18,500.00",
1309 "$1,595.63",
1310 "$20,095.63",
1311 ),
1312 (
1313 "Load Testing & QA (80 hrs)",
1314 "80",
1315 "$195.00",
1316 "$1,345.50",
1317 "$16,945.50",
1318 ),
1319 (
1320 "Documentation & Training",
1321 "1",
1322 "$8,000.00",
1323 "$690.00",
1324 "$8,690.00",
1325 ),
1326 (
1327 "Project Management (3 months)",
1328 "3",
1329 "$6,500.00",
1330 "$1,679.63",
1331 "$21,179.63",
1332 ),
1333 ];
1334 for (i, (desc, qty, unit, tax, amt)) in items.iter().enumerate() {
1335 let r = i + 1;
1336 tbl.cell(r, 0).unwrap().set_text(desc);
1337 tbl.cell(r, 1).unwrap().set_text(qty);
1338 tbl.cell(r, 2).unwrap().set_text(unit);
1339 tbl.cell(r, 3).unwrap().set_text(tax);
1340 tbl.cell(r, 4).unwrap().set_text(amt);
1341 if i % 2 == 0 {
1342 for c in 0..5 {
1343 tbl.cell(r, c).unwrap().shading("FAF0F0");
1344 }
1345 }
1346 }
1347 }
1348
1349 doc.add_paragraph("");
1350
1351 // ── Totals ──
1352 {
1353 let mut tbl = doc
1354 .add_table(4, 2)
1355 .borders(BorderStyle::Single, 2, "B22222")
1356 .width(Length::inches(3.0))
1357 .alignment(Alignment::Right);
1358 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1359 tbl.cell(0, 1).unwrap().set_text("$154,600.00");
1360 tbl.cell(1, 0).unwrap().set_text("Tax (8.625%)");
1361 tbl.cell(1, 1).unwrap().set_text("$13,334.25");
1362 tbl.cell(2, 0).unwrap().set_text("Discount (5%)");
1363 tbl.cell(2, 1).unwrap().set_text("-$7,730.00");
1364 tbl.cell(3, 0).unwrap().set_text("AMOUNT DUE");
1365 tbl.cell(3, 0).unwrap().shading("B22222");
1366 tbl.cell(3, 1).unwrap().set_text("$160,204.25");
1367 tbl.cell(3, 1).unwrap().shading("B22222");
1368 }
1369
1370 doc.add_paragraph("");
1371 doc.add_paragraph("");
1372
1373 // ── Payment Details ──
1374 {
1375 let mut p = doc.add_paragraph("");
1376 p.add_run("Payment Details")
1377 .bold(true)
1378 .size(14.0)
1379 .color("333333");
1380 }
1381 doc.add_paragraph("Bank: Silicon Valley Bank")
1382 .indent_left(Length::inches(0.3));
1383 doc.add_paragraph("Account: Tensorbee Inc. — 0847-2953-1120")
1384 .indent_left(Length::inches(0.3));
1385 doc.add_paragraph("Routing: 121140399")
1386 .indent_left(Length::inches(0.3));
1387 doc.add_paragraph("Swift: SVBKUS6S")
1388 .indent_left(Length::inches(0.3));
1389
1390 doc.add_paragraph("");
1391
1392 doc.add_paragraph("Please include invoice number INV-2026-0392 in the payment reference.")
1393 .shading("FAF0F0")
1394 .border_all(BorderStyle::Single, 2, "B22222");
1395
1396 doc
1397}
1398
1399// =============================================================================
1400// 5. REPORT — Forest green + earth tones, with images & hierarchical sections
1401// =============================================================================
1402fn generate_report(_samples_dir: &Path) -> Document {
1403 let mut doc = Document::new();
1404
1405 // Colors: Forest #2D5016, Sage #6B8E23, Earth #8B7355, Cream #FFFAF0
1406 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1407 doc.set_margins(
1408 Length::inches(1.0),
1409 Length::inches(1.0),
1410 Length::inches(1.0),
1411 Length::inches(1.0),
1412 );
1413 doc.set_title("Q4 2025 Environmental Impact Report");
1414 doc.set_author("Walter White");
1415 doc.set_subject("Quarterly Environmental Report");
1416 doc.set_keywords("environment, sustainability, report, Tensorbee");
1417
1418 doc.set_different_first_page(true);
1419 doc.set_first_page_header("");
1420 doc.set_header("Tensorbee — Q4 2025 Environmental Impact Report");
1421 doc.set_footer("CONFIDENTIAL — Page");
1422
1423 // ── Cover ──
1424 let cover_bg = create_sample_png(612, 792, [20, 50, 15]);
1425 doc.add_background_image(&cover_bg, "report_cover.png");
1426
1427 for _ in 0..5 {
1428 doc.add_paragraph("");
1429 }
1430 {
1431 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1432 p.add_run("Q4 2025")
1433 .bold(true)
1434 .size(48.0)
1435 .color("FFFFFF")
1436 .font("Georgia");
1437 }
1438 {
1439 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1440 p.add_run("Environmental Impact Report")
1441 .size(24.0)
1442 .color("90EE90")
1443 .font("Georgia")
1444 .italic(true);
1445 }
1446 doc.add_paragraph("");
1447 {
1448 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1449 p.add_run("Tensorbee — Sustainability Division")
1450 .size(14.0)
1451 .color("C0C0C0");
1452 }
1453 {
1454 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1455 p.add_run("Prepared by Walter White, Chief Sustainability Officer")
1456 .size(11.0)
1457 .color("AAAAAA");
1458 }
1459
1460 // ── TOC ──
1461 doc.add_paragraph("").page_break_before(true);
1462 doc.insert_toc(doc.content_count(), 3);
1463
1464 // ── Section 1: Executive Overview ──
1465 doc.add_paragraph("").page_break_before(true);
1466 doc.add_paragraph("Executive Overview").style("Heading1");
1467 doc.add_paragraph(
1468 "This report presents Tensorbee's environmental performance for Q4 2025. Our \
1469 sustainability initiatives have yielded a 34% reduction in carbon emissions compared \
1470 to Q4 2024, exceeding our target of 25%. Key achievements include the transition to \
1471 100% renewable energy in our primary data centers and the launch of our carbon offset \
1472 marketplace.",
1473 )
1474 .first_line_indent(Length::inches(0.3));
1475
1476 // Image: performance chart
1477 let chart_img = create_chart_png(400, 200);
1478 doc.add_paragraph("");
1479 doc.add_picture(
1480 &chart_img,
1481 "performance_chart.png",
1482 Length::inches(5.0),
1483 Length::inches(2.5),
1484 )
1485 .alignment(Alignment::Center);
1486 {
1487 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1488 p.add_run("Figure 1: Quarterly Carbon Emissions (tonnes CO2e)")
1489 .italic(true)
1490 .size(9.0)
1491 .color("666666");
1492 }
1493
1494 // ── Section 2: Energy Consumption ──
1495 doc.add_paragraph("").page_break_before(true);
1496 doc.add_paragraph("Energy Consumption").style("Heading1");
1497
1498 doc.add_paragraph("Data Center Operations")
1499 .style("Heading2");
1500 doc.add_paragraph(
1501 "Our three primary data centers consumed a combined 4.2 GWh in Q4 2025, \
1502 a 12% reduction from Q3 2025 driven by improved cooling efficiency and \
1503 server consolidation.",
1504 );
1505
1506 // Data table
1507 {
1508 let mut tbl = doc
1509 .add_table(5, 4)
1510 .borders(BorderStyle::Single, 4, "2D5016")
1511 .width_pct(100.0);
1512 let hdrs = ["Data Center", "Capacity (MW)", "Usage (GWh)", "PUE"];
1513 for (c, h) in hdrs.iter().enumerate() {
1514 tbl.cell(0, c).unwrap().set_text(h);
1515 tbl.cell(0, c).unwrap().shading("2D5016");
1516 }
1517 let rows = [
1518 ("San Francisco (Primary)", "3.2", "1.8", "1.12"),
1519 ("Dublin (EU)", "2.1", "1.2", "1.18"),
1520 ("Singapore (APAC)", "1.8", "1.2", "1.24"),
1521 ("TOTAL", "7.1", "4.2", "1.17 avg"),
1522 ];
1523 for (i, (dc, cap, use_, pue)) in rows.iter().enumerate() {
1524 tbl.cell(i + 1, 0).unwrap().set_text(dc);
1525 tbl.cell(i + 1, 1).unwrap().set_text(cap);
1526 tbl.cell(i + 1, 2).unwrap().set_text(use_);
1527 tbl.cell(i + 1, 3).unwrap().set_text(pue);
1528 if i == 3 {
1529 for c in 0..4 {
1530 tbl.cell(i + 1, c).unwrap().shading("E2EFDA");
1531 }
1532 }
1533 }
1534 }
1535
1536 doc.add_paragraph("");
1537
1538 doc.add_paragraph("Renewable Energy Mix").style("Heading2");
1539 doc.add_paragraph("Breakdown of energy sources across all facilities:");
1540
1541 doc.add_bullet_list_item("Solar PV: 42% (1.76 GWh)", 0);
1542 doc.add_bullet_list_item("Wind Power Purchase Agreements: 38% (1.60 GWh)", 0);
1543 doc.add_bullet_list_item("Hydroelectric: 12% (0.50 GWh)", 0);
1544 doc.add_bullet_list_item("Grid (non-renewable): 8% (0.34 GWh)", 0);
1545
1546 // ── Section 3: Water & Waste ──
1547 doc.add_paragraph("Water & Waste Management")
1548 .style("Heading1");
1549
1550 doc.add_paragraph("Water Usage").style("Heading2");
1551 doc.add_paragraph(
1552 "Total water consumption was 12.4 million gallons, a 15% reduction from Q3. \
1553 Our closed-loop cooling systems now recycle 78% of water used in cooling operations.",
1554 );
1555
1556 doc.add_paragraph("Waste Reduction").style("Heading2");
1557 doc.add_paragraph("E-waste management results for Q4:")
1558 .keep_with_next(true);
1559 {
1560 let mut tbl = doc
1561 .add_table(4, 3)
1562 .borders(BorderStyle::Single, 2, "6B8E23")
1563 .width_pct(80.0);
1564 tbl.cell(0, 0).unwrap().set_text("Category");
1565 tbl.cell(0, 0).unwrap().shading("6B8E23");
1566 tbl.cell(0, 1).unwrap().set_text("Weight (kg)");
1567 tbl.cell(0, 1).unwrap().shading("6B8E23");
1568 tbl.cell(0, 2).unwrap().set_text("Recycled %");
1569 tbl.cell(0, 2).unwrap().shading("6B8E23");
1570 let rows = [
1571 ("Server Hardware", "2,450", "94%"),
1572 ("Networking Equipment", "820", "91%"),
1573 ("Storage Media", "340", "99%"),
1574 ];
1575 for (i, (cat, wt, pct)) in rows.iter().enumerate() {
1576 tbl.cell(i + 1, 0).unwrap().set_text(cat);
1577 tbl.cell(i + 1, 1).unwrap().set_text(wt);
1578 tbl.cell(i + 1, 2).unwrap().set_text(pct);
1579 }
1580 }
1581
1582 // ── Section 4: Initiatives ──
1583 doc.add_paragraph("").page_break_before(true);
1584 doc.add_paragraph("Q1 2026 Initiatives").style("Heading1");
1585
1586 doc.add_paragraph("Planned Programs").style("Heading2");
1587 doc.add_numbered_list_item(
1588 "Deploy on-site battery storage at SF data center (2 MWh capacity)",
1589 0,
1590 );
1591 doc.add_numbered_list_item("Pilot immersion cooling in Dublin facility", 0);
1592 doc.add_numbered_list_item("Launch employee commute carbon offset program", 0);
1593 doc.add_numbered_list_item("Achieve ISO 14001 certification for Singapore facility", 0);
1594
1595 doc.add_paragraph("Investment Targets").style("Heading2");
1596 doc.add_paragraph("Sustainability CapEx allocation for FY2026:")
1597 .keep_with_next(true);
1598 {
1599 let mut tbl = doc
1600 .add_table(5, 2)
1601 .borders(BorderStyle::Single, 4, "2D5016");
1602 tbl.cell(0, 0).unwrap().set_text("Initiative");
1603 tbl.cell(0, 0).unwrap().shading("2D5016");
1604 tbl.cell(0, 1).unwrap().set_text("Budget");
1605 tbl.cell(0, 1).unwrap().shading("2D5016");
1606 let rows = [
1607 ("Battery Storage", "$1.2M"),
1608 ("Immersion Cooling Pilot", "$800K"),
1609 ("Solar Panel Expansion", "$2.1M"),
1610 ("Carbon Credits", "$500K"),
1611 ];
1612 for (i, (init, budget)) in rows.iter().enumerate() {
1613 tbl.cell(i + 1, 0).unwrap().set_text(init);
1614 tbl.cell(i + 1, 1).unwrap().set_text(budget);
1615 if i % 2 == 0 {
1616 tbl.cell(i + 1, 0).unwrap().shading("FFFAF0");
1617 tbl.cell(i + 1, 1).unwrap().shading("FFFAF0");
1618 }
1619 }
1620 }
1621
1622 // Image: sustainability roadmap
1623 let roadmap_img = create_sample_png(500, 100, [40, 80, 30]);
1624 doc.add_paragraph("");
1625 doc.add_picture(
1626 &roadmap_img,
1627 "roadmap.png",
1628 Length::inches(6.0),
1629 Length::inches(1.2),
1630 )
1631 .alignment(Alignment::Center);
1632 {
1633 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1634 p.add_run("Figure 2: 2026 Sustainability Roadmap")
1635 .italic(true)
1636 .size(9.0)
1637 .color("666666");
1638 }
1639
1640 doc.add_paragraph("");
1641 doc.add_paragraph(
1642 "For questions about this report, contact Walter White at sustainability@tensorbee.com.",
1643 )
1644 .shading("FFFAF0")
1645 .border_all(BorderStyle::Single, 2, "2D5016");
1646
1647 doc
1648}
1649
1650// =============================================================================
1651// 6. LETTER — Slate blue + silver scheme
1652// =============================================================================
1653fn generate_letter(_samples_dir: &Path) -> Document {
1654 let mut doc = Document::new();
1655
1656 // Colors: Slate #4A5568, Blue accent #3182CE
1657 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1658 doc.set_margins(
1659 Length::inches(1.25),
1660 Length::inches(1.25),
1661 Length::inches(1.25),
1662 Length::inches(1.25),
1663 );
1664 doc.set_title("Business Letter");
1665 doc.set_author("Walter White");
1666
1667 // Banner header using raw XML
1668 let logo_img = create_logo_png(220, 48);
1669 let banner = build_header_banner_xml(
1670 "rId1",
1671 &BannerOpts {
1672 bg_color: "4A5568",
1673 banner_width: 7772400,
1674 banner_height: 731520, // ~0.8"
1675 logo_width: 2011680,
1676 logo_height: 438912,
1677 logo_x_offset: 295125,
1678 logo_y_offset: 146304,
1679 },
1680 );
1681 doc.set_raw_header_with_images(
1682 banner,
1683 &[("rId1", &logo_img, "logo.png")],
1684 rdocx_oxml::header_footer::HdrFtrType::Default,
1685 );
1686 doc.set_margins(
1687 Length::twips(2000),
1688 Length::twips(1800),
1689 Length::twips(1440),
1690 Length::twips(1800),
1691 );
1692 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
1693
1694 // Footer with contact
1695 doc.set_footer(
1696 "Tensorbee | 123 Innovation Drive, Suite 400 | San Francisco, CA 94105 | tensorbee.com",
1697 );
1698
1699 // ── Sender Address ──
1700 doc.add_paragraph("Walter White");
1701 doc.add_paragraph("Chief Executive Officer");
1702 doc.add_paragraph("Tensorbee");
1703 doc.add_paragraph("123 Innovation Drive, Suite 400");
1704 doc.add_paragraph("San Francisco, CA 94105");
1705 doc.add_paragraph("");
1706 doc.add_paragraph("February 22, 2026");
1707
1708 doc.add_paragraph("");
1709
1710 // ── Recipient ──
1711 doc.add_paragraph("Ms. Sarah Chen");
1712 doc.add_paragraph("VP of Engineering");
1713 doc.add_paragraph("Meridian Dynamics LLC");
1714 doc.add_paragraph("456 Enterprise Boulevard");
1715 doc.add_paragraph("Austin, TX 78701");
1716
1717 doc.add_paragraph("");
1718
1719 // ── Subject ──
1720 {
1721 let mut p = doc.add_paragraph("");
1722 p.add_run("Re: Strategic Technology Partnership — Phase 2 Expansion")
1723 .bold(true);
1724 }
1725
1726 doc.add_paragraph("");
1727
1728 // ── Body ──
1729 doc.add_paragraph("Dear Ms. Chen,");
1730 doc.add_paragraph("");
1731
1732 doc.add_paragraph(
1733 "I am writing to express Tensorbee's enthusiasm for expanding our strategic technology \
1734 partnership with Meridian Dynamics. Following the successful completion of Phase 1, \
1735 which delivered a 40% improvement in your ML inference pipeline throughput, we believe \
1736 the foundation is firmly established for an ambitious Phase 2 engagement.",
1737 )
1738 .first_line_indent(Length::inches(0.5));
1739
1740 doc.add_paragraph(
1741 "During our recent executive review, your team highlighted three priority areas for \
1742 the next phase of collaboration:",
1743 )
1744 .first_line_indent(Length::inches(0.5));
1745
1746 doc.add_numbered_list_item(
1747 "Real-time anomaly detection for your financial transaction monitoring system, \
1748 targeting sub-100ms latency at 50,000 transactions per second.",
1749 0,
1750 );
1751 doc.add_numbered_list_item(
1752 "Federated learning infrastructure to enable model training across your distributed \
1753 data centers without centralizing sensitive financial data.",
1754 0,
1755 );
1756 doc.add_numbered_list_item(
1757 "MLOps automation to reduce your model deployment cycle from the current 5 days \
1758 to under 4 hours.",
1759 0,
1760 );
1761
1762 doc.add_paragraph(
1763 "Our engineering team has prepared a detailed technical proposal addressing each of \
1764 these areas. We have allocated a dedicated team of eight senior engineers, led by \
1765 our CTO, to ensure continuity with the Phase 1 team your organization has already \
1766 built a productive working relationship with.",
1767 )
1768 .first_line_indent(Length::inches(0.5));
1769
1770 doc.add_paragraph(
1771 "I would welcome the opportunity to present our Phase 2 proposal in person. My \
1772 assistant will reach out to coordinate a meeting at your convenience during the \
1773 first week of March.",
1774 )
1775 .first_line_indent(Length::inches(0.5));
1776
1777 doc.add_paragraph("");
1778
1779 doc.add_paragraph("Warm regards,");
1780 doc.add_paragraph("");
1781 doc.add_paragraph("");
1782
1783 // Signature line
1784 doc.add_paragraph("")
1785 .border_bottom(BorderStyle::Single, 4, "4A5568");
1786 {
1787 let mut p = doc.add_paragraph("");
1788 p.add_run("Walter White").bold(true).size(12.0);
1789 }
1790 {
1791 let mut p = doc.add_paragraph("");
1792 p.add_run("Chief Executive Officer, Tensorbee")
1793 .italic(true)
1794 .size(10.0)
1795 .color("666666");
1796 }
1797 {
1798 let mut p = doc.add_paragraph("");
1799 p.add_run("walter@tensorbee.com | +1 (415) 555-0199")
1800 .size(10.0)
1801 .color("888888");
1802 }
1803
1804 doc
1805}Sourcepub fn set_gutter(&mut self, gutter: Length)
pub fn set_gutter(&mut self, gutter: Length)
Set the gutter margin.
Examples found in repository?
34fn generate_feature_showcase(path: &Path) {
35 let mut doc = Document::new();
36
37 // =========================================================================
38 // PAGE SETUP & METADATA
39 // =========================================================================
40 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
41 doc.set_margins(
42 Length::inches(1.0), // top
43 Length::inches(1.0), // right
44 Length::inches(1.0), // bottom
45 Length::inches(1.0), // left
46 );
47 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
48 doc.set_gutter(Length::twips(0));
49
50 doc.set_title("rdocx Feature Showcase");
51 doc.set_author("rdocx Sample Generator");
52 doc.set_subject("Comprehensive feature demonstration");
53 doc.set_keywords("rdocx, docx, rust, sample");
54
55 // Header & Footer
56 doc.set_header("rdocx Feature Showcase");
57 doc.set_footer("Generated by rdocx — Page");
58
59 // Different first page header
60 doc.set_different_first_page(true);
61 doc.set_first_page_header("rdocx");
62 doc.set_first_page_footer("Feature Showcase — Cover Page");
63
64 // =========================================================================
65 // PAGE 1: COVER PAGE — background image, run formatting
66 // =========================================================================
67 let bg_cover = create_sample_png(612, 792, [30, 60, 120]);
68 doc.add_background_image(&bg_cover, "cover_bg.png");
69
70 doc.add_paragraph(""); // spacer
71 doc.add_paragraph(""); // spacer
72 doc.add_paragraph(""); // spacer
73
74 {
75 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
76 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
77 }
78 {
79 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
80 p.add_run("Feature Showcase")
81 .size(28.0)
82 .color("FFFFFF")
83 .italic(true);
84 }
85
86 doc.add_paragraph(""); // spacer
87
88 {
89 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
90 p.add_run("A comprehensive demonstration of every feature")
91 .size(14.0)
92 .color("CCDDFF");
93 }
94 {
95 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
96 p.add_run("provided by the rdocx Rust crate for DOCX generation.")
97 .size(14.0)
98 .color("CCDDFF");
99 }
100
101 // =========================================================================
102 // PAGE 2: TEXT FORMATTING
103 // =========================================================================
104 doc.add_paragraph("").page_break_before(true);
105
106 doc.add_paragraph("1. Text Formatting").style("Heading1");
107
108 doc.add_paragraph("This section demonstrates paragraph and run-level formatting options.");
109 doc.add_paragraph("");
110
111 // --- Paragraph alignment ---
112 doc.add_paragraph("Paragraph Alignment").style("Heading2");
113
114 doc.add_paragraph("This paragraph is left-aligned (the default).")
115 .alignment(Alignment::Left);
116 doc.add_paragraph("This paragraph is center-aligned.")
117 .alignment(Alignment::Center);
118 doc.add_paragraph("This paragraph is right-aligned.")
119 .alignment(Alignment::Right);
120 doc.add_paragraph(
121 "This paragraph is justified. To demonstrate justified text properly, it needs \
122 to be long enough to span multiple lines so the word spacing adjustment is visible \
123 across the full width of the text area on the page.",
124 )
125 .alignment(Alignment::Justify);
126
127 doc.add_paragraph("");
128
129 // --- Run formatting ---
130 doc.add_paragraph("Run Formatting").style("Heading2");
131
132 {
133 let mut p = doc.add_paragraph("");
134 p.add_run("Bold text").bold(true);
135 p.add_run(" | ");
136 p.add_run("Italic text").italic(true);
137 p.add_run(" | ");
138 p.add_run("Bold + Italic").bold(true).italic(true);
139 }
140 {
141 let mut p = doc.add_paragraph("");
142 p.add_run("Single underline").underline(true);
143 p.add_run(" | ");
144 p.add_run("Strikethrough").strike(true);
145 p.add_run(" | ");
146 p.add_run("Double strikethrough").double_strike(true);
147 }
148 {
149 let mut p = doc.add_paragraph("");
150 p.add_run("Red text").color("FF0000");
151 p.add_run(" | ");
152 p.add_run("Blue text").color("0000FF");
153 p.add_run(" | ");
154 p.add_run("Green text").color("00AA00");
155 p.add_run(" | ");
156 p.add_run("Highlighted").highlight("FFFF00");
157 }
158 {
159 let mut p = doc.add_paragraph("");
160 p.add_run("8pt small").size(8.0);
161 p.add_run(" | ");
162 p.add_run("11pt normal").size(11.0);
163 p.add_run(" | ");
164 p.add_run("16pt large").size(16.0);
165 p.add_run(" | ");
166 p.add_run("24pt extra-large").size(24.0);
167 }
168 {
169 let mut p = doc.add_paragraph("");
170 p.add_run("Arial font").font("Arial");
171 p.add_run(" | ");
172 p.add_run("Times New Roman font").font("Times New Roman");
173 p.add_run(" | ");
174 p.add_run("Courier New font").font("Courier New");
175 }
176 {
177 let mut p = doc.add_paragraph("");
178 p.add_run("Normal");
179 p.add_run(" H").size(11.0);
180 p.add_run("2").subscript();
181 p.add_run("O (subscript)").size(11.0);
182 p.add_run(" | E = mc").size(11.0);
183 p.add_run("2").superscript();
184 p.add_run(" (superscript)").size(11.0);
185 }
186 {
187 let mut p = doc.add_paragraph("");
188 p.add_run("ALL CAPS").all_caps(true);
189 p.add_run(" | ");
190 p.add_run("Small Caps").small_caps(true);
191 p.add_run(" | ");
192 p.add_run("Expanded spacing")
193 .character_spacing(Length::twips(40));
194 }
195
196 doc.add_paragraph("");
197
198 // --- Paragraph formatting ---
199 doc.add_paragraph("Paragraph Formatting").style("Heading2");
200
201 doc.add_paragraph("Paragraph with shading (light green background)")
202 .shading("E2EFDA");
203
204 doc.add_paragraph("Paragraph with bottom border")
205 .border_bottom(BorderStyle::Single, 6, "2E75B6");
206
207 doc.add_paragraph("Paragraph with all borders")
208 .border_all(BorderStyle::Single, 4, "FF0000");
209
210 doc.add_paragraph("Paragraph with 1-inch left indent and hanging indent")
211 .indent_left(Length::inches(1.0))
212 .hanging_indent(Length::inches(0.5));
213
214 doc.add_paragraph("Paragraph with first-line indent of 0.5 inches")
215 .first_line_indent(Length::inches(0.5));
216
217 doc.add_paragraph("Paragraph with extra space before (24pt) and after (12pt)")
218 .space_before(Length::pt(24.0))
219 .space_after(Length::pt(12.0));
220
221 doc.add_paragraph(
222 "Paragraph with double line spacing. This text should have extra vertical \
223 space between lines to demonstrate the line_spacing_multiple setting.",
224 )
225 .line_spacing_multiple(2.0);
226
227 doc.add_paragraph("Paragraph with keep-with-next (won't break from the next paragraph)")
228 .keep_with_next(true);
229 doc.add_paragraph("(This stays with the paragraph above.)");
230
231 // =========================================================================
232 // PAGE 3: LISTS & TAB STOPS
233 // =========================================================================
234 doc.add_paragraph("").page_break_before(true);
235
236 doc.add_paragraph("2. Lists").style("Heading1");
237
238 doc.add_paragraph("Bullet List").style("Heading2");
239
240 doc.add_bullet_list_item("First bullet item", 0);
241 doc.add_bullet_list_item("Second bullet item", 0);
242 doc.add_bullet_list_item("Nested level 1", 1);
243 doc.add_bullet_list_item("Nested level 2", 2);
244 doc.add_bullet_list_item("Back to level 1", 1);
245 doc.add_bullet_list_item("Third bullet item", 0);
246
247 doc.add_paragraph("");
248
249 doc.add_paragraph("Numbered List").style("Heading2");
250
251 doc.add_numbered_list_item("First numbered item", 0);
252 doc.add_numbered_list_item("Second numbered item", 0);
253 doc.add_numbered_list_item("Sub-item A", 1);
254 doc.add_numbered_list_item("Sub-item B", 1);
255 doc.add_numbered_list_item("Third numbered item", 0);
256
257 doc.add_paragraph("");
258
259 // --- Tab stops ---
260 doc.add_paragraph("Tab Stops").style("Heading2");
261
262 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
263 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
264 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
265 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
266 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
267
268 doc.add_paragraph("Item\t........\tPrice")
269 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
270 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
271 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
272
273 doc.add_paragraph("Widget A\t........\t$19.99")
274 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
275 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
276 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
277
278 doc.add_paragraph("Gadget B\t________\t$249.50")
279 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
280 .add_tab_stop_with_leader(
281 TabAlignment::Right,
282 Length::inches(4.0),
283 TabLeader::Underscore,
284 )
285 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
286
287 // =========================================================================
288 // PAGE 4: TABLES
289 // =========================================================================
290 doc.add_paragraph("").page_break_before(true);
291
292 doc.add_paragraph("3. Tables").style("Heading1");
293
294 // --- Basic table with borders ---
295 doc.add_paragraph("Basic Table with Borders")
296 .style("Heading2");
297
298 {
299 let mut tbl = doc.add_table(4, 3);
300 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
301
302 // Header row
303 for col in 0..3 {
304 tbl.cell(0, col).unwrap().shading("2E75B6");
305 }
306 tbl.cell(0, 0).unwrap().set_text("Name");
307 tbl.cell(0, 1).unwrap().set_text("Role");
308 tbl.cell(0, 2).unwrap().set_text("Location");
309
310 tbl.cell(1, 0).unwrap().set_text("Alice Johnson");
311 tbl.cell(1, 1).unwrap().set_text("Engineering Lead");
312 tbl.cell(1, 2).unwrap().set_text("New York");
313
314 tbl.cell(2, 0).unwrap().set_text("Bob Smith");
315 tbl.cell(2, 1).unwrap().set_text("Product Manager");
316 tbl.cell(2, 2).unwrap().set_text("San Francisco");
317
318 tbl.cell(3, 0).unwrap().set_text("Carol Davis");
319 tbl.cell(3, 1).unwrap().set_text("Designer");
320 tbl.cell(3, 2).unwrap().set_text("London");
321 }
322
323 doc.add_paragraph("");
324
325 // --- Table with cell merging ---
326 doc.add_paragraph("Table with Cell Merging & Vertical Alignment")
327 .style("Heading2");
328
329 {
330 let mut tbl = doc.add_table(4, 4);
331 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
332 tbl = tbl.width_pct(100.0);
333
334 // Header spanning all columns
335 tbl.cell(0, 0).unwrap().set_text("Quarterly Revenue Report");
336 tbl.cell(0, 0).unwrap().shading("1F4E79");
337 tbl.cell(0, 0).unwrap().grid_span(4);
338
339 // Sub-header
340 tbl.cell(1, 0).unwrap().set_text("Region");
341 tbl.cell(1, 0).unwrap().shading("D6E4F0");
342 tbl.cell(1, 1).unwrap().set_text("Q1");
343 tbl.cell(1, 1).unwrap().shading("D6E4F0");
344 tbl.cell(1, 2).unwrap().set_text("Q2");
345 tbl.cell(1, 2).unwrap().shading("D6E4F0");
346 tbl.cell(1, 3).unwrap().set_text("Total");
347 tbl.cell(1, 3).unwrap().shading("D6E4F0");
348
349 // Data
350 tbl.cell(2, 0).unwrap().set_text("North America");
351 tbl.cell(2, 1).unwrap().set_text("$2.4M");
352 tbl.cell(2, 2).unwrap().set_text("$2.7M");
353 tbl.cell(2, 3).unwrap().set_text("$5.1M");
354
355 tbl.cell(3, 0).unwrap().set_text("Europe");
356 tbl.cell(3, 1).unwrap().set_text("$1.8M");
357 tbl.cell(3, 2).unwrap().set_text("$2.0M");
358 tbl.cell(3, 3).unwrap().set_text("$3.8M");
359
360 // Vertical alignment on data cells
361 tbl.cell(2, 3)
362 .unwrap()
363 .vertical_alignment(VerticalAlignment::Center);
364 tbl.cell(3, 3)
365 .unwrap()
366 .vertical_alignment(VerticalAlignment::Bottom);
367 }
368
369 doc.add_paragraph("");
370
371 // --- Table with vertical merge ---
372 doc.add_paragraph("Table with Vertical Merge")
373 .style("Heading2");
374
375 {
376 let mut tbl = doc.add_table(4, 3);
377 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
378
379 tbl.cell(0, 0).unwrap().set_text("Category");
380 tbl.cell(0, 0).unwrap().shading("E2EFDA");
381 tbl.cell(0, 1).unwrap().set_text("Item");
382 tbl.cell(0, 1).unwrap().shading("E2EFDA");
383 tbl.cell(0, 2).unwrap().set_text("Price");
384 tbl.cell(0, 2).unwrap().shading("E2EFDA");
385
386 // "Hardware" spans rows 1-2
387 tbl.cell(1, 0).unwrap().set_text("Hardware");
388 tbl.cell(1, 0).unwrap().v_merge_restart();
389 tbl.cell(1, 1).unwrap().set_text("Laptop");
390 tbl.cell(1, 2).unwrap().set_text("$1,200");
391
392 tbl.cell(2, 0).unwrap().v_merge_continue();
393 tbl.cell(2, 1).unwrap().set_text("Monitor");
394 tbl.cell(2, 2).unwrap().set_text("$450");
395
396 // "Software" on row 3
397 tbl.cell(3, 0).unwrap().set_text("Software");
398 tbl.cell(3, 1).unwrap().set_text("IDE License");
399 tbl.cell(3, 2).unwrap().set_text("$200/yr");
400 }
401
402 doc.add_paragraph("");
403
404 // --- Nested table ---
405 doc.add_paragraph("Nested Table").style("Heading2");
406
407 {
408 let mut tbl = doc.add_table(2, 2);
409 tbl = tbl.borders(BorderStyle::Single, 6, "2E75B6");
410
411 tbl.cell(0, 0).unwrap().set_text("Outer Cell (0,0)");
412 tbl.cell(0, 1).unwrap().set_text("Outer Cell (0,1)");
413 tbl.cell(1, 0).unwrap().set_text("Outer Cell (1,0)");
414
415 // Nested table inside cell (1,1)
416 {
417 let mut cell = tbl.cell(1, 1).unwrap();
418 cell.set_text("Contains nested table:");
419 let mut nested = cell.add_table(2, 2);
420 nested = nested.borders(BorderStyle::Single, 2, "FF6600");
421 nested.cell(0, 0).unwrap().set_text("Inner A");
422 nested.cell(0, 1).unwrap().set_text("Inner B");
423 nested.cell(1, 0).unwrap().set_text("Inner C");
424 nested.cell(1, 1).unwrap().set_text("Inner D");
425 }
426 }
427
428 // =========================================================================
429 // PAGE 5: IMAGES
430 // =========================================================================
431 doc.add_paragraph("").page_break_before(true);
432
433 doc.add_paragraph("4. Images").style("Heading1");
434
435 doc.add_paragraph("Inline Image").style("Heading2");
436
437 doc.add_paragraph("Below is an inline image (200x50 pixels, blue gradient):");
438 let inline_img = create_sample_png(200, 50, [0, 80, 200]);
439 doc.add_picture(
440 &inline_img,
441 "inline_chart.png",
442 Length::inches(3.0),
443 Length::inches(0.75),
444 );
445
446 doc.add_paragraph("");
447
448 doc.add_paragraph("Header Image").style("Heading2");
449
450 // Replace the text-only header with an image header
451 let header_img = create_sample_png(400, 40, [40, 40, 40]);
452 doc.set_header_image(
453 &header_img,
454 "header_logo.png",
455 Length::inches(2.0),
456 Length::inches(0.2),
457 );
458
459 doc.add_paragraph(
460 "The document header has been replaced with an inline image. \
461 Check the header area at the top of this page.",
462 );
463
464 doc.add_paragraph("");
465 doc.add_paragraph(
466 "Note: The cover page uses a full-page background image behind the text, \
467 demonstrated on page 1 via add_background_image().",
468 );
469
470 // =========================================================================
471 // PAGE 6: CONTENT MANIPULATION — placeholder replacement, insertion
472 // =========================================================================
473 doc.add_paragraph("").page_break_before(true);
474
475 doc.add_paragraph("5. Content Manipulation")
476 .style("Heading1");
477
478 // --- Placeholder replacement ---
479 doc.add_paragraph("Placeholder Replacement")
480 .style("Heading2");
481
482 doc.add_paragraph(
483 "Before replacement, this document contained {{customer}} and {{date}} placeholders.",
484 );
485
486 {
487 let mut p = doc.add_paragraph("");
488 p.add_run("Customer: ").bold(true);
489 p.add_run("{{customer}}");
490 }
491 {
492 let mut p = doc.add_paragraph("");
493 p.add_run("Date: ").bold(true);
494 p.add_run("{{date}}");
495 }
496 doc.add_paragraph("Reference: {{ref_number}}");
497
498 // Table with placeholders
499 {
500 let mut tbl = doc.add_table(3, 2);
501 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
502 tbl.cell(0, 0).unwrap().set_text("Field");
503 tbl.cell(0, 0).unwrap().shading("D6E4F0");
504 tbl.cell(0, 1).unwrap().set_text("Value");
505 tbl.cell(0, 1).unwrap().shading("D6E4F0");
506 tbl.cell(1, 0).unwrap().set_text("Project");
507 tbl.cell(1, 1).unwrap().set_text("{{project}}");
508 tbl.cell(2, 0).unwrap().set_text("Status");
509 tbl.cell(2, 1).unwrap().set_text("{{status}}");
510 }
511
512 // Perform replacements
513 let mut replacements = HashMap::new();
514 replacements.insert("{{customer}}", "Acme Corporation");
515 replacements.insert("{{date}}", "February 22, 2026");
516 replacements.insert("{{ref_number}}", "REF-2026-001");
517 replacements.insert("{{project}}", "Infrastructure Upgrade");
518 replacements.insert("{{status}}", "In Progress");
519 let replace_count = doc.replace_all(&replacements);
520
521 doc.add_paragraph("");
522 doc.add_paragraph(&format!(
523 "(Replaced {} placeholders above — in body text and table cells)",
524 replace_count
525 ));
526
527 doc.add_paragraph("");
528
529 // --- Content insertion ---
530 doc.add_paragraph("Content Insertion").style("Heading2");
531
532 doc.add_paragraph("Section A: First section of content.");
533 doc.add_paragraph("Section C: Third section of content.");
534
535 // Insert "Section B" between A and C
536 if let Some(idx) = doc.find_content_index("Section C") {
537 doc.insert_paragraph(
538 idx,
539 "Section B: Inserted between A and C using find_content_index().",
540 );
541 }
542
543 doc.add_paragraph("");
544 doc.add_paragraph(
545 "The paragraph above ('Section B') was inserted at a specific position \
546 using find_content_index() + insert_paragraph().",
547 );
548
549 // =========================================================================
550 // PAGE 7: LANDSCAPE — section break, wide table
551 // =========================================================================
552 doc.add_paragraph("").section_break(SectionBreak::NextPage);
553
554 doc.add_paragraph("6. Mixed Page Orientation")
555 .style("Heading1");
556
557 doc.add_paragraph(
558 "This page is in LANDSCAPE orientation. It was created using a section break \
559 followed by section_landscape(). This is useful for wide tables or charts.",
560 );
561
562 doc.add_paragraph("");
563
564 // Wide table for landscape
565 {
566 let mut tbl = doc.add_table(4, 7);
567 tbl = tbl.borders(BorderStyle::Single, 4, "2E75B6");
568
569 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
570 for (col, h) in headers.iter().enumerate() {
571 tbl.cell(0, col).unwrap().set_text(h);
572 tbl.cell(0, col).unwrap().shading("2E75B6");
573 }
574
575 let data = [
576 [
577 "North America",
578 "$1.2M",
579 "$1.3M",
580 "$1.4M",
581 "$1.5M",
582 "$1.6M",
583 "$7.0M",
584 ],
585 [
586 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
587 ],
588 [
589 "Asia Pacific",
590 "$0.5M",
591 "$0.6M",
592 "$0.7M",
593 "$0.7M",
594 "$0.8M",
595 "$3.3M",
596 ],
597 ];
598 for (row_idx, row_data) in data.iter().enumerate() {
599 for (col, val) in row_data.iter().enumerate() {
600 tbl.cell(row_idx + 1, col).unwrap().set_text(val);
601 }
602 }
603 }
604
605 // End landscape, return to portrait
606 doc.add_paragraph("")
607 .section_break(SectionBreak::NextPage)
608 .section_landscape();
609
610 // =========================================================================
611 // PAGE 8: BACK TO PORTRAIT — styles, final notes
612 // =========================================================================
613 doc.add_paragraph("7. Custom Styles & Summary")
614 .style("Heading1");
615
616 doc.add_paragraph(
617 "This final page is back in portrait orientation after a section break. \
618 The document has demonstrated:",
619 );
620
621 doc.add_bullet_list_item(
622 "Page setup: size, margins, header/footer distance, gutter",
623 0,
624 );
625 doc.add_bullet_list_item("Document metadata: title, author, subject, keywords", 0);
626 doc.add_bullet_list_item("Headers and footers: text, images, different first page", 0);
627 doc.add_bullet_list_item("Background images: full-page behind text", 0);
628 doc.add_bullet_list_item(
629 "Text formatting: bold, italic, underline, strike, color, size, font",
630 0,
631 );
632 doc.add_bullet_list_item(
633 "Advanced run formatting: superscript, subscript, caps, spacing",
634 0,
635 );
636 doc.add_bullet_list_item(
637 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
638 0,
639 );
640 doc.add_bullet_list_item("Bullet and numbered lists with nesting levels", 0);
641 doc.add_bullet_list_item("Tab stops with dot/underscore leaders", 0);
642 doc.add_bullet_list_item(
643 "Tables: borders, shading, column spans, row spans, nesting",
644 0,
645 );
646 doc.add_bullet_list_item("Vertical alignment in table cells", 0);
647 doc.add_bullet_list_item("Inline images", 0);
648 doc.add_bullet_list_item("Placeholder replacement in body and table cells", 0);
649 doc.add_bullet_list_item("Content insertion at specific positions", 0);
650 doc.add_bullet_list_item(
651 "Section breaks with mixed portrait/landscape orientation",
652 0,
653 );
654
655 doc.add_paragraph("");
656 doc.add_paragraph("All features above were built entirely from scratch using the rdocx API.")
657 .alignment(Alignment::Center)
658 .shading("E2EFDA")
659 .border_all(BorderStyle::Single, 2, "00AA00");
660
661 doc.save(path).unwrap();
662}More examples
86fn generate_feature_showcase(_samples_dir: &Path) -> Document {
87 let mut doc = Document::new();
88
89 // ── Page Setup & Metadata ──
90 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
91 doc.set_margins(
92 Length::inches(1.0),
93 Length::inches(1.0),
94 Length::inches(1.0),
95 Length::inches(1.0),
96 );
97 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
98 doc.set_gutter(Length::twips(0));
99 doc.set_title("rdocx Feature Showcase");
100 doc.set_author("rdocx Sample Generator");
101 doc.set_subject("Comprehensive feature demonstration");
102 doc.set_keywords("rdocx, docx, rust, sample, showcase");
103
104 // Headers & Footers with different first page
105 doc.set_different_first_page(true);
106 doc.set_first_page_header("rdocx");
107 doc.set_first_page_footer("Feature Showcase — Cover Page");
108 doc.set_header("rdocx Feature Showcase");
109 doc.set_footer("Generated by rdocx");
110
111 // ── COVER PAGE ──
112 let bg = create_sample_png(612, 792, [20, 45, 90]);
113 doc.add_background_image(&bg, "cover_bg.png");
114
115 for _ in 0..3 {
116 doc.add_paragraph("");
117 }
118 {
119 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
120 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
121 }
122 {
123 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
124 p.add_run("Complete Feature Showcase")
125 .size(28.0)
126 .color("FFFFFF")
127 .italic(true);
128 }
129 doc.add_paragraph("");
130 {
131 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
132 p.add_run("Every feature of the rdocx Rust library")
133 .size(13.0)
134 .color("B0C4DE");
135 }
136 {
137 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
138 p.add_run("demonstrated in a single document.")
139 .size(13.0)
140 .color("B0C4DE");
141 }
142
143 // ── TABLE OF CONTENTS ──
144 doc.add_paragraph("").page_break_before(true);
145 doc.insert_toc(doc.content_count(), 3);
146
147 // ── SECTION 1: TEXT FORMATTING ──
148 doc.add_paragraph("").page_break_before(true);
149 doc.add_paragraph("1. Text Formatting").style("Heading1");
150
151 // Paragraph Alignment
152 doc.add_paragraph("1.1 Paragraph Alignment")
153 .style("Heading2");
154 doc.add_paragraph("Left-aligned paragraph (default).")
155 .alignment(Alignment::Left);
156 doc.add_paragraph("Center-aligned paragraph.")
157 .alignment(Alignment::Center);
158 doc.add_paragraph("Right-aligned paragraph.")
159 .alignment(Alignment::Right);
160 doc.add_paragraph(
161 "Justified paragraph — this text is long enough to demonstrate how justified alignment \
162 distributes extra space across word gaps so lines fill the full width of the text area.",
163 )
164 .alignment(Alignment::Justify);
165
166 doc.add_paragraph("");
167
168 // Run Formatting
169 doc.add_paragraph("1.2 Run Formatting").style("Heading2");
170 {
171 let mut p = doc.add_paragraph("");
172 p.add_run("Bold").bold(true);
173 p.add_run(" | ");
174 p.add_run("Italic").italic(true);
175 p.add_run(" | ");
176 p.add_run("Bold Italic").bold(true).italic(true);
177 p.add_run(" | ");
178 p.add_run("Underline").underline(true);
179 p.add_run(" | ");
180 p.add_run("Strikethrough").strike(true);
181 p.add_run(" | ");
182 p.add_run("Double Strike").double_strike(true);
183 }
184 {
185 let mut p = doc.add_paragraph("");
186 p.add_run("Red").color("FF0000");
187 p.add_run(" | ");
188 p.add_run("Blue").color("0000FF");
189 p.add_run(" | ");
190 p.add_run("Green").color("00AA00");
191 p.add_run(" | ");
192 p.add_run("Highlighted").highlight("FFFF00");
193 }
194 {
195 let mut p = doc.add_paragraph("");
196 p.add_run("8pt").size(8.0);
197 p.add_run(" | ");
198 p.add_run("11pt").size(11.0);
199 p.add_run(" | ");
200 p.add_run("16pt").size(16.0);
201 p.add_run(" | ");
202 p.add_run("24pt").size(24.0);
203 }
204 {
205 let mut p = doc.add_paragraph("");
206 p.add_run("Arial").font("Arial");
207 p.add_run(" | ");
208 p.add_run("Times New Roman").font("Times New Roman");
209 p.add_run(" | ");
210 p.add_run("Courier New").font("Courier New");
211 }
212 {
213 let mut p = doc.add_paragraph("");
214 p.add_run("H");
215 p.add_run("2").subscript();
216 p.add_run("O (subscript) | E=mc");
217 p.add_run("2").superscript();
218 p.add_run(" (superscript)");
219 }
220 {
221 let mut p = doc.add_paragraph("");
222 p.add_run("ALL CAPS").all_caps(true);
223 p.add_run(" | ");
224 p.add_run("Small Caps").small_caps(true);
225 p.add_run(" | ");
226 p.add_run("Expanded").character_spacing(Length::twips(40));
227 p.add_run(" | ");
228 p.add_run("Hidden text (not visible)").hidden(true);
229 }
230
231 doc.add_paragraph("");
232
233 // Underline Styles
234 doc.add_paragraph("1.3 Underline Styles").style("Heading2");
235 {
236 let mut p = doc.add_paragraph("");
237 p.add_run("Single ")
238 .underline_style(rdocx::UnderlineStyle::Single);
239 p.add_run("Double ")
240 .underline_style(rdocx::UnderlineStyle::Double);
241 p.add_run("Thick ")
242 .underline_style(rdocx::UnderlineStyle::Thick);
243 p.add_run("Dotted ")
244 .underline_style(rdocx::UnderlineStyle::Dotted);
245 p.add_run("Dash ")
246 .underline_style(rdocx::UnderlineStyle::Dash);
247 p.add_run("Wave ")
248 .underline_style(rdocx::UnderlineStyle::Wave);
249 }
250
251 // ── SECTION 2: PARAGRAPH FORMATTING ──
252 doc.add_paragraph("").page_break_before(true);
253 doc.add_paragraph("2. Paragraph Formatting")
254 .style("Heading1");
255
256 doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
257 doc.add_paragraph("Paragraph with light green shading")
258 .shading("E2EFDA");
259 doc.add_paragraph("Paragraph with bottom border")
260 .border_bottom(BorderStyle::Single, 6, "2E75B6");
261 doc.add_paragraph("Paragraph with all borders (red)")
262 .border_all(BorderStyle::Single, 4, "FF0000");
263
264 doc.add_paragraph("2.2 Indentation").style("Heading2");
265 doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
266 .indent_left(Length::inches(1.0))
267 .hanging_indent(Length::inches(0.5));
268 doc.add_paragraph("0.5-inch first-line indent on this paragraph")
269 .first_line_indent(Length::inches(0.5));
270 doc.add_paragraph("Both left and right indent (0.75 inches each)")
271 .indent_left(Length::inches(0.75))
272 .indent_right(Length::inches(0.75));
273
274 doc.add_paragraph("2.3 Spacing & Line Height")
275 .style("Heading2");
276 doc.add_paragraph("Extra space before (24pt) and after (12pt)")
277 .space_before(Length::pt(24.0))
278 .space_after(Length::pt(12.0));
279 doc.add_paragraph(
280 "Double line spacing paragraph. This text should have extra vertical space between \
281 lines to demonstrate line_spacing_multiple(2.0).",
282 )
283 .line_spacing_multiple(2.0);
284 doc.add_paragraph("Exact 20pt line spacing (fixed height).")
285 .line_spacing(20.0);
286
287 doc.add_paragraph("2.4 Pagination Controls")
288 .style("Heading2");
289 doc.add_paragraph("keep_with_next — this paragraph stays with the next")
290 .keep_with_next(true);
291 doc.add_paragraph("(This paragraph was kept with the one above.)");
292 doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
293 .keep_together(true);
294 doc.add_paragraph("widow_control — prevents widow/orphan lines")
295 .widow_control(true);
296
297 // ── SECTION 3: LISTS ──
298 doc.add_paragraph("").page_break_before(true);
299 doc.add_paragraph("3. Lists").style("Heading1");
300
301 doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
302 doc.add_bullet_list_item("First item", 0);
303 doc.add_bullet_list_item("Second item", 0);
304 doc.add_bullet_list_item("Nested level 1", 1);
305 doc.add_bullet_list_item("Nested level 2", 2);
306 doc.add_bullet_list_item("Back to level 1", 1);
307 doc.add_bullet_list_item("Third item", 0);
308
309 doc.add_paragraph("");
310
311 doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
312 doc.add_numbered_list_item("First numbered item", 0);
313 doc.add_numbered_list_item("Second numbered item", 0);
314 doc.add_numbered_list_item("Sub-item A", 1);
315 doc.add_numbered_list_item("Sub-item B", 1);
316 doc.add_numbered_list_item("Third numbered item", 0);
317
318 // ── SECTION 4: TAB STOPS ──
319 doc.add_paragraph("");
320 doc.add_paragraph("4. Tab Stops").style("Heading1");
321
322 doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
323 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
324 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
325 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
326 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
327 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
328
329 doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
330 doc.add_paragraph("Item\t\tPrice")
331 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
332 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
333 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
334 doc.add_paragraph("Widget\t\t$19.99")
335 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
336 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
337 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
338 doc.add_paragraph("Gadget\t\t$249.50")
339 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
340 .add_tab_stop_with_leader(
341 TabAlignment::Right,
342 Length::inches(4.0),
343 TabLeader::Underscore,
344 )
345 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
346
347 // ── SECTION 5: TABLES ──
348 doc.add_paragraph("").page_break_before(true);
349 doc.add_paragraph("5. Tables").style("Heading1");
350
351 doc.add_paragraph("5.1 Basic Table").style("Heading2");
352 {
353 let mut tbl = doc
354 .add_table(4, 3)
355 .borders(BorderStyle::Single, 4, "000000");
356 for col in 0..3 {
357 tbl.cell(0, col).unwrap().shading("2E75B6");
358 }
359 tbl.cell(0, 0).unwrap().set_text("Name");
360 tbl.cell(0, 1).unwrap().set_text("Role");
361 tbl.cell(0, 2).unwrap().set_text("Location");
362 tbl.cell(1, 0).unwrap().set_text("Walter White");
363 tbl.cell(1, 1).unwrap().set_text("CEO");
364 tbl.cell(1, 2).unwrap().set_text("Albuquerque");
365 tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
366 tbl.cell(2, 1).unwrap().set_text("CTO");
367 tbl.cell(2, 2).unwrap().set_text("Remote");
368 tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
369 tbl.cell(3, 1).unwrap().set_text("Security");
370 tbl.cell(3, 2).unwrap().set_text("Washington");
371 }
372
373 doc.add_paragraph("");
374
375 doc.add_paragraph("5.2 Column Span & Cell Shading")
376 .style("Heading2");
377 {
378 let mut tbl = doc
379 .add_table(3, 4)
380 .borders(BorderStyle::Single, 4, "000000")
381 .width_pct(100.0);
382 tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
383 tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
384 tbl.cell(1, 0).unwrap().set_text("Region");
385 tbl.cell(1, 0).unwrap().shading("D6E4F0");
386 tbl.cell(1, 1).unwrap().set_text("Q1");
387 tbl.cell(1, 1).unwrap().shading("D6E4F0");
388 tbl.cell(1, 2).unwrap().set_text("Q2");
389 tbl.cell(1, 2).unwrap().shading("D6E4F0");
390 tbl.cell(1, 3).unwrap().set_text("Total");
391 tbl.cell(1, 3).unwrap().shading("D6E4F0");
392 tbl.cell(2, 0).unwrap().set_text("Americas");
393 tbl.cell(2, 1).unwrap().set_text("$2.4M");
394 tbl.cell(2, 2).unwrap().set_text("$2.7M");
395 tbl.cell(2, 3).unwrap().set_text("$5.1M");
396 }
397
398 doc.add_paragraph("");
399
400 doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
401 {
402 let mut tbl = doc
403 .add_table(4, 3)
404 .borders(BorderStyle::Single, 4, "000000");
405 tbl.cell(0, 0).unwrap().set_text("Category");
406 tbl.cell(0, 0).unwrap().shading("E2EFDA");
407 tbl.cell(0, 1).unwrap().set_text("Item");
408 tbl.cell(0, 1).unwrap().shading("E2EFDA");
409 tbl.cell(0, 2).unwrap().set_text("Price");
410 tbl.cell(0, 2).unwrap().shading("E2EFDA");
411 tbl.cell(1, 0).unwrap().set_text("Hardware");
412 tbl.cell(1, 0).unwrap().v_merge_restart();
413 tbl.cell(1, 1).unwrap().set_text("Laptop");
414 tbl.cell(1, 2).unwrap().set_text("$1,200");
415 tbl.cell(2, 0).unwrap().v_merge_continue();
416 tbl.cell(2, 1).unwrap().set_text("Monitor");
417 tbl.cell(2, 2).unwrap().set_text("$450");
418 tbl.cell(3, 0).unwrap().set_text("Software");
419 tbl.cell(3, 1).unwrap().set_text("IDE License");
420 tbl.cell(3, 2).unwrap().set_text("$200/yr");
421 }
422
423 doc.add_paragraph("");
424
425 doc.add_paragraph("5.4 Nested Table").style("Heading2");
426 {
427 let mut tbl = doc
428 .add_table(2, 2)
429 .borders(BorderStyle::Single, 6, "2E75B6");
430 tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
431 tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
432 tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
433 {
434 let mut cell = tbl.cell(1, 1).unwrap();
435 cell.set_text("Nested table below:");
436 let mut nested = cell
437 .add_table(2, 2)
438 .borders(BorderStyle::Single, 2, "FF6600");
439 nested.cell(0, 0).unwrap().set_text("A");
440 nested.cell(0, 1).unwrap().set_text("B");
441 nested.cell(1, 0).unwrap().set_text("C");
442 nested.cell(1, 1).unwrap().set_text("D");
443 }
444 }
445
446 doc.add_paragraph("");
447
448 doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
449 .style("Heading2");
450 {
451 let mut tbl = doc
452 .add_table(2, 3)
453 .borders(BorderStyle::Single, 4, "666666");
454 tbl.row(0).unwrap().height(Length::pt(50.0)).header();
455 tbl.cell(0, 0).unwrap().set_text("Top");
456 tbl.cell(0, 0)
457 .unwrap()
458 .vertical_alignment(VerticalAlignment::Top)
459 .shading("FFF2CC");
460 tbl.cell(0, 1).unwrap().set_text("Center");
461 tbl.cell(0, 1)
462 .unwrap()
463 .vertical_alignment(VerticalAlignment::Center)
464 .shading("D9E2F3");
465 tbl.cell(0, 2).unwrap().set_text("Bottom");
466 tbl.cell(0, 2)
467 .unwrap()
468 .vertical_alignment(VerticalAlignment::Bottom)
469 .shading("E2EFDA");
470 tbl.row(1).unwrap().cant_split();
471 tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
472 tbl.cell(1, 0).unwrap().no_wrap();
473 tbl.cell(1, 1).unwrap().set_text("Fixed width");
474 tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
475 tbl.cell(1, 2).unwrap().set_text("Normal");
476 }
477
478 // ── SECTION 6: IMAGES ──
479 doc.add_paragraph("").page_break_before(true);
480 doc.add_paragraph("6. Images").style("Heading1");
481
482 doc.add_paragraph("6.1 Inline Image").style("Heading2");
483 doc.add_paragraph("A blue gradient image below:");
484 let img = create_sample_png(200, 50, [0, 80, 200]);
485 doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
486
487 doc.add_paragraph("");
488 doc.add_paragraph("6.2 Header Image").style("Heading2");
489 let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
490 doc.set_header_image(
491 &hdr_img,
492 "header_logo.png",
493 Length::inches(2.0),
494 Length::inches(0.2),
495 );
496 doc.add_paragraph("The header now contains an inline image (check the top of this page).");
497
498 doc.add_paragraph("");
499 doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
500
501 // ── SECTION 7: CONTENT MANIPULATION ──
502 doc.add_paragraph("").page_break_before(true);
503 doc.add_paragraph("7. Content Manipulation")
504 .style("Heading1");
505
506 doc.add_paragraph("7.1 Placeholder Replacement")
507 .style("Heading2");
508 doc.add_paragraph("Customer: {{customer}}");
509 doc.add_paragraph("Date: {{date}}");
510 doc.add_paragraph("Reference: {{ref_number}}");
511 {
512 let mut tbl = doc
513 .add_table(2, 2)
514 .borders(BorderStyle::Single, 4, "000000");
515 tbl.cell(0, 0).unwrap().set_text("Project");
516 tbl.cell(0, 0).unwrap().shading("D6E4F0");
517 tbl.cell(0, 1).unwrap().set_text("{{project}}");
518 tbl.cell(1, 0).unwrap().set_text("Status");
519 tbl.cell(1, 0).unwrap().shading("D6E4F0");
520 tbl.cell(1, 1).unwrap().set_text("{{status}}");
521 }
522
523 let mut replacements = HashMap::new();
524 replacements.insert("{{customer}}", "Tensorbee Inc.");
525 replacements.insert("{{date}}", "February 22, 2026");
526 replacements.insert("{{ref_number}}", "TB-2026-001");
527 replacements.insert("{{project}}", "Infrastructure Upgrade");
528 replacements.insert("{{status}}", "In Progress");
529 let n = doc.replace_all(&replacements);
530 doc.add_paragraph(&format!("({n} placeholders replaced above)"));
531
532 doc.add_paragraph("");
533
534 doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
535 doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
536 let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
537 doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
538
539 doc.add_paragraph("");
540
541 doc.add_paragraph("7.3 Content Insertion").style("Heading2");
542 doc.add_paragraph("Section A: First content.");
543 doc.add_paragraph("Section C: Third content.");
544 if let Some(idx) = doc.find_content_index("Section C") {
545 doc.insert_paragraph(
546 idx,
547 "Section B: Inserted between A and C via find_content_index().",
548 );
549 }
550
551 // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
552 doc.add_paragraph("").section_break(SectionBreak::NextPage);
553 doc.add_paragraph("8. Section Breaks & Orientation")
554 .style("Heading1");
555 doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
556
557 {
558 let mut tbl = doc
559 .add_table(3, 7)
560 .borders(BorderStyle::Single, 4, "2E75B6");
561 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
562 for (c, h) in headers.iter().enumerate() {
563 tbl.cell(0, c).unwrap().set_text(h);
564 tbl.cell(0, c).unwrap().shading("2E75B6");
565 }
566 let data = [
567 [
568 "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
569 ],
570 [
571 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
572 ],
573 ];
574 for (r, row) in data.iter().enumerate() {
575 for (c, v) in row.iter().enumerate() {
576 tbl.cell(r + 1, c).unwrap().set_text(v);
577 }
578 }
579 }
580
581 doc.add_paragraph("")
582 .section_break(SectionBreak::NextPage)
583 .section_landscape();
584
585 // ── SECTION 9: CUSTOM STYLES ──
586 doc.add_paragraph("9. Custom Styles").style("Heading1");
587 doc.add_style(
588 StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
589 .based_on("Normal")
590 .paragraph_properties({
591 let mut ppr = rdocx_oxml::properties::CT_PPr::default();
592 ppr.shading = Some(rdocx_oxml::properties::CT_Shd {
593 val: "clear".to_string(),
594 color: None,
595 fill: Some("FFF2CC".to_string()),
596 });
597 ppr
598 })
599 .run_properties({
600 let mut rpr = rdocx_oxml::properties::CT_RPr::default();
601 rpr.bold = Some(true);
602 rpr.color = Some("C45911".to_string());
603 rpr
604 }),
605 );
606 doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
607 .style("CustomHighlight");
608
609 doc.add_paragraph("");
610
611 // ── SECTION 10: DOCUMENT INTELLIGENCE ──
612 doc.add_paragraph("10. Document Intelligence API")
613 .style("Heading1");
614
615 let wc = doc.word_count();
616 let hc = doc.headings().len();
617 let ic = doc.images().len();
618 let lc = doc.links().len();
619 doc.add_paragraph(&format!("Word count: {wc}"));
620 doc.add_paragraph(&format!("Heading count: {hc}"));
621 doc.add_paragraph(&format!("Image count: {ic}"));
622 doc.add_paragraph(&format!("Link count: {lc}"));
623
624 let outline = doc.document_outline();
625 doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
626
627 let issues = doc.audit_accessibility();
628 doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
629 for issue in &issues {
630 doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
631 }
632
633 // ── SECTION 11: DOCUMENT MERGING ──
634 doc.add_paragraph("");
635 doc.add_paragraph("11. Document Merging").style("Heading1");
636 {
637 let mut other = Document::new();
638 other.add_paragraph("This paragraph was merged from another document using append().");
639 doc.append(&other);
640 }
641
642 doc.add_paragraph("");
643
644 // ── SUMMARY ──
645 doc.add_paragraph("Summary of Demonstrated Features")
646 .style("Heading1");
647 let features = [
648 "Page setup: size, margins, header/footer distance, gutter",
649 "Document metadata: title, author, subject, keywords",
650 "Headers and footers: text, images, different first page",
651 "Background images (full-page behind text)",
652 "Table of Contents generation with bookmarks",
653 "Text formatting: bold, italic, underline styles, strike, color, size, font",
654 "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
655 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
656 "Pagination controls: keep-with-next, keep-together, widow control",
657 "Bullet and numbered lists with nesting",
658 "Tab stops with dot/underscore/hyphen leaders",
659 "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
660 "Row properties: header rows, exact height, cant-split",
661 "Cell properties: width, no-wrap, vertical alignment",
662 "Inline images and header images",
663 "Placeholder replacement (single and batch)",
664 "Regex-based find and replace",
665 "Content insertion at specific positions",
666 "Section breaks with mixed portrait/landscape",
667 "Custom paragraph and character styles",
668 "Document intelligence: word count, headings, outline, images, links",
669 "Accessibility audit",
670 "Document merging (append)",
671 "Export: DOCX, PDF, HTML, Markdown",
672 ];
673 for f in &features {
674 doc.add_bullet_list_item(f, 0);
675 }
676 doc.add_paragraph("");
677 doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
678 .alignment(Alignment::Center)
679 .shading("E2EFDA")
680 .border_all(BorderStyle::Single, 2, "00AA00");
681
682 doc
683}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
34fn generate_feature_showcase(path: &Path) {
35 let mut doc = Document::new();
36
37 // =========================================================================
38 // PAGE SETUP & METADATA
39 // =========================================================================
40 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
41 doc.set_margins(
42 Length::inches(1.0), // top
43 Length::inches(1.0), // right
44 Length::inches(1.0), // bottom
45 Length::inches(1.0), // left
46 );
47 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
48 doc.set_gutter(Length::twips(0));
49
50 doc.set_title("rdocx Feature Showcase");
51 doc.set_author("rdocx Sample Generator");
52 doc.set_subject("Comprehensive feature demonstration");
53 doc.set_keywords("rdocx, docx, rust, sample");
54
55 // Header & Footer
56 doc.set_header("rdocx Feature Showcase");
57 doc.set_footer("Generated by rdocx — Page");
58
59 // Different first page header
60 doc.set_different_first_page(true);
61 doc.set_first_page_header("rdocx");
62 doc.set_first_page_footer("Feature Showcase — Cover Page");
63
64 // =========================================================================
65 // PAGE 1: COVER PAGE — background image, run formatting
66 // =========================================================================
67 let bg_cover = create_sample_png(612, 792, [30, 60, 120]);
68 doc.add_background_image(&bg_cover, "cover_bg.png");
69
70 doc.add_paragraph(""); // spacer
71 doc.add_paragraph(""); // spacer
72 doc.add_paragraph(""); // spacer
73
74 {
75 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
76 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
77 }
78 {
79 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
80 p.add_run("Feature Showcase")
81 .size(28.0)
82 .color("FFFFFF")
83 .italic(true);
84 }
85
86 doc.add_paragraph(""); // spacer
87
88 {
89 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
90 p.add_run("A comprehensive demonstration of every feature")
91 .size(14.0)
92 .color("CCDDFF");
93 }
94 {
95 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
96 p.add_run("provided by the rdocx Rust crate for DOCX generation.")
97 .size(14.0)
98 .color("CCDDFF");
99 }
100
101 // =========================================================================
102 // PAGE 2: TEXT FORMATTING
103 // =========================================================================
104 doc.add_paragraph("").page_break_before(true);
105
106 doc.add_paragraph("1. Text Formatting").style("Heading1");
107
108 doc.add_paragraph("This section demonstrates paragraph and run-level formatting options.");
109 doc.add_paragraph("");
110
111 // --- Paragraph alignment ---
112 doc.add_paragraph("Paragraph Alignment").style("Heading2");
113
114 doc.add_paragraph("This paragraph is left-aligned (the default).")
115 .alignment(Alignment::Left);
116 doc.add_paragraph("This paragraph is center-aligned.")
117 .alignment(Alignment::Center);
118 doc.add_paragraph("This paragraph is right-aligned.")
119 .alignment(Alignment::Right);
120 doc.add_paragraph(
121 "This paragraph is justified. To demonstrate justified text properly, it needs \
122 to be long enough to span multiple lines so the word spacing adjustment is visible \
123 across the full width of the text area on the page.",
124 )
125 .alignment(Alignment::Justify);
126
127 doc.add_paragraph("");
128
129 // --- Run formatting ---
130 doc.add_paragraph("Run Formatting").style("Heading2");
131
132 {
133 let mut p = doc.add_paragraph("");
134 p.add_run("Bold text").bold(true);
135 p.add_run(" | ");
136 p.add_run("Italic text").italic(true);
137 p.add_run(" | ");
138 p.add_run("Bold + Italic").bold(true).italic(true);
139 }
140 {
141 let mut p = doc.add_paragraph("");
142 p.add_run("Single underline").underline(true);
143 p.add_run(" | ");
144 p.add_run("Strikethrough").strike(true);
145 p.add_run(" | ");
146 p.add_run("Double strikethrough").double_strike(true);
147 }
148 {
149 let mut p = doc.add_paragraph("");
150 p.add_run("Red text").color("FF0000");
151 p.add_run(" | ");
152 p.add_run("Blue text").color("0000FF");
153 p.add_run(" | ");
154 p.add_run("Green text").color("00AA00");
155 p.add_run(" | ");
156 p.add_run("Highlighted").highlight("FFFF00");
157 }
158 {
159 let mut p = doc.add_paragraph("");
160 p.add_run("8pt small").size(8.0);
161 p.add_run(" | ");
162 p.add_run("11pt normal").size(11.0);
163 p.add_run(" | ");
164 p.add_run("16pt large").size(16.0);
165 p.add_run(" | ");
166 p.add_run("24pt extra-large").size(24.0);
167 }
168 {
169 let mut p = doc.add_paragraph("");
170 p.add_run("Arial font").font("Arial");
171 p.add_run(" | ");
172 p.add_run("Times New Roman font").font("Times New Roman");
173 p.add_run(" | ");
174 p.add_run("Courier New font").font("Courier New");
175 }
176 {
177 let mut p = doc.add_paragraph("");
178 p.add_run("Normal");
179 p.add_run(" H").size(11.0);
180 p.add_run("2").subscript();
181 p.add_run("O (subscript)").size(11.0);
182 p.add_run(" | E = mc").size(11.0);
183 p.add_run("2").superscript();
184 p.add_run(" (superscript)").size(11.0);
185 }
186 {
187 let mut p = doc.add_paragraph("");
188 p.add_run("ALL CAPS").all_caps(true);
189 p.add_run(" | ");
190 p.add_run("Small Caps").small_caps(true);
191 p.add_run(" | ");
192 p.add_run("Expanded spacing")
193 .character_spacing(Length::twips(40));
194 }
195
196 doc.add_paragraph("");
197
198 // --- Paragraph formatting ---
199 doc.add_paragraph("Paragraph Formatting").style("Heading2");
200
201 doc.add_paragraph("Paragraph with shading (light green background)")
202 .shading("E2EFDA");
203
204 doc.add_paragraph("Paragraph with bottom border")
205 .border_bottom(BorderStyle::Single, 6, "2E75B6");
206
207 doc.add_paragraph("Paragraph with all borders")
208 .border_all(BorderStyle::Single, 4, "FF0000");
209
210 doc.add_paragraph("Paragraph with 1-inch left indent and hanging indent")
211 .indent_left(Length::inches(1.0))
212 .hanging_indent(Length::inches(0.5));
213
214 doc.add_paragraph("Paragraph with first-line indent of 0.5 inches")
215 .first_line_indent(Length::inches(0.5));
216
217 doc.add_paragraph("Paragraph with extra space before (24pt) and after (12pt)")
218 .space_before(Length::pt(24.0))
219 .space_after(Length::pt(12.0));
220
221 doc.add_paragraph(
222 "Paragraph with double line spacing. This text should have extra vertical \
223 space between lines to demonstrate the line_spacing_multiple setting.",
224 )
225 .line_spacing_multiple(2.0);
226
227 doc.add_paragraph("Paragraph with keep-with-next (won't break from the next paragraph)")
228 .keep_with_next(true);
229 doc.add_paragraph("(This stays with the paragraph above.)");
230
231 // =========================================================================
232 // PAGE 3: LISTS & TAB STOPS
233 // =========================================================================
234 doc.add_paragraph("").page_break_before(true);
235
236 doc.add_paragraph("2. Lists").style("Heading1");
237
238 doc.add_paragraph("Bullet List").style("Heading2");
239
240 doc.add_bullet_list_item("First bullet item", 0);
241 doc.add_bullet_list_item("Second bullet item", 0);
242 doc.add_bullet_list_item("Nested level 1", 1);
243 doc.add_bullet_list_item("Nested level 2", 2);
244 doc.add_bullet_list_item("Back to level 1", 1);
245 doc.add_bullet_list_item("Third bullet item", 0);
246
247 doc.add_paragraph("");
248
249 doc.add_paragraph("Numbered List").style("Heading2");
250
251 doc.add_numbered_list_item("First numbered item", 0);
252 doc.add_numbered_list_item("Second numbered item", 0);
253 doc.add_numbered_list_item("Sub-item A", 1);
254 doc.add_numbered_list_item("Sub-item B", 1);
255 doc.add_numbered_list_item("Third numbered item", 0);
256
257 doc.add_paragraph("");
258
259 // --- Tab stops ---
260 doc.add_paragraph("Tab Stops").style("Heading2");
261
262 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
263 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
264 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
265 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
266 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
267
268 doc.add_paragraph("Item\t........\tPrice")
269 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
270 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
271 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
272
273 doc.add_paragraph("Widget A\t........\t$19.99")
274 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
275 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
276 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
277
278 doc.add_paragraph("Gadget B\t________\t$249.50")
279 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
280 .add_tab_stop_with_leader(
281 TabAlignment::Right,
282 Length::inches(4.0),
283 TabLeader::Underscore,
284 )
285 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
286
287 // =========================================================================
288 // PAGE 4: TABLES
289 // =========================================================================
290 doc.add_paragraph("").page_break_before(true);
291
292 doc.add_paragraph("3. Tables").style("Heading1");
293
294 // --- Basic table with borders ---
295 doc.add_paragraph("Basic Table with Borders")
296 .style("Heading2");
297
298 {
299 let mut tbl = doc.add_table(4, 3);
300 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
301
302 // Header row
303 for col in 0..3 {
304 tbl.cell(0, col).unwrap().shading("2E75B6");
305 }
306 tbl.cell(0, 0).unwrap().set_text("Name");
307 tbl.cell(0, 1).unwrap().set_text("Role");
308 tbl.cell(0, 2).unwrap().set_text("Location");
309
310 tbl.cell(1, 0).unwrap().set_text("Alice Johnson");
311 tbl.cell(1, 1).unwrap().set_text("Engineering Lead");
312 tbl.cell(1, 2).unwrap().set_text("New York");
313
314 tbl.cell(2, 0).unwrap().set_text("Bob Smith");
315 tbl.cell(2, 1).unwrap().set_text("Product Manager");
316 tbl.cell(2, 2).unwrap().set_text("San Francisco");
317
318 tbl.cell(3, 0).unwrap().set_text("Carol Davis");
319 tbl.cell(3, 1).unwrap().set_text("Designer");
320 tbl.cell(3, 2).unwrap().set_text("London");
321 }
322
323 doc.add_paragraph("");
324
325 // --- Table with cell merging ---
326 doc.add_paragraph("Table with Cell Merging & Vertical Alignment")
327 .style("Heading2");
328
329 {
330 let mut tbl = doc.add_table(4, 4);
331 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
332 tbl = tbl.width_pct(100.0);
333
334 // Header spanning all columns
335 tbl.cell(0, 0).unwrap().set_text("Quarterly Revenue Report");
336 tbl.cell(0, 0).unwrap().shading("1F4E79");
337 tbl.cell(0, 0).unwrap().grid_span(4);
338
339 // Sub-header
340 tbl.cell(1, 0).unwrap().set_text("Region");
341 tbl.cell(1, 0).unwrap().shading("D6E4F0");
342 tbl.cell(1, 1).unwrap().set_text("Q1");
343 tbl.cell(1, 1).unwrap().shading("D6E4F0");
344 tbl.cell(1, 2).unwrap().set_text("Q2");
345 tbl.cell(1, 2).unwrap().shading("D6E4F0");
346 tbl.cell(1, 3).unwrap().set_text("Total");
347 tbl.cell(1, 3).unwrap().shading("D6E4F0");
348
349 // Data
350 tbl.cell(2, 0).unwrap().set_text("North America");
351 tbl.cell(2, 1).unwrap().set_text("$2.4M");
352 tbl.cell(2, 2).unwrap().set_text("$2.7M");
353 tbl.cell(2, 3).unwrap().set_text("$5.1M");
354
355 tbl.cell(3, 0).unwrap().set_text("Europe");
356 tbl.cell(3, 1).unwrap().set_text("$1.8M");
357 tbl.cell(3, 2).unwrap().set_text("$2.0M");
358 tbl.cell(3, 3).unwrap().set_text("$3.8M");
359
360 // Vertical alignment on data cells
361 tbl.cell(2, 3)
362 .unwrap()
363 .vertical_alignment(VerticalAlignment::Center);
364 tbl.cell(3, 3)
365 .unwrap()
366 .vertical_alignment(VerticalAlignment::Bottom);
367 }
368
369 doc.add_paragraph("");
370
371 // --- Table with vertical merge ---
372 doc.add_paragraph("Table with Vertical Merge")
373 .style("Heading2");
374
375 {
376 let mut tbl = doc.add_table(4, 3);
377 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
378
379 tbl.cell(0, 0).unwrap().set_text("Category");
380 tbl.cell(0, 0).unwrap().shading("E2EFDA");
381 tbl.cell(0, 1).unwrap().set_text("Item");
382 tbl.cell(0, 1).unwrap().shading("E2EFDA");
383 tbl.cell(0, 2).unwrap().set_text("Price");
384 tbl.cell(0, 2).unwrap().shading("E2EFDA");
385
386 // "Hardware" spans rows 1-2
387 tbl.cell(1, 0).unwrap().set_text("Hardware");
388 tbl.cell(1, 0).unwrap().v_merge_restart();
389 tbl.cell(1, 1).unwrap().set_text("Laptop");
390 tbl.cell(1, 2).unwrap().set_text("$1,200");
391
392 tbl.cell(2, 0).unwrap().v_merge_continue();
393 tbl.cell(2, 1).unwrap().set_text("Monitor");
394 tbl.cell(2, 2).unwrap().set_text("$450");
395
396 // "Software" on row 3
397 tbl.cell(3, 0).unwrap().set_text("Software");
398 tbl.cell(3, 1).unwrap().set_text("IDE License");
399 tbl.cell(3, 2).unwrap().set_text("$200/yr");
400 }
401
402 doc.add_paragraph("");
403
404 // --- Nested table ---
405 doc.add_paragraph("Nested Table").style("Heading2");
406
407 {
408 let mut tbl = doc.add_table(2, 2);
409 tbl = tbl.borders(BorderStyle::Single, 6, "2E75B6");
410
411 tbl.cell(0, 0).unwrap().set_text("Outer Cell (0,0)");
412 tbl.cell(0, 1).unwrap().set_text("Outer Cell (0,1)");
413 tbl.cell(1, 0).unwrap().set_text("Outer Cell (1,0)");
414
415 // Nested table inside cell (1,1)
416 {
417 let mut cell = tbl.cell(1, 1).unwrap();
418 cell.set_text("Contains nested table:");
419 let mut nested = cell.add_table(2, 2);
420 nested = nested.borders(BorderStyle::Single, 2, "FF6600");
421 nested.cell(0, 0).unwrap().set_text("Inner A");
422 nested.cell(0, 1).unwrap().set_text("Inner B");
423 nested.cell(1, 0).unwrap().set_text("Inner C");
424 nested.cell(1, 1).unwrap().set_text("Inner D");
425 }
426 }
427
428 // =========================================================================
429 // PAGE 5: IMAGES
430 // =========================================================================
431 doc.add_paragraph("").page_break_before(true);
432
433 doc.add_paragraph("4. Images").style("Heading1");
434
435 doc.add_paragraph("Inline Image").style("Heading2");
436
437 doc.add_paragraph("Below is an inline image (200x50 pixels, blue gradient):");
438 let inline_img = create_sample_png(200, 50, [0, 80, 200]);
439 doc.add_picture(
440 &inline_img,
441 "inline_chart.png",
442 Length::inches(3.0),
443 Length::inches(0.75),
444 );
445
446 doc.add_paragraph("");
447
448 doc.add_paragraph("Header Image").style("Heading2");
449
450 // Replace the text-only header with an image header
451 let header_img = create_sample_png(400, 40, [40, 40, 40]);
452 doc.set_header_image(
453 &header_img,
454 "header_logo.png",
455 Length::inches(2.0),
456 Length::inches(0.2),
457 );
458
459 doc.add_paragraph(
460 "The document header has been replaced with an inline image. \
461 Check the header area at the top of this page.",
462 );
463
464 doc.add_paragraph("");
465 doc.add_paragraph(
466 "Note: The cover page uses a full-page background image behind the text, \
467 demonstrated on page 1 via add_background_image().",
468 );
469
470 // =========================================================================
471 // PAGE 6: CONTENT MANIPULATION — placeholder replacement, insertion
472 // =========================================================================
473 doc.add_paragraph("").page_break_before(true);
474
475 doc.add_paragraph("5. Content Manipulation")
476 .style("Heading1");
477
478 // --- Placeholder replacement ---
479 doc.add_paragraph("Placeholder Replacement")
480 .style("Heading2");
481
482 doc.add_paragraph(
483 "Before replacement, this document contained {{customer}} and {{date}} placeholders.",
484 );
485
486 {
487 let mut p = doc.add_paragraph("");
488 p.add_run("Customer: ").bold(true);
489 p.add_run("{{customer}}");
490 }
491 {
492 let mut p = doc.add_paragraph("");
493 p.add_run("Date: ").bold(true);
494 p.add_run("{{date}}");
495 }
496 doc.add_paragraph("Reference: {{ref_number}}");
497
498 // Table with placeholders
499 {
500 let mut tbl = doc.add_table(3, 2);
501 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
502 tbl.cell(0, 0).unwrap().set_text("Field");
503 tbl.cell(0, 0).unwrap().shading("D6E4F0");
504 tbl.cell(0, 1).unwrap().set_text("Value");
505 tbl.cell(0, 1).unwrap().shading("D6E4F0");
506 tbl.cell(1, 0).unwrap().set_text("Project");
507 tbl.cell(1, 1).unwrap().set_text("{{project}}");
508 tbl.cell(2, 0).unwrap().set_text("Status");
509 tbl.cell(2, 1).unwrap().set_text("{{status}}");
510 }
511
512 // Perform replacements
513 let mut replacements = HashMap::new();
514 replacements.insert("{{customer}}", "Acme Corporation");
515 replacements.insert("{{date}}", "February 22, 2026");
516 replacements.insert("{{ref_number}}", "REF-2026-001");
517 replacements.insert("{{project}}", "Infrastructure Upgrade");
518 replacements.insert("{{status}}", "In Progress");
519 let replace_count = doc.replace_all(&replacements);
520
521 doc.add_paragraph("");
522 doc.add_paragraph(&format!(
523 "(Replaced {} placeholders above — in body text and table cells)",
524 replace_count
525 ));
526
527 doc.add_paragraph("");
528
529 // --- Content insertion ---
530 doc.add_paragraph("Content Insertion").style("Heading2");
531
532 doc.add_paragraph("Section A: First section of content.");
533 doc.add_paragraph("Section C: Third section of content.");
534
535 // Insert "Section B" between A and C
536 if let Some(idx) = doc.find_content_index("Section C") {
537 doc.insert_paragraph(
538 idx,
539 "Section B: Inserted between A and C using find_content_index().",
540 );
541 }
542
543 doc.add_paragraph("");
544 doc.add_paragraph(
545 "The paragraph above ('Section B') was inserted at a specific position \
546 using find_content_index() + insert_paragraph().",
547 );
548
549 // =========================================================================
550 // PAGE 7: LANDSCAPE — section break, wide table
551 // =========================================================================
552 doc.add_paragraph("").section_break(SectionBreak::NextPage);
553
554 doc.add_paragraph("6. Mixed Page Orientation")
555 .style("Heading1");
556
557 doc.add_paragraph(
558 "This page is in LANDSCAPE orientation. It was created using a section break \
559 followed by section_landscape(). This is useful for wide tables or charts.",
560 );
561
562 doc.add_paragraph("");
563
564 // Wide table for landscape
565 {
566 let mut tbl = doc.add_table(4, 7);
567 tbl = tbl.borders(BorderStyle::Single, 4, "2E75B6");
568
569 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
570 for (col, h) in headers.iter().enumerate() {
571 tbl.cell(0, col).unwrap().set_text(h);
572 tbl.cell(0, col).unwrap().shading("2E75B6");
573 }
574
575 let data = [
576 [
577 "North America",
578 "$1.2M",
579 "$1.3M",
580 "$1.4M",
581 "$1.5M",
582 "$1.6M",
583 "$7.0M",
584 ],
585 [
586 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
587 ],
588 [
589 "Asia Pacific",
590 "$0.5M",
591 "$0.6M",
592 "$0.7M",
593 "$0.7M",
594 "$0.8M",
595 "$3.3M",
596 ],
597 ];
598 for (row_idx, row_data) in data.iter().enumerate() {
599 for (col, val) in row_data.iter().enumerate() {
600 tbl.cell(row_idx + 1, col).unwrap().set_text(val);
601 }
602 }
603 }
604
605 // End landscape, return to portrait
606 doc.add_paragraph("")
607 .section_break(SectionBreak::NextPage)
608 .section_landscape();
609
610 // =========================================================================
611 // PAGE 8: BACK TO PORTRAIT — styles, final notes
612 // =========================================================================
613 doc.add_paragraph("7. Custom Styles & Summary")
614 .style("Heading1");
615
616 doc.add_paragraph(
617 "This final page is back in portrait orientation after a section break. \
618 The document has demonstrated:",
619 );
620
621 doc.add_bullet_list_item(
622 "Page setup: size, margins, header/footer distance, gutter",
623 0,
624 );
625 doc.add_bullet_list_item("Document metadata: title, author, subject, keywords", 0);
626 doc.add_bullet_list_item("Headers and footers: text, images, different first page", 0);
627 doc.add_bullet_list_item("Background images: full-page behind text", 0);
628 doc.add_bullet_list_item(
629 "Text formatting: bold, italic, underline, strike, color, size, font",
630 0,
631 );
632 doc.add_bullet_list_item(
633 "Advanced run formatting: superscript, subscript, caps, spacing",
634 0,
635 );
636 doc.add_bullet_list_item(
637 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
638 0,
639 );
640 doc.add_bullet_list_item("Bullet and numbered lists with nesting levels", 0);
641 doc.add_bullet_list_item("Tab stops with dot/underscore leaders", 0);
642 doc.add_bullet_list_item(
643 "Tables: borders, shading, column spans, row spans, nesting",
644 0,
645 );
646 doc.add_bullet_list_item("Vertical alignment in table cells", 0);
647 doc.add_bullet_list_item("Inline images", 0);
648 doc.add_bullet_list_item("Placeholder replacement in body and table cells", 0);
649 doc.add_bullet_list_item("Content insertion at specific positions", 0);
650 doc.add_bullet_list_item(
651 "Section breaks with mixed portrait/landscape orientation",
652 0,
653 );
654
655 doc.add_paragraph("");
656 doc.add_paragraph("All features above were built entirely from scratch using the rdocx API.")
657 .alignment(Alignment::Center)
658 .shading("E2EFDA")
659 .border_all(BorderStyle::Single, 2, "00AA00");
660
661 doc.save(path).unwrap();
662}86fn generate_feature_showcase(_samples_dir: &Path) -> Document {
87 let mut doc = Document::new();
88
89 // ── Page Setup & Metadata ──
90 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
91 doc.set_margins(
92 Length::inches(1.0),
93 Length::inches(1.0),
94 Length::inches(1.0),
95 Length::inches(1.0),
96 );
97 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
98 doc.set_gutter(Length::twips(0));
99 doc.set_title("rdocx Feature Showcase");
100 doc.set_author("rdocx Sample Generator");
101 doc.set_subject("Comprehensive feature demonstration");
102 doc.set_keywords("rdocx, docx, rust, sample, showcase");
103
104 // Headers & Footers with different first page
105 doc.set_different_first_page(true);
106 doc.set_first_page_header("rdocx");
107 doc.set_first_page_footer("Feature Showcase — Cover Page");
108 doc.set_header("rdocx Feature Showcase");
109 doc.set_footer("Generated by rdocx");
110
111 // ── COVER PAGE ──
112 let bg = create_sample_png(612, 792, [20, 45, 90]);
113 doc.add_background_image(&bg, "cover_bg.png");
114
115 for _ in 0..3 {
116 doc.add_paragraph("");
117 }
118 {
119 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
120 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
121 }
122 {
123 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
124 p.add_run("Complete Feature Showcase")
125 .size(28.0)
126 .color("FFFFFF")
127 .italic(true);
128 }
129 doc.add_paragraph("");
130 {
131 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
132 p.add_run("Every feature of the rdocx Rust library")
133 .size(13.0)
134 .color("B0C4DE");
135 }
136 {
137 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
138 p.add_run("demonstrated in a single document.")
139 .size(13.0)
140 .color("B0C4DE");
141 }
142
143 // ── TABLE OF CONTENTS ──
144 doc.add_paragraph("").page_break_before(true);
145 doc.insert_toc(doc.content_count(), 3);
146
147 // ── SECTION 1: TEXT FORMATTING ──
148 doc.add_paragraph("").page_break_before(true);
149 doc.add_paragraph("1. Text Formatting").style("Heading1");
150
151 // Paragraph Alignment
152 doc.add_paragraph("1.1 Paragraph Alignment")
153 .style("Heading2");
154 doc.add_paragraph("Left-aligned paragraph (default).")
155 .alignment(Alignment::Left);
156 doc.add_paragraph("Center-aligned paragraph.")
157 .alignment(Alignment::Center);
158 doc.add_paragraph("Right-aligned paragraph.")
159 .alignment(Alignment::Right);
160 doc.add_paragraph(
161 "Justified paragraph — this text is long enough to demonstrate how justified alignment \
162 distributes extra space across word gaps so lines fill the full width of the text area.",
163 )
164 .alignment(Alignment::Justify);
165
166 doc.add_paragraph("");
167
168 // Run Formatting
169 doc.add_paragraph("1.2 Run Formatting").style("Heading2");
170 {
171 let mut p = doc.add_paragraph("");
172 p.add_run("Bold").bold(true);
173 p.add_run(" | ");
174 p.add_run("Italic").italic(true);
175 p.add_run(" | ");
176 p.add_run("Bold Italic").bold(true).italic(true);
177 p.add_run(" | ");
178 p.add_run("Underline").underline(true);
179 p.add_run(" | ");
180 p.add_run("Strikethrough").strike(true);
181 p.add_run(" | ");
182 p.add_run("Double Strike").double_strike(true);
183 }
184 {
185 let mut p = doc.add_paragraph("");
186 p.add_run("Red").color("FF0000");
187 p.add_run(" | ");
188 p.add_run("Blue").color("0000FF");
189 p.add_run(" | ");
190 p.add_run("Green").color("00AA00");
191 p.add_run(" | ");
192 p.add_run("Highlighted").highlight("FFFF00");
193 }
194 {
195 let mut p = doc.add_paragraph("");
196 p.add_run("8pt").size(8.0);
197 p.add_run(" | ");
198 p.add_run("11pt").size(11.0);
199 p.add_run(" | ");
200 p.add_run("16pt").size(16.0);
201 p.add_run(" | ");
202 p.add_run("24pt").size(24.0);
203 }
204 {
205 let mut p = doc.add_paragraph("");
206 p.add_run("Arial").font("Arial");
207 p.add_run(" | ");
208 p.add_run("Times New Roman").font("Times New Roman");
209 p.add_run(" | ");
210 p.add_run("Courier New").font("Courier New");
211 }
212 {
213 let mut p = doc.add_paragraph("");
214 p.add_run("H");
215 p.add_run("2").subscript();
216 p.add_run("O (subscript) | E=mc");
217 p.add_run("2").superscript();
218 p.add_run(" (superscript)");
219 }
220 {
221 let mut p = doc.add_paragraph("");
222 p.add_run("ALL CAPS").all_caps(true);
223 p.add_run(" | ");
224 p.add_run("Small Caps").small_caps(true);
225 p.add_run(" | ");
226 p.add_run("Expanded").character_spacing(Length::twips(40));
227 p.add_run(" | ");
228 p.add_run("Hidden text (not visible)").hidden(true);
229 }
230
231 doc.add_paragraph("");
232
233 // Underline Styles
234 doc.add_paragraph("1.3 Underline Styles").style("Heading2");
235 {
236 let mut p = doc.add_paragraph("");
237 p.add_run("Single ")
238 .underline_style(rdocx::UnderlineStyle::Single);
239 p.add_run("Double ")
240 .underline_style(rdocx::UnderlineStyle::Double);
241 p.add_run("Thick ")
242 .underline_style(rdocx::UnderlineStyle::Thick);
243 p.add_run("Dotted ")
244 .underline_style(rdocx::UnderlineStyle::Dotted);
245 p.add_run("Dash ")
246 .underline_style(rdocx::UnderlineStyle::Dash);
247 p.add_run("Wave ")
248 .underline_style(rdocx::UnderlineStyle::Wave);
249 }
250
251 // ── SECTION 2: PARAGRAPH FORMATTING ──
252 doc.add_paragraph("").page_break_before(true);
253 doc.add_paragraph("2. Paragraph Formatting")
254 .style("Heading1");
255
256 doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
257 doc.add_paragraph("Paragraph with light green shading")
258 .shading("E2EFDA");
259 doc.add_paragraph("Paragraph with bottom border")
260 .border_bottom(BorderStyle::Single, 6, "2E75B6");
261 doc.add_paragraph("Paragraph with all borders (red)")
262 .border_all(BorderStyle::Single, 4, "FF0000");
263
264 doc.add_paragraph("2.2 Indentation").style("Heading2");
265 doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
266 .indent_left(Length::inches(1.0))
267 .hanging_indent(Length::inches(0.5));
268 doc.add_paragraph("0.5-inch first-line indent on this paragraph")
269 .first_line_indent(Length::inches(0.5));
270 doc.add_paragraph("Both left and right indent (0.75 inches each)")
271 .indent_left(Length::inches(0.75))
272 .indent_right(Length::inches(0.75));
273
274 doc.add_paragraph("2.3 Spacing & Line Height")
275 .style("Heading2");
276 doc.add_paragraph("Extra space before (24pt) and after (12pt)")
277 .space_before(Length::pt(24.0))
278 .space_after(Length::pt(12.0));
279 doc.add_paragraph(
280 "Double line spacing paragraph. This text should have extra vertical space between \
281 lines to demonstrate line_spacing_multiple(2.0).",
282 )
283 .line_spacing_multiple(2.0);
284 doc.add_paragraph("Exact 20pt line spacing (fixed height).")
285 .line_spacing(20.0);
286
287 doc.add_paragraph("2.4 Pagination Controls")
288 .style("Heading2");
289 doc.add_paragraph("keep_with_next — this paragraph stays with the next")
290 .keep_with_next(true);
291 doc.add_paragraph("(This paragraph was kept with the one above.)");
292 doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
293 .keep_together(true);
294 doc.add_paragraph("widow_control — prevents widow/orphan lines")
295 .widow_control(true);
296
297 // ── SECTION 3: LISTS ──
298 doc.add_paragraph("").page_break_before(true);
299 doc.add_paragraph("3. Lists").style("Heading1");
300
301 doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
302 doc.add_bullet_list_item("First item", 0);
303 doc.add_bullet_list_item("Second item", 0);
304 doc.add_bullet_list_item("Nested level 1", 1);
305 doc.add_bullet_list_item("Nested level 2", 2);
306 doc.add_bullet_list_item("Back to level 1", 1);
307 doc.add_bullet_list_item("Third item", 0);
308
309 doc.add_paragraph("");
310
311 doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
312 doc.add_numbered_list_item("First numbered item", 0);
313 doc.add_numbered_list_item("Second numbered item", 0);
314 doc.add_numbered_list_item("Sub-item A", 1);
315 doc.add_numbered_list_item("Sub-item B", 1);
316 doc.add_numbered_list_item("Third numbered item", 0);
317
318 // ── SECTION 4: TAB STOPS ──
319 doc.add_paragraph("");
320 doc.add_paragraph("4. Tab Stops").style("Heading1");
321
322 doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
323 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
324 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
325 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
326 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
327 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
328
329 doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
330 doc.add_paragraph("Item\t\tPrice")
331 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
332 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
333 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
334 doc.add_paragraph("Widget\t\t$19.99")
335 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
336 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
337 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
338 doc.add_paragraph("Gadget\t\t$249.50")
339 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
340 .add_tab_stop_with_leader(
341 TabAlignment::Right,
342 Length::inches(4.0),
343 TabLeader::Underscore,
344 )
345 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
346
347 // ── SECTION 5: TABLES ──
348 doc.add_paragraph("").page_break_before(true);
349 doc.add_paragraph("5. Tables").style("Heading1");
350
351 doc.add_paragraph("5.1 Basic Table").style("Heading2");
352 {
353 let mut tbl = doc
354 .add_table(4, 3)
355 .borders(BorderStyle::Single, 4, "000000");
356 for col in 0..3 {
357 tbl.cell(0, col).unwrap().shading("2E75B6");
358 }
359 tbl.cell(0, 0).unwrap().set_text("Name");
360 tbl.cell(0, 1).unwrap().set_text("Role");
361 tbl.cell(0, 2).unwrap().set_text("Location");
362 tbl.cell(1, 0).unwrap().set_text("Walter White");
363 tbl.cell(1, 1).unwrap().set_text("CEO");
364 tbl.cell(1, 2).unwrap().set_text("Albuquerque");
365 tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
366 tbl.cell(2, 1).unwrap().set_text("CTO");
367 tbl.cell(2, 2).unwrap().set_text("Remote");
368 tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
369 tbl.cell(3, 1).unwrap().set_text("Security");
370 tbl.cell(3, 2).unwrap().set_text("Washington");
371 }
372
373 doc.add_paragraph("");
374
375 doc.add_paragraph("5.2 Column Span & Cell Shading")
376 .style("Heading2");
377 {
378 let mut tbl = doc
379 .add_table(3, 4)
380 .borders(BorderStyle::Single, 4, "000000")
381 .width_pct(100.0);
382 tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
383 tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
384 tbl.cell(1, 0).unwrap().set_text("Region");
385 tbl.cell(1, 0).unwrap().shading("D6E4F0");
386 tbl.cell(1, 1).unwrap().set_text("Q1");
387 tbl.cell(1, 1).unwrap().shading("D6E4F0");
388 tbl.cell(1, 2).unwrap().set_text("Q2");
389 tbl.cell(1, 2).unwrap().shading("D6E4F0");
390 tbl.cell(1, 3).unwrap().set_text("Total");
391 tbl.cell(1, 3).unwrap().shading("D6E4F0");
392 tbl.cell(2, 0).unwrap().set_text("Americas");
393 tbl.cell(2, 1).unwrap().set_text("$2.4M");
394 tbl.cell(2, 2).unwrap().set_text("$2.7M");
395 tbl.cell(2, 3).unwrap().set_text("$5.1M");
396 }
397
398 doc.add_paragraph("");
399
400 doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
401 {
402 let mut tbl = doc
403 .add_table(4, 3)
404 .borders(BorderStyle::Single, 4, "000000");
405 tbl.cell(0, 0).unwrap().set_text("Category");
406 tbl.cell(0, 0).unwrap().shading("E2EFDA");
407 tbl.cell(0, 1).unwrap().set_text("Item");
408 tbl.cell(0, 1).unwrap().shading("E2EFDA");
409 tbl.cell(0, 2).unwrap().set_text("Price");
410 tbl.cell(0, 2).unwrap().shading("E2EFDA");
411 tbl.cell(1, 0).unwrap().set_text("Hardware");
412 tbl.cell(1, 0).unwrap().v_merge_restart();
413 tbl.cell(1, 1).unwrap().set_text("Laptop");
414 tbl.cell(1, 2).unwrap().set_text("$1,200");
415 tbl.cell(2, 0).unwrap().v_merge_continue();
416 tbl.cell(2, 1).unwrap().set_text("Monitor");
417 tbl.cell(2, 2).unwrap().set_text("$450");
418 tbl.cell(3, 0).unwrap().set_text("Software");
419 tbl.cell(3, 1).unwrap().set_text("IDE License");
420 tbl.cell(3, 2).unwrap().set_text("$200/yr");
421 }
422
423 doc.add_paragraph("");
424
425 doc.add_paragraph("5.4 Nested Table").style("Heading2");
426 {
427 let mut tbl = doc
428 .add_table(2, 2)
429 .borders(BorderStyle::Single, 6, "2E75B6");
430 tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
431 tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
432 tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
433 {
434 let mut cell = tbl.cell(1, 1).unwrap();
435 cell.set_text("Nested table below:");
436 let mut nested = cell
437 .add_table(2, 2)
438 .borders(BorderStyle::Single, 2, "FF6600");
439 nested.cell(0, 0).unwrap().set_text("A");
440 nested.cell(0, 1).unwrap().set_text("B");
441 nested.cell(1, 0).unwrap().set_text("C");
442 nested.cell(1, 1).unwrap().set_text("D");
443 }
444 }
445
446 doc.add_paragraph("");
447
448 doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
449 .style("Heading2");
450 {
451 let mut tbl = doc
452 .add_table(2, 3)
453 .borders(BorderStyle::Single, 4, "666666");
454 tbl.row(0).unwrap().height(Length::pt(50.0)).header();
455 tbl.cell(0, 0).unwrap().set_text("Top");
456 tbl.cell(0, 0)
457 .unwrap()
458 .vertical_alignment(VerticalAlignment::Top)
459 .shading("FFF2CC");
460 tbl.cell(0, 1).unwrap().set_text("Center");
461 tbl.cell(0, 1)
462 .unwrap()
463 .vertical_alignment(VerticalAlignment::Center)
464 .shading("D9E2F3");
465 tbl.cell(0, 2).unwrap().set_text("Bottom");
466 tbl.cell(0, 2)
467 .unwrap()
468 .vertical_alignment(VerticalAlignment::Bottom)
469 .shading("E2EFDA");
470 tbl.row(1).unwrap().cant_split();
471 tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
472 tbl.cell(1, 0).unwrap().no_wrap();
473 tbl.cell(1, 1).unwrap().set_text("Fixed width");
474 tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
475 tbl.cell(1, 2).unwrap().set_text("Normal");
476 }
477
478 // ── SECTION 6: IMAGES ──
479 doc.add_paragraph("").page_break_before(true);
480 doc.add_paragraph("6. Images").style("Heading1");
481
482 doc.add_paragraph("6.1 Inline Image").style("Heading2");
483 doc.add_paragraph("A blue gradient image below:");
484 let img = create_sample_png(200, 50, [0, 80, 200]);
485 doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
486
487 doc.add_paragraph("");
488 doc.add_paragraph("6.2 Header Image").style("Heading2");
489 let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
490 doc.set_header_image(
491 &hdr_img,
492 "header_logo.png",
493 Length::inches(2.0),
494 Length::inches(0.2),
495 );
496 doc.add_paragraph("The header now contains an inline image (check the top of this page).");
497
498 doc.add_paragraph("");
499 doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
500
501 // ── SECTION 7: CONTENT MANIPULATION ──
502 doc.add_paragraph("").page_break_before(true);
503 doc.add_paragraph("7. Content Manipulation")
504 .style("Heading1");
505
506 doc.add_paragraph("7.1 Placeholder Replacement")
507 .style("Heading2");
508 doc.add_paragraph("Customer: {{customer}}");
509 doc.add_paragraph("Date: {{date}}");
510 doc.add_paragraph("Reference: {{ref_number}}");
511 {
512 let mut tbl = doc
513 .add_table(2, 2)
514 .borders(BorderStyle::Single, 4, "000000");
515 tbl.cell(0, 0).unwrap().set_text("Project");
516 tbl.cell(0, 0).unwrap().shading("D6E4F0");
517 tbl.cell(0, 1).unwrap().set_text("{{project}}");
518 tbl.cell(1, 0).unwrap().set_text("Status");
519 tbl.cell(1, 0).unwrap().shading("D6E4F0");
520 tbl.cell(1, 1).unwrap().set_text("{{status}}");
521 }
522
523 let mut replacements = HashMap::new();
524 replacements.insert("{{customer}}", "Tensorbee Inc.");
525 replacements.insert("{{date}}", "February 22, 2026");
526 replacements.insert("{{ref_number}}", "TB-2026-001");
527 replacements.insert("{{project}}", "Infrastructure Upgrade");
528 replacements.insert("{{status}}", "In Progress");
529 let n = doc.replace_all(&replacements);
530 doc.add_paragraph(&format!("({n} placeholders replaced above)"));
531
532 doc.add_paragraph("");
533
534 doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
535 doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
536 let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
537 doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
538
539 doc.add_paragraph("");
540
541 doc.add_paragraph("7.3 Content Insertion").style("Heading2");
542 doc.add_paragraph("Section A: First content.");
543 doc.add_paragraph("Section C: Third content.");
544 if let Some(idx) = doc.find_content_index("Section C") {
545 doc.insert_paragraph(
546 idx,
547 "Section B: Inserted between A and C via find_content_index().",
548 );
549 }
550
551 // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
552 doc.add_paragraph("").section_break(SectionBreak::NextPage);
553 doc.add_paragraph("8. Section Breaks & Orientation")
554 .style("Heading1");
555 doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
556
557 {
558 let mut tbl = doc
559 .add_table(3, 7)
560 .borders(BorderStyle::Single, 4, "2E75B6");
561 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
562 for (c, h) in headers.iter().enumerate() {
563 tbl.cell(0, c).unwrap().set_text(h);
564 tbl.cell(0, c).unwrap().shading("2E75B6");
565 }
566 let data = [
567 [
568 "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
569 ],
570 [
571 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
572 ],
573 ];
574 for (r, row) in data.iter().enumerate() {
575 for (c, v) in row.iter().enumerate() {
576 tbl.cell(r + 1, c).unwrap().set_text(v);
577 }
578 }
579 }
580
581 doc.add_paragraph("")
582 .section_break(SectionBreak::NextPage)
583 .section_landscape();
584
585 // ── SECTION 9: CUSTOM STYLES ──
586 doc.add_paragraph("9. Custom Styles").style("Heading1");
587 doc.add_style(
588 StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
589 .based_on("Normal")
590 .paragraph_properties({
591 let mut ppr = rdocx_oxml::properties::CT_PPr::default();
592 ppr.shading = Some(rdocx_oxml::properties::CT_Shd {
593 val: "clear".to_string(),
594 color: None,
595 fill: Some("FFF2CC".to_string()),
596 });
597 ppr
598 })
599 .run_properties({
600 let mut rpr = rdocx_oxml::properties::CT_RPr::default();
601 rpr.bold = Some(true);
602 rpr.color = Some("C45911".to_string());
603 rpr
604 }),
605 );
606 doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
607 .style("CustomHighlight");
608
609 doc.add_paragraph("");
610
611 // ── SECTION 10: DOCUMENT INTELLIGENCE ──
612 doc.add_paragraph("10. Document Intelligence API")
613 .style("Heading1");
614
615 let wc = doc.word_count();
616 let hc = doc.headings().len();
617 let ic = doc.images().len();
618 let lc = doc.links().len();
619 doc.add_paragraph(&format!("Word count: {wc}"));
620 doc.add_paragraph(&format!("Heading count: {hc}"));
621 doc.add_paragraph(&format!("Image count: {ic}"));
622 doc.add_paragraph(&format!("Link count: {lc}"));
623
624 let outline = doc.document_outline();
625 doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
626
627 let issues = doc.audit_accessibility();
628 doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
629 for issue in &issues {
630 doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
631 }
632
633 // ── SECTION 11: DOCUMENT MERGING ──
634 doc.add_paragraph("");
635 doc.add_paragraph("11. Document Merging").style("Heading1");
636 {
637 let mut other = Document::new();
638 other.add_paragraph("This paragraph was merged from another document using append().");
639 doc.append(&other);
640 }
641
642 doc.add_paragraph("");
643
644 // ── SUMMARY ──
645 doc.add_paragraph("Summary of Demonstrated Features")
646 .style("Heading1");
647 let features = [
648 "Page setup: size, margins, header/footer distance, gutter",
649 "Document metadata: title, author, subject, keywords",
650 "Headers and footers: text, images, different first page",
651 "Background images (full-page behind text)",
652 "Table of Contents generation with bookmarks",
653 "Text formatting: bold, italic, underline styles, strike, color, size, font",
654 "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
655 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
656 "Pagination controls: keep-with-next, keep-together, widow control",
657 "Bullet and numbered lists with nesting",
658 "Tab stops with dot/underscore/hyphen leaders",
659 "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
660 "Row properties: header rows, exact height, cant-split",
661 "Cell properties: width, no-wrap, vertical alignment",
662 "Inline images and header images",
663 "Placeholder replacement (single and batch)",
664 "Regex-based find and replace",
665 "Content insertion at specific positions",
666 "Section breaks with mixed portrait/landscape",
667 "Custom paragraph and character styles",
668 "Document intelligence: word count, headings, outline, images, links",
669 "Accessibility audit",
670 "Document merging (append)",
671 "Export: DOCX, PDF, HTML, Markdown",
672 ];
673 for f in &features {
674 doc.add_bullet_list_item(f, 0);
675 }
676 doc.add_paragraph("");
677 doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
678 .alignment(Alignment::Center)
679 .shading("E2EFDA")
680 .border_all(BorderStyle::Single, 2, "00AA00");
681
682 doc
683}
684
685// =============================================================================
686// 2. PROPOSAL DOCUMENT — Deep navy + gold scheme
687// =============================================================================
688fn generate_proposal(_samples_dir: &Path) -> Document {
689 let mut doc = Document::new();
690
691 // Colors: Navy #1B2A4A, Gold #C5922E, Light #F4F1EB
692 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
693 doc.set_margins(
694 Length::inches(1.0),
695 Length::inches(1.0),
696 Length::inches(1.0),
697 Length::inches(1.0),
698 );
699 doc.set_title("AI Platform Modernization Proposal");
700 doc.set_author("Walter White");
701 doc.set_subject("Technology Proposal");
702 doc.set_keywords("proposal, AI, modernization, Tensorbee");
703
704 // Banner header
705 let logo_img = create_logo_png(220, 48);
706 let banner = build_header_banner_xml(
707 "rId1",
708 &BannerOpts {
709 bg_color: "1B2A4A",
710 banner_width: 7772400,
711 banner_height: 914400,
712 logo_width: 2011680,
713 logo_height: 438912,
714 logo_x_offset: 295125,
715 logo_y_offset: 237744,
716 },
717 );
718 doc.set_raw_header_with_images(
719 banner,
720 &[("rId1", &logo_img, "logo.png")],
721 rdocx_oxml::header_footer::HdrFtrType::Default,
722 );
723 doc.set_different_first_page(true);
724 let cover_banner = build_header_banner_xml(
725 "rId1",
726 &BannerOpts {
727 bg_color: "C5922E",
728 banner_width: 7772400,
729 banner_height: 914400,
730 logo_width: 2011680,
731 logo_height: 438912,
732 logo_x_offset: 295125,
733 logo_y_offset: 237744,
734 },
735 );
736 doc.set_raw_header_with_images(
737 cover_banner,
738 &[("rId1", &logo_img, "logo.png")],
739 rdocx_oxml::header_footer::HdrFtrType::First,
740 );
741 doc.set_margins(
742 Length::twips(2292),
743 Length::twips(1440),
744 Length::twips(1440),
745 Length::twips(1440),
746 );
747 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
748 doc.set_footer("Tensorbee — Confidential");
749
750 // Custom styles
751 doc.add_style(
752 StyleBuilder::paragraph("ProposalTitle", "Proposal Title")
753 .based_on("Normal")
754 .run_properties({
755 let mut rpr = rdocx_oxml::properties::CT_RPr::default();
756 rpr.bold = Some(true);
757 rpr.font_ascii = Some("Georgia".to_string());
758 rpr.font_hansi = Some("Georgia".to_string());
759 rpr.sz = Some(rdocx_oxml::HalfPoint(56)); // half-points → 28pt
760 rpr.color = Some("1B2A4A".to_string());
761 rpr
762 }),
763 );
764
765 // ── Cover Page ──
766 for _ in 0..4 {
767 doc.add_paragraph("");
768 }
769 doc.add_paragraph("AI Platform Modernization")
770 .style("ProposalTitle")
771 .alignment(Alignment::Center);
772 doc.add_paragraph("")
773 .alignment(Alignment::Center)
774 .border_bottom(BorderStyle::Single, 8, "C5922E");
775 {
776 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
777 p.add_run("Prepared for Global Financial Corp.")
778 .size(14.0)
779 .color("666666")
780 .italic(true);
781 }
782 {
783 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
784 p.add_run("Prepared by: Walter White, CEO — Tensorbee")
785 .size(12.0)
786 .color("888888");
787 }
788 {
789 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
790 p.add_run("February 22, 2026").size(12.0).color("888888");
791 }
792
793 // ── TOC ──
794 doc.add_paragraph("").page_break_before(true);
795 doc.add_paragraph("Table of Contents").style("Heading1");
796 // We'll add a manual-style TOC since the insert_toc goes at a specific position
797 doc.insert_toc(doc.content_count(), 2);
798
799 // ── Executive Summary ──
800 doc.add_paragraph("").page_break_before(true);
801 doc.add_paragraph("Executive Summary").style("Heading1");
802 doc.add_paragraph(
803 "Tensorbee proposes a comprehensive modernization of Global Financial Corp's AI platform, \
804 transitioning from legacy batch-processing systems to a real-time inference architecture. \
805 This transformation will reduce model deployment time from weeks to hours, improve \
806 prediction accuracy by an estimated 23%, and deliver $4.2M in annual operational savings.",
807 )
808 .first_line_indent(Length::inches(0.3));
809 doc.add_paragraph(
810 "The project spans three phases over 18 months, with a total investment of $2.8M. \
811 Tensorbee brings deep expertise in ML infrastructure, having successfully completed \
812 similar transformations for three Fortune 500 financial institutions.",
813 )
814 .first_line_indent(Length::inches(0.3));
815
816 // Key metrics table
817 doc.add_paragraph("");
818 {
819 let mut tbl = doc
820 .add_table(5, 2)
821 .borders(BorderStyle::Single, 4, "1B2A4A")
822 .width_pct(80.0);
823 tbl.cell(0, 0).unwrap().set_text("Key Metric");
824 tbl.cell(0, 0).unwrap().shading("1B2A4A");
825 tbl.cell(0, 1).unwrap().set_text("Value");
826 tbl.cell(0, 1).unwrap().shading("1B2A4A");
827 let rows = [
828 ("Total Investment", "$2.8M"),
829 ("Annual Savings", "$4.2M"),
830 ("ROI (Year 1)", "150%"),
831 ("Timeline", "18 months"),
832 ];
833 for (i, (k, v)) in rows.iter().enumerate() {
834 tbl.cell(i + 1, 0).unwrap().set_text(k);
835 if i % 2 == 0 {
836 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
837 }
838 tbl.cell(i + 1, 1).unwrap().set_text(v);
839 if i % 2 == 0 {
840 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
841 }
842 }
843 }
844
845 // ── Problem Statement ──
846 doc.add_paragraph("").page_break_before(true);
847 doc.add_paragraph("Problem Statement").style("Heading1");
848 doc.add_paragraph(
849 "Global Financial Corp's current AI infrastructure faces three critical challenges:",
850 );
851 doc.add_numbered_list_item("Deployment Latency — New models take 3-4 weeks to deploy to production, compared to the industry benchmark of 2-3 days.", 0);
852 doc.add_numbered_list_item("Scalability Constraints — The batch processing architecture cannot handle real-time inference demands during peak trading hours.", 0);
853 doc.add_numbered_list_item("Technical Debt — Legacy Python 2.7 codebases and manual deployment scripts create reliability risks and slow iteration speed.", 0);
854
855 // ── Proposed Solution ──
856 doc.add_paragraph("").page_break_before(true);
857 doc.add_paragraph("Proposed Solution").style("Heading1");
858
859 doc.add_paragraph("Phase 1: Foundation (Months 1-6)")
860 .style("Heading2");
861 doc.add_bullet_list_item("Deploy Kubernetes-based ML serving infrastructure", 0);
862 doc.add_bullet_list_item("Implement CI/CD pipeline for model deployment", 0);
863 doc.add_bullet_list_item("Migrate top 5 production models to new platform", 0);
864
865 doc.add_paragraph("Phase 2: Expansion (Months 7-12)")
866 .style("Heading2");
867 doc.add_bullet_list_item(
868 "Real-time feature engineering pipeline (Apache Kafka + Flink)",
869 0,
870 );
871 doc.add_bullet_list_item("A/B testing framework for model variants", 0);
872 doc.add_bullet_list_item("Automated model monitoring and drift detection", 0);
873
874 doc.add_paragraph("Phase 3: Optimization (Months 13-18)")
875 .style("Heading2");
876 doc.add_bullet_list_item("GPU inference optimization (TensorRT/ONNX Runtime)", 0);
877 doc.add_bullet_list_item("Multi-region deployment for latency reduction", 0);
878 doc.add_bullet_list_item(
879 "Self-service model deployment portal for data science teams",
880 0,
881 );
882
883 // ── Budget ──
884 doc.add_paragraph("Budget Breakdown").style("Heading1");
885 {
886 let mut tbl = doc
887 .add_table(6, 3)
888 .borders(BorderStyle::Single, 4, "1B2A4A")
889 .width_pct(100.0);
890 tbl.cell(0, 0).unwrap().set_text("Category");
891 tbl.cell(0, 0).unwrap().shading("1B2A4A");
892 tbl.cell(0, 1).unwrap().set_text("Cost");
893 tbl.cell(0, 1).unwrap().shading("1B2A4A");
894 tbl.cell(0, 2).unwrap().set_text("Notes");
895 tbl.cell(0, 2).unwrap().shading("1B2A4A");
896 let items = [
897 (
898 "Infrastructure",
899 "$450,000",
900 "Cloud compute + GPU instances",
901 ),
902 ("Engineering Services", "$1,600,000", "12 FTE x 18 months"),
903 (
904 "Software Licenses",
905 "$350,000",
906 "Kafka, monitoring, CI/CD tools",
907 ),
908 (
909 "Training & Enablement",
910 "$200,000",
911 "Team training, documentation",
912 ),
913 ("Contingency (10%)", "$200,000", "Risk buffer"),
914 ];
915 for (i, (cat, cost, note)) in items.iter().enumerate() {
916 tbl.cell(i + 1, 0).unwrap().set_text(cat);
917 tbl.cell(i + 1, 1).unwrap().set_text(cost);
918 tbl.cell(i + 1, 2).unwrap().set_text(note);
919 if i % 2 == 0 {
920 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
921 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
922 tbl.cell(i + 1, 2).unwrap().shading("F4F1EB");
923 }
924 }
925 }
926
927 // ── Closing ──
928 doc.add_paragraph("");
929 doc.add_paragraph("Next Steps").style("Heading1");
930 doc.add_numbered_list_item(
931 "Schedule technical deep-dive with GFC engineering team (Week 1)",
932 0,
933 );
934 doc.add_numbered_list_item("Finalize scope and sign SOW (Week 2-3)", 0);
935 doc.add_numbered_list_item("Kick off Phase 1 with joint planning session (Week 4)", 0);
936
937 doc.add_paragraph("");
938 {
939 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
940 p.add_run("Walter White")
941 .bold(true)
942 .size(14.0)
943 .color("1B2A4A");
944 }
945 {
946 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
947 p.add_run("CEO, Tensorbee | walter@tensorbee.com")
948 .size(10.0)
949 .color("888888");
950 }
951
952 doc
953}
954
955// =============================================================================
956// 3. QUOTE / BILL OF MATERIALS — Teal + orange scheme
957// =============================================================================
958fn generate_quote(_samples_dir: &Path) -> Document {
959 let mut doc = Document::new();
960
961 // Colors: Teal #008B8B, Dark #1A3C3C, Orange #E07020, Light #F0F8F8
962 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
963 doc.set_margins(
964 Length::inches(0.75),
965 Length::inches(0.75),
966 Length::inches(0.75),
967 Length::inches(0.75),
968 );
969 doc.set_title("Quotation QT-2026-0147");
970 doc.set_author("Walter White");
971 doc.set_keywords("quote, BOM, Tensorbee");
972
973 doc.set_header("Tensorbee — Quotation");
974 doc.set_footer("QT-2026-0147 | Page");
975
976 // ── Header Block ──
977 {
978 let mut p = doc.add_paragraph("").alignment(Alignment::Left);
979 p.add_run("TENSORBEE")
980 .bold(true)
981 .size(28.0)
982 .color("008B8B")
983 .font("Helvetica");
984 }
985 doc.add_paragraph("123 Innovation Drive, Suite 400")
986 .alignment(Alignment::Left);
987 doc.add_paragraph("San Francisco, CA 94105 | +1 (415) 555-0199")
988 .alignment(Alignment::Left);
989 doc.add_paragraph("")
990 .border_bottom(BorderStyle::Single, 8, "008B8B");
991
992 // Quote meta
993 doc.add_paragraph("");
994 {
995 let mut tbl = doc.add_table(4, 4).width_pct(100.0);
996 tbl.cell(0, 0).unwrap().set_text("QUOTATION");
997 tbl.cell(0, 0).unwrap().shading("008B8B").grid_span(2);
998 tbl.cell(0, 2).unwrap().set_text("DATE");
999 tbl.cell(0, 2).unwrap().shading("008B8B");
1000 tbl.cell(0, 3).unwrap().set_text("VALID UNTIL");
1001 tbl.cell(0, 3).unwrap().shading("008B8B");
1002 tbl.cell(1, 0).unwrap().set_text("Quote #:");
1003 tbl.cell(1, 1).unwrap().set_text("QT-2026-0147");
1004 tbl.cell(1, 2).unwrap().set_text("Feb 22, 2026");
1005 tbl.cell(1, 3).unwrap().set_text("Mar 22, 2026");
1006 tbl.cell(2, 0).unwrap().set_text("Prepared by:");
1007 tbl.cell(2, 1).unwrap().set_text("Walter White");
1008 tbl.cell(2, 2).unwrap().set_text("Payment:");
1009 tbl.cell(2, 3).unwrap().set_text("Net 30");
1010 tbl.cell(3, 0).unwrap().set_text("Customer:");
1011 tbl.cell(3, 1).unwrap().set_text("Meridian Dynamics LLC");
1012 tbl.cell(3, 2).unwrap().set_text("Currency:");
1013 tbl.cell(3, 3).unwrap().set_text("USD");
1014 }
1015
1016 doc.add_paragraph("");
1017
1018 // ── Bill of Materials ──
1019 {
1020 let mut p = doc.add_paragraph("");
1021 p.add_run("Bill of Materials")
1022 .bold(true)
1023 .size(16.0)
1024 .color("1A3C3C");
1025 }
1026 doc.add_paragraph("");
1027
1028 {
1029 let mut tbl = doc
1030 .add_table(10, 6)
1031 .borders(BorderStyle::Single, 2, "008B8B")
1032 .width_pct(100.0)
1033 .layout_fixed();
1034 // Headers
1035 let hdrs = [
1036 "#",
1037 "Part Number",
1038 "Description",
1039 "Qty",
1040 "Unit Price",
1041 "Total",
1042 ];
1043 for (c, h) in hdrs.iter().enumerate() {
1044 tbl.cell(0, c).unwrap().set_text(h);
1045 tbl.cell(0, c).unwrap().shading("008B8B");
1046 }
1047
1048 let items: Vec<(&str, &str, &str, &str, &str)> = vec![
1049 (
1050 "TB-GPU-A100",
1051 "NVIDIA A100 80GB GPU",
1052 "4",
1053 "$12,500.00",
1054 "$50,000.00",
1055 ),
1056 (
1057 "TB-SRV-R750",
1058 "Dell R750xa Server Chassis",
1059 "2",
1060 "$8,200.00",
1061 "$16,400.00",
1062 ),
1063 (
1064 "TB-RAM-256G",
1065 "256GB DDR5 ECC Memory Module",
1066 "8",
1067 "$890.00",
1068 "$7,120.00",
1069 ),
1070 (
1071 "TB-SSD-3840",
1072 "3.84TB NVMe U.2 SSD",
1073 "8",
1074 "$1,150.00",
1075 "$9,200.00",
1076 ),
1077 (
1078 "TB-NET-CX7",
1079 "ConnectX-7 200GbE NIC",
1080 "4",
1081 "$1,800.00",
1082 "$7,200.00",
1083 ),
1084 (
1085 "TB-SW-48P",
1086 "48-Port 100GbE Switch",
1087 "1",
1088 "$22,000.00",
1089 "$22,000.00",
1090 ),
1091 (
1092 "TB-CAB-RACK",
1093 "42U Server Rack + PDU",
1094 "1",
1095 "$4,500.00",
1096 "$4,500.00",
1097 ),
1098 (
1099 "TB-SVC-INST",
1100 "Installation & Configuration",
1101 "1",
1102 "$8,500.00",
1103 "$8,500.00",
1104 ),
1105 (
1106 "TB-SVC-SUPP",
1107 "3-Year Premium Support",
1108 "1",
1109 "$15,000.00",
1110 "$15,000.00",
1111 ),
1112 ];
1113
1114 for (i, (pn, desc, qty, unit, total)) in items.iter().enumerate() {
1115 let row = i + 1;
1116 tbl.cell(row, 0).unwrap().set_text(&format!("{}", i + 1));
1117 tbl.cell(row, 1).unwrap().set_text(pn);
1118 tbl.cell(row, 2).unwrap().set_text(desc);
1119 tbl.cell(row, 3).unwrap().set_text(qty);
1120 tbl.cell(row, 4).unwrap().set_text(unit);
1121 tbl.cell(row, 5).unwrap().set_text(total);
1122 if i % 2 == 0 {
1123 for c in 0..6 {
1124 tbl.cell(row, c).unwrap().shading("F0F8F8");
1125 }
1126 }
1127 }
1128 }
1129
1130 doc.add_paragraph("");
1131
1132 // ── Totals ──
1133 {
1134 let mut tbl = doc
1135 .add_table(4, 2)
1136 .borders(BorderStyle::Single, 2, "008B8B")
1137 .width(Length::inches(3.5))
1138 .alignment(Alignment::Right);
1139 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1140 tbl.cell(0, 1).unwrap().set_text("$139,920.00");
1141 tbl.cell(1, 0).unwrap().set_text("Shipping & Handling");
1142 tbl.cell(1, 1).unwrap().set_text("$2,500.00");
1143 tbl.cell(2, 0).unwrap().set_text("Tax (8.625%)");
1144 tbl.cell(2, 1).unwrap().set_text("$12,068.10");
1145 tbl.cell(3, 0).unwrap().set_text("TOTAL");
1146 tbl.cell(3, 0).unwrap().shading("E07020");
1147 tbl.cell(3, 1).unwrap().set_text("$154,488.10");
1148 tbl.cell(3, 1).unwrap().shading("E07020");
1149 }
1150
1151 doc.add_paragraph("");
1152
1153 // ── Terms & Conditions ──
1154 {
1155 let mut p = doc.add_paragraph("");
1156 p.add_run("Terms & Conditions")
1157 .bold(true)
1158 .size(14.0)
1159 .color("1A3C3C");
1160 }
1161 doc.add_numbered_list_item(
1162 "This quotation is valid for 30 calendar days from the date of issue.",
1163 0,
1164 );
1165 doc.add_numbered_list_item(
1166 "All prices are in USD and exclusive of applicable taxes unless stated.",
1167 0,
1168 );
1169 doc.add_numbered_list_item("Standard lead time is 4-6 weeks from PO receipt.", 0);
1170 doc.add_numbered_list_item(
1171 "Payment terms: Net 30 from invoice date. 2% discount for payment within 10 days.",
1172 0,
1173 );
1174 doc.add_numbered_list_item(
1175 "Warranty: 3-year manufacturer warranty on all hardware components.",
1176 0,
1177 );
1178 doc.add_numbered_list_item(
1179 "Returns subject to 15% restocking fee if initiated after 14 days.",
1180 0,
1181 );
1182
1183 doc.add_paragraph("");
1184 doc.add_paragraph("")
1185 .border_bottom(BorderStyle::Single, 4, "008B8B");
1186 doc.add_paragraph("");
1187 {
1188 let mut p = doc.add_paragraph("");
1189 p.add_run("Accepted by: ").bold(true);
1190 p.add_run("___________________________ Date: ___________");
1191 }
1192 {
1193 let mut p = doc.add_paragraph("");
1194 p.add_run("Print Name: ").bold(true);
1195 p.add_run("___________________________ Title: ___________");
1196 }
1197
1198 doc
1199}
1200
1201// =============================================================================
1202// 4. INVOICE — Crimson + charcoal scheme
1203// =============================================================================
1204fn generate_invoice(_samples_dir: &Path) -> Document {
1205 let mut doc = Document::new();
1206
1207 // Colors: Crimson #B22222, Charcoal #333333, Light #FAF0F0
1208 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1209 doc.set_margins(
1210 Length::inches(0.75),
1211 Length::inches(0.75),
1212 Length::inches(0.75),
1213 Length::inches(0.75),
1214 );
1215 doc.set_title("Invoice INV-2026-0392");
1216 doc.set_author("Walter White");
1217 doc.set_keywords("invoice, Tensorbee");
1218
1219 doc.set_footer("Tensorbee — Thank you for your business!");
1220
1221 // ── Company & Invoice Header ──
1222 {
1223 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1224 // Company name left, INVOICE right
1225 {
1226 let mut cell = tbl.cell(0, 0).unwrap();
1227 let mut p = cell.add_paragraph("");
1228 p.add_run("TENSORBEE")
1229 .bold(true)
1230 .size(32.0)
1231 .color("B22222")
1232 .font("Helvetica");
1233 }
1234 {
1235 let mut cell = tbl.cell(0, 1).unwrap();
1236 let mut p = cell.add_paragraph("");
1237 p.add_run("INVOICE").bold(true).size(32.0).color("333333");
1238 }
1239 tbl.cell(1, 0)
1240 .unwrap()
1241 .set_text("123 Innovation Drive, Suite 400");
1242 tbl.cell(1, 1).unwrap().set_text("Invoice #: INV-2026-0392");
1243 tbl.cell(2, 0).unwrap().set_text("San Francisco, CA 94105");
1244 tbl.cell(2, 1).unwrap().set_text("Date: February 22, 2026");
1245 tbl.cell(3, 0).unwrap().set_text("walter@tensorbee.com");
1246 tbl.cell(3, 1).unwrap().set_text("Due Date: March 24, 2026");
1247 }
1248
1249 doc.add_paragraph("")
1250 .border_bottom(BorderStyle::Thick, 8, "B22222");
1251 doc.add_paragraph("");
1252
1253 // ── Bill To / Ship To ──
1254 {
1255 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1256 tbl.cell(0, 0).unwrap().set_text("BILL TO");
1257 tbl.cell(0, 0).unwrap().shading("B22222");
1258 tbl.cell(0, 1).unwrap().set_text("SHIP TO");
1259 tbl.cell(0, 1).unwrap().shading("B22222");
1260 tbl.cell(1, 0).unwrap().set_text("Meridian Dynamics LLC");
1261 tbl.cell(1, 1).unwrap().set_text("Meridian Dynamics LLC");
1262 tbl.cell(2, 0).unwrap().set_text("456 Enterprise Blvd");
1263 tbl.cell(2, 1).unwrap().set_text("Attn: Server Room B");
1264 tbl.cell(3, 0).unwrap().set_text("Austin, TX 78701");
1265 tbl.cell(3, 1)
1266 .unwrap()
1267 .set_text("456 Enterprise Blvd, Austin, TX 78701");
1268 }
1269
1270 doc.add_paragraph("");
1271
1272 // ── Line Items ──
1273 {
1274 let mut tbl = doc
1275 .add_table(8, 5)
1276 .borders(BorderStyle::Single, 2, "B22222")
1277 .width_pct(100.0);
1278 let hdrs = ["Description", "Qty", "Unit Price", "Tax", "Amount"];
1279 for (c, h) in hdrs.iter().enumerate() {
1280 tbl.cell(0, c).unwrap().set_text(h);
1281 tbl.cell(0, c).unwrap().shading("B22222");
1282 }
1283 let items = [
1284 (
1285 "ML Infrastructure Setup — Phase 1",
1286 "1",
1287 "$45,000.00",
1288 "$3,881.25",
1289 "$48,881.25",
1290 ),
1291 (
1292 "Data Pipeline Development (160 hrs)",
1293 "160",
1294 "$225.00",
1295 "$3,105.00",
1296 "$39,105.00",
1297 ),
1298 (
1299 "GPU Cluster Configuration",
1300 "1",
1301 "$12,000.00",
1302 "$1,035.00",
1303 "$13,035.00",
1304 ),
1305 (
1306 "API Gateway Implementation",
1307 "1",
1308 "$18,500.00",
1309 "$1,595.63",
1310 "$20,095.63",
1311 ),
1312 (
1313 "Load Testing & QA (80 hrs)",
1314 "80",
1315 "$195.00",
1316 "$1,345.50",
1317 "$16,945.50",
1318 ),
1319 (
1320 "Documentation & Training",
1321 "1",
1322 "$8,000.00",
1323 "$690.00",
1324 "$8,690.00",
1325 ),
1326 (
1327 "Project Management (3 months)",
1328 "3",
1329 "$6,500.00",
1330 "$1,679.63",
1331 "$21,179.63",
1332 ),
1333 ];
1334 for (i, (desc, qty, unit, tax, amt)) in items.iter().enumerate() {
1335 let r = i + 1;
1336 tbl.cell(r, 0).unwrap().set_text(desc);
1337 tbl.cell(r, 1).unwrap().set_text(qty);
1338 tbl.cell(r, 2).unwrap().set_text(unit);
1339 tbl.cell(r, 3).unwrap().set_text(tax);
1340 tbl.cell(r, 4).unwrap().set_text(amt);
1341 if i % 2 == 0 {
1342 for c in 0..5 {
1343 tbl.cell(r, c).unwrap().shading("FAF0F0");
1344 }
1345 }
1346 }
1347 }
1348
1349 doc.add_paragraph("");
1350
1351 // ── Totals ──
1352 {
1353 let mut tbl = doc
1354 .add_table(4, 2)
1355 .borders(BorderStyle::Single, 2, "B22222")
1356 .width(Length::inches(3.0))
1357 .alignment(Alignment::Right);
1358 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1359 tbl.cell(0, 1).unwrap().set_text("$154,600.00");
1360 tbl.cell(1, 0).unwrap().set_text("Tax (8.625%)");
1361 tbl.cell(1, 1).unwrap().set_text("$13,334.25");
1362 tbl.cell(2, 0).unwrap().set_text("Discount (5%)");
1363 tbl.cell(2, 1).unwrap().set_text("-$7,730.00");
1364 tbl.cell(3, 0).unwrap().set_text("AMOUNT DUE");
1365 tbl.cell(3, 0).unwrap().shading("B22222");
1366 tbl.cell(3, 1).unwrap().set_text("$160,204.25");
1367 tbl.cell(3, 1).unwrap().shading("B22222");
1368 }
1369
1370 doc.add_paragraph("");
1371 doc.add_paragraph("");
1372
1373 // ── Payment Details ──
1374 {
1375 let mut p = doc.add_paragraph("");
1376 p.add_run("Payment Details")
1377 .bold(true)
1378 .size(14.0)
1379 .color("333333");
1380 }
1381 doc.add_paragraph("Bank: Silicon Valley Bank")
1382 .indent_left(Length::inches(0.3));
1383 doc.add_paragraph("Account: Tensorbee Inc. — 0847-2953-1120")
1384 .indent_left(Length::inches(0.3));
1385 doc.add_paragraph("Routing: 121140399")
1386 .indent_left(Length::inches(0.3));
1387 doc.add_paragraph("Swift: SVBKUS6S")
1388 .indent_left(Length::inches(0.3));
1389
1390 doc.add_paragraph("");
1391
1392 doc.add_paragraph("Please include invoice number INV-2026-0392 in the payment reference.")
1393 .shading("FAF0F0")
1394 .border_all(BorderStyle::Single, 2, "B22222");
1395
1396 doc
1397}
1398
1399// =============================================================================
1400// 5. REPORT — Forest green + earth tones, with images & hierarchical sections
1401// =============================================================================
1402fn generate_report(_samples_dir: &Path) -> Document {
1403 let mut doc = Document::new();
1404
1405 // Colors: Forest #2D5016, Sage #6B8E23, Earth #8B7355, Cream #FFFAF0
1406 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1407 doc.set_margins(
1408 Length::inches(1.0),
1409 Length::inches(1.0),
1410 Length::inches(1.0),
1411 Length::inches(1.0),
1412 );
1413 doc.set_title("Q4 2025 Environmental Impact Report");
1414 doc.set_author("Walter White");
1415 doc.set_subject("Quarterly Environmental Report");
1416 doc.set_keywords("environment, sustainability, report, Tensorbee");
1417
1418 doc.set_different_first_page(true);
1419 doc.set_first_page_header("");
1420 doc.set_header("Tensorbee — Q4 2025 Environmental Impact Report");
1421 doc.set_footer("CONFIDENTIAL — Page");
1422
1423 // ── Cover ──
1424 let cover_bg = create_sample_png(612, 792, [20, 50, 15]);
1425 doc.add_background_image(&cover_bg, "report_cover.png");
1426
1427 for _ in 0..5 {
1428 doc.add_paragraph("");
1429 }
1430 {
1431 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1432 p.add_run("Q4 2025")
1433 .bold(true)
1434 .size(48.0)
1435 .color("FFFFFF")
1436 .font("Georgia");
1437 }
1438 {
1439 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1440 p.add_run("Environmental Impact Report")
1441 .size(24.0)
1442 .color("90EE90")
1443 .font("Georgia")
1444 .italic(true);
1445 }
1446 doc.add_paragraph("");
1447 {
1448 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1449 p.add_run("Tensorbee — Sustainability Division")
1450 .size(14.0)
1451 .color("C0C0C0");
1452 }
1453 {
1454 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1455 p.add_run("Prepared by Walter White, Chief Sustainability Officer")
1456 .size(11.0)
1457 .color("AAAAAA");
1458 }
1459
1460 // ── TOC ──
1461 doc.add_paragraph("").page_break_before(true);
1462 doc.insert_toc(doc.content_count(), 3);
1463
1464 // ── Section 1: Executive Overview ──
1465 doc.add_paragraph("").page_break_before(true);
1466 doc.add_paragraph("Executive Overview").style("Heading1");
1467 doc.add_paragraph(
1468 "This report presents Tensorbee's environmental performance for Q4 2025. Our \
1469 sustainability initiatives have yielded a 34% reduction in carbon emissions compared \
1470 to Q4 2024, exceeding our target of 25%. Key achievements include the transition to \
1471 100% renewable energy in our primary data centers and the launch of our carbon offset \
1472 marketplace.",
1473 )
1474 .first_line_indent(Length::inches(0.3));
1475
1476 // Image: performance chart
1477 let chart_img = create_chart_png(400, 200);
1478 doc.add_paragraph("");
1479 doc.add_picture(
1480 &chart_img,
1481 "performance_chart.png",
1482 Length::inches(5.0),
1483 Length::inches(2.5),
1484 )
1485 .alignment(Alignment::Center);
1486 {
1487 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1488 p.add_run("Figure 1: Quarterly Carbon Emissions (tonnes CO2e)")
1489 .italic(true)
1490 .size(9.0)
1491 .color("666666");
1492 }
1493
1494 // ── Section 2: Energy Consumption ──
1495 doc.add_paragraph("").page_break_before(true);
1496 doc.add_paragraph("Energy Consumption").style("Heading1");
1497
1498 doc.add_paragraph("Data Center Operations")
1499 .style("Heading2");
1500 doc.add_paragraph(
1501 "Our three primary data centers consumed a combined 4.2 GWh in Q4 2025, \
1502 a 12% reduction from Q3 2025 driven by improved cooling efficiency and \
1503 server consolidation.",
1504 );
1505
1506 // Data table
1507 {
1508 let mut tbl = doc
1509 .add_table(5, 4)
1510 .borders(BorderStyle::Single, 4, "2D5016")
1511 .width_pct(100.0);
1512 let hdrs = ["Data Center", "Capacity (MW)", "Usage (GWh)", "PUE"];
1513 for (c, h) in hdrs.iter().enumerate() {
1514 tbl.cell(0, c).unwrap().set_text(h);
1515 tbl.cell(0, c).unwrap().shading("2D5016");
1516 }
1517 let rows = [
1518 ("San Francisco (Primary)", "3.2", "1.8", "1.12"),
1519 ("Dublin (EU)", "2.1", "1.2", "1.18"),
1520 ("Singapore (APAC)", "1.8", "1.2", "1.24"),
1521 ("TOTAL", "7.1", "4.2", "1.17 avg"),
1522 ];
1523 for (i, (dc, cap, use_, pue)) in rows.iter().enumerate() {
1524 tbl.cell(i + 1, 0).unwrap().set_text(dc);
1525 tbl.cell(i + 1, 1).unwrap().set_text(cap);
1526 tbl.cell(i + 1, 2).unwrap().set_text(use_);
1527 tbl.cell(i + 1, 3).unwrap().set_text(pue);
1528 if i == 3 {
1529 for c in 0..4 {
1530 tbl.cell(i + 1, c).unwrap().shading("E2EFDA");
1531 }
1532 }
1533 }
1534 }
1535
1536 doc.add_paragraph("");
1537
1538 doc.add_paragraph("Renewable Energy Mix").style("Heading2");
1539 doc.add_paragraph("Breakdown of energy sources across all facilities:");
1540
1541 doc.add_bullet_list_item("Solar PV: 42% (1.76 GWh)", 0);
1542 doc.add_bullet_list_item("Wind Power Purchase Agreements: 38% (1.60 GWh)", 0);
1543 doc.add_bullet_list_item("Hydroelectric: 12% (0.50 GWh)", 0);
1544 doc.add_bullet_list_item("Grid (non-renewable): 8% (0.34 GWh)", 0);
1545
1546 // ── Section 3: Water & Waste ──
1547 doc.add_paragraph("Water & Waste Management")
1548 .style("Heading1");
1549
1550 doc.add_paragraph("Water Usage").style("Heading2");
1551 doc.add_paragraph(
1552 "Total water consumption was 12.4 million gallons, a 15% reduction from Q3. \
1553 Our closed-loop cooling systems now recycle 78% of water used in cooling operations.",
1554 );
1555
1556 doc.add_paragraph("Waste Reduction").style("Heading2");
1557 doc.add_paragraph("E-waste management results for Q4:")
1558 .keep_with_next(true);
1559 {
1560 let mut tbl = doc
1561 .add_table(4, 3)
1562 .borders(BorderStyle::Single, 2, "6B8E23")
1563 .width_pct(80.0);
1564 tbl.cell(0, 0).unwrap().set_text("Category");
1565 tbl.cell(0, 0).unwrap().shading("6B8E23");
1566 tbl.cell(0, 1).unwrap().set_text("Weight (kg)");
1567 tbl.cell(0, 1).unwrap().shading("6B8E23");
1568 tbl.cell(0, 2).unwrap().set_text("Recycled %");
1569 tbl.cell(0, 2).unwrap().shading("6B8E23");
1570 let rows = [
1571 ("Server Hardware", "2,450", "94%"),
1572 ("Networking Equipment", "820", "91%"),
1573 ("Storage Media", "340", "99%"),
1574 ];
1575 for (i, (cat, wt, pct)) in rows.iter().enumerate() {
1576 tbl.cell(i + 1, 0).unwrap().set_text(cat);
1577 tbl.cell(i + 1, 1).unwrap().set_text(wt);
1578 tbl.cell(i + 1, 2).unwrap().set_text(pct);
1579 }
1580 }
1581
1582 // ── Section 4: Initiatives ──
1583 doc.add_paragraph("").page_break_before(true);
1584 doc.add_paragraph("Q1 2026 Initiatives").style("Heading1");
1585
1586 doc.add_paragraph("Planned Programs").style("Heading2");
1587 doc.add_numbered_list_item(
1588 "Deploy on-site battery storage at SF data center (2 MWh capacity)",
1589 0,
1590 );
1591 doc.add_numbered_list_item("Pilot immersion cooling in Dublin facility", 0);
1592 doc.add_numbered_list_item("Launch employee commute carbon offset program", 0);
1593 doc.add_numbered_list_item("Achieve ISO 14001 certification for Singapore facility", 0);
1594
1595 doc.add_paragraph("Investment Targets").style("Heading2");
1596 doc.add_paragraph("Sustainability CapEx allocation for FY2026:")
1597 .keep_with_next(true);
1598 {
1599 let mut tbl = doc
1600 .add_table(5, 2)
1601 .borders(BorderStyle::Single, 4, "2D5016");
1602 tbl.cell(0, 0).unwrap().set_text("Initiative");
1603 tbl.cell(0, 0).unwrap().shading("2D5016");
1604 tbl.cell(0, 1).unwrap().set_text("Budget");
1605 tbl.cell(0, 1).unwrap().shading("2D5016");
1606 let rows = [
1607 ("Battery Storage", "$1.2M"),
1608 ("Immersion Cooling Pilot", "$800K"),
1609 ("Solar Panel Expansion", "$2.1M"),
1610 ("Carbon Credits", "$500K"),
1611 ];
1612 for (i, (init, budget)) in rows.iter().enumerate() {
1613 tbl.cell(i + 1, 0).unwrap().set_text(init);
1614 tbl.cell(i + 1, 1).unwrap().set_text(budget);
1615 if i % 2 == 0 {
1616 tbl.cell(i + 1, 0).unwrap().shading("FFFAF0");
1617 tbl.cell(i + 1, 1).unwrap().shading("FFFAF0");
1618 }
1619 }
1620 }
1621
1622 // Image: sustainability roadmap
1623 let roadmap_img = create_sample_png(500, 100, [40, 80, 30]);
1624 doc.add_paragraph("");
1625 doc.add_picture(
1626 &roadmap_img,
1627 "roadmap.png",
1628 Length::inches(6.0),
1629 Length::inches(1.2),
1630 )
1631 .alignment(Alignment::Center);
1632 {
1633 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1634 p.add_run("Figure 2: 2026 Sustainability Roadmap")
1635 .italic(true)
1636 .size(9.0)
1637 .color("666666");
1638 }
1639
1640 doc.add_paragraph("");
1641 doc.add_paragraph(
1642 "For questions about this report, contact Walter White at sustainability@tensorbee.com.",
1643 )
1644 .shading("FFFAF0")
1645 .border_all(BorderStyle::Single, 2, "2D5016");
1646
1647 doc
1648}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}34fn generate_feature_showcase(path: &Path) {
35 let mut doc = Document::new();
36
37 // =========================================================================
38 // PAGE SETUP & METADATA
39 // =========================================================================
40 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
41 doc.set_margins(
42 Length::inches(1.0), // top
43 Length::inches(1.0), // right
44 Length::inches(1.0), // bottom
45 Length::inches(1.0), // left
46 );
47 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
48 doc.set_gutter(Length::twips(0));
49
50 doc.set_title("rdocx Feature Showcase");
51 doc.set_author("rdocx Sample Generator");
52 doc.set_subject("Comprehensive feature demonstration");
53 doc.set_keywords("rdocx, docx, rust, sample");
54
55 // Header & Footer
56 doc.set_header("rdocx Feature Showcase");
57 doc.set_footer("Generated by rdocx — Page");
58
59 // Different first page header
60 doc.set_different_first_page(true);
61 doc.set_first_page_header("rdocx");
62 doc.set_first_page_footer("Feature Showcase — Cover Page");
63
64 // =========================================================================
65 // PAGE 1: COVER PAGE — background image, run formatting
66 // =========================================================================
67 let bg_cover = create_sample_png(612, 792, [30, 60, 120]);
68 doc.add_background_image(&bg_cover, "cover_bg.png");
69
70 doc.add_paragraph(""); // spacer
71 doc.add_paragraph(""); // spacer
72 doc.add_paragraph(""); // spacer
73
74 {
75 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
76 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
77 }
78 {
79 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
80 p.add_run("Feature Showcase")
81 .size(28.0)
82 .color("FFFFFF")
83 .italic(true);
84 }
85
86 doc.add_paragraph(""); // spacer
87
88 {
89 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
90 p.add_run("A comprehensive demonstration of every feature")
91 .size(14.0)
92 .color("CCDDFF");
93 }
94 {
95 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
96 p.add_run("provided by the rdocx Rust crate for DOCX generation.")
97 .size(14.0)
98 .color("CCDDFF");
99 }
100
101 // =========================================================================
102 // PAGE 2: TEXT FORMATTING
103 // =========================================================================
104 doc.add_paragraph("").page_break_before(true);
105
106 doc.add_paragraph("1. Text Formatting").style("Heading1");
107
108 doc.add_paragraph("This section demonstrates paragraph and run-level formatting options.");
109 doc.add_paragraph("");
110
111 // --- Paragraph alignment ---
112 doc.add_paragraph("Paragraph Alignment").style("Heading2");
113
114 doc.add_paragraph("This paragraph is left-aligned (the default).")
115 .alignment(Alignment::Left);
116 doc.add_paragraph("This paragraph is center-aligned.")
117 .alignment(Alignment::Center);
118 doc.add_paragraph("This paragraph is right-aligned.")
119 .alignment(Alignment::Right);
120 doc.add_paragraph(
121 "This paragraph is justified. To demonstrate justified text properly, it needs \
122 to be long enough to span multiple lines so the word spacing adjustment is visible \
123 across the full width of the text area on the page.",
124 )
125 .alignment(Alignment::Justify);
126
127 doc.add_paragraph("");
128
129 // --- Run formatting ---
130 doc.add_paragraph("Run Formatting").style("Heading2");
131
132 {
133 let mut p = doc.add_paragraph("");
134 p.add_run("Bold text").bold(true);
135 p.add_run(" | ");
136 p.add_run("Italic text").italic(true);
137 p.add_run(" | ");
138 p.add_run("Bold + Italic").bold(true).italic(true);
139 }
140 {
141 let mut p = doc.add_paragraph("");
142 p.add_run("Single underline").underline(true);
143 p.add_run(" | ");
144 p.add_run("Strikethrough").strike(true);
145 p.add_run(" | ");
146 p.add_run("Double strikethrough").double_strike(true);
147 }
148 {
149 let mut p = doc.add_paragraph("");
150 p.add_run("Red text").color("FF0000");
151 p.add_run(" | ");
152 p.add_run("Blue text").color("0000FF");
153 p.add_run(" | ");
154 p.add_run("Green text").color("00AA00");
155 p.add_run(" | ");
156 p.add_run("Highlighted").highlight("FFFF00");
157 }
158 {
159 let mut p = doc.add_paragraph("");
160 p.add_run("8pt small").size(8.0);
161 p.add_run(" | ");
162 p.add_run("11pt normal").size(11.0);
163 p.add_run(" | ");
164 p.add_run("16pt large").size(16.0);
165 p.add_run(" | ");
166 p.add_run("24pt extra-large").size(24.0);
167 }
168 {
169 let mut p = doc.add_paragraph("");
170 p.add_run("Arial font").font("Arial");
171 p.add_run(" | ");
172 p.add_run("Times New Roman font").font("Times New Roman");
173 p.add_run(" | ");
174 p.add_run("Courier New font").font("Courier New");
175 }
176 {
177 let mut p = doc.add_paragraph("");
178 p.add_run("Normal");
179 p.add_run(" H").size(11.0);
180 p.add_run("2").subscript();
181 p.add_run("O (subscript)").size(11.0);
182 p.add_run(" | E = mc").size(11.0);
183 p.add_run("2").superscript();
184 p.add_run(" (superscript)").size(11.0);
185 }
186 {
187 let mut p = doc.add_paragraph("");
188 p.add_run("ALL CAPS").all_caps(true);
189 p.add_run(" | ");
190 p.add_run("Small Caps").small_caps(true);
191 p.add_run(" | ");
192 p.add_run("Expanded spacing")
193 .character_spacing(Length::twips(40));
194 }
195
196 doc.add_paragraph("");
197
198 // --- Paragraph formatting ---
199 doc.add_paragraph("Paragraph Formatting").style("Heading2");
200
201 doc.add_paragraph("Paragraph with shading (light green background)")
202 .shading("E2EFDA");
203
204 doc.add_paragraph("Paragraph with bottom border")
205 .border_bottom(BorderStyle::Single, 6, "2E75B6");
206
207 doc.add_paragraph("Paragraph with all borders")
208 .border_all(BorderStyle::Single, 4, "FF0000");
209
210 doc.add_paragraph("Paragraph with 1-inch left indent and hanging indent")
211 .indent_left(Length::inches(1.0))
212 .hanging_indent(Length::inches(0.5));
213
214 doc.add_paragraph("Paragraph with first-line indent of 0.5 inches")
215 .first_line_indent(Length::inches(0.5));
216
217 doc.add_paragraph("Paragraph with extra space before (24pt) and after (12pt)")
218 .space_before(Length::pt(24.0))
219 .space_after(Length::pt(12.0));
220
221 doc.add_paragraph(
222 "Paragraph with double line spacing. This text should have extra vertical \
223 space between lines to demonstrate the line_spacing_multiple setting.",
224 )
225 .line_spacing_multiple(2.0);
226
227 doc.add_paragraph("Paragraph with keep-with-next (won't break from the next paragraph)")
228 .keep_with_next(true);
229 doc.add_paragraph("(This stays with the paragraph above.)");
230
231 // =========================================================================
232 // PAGE 3: LISTS & TAB STOPS
233 // =========================================================================
234 doc.add_paragraph("").page_break_before(true);
235
236 doc.add_paragraph("2. Lists").style("Heading1");
237
238 doc.add_paragraph("Bullet List").style("Heading2");
239
240 doc.add_bullet_list_item("First bullet item", 0);
241 doc.add_bullet_list_item("Second bullet item", 0);
242 doc.add_bullet_list_item("Nested level 1", 1);
243 doc.add_bullet_list_item("Nested level 2", 2);
244 doc.add_bullet_list_item("Back to level 1", 1);
245 doc.add_bullet_list_item("Third bullet item", 0);
246
247 doc.add_paragraph("");
248
249 doc.add_paragraph("Numbered List").style("Heading2");
250
251 doc.add_numbered_list_item("First numbered item", 0);
252 doc.add_numbered_list_item("Second numbered item", 0);
253 doc.add_numbered_list_item("Sub-item A", 1);
254 doc.add_numbered_list_item("Sub-item B", 1);
255 doc.add_numbered_list_item("Third numbered item", 0);
256
257 doc.add_paragraph("");
258
259 // --- Tab stops ---
260 doc.add_paragraph("Tab Stops").style("Heading2");
261
262 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
263 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
264 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
265 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
266 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
267
268 doc.add_paragraph("Item\t........\tPrice")
269 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
270 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
271 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
272
273 doc.add_paragraph("Widget A\t........\t$19.99")
274 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
275 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
276 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
277
278 doc.add_paragraph("Gadget B\t________\t$249.50")
279 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
280 .add_tab_stop_with_leader(
281 TabAlignment::Right,
282 Length::inches(4.0),
283 TabLeader::Underscore,
284 )
285 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
286
287 // =========================================================================
288 // PAGE 4: TABLES
289 // =========================================================================
290 doc.add_paragraph("").page_break_before(true);
291
292 doc.add_paragraph("3. Tables").style("Heading1");
293
294 // --- Basic table with borders ---
295 doc.add_paragraph("Basic Table with Borders")
296 .style("Heading2");
297
298 {
299 let mut tbl = doc.add_table(4, 3);
300 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
301
302 // Header row
303 for col in 0..3 {
304 tbl.cell(0, col).unwrap().shading("2E75B6");
305 }
306 tbl.cell(0, 0).unwrap().set_text("Name");
307 tbl.cell(0, 1).unwrap().set_text("Role");
308 tbl.cell(0, 2).unwrap().set_text("Location");
309
310 tbl.cell(1, 0).unwrap().set_text("Alice Johnson");
311 tbl.cell(1, 1).unwrap().set_text("Engineering Lead");
312 tbl.cell(1, 2).unwrap().set_text("New York");
313
314 tbl.cell(2, 0).unwrap().set_text("Bob Smith");
315 tbl.cell(2, 1).unwrap().set_text("Product Manager");
316 tbl.cell(2, 2).unwrap().set_text("San Francisco");
317
318 tbl.cell(3, 0).unwrap().set_text("Carol Davis");
319 tbl.cell(3, 1).unwrap().set_text("Designer");
320 tbl.cell(3, 2).unwrap().set_text("London");
321 }
322
323 doc.add_paragraph("");
324
325 // --- Table with cell merging ---
326 doc.add_paragraph("Table with Cell Merging & Vertical Alignment")
327 .style("Heading2");
328
329 {
330 let mut tbl = doc.add_table(4, 4);
331 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
332 tbl = tbl.width_pct(100.0);
333
334 // Header spanning all columns
335 tbl.cell(0, 0).unwrap().set_text("Quarterly Revenue Report");
336 tbl.cell(0, 0).unwrap().shading("1F4E79");
337 tbl.cell(0, 0).unwrap().grid_span(4);
338
339 // Sub-header
340 tbl.cell(1, 0).unwrap().set_text("Region");
341 tbl.cell(1, 0).unwrap().shading("D6E4F0");
342 tbl.cell(1, 1).unwrap().set_text("Q1");
343 tbl.cell(1, 1).unwrap().shading("D6E4F0");
344 tbl.cell(1, 2).unwrap().set_text("Q2");
345 tbl.cell(1, 2).unwrap().shading("D6E4F0");
346 tbl.cell(1, 3).unwrap().set_text("Total");
347 tbl.cell(1, 3).unwrap().shading("D6E4F0");
348
349 // Data
350 tbl.cell(2, 0).unwrap().set_text("North America");
351 tbl.cell(2, 1).unwrap().set_text("$2.4M");
352 tbl.cell(2, 2).unwrap().set_text("$2.7M");
353 tbl.cell(2, 3).unwrap().set_text("$5.1M");
354
355 tbl.cell(3, 0).unwrap().set_text("Europe");
356 tbl.cell(3, 1).unwrap().set_text("$1.8M");
357 tbl.cell(3, 2).unwrap().set_text("$2.0M");
358 tbl.cell(3, 3).unwrap().set_text("$3.8M");
359
360 // Vertical alignment on data cells
361 tbl.cell(2, 3)
362 .unwrap()
363 .vertical_alignment(VerticalAlignment::Center);
364 tbl.cell(3, 3)
365 .unwrap()
366 .vertical_alignment(VerticalAlignment::Bottom);
367 }
368
369 doc.add_paragraph("");
370
371 // --- Table with vertical merge ---
372 doc.add_paragraph("Table with Vertical Merge")
373 .style("Heading2");
374
375 {
376 let mut tbl = doc.add_table(4, 3);
377 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
378
379 tbl.cell(0, 0).unwrap().set_text("Category");
380 tbl.cell(0, 0).unwrap().shading("E2EFDA");
381 tbl.cell(0, 1).unwrap().set_text("Item");
382 tbl.cell(0, 1).unwrap().shading("E2EFDA");
383 tbl.cell(0, 2).unwrap().set_text("Price");
384 tbl.cell(0, 2).unwrap().shading("E2EFDA");
385
386 // "Hardware" spans rows 1-2
387 tbl.cell(1, 0).unwrap().set_text("Hardware");
388 tbl.cell(1, 0).unwrap().v_merge_restart();
389 tbl.cell(1, 1).unwrap().set_text("Laptop");
390 tbl.cell(1, 2).unwrap().set_text("$1,200");
391
392 tbl.cell(2, 0).unwrap().v_merge_continue();
393 tbl.cell(2, 1).unwrap().set_text("Monitor");
394 tbl.cell(2, 2).unwrap().set_text("$450");
395
396 // "Software" on row 3
397 tbl.cell(3, 0).unwrap().set_text("Software");
398 tbl.cell(3, 1).unwrap().set_text("IDE License");
399 tbl.cell(3, 2).unwrap().set_text("$200/yr");
400 }
401
402 doc.add_paragraph("");
403
404 // --- Nested table ---
405 doc.add_paragraph("Nested Table").style("Heading2");
406
407 {
408 let mut tbl = doc.add_table(2, 2);
409 tbl = tbl.borders(BorderStyle::Single, 6, "2E75B6");
410
411 tbl.cell(0, 0).unwrap().set_text("Outer Cell (0,0)");
412 tbl.cell(0, 1).unwrap().set_text("Outer Cell (0,1)");
413 tbl.cell(1, 0).unwrap().set_text("Outer Cell (1,0)");
414
415 // Nested table inside cell (1,1)
416 {
417 let mut cell = tbl.cell(1, 1).unwrap();
418 cell.set_text("Contains nested table:");
419 let mut nested = cell.add_table(2, 2);
420 nested = nested.borders(BorderStyle::Single, 2, "FF6600");
421 nested.cell(0, 0).unwrap().set_text("Inner A");
422 nested.cell(0, 1).unwrap().set_text("Inner B");
423 nested.cell(1, 0).unwrap().set_text("Inner C");
424 nested.cell(1, 1).unwrap().set_text("Inner D");
425 }
426 }
427
428 // =========================================================================
429 // PAGE 5: IMAGES
430 // =========================================================================
431 doc.add_paragraph("").page_break_before(true);
432
433 doc.add_paragraph("4. Images").style("Heading1");
434
435 doc.add_paragraph("Inline Image").style("Heading2");
436
437 doc.add_paragraph("Below is an inline image (200x50 pixels, blue gradient):");
438 let inline_img = create_sample_png(200, 50, [0, 80, 200]);
439 doc.add_picture(
440 &inline_img,
441 "inline_chart.png",
442 Length::inches(3.0),
443 Length::inches(0.75),
444 );
445
446 doc.add_paragraph("");
447
448 doc.add_paragraph("Header Image").style("Heading2");
449
450 // Replace the text-only header with an image header
451 let header_img = create_sample_png(400, 40, [40, 40, 40]);
452 doc.set_header_image(
453 &header_img,
454 "header_logo.png",
455 Length::inches(2.0),
456 Length::inches(0.2),
457 );
458
459 doc.add_paragraph(
460 "The document header has been replaced with an inline image. \
461 Check the header area at the top of this page.",
462 );
463
464 doc.add_paragraph("");
465 doc.add_paragraph(
466 "Note: The cover page uses a full-page background image behind the text, \
467 demonstrated on page 1 via add_background_image().",
468 );
469
470 // =========================================================================
471 // PAGE 6: CONTENT MANIPULATION — placeholder replacement, insertion
472 // =========================================================================
473 doc.add_paragraph("").page_break_before(true);
474
475 doc.add_paragraph("5. Content Manipulation")
476 .style("Heading1");
477
478 // --- Placeholder replacement ---
479 doc.add_paragraph("Placeholder Replacement")
480 .style("Heading2");
481
482 doc.add_paragraph(
483 "Before replacement, this document contained {{customer}} and {{date}} placeholders.",
484 );
485
486 {
487 let mut p = doc.add_paragraph("");
488 p.add_run("Customer: ").bold(true);
489 p.add_run("{{customer}}");
490 }
491 {
492 let mut p = doc.add_paragraph("");
493 p.add_run("Date: ").bold(true);
494 p.add_run("{{date}}");
495 }
496 doc.add_paragraph("Reference: {{ref_number}}");
497
498 // Table with placeholders
499 {
500 let mut tbl = doc.add_table(3, 2);
501 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
502 tbl.cell(0, 0).unwrap().set_text("Field");
503 tbl.cell(0, 0).unwrap().shading("D6E4F0");
504 tbl.cell(0, 1).unwrap().set_text("Value");
505 tbl.cell(0, 1).unwrap().shading("D6E4F0");
506 tbl.cell(1, 0).unwrap().set_text("Project");
507 tbl.cell(1, 1).unwrap().set_text("{{project}}");
508 tbl.cell(2, 0).unwrap().set_text("Status");
509 tbl.cell(2, 1).unwrap().set_text("{{status}}");
510 }
511
512 // Perform replacements
513 let mut replacements = HashMap::new();
514 replacements.insert("{{customer}}", "Acme Corporation");
515 replacements.insert("{{date}}", "February 22, 2026");
516 replacements.insert("{{ref_number}}", "REF-2026-001");
517 replacements.insert("{{project}}", "Infrastructure Upgrade");
518 replacements.insert("{{status}}", "In Progress");
519 let replace_count = doc.replace_all(&replacements);
520
521 doc.add_paragraph("");
522 doc.add_paragraph(&format!(
523 "(Replaced {} placeholders above — in body text and table cells)",
524 replace_count
525 ));
526
527 doc.add_paragraph("");
528
529 // --- Content insertion ---
530 doc.add_paragraph("Content Insertion").style("Heading2");
531
532 doc.add_paragraph("Section A: First section of content.");
533 doc.add_paragraph("Section C: Third section of content.");
534
535 // Insert "Section B" between A and C
536 if let Some(idx) = doc.find_content_index("Section C") {
537 doc.insert_paragraph(
538 idx,
539 "Section B: Inserted between A and C using find_content_index().",
540 );
541 }
542
543 doc.add_paragraph("");
544 doc.add_paragraph(
545 "The paragraph above ('Section B') was inserted at a specific position \
546 using find_content_index() + insert_paragraph().",
547 );
548
549 // =========================================================================
550 // PAGE 7: LANDSCAPE — section break, wide table
551 // =========================================================================
552 doc.add_paragraph("").section_break(SectionBreak::NextPage);
553
554 doc.add_paragraph("6. Mixed Page Orientation")
555 .style("Heading1");
556
557 doc.add_paragraph(
558 "This page is in LANDSCAPE orientation. It was created using a section break \
559 followed by section_landscape(). This is useful for wide tables or charts.",
560 );
561
562 doc.add_paragraph("");
563
564 // Wide table for landscape
565 {
566 let mut tbl = doc.add_table(4, 7);
567 tbl = tbl.borders(BorderStyle::Single, 4, "2E75B6");
568
569 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
570 for (col, h) in headers.iter().enumerate() {
571 tbl.cell(0, col).unwrap().set_text(h);
572 tbl.cell(0, col).unwrap().shading("2E75B6");
573 }
574
575 let data = [
576 [
577 "North America",
578 "$1.2M",
579 "$1.3M",
580 "$1.4M",
581 "$1.5M",
582 "$1.6M",
583 "$7.0M",
584 ],
585 [
586 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
587 ],
588 [
589 "Asia Pacific",
590 "$0.5M",
591 "$0.6M",
592 "$0.7M",
593 "$0.7M",
594 "$0.8M",
595 "$3.3M",
596 ],
597 ];
598 for (row_idx, row_data) in data.iter().enumerate() {
599 for (col, val) in row_data.iter().enumerate() {
600 tbl.cell(row_idx + 1, col).unwrap().set_text(val);
601 }
602 }
603 }
604
605 // End landscape, return to portrait
606 doc.add_paragraph("")
607 .section_break(SectionBreak::NextPage)
608 .section_landscape();
609
610 // =========================================================================
611 // PAGE 8: BACK TO PORTRAIT — styles, final notes
612 // =========================================================================
613 doc.add_paragraph("7. Custom Styles & Summary")
614 .style("Heading1");
615
616 doc.add_paragraph(
617 "This final page is back in portrait orientation after a section break. \
618 The document has demonstrated:",
619 );
620
621 doc.add_bullet_list_item(
622 "Page setup: size, margins, header/footer distance, gutter",
623 0,
624 );
625 doc.add_bullet_list_item("Document metadata: title, author, subject, keywords", 0);
626 doc.add_bullet_list_item("Headers and footers: text, images, different first page", 0);
627 doc.add_bullet_list_item("Background images: full-page behind text", 0);
628 doc.add_bullet_list_item(
629 "Text formatting: bold, italic, underline, strike, color, size, font",
630 0,
631 );
632 doc.add_bullet_list_item(
633 "Advanced run formatting: superscript, subscript, caps, spacing",
634 0,
635 );
636 doc.add_bullet_list_item(
637 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
638 0,
639 );
640 doc.add_bullet_list_item("Bullet and numbered lists with nesting levels", 0);
641 doc.add_bullet_list_item("Tab stops with dot/underscore leaders", 0);
642 doc.add_bullet_list_item(
643 "Tables: borders, shading, column spans, row spans, nesting",
644 0,
645 );
646 doc.add_bullet_list_item("Vertical alignment in table cells", 0);
647 doc.add_bullet_list_item("Inline images", 0);
648 doc.add_bullet_list_item("Placeholder replacement in body and table cells", 0);
649 doc.add_bullet_list_item("Content insertion at specific positions", 0);
650 doc.add_bullet_list_item(
651 "Section breaks with mixed portrait/landscape orientation",
652 0,
653 );
654
655 doc.add_paragraph("");
656 doc.add_paragraph("All features above were built entirely from scratch using the rdocx API.")
657 .alignment(Alignment::Center)
658 .shading("E2EFDA")
659 .border_all(BorderStyle::Single, 2, "00AA00");
660
661 doc.save(path).unwrap();
662}86fn generate_feature_showcase(_samples_dir: &Path) -> Document {
87 let mut doc = Document::new();
88
89 // ── Page Setup & Metadata ──
90 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
91 doc.set_margins(
92 Length::inches(1.0),
93 Length::inches(1.0),
94 Length::inches(1.0),
95 Length::inches(1.0),
96 );
97 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
98 doc.set_gutter(Length::twips(0));
99 doc.set_title("rdocx Feature Showcase");
100 doc.set_author("rdocx Sample Generator");
101 doc.set_subject("Comprehensive feature demonstration");
102 doc.set_keywords("rdocx, docx, rust, sample, showcase");
103
104 // Headers & Footers with different first page
105 doc.set_different_first_page(true);
106 doc.set_first_page_header("rdocx");
107 doc.set_first_page_footer("Feature Showcase — Cover Page");
108 doc.set_header("rdocx Feature Showcase");
109 doc.set_footer("Generated by rdocx");
110
111 // ── COVER PAGE ──
112 let bg = create_sample_png(612, 792, [20, 45, 90]);
113 doc.add_background_image(&bg, "cover_bg.png");
114
115 for _ in 0..3 {
116 doc.add_paragraph("");
117 }
118 {
119 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
120 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
121 }
122 {
123 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
124 p.add_run("Complete Feature Showcase")
125 .size(28.0)
126 .color("FFFFFF")
127 .italic(true);
128 }
129 doc.add_paragraph("");
130 {
131 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
132 p.add_run("Every feature of the rdocx Rust library")
133 .size(13.0)
134 .color("B0C4DE");
135 }
136 {
137 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
138 p.add_run("demonstrated in a single document.")
139 .size(13.0)
140 .color("B0C4DE");
141 }
142
143 // ── TABLE OF CONTENTS ──
144 doc.add_paragraph("").page_break_before(true);
145 doc.insert_toc(doc.content_count(), 3);
146
147 // ── SECTION 1: TEXT FORMATTING ──
148 doc.add_paragraph("").page_break_before(true);
149 doc.add_paragraph("1. Text Formatting").style("Heading1");
150
151 // Paragraph Alignment
152 doc.add_paragraph("1.1 Paragraph Alignment")
153 .style("Heading2");
154 doc.add_paragraph("Left-aligned paragraph (default).")
155 .alignment(Alignment::Left);
156 doc.add_paragraph("Center-aligned paragraph.")
157 .alignment(Alignment::Center);
158 doc.add_paragraph("Right-aligned paragraph.")
159 .alignment(Alignment::Right);
160 doc.add_paragraph(
161 "Justified paragraph — this text is long enough to demonstrate how justified alignment \
162 distributes extra space across word gaps so lines fill the full width of the text area.",
163 )
164 .alignment(Alignment::Justify);
165
166 doc.add_paragraph("");
167
168 // Run Formatting
169 doc.add_paragraph("1.2 Run Formatting").style("Heading2");
170 {
171 let mut p = doc.add_paragraph("");
172 p.add_run("Bold").bold(true);
173 p.add_run(" | ");
174 p.add_run("Italic").italic(true);
175 p.add_run(" | ");
176 p.add_run("Bold Italic").bold(true).italic(true);
177 p.add_run(" | ");
178 p.add_run("Underline").underline(true);
179 p.add_run(" | ");
180 p.add_run("Strikethrough").strike(true);
181 p.add_run(" | ");
182 p.add_run("Double Strike").double_strike(true);
183 }
184 {
185 let mut p = doc.add_paragraph("");
186 p.add_run("Red").color("FF0000");
187 p.add_run(" | ");
188 p.add_run("Blue").color("0000FF");
189 p.add_run(" | ");
190 p.add_run("Green").color("00AA00");
191 p.add_run(" | ");
192 p.add_run("Highlighted").highlight("FFFF00");
193 }
194 {
195 let mut p = doc.add_paragraph("");
196 p.add_run("8pt").size(8.0);
197 p.add_run(" | ");
198 p.add_run("11pt").size(11.0);
199 p.add_run(" | ");
200 p.add_run("16pt").size(16.0);
201 p.add_run(" | ");
202 p.add_run("24pt").size(24.0);
203 }
204 {
205 let mut p = doc.add_paragraph("");
206 p.add_run("Arial").font("Arial");
207 p.add_run(" | ");
208 p.add_run("Times New Roman").font("Times New Roman");
209 p.add_run(" | ");
210 p.add_run("Courier New").font("Courier New");
211 }
212 {
213 let mut p = doc.add_paragraph("");
214 p.add_run("H");
215 p.add_run("2").subscript();
216 p.add_run("O (subscript) | E=mc");
217 p.add_run("2").superscript();
218 p.add_run(" (superscript)");
219 }
220 {
221 let mut p = doc.add_paragraph("");
222 p.add_run("ALL CAPS").all_caps(true);
223 p.add_run(" | ");
224 p.add_run("Small Caps").small_caps(true);
225 p.add_run(" | ");
226 p.add_run("Expanded").character_spacing(Length::twips(40));
227 p.add_run(" | ");
228 p.add_run("Hidden text (not visible)").hidden(true);
229 }
230
231 doc.add_paragraph("");
232
233 // Underline Styles
234 doc.add_paragraph("1.3 Underline Styles").style("Heading2");
235 {
236 let mut p = doc.add_paragraph("");
237 p.add_run("Single ")
238 .underline_style(rdocx::UnderlineStyle::Single);
239 p.add_run("Double ")
240 .underline_style(rdocx::UnderlineStyle::Double);
241 p.add_run("Thick ")
242 .underline_style(rdocx::UnderlineStyle::Thick);
243 p.add_run("Dotted ")
244 .underline_style(rdocx::UnderlineStyle::Dotted);
245 p.add_run("Dash ")
246 .underline_style(rdocx::UnderlineStyle::Dash);
247 p.add_run("Wave ")
248 .underline_style(rdocx::UnderlineStyle::Wave);
249 }
250
251 // ── SECTION 2: PARAGRAPH FORMATTING ──
252 doc.add_paragraph("").page_break_before(true);
253 doc.add_paragraph("2. Paragraph Formatting")
254 .style("Heading1");
255
256 doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
257 doc.add_paragraph("Paragraph with light green shading")
258 .shading("E2EFDA");
259 doc.add_paragraph("Paragraph with bottom border")
260 .border_bottom(BorderStyle::Single, 6, "2E75B6");
261 doc.add_paragraph("Paragraph with all borders (red)")
262 .border_all(BorderStyle::Single, 4, "FF0000");
263
264 doc.add_paragraph("2.2 Indentation").style("Heading2");
265 doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
266 .indent_left(Length::inches(1.0))
267 .hanging_indent(Length::inches(0.5));
268 doc.add_paragraph("0.5-inch first-line indent on this paragraph")
269 .first_line_indent(Length::inches(0.5));
270 doc.add_paragraph("Both left and right indent (0.75 inches each)")
271 .indent_left(Length::inches(0.75))
272 .indent_right(Length::inches(0.75));
273
274 doc.add_paragraph("2.3 Spacing & Line Height")
275 .style("Heading2");
276 doc.add_paragraph("Extra space before (24pt) and after (12pt)")
277 .space_before(Length::pt(24.0))
278 .space_after(Length::pt(12.0));
279 doc.add_paragraph(
280 "Double line spacing paragraph. This text should have extra vertical space between \
281 lines to demonstrate line_spacing_multiple(2.0).",
282 )
283 .line_spacing_multiple(2.0);
284 doc.add_paragraph("Exact 20pt line spacing (fixed height).")
285 .line_spacing(20.0);
286
287 doc.add_paragraph("2.4 Pagination Controls")
288 .style("Heading2");
289 doc.add_paragraph("keep_with_next — this paragraph stays with the next")
290 .keep_with_next(true);
291 doc.add_paragraph("(This paragraph was kept with the one above.)");
292 doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
293 .keep_together(true);
294 doc.add_paragraph("widow_control — prevents widow/orphan lines")
295 .widow_control(true);
296
297 // ── SECTION 3: LISTS ──
298 doc.add_paragraph("").page_break_before(true);
299 doc.add_paragraph("3. Lists").style("Heading1");
300
301 doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
302 doc.add_bullet_list_item("First item", 0);
303 doc.add_bullet_list_item("Second item", 0);
304 doc.add_bullet_list_item("Nested level 1", 1);
305 doc.add_bullet_list_item("Nested level 2", 2);
306 doc.add_bullet_list_item("Back to level 1", 1);
307 doc.add_bullet_list_item("Third item", 0);
308
309 doc.add_paragraph("");
310
311 doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
312 doc.add_numbered_list_item("First numbered item", 0);
313 doc.add_numbered_list_item("Second numbered item", 0);
314 doc.add_numbered_list_item("Sub-item A", 1);
315 doc.add_numbered_list_item("Sub-item B", 1);
316 doc.add_numbered_list_item("Third numbered item", 0);
317
318 // ── SECTION 4: TAB STOPS ──
319 doc.add_paragraph("");
320 doc.add_paragraph("4. Tab Stops").style("Heading1");
321
322 doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
323 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
324 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
325 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
326 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
327 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
328
329 doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
330 doc.add_paragraph("Item\t\tPrice")
331 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
332 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
333 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
334 doc.add_paragraph("Widget\t\t$19.99")
335 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
336 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
337 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
338 doc.add_paragraph("Gadget\t\t$249.50")
339 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
340 .add_tab_stop_with_leader(
341 TabAlignment::Right,
342 Length::inches(4.0),
343 TabLeader::Underscore,
344 )
345 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
346
347 // ── SECTION 5: TABLES ──
348 doc.add_paragraph("").page_break_before(true);
349 doc.add_paragraph("5. Tables").style("Heading1");
350
351 doc.add_paragraph("5.1 Basic Table").style("Heading2");
352 {
353 let mut tbl = doc
354 .add_table(4, 3)
355 .borders(BorderStyle::Single, 4, "000000");
356 for col in 0..3 {
357 tbl.cell(0, col).unwrap().shading("2E75B6");
358 }
359 tbl.cell(0, 0).unwrap().set_text("Name");
360 tbl.cell(0, 1).unwrap().set_text("Role");
361 tbl.cell(0, 2).unwrap().set_text("Location");
362 tbl.cell(1, 0).unwrap().set_text("Walter White");
363 tbl.cell(1, 1).unwrap().set_text("CEO");
364 tbl.cell(1, 2).unwrap().set_text("Albuquerque");
365 tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
366 tbl.cell(2, 1).unwrap().set_text("CTO");
367 tbl.cell(2, 2).unwrap().set_text("Remote");
368 tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
369 tbl.cell(3, 1).unwrap().set_text("Security");
370 tbl.cell(3, 2).unwrap().set_text("Washington");
371 }
372
373 doc.add_paragraph("");
374
375 doc.add_paragraph("5.2 Column Span & Cell Shading")
376 .style("Heading2");
377 {
378 let mut tbl = doc
379 .add_table(3, 4)
380 .borders(BorderStyle::Single, 4, "000000")
381 .width_pct(100.0);
382 tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
383 tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
384 tbl.cell(1, 0).unwrap().set_text("Region");
385 tbl.cell(1, 0).unwrap().shading("D6E4F0");
386 tbl.cell(1, 1).unwrap().set_text("Q1");
387 tbl.cell(1, 1).unwrap().shading("D6E4F0");
388 tbl.cell(1, 2).unwrap().set_text("Q2");
389 tbl.cell(1, 2).unwrap().shading("D6E4F0");
390 tbl.cell(1, 3).unwrap().set_text("Total");
391 tbl.cell(1, 3).unwrap().shading("D6E4F0");
392 tbl.cell(2, 0).unwrap().set_text("Americas");
393 tbl.cell(2, 1).unwrap().set_text("$2.4M");
394 tbl.cell(2, 2).unwrap().set_text("$2.7M");
395 tbl.cell(2, 3).unwrap().set_text("$5.1M");
396 }
397
398 doc.add_paragraph("");
399
400 doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
401 {
402 let mut tbl = doc
403 .add_table(4, 3)
404 .borders(BorderStyle::Single, 4, "000000");
405 tbl.cell(0, 0).unwrap().set_text("Category");
406 tbl.cell(0, 0).unwrap().shading("E2EFDA");
407 tbl.cell(0, 1).unwrap().set_text("Item");
408 tbl.cell(0, 1).unwrap().shading("E2EFDA");
409 tbl.cell(0, 2).unwrap().set_text("Price");
410 tbl.cell(0, 2).unwrap().shading("E2EFDA");
411 tbl.cell(1, 0).unwrap().set_text("Hardware");
412 tbl.cell(1, 0).unwrap().v_merge_restart();
413 tbl.cell(1, 1).unwrap().set_text("Laptop");
414 tbl.cell(1, 2).unwrap().set_text("$1,200");
415 tbl.cell(2, 0).unwrap().v_merge_continue();
416 tbl.cell(2, 1).unwrap().set_text("Monitor");
417 tbl.cell(2, 2).unwrap().set_text("$450");
418 tbl.cell(3, 0).unwrap().set_text("Software");
419 tbl.cell(3, 1).unwrap().set_text("IDE License");
420 tbl.cell(3, 2).unwrap().set_text("$200/yr");
421 }
422
423 doc.add_paragraph("");
424
425 doc.add_paragraph("5.4 Nested Table").style("Heading2");
426 {
427 let mut tbl = doc
428 .add_table(2, 2)
429 .borders(BorderStyle::Single, 6, "2E75B6");
430 tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
431 tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
432 tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
433 {
434 let mut cell = tbl.cell(1, 1).unwrap();
435 cell.set_text("Nested table below:");
436 let mut nested = cell
437 .add_table(2, 2)
438 .borders(BorderStyle::Single, 2, "FF6600");
439 nested.cell(0, 0).unwrap().set_text("A");
440 nested.cell(0, 1).unwrap().set_text("B");
441 nested.cell(1, 0).unwrap().set_text("C");
442 nested.cell(1, 1).unwrap().set_text("D");
443 }
444 }
445
446 doc.add_paragraph("");
447
448 doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
449 .style("Heading2");
450 {
451 let mut tbl = doc
452 .add_table(2, 3)
453 .borders(BorderStyle::Single, 4, "666666");
454 tbl.row(0).unwrap().height(Length::pt(50.0)).header();
455 tbl.cell(0, 0).unwrap().set_text("Top");
456 tbl.cell(0, 0)
457 .unwrap()
458 .vertical_alignment(VerticalAlignment::Top)
459 .shading("FFF2CC");
460 tbl.cell(0, 1).unwrap().set_text("Center");
461 tbl.cell(0, 1)
462 .unwrap()
463 .vertical_alignment(VerticalAlignment::Center)
464 .shading("D9E2F3");
465 tbl.cell(0, 2).unwrap().set_text("Bottom");
466 tbl.cell(0, 2)
467 .unwrap()
468 .vertical_alignment(VerticalAlignment::Bottom)
469 .shading("E2EFDA");
470 tbl.row(1).unwrap().cant_split();
471 tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
472 tbl.cell(1, 0).unwrap().no_wrap();
473 tbl.cell(1, 1).unwrap().set_text("Fixed width");
474 tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
475 tbl.cell(1, 2).unwrap().set_text("Normal");
476 }
477
478 // ── SECTION 6: IMAGES ──
479 doc.add_paragraph("").page_break_before(true);
480 doc.add_paragraph("6. Images").style("Heading1");
481
482 doc.add_paragraph("6.1 Inline Image").style("Heading2");
483 doc.add_paragraph("A blue gradient image below:");
484 let img = create_sample_png(200, 50, [0, 80, 200]);
485 doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
486
487 doc.add_paragraph("");
488 doc.add_paragraph("6.2 Header Image").style("Heading2");
489 let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
490 doc.set_header_image(
491 &hdr_img,
492 "header_logo.png",
493 Length::inches(2.0),
494 Length::inches(0.2),
495 );
496 doc.add_paragraph("The header now contains an inline image (check the top of this page).");
497
498 doc.add_paragraph("");
499 doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
500
501 // ── SECTION 7: CONTENT MANIPULATION ──
502 doc.add_paragraph("").page_break_before(true);
503 doc.add_paragraph("7. Content Manipulation")
504 .style("Heading1");
505
506 doc.add_paragraph("7.1 Placeholder Replacement")
507 .style("Heading2");
508 doc.add_paragraph("Customer: {{customer}}");
509 doc.add_paragraph("Date: {{date}}");
510 doc.add_paragraph("Reference: {{ref_number}}");
511 {
512 let mut tbl = doc
513 .add_table(2, 2)
514 .borders(BorderStyle::Single, 4, "000000");
515 tbl.cell(0, 0).unwrap().set_text("Project");
516 tbl.cell(0, 0).unwrap().shading("D6E4F0");
517 tbl.cell(0, 1).unwrap().set_text("{{project}}");
518 tbl.cell(1, 0).unwrap().set_text("Status");
519 tbl.cell(1, 0).unwrap().shading("D6E4F0");
520 tbl.cell(1, 1).unwrap().set_text("{{status}}");
521 }
522
523 let mut replacements = HashMap::new();
524 replacements.insert("{{customer}}", "Tensorbee Inc.");
525 replacements.insert("{{date}}", "February 22, 2026");
526 replacements.insert("{{ref_number}}", "TB-2026-001");
527 replacements.insert("{{project}}", "Infrastructure Upgrade");
528 replacements.insert("{{status}}", "In Progress");
529 let n = doc.replace_all(&replacements);
530 doc.add_paragraph(&format!("({n} placeholders replaced above)"));
531
532 doc.add_paragraph("");
533
534 doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
535 doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
536 let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
537 doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
538
539 doc.add_paragraph("");
540
541 doc.add_paragraph("7.3 Content Insertion").style("Heading2");
542 doc.add_paragraph("Section A: First content.");
543 doc.add_paragraph("Section C: Third content.");
544 if let Some(idx) = doc.find_content_index("Section C") {
545 doc.insert_paragraph(
546 idx,
547 "Section B: Inserted between A and C via find_content_index().",
548 );
549 }
550
551 // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
552 doc.add_paragraph("").section_break(SectionBreak::NextPage);
553 doc.add_paragraph("8. Section Breaks & Orientation")
554 .style("Heading1");
555 doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
556
557 {
558 let mut tbl = doc
559 .add_table(3, 7)
560 .borders(BorderStyle::Single, 4, "2E75B6");
561 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
562 for (c, h) in headers.iter().enumerate() {
563 tbl.cell(0, c).unwrap().set_text(h);
564 tbl.cell(0, c).unwrap().shading("2E75B6");
565 }
566 let data = [
567 [
568 "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
569 ],
570 [
571 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
572 ],
573 ];
574 for (r, row) in data.iter().enumerate() {
575 for (c, v) in row.iter().enumerate() {
576 tbl.cell(r + 1, c).unwrap().set_text(v);
577 }
578 }
579 }
580
581 doc.add_paragraph("")
582 .section_break(SectionBreak::NextPage)
583 .section_landscape();
584
585 // ── SECTION 9: CUSTOM STYLES ──
586 doc.add_paragraph("9. Custom Styles").style("Heading1");
587 doc.add_style(
588 StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
589 .based_on("Normal")
590 .paragraph_properties({
591 let mut ppr = rdocx_oxml::properties::CT_PPr::default();
592 ppr.shading = Some(rdocx_oxml::properties::CT_Shd {
593 val: "clear".to_string(),
594 color: None,
595 fill: Some("FFF2CC".to_string()),
596 });
597 ppr
598 })
599 .run_properties({
600 let mut rpr = rdocx_oxml::properties::CT_RPr::default();
601 rpr.bold = Some(true);
602 rpr.color = Some("C45911".to_string());
603 rpr
604 }),
605 );
606 doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
607 .style("CustomHighlight");
608
609 doc.add_paragraph("");
610
611 // ── SECTION 10: DOCUMENT INTELLIGENCE ──
612 doc.add_paragraph("10. Document Intelligence API")
613 .style("Heading1");
614
615 let wc = doc.word_count();
616 let hc = doc.headings().len();
617 let ic = doc.images().len();
618 let lc = doc.links().len();
619 doc.add_paragraph(&format!("Word count: {wc}"));
620 doc.add_paragraph(&format!("Heading count: {hc}"));
621 doc.add_paragraph(&format!("Image count: {ic}"));
622 doc.add_paragraph(&format!("Link count: {lc}"));
623
624 let outline = doc.document_outline();
625 doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
626
627 let issues = doc.audit_accessibility();
628 doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
629 for issue in &issues {
630 doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
631 }
632
633 // ── SECTION 11: DOCUMENT MERGING ──
634 doc.add_paragraph("");
635 doc.add_paragraph("11. Document Merging").style("Heading1");
636 {
637 let mut other = Document::new();
638 other.add_paragraph("This paragraph was merged from another document using append().");
639 doc.append(&other);
640 }
641
642 doc.add_paragraph("");
643
644 // ── SUMMARY ──
645 doc.add_paragraph("Summary of Demonstrated Features")
646 .style("Heading1");
647 let features = [
648 "Page setup: size, margins, header/footer distance, gutter",
649 "Document metadata: title, author, subject, keywords",
650 "Headers and footers: text, images, different first page",
651 "Background images (full-page behind text)",
652 "Table of Contents generation with bookmarks",
653 "Text formatting: bold, italic, underline styles, strike, color, size, font",
654 "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
655 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
656 "Pagination controls: keep-with-next, keep-together, widow control",
657 "Bullet and numbered lists with nesting",
658 "Tab stops with dot/underscore/hyphen leaders",
659 "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
660 "Row properties: header rows, exact height, cant-split",
661 "Cell properties: width, no-wrap, vertical alignment",
662 "Inline images and header images",
663 "Placeholder replacement (single and batch)",
664 "Regex-based find and replace",
665 "Content insertion at specific positions",
666 "Section breaks with mixed portrait/landscape",
667 "Custom paragraph and character styles",
668 "Document intelligence: word count, headings, outline, images, links",
669 "Accessibility audit",
670 "Document merging (append)",
671 "Export: DOCX, PDF, HTML, Markdown",
672 ];
673 for f in &features {
674 doc.add_bullet_list_item(f, 0);
675 }
676 doc.add_paragraph("");
677 doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
678 .alignment(Alignment::Center)
679 .shading("E2EFDA")
680 .border_all(BorderStyle::Single, 2, "00AA00");
681
682 doc
683}
684
685// =============================================================================
686// 2. PROPOSAL DOCUMENT — Deep navy + gold scheme
687// =============================================================================
688fn generate_proposal(_samples_dir: &Path) -> Document {
689 let mut doc = Document::new();
690
691 // Colors: Navy #1B2A4A, Gold #C5922E, Light #F4F1EB
692 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
693 doc.set_margins(
694 Length::inches(1.0),
695 Length::inches(1.0),
696 Length::inches(1.0),
697 Length::inches(1.0),
698 );
699 doc.set_title("AI Platform Modernization Proposal");
700 doc.set_author("Walter White");
701 doc.set_subject("Technology Proposal");
702 doc.set_keywords("proposal, AI, modernization, Tensorbee");
703
704 // Banner header
705 let logo_img = create_logo_png(220, 48);
706 let banner = build_header_banner_xml(
707 "rId1",
708 &BannerOpts {
709 bg_color: "1B2A4A",
710 banner_width: 7772400,
711 banner_height: 914400,
712 logo_width: 2011680,
713 logo_height: 438912,
714 logo_x_offset: 295125,
715 logo_y_offset: 237744,
716 },
717 );
718 doc.set_raw_header_with_images(
719 banner,
720 &[("rId1", &logo_img, "logo.png")],
721 rdocx_oxml::header_footer::HdrFtrType::Default,
722 );
723 doc.set_different_first_page(true);
724 let cover_banner = build_header_banner_xml(
725 "rId1",
726 &BannerOpts {
727 bg_color: "C5922E",
728 banner_width: 7772400,
729 banner_height: 914400,
730 logo_width: 2011680,
731 logo_height: 438912,
732 logo_x_offset: 295125,
733 logo_y_offset: 237744,
734 },
735 );
736 doc.set_raw_header_with_images(
737 cover_banner,
738 &[("rId1", &logo_img, "logo.png")],
739 rdocx_oxml::header_footer::HdrFtrType::First,
740 );
741 doc.set_margins(
742 Length::twips(2292),
743 Length::twips(1440),
744 Length::twips(1440),
745 Length::twips(1440),
746 );
747 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
748 doc.set_footer("Tensorbee — Confidential");
749
750 // Custom styles
751 doc.add_style(
752 StyleBuilder::paragraph("ProposalTitle", "Proposal Title")
753 .based_on("Normal")
754 .run_properties({
755 let mut rpr = rdocx_oxml::properties::CT_RPr::default();
756 rpr.bold = Some(true);
757 rpr.font_ascii = Some("Georgia".to_string());
758 rpr.font_hansi = Some("Georgia".to_string());
759 rpr.sz = Some(rdocx_oxml::HalfPoint(56)); // half-points → 28pt
760 rpr.color = Some("1B2A4A".to_string());
761 rpr
762 }),
763 );
764
765 // ── Cover Page ──
766 for _ in 0..4 {
767 doc.add_paragraph("");
768 }
769 doc.add_paragraph("AI Platform Modernization")
770 .style("ProposalTitle")
771 .alignment(Alignment::Center);
772 doc.add_paragraph("")
773 .alignment(Alignment::Center)
774 .border_bottom(BorderStyle::Single, 8, "C5922E");
775 {
776 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
777 p.add_run("Prepared for Global Financial Corp.")
778 .size(14.0)
779 .color("666666")
780 .italic(true);
781 }
782 {
783 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
784 p.add_run("Prepared by: Walter White, CEO — Tensorbee")
785 .size(12.0)
786 .color("888888");
787 }
788 {
789 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
790 p.add_run("February 22, 2026").size(12.0).color("888888");
791 }
792
793 // ── TOC ──
794 doc.add_paragraph("").page_break_before(true);
795 doc.add_paragraph("Table of Contents").style("Heading1");
796 // We'll add a manual-style TOC since the insert_toc goes at a specific position
797 doc.insert_toc(doc.content_count(), 2);
798
799 // ── Executive Summary ──
800 doc.add_paragraph("").page_break_before(true);
801 doc.add_paragraph("Executive Summary").style("Heading1");
802 doc.add_paragraph(
803 "Tensorbee proposes a comprehensive modernization of Global Financial Corp's AI platform, \
804 transitioning from legacy batch-processing systems to a real-time inference architecture. \
805 This transformation will reduce model deployment time from weeks to hours, improve \
806 prediction accuracy by an estimated 23%, and deliver $4.2M in annual operational savings.",
807 )
808 .first_line_indent(Length::inches(0.3));
809 doc.add_paragraph(
810 "The project spans three phases over 18 months, with a total investment of $2.8M. \
811 Tensorbee brings deep expertise in ML infrastructure, having successfully completed \
812 similar transformations for three Fortune 500 financial institutions.",
813 )
814 .first_line_indent(Length::inches(0.3));
815
816 // Key metrics table
817 doc.add_paragraph("");
818 {
819 let mut tbl = doc
820 .add_table(5, 2)
821 .borders(BorderStyle::Single, 4, "1B2A4A")
822 .width_pct(80.0);
823 tbl.cell(0, 0).unwrap().set_text("Key Metric");
824 tbl.cell(0, 0).unwrap().shading("1B2A4A");
825 tbl.cell(0, 1).unwrap().set_text("Value");
826 tbl.cell(0, 1).unwrap().shading("1B2A4A");
827 let rows = [
828 ("Total Investment", "$2.8M"),
829 ("Annual Savings", "$4.2M"),
830 ("ROI (Year 1)", "150%"),
831 ("Timeline", "18 months"),
832 ];
833 for (i, (k, v)) in rows.iter().enumerate() {
834 tbl.cell(i + 1, 0).unwrap().set_text(k);
835 if i % 2 == 0 {
836 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
837 }
838 tbl.cell(i + 1, 1).unwrap().set_text(v);
839 if i % 2 == 0 {
840 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
841 }
842 }
843 }
844
845 // ── Problem Statement ──
846 doc.add_paragraph("").page_break_before(true);
847 doc.add_paragraph("Problem Statement").style("Heading1");
848 doc.add_paragraph(
849 "Global Financial Corp's current AI infrastructure faces three critical challenges:",
850 );
851 doc.add_numbered_list_item("Deployment Latency — New models take 3-4 weeks to deploy to production, compared to the industry benchmark of 2-3 days.", 0);
852 doc.add_numbered_list_item("Scalability Constraints — The batch processing architecture cannot handle real-time inference demands during peak trading hours.", 0);
853 doc.add_numbered_list_item("Technical Debt — Legacy Python 2.7 codebases and manual deployment scripts create reliability risks and slow iteration speed.", 0);
854
855 // ── Proposed Solution ──
856 doc.add_paragraph("").page_break_before(true);
857 doc.add_paragraph("Proposed Solution").style("Heading1");
858
859 doc.add_paragraph("Phase 1: Foundation (Months 1-6)")
860 .style("Heading2");
861 doc.add_bullet_list_item("Deploy Kubernetes-based ML serving infrastructure", 0);
862 doc.add_bullet_list_item("Implement CI/CD pipeline for model deployment", 0);
863 doc.add_bullet_list_item("Migrate top 5 production models to new platform", 0);
864
865 doc.add_paragraph("Phase 2: Expansion (Months 7-12)")
866 .style("Heading2");
867 doc.add_bullet_list_item(
868 "Real-time feature engineering pipeline (Apache Kafka + Flink)",
869 0,
870 );
871 doc.add_bullet_list_item("A/B testing framework for model variants", 0);
872 doc.add_bullet_list_item("Automated model monitoring and drift detection", 0);
873
874 doc.add_paragraph("Phase 3: Optimization (Months 13-18)")
875 .style("Heading2");
876 doc.add_bullet_list_item("GPU inference optimization (TensorRT/ONNX Runtime)", 0);
877 doc.add_bullet_list_item("Multi-region deployment for latency reduction", 0);
878 doc.add_bullet_list_item(
879 "Self-service model deployment portal for data science teams",
880 0,
881 );
882
883 // ── Budget ──
884 doc.add_paragraph("Budget Breakdown").style("Heading1");
885 {
886 let mut tbl = doc
887 .add_table(6, 3)
888 .borders(BorderStyle::Single, 4, "1B2A4A")
889 .width_pct(100.0);
890 tbl.cell(0, 0).unwrap().set_text("Category");
891 tbl.cell(0, 0).unwrap().shading("1B2A4A");
892 tbl.cell(0, 1).unwrap().set_text("Cost");
893 tbl.cell(0, 1).unwrap().shading("1B2A4A");
894 tbl.cell(0, 2).unwrap().set_text("Notes");
895 tbl.cell(0, 2).unwrap().shading("1B2A4A");
896 let items = [
897 (
898 "Infrastructure",
899 "$450,000",
900 "Cloud compute + GPU instances",
901 ),
902 ("Engineering Services", "$1,600,000", "12 FTE x 18 months"),
903 (
904 "Software Licenses",
905 "$350,000",
906 "Kafka, monitoring, CI/CD tools",
907 ),
908 (
909 "Training & Enablement",
910 "$200,000",
911 "Team training, documentation",
912 ),
913 ("Contingency (10%)", "$200,000", "Risk buffer"),
914 ];
915 for (i, (cat, cost, note)) in items.iter().enumerate() {
916 tbl.cell(i + 1, 0).unwrap().set_text(cat);
917 tbl.cell(i + 1, 1).unwrap().set_text(cost);
918 tbl.cell(i + 1, 2).unwrap().set_text(note);
919 if i % 2 == 0 {
920 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
921 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
922 tbl.cell(i + 1, 2).unwrap().shading("F4F1EB");
923 }
924 }
925 }
926
927 // ── Closing ──
928 doc.add_paragraph("");
929 doc.add_paragraph("Next Steps").style("Heading1");
930 doc.add_numbered_list_item(
931 "Schedule technical deep-dive with GFC engineering team (Week 1)",
932 0,
933 );
934 doc.add_numbered_list_item("Finalize scope and sign SOW (Week 2-3)", 0);
935 doc.add_numbered_list_item("Kick off Phase 1 with joint planning session (Week 4)", 0);
936
937 doc.add_paragraph("");
938 {
939 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
940 p.add_run("Walter White")
941 .bold(true)
942 .size(14.0)
943 .color("1B2A4A");
944 }
945 {
946 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
947 p.add_run("CEO, Tensorbee | walter@tensorbee.com")
948 .size(10.0)
949 .color("888888");
950 }
951
952 doc
953}
954
955// =============================================================================
956// 3. QUOTE / BILL OF MATERIALS — Teal + orange scheme
957// =============================================================================
958fn generate_quote(_samples_dir: &Path) -> Document {
959 let mut doc = Document::new();
960
961 // Colors: Teal #008B8B, Dark #1A3C3C, Orange #E07020, Light #F0F8F8
962 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
963 doc.set_margins(
964 Length::inches(0.75),
965 Length::inches(0.75),
966 Length::inches(0.75),
967 Length::inches(0.75),
968 );
969 doc.set_title("Quotation QT-2026-0147");
970 doc.set_author("Walter White");
971 doc.set_keywords("quote, BOM, Tensorbee");
972
973 doc.set_header("Tensorbee — Quotation");
974 doc.set_footer("QT-2026-0147 | Page");
975
976 // ── Header Block ──
977 {
978 let mut p = doc.add_paragraph("").alignment(Alignment::Left);
979 p.add_run("TENSORBEE")
980 .bold(true)
981 .size(28.0)
982 .color("008B8B")
983 .font("Helvetica");
984 }
985 doc.add_paragraph("123 Innovation Drive, Suite 400")
986 .alignment(Alignment::Left);
987 doc.add_paragraph("San Francisco, CA 94105 | +1 (415) 555-0199")
988 .alignment(Alignment::Left);
989 doc.add_paragraph("")
990 .border_bottom(BorderStyle::Single, 8, "008B8B");
991
992 // Quote meta
993 doc.add_paragraph("");
994 {
995 let mut tbl = doc.add_table(4, 4).width_pct(100.0);
996 tbl.cell(0, 0).unwrap().set_text("QUOTATION");
997 tbl.cell(0, 0).unwrap().shading("008B8B").grid_span(2);
998 tbl.cell(0, 2).unwrap().set_text("DATE");
999 tbl.cell(0, 2).unwrap().shading("008B8B");
1000 tbl.cell(0, 3).unwrap().set_text("VALID UNTIL");
1001 tbl.cell(0, 3).unwrap().shading("008B8B");
1002 tbl.cell(1, 0).unwrap().set_text("Quote #:");
1003 tbl.cell(1, 1).unwrap().set_text("QT-2026-0147");
1004 tbl.cell(1, 2).unwrap().set_text("Feb 22, 2026");
1005 tbl.cell(1, 3).unwrap().set_text("Mar 22, 2026");
1006 tbl.cell(2, 0).unwrap().set_text("Prepared by:");
1007 tbl.cell(2, 1).unwrap().set_text("Walter White");
1008 tbl.cell(2, 2).unwrap().set_text("Payment:");
1009 tbl.cell(2, 3).unwrap().set_text("Net 30");
1010 tbl.cell(3, 0).unwrap().set_text("Customer:");
1011 tbl.cell(3, 1).unwrap().set_text("Meridian Dynamics LLC");
1012 tbl.cell(3, 2).unwrap().set_text("Currency:");
1013 tbl.cell(3, 3).unwrap().set_text("USD");
1014 }
1015
1016 doc.add_paragraph("");
1017
1018 // ── Bill of Materials ──
1019 {
1020 let mut p = doc.add_paragraph("");
1021 p.add_run("Bill of Materials")
1022 .bold(true)
1023 .size(16.0)
1024 .color("1A3C3C");
1025 }
1026 doc.add_paragraph("");
1027
1028 {
1029 let mut tbl = doc
1030 .add_table(10, 6)
1031 .borders(BorderStyle::Single, 2, "008B8B")
1032 .width_pct(100.0)
1033 .layout_fixed();
1034 // Headers
1035 let hdrs = [
1036 "#",
1037 "Part Number",
1038 "Description",
1039 "Qty",
1040 "Unit Price",
1041 "Total",
1042 ];
1043 for (c, h) in hdrs.iter().enumerate() {
1044 tbl.cell(0, c).unwrap().set_text(h);
1045 tbl.cell(0, c).unwrap().shading("008B8B");
1046 }
1047
1048 let items: Vec<(&str, &str, &str, &str, &str)> = vec![
1049 (
1050 "TB-GPU-A100",
1051 "NVIDIA A100 80GB GPU",
1052 "4",
1053 "$12,500.00",
1054 "$50,000.00",
1055 ),
1056 (
1057 "TB-SRV-R750",
1058 "Dell R750xa Server Chassis",
1059 "2",
1060 "$8,200.00",
1061 "$16,400.00",
1062 ),
1063 (
1064 "TB-RAM-256G",
1065 "256GB DDR5 ECC Memory Module",
1066 "8",
1067 "$890.00",
1068 "$7,120.00",
1069 ),
1070 (
1071 "TB-SSD-3840",
1072 "3.84TB NVMe U.2 SSD",
1073 "8",
1074 "$1,150.00",
1075 "$9,200.00",
1076 ),
1077 (
1078 "TB-NET-CX7",
1079 "ConnectX-7 200GbE NIC",
1080 "4",
1081 "$1,800.00",
1082 "$7,200.00",
1083 ),
1084 (
1085 "TB-SW-48P",
1086 "48-Port 100GbE Switch",
1087 "1",
1088 "$22,000.00",
1089 "$22,000.00",
1090 ),
1091 (
1092 "TB-CAB-RACK",
1093 "42U Server Rack + PDU",
1094 "1",
1095 "$4,500.00",
1096 "$4,500.00",
1097 ),
1098 (
1099 "TB-SVC-INST",
1100 "Installation & Configuration",
1101 "1",
1102 "$8,500.00",
1103 "$8,500.00",
1104 ),
1105 (
1106 "TB-SVC-SUPP",
1107 "3-Year Premium Support",
1108 "1",
1109 "$15,000.00",
1110 "$15,000.00",
1111 ),
1112 ];
1113
1114 for (i, (pn, desc, qty, unit, total)) in items.iter().enumerate() {
1115 let row = i + 1;
1116 tbl.cell(row, 0).unwrap().set_text(&format!("{}", i + 1));
1117 tbl.cell(row, 1).unwrap().set_text(pn);
1118 tbl.cell(row, 2).unwrap().set_text(desc);
1119 tbl.cell(row, 3).unwrap().set_text(qty);
1120 tbl.cell(row, 4).unwrap().set_text(unit);
1121 tbl.cell(row, 5).unwrap().set_text(total);
1122 if i % 2 == 0 {
1123 for c in 0..6 {
1124 tbl.cell(row, c).unwrap().shading("F0F8F8");
1125 }
1126 }
1127 }
1128 }
1129
1130 doc.add_paragraph("");
1131
1132 // ── Totals ──
1133 {
1134 let mut tbl = doc
1135 .add_table(4, 2)
1136 .borders(BorderStyle::Single, 2, "008B8B")
1137 .width(Length::inches(3.5))
1138 .alignment(Alignment::Right);
1139 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1140 tbl.cell(0, 1).unwrap().set_text("$139,920.00");
1141 tbl.cell(1, 0).unwrap().set_text("Shipping & Handling");
1142 tbl.cell(1, 1).unwrap().set_text("$2,500.00");
1143 tbl.cell(2, 0).unwrap().set_text("Tax (8.625%)");
1144 tbl.cell(2, 1).unwrap().set_text("$12,068.10");
1145 tbl.cell(3, 0).unwrap().set_text("TOTAL");
1146 tbl.cell(3, 0).unwrap().shading("E07020");
1147 tbl.cell(3, 1).unwrap().set_text("$154,488.10");
1148 tbl.cell(3, 1).unwrap().shading("E07020");
1149 }
1150
1151 doc.add_paragraph("");
1152
1153 // ── Terms & Conditions ──
1154 {
1155 let mut p = doc.add_paragraph("");
1156 p.add_run("Terms & Conditions")
1157 .bold(true)
1158 .size(14.0)
1159 .color("1A3C3C");
1160 }
1161 doc.add_numbered_list_item(
1162 "This quotation is valid for 30 calendar days from the date of issue.",
1163 0,
1164 );
1165 doc.add_numbered_list_item(
1166 "All prices are in USD and exclusive of applicable taxes unless stated.",
1167 0,
1168 );
1169 doc.add_numbered_list_item("Standard lead time is 4-6 weeks from PO receipt.", 0);
1170 doc.add_numbered_list_item(
1171 "Payment terms: Net 30 from invoice date. 2% discount for payment within 10 days.",
1172 0,
1173 );
1174 doc.add_numbered_list_item(
1175 "Warranty: 3-year manufacturer warranty on all hardware components.",
1176 0,
1177 );
1178 doc.add_numbered_list_item(
1179 "Returns subject to 15% restocking fee if initiated after 14 days.",
1180 0,
1181 );
1182
1183 doc.add_paragraph("");
1184 doc.add_paragraph("")
1185 .border_bottom(BorderStyle::Single, 4, "008B8B");
1186 doc.add_paragraph("");
1187 {
1188 let mut p = doc.add_paragraph("");
1189 p.add_run("Accepted by: ").bold(true);
1190 p.add_run("___________________________ Date: ___________");
1191 }
1192 {
1193 let mut p = doc.add_paragraph("");
1194 p.add_run("Print Name: ").bold(true);
1195 p.add_run("___________________________ Title: ___________");
1196 }
1197
1198 doc
1199}
1200
1201// =============================================================================
1202// 4. INVOICE — Crimson + charcoal scheme
1203// =============================================================================
1204fn generate_invoice(_samples_dir: &Path) -> Document {
1205 let mut doc = Document::new();
1206
1207 // Colors: Crimson #B22222, Charcoal #333333, Light #FAF0F0
1208 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1209 doc.set_margins(
1210 Length::inches(0.75),
1211 Length::inches(0.75),
1212 Length::inches(0.75),
1213 Length::inches(0.75),
1214 );
1215 doc.set_title("Invoice INV-2026-0392");
1216 doc.set_author("Walter White");
1217 doc.set_keywords("invoice, Tensorbee");
1218
1219 doc.set_footer("Tensorbee — Thank you for your business!");
1220
1221 // ── Company & Invoice Header ──
1222 {
1223 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1224 // Company name left, INVOICE right
1225 {
1226 let mut cell = tbl.cell(0, 0).unwrap();
1227 let mut p = cell.add_paragraph("");
1228 p.add_run("TENSORBEE")
1229 .bold(true)
1230 .size(32.0)
1231 .color("B22222")
1232 .font("Helvetica");
1233 }
1234 {
1235 let mut cell = tbl.cell(0, 1).unwrap();
1236 let mut p = cell.add_paragraph("");
1237 p.add_run("INVOICE").bold(true).size(32.0).color("333333");
1238 }
1239 tbl.cell(1, 0)
1240 .unwrap()
1241 .set_text("123 Innovation Drive, Suite 400");
1242 tbl.cell(1, 1).unwrap().set_text("Invoice #: INV-2026-0392");
1243 tbl.cell(2, 0).unwrap().set_text("San Francisco, CA 94105");
1244 tbl.cell(2, 1).unwrap().set_text("Date: February 22, 2026");
1245 tbl.cell(3, 0).unwrap().set_text("walter@tensorbee.com");
1246 tbl.cell(3, 1).unwrap().set_text("Due Date: March 24, 2026");
1247 }
1248
1249 doc.add_paragraph("")
1250 .border_bottom(BorderStyle::Thick, 8, "B22222");
1251 doc.add_paragraph("");
1252
1253 // ── Bill To / Ship To ──
1254 {
1255 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1256 tbl.cell(0, 0).unwrap().set_text("BILL TO");
1257 tbl.cell(0, 0).unwrap().shading("B22222");
1258 tbl.cell(0, 1).unwrap().set_text("SHIP TO");
1259 tbl.cell(0, 1).unwrap().shading("B22222");
1260 tbl.cell(1, 0).unwrap().set_text("Meridian Dynamics LLC");
1261 tbl.cell(1, 1).unwrap().set_text("Meridian Dynamics LLC");
1262 tbl.cell(2, 0).unwrap().set_text("456 Enterprise Blvd");
1263 tbl.cell(2, 1).unwrap().set_text("Attn: Server Room B");
1264 tbl.cell(3, 0).unwrap().set_text("Austin, TX 78701");
1265 tbl.cell(3, 1)
1266 .unwrap()
1267 .set_text("456 Enterprise Blvd, Austin, TX 78701");
1268 }
1269
1270 doc.add_paragraph("");
1271
1272 // ── Line Items ──
1273 {
1274 let mut tbl = doc
1275 .add_table(8, 5)
1276 .borders(BorderStyle::Single, 2, "B22222")
1277 .width_pct(100.0);
1278 let hdrs = ["Description", "Qty", "Unit Price", "Tax", "Amount"];
1279 for (c, h) in hdrs.iter().enumerate() {
1280 tbl.cell(0, c).unwrap().set_text(h);
1281 tbl.cell(0, c).unwrap().shading("B22222");
1282 }
1283 let items = [
1284 (
1285 "ML Infrastructure Setup — Phase 1",
1286 "1",
1287 "$45,000.00",
1288 "$3,881.25",
1289 "$48,881.25",
1290 ),
1291 (
1292 "Data Pipeline Development (160 hrs)",
1293 "160",
1294 "$225.00",
1295 "$3,105.00",
1296 "$39,105.00",
1297 ),
1298 (
1299 "GPU Cluster Configuration",
1300 "1",
1301 "$12,000.00",
1302 "$1,035.00",
1303 "$13,035.00",
1304 ),
1305 (
1306 "API Gateway Implementation",
1307 "1",
1308 "$18,500.00",
1309 "$1,595.63",
1310 "$20,095.63",
1311 ),
1312 (
1313 "Load Testing & QA (80 hrs)",
1314 "80",
1315 "$195.00",
1316 "$1,345.50",
1317 "$16,945.50",
1318 ),
1319 (
1320 "Documentation & Training",
1321 "1",
1322 "$8,000.00",
1323 "$690.00",
1324 "$8,690.00",
1325 ),
1326 (
1327 "Project Management (3 months)",
1328 "3",
1329 "$6,500.00",
1330 "$1,679.63",
1331 "$21,179.63",
1332 ),
1333 ];
1334 for (i, (desc, qty, unit, tax, amt)) in items.iter().enumerate() {
1335 let r = i + 1;
1336 tbl.cell(r, 0).unwrap().set_text(desc);
1337 tbl.cell(r, 1).unwrap().set_text(qty);
1338 tbl.cell(r, 2).unwrap().set_text(unit);
1339 tbl.cell(r, 3).unwrap().set_text(tax);
1340 tbl.cell(r, 4).unwrap().set_text(amt);
1341 if i % 2 == 0 {
1342 for c in 0..5 {
1343 tbl.cell(r, c).unwrap().shading("FAF0F0");
1344 }
1345 }
1346 }
1347 }
1348
1349 doc.add_paragraph("");
1350
1351 // ── Totals ──
1352 {
1353 let mut tbl = doc
1354 .add_table(4, 2)
1355 .borders(BorderStyle::Single, 2, "B22222")
1356 .width(Length::inches(3.0))
1357 .alignment(Alignment::Right);
1358 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1359 tbl.cell(0, 1).unwrap().set_text("$154,600.00");
1360 tbl.cell(1, 0).unwrap().set_text("Tax (8.625%)");
1361 tbl.cell(1, 1).unwrap().set_text("$13,334.25");
1362 tbl.cell(2, 0).unwrap().set_text("Discount (5%)");
1363 tbl.cell(2, 1).unwrap().set_text("-$7,730.00");
1364 tbl.cell(3, 0).unwrap().set_text("AMOUNT DUE");
1365 tbl.cell(3, 0).unwrap().shading("B22222");
1366 tbl.cell(3, 1).unwrap().set_text("$160,204.25");
1367 tbl.cell(3, 1).unwrap().shading("B22222");
1368 }
1369
1370 doc.add_paragraph("");
1371 doc.add_paragraph("");
1372
1373 // ── Payment Details ──
1374 {
1375 let mut p = doc.add_paragraph("");
1376 p.add_run("Payment Details")
1377 .bold(true)
1378 .size(14.0)
1379 .color("333333");
1380 }
1381 doc.add_paragraph("Bank: Silicon Valley Bank")
1382 .indent_left(Length::inches(0.3));
1383 doc.add_paragraph("Account: Tensorbee Inc. — 0847-2953-1120")
1384 .indent_left(Length::inches(0.3));
1385 doc.add_paragraph("Routing: 121140399")
1386 .indent_left(Length::inches(0.3));
1387 doc.add_paragraph("Swift: SVBKUS6S")
1388 .indent_left(Length::inches(0.3));
1389
1390 doc.add_paragraph("");
1391
1392 doc.add_paragraph("Please include invoice number INV-2026-0392 in the payment reference.")
1393 .shading("FAF0F0")
1394 .border_all(BorderStyle::Single, 2, "B22222");
1395
1396 doc
1397}
1398
1399// =============================================================================
1400// 5. REPORT — Forest green + earth tones, with images & hierarchical sections
1401// =============================================================================
1402fn generate_report(_samples_dir: &Path) -> Document {
1403 let mut doc = Document::new();
1404
1405 // Colors: Forest #2D5016, Sage #6B8E23, Earth #8B7355, Cream #FFFAF0
1406 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1407 doc.set_margins(
1408 Length::inches(1.0),
1409 Length::inches(1.0),
1410 Length::inches(1.0),
1411 Length::inches(1.0),
1412 );
1413 doc.set_title("Q4 2025 Environmental Impact Report");
1414 doc.set_author("Walter White");
1415 doc.set_subject("Quarterly Environmental Report");
1416 doc.set_keywords("environment, sustainability, report, Tensorbee");
1417
1418 doc.set_different_first_page(true);
1419 doc.set_first_page_header("");
1420 doc.set_header("Tensorbee — Q4 2025 Environmental Impact Report");
1421 doc.set_footer("CONFIDENTIAL — Page");
1422
1423 // ── Cover ──
1424 let cover_bg = create_sample_png(612, 792, [20, 50, 15]);
1425 doc.add_background_image(&cover_bg, "report_cover.png");
1426
1427 for _ in 0..5 {
1428 doc.add_paragraph("");
1429 }
1430 {
1431 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1432 p.add_run("Q4 2025")
1433 .bold(true)
1434 .size(48.0)
1435 .color("FFFFFF")
1436 .font("Georgia");
1437 }
1438 {
1439 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1440 p.add_run("Environmental Impact Report")
1441 .size(24.0)
1442 .color("90EE90")
1443 .font("Georgia")
1444 .italic(true);
1445 }
1446 doc.add_paragraph("");
1447 {
1448 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1449 p.add_run("Tensorbee — Sustainability Division")
1450 .size(14.0)
1451 .color("C0C0C0");
1452 }
1453 {
1454 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1455 p.add_run("Prepared by Walter White, Chief Sustainability Officer")
1456 .size(11.0)
1457 .color("AAAAAA");
1458 }
1459
1460 // ── TOC ──
1461 doc.add_paragraph("").page_break_before(true);
1462 doc.insert_toc(doc.content_count(), 3);
1463
1464 // ── Section 1: Executive Overview ──
1465 doc.add_paragraph("").page_break_before(true);
1466 doc.add_paragraph("Executive Overview").style("Heading1");
1467 doc.add_paragraph(
1468 "This report presents Tensorbee's environmental performance for Q4 2025. Our \
1469 sustainability initiatives have yielded a 34% reduction in carbon emissions compared \
1470 to Q4 2024, exceeding our target of 25%. Key achievements include the transition to \
1471 100% renewable energy in our primary data centers and the launch of our carbon offset \
1472 marketplace.",
1473 )
1474 .first_line_indent(Length::inches(0.3));
1475
1476 // Image: performance chart
1477 let chart_img = create_chart_png(400, 200);
1478 doc.add_paragraph("");
1479 doc.add_picture(
1480 &chart_img,
1481 "performance_chart.png",
1482 Length::inches(5.0),
1483 Length::inches(2.5),
1484 )
1485 .alignment(Alignment::Center);
1486 {
1487 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1488 p.add_run("Figure 1: Quarterly Carbon Emissions (tonnes CO2e)")
1489 .italic(true)
1490 .size(9.0)
1491 .color("666666");
1492 }
1493
1494 // ── Section 2: Energy Consumption ──
1495 doc.add_paragraph("").page_break_before(true);
1496 doc.add_paragraph("Energy Consumption").style("Heading1");
1497
1498 doc.add_paragraph("Data Center Operations")
1499 .style("Heading2");
1500 doc.add_paragraph(
1501 "Our three primary data centers consumed a combined 4.2 GWh in Q4 2025, \
1502 a 12% reduction from Q3 2025 driven by improved cooling efficiency and \
1503 server consolidation.",
1504 );
1505
1506 // Data table
1507 {
1508 let mut tbl = doc
1509 .add_table(5, 4)
1510 .borders(BorderStyle::Single, 4, "2D5016")
1511 .width_pct(100.0);
1512 let hdrs = ["Data Center", "Capacity (MW)", "Usage (GWh)", "PUE"];
1513 for (c, h) in hdrs.iter().enumerate() {
1514 tbl.cell(0, c).unwrap().set_text(h);
1515 tbl.cell(0, c).unwrap().shading("2D5016");
1516 }
1517 let rows = [
1518 ("San Francisco (Primary)", "3.2", "1.8", "1.12"),
1519 ("Dublin (EU)", "2.1", "1.2", "1.18"),
1520 ("Singapore (APAC)", "1.8", "1.2", "1.24"),
1521 ("TOTAL", "7.1", "4.2", "1.17 avg"),
1522 ];
1523 for (i, (dc, cap, use_, pue)) in rows.iter().enumerate() {
1524 tbl.cell(i + 1, 0).unwrap().set_text(dc);
1525 tbl.cell(i + 1, 1).unwrap().set_text(cap);
1526 tbl.cell(i + 1, 2).unwrap().set_text(use_);
1527 tbl.cell(i + 1, 3).unwrap().set_text(pue);
1528 if i == 3 {
1529 for c in 0..4 {
1530 tbl.cell(i + 1, c).unwrap().shading("E2EFDA");
1531 }
1532 }
1533 }
1534 }
1535
1536 doc.add_paragraph("");
1537
1538 doc.add_paragraph("Renewable Energy Mix").style("Heading2");
1539 doc.add_paragraph("Breakdown of energy sources across all facilities:");
1540
1541 doc.add_bullet_list_item("Solar PV: 42% (1.76 GWh)", 0);
1542 doc.add_bullet_list_item("Wind Power Purchase Agreements: 38% (1.60 GWh)", 0);
1543 doc.add_bullet_list_item("Hydroelectric: 12% (0.50 GWh)", 0);
1544 doc.add_bullet_list_item("Grid (non-renewable): 8% (0.34 GWh)", 0);
1545
1546 // ── Section 3: Water & Waste ──
1547 doc.add_paragraph("Water & Waste Management")
1548 .style("Heading1");
1549
1550 doc.add_paragraph("Water Usage").style("Heading2");
1551 doc.add_paragraph(
1552 "Total water consumption was 12.4 million gallons, a 15% reduction from Q3. \
1553 Our closed-loop cooling systems now recycle 78% of water used in cooling operations.",
1554 );
1555
1556 doc.add_paragraph("Waste Reduction").style("Heading2");
1557 doc.add_paragraph("E-waste management results for Q4:")
1558 .keep_with_next(true);
1559 {
1560 let mut tbl = doc
1561 .add_table(4, 3)
1562 .borders(BorderStyle::Single, 2, "6B8E23")
1563 .width_pct(80.0);
1564 tbl.cell(0, 0).unwrap().set_text("Category");
1565 tbl.cell(0, 0).unwrap().shading("6B8E23");
1566 tbl.cell(0, 1).unwrap().set_text("Weight (kg)");
1567 tbl.cell(0, 1).unwrap().shading("6B8E23");
1568 tbl.cell(0, 2).unwrap().set_text("Recycled %");
1569 tbl.cell(0, 2).unwrap().shading("6B8E23");
1570 let rows = [
1571 ("Server Hardware", "2,450", "94%"),
1572 ("Networking Equipment", "820", "91%"),
1573 ("Storage Media", "340", "99%"),
1574 ];
1575 for (i, (cat, wt, pct)) in rows.iter().enumerate() {
1576 tbl.cell(i + 1, 0).unwrap().set_text(cat);
1577 tbl.cell(i + 1, 1).unwrap().set_text(wt);
1578 tbl.cell(i + 1, 2).unwrap().set_text(pct);
1579 }
1580 }
1581
1582 // ── Section 4: Initiatives ──
1583 doc.add_paragraph("").page_break_before(true);
1584 doc.add_paragraph("Q1 2026 Initiatives").style("Heading1");
1585
1586 doc.add_paragraph("Planned Programs").style("Heading2");
1587 doc.add_numbered_list_item(
1588 "Deploy on-site battery storage at SF data center (2 MWh capacity)",
1589 0,
1590 );
1591 doc.add_numbered_list_item("Pilot immersion cooling in Dublin facility", 0);
1592 doc.add_numbered_list_item("Launch employee commute carbon offset program", 0);
1593 doc.add_numbered_list_item("Achieve ISO 14001 certification for Singapore facility", 0);
1594
1595 doc.add_paragraph("Investment Targets").style("Heading2");
1596 doc.add_paragraph("Sustainability CapEx allocation for FY2026:")
1597 .keep_with_next(true);
1598 {
1599 let mut tbl = doc
1600 .add_table(5, 2)
1601 .borders(BorderStyle::Single, 4, "2D5016");
1602 tbl.cell(0, 0).unwrap().set_text("Initiative");
1603 tbl.cell(0, 0).unwrap().shading("2D5016");
1604 tbl.cell(0, 1).unwrap().set_text("Budget");
1605 tbl.cell(0, 1).unwrap().shading("2D5016");
1606 let rows = [
1607 ("Battery Storage", "$1.2M"),
1608 ("Immersion Cooling Pilot", "$800K"),
1609 ("Solar Panel Expansion", "$2.1M"),
1610 ("Carbon Credits", "$500K"),
1611 ];
1612 for (i, (init, budget)) in rows.iter().enumerate() {
1613 tbl.cell(i + 1, 0).unwrap().set_text(init);
1614 tbl.cell(i + 1, 1).unwrap().set_text(budget);
1615 if i % 2 == 0 {
1616 tbl.cell(i + 1, 0).unwrap().shading("FFFAF0");
1617 tbl.cell(i + 1, 1).unwrap().shading("FFFAF0");
1618 }
1619 }
1620 }
1621
1622 // Image: sustainability roadmap
1623 let roadmap_img = create_sample_png(500, 100, [40, 80, 30]);
1624 doc.add_paragraph("");
1625 doc.add_picture(
1626 &roadmap_img,
1627 "roadmap.png",
1628 Length::inches(6.0),
1629 Length::inches(1.2),
1630 )
1631 .alignment(Alignment::Center);
1632 {
1633 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1634 p.add_run("Figure 2: 2026 Sustainability Roadmap")
1635 .italic(true)
1636 .size(9.0)
1637 .color("666666");
1638 }
1639
1640 doc.add_paragraph("");
1641 doc.add_paragraph(
1642 "For questions about this report, contact Walter White at sustainability@tensorbee.com.",
1643 )
1644 .shading("FFFAF0")
1645 .border_all(BorderStyle::Single, 2, "2D5016");
1646
1647 doc
1648}
1649
1650// =============================================================================
1651// 6. LETTER — Slate blue + silver scheme
1652// =============================================================================
1653fn generate_letter(_samples_dir: &Path) -> Document {
1654 let mut doc = Document::new();
1655
1656 // Colors: Slate #4A5568, Blue accent #3182CE
1657 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1658 doc.set_margins(
1659 Length::inches(1.25),
1660 Length::inches(1.25),
1661 Length::inches(1.25),
1662 Length::inches(1.25),
1663 );
1664 doc.set_title("Business Letter");
1665 doc.set_author("Walter White");
1666
1667 // Banner header using raw XML
1668 let logo_img = create_logo_png(220, 48);
1669 let banner = build_header_banner_xml(
1670 "rId1",
1671 &BannerOpts {
1672 bg_color: "4A5568",
1673 banner_width: 7772400,
1674 banner_height: 731520, // ~0.8"
1675 logo_width: 2011680,
1676 logo_height: 438912,
1677 logo_x_offset: 295125,
1678 logo_y_offset: 146304,
1679 },
1680 );
1681 doc.set_raw_header_with_images(
1682 banner,
1683 &[("rId1", &logo_img, "logo.png")],
1684 rdocx_oxml::header_footer::HdrFtrType::Default,
1685 );
1686 doc.set_margins(
1687 Length::twips(2000),
1688 Length::twips(1800),
1689 Length::twips(1440),
1690 Length::twips(1800),
1691 );
1692 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
1693
1694 // Footer with contact
1695 doc.set_footer(
1696 "Tensorbee | 123 Innovation Drive, Suite 400 | San Francisco, CA 94105 | tensorbee.com",
1697 );
1698
1699 // ── Sender Address ──
1700 doc.add_paragraph("Walter White");
1701 doc.add_paragraph("Chief Executive Officer");
1702 doc.add_paragraph("Tensorbee");
1703 doc.add_paragraph("123 Innovation Drive, Suite 400");
1704 doc.add_paragraph("San Francisco, CA 94105");
1705 doc.add_paragraph("");
1706 doc.add_paragraph("February 22, 2026");
1707
1708 doc.add_paragraph("");
1709
1710 // ── Recipient ──
1711 doc.add_paragraph("Ms. Sarah Chen");
1712 doc.add_paragraph("VP of Engineering");
1713 doc.add_paragraph("Meridian Dynamics LLC");
1714 doc.add_paragraph("456 Enterprise Boulevard");
1715 doc.add_paragraph("Austin, TX 78701");
1716
1717 doc.add_paragraph("");
1718
1719 // ── Subject ──
1720 {
1721 let mut p = doc.add_paragraph("");
1722 p.add_run("Re: Strategic Technology Partnership — Phase 2 Expansion")
1723 .bold(true);
1724 }
1725
1726 doc.add_paragraph("");
1727
1728 // ── Body ──
1729 doc.add_paragraph("Dear Ms. Chen,");
1730 doc.add_paragraph("");
1731
1732 doc.add_paragraph(
1733 "I am writing to express Tensorbee's enthusiasm for expanding our strategic technology \
1734 partnership with Meridian Dynamics. Following the successful completion of Phase 1, \
1735 which delivered a 40% improvement in your ML inference pipeline throughput, we believe \
1736 the foundation is firmly established for an ambitious Phase 2 engagement.",
1737 )
1738 .first_line_indent(Length::inches(0.5));
1739
1740 doc.add_paragraph(
1741 "During our recent executive review, your team highlighted three priority areas for \
1742 the next phase of collaboration:",
1743 )
1744 .first_line_indent(Length::inches(0.5));
1745
1746 doc.add_numbered_list_item(
1747 "Real-time anomaly detection for your financial transaction monitoring system, \
1748 targeting sub-100ms latency at 50,000 transactions per second.",
1749 0,
1750 );
1751 doc.add_numbered_list_item(
1752 "Federated learning infrastructure to enable model training across your distributed \
1753 data centers without centralizing sensitive financial data.",
1754 0,
1755 );
1756 doc.add_numbered_list_item(
1757 "MLOps automation to reduce your model deployment cycle from the current 5 days \
1758 to under 4 hours.",
1759 0,
1760 );
1761
1762 doc.add_paragraph(
1763 "Our engineering team has prepared a detailed technical proposal addressing each of \
1764 these areas. We have allocated a dedicated team of eight senior engineers, led by \
1765 our CTO, to ensure continuity with the Phase 1 team your organization has already \
1766 built a productive working relationship with.",
1767 )
1768 .first_line_indent(Length::inches(0.5));
1769
1770 doc.add_paragraph(
1771 "I would welcome the opportunity to present our Phase 2 proposal in person. My \
1772 assistant will reach out to coordinate a meeting at your convenience during the \
1773 first week of March.",
1774 )
1775 .first_line_indent(Length::inches(0.5));
1776
1777 doc.add_paragraph("");
1778
1779 doc.add_paragraph("Warm regards,");
1780 doc.add_paragraph("");
1781 doc.add_paragraph("");
1782
1783 // Signature line
1784 doc.add_paragraph("")
1785 .border_bottom(BorderStyle::Single, 4, "4A5568");
1786 {
1787 let mut p = doc.add_paragraph("");
1788 p.add_run("Walter White").bold(true).size(12.0);
1789 }
1790 {
1791 let mut p = doc.add_paragraph("");
1792 p.add_run("Chief Executive Officer, Tensorbee")
1793 .italic(true)
1794 .size(10.0)
1795 .color("666666");
1796 }
1797 {
1798 let mut p = doc.add_paragraph("");
1799 p.add_run("walter@tensorbee.com | +1 (415) 555-0199")
1800 .size(10.0)
1801 .color("888888");
1802 }
1803
1804 doc
1805}
1806
1807// =============================================================================
1808// 7. EMPLOYMENT CONTRACT — Purple + charcoal scheme
1809// =============================================================================
1810fn generate_contract(_samples_dir: &Path) -> Document {
1811 let mut doc = Document::new();
1812
1813 // Colors: Purple #5B2C6F, Plum #8E44AD, Gray #2C3E50
1814 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1815 doc.set_margins(
1816 Length::inches(1.25),
1817 Length::inches(1.0),
1818 Length::inches(1.0),
1819 Length::inches(1.0),
1820 );
1821 doc.set_title("Employment Agreement");
1822 doc.set_author("Tensorbee HR Department");
1823 doc.set_subject("Employment Contract — Walter White");
1824 doc.set_keywords("employment, contract, agreement, Tensorbee");
1825
1826 doc.set_header("TENSORBEE — Employment Agreement");
1827 doc.set_footer("Employment Agreement — Walter White — Page");
1828
1829 // ── Title Block ──
1830 doc.add_paragraph("")
1831 .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1832 {
1833 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1834 p.add_run("EMPLOYMENT AGREEMENT")
1835 .bold(true)
1836 .size(24.0)
1837 .color("5B2C6F")
1838 .font("Georgia");
1839 }
1840 doc.add_paragraph("")
1841 .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1842 doc.add_paragraph("");
1843
1844 // ── Parties ──
1845 doc.add_paragraph(
1846 "This Employment Agreement (\"Agreement\") is entered into as of February 22, 2026 \
1847 (\"Effective Date\"), by and between:",
1848 );
1849
1850 doc.add_paragraph("");
1851
1852 {
1853 let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1854 p.add_run("EMPLOYER: ").bold(true);
1855 p.add_run(
1856 "Tensorbee, Inc., a Delaware corporation with its principal place of business at \
1857 123 Innovation Drive, Suite 400, San Francisco, CA 94105 (\"Company\")",
1858 );
1859 }
1860 doc.add_paragraph("");
1861 {
1862 let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1863 p.add_run("EMPLOYEE: ").bold(true);
1864 p.add_run(
1865 "Walter White, residing at 308 Negra Arroyo Lane, Albuquerque, NM 87104 (\"Employee\")",
1866 );
1867 }
1868
1869 doc.add_paragraph("");
1870 doc.add_paragraph("The Company and Employee are collectively referred to as the \"Parties.\"");
1871 doc.add_paragraph("");
1872
1873 // ── Article 1: Position and Duties ──
1874 doc.add_paragraph("Article 1 — Position and Duties")
1875 .style("Heading1");
1876 {
1877 let mut p = doc.add_paragraph("");
1878 p.add_run("1.1 ").bold(true);
1879 p.add_run("The Company hereby employs the Employee as ");
1880 p.add_run("Chief Executive Officer (CEO)")
1881 .bold(true)
1882 .italic(true);
1883 p.add_run(", reporting directly to the Board of Directors.");
1884 }
1885 {
1886 let mut p = doc.add_paragraph("");
1887 p.add_run("1.2 ").bold(true);
1888 p.add_run("The Employee shall devote full working time, attention, and best efforts to \
1889 the performance of duties as reasonably assigned by the Board, including but not limited to:");
1890 }
1891 doc.add_bullet_list_item(
1892 "Setting and executing the Company's strategic vision and business plan",
1893 0,
1894 );
1895 doc.add_bullet_list_item(
1896 "Overseeing all operations, engineering, and commercial activities",
1897 0,
1898 );
1899 doc.add_bullet_list_item(
1900 "Representing the Company to investors, customers, and the public",
1901 0,
1902 );
1903 doc.add_bullet_list_item("Recruiting, developing, and retaining key talent", 0);
1904
1905 {
1906 let mut p = doc.add_paragraph("");
1907 p.add_run("1.3 ").bold(true);
1908 p.add_run(
1909 "The Employee's primary work location shall be the Company's San Francisco \
1910 headquarters, with reasonable travel as required by business needs.",
1911 );
1912 }
1913
1914 // ── Article 2: Compensation ──
1915 doc.add_paragraph("Article 2 — Compensation and Benefits")
1916 .style("Heading1");
1917 {
1918 let mut p = doc.add_paragraph("");
1919 p.add_run("2.1 Base Salary. ").bold(true);
1920 p.add_run("The Company shall pay the Employee an annual base salary of ");
1921 p.add_run("$375,000.00").bold(true);
1922 p.add_run(" (Three Hundred Seventy-Five Thousand Dollars), payable in accordance with the \
1923 Company's standard payroll schedule, less applicable withholdings and deductions.");
1924 }
1925 {
1926 let mut p = doc.add_paragraph("");
1927 p.add_run("2.2 Annual Bonus. ").bold(true);
1928 p.add_run("The Employee shall be eligible for an annual performance bonus of up to ");
1929 p.add_run("40%").bold(true);
1930 p.add_run(
1931 " of base salary, based on achievement of mutually agreed performance objectives.",
1932 );
1933 }
1934 {
1935 let mut p = doc.add_paragraph("");
1936 p.add_run("2.3 Equity. ").bold(true);
1937 p.add_run("Subject to Board approval, the Employee shall receive a stock option grant of ");
1938 p.add_run("500,000 shares").bold(true);
1939 p.add_run(
1940 " of the Company's common stock, vesting over four (4) years with a one-year cliff, \
1941 at an exercise price equal to the fair market value on the date of grant.",
1942 );
1943 }
1944
1945 // Compensation summary table
1946 doc.add_paragraph("");
1947 {
1948 let mut tbl = doc
1949 .add_table(5, 2)
1950 .borders(BorderStyle::Single, 4, "5B2C6F")
1951 .width_pct(70.0)
1952 .alignment(Alignment::Center);
1953 tbl.cell(0, 0).unwrap().set_text("Compensation Element");
1954 tbl.cell(0, 0).unwrap().shading("5B2C6F");
1955 tbl.cell(0, 1).unwrap().set_text("Value");
1956 tbl.cell(0, 1).unwrap().shading("5B2C6F");
1957 let items = [
1958 ("Base Salary", "$375,000/year"),
1959 ("Target Bonus", "Up to 40% ($150,000)"),
1960 ("Equity Grant", "500,000 shares (4yr vest)"),
1961 ("Total Target Comp", "$525,000 + equity"),
1962 ];
1963 for (i, (elem, val)) in items.iter().enumerate() {
1964 tbl.cell(i + 1, 0).unwrap().set_text(elem);
1965 tbl.cell(i + 1, 1).unwrap().set_text(val);
1966 if i == 3 {
1967 tbl.cell(i + 1, 0).unwrap().shading("E8DAEF");
1968 tbl.cell(i + 1, 1).unwrap().shading("E8DAEF");
1969 }
1970 }
1971 }
1972
1973 // ── Article 3: Benefits ──
1974 doc.add_paragraph("").page_break_before(true);
1975 doc.add_paragraph("Article 3 — Benefits").style("Heading1");
1976 {
1977 let mut p = doc.add_paragraph("");
1978 p.add_run("3.1 ").bold(true);
1979 p.add_run(
1980 "The Employee shall be entitled to participate in all benefit programs \
1981 generally available to senior executives, including:",
1982 );
1983 }
1984 doc.add_bullet_list_item(
1985 "Medical, dental, and vision insurance (100% premium coverage for employee and dependents)",
1986 0,
1987 );
1988 doc.add_bullet_list_item("401(k) retirement plan with 6% company match", 0);
1989 doc.add_bullet_list_item("Life insurance and long-term disability coverage", 0);
1990 doc.add_bullet_list_item("Annual professional development allowance of $10,000", 0);
1991
1992 {
1993 let mut p = doc.add_paragraph("");
1994 p.add_run("3.2 Paid Time Off. ").bold(true);
1995 p.add_run(
1996 "The Employee shall receive 25 days of paid vacation per year, plus Company holidays, \
1997 accruing on a monthly basis.",
1998 );
1999 }
2000
2001 // ── Article 4: Term and Termination ──
2002 doc.add_paragraph("Article 4 — Term and Termination")
2003 .style("Heading1");
2004 {
2005 let mut p = doc.add_paragraph("");
2006 p.add_run("4.1 At-Will Employment. ").bold(true);
2007 p.add_run(
2008 "This Agreement is for at-will employment and may be terminated by either Party \
2009 at any time, with or without cause, subject to the notice provisions herein.",
2010 );
2011 }
2012 {
2013 let mut p = doc.add_paragraph("");
2014 p.add_run("4.2 Notice Period. ").bold(true);
2015 p.add_run("Either Party shall provide ");
2016 p.add_run("ninety (90) days").bold(true);
2017 p.add_run(" written notice of termination, or pay in lieu thereof.");
2018 }
2019 {
2020 let mut p = doc.add_paragraph("");
2021 p.add_run("4.3 Severance. ").bold(true);
2022 p.add_run("In the event of termination by the Company without Cause, the Employee shall \
2023 receive (a) twelve (12) months of base salary continuation, (b) pro-rata bonus \
2024 for the year of termination, and (c) twelve (12) months of COBRA premium coverage.");
2025 }
2026
2027 // ── Article 5: Confidentiality ──
2028 doc.add_paragraph("Article 5 — Confidentiality and IP")
2029 .style("Heading1");
2030 {
2031 let mut p = doc.add_paragraph("");
2032 p.add_run("5.1 ").bold(true);
2033 p.add_run(
2034 "The Employee agrees to maintain strict confidentiality of all proprietary \
2035 information, trade secrets, and business plans of the Company during and after \
2036 employment.",
2037 );
2038 }
2039 {
2040 let mut p = doc.add_paragraph("");
2041 p.add_run("5.2 ").bold(true);
2042 p.add_run(
2043 "All intellectual property, inventions, and works of authorship created by the \
2044 Employee during the term of employment and within the scope of duties shall be \
2045 the sole and exclusive property of the Company.",
2046 );
2047 }
2048
2049 // ── Article 6: Non-Compete ──
2050 doc.add_paragraph("Article 6 — Non-Competition")
2051 .style("Heading1");
2052 {
2053 let mut p = doc.add_paragraph("");
2054 p.add_run("6.1 ").bold(true);
2055 p.add_run("For a period of twelve (12) months following termination, the Employee agrees \
2056 not to engage in any business that directly competes with the Company's core \
2057 business of AI infrastructure and ML operations services, within the United States.");
2058 }
2059
2060 // ── Article 7: General Provisions ──
2061 doc.add_paragraph("Article 7 — General Provisions")
2062 .style("Heading1");
2063 {
2064 let mut p = doc.add_paragraph("");
2065 p.add_run("7.1 Governing Law. ").bold(true);
2066 p.add_run(
2067 "This Agreement shall be governed by and construed in accordance with the laws of \
2068 the State of California.",
2069 );
2070 }
2071 {
2072 let mut p = doc.add_paragraph("");
2073 p.add_run("7.2 Entire Agreement. ").bold(true);
2074 p.add_run(
2075 "This Agreement constitutes the entire agreement between the Parties and supersedes \
2076 all prior negotiations, representations, and agreements.",
2077 );
2078 }
2079 {
2080 let mut p = doc.add_paragraph("");
2081 p.add_run("7.3 Amendment. ").bold(true);
2082 p.add_run(
2083 "This Agreement may only be amended by a written instrument signed by both Parties.",
2084 );
2085 }
2086
2087 // ── Signature Block ──
2088 doc.add_paragraph("").page_break_before(true);
2089 doc.add_paragraph("IN WITNESS WHEREOF, the Parties have executed this Employment Agreement as of the Effective Date.")
2090 .space_after(Length::pt(24.0));
2091
2092 // Signature table
2093 {
2094 let mut tbl = doc.add_table(6, 2).width_pct(100.0);
2095 tbl.cell(0, 0).unwrap().set_text("FOR THE COMPANY:");
2096 tbl.cell(0, 0).unwrap().shading("5B2C6F");
2097 tbl.cell(0, 1).unwrap().set_text("EMPLOYEE:");
2098 tbl.cell(0, 1).unwrap().shading("5B2C6F");
2099
2100 tbl.cell(1, 0).unwrap().set_text("");
2101 tbl.cell(1, 1).unwrap().set_text("");
2102 tbl.row(1).unwrap().height(Length::pt(40.0));
2103
2104 tbl.cell(2, 0)
2105 .unwrap()
2106 .set_text("Signature: ___________________________");
2107 tbl.cell(2, 1)
2108 .unwrap()
2109 .set_text("Signature: ___________________________");
2110 tbl.cell(3, 0)
2111 .unwrap()
2112 .set_text("Name: Board Representative");
2113 tbl.cell(3, 1).unwrap().set_text("Name: Walter White");
2114 tbl.cell(4, 0)
2115 .unwrap()
2116 .set_text("Title: Chair, Board of Directors");
2117 tbl.cell(4, 1)
2118 .unwrap()
2119 .set_text("Title: Chief Executive Officer");
2120 tbl.cell(5, 0).unwrap().set_text("Date: _______________");
2121 tbl.cell(5, 1).unwrap().set_text("Date: _______________");
2122 }
2123
2124 doc
2125}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}34fn generate_feature_showcase(path: &Path) {
35 let mut doc = Document::new();
36
37 // =========================================================================
38 // PAGE SETUP & METADATA
39 // =========================================================================
40 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
41 doc.set_margins(
42 Length::inches(1.0), // top
43 Length::inches(1.0), // right
44 Length::inches(1.0), // bottom
45 Length::inches(1.0), // left
46 );
47 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
48 doc.set_gutter(Length::twips(0));
49
50 doc.set_title("rdocx Feature Showcase");
51 doc.set_author("rdocx Sample Generator");
52 doc.set_subject("Comprehensive feature demonstration");
53 doc.set_keywords("rdocx, docx, rust, sample");
54
55 // Header & Footer
56 doc.set_header("rdocx Feature Showcase");
57 doc.set_footer("Generated by rdocx — Page");
58
59 // Different first page header
60 doc.set_different_first_page(true);
61 doc.set_first_page_header("rdocx");
62 doc.set_first_page_footer("Feature Showcase — Cover Page");
63
64 // =========================================================================
65 // PAGE 1: COVER PAGE — background image, run formatting
66 // =========================================================================
67 let bg_cover = create_sample_png(612, 792, [30, 60, 120]);
68 doc.add_background_image(&bg_cover, "cover_bg.png");
69
70 doc.add_paragraph(""); // spacer
71 doc.add_paragraph(""); // spacer
72 doc.add_paragraph(""); // spacer
73
74 {
75 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
76 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
77 }
78 {
79 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
80 p.add_run("Feature Showcase")
81 .size(28.0)
82 .color("FFFFFF")
83 .italic(true);
84 }
85
86 doc.add_paragraph(""); // spacer
87
88 {
89 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
90 p.add_run("A comprehensive demonstration of every feature")
91 .size(14.0)
92 .color("CCDDFF");
93 }
94 {
95 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
96 p.add_run("provided by the rdocx Rust crate for DOCX generation.")
97 .size(14.0)
98 .color("CCDDFF");
99 }
100
101 // =========================================================================
102 // PAGE 2: TEXT FORMATTING
103 // =========================================================================
104 doc.add_paragraph("").page_break_before(true);
105
106 doc.add_paragraph("1. Text Formatting").style("Heading1");
107
108 doc.add_paragraph("This section demonstrates paragraph and run-level formatting options.");
109 doc.add_paragraph("");
110
111 // --- Paragraph alignment ---
112 doc.add_paragraph("Paragraph Alignment").style("Heading2");
113
114 doc.add_paragraph("This paragraph is left-aligned (the default).")
115 .alignment(Alignment::Left);
116 doc.add_paragraph("This paragraph is center-aligned.")
117 .alignment(Alignment::Center);
118 doc.add_paragraph("This paragraph is right-aligned.")
119 .alignment(Alignment::Right);
120 doc.add_paragraph(
121 "This paragraph is justified. To demonstrate justified text properly, it needs \
122 to be long enough to span multiple lines so the word spacing adjustment is visible \
123 across the full width of the text area on the page.",
124 )
125 .alignment(Alignment::Justify);
126
127 doc.add_paragraph("");
128
129 // --- Run formatting ---
130 doc.add_paragraph("Run Formatting").style("Heading2");
131
132 {
133 let mut p = doc.add_paragraph("");
134 p.add_run("Bold text").bold(true);
135 p.add_run(" | ");
136 p.add_run("Italic text").italic(true);
137 p.add_run(" | ");
138 p.add_run("Bold + Italic").bold(true).italic(true);
139 }
140 {
141 let mut p = doc.add_paragraph("");
142 p.add_run("Single underline").underline(true);
143 p.add_run(" | ");
144 p.add_run("Strikethrough").strike(true);
145 p.add_run(" | ");
146 p.add_run("Double strikethrough").double_strike(true);
147 }
148 {
149 let mut p = doc.add_paragraph("");
150 p.add_run("Red text").color("FF0000");
151 p.add_run(" | ");
152 p.add_run("Blue text").color("0000FF");
153 p.add_run(" | ");
154 p.add_run("Green text").color("00AA00");
155 p.add_run(" | ");
156 p.add_run("Highlighted").highlight("FFFF00");
157 }
158 {
159 let mut p = doc.add_paragraph("");
160 p.add_run("8pt small").size(8.0);
161 p.add_run(" | ");
162 p.add_run("11pt normal").size(11.0);
163 p.add_run(" | ");
164 p.add_run("16pt large").size(16.0);
165 p.add_run(" | ");
166 p.add_run("24pt extra-large").size(24.0);
167 }
168 {
169 let mut p = doc.add_paragraph("");
170 p.add_run("Arial font").font("Arial");
171 p.add_run(" | ");
172 p.add_run("Times New Roman font").font("Times New Roman");
173 p.add_run(" | ");
174 p.add_run("Courier New font").font("Courier New");
175 }
176 {
177 let mut p = doc.add_paragraph("");
178 p.add_run("Normal");
179 p.add_run(" H").size(11.0);
180 p.add_run("2").subscript();
181 p.add_run("O (subscript)").size(11.0);
182 p.add_run(" | E = mc").size(11.0);
183 p.add_run("2").superscript();
184 p.add_run(" (superscript)").size(11.0);
185 }
186 {
187 let mut p = doc.add_paragraph("");
188 p.add_run("ALL CAPS").all_caps(true);
189 p.add_run(" | ");
190 p.add_run("Small Caps").small_caps(true);
191 p.add_run(" | ");
192 p.add_run("Expanded spacing")
193 .character_spacing(Length::twips(40));
194 }
195
196 doc.add_paragraph("");
197
198 // --- Paragraph formatting ---
199 doc.add_paragraph("Paragraph Formatting").style("Heading2");
200
201 doc.add_paragraph("Paragraph with shading (light green background)")
202 .shading("E2EFDA");
203
204 doc.add_paragraph("Paragraph with bottom border")
205 .border_bottom(BorderStyle::Single, 6, "2E75B6");
206
207 doc.add_paragraph("Paragraph with all borders")
208 .border_all(BorderStyle::Single, 4, "FF0000");
209
210 doc.add_paragraph("Paragraph with 1-inch left indent and hanging indent")
211 .indent_left(Length::inches(1.0))
212 .hanging_indent(Length::inches(0.5));
213
214 doc.add_paragraph("Paragraph with first-line indent of 0.5 inches")
215 .first_line_indent(Length::inches(0.5));
216
217 doc.add_paragraph("Paragraph with extra space before (24pt) and after (12pt)")
218 .space_before(Length::pt(24.0))
219 .space_after(Length::pt(12.0));
220
221 doc.add_paragraph(
222 "Paragraph with double line spacing. This text should have extra vertical \
223 space between lines to demonstrate the line_spacing_multiple setting.",
224 )
225 .line_spacing_multiple(2.0);
226
227 doc.add_paragraph("Paragraph with keep-with-next (won't break from the next paragraph)")
228 .keep_with_next(true);
229 doc.add_paragraph("(This stays with the paragraph above.)");
230
231 // =========================================================================
232 // PAGE 3: LISTS & TAB STOPS
233 // =========================================================================
234 doc.add_paragraph("").page_break_before(true);
235
236 doc.add_paragraph("2. Lists").style("Heading1");
237
238 doc.add_paragraph("Bullet List").style("Heading2");
239
240 doc.add_bullet_list_item("First bullet item", 0);
241 doc.add_bullet_list_item("Second bullet item", 0);
242 doc.add_bullet_list_item("Nested level 1", 1);
243 doc.add_bullet_list_item("Nested level 2", 2);
244 doc.add_bullet_list_item("Back to level 1", 1);
245 doc.add_bullet_list_item("Third bullet item", 0);
246
247 doc.add_paragraph("");
248
249 doc.add_paragraph("Numbered List").style("Heading2");
250
251 doc.add_numbered_list_item("First numbered item", 0);
252 doc.add_numbered_list_item("Second numbered item", 0);
253 doc.add_numbered_list_item("Sub-item A", 1);
254 doc.add_numbered_list_item("Sub-item B", 1);
255 doc.add_numbered_list_item("Third numbered item", 0);
256
257 doc.add_paragraph("");
258
259 // --- Tab stops ---
260 doc.add_paragraph("Tab Stops").style("Heading2");
261
262 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
263 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
264 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
265 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
266 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
267
268 doc.add_paragraph("Item\t........\tPrice")
269 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
270 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
271 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
272
273 doc.add_paragraph("Widget A\t........\t$19.99")
274 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
275 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
276 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
277
278 doc.add_paragraph("Gadget B\t________\t$249.50")
279 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
280 .add_tab_stop_with_leader(
281 TabAlignment::Right,
282 Length::inches(4.0),
283 TabLeader::Underscore,
284 )
285 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
286
287 // =========================================================================
288 // PAGE 4: TABLES
289 // =========================================================================
290 doc.add_paragraph("").page_break_before(true);
291
292 doc.add_paragraph("3. Tables").style("Heading1");
293
294 // --- Basic table with borders ---
295 doc.add_paragraph("Basic Table with Borders")
296 .style("Heading2");
297
298 {
299 let mut tbl = doc.add_table(4, 3);
300 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
301
302 // Header row
303 for col in 0..3 {
304 tbl.cell(0, col).unwrap().shading("2E75B6");
305 }
306 tbl.cell(0, 0).unwrap().set_text("Name");
307 tbl.cell(0, 1).unwrap().set_text("Role");
308 tbl.cell(0, 2).unwrap().set_text("Location");
309
310 tbl.cell(1, 0).unwrap().set_text("Alice Johnson");
311 tbl.cell(1, 1).unwrap().set_text("Engineering Lead");
312 tbl.cell(1, 2).unwrap().set_text("New York");
313
314 tbl.cell(2, 0).unwrap().set_text("Bob Smith");
315 tbl.cell(2, 1).unwrap().set_text("Product Manager");
316 tbl.cell(2, 2).unwrap().set_text("San Francisco");
317
318 tbl.cell(3, 0).unwrap().set_text("Carol Davis");
319 tbl.cell(3, 1).unwrap().set_text("Designer");
320 tbl.cell(3, 2).unwrap().set_text("London");
321 }
322
323 doc.add_paragraph("");
324
325 // --- Table with cell merging ---
326 doc.add_paragraph("Table with Cell Merging & Vertical Alignment")
327 .style("Heading2");
328
329 {
330 let mut tbl = doc.add_table(4, 4);
331 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
332 tbl = tbl.width_pct(100.0);
333
334 // Header spanning all columns
335 tbl.cell(0, 0).unwrap().set_text("Quarterly Revenue Report");
336 tbl.cell(0, 0).unwrap().shading("1F4E79");
337 tbl.cell(0, 0).unwrap().grid_span(4);
338
339 // Sub-header
340 tbl.cell(1, 0).unwrap().set_text("Region");
341 tbl.cell(1, 0).unwrap().shading("D6E4F0");
342 tbl.cell(1, 1).unwrap().set_text("Q1");
343 tbl.cell(1, 1).unwrap().shading("D6E4F0");
344 tbl.cell(1, 2).unwrap().set_text("Q2");
345 tbl.cell(1, 2).unwrap().shading("D6E4F0");
346 tbl.cell(1, 3).unwrap().set_text("Total");
347 tbl.cell(1, 3).unwrap().shading("D6E4F0");
348
349 // Data
350 tbl.cell(2, 0).unwrap().set_text("North America");
351 tbl.cell(2, 1).unwrap().set_text("$2.4M");
352 tbl.cell(2, 2).unwrap().set_text("$2.7M");
353 tbl.cell(2, 3).unwrap().set_text("$5.1M");
354
355 tbl.cell(3, 0).unwrap().set_text("Europe");
356 tbl.cell(3, 1).unwrap().set_text("$1.8M");
357 tbl.cell(3, 2).unwrap().set_text("$2.0M");
358 tbl.cell(3, 3).unwrap().set_text("$3.8M");
359
360 // Vertical alignment on data cells
361 tbl.cell(2, 3)
362 .unwrap()
363 .vertical_alignment(VerticalAlignment::Center);
364 tbl.cell(3, 3)
365 .unwrap()
366 .vertical_alignment(VerticalAlignment::Bottom);
367 }
368
369 doc.add_paragraph("");
370
371 // --- Table with vertical merge ---
372 doc.add_paragraph("Table with Vertical Merge")
373 .style("Heading2");
374
375 {
376 let mut tbl = doc.add_table(4, 3);
377 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
378
379 tbl.cell(0, 0).unwrap().set_text("Category");
380 tbl.cell(0, 0).unwrap().shading("E2EFDA");
381 tbl.cell(0, 1).unwrap().set_text("Item");
382 tbl.cell(0, 1).unwrap().shading("E2EFDA");
383 tbl.cell(0, 2).unwrap().set_text("Price");
384 tbl.cell(0, 2).unwrap().shading("E2EFDA");
385
386 // "Hardware" spans rows 1-2
387 tbl.cell(1, 0).unwrap().set_text("Hardware");
388 tbl.cell(1, 0).unwrap().v_merge_restart();
389 tbl.cell(1, 1).unwrap().set_text("Laptop");
390 tbl.cell(1, 2).unwrap().set_text("$1,200");
391
392 tbl.cell(2, 0).unwrap().v_merge_continue();
393 tbl.cell(2, 1).unwrap().set_text("Monitor");
394 tbl.cell(2, 2).unwrap().set_text("$450");
395
396 // "Software" on row 3
397 tbl.cell(3, 0).unwrap().set_text("Software");
398 tbl.cell(3, 1).unwrap().set_text("IDE License");
399 tbl.cell(3, 2).unwrap().set_text("$200/yr");
400 }
401
402 doc.add_paragraph("");
403
404 // --- Nested table ---
405 doc.add_paragraph("Nested Table").style("Heading2");
406
407 {
408 let mut tbl = doc.add_table(2, 2);
409 tbl = tbl.borders(BorderStyle::Single, 6, "2E75B6");
410
411 tbl.cell(0, 0).unwrap().set_text("Outer Cell (0,0)");
412 tbl.cell(0, 1).unwrap().set_text("Outer Cell (0,1)");
413 tbl.cell(1, 0).unwrap().set_text("Outer Cell (1,0)");
414
415 // Nested table inside cell (1,1)
416 {
417 let mut cell = tbl.cell(1, 1).unwrap();
418 cell.set_text("Contains nested table:");
419 let mut nested = cell.add_table(2, 2);
420 nested = nested.borders(BorderStyle::Single, 2, "FF6600");
421 nested.cell(0, 0).unwrap().set_text("Inner A");
422 nested.cell(0, 1).unwrap().set_text("Inner B");
423 nested.cell(1, 0).unwrap().set_text("Inner C");
424 nested.cell(1, 1).unwrap().set_text("Inner D");
425 }
426 }
427
428 // =========================================================================
429 // PAGE 5: IMAGES
430 // =========================================================================
431 doc.add_paragraph("").page_break_before(true);
432
433 doc.add_paragraph("4. Images").style("Heading1");
434
435 doc.add_paragraph("Inline Image").style("Heading2");
436
437 doc.add_paragraph("Below is an inline image (200x50 pixels, blue gradient):");
438 let inline_img = create_sample_png(200, 50, [0, 80, 200]);
439 doc.add_picture(
440 &inline_img,
441 "inline_chart.png",
442 Length::inches(3.0),
443 Length::inches(0.75),
444 );
445
446 doc.add_paragraph("");
447
448 doc.add_paragraph("Header Image").style("Heading2");
449
450 // Replace the text-only header with an image header
451 let header_img = create_sample_png(400, 40, [40, 40, 40]);
452 doc.set_header_image(
453 &header_img,
454 "header_logo.png",
455 Length::inches(2.0),
456 Length::inches(0.2),
457 );
458
459 doc.add_paragraph(
460 "The document header has been replaced with an inline image. \
461 Check the header area at the top of this page.",
462 );
463
464 doc.add_paragraph("");
465 doc.add_paragraph(
466 "Note: The cover page uses a full-page background image behind the text, \
467 demonstrated on page 1 via add_background_image().",
468 );
469
470 // =========================================================================
471 // PAGE 6: CONTENT MANIPULATION — placeholder replacement, insertion
472 // =========================================================================
473 doc.add_paragraph("").page_break_before(true);
474
475 doc.add_paragraph("5. Content Manipulation")
476 .style("Heading1");
477
478 // --- Placeholder replacement ---
479 doc.add_paragraph("Placeholder Replacement")
480 .style("Heading2");
481
482 doc.add_paragraph(
483 "Before replacement, this document contained {{customer}} and {{date}} placeholders.",
484 );
485
486 {
487 let mut p = doc.add_paragraph("");
488 p.add_run("Customer: ").bold(true);
489 p.add_run("{{customer}}");
490 }
491 {
492 let mut p = doc.add_paragraph("");
493 p.add_run("Date: ").bold(true);
494 p.add_run("{{date}}");
495 }
496 doc.add_paragraph("Reference: {{ref_number}}");
497
498 // Table with placeholders
499 {
500 let mut tbl = doc.add_table(3, 2);
501 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
502 tbl.cell(0, 0).unwrap().set_text("Field");
503 tbl.cell(0, 0).unwrap().shading("D6E4F0");
504 tbl.cell(0, 1).unwrap().set_text("Value");
505 tbl.cell(0, 1).unwrap().shading("D6E4F0");
506 tbl.cell(1, 0).unwrap().set_text("Project");
507 tbl.cell(1, 1).unwrap().set_text("{{project}}");
508 tbl.cell(2, 0).unwrap().set_text("Status");
509 tbl.cell(2, 1).unwrap().set_text("{{status}}");
510 }
511
512 // Perform replacements
513 let mut replacements = HashMap::new();
514 replacements.insert("{{customer}}", "Acme Corporation");
515 replacements.insert("{{date}}", "February 22, 2026");
516 replacements.insert("{{ref_number}}", "REF-2026-001");
517 replacements.insert("{{project}}", "Infrastructure Upgrade");
518 replacements.insert("{{status}}", "In Progress");
519 let replace_count = doc.replace_all(&replacements);
520
521 doc.add_paragraph("");
522 doc.add_paragraph(&format!(
523 "(Replaced {} placeholders above — in body text and table cells)",
524 replace_count
525 ));
526
527 doc.add_paragraph("");
528
529 // --- Content insertion ---
530 doc.add_paragraph("Content Insertion").style("Heading2");
531
532 doc.add_paragraph("Section A: First section of content.");
533 doc.add_paragraph("Section C: Third section of content.");
534
535 // Insert "Section B" between A and C
536 if let Some(idx) = doc.find_content_index("Section C") {
537 doc.insert_paragraph(
538 idx,
539 "Section B: Inserted between A and C using find_content_index().",
540 );
541 }
542
543 doc.add_paragraph("");
544 doc.add_paragraph(
545 "The paragraph above ('Section B') was inserted at a specific position \
546 using find_content_index() + insert_paragraph().",
547 );
548
549 // =========================================================================
550 // PAGE 7: LANDSCAPE — section break, wide table
551 // =========================================================================
552 doc.add_paragraph("").section_break(SectionBreak::NextPage);
553
554 doc.add_paragraph("6. Mixed Page Orientation")
555 .style("Heading1");
556
557 doc.add_paragraph(
558 "This page is in LANDSCAPE orientation. It was created using a section break \
559 followed by section_landscape(). This is useful for wide tables or charts.",
560 );
561
562 doc.add_paragraph("");
563
564 // Wide table for landscape
565 {
566 let mut tbl = doc.add_table(4, 7);
567 tbl = tbl.borders(BorderStyle::Single, 4, "2E75B6");
568
569 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
570 for (col, h) in headers.iter().enumerate() {
571 tbl.cell(0, col).unwrap().set_text(h);
572 tbl.cell(0, col).unwrap().shading("2E75B6");
573 }
574
575 let data = [
576 [
577 "North America",
578 "$1.2M",
579 "$1.3M",
580 "$1.4M",
581 "$1.5M",
582 "$1.6M",
583 "$7.0M",
584 ],
585 [
586 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
587 ],
588 [
589 "Asia Pacific",
590 "$0.5M",
591 "$0.6M",
592 "$0.7M",
593 "$0.7M",
594 "$0.8M",
595 "$3.3M",
596 ],
597 ];
598 for (row_idx, row_data) in data.iter().enumerate() {
599 for (col, val) in row_data.iter().enumerate() {
600 tbl.cell(row_idx + 1, col).unwrap().set_text(val);
601 }
602 }
603 }
604
605 // End landscape, return to portrait
606 doc.add_paragraph("")
607 .section_break(SectionBreak::NextPage)
608 .section_landscape();
609
610 // =========================================================================
611 // PAGE 8: BACK TO PORTRAIT — styles, final notes
612 // =========================================================================
613 doc.add_paragraph("7. Custom Styles & Summary")
614 .style("Heading1");
615
616 doc.add_paragraph(
617 "This final page is back in portrait orientation after a section break. \
618 The document has demonstrated:",
619 );
620
621 doc.add_bullet_list_item(
622 "Page setup: size, margins, header/footer distance, gutter",
623 0,
624 );
625 doc.add_bullet_list_item("Document metadata: title, author, subject, keywords", 0);
626 doc.add_bullet_list_item("Headers and footers: text, images, different first page", 0);
627 doc.add_bullet_list_item("Background images: full-page behind text", 0);
628 doc.add_bullet_list_item(
629 "Text formatting: bold, italic, underline, strike, color, size, font",
630 0,
631 );
632 doc.add_bullet_list_item(
633 "Advanced run formatting: superscript, subscript, caps, spacing",
634 0,
635 );
636 doc.add_bullet_list_item(
637 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
638 0,
639 );
640 doc.add_bullet_list_item("Bullet and numbered lists with nesting levels", 0);
641 doc.add_bullet_list_item("Tab stops with dot/underscore leaders", 0);
642 doc.add_bullet_list_item(
643 "Tables: borders, shading, column spans, row spans, nesting",
644 0,
645 );
646 doc.add_bullet_list_item("Vertical alignment in table cells", 0);
647 doc.add_bullet_list_item("Inline images", 0);
648 doc.add_bullet_list_item("Placeholder replacement in body and table cells", 0);
649 doc.add_bullet_list_item("Content insertion at specific positions", 0);
650 doc.add_bullet_list_item(
651 "Section breaks with mixed portrait/landscape orientation",
652 0,
653 );
654
655 doc.add_paragraph("");
656 doc.add_paragraph("All features above were built entirely from scratch using the rdocx API.")
657 .alignment(Alignment::Center)
658 .shading("E2EFDA")
659 .border_all(BorderStyle::Single, 2, "00AA00");
660
661 doc.save(path).unwrap();
662}86fn generate_feature_showcase(_samples_dir: &Path) -> Document {
87 let mut doc = Document::new();
88
89 // ── Page Setup & Metadata ──
90 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
91 doc.set_margins(
92 Length::inches(1.0),
93 Length::inches(1.0),
94 Length::inches(1.0),
95 Length::inches(1.0),
96 );
97 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
98 doc.set_gutter(Length::twips(0));
99 doc.set_title("rdocx Feature Showcase");
100 doc.set_author("rdocx Sample Generator");
101 doc.set_subject("Comprehensive feature demonstration");
102 doc.set_keywords("rdocx, docx, rust, sample, showcase");
103
104 // Headers & Footers with different first page
105 doc.set_different_first_page(true);
106 doc.set_first_page_header("rdocx");
107 doc.set_first_page_footer("Feature Showcase — Cover Page");
108 doc.set_header("rdocx Feature Showcase");
109 doc.set_footer("Generated by rdocx");
110
111 // ── COVER PAGE ──
112 let bg = create_sample_png(612, 792, [20, 45, 90]);
113 doc.add_background_image(&bg, "cover_bg.png");
114
115 for _ in 0..3 {
116 doc.add_paragraph("");
117 }
118 {
119 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
120 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
121 }
122 {
123 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
124 p.add_run("Complete Feature Showcase")
125 .size(28.0)
126 .color("FFFFFF")
127 .italic(true);
128 }
129 doc.add_paragraph("");
130 {
131 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
132 p.add_run("Every feature of the rdocx Rust library")
133 .size(13.0)
134 .color("B0C4DE");
135 }
136 {
137 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
138 p.add_run("demonstrated in a single document.")
139 .size(13.0)
140 .color("B0C4DE");
141 }
142
143 // ── TABLE OF CONTENTS ──
144 doc.add_paragraph("").page_break_before(true);
145 doc.insert_toc(doc.content_count(), 3);
146
147 // ── SECTION 1: TEXT FORMATTING ──
148 doc.add_paragraph("").page_break_before(true);
149 doc.add_paragraph("1. Text Formatting").style("Heading1");
150
151 // Paragraph Alignment
152 doc.add_paragraph("1.1 Paragraph Alignment")
153 .style("Heading2");
154 doc.add_paragraph("Left-aligned paragraph (default).")
155 .alignment(Alignment::Left);
156 doc.add_paragraph("Center-aligned paragraph.")
157 .alignment(Alignment::Center);
158 doc.add_paragraph("Right-aligned paragraph.")
159 .alignment(Alignment::Right);
160 doc.add_paragraph(
161 "Justified paragraph — this text is long enough to demonstrate how justified alignment \
162 distributes extra space across word gaps so lines fill the full width of the text area.",
163 )
164 .alignment(Alignment::Justify);
165
166 doc.add_paragraph("");
167
168 // Run Formatting
169 doc.add_paragraph("1.2 Run Formatting").style("Heading2");
170 {
171 let mut p = doc.add_paragraph("");
172 p.add_run("Bold").bold(true);
173 p.add_run(" | ");
174 p.add_run("Italic").italic(true);
175 p.add_run(" | ");
176 p.add_run("Bold Italic").bold(true).italic(true);
177 p.add_run(" | ");
178 p.add_run("Underline").underline(true);
179 p.add_run(" | ");
180 p.add_run("Strikethrough").strike(true);
181 p.add_run(" | ");
182 p.add_run("Double Strike").double_strike(true);
183 }
184 {
185 let mut p = doc.add_paragraph("");
186 p.add_run("Red").color("FF0000");
187 p.add_run(" | ");
188 p.add_run("Blue").color("0000FF");
189 p.add_run(" | ");
190 p.add_run("Green").color("00AA00");
191 p.add_run(" | ");
192 p.add_run("Highlighted").highlight("FFFF00");
193 }
194 {
195 let mut p = doc.add_paragraph("");
196 p.add_run("8pt").size(8.0);
197 p.add_run(" | ");
198 p.add_run("11pt").size(11.0);
199 p.add_run(" | ");
200 p.add_run("16pt").size(16.0);
201 p.add_run(" | ");
202 p.add_run("24pt").size(24.0);
203 }
204 {
205 let mut p = doc.add_paragraph("");
206 p.add_run("Arial").font("Arial");
207 p.add_run(" | ");
208 p.add_run("Times New Roman").font("Times New Roman");
209 p.add_run(" | ");
210 p.add_run("Courier New").font("Courier New");
211 }
212 {
213 let mut p = doc.add_paragraph("");
214 p.add_run("H");
215 p.add_run("2").subscript();
216 p.add_run("O (subscript) | E=mc");
217 p.add_run("2").superscript();
218 p.add_run(" (superscript)");
219 }
220 {
221 let mut p = doc.add_paragraph("");
222 p.add_run("ALL CAPS").all_caps(true);
223 p.add_run(" | ");
224 p.add_run("Small Caps").small_caps(true);
225 p.add_run(" | ");
226 p.add_run("Expanded").character_spacing(Length::twips(40));
227 p.add_run(" | ");
228 p.add_run("Hidden text (not visible)").hidden(true);
229 }
230
231 doc.add_paragraph("");
232
233 // Underline Styles
234 doc.add_paragraph("1.3 Underline Styles").style("Heading2");
235 {
236 let mut p = doc.add_paragraph("");
237 p.add_run("Single ")
238 .underline_style(rdocx::UnderlineStyle::Single);
239 p.add_run("Double ")
240 .underline_style(rdocx::UnderlineStyle::Double);
241 p.add_run("Thick ")
242 .underline_style(rdocx::UnderlineStyle::Thick);
243 p.add_run("Dotted ")
244 .underline_style(rdocx::UnderlineStyle::Dotted);
245 p.add_run("Dash ")
246 .underline_style(rdocx::UnderlineStyle::Dash);
247 p.add_run("Wave ")
248 .underline_style(rdocx::UnderlineStyle::Wave);
249 }
250
251 // ── SECTION 2: PARAGRAPH FORMATTING ──
252 doc.add_paragraph("").page_break_before(true);
253 doc.add_paragraph("2. Paragraph Formatting")
254 .style("Heading1");
255
256 doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
257 doc.add_paragraph("Paragraph with light green shading")
258 .shading("E2EFDA");
259 doc.add_paragraph("Paragraph with bottom border")
260 .border_bottom(BorderStyle::Single, 6, "2E75B6");
261 doc.add_paragraph("Paragraph with all borders (red)")
262 .border_all(BorderStyle::Single, 4, "FF0000");
263
264 doc.add_paragraph("2.2 Indentation").style("Heading2");
265 doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
266 .indent_left(Length::inches(1.0))
267 .hanging_indent(Length::inches(0.5));
268 doc.add_paragraph("0.5-inch first-line indent on this paragraph")
269 .first_line_indent(Length::inches(0.5));
270 doc.add_paragraph("Both left and right indent (0.75 inches each)")
271 .indent_left(Length::inches(0.75))
272 .indent_right(Length::inches(0.75));
273
274 doc.add_paragraph("2.3 Spacing & Line Height")
275 .style("Heading2");
276 doc.add_paragraph("Extra space before (24pt) and after (12pt)")
277 .space_before(Length::pt(24.0))
278 .space_after(Length::pt(12.0));
279 doc.add_paragraph(
280 "Double line spacing paragraph. This text should have extra vertical space between \
281 lines to demonstrate line_spacing_multiple(2.0).",
282 )
283 .line_spacing_multiple(2.0);
284 doc.add_paragraph("Exact 20pt line spacing (fixed height).")
285 .line_spacing(20.0);
286
287 doc.add_paragraph("2.4 Pagination Controls")
288 .style("Heading2");
289 doc.add_paragraph("keep_with_next — this paragraph stays with the next")
290 .keep_with_next(true);
291 doc.add_paragraph("(This paragraph was kept with the one above.)");
292 doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
293 .keep_together(true);
294 doc.add_paragraph("widow_control — prevents widow/orphan lines")
295 .widow_control(true);
296
297 // ── SECTION 3: LISTS ──
298 doc.add_paragraph("").page_break_before(true);
299 doc.add_paragraph("3. Lists").style("Heading1");
300
301 doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
302 doc.add_bullet_list_item("First item", 0);
303 doc.add_bullet_list_item("Second item", 0);
304 doc.add_bullet_list_item("Nested level 1", 1);
305 doc.add_bullet_list_item("Nested level 2", 2);
306 doc.add_bullet_list_item("Back to level 1", 1);
307 doc.add_bullet_list_item("Third item", 0);
308
309 doc.add_paragraph("");
310
311 doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
312 doc.add_numbered_list_item("First numbered item", 0);
313 doc.add_numbered_list_item("Second numbered item", 0);
314 doc.add_numbered_list_item("Sub-item A", 1);
315 doc.add_numbered_list_item("Sub-item B", 1);
316 doc.add_numbered_list_item("Third numbered item", 0);
317
318 // ── SECTION 4: TAB STOPS ──
319 doc.add_paragraph("");
320 doc.add_paragraph("4. Tab Stops").style("Heading1");
321
322 doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
323 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
324 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
325 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
326 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
327 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
328
329 doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
330 doc.add_paragraph("Item\t\tPrice")
331 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
332 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
333 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
334 doc.add_paragraph("Widget\t\t$19.99")
335 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
336 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
337 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
338 doc.add_paragraph("Gadget\t\t$249.50")
339 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
340 .add_tab_stop_with_leader(
341 TabAlignment::Right,
342 Length::inches(4.0),
343 TabLeader::Underscore,
344 )
345 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
346
347 // ── SECTION 5: TABLES ──
348 doc.add_paragraph("").page_break_before(true);
349 doc.add_paragraph("5. Tables").style("Heading1");
350
351 doc.add_paragraph("5.1 Basic Table").style("Heading2");
352 {
353 let mut tbl = doc
354 .add_table(4, 3)
355 .borders(BorderStyle::Single, 4, "000000");
356 for col in 0..3 {
357 tbl.cell(0, col).unwrap().shading("2E75B6");
358 }
359 tbl.cell(0, 0).unwrap().set_text("Name");
360 tbl.cell(0, 1).unwrap().set_text("Role");
361 tbl.cell(0, 2).unwrap().set_text("Location");
362 tbl.cell(1, 0).unwrap().set_text("Walter White");
363 tbl.cell(1, 1).unwrap().set_text("CEO");
364 tbl.cell(1, 2).unwrap().set_text("Albuquerque");
365 tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
366 tbl.cell(2, 1).unwrap().set_text("CTO");
367 tbl.cell(2, 2).unwrap().set_text("Remote");
368 tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
369 tbl.cell(3, 1).unwrap().set_text("Security");
370 tbl.cell(3, 2).unwrap().set_text("Washington");
371 }
372
373 doc.add_paragraph("");
374
375 doc.add_paragraph("5.2 Column Span & Cell Shading")
376 .style("Heading2");
377 {
378 let mut tbl = doc
379 .add_table(3, 4)
380 .borders(BorderStyle::Single, 4, "000000")
381 .width_pct(100.0);
382 tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
383 tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
384 tbl.cell(1, 0).unwrap().set_text("Region");
385 tbl.cell(1, 0).unwrap().shading("D6E4F0");
386 tbl.cell(1, 1).unwrap().set_text("Q1");
387 tbl.cell(1, 1).unwrap().shading("D6E4F0");
388 tbl.cell(1, 2).unwrap().set_text("Q2");
389 tbl.cell(1, 2).unwrap().shading("D6E4F0");
390 tbl.cell(1, 3).unwrap().set_text("Total");
391 tbl.cell(1, 3).unwrap().shading("D6E4F0");
392 tbl.cell(2, 0).unwrap().set_text("Americas");
393 tbl.cell(2, 1).unwrap().set_text("$2.4M");
394 tbl.cell(2, 2).unwrap().set_text("$2.7M");
395 tbl.cell(2, 3).unwrap().set_text("$5.1M");
396 }
397
398 doc.add_paragraph("");
399
400 doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
401 {
402 let mut tbl = doc
403 .add_table(4, 3)
404 .borders(BorderStyle::Single, 4, "000000");
405 tbl.cell(0, 0).unwrap().set_text("Category");
406 tbl.cell(0, 0).unwrap().shading("E2EFDA");
407 tbl.cell(0, 1).unwrap().set_text("Item");
408 tbl.cell(0, 1).unwrap().shading("E2EFDA");
409 tbl.cell(0, 2).unwrap().set_text("Price");
410 tbl.cell(0, 2).unwrap().shading("E2EFDA");
411 tbl.cell(1, 0).unwrap().set_text("Hardware");
412 tbl.cell(1, 0).unwrap().v_merge_restart();
413 tbl.cell(1, 1).unwrap().set_text("Laptop");
414 tbl.cell(1, 2).unwrap().set_text("$1,200");
415 tbl.cell(2, 0).unwrap().v_merge_continue();
416 tbl.cell(2, 1).unwrap().set_text("Monitor");
417 tbl.cell(2, 2).unwrap().set_text("$450");
418 tbl.cell(3, 0).unwrap().set_text("Software");
419 tbl.cell(3, 1).unwrap().set_text("IDE License");
420 tbl.cell(3, 2).unwrap().set_text("$200/yr");
421 }
422
423 doc.add_paragraph("");
424
425 doc.add_paragraph("5.4 Nested Table").style("Heading2");
426 {
427 let mut tbl = doc
428 .add_table(2, 2)
429 .borders(BorderStyle::Single, 6, "2E75B6");
430 tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
431 tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
432 tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
433 {
434 let mut cell = tbl.cell(1, 1).unwrap();
435 cell.set_text("Nested table below:");
436 let mut nested = cell
437 .add_table(2, 2)
438 .borders(BorderStyle::Single, 2, "FF6600");
439 nested.cell(0, 0).unwrap().set_text("A");
440 nested.cell(0, 1).unwrap().set_text("B");
441 nested.cell(1, 0).unwrap().set_text("C");
442 nested.cell(1, 1).unwrap().set_text("D");
443 }
444 }
445
446 doc.add_paragraph("");
447
448 doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
449 .style("Heading2");
450 {
451 let mut tbl = doc
452 .add_table(2, 3)
453 .borders(BorderStyle::Single, 4, "666666");
454 tbl.row(0).unwrap().height(Length::pt(50.0)).header();
455 tbl.cell(0, 0).unwrap().set_text("Top");
456 tbl.cell(0, 0)
457 .unwrap()
458 .vertical_alignment(VerticalAlignment::Top)
459 .shading("FFF2CC");
460 tbl.cell(0, 1).unwrap().set_text("Center");
461 tbl.cell(0, 1)
462 .unwrap()
463 .vertical_alignment(VerticalAlignment::Center)
464 .shading("D9E2F3");
465 tbl.cell(0, 2).unwrap().set_text("Bottom");
466 tbl.cell(0, 2)
467 .unwrap()
468 .vertical_alignment(VerticalAlignment::Bottom)
469 .shading("E2EFDA");
470 tbl.row(1).unwrap().cant_split();
471 tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
472 tbl.cell(1, 0).unwrap().no_wrap();
473 tbl.cell(1, 1).unwrap().set_text("Fixed width");
474 tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
475 tbl.cell(1, 2).unwrap().set_text("Normal");
476 }
477
478 // ── SECTION 6: IMAGES ──
479 doc.add_paragraph("").page_break_before(true);
480 doc.add_paragraph("6. Images").style("Heading1");
481
482 doc.add_paragraph("6.1 Inline Image").style("Heading2");
483 doc.add_paragraph("A blue gradient image below:");
484 let img = create_sample_png(200, 50, [0, 80, 200]);
485 doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
486
487 doc.add_paragraph("");
488 doc.add_paragraph("6.2 Header Image").style("Heading2");
489 let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
490 doc.set_header_image(
491 &hdr_img,
492 "header_logo.png",
493 Length::inches(2.0),
494 Length::inches(0.2),
495 );
496 doc.add_paragraph("The header now contains an inline image (check the top of this page).");
497
498 doc.add_paragraph("");
499 doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
500
501 // ── SECTION 7: CONTENT MANIPULATION ──
502 doc.add_paragraph("").page_break_before(true);
503 doc.add_paragraph("7. Content Manipulation")
504 .style("Heading1");
505
506 doc.add_paragraph("7.1 Placeholder Replacement")
507 .style("Heading2");
508 doc.add_paragraph("Customer: {{customer}}");
509 doc.add_paragraph("Date: {{date}}");
510 doc.add_paragraph("Reference: {{ref_number}}");
511 {
512 let mut tbl = doc
513 .add_table(2, 2)
514 .borders(BorderStyle::Single, 4, "000000");
515 tbl.cell(0, 0).unwrap().set_text("Project");
516 tbl.cell(0, 0).unwrap().shading("D6E4F0");
517 tbl.cell(0, 1).unwrap().set_text("{{project}}");
518 tbl.cell(1, 0).unwrap().set_text("Status");
519 tbl.cell(1, 0).unwrap().shading("D6E4F0");
520 tbl.cell(1, 1).unwrap().set_text("{{status}}");
521 }
522
523 let mut replacements = HashMap::new();
524 replacements.insert("{{customer}}", "Tensorbee Inc.");
525 replacements.insert("{{date}}", "February 22, 2026");
526 replacements.insert("{{ref_number}}", "TB-2026-001");
527 replacements.insert("{{project}}", "Infrastructure Upgrade");
528 replacements.insert("{{status}}", "In Progress");
529 let n = doc.replace_all(&replacements);
530 doc.add_paragraph(&format!("({n} placeholders replaced above)"));
531
532 doc.add_paragraph("");
533
534 doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
535 doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
536 let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
537 doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
538
539 doc.add_paragraph("");
540
541 doc.add_paragraph("7.3 Content Insertion").style("Heading2");
542 doc.add_paragraph("Section A: First content.");
543 doc.add_paragraph("Section C: Third content.");
544 if let Some(idx) = doc.find_content_index("Section C") {
545 doc.insert_paragraph(
546 idx,
547 "Section B: Inserted between A and C via find_content_index().",
548 );
549 }
550
551 // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
552 doc.add_paragraph("").section_break(SectionBreak::NextPage);
553 doc.add_paragraph("8. Section Breaks & Orientation")
554 .style("Heading1");
555 doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
556
557 {
558 let mut tbl = doc
559 .add_table(3, 7)
560 .borders(BorderStyle::Single, 4, "2E75B6");
561 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
562 for (c, h) in headers.iter().enumerate() {
563 tbl.cell(0, c).unwrap().set_text(h);
564 tbl.cell(0, c).unwrap().shading("2E75B6");
565 }
566 let data = [
567 [
568 "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
569 ],
570 [
571 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
572 ],
573 ];
574 for (r, row) in data.iter().enumerate() {
575 for (c, v) in row.iter().enumerate() {
576 tbl.cell(r + 1, c).unwrap().set_text(v);
577 }
578 }
579 }
580
581 doc.add_paragraph("")
582 .section_break(SectionBreak::NextPage)
583 .section_landscape();
584
585 // ── SECTION 9: CUSTOM STYLES ──
586 doc.add_paragraph("9. Custom Styles").style("Heading1");
587 doc.add_style(
588 StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
589 .based_on("Normal")
590 .paragraph_properties({
591 let mut ppr = rdocx_oxml::properties::CT_PPr::default();
592 ppr.shading = Some(rdocx_oxml::properties::CT_Shd {
593 val: "clear".to_string(),
594 color: None,
595 fill: Some("FFF2CC".to_string()),
596 });
597 ppr
598 })
599 .run_properties({
600 let mut rpr = rdocx_oxml::properties::CT_RPr::default();
601 rpr.bold = Some(true);
602 rpr.color = Some("C45911".to_string());
603 rpr
604 }),
605 );
606 doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
607 .style("CustomHighlight");
608
609 doc.add_paragraph("");
610
611 // ── SECTION 10: DOCUMENT INTELLIGENCE ──
612 doc.add_paragraph("10. Document Intelligence API")
613 .style("Heading1");
614
615 let wc = doc.word_count();
616 let hc = doc.headings().len();
617 let ic = doc.images().len();
618 let lc = doc.links().len();
619 doc.add_paragraph(&format!("Word count: {wc}"));
620 doc.add_paragraph(&format!("Heading count: {hc}"));
621 doc.add_paragraph(&format!("Image count: {ic}"));
622 doc.add_paragraph(&format!("Link count: {lc}"));
623
624 let outline = doc.document_outline();
625 doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
626
627 let issues = doc.audit_accessibility();
628 doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
629 for issue in &issues {
630 doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
631 }
632
633 // ── SECTION 11: DOCUMENT MERGING ──
634 doc.add_paragraph("");
635 doc.add_paragraph("11. Document Merging").style("Heading1");
636 {
637 let mut other = Document::new();
638 other.add_paragraph("This paragraph was merged from another document using append().");
639 doc.append(&other);
640 }
641
642 doc.add_paragraph("");
643
644 // ── SUMMARY ──
645 doc.add_paragraph("Summary of Demonstrated Features")
646 .style("Heading1");
647 let features = [
648 "Page setup: size, margins, header/footer distance, gutter",
649 "Document metadata: title, author, subject, keywords",
650 "Headers and footers: text, images, different first page",
651 "Background images (full-page behind text)",
652 "Table of Contents generation with bookmarks",
653 "Text formatting: bold, italic, underline styles, strike, color, size, font",
654 "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
655 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
656 "Pagination controls: keep-with-next, keep-together, widow control",
657 "Bullet and numbered lists with nesting",
658 "Tab stops with dot/underscore/hyphen leaders",
659 "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
660 "Row properties: header rows, exact height, cant-split",
661 "Cell properties: width, no-wrap, vertical alignment",
662 "Inline images and header images",
663 "Placeholder replacement (single and batch)",
664 "Regex-based find and replace",
665 "Content insertion at specific positions",
666 "Section breaks with mixed portrait/landscape",
667 "Custom paragraph and character styles",
668 "Document intelligence: word count, headings, outline, images, links",
669 "Accessibility audit",
670 "Document merging (append)",
671 "Export: DOCX, PDF, HTML, Markdown",
672 ];
673 for f in &features {
674 doc.add_bullet_list_item(f, 0);
675 }
676 doc.add_paragraph("");
677 doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
678 .alignment(Alignment::Center)
679 .shading("E2EFDA")
680 .border_all(BorderStyle::Single, 2, "00AA00");
681
682 doc
683}
684
685// =============================================================================
686// 2. PROPOSAL DOCUMENT — Deep navy + gold scheme
687// =============================================================================
688fn generate_proposal(_samples_dir: &Path) -> Document {
689 let mut doc = Document::new();
690
691 // Colors: Navy #1B2A4A, Gold #C5922E, Light #F4F1EB
692 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
693 doc.set_margins(
694 Length::inches(1.0),
695 Length::inches(1.0),
696 Length::inches(1.0),
697 Length::inches(1.0),
698 );
699 doc.set_title("AI Platform Modernization Proposal");
700 doc.set_author("Walter White");
701 doc.set_subject("Technology Proposal");
702 doc.set_keywords("proposal, AI, modernization, Tensorbee");
703
704 // Banner header
705 let logo_img = create_logo_png(220, 48);
706 let banner = build_header_banner_xml(
707 "rId1",
708 &BannerOpts {
709 bg_color: "1B2A4A",
710 banner_width: 7772400,
711 banner_height: 914400,
712 logo_width: 2011680,
713 logo_height: 438912,
714 logo_x_offset: 295125,
715 logo_y_offset: 237744,
716 },
717 );
718 doc.set_raw_header_with_images(
719 banner,
720 &[("rId1", &logo_img, "logo.png")],
721 rdocx_oxml::header_footer::HdrFtrType::Default,
722 );
723 doc.set_different_first_page(true);
724 let cover_banner = build_header_banner_xml(
725 "rId1",
726 &BannerOpts {
727 bg_color: "C5922E",
728 banner_width: 7772400,
729 banner_height: 914400,
730 logo_width: 2011680,
731 logo_height: 438912,
732 logo_x_offset: 295125,
733 logo_y_offset: 237744,
734 },
735 );
736 doc.set_raw_header_with_images(
737 cover_banner,
738 &[("rId1", &logo_img, "logo.png")],
739 rdocx_oxml::header_footer::HdrFtrType::First,
740 );
741 doc.set_margins(
742 Length::twips(2292),
743 Length::twips(1440),
744 Length::twips(1440),
745 Length::twips(1440),
746 );
747 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
748 doc.set_footer("Tensorbee — Confidential");
749
750 // Custom styles
751 doc.add_style(
752 StyleBuilder::paragraph("ProposalTitle", "Proposal Title")
753 .based_on("Normal")
754 .run_properties({
755 let mut rpr = rdocx_oxml::properties::CT_RPr::default();
756 rpr.bold = Some(true);
757 rpr.font_ascii = Some("Georgia".to_string());
758 rpr.font_hansi = Some("Georgia".to_string());
759 rpr.sz = Some(rdocx_oxml::HalfPoint(56)); // half-points → 28pt
760 rpr.color = Some("1B2A4A".to_string());
761 rpr
762 }),
763 );
764
765 // ── Cover Page ──
766 for _ in 0..4 {
767 doc.add_paragraph("");
768 }
769 doc.add_paragraph("AI Platform Modernization")
770 .style("ProposalTitle")
771 .alignment(Alignment::Center);
772 doc.add_paragraph("")
773 .alignment(Alignment::Center)
774 .border_bottom(BorderStyle::Single, 8, "C5922E");
775 {
776 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
777 p.add_run("Prepared for Global Financial Corp.")
778 .size(14.0)
779 .color("666666")
780 .italic(true);
781 }
782 {
783 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
784 p.add_run("Prepared by: Walter White, CEO — Tensorbee")
785 .size(12.0)
786 .color("888888");
787 }
788 {
789 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
790 p.add_run("February 22, 2026").size(12.0).color("888888");
791 }
792
793 // ── TOC ──
794 doc.add_paragraph("").page_break_before(true);
795 doc.add_paragraph("Table of Contents").style("Heading1");
796 // We'll add a manual-style TOC since the insert_toc goes at a specific position
797 doc.insert_toc(doc.content_count(), 2);
798
799 // ── Executive Summary ──
800 doc.add_paragraph("").page_break_before(true);
801 doc.add_paragraph("Executive Summary").style("Heading1");
802 doc.add_paragraph(
803 "Tensorbee proposes a comprehensive modernization of Global Financial Corp's AI platform, \
804 transitioning from legacy batch-processing systems to a real-time inference architecture. \
805 This transformation will reduce model deployment time from weeks to hours, improve \
806 prediction accuracy by an estimated 23%, and deliver $4.2M in annual operational savings.",
807 )
808 .first_line_indent(Length::inches(0.3));
809 doc.add_paragraph(
810 "The project spans three phases over 18 months, with a total investment of $2.8M. \
811 Tensorbee brings deep expertise in ML infrastructure, having successfully completed \
812 similar transformations for three Fortune 500 financial institutions.",
813 )
814 .first_line_indent(Length::inches(0.3));
815
816 // Key metrics table
817 doc.add_paragraph("");
818 {
819 let mut tbl = doc
820 .add_table(5, 2)
821 .borders(BorderStyle::Single, 4, "1B2A4A")
822 .width_pct(80.0);
823 tbl.cell(0, 0).unwrap().set_text("Key Metric");
824 tbl.cell(0, 0).unwrap().shading("1B2A4A");
825 tbl.cell(0, 1).unwrap().set_text("Value");
826 tbl.cell(0, 1).unwrap().shading("1B2A4A");
827 let rows = [
828 ("Total Investment", "$2.8M"),
829 ("Annual Savings", "$4.2M"),
830 ("ROI (Year 1)", "150%"),
831 ("Timeline", "18 months"),
832 ];
833 for (i, (k, v)) in rows.iter().enumerate() {
834 tbl.cell(i + 1, 0).unwrap().set_text(k);
835 if i % 2 == 0 {
836 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
837 }
838 tbl.cell(i + 1, 1).unwrap().set_text(v);
839 if i % 2 == 0 {
840 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
841 }
842 }
843 }
844
845 // ── Problem Statement ──
846 doc.add_paragraph("").page_break_before(true);
847 doc.add_paragraph("Problem Statement").style("Heading1");
848 doc.add_paragraph(
849 "Global Financial Corp's current AI infrastructure faces three critical challenges:",
850 );
851 doc.add_numbered_list_item("Deployment Latency — New models take 3-4 weeks to deploy to production, compared to the industry benchmark of 2-3 days.", 0);
852 doc.add_numbered_list_item("Scalability Constraints — The batch processing architecture cannot handle real-time inference demands during peak trading hours.", 0);
853 doc.add_numbered_list_item("Technical Debt — Legacy Python 2.7 codebases and manual deployment scripts create reliability risks and slow iteration speed.", 0);
854
855 // ── Proposed Solution ──
856 doc.add_paragraph("").page_break_before(true);
857 doc.add_paragraph("Proposed Solution").style("Heading1");
858
859 doc.add_paragraph("Phase 1: Foundation (Months 1-6)")
860 .style("Heading2");
861 doc.add_bullet_list_item("Deploy Kubernetes-based ML serving infrastructure", 0);
862 doc.add_bullet_list_item("Implement CI/CD pipeline for model deployment", 0);
863 doc.add_bullet_list_item("Migrate top 5 production models to new platform", 0);
864
865 doc.add_paragraph("Phase 2: Expansion (Months 7-12)")
866 .style("Heading2");
867 doc.add_bullet_list_item(
868 "Real-time feature engineering pipeline (Apache Kafka + Flink)",
869 0,
870 );
871 doc.add_bullet_list_item("A/B testing framework for model variants", 0);
872 doc.add_bullet_list_item("Automated model monitoring and drift detection", 0);
873
874 doc.add_paragraph("Phase 3: Optimization (Months 13-18)")
875 .style("Heading2");
876 doc.add_bullet_list_item("GPU inference optimization (TensorRT/ONNX Runtime)", 0);
877 doc.add_bullet_list_item("Multi-region deployment for latency reduction", 0);
878 doc.add_bullet_list_item(
879 "Self-service model deployment portal for data science teams",
880 0,
881 );
882
883 // ── Budget ──
884 doc.add_paragraph("Budget Breakdown").style("Heading1");
885 {
886 let mut tbl = doc
887 .add_table(6, 3)
888 .borders(BorderStyle::Single, 4, "1B2A4A")
889 .width_pct(100.0);
890 tbl.cell(0, 0).unwrap().set_text("Category");
891 tbl.cell(0, 0).unwrap().shading("1B2A4A");
892 tbl.cell(0, 1).unwrap().set_text("Cost");
893 tbl.cell(0, 1).unwrap().shading("1B2A4A");
894 tbl.cell(0, 2).unwrap().set_text("Notes");
895 tbl.cell(0, 2).unwrap().shading("1B2A4A");
896 let items = [
897 (
898 "Infrastructure",
899 "$450,000",
900 "Cloud compute + GPU instances",
901 ),
902 ("Engineering Services", "$1,600,000", "12 FTE x 18 months"),
903 (
904 "Software Licenses",
905 "$350,000",
906 "Kafka, monitoring, CI/CD tools",
907 ),
908 (
909 "Training & Enablement",
910 "$200,000",
911 "Team training, documentation",
912 ),
913 ("Contingency (10%)", "$200,000", "Risk buffer"),
914 ];
915 for (i, (cat, cost, note)) in items.iter().enumerate() {
916 tbl.cell(i + 1, 0).unwrap().set_text(cat);
917 tbl.cell(i + 1, 1).unwrap().set_text(cost);
918 tbl.cell(i + 1, 2).unwrap().set_text(note);
919 if i % 2 == 0 {
920 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
921 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
922 tbl.cell(i + 1, 2).unwrap().shading("F4F1EB");
923 }
924 }
925 }
926
927 // ── Closing ──
928 doc.add_paragraph("");
929 doc.add_paragraph("Next Steps").style("Heading1");
930 doc.add_numbered_list_item(
931 "Schedule technical deep-dive with GFC engineering team (Week 1)",
932 0,
933 );
934 doc.add_numbered_list_item("Finalize scope and sign SOW (Week 2-3)", 0);
935 doc.add_numbered_list_item("Kick off Phase 1 with joint planning session (Week 4)", 0);
936
937 doc.add_paragraph("");
938 {
939 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
940 p.add_run("Walter White")
941 .bold(true)
942 .size(14.0)
943 .color("1B2A4A");
944 }
945 {
946 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
947 p.add_run("CEO, Tensorbee | walter@tensorbee.com")
948 .size(10.0)
949 .color("888888");
950 }
951
952 doc
953}
954
955// =============================================================================
956// 3. QUOTE / BILL OF MATERIALS — Teal + orange scheme
957// =============================================================================
958fn generate_quote(_samples_dir: &Path) -> Document {
959 let mut doc = Document::new();
960
961 // Colors: Teal #008B8B, Dark #1A3C3C, Orange #E07020, Light #F0F8F8
962 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
963 doc.set_margins(
964 Length::inches(0.75),
965 Length::inches(0.75),
966 Length::inches(0.75),
967 Length::inches(0.75),
968 );
969 doc.set_title("Quotation QT-2026-0147");
970 doc.set_author("Walter White");
971 doc.set_keywords("quote, BOM, Tensorbee");
972
973 doc.set_header("Tensorbee — Quotation");
974 doc.set_footer("QT-2026-0147 | Page");
975
976 // ── Header Block ──
977 {
978 let mut p = doc.add_paragraph("").alignment(Alignment::Left);
979 p.add_run("TENSORBEE")
980 .bold(true)
981 .size(28.0)
982 .color("008B8B")
983 .font("Helvetica");
984 }
985 doc.add_paragraph("123 Innovation Drive, Suite 400")
986 .alignment(Alignment::Left);
987 doc.add_paragraph("San Francisco, CA 94105 | +1 (415) 555-0199")
988 .alignment(Alignment::Left);
989 doc.add_paragraph("")
990 .border_bottom(BorderStyle::Single, 8, "008B8B");
991
992 // Quote meta
993 doc.add_paragraph("");
994 {
995 let mut tbl = doc.add_table(4, 4).width_pct(100.0);
996 tbl.cell(0, 0).unwrap().set_text("QUOTATION");
997 tbl.cell(0, 0).unwrap().shading("008B8B").grid_span(2);
998 tbl.cell(0, 2).unwrap().set_text("DATE");
999 tbl.cell(0, 2).unwrap().shading("008B8B");
1000 tbl.cell(0, 3).unwrap().set_text("VALID UNTIL");
1001 tbl.cell(0, 3).unwrap().shading("008B8B");
1002 tbl.cell(1, 0).unwrap().set_text("Quote #:");
1003 tbl.cell(1, 1).unwrap().set_text("QT-2026-0147");
1004 tbl.cell(1, 2).unwrap().set_text("Feb 22, 2026");
1005 tbl.cell(1, 3).unwrap().set_text("Mar 22, 2026");
1006 tbl.cell(2, 0).unwrap().set_text("Prepared by:");
1007 tbl.cell(2, 1).unwrap().set_text("Walter White");
1008 tbl.cell(2, 2).unwrap().set_text("Payment:");
1009 tbl.cell(2, 3).unwrap().set_text("Net 30");
1010 tbl.cell(3, 0).unwrap().set_text("Customer:");
1011 tbl.cell(3, 1).unwrap().set_text("Meridian Dynamics LLC");
1012 tbl.cell(3, 2).unwrap().set_text("Currency:");
1013 tbl.cell(3, 3).unwrap().set_text("USD");
1014 }
1015
1016 doc.add_paragraph("");
1017
1018 // ── Bill of Materials ──
1019 {
1020 let mut p = doc.add_paragraph("");
1021 p.add_run("Bill of Materials")
1022 .bold(true)
1023 .size(16.0)
1024 .color("1A3C3C");
1025 }
1026 doc.add_paragraph("");
1027
1028 {
1029 let mut tbl = doc
1030 .add_table(10, 6)
1031 .borders(BorderStyle::Single, 2, "008B8B")
1032 .width_pct(100.0)
1033 .layout_fixed();
1034 // Headers
1035 let hdrs = [
1036 "#",
1037 "Part Number",
1038 "Description",
1039 "Qty",
1040 "Unit Price",
1041 "Total",
1042 ];
1043 for (c, h) in hdrs.iter().enumerate() {
1044 tbl.cell(0, c).unwrap().set_text(h);
1045 tbl.cell(0, c).unwrap().shading("008B8B");
1046 }
1047
1048 let items: Vec<(&str, &str, &str, &str, &str)> = vec![
1049 (
1050 "TB-GPU-A100",
1051 "NVIDIA A100 80GB GPU",
1052 "4",
1053 "$12,500.00",
1054 "$50,000.00",
1055 ),
1056 (
1057 "TB-SRV-R750",
1058 "Dell R750xa Server Chassis",
1059 "2",
1060 "$8,200.00",
1061 "$16,400.00",
1062 ),
1063 (
1064 "TB-RAM-256G",
1065 "256GB DDR5 ECC Memory Module",
1066 "8",
1067 "$890.00",
1068 "$7,120.00",
1069 ),
1070 (
1071 "TB-SSD-3840",
1072 "3.84TB NVMe U.2 SSD",
1073 "8",
1074 "$1,150.00",
1075 "$9,200.00",
1076 ),
1077 (
1078 "TB-NET-CX7",
1079 "ConnectX-7 200GbE NIC",
1080 "4",
1081 "$1,800.00",
1082 "$7,200.00",
1083 ),
1084 (
1085 "TB-SW-48P",
1086 "48-Port 100GbE Switch",
1087 "1",
1088 "$22,000.00",
1089 "$22,000.00",
1090 ),
1091 (
1092 "TB-CAB-RACK",
1093 "42U Server Rack + PDU",
1094 "1",
1095 "$4,500.00",
1096 "$4,500.00",
1097 ),
1098 (
1099 "TB-SVC-INST",
1100 "Installation & Configuration",
1101 "1",
1102 "$8,500.00",
1103 "$8,500.00",
1104 ),
1105 (
1106 "TB-SVC-SUPP",
1107 "3-Year Premium Support",
1108 "1",
1109 "$15,000.00",
1110 "$15,000.00",
1111 ),
1112 ];
1113
1114 for (i, (pn, desc, qty, unit, total)) in items.iter().enumerate() {
1115 let row = i + 1;
1116 tbl.cell(row, 0).unwrap().set_text(&format!("{}", i + 1));
1117 tbl.cell(row, 1).unwrap().set_text(pn);
1118 tbl.cell(row, 2).unwrap().set_text(desc);
1119 tbl.cell(row, 3).unwrap().set_text(qty);
1120 tbl.cell(row, 4).unwrap().set_text(unit);
1121 tbl.cell(row, 5).unwrap().set_text(total);
1122 if i % 2 == 0 {
1123 for c in 0..6 {
1124 tbl.cell(row, c).unwrap().shading("F0F8F8");
1125 }
1126 }
1127 }
1128 }
1129
1130 doc.add_paragraph("");
1131
1132 // ── Totals ──
1133 {
1134 let mut tbl = doc
1135 .add_table(4, 2)
1136 .borders(BorderStyle::Single, 2, "008B8B")
1137 .width(Length::inches(3.5))
1138 .alignment(Alignment::Right);
1139 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1140 tbl.cell(0, 1).unwrap().set_text("$139,920.00");
1141 tbl.cell(1, 0).unwrap().set_text("Shipping & Handling");
1142 tbl.cell(1, 1).unwrap().set_text("$2,500.00");
1143 tbl.cell(2, 0).unwrap().set_text("Tax (8.625%)");
1144 tbl.cell(2, 1).unwrap().set_text("$12,068.10");
1145 tbl.cell(3, 0).unwrap().set_text("TOTAL");
1146 tbl.cell(3, 0).unwrap().shading("E07020");
1147 tbl.cell(3, 1).unwrap().set_text("$154,488.10");
1148 tbl.cell(3, 1).unwrap().shading("E07020");
1149 }
1150
1151 doc.add_paragraph("");
1152
1153 // ── Terms & Conditions ──
1154 {
1155 let mut p = doc.add_paragraph("");
1156 p.add_run("Terms & Conditions")
1157 .bold(true)
1158 .size(14.0)
1159 .color("1A3C3C");
1160 }
1161 doc.add_numbered_list_item(
1162 "This quotation is valid for 30 calendar days from the date of issue.",
1163 0,
1164 );
1165 doc.add_numbered_list_item(
1166 "All prices are in USD and exclusive of applicable taxes unless stated.",
1167 0,
1168 );
1169 doc.add_numbered_list_item("Standard lead time is 4-6 weeks from PO receipt.", 0);
1170 doc.add_numbered_list_item(
1171 "Payment terms: Net 30 from invoice date. 2% discount for payment within 10 days.",
1172 0,
1173 );
1174 doc.add_numbered_list_item(
1175 "Warranty: 3-year manufacturer warranty on all hardware components.",
1176 0,
1177 );
1178 doc.add_numbered_list_item(
1179 "Returns subject to 15% restocking fee if initiated after 14 days.",
1180 0,
1181 );
1182
1183 doc.add_paragraph("");
1184 doc.add_paragraph("")
1185 .border_bottom(BorderStyle::Single, 4, "008B8B");
1186 doc.add_paragraph("");
1187 {
1188 let mut p = doc.add_paragraph("");
1189 p.add_run("Accepted by: ").bold(true);
1190 p.add_run("___________________________ Date: ___________");
1191 }
1192 {
1193 let mut p = doc.add_paragraph("");
1194 p.add_run("Print Name: ").bold(true);
1195 p.add_run("___________________________ Title: ___________");
1196 }
1197
1198 doc
1199}
1200
1201// =============================================================================
1202// 4. INVOICE — Crimson + charcoal scheme
1203// =============================================================================
1204fn generate_invoice(_samples_dir: &Path) -> Document {
1205 let mut doc = Document::new();
1206
1207 // Colors: Crimson #B22222, Charcoal #333333, Light #FAF0F0
1208 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1209 doc.set_margins(
1210 Length::inches(0.75),
1211 Length::inches(0.75),
1212 Length::inches(0.75),
1213 Length::inches(0.75),
1214 );
1215 doc.set_title("Invoice INV-2026-0392");
1216 doc.set_author("Walter White");
1217 doc.set_keywords("invoice, Tensorbee");
1218
1219 doc.set_footer("Tensorbee — Thank you for your business!");
1220
1221 // ── Company & Invoice Header ──
1222 {
1223 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1224 // Company name left, INVOICE right
1225 {
1226 let mut cell = tbl.cell(0, 0).unwrap();
1227 let mut p = cell.add_paragraph("");
1228 p.add_run("TENSORBEE")
1229 .bold(true)
1230 .size(32.0)
1231 .color("B22222")
1232 .font("Helvetica");
1233 }
1234 {
1235 let mut cell = tbl.cell(0, 1).unwrap();
1236 let mut p = cell.add_paragraph("");
1237 p.add_run("INVOICE").bold(true).size(32.0).color("333333");
1238 }
1239 tbl.cell(1, 0)
1240 .unwrap()
1241 .set_text("123 Innovation Drive, Suite 400");
1242 tbl.cell(1, 1).unwrap().set_text("Invoice #: INV-2026-0392");
1243 tbl.cell(2, 0).unwrap().set_text("San Francisco, CA 94105");
1244 tbl.cell(2, 1).unwrap().set_text("Date: February 22, 2026");
1245 tbl.cell(3, 0).unwrap().set_text("walter@tensorbee.com");
1246 tbl.cell(3, 1).unwrap().set_text("Due Date: March 24, 2026");
1247 }
1248
1249 doc.add_paragraph("")
1250 .border_bottom(BorderStyle::Thick, 8, "B22222");
1251 doc.add_paragraph("");
1252
1253 // ── Bill To / Ship To ──
1254 {
1255 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1256 tbl.cell(0, 0).unwrap().set_text("BILL TO");
1257 tbl.cell(0, 0).unwrap().shading("B22222");
1258 tbl.cell(0, 1).unwrap().set_text("SHIP TO");
1259 tbl.cell(0, 1).unwrap().shading("B22222");
1260 tbl.cell(1, 0).unwrap().set_text("Meridian Dynamics LLC");
1261 tbl.cell(1, 1).unwrap().set_text("Meridian Dynamics LLC");
1262 tbl.cell(2, 0).unwrap().set_text("456 Enterprise Blvd");
1263 tbl.cell(2, 1).unwrap().set_text("Attn: Server Room B");
1264 tbl.cell(3, 0).unwrap().set_text("Austin, TX 78701");
1265 tbl.cell(3, 1)
1266 .unwrap()
1267 .set_text("456 Enterprise Blvd, Austin, TX 78701");
1268 }
1269
1270 doc.add_paragraph("");
1271
1272 // ── Line Items ──
1273 {
1274 let mut tbl = doc
1275 .add_table(8, 5)
1276 .borders(BorderStyle::Single, 2, "B22222")
1277 .width_pct(100.0);
1278 let hdrs = ["Description", "Qty", "Unit Price", "Tax", "Amount"];
1279 for (c, h) in hdrs.iter().enumerate() {
1280 tbl.cell(0, c).unwrap().set_text(h);
1281 tbl.cell(0, c).unwrap().shading("B22222");
1282 }
1283 let items = [
1284 (
1285 "ML Infrastructure Setup — Phase 1",
1286 "1",
1287 "$45,000.00",
1288 "$3,881.25",
1289 "$48,881.25",
1290 ),
1291 (
1292 "Data Pipeline Development (160 hrs)",
1293 "160",
1294 "$225.00",
1295 "$3,105.00",
1296 "$39,105.00",
1297 ),
1298 (
1299 "GPU Cluster Configuration",
1300 "1",
1301 "$12,000.00",
1302 "$1,035.00",
1303 "$13,035.00",
1304 ),
1305 (
1306 "API Gateway Implementation",
1307 "1",
1308 "$18,500.00",
1309 "$1,595.63",
1310 "$20,095.63",
1311 ),
1312 (
1313 "Load Testing & QA (80 hrs)",
1314 "80",
1315 "$195.00",
1316 "$1,345.50",
1317 "$16,945.50",
1318 ),
1319 (
1320 "Documentation & Training",
1321 "1",
1322 "$8,000.00",
1323 "$690.00",
1324 "$8,690.00",
1325 ),
1326 (
1327 "Project Management (3 months)",
1328 "3",
1329 "$6,500.00",
1330 "$1,679.63",
1331 "$21,179.63",
1332 ),
1333 ];
1334 for (i, (desc, qty, unit, tax, amt)) in items.iter().enumerate() {
1335 let r = i + 1;
1336 tbl.cell(r, 0).unwrap().set_text(desc);
1337 tbl.cell(r, 1).unwrap().set_text(qty);
1338 tbl.cell(r, 2).unwrap().set_text(unit);
1339 tbl.cell(r, 3).unwrap().set_text(tax);
1340 tbl.cell(r, 4).unwrap().set_text(amt);
1341 if i % 2 == 0 {
1342 for c in 0..5 {
1343 tbl.cell(r, c).unwrap().shading("FAF0F0");
1344 }
1345 }
1346 }
1347 }
1348
1349 doc.add_paragraph("");
1350
1351 // ── Totals ──
1352 {
1353 let mut tbl = doc
1354 .add_table(4, 2)
1355 .borders(BorderStyle::Single, 2, "B22222")
1356 .width(Length::inches(3.0))
1357 .alignment(Alignment::Right);
1358 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1359 tbl.cell(0, 1).unwrap().set_text("$154,600.00");
1360 tbl.cell(1, 0).unwrap().set_text("Tax (8.625%)");
1361 tbl.cell(1, 1).unwrap().set_text("$13,334.25");
1362 tbl.cell(2, 0).unwrap().set_text("Discount (5%)");
1363 tbl.cell(2, 1).unwrap().set_text("-$7,730.00");
1364 tbl.cell(3, 0).unwrap().set_text("AMOUNT DUE");
1365 tbl.cell(3, 0).unwrap().shading("B22222");
1366 tbl.cell(3, 1).unwrap().set_text("$160,204.25");
1367 tbl.cell(3, 1).unwrap().shading("B22222");
1368 }
1369
1370 doc.add_paragraph("");
1371 doc.add_paragraph("");
1372
1373 // ── Payment Details ──
1374 {
1375 let mut p = doc.add_paragraph("");
1376 p.add_run("Payment Details")
1377 .bold(true)
1378 .size(14.0)
1379 .color("333333");
1380 }
1381 doc.add_paragraph("Bank: Silicon Valley Bank")
1382 .indent_left(Length::inches(0.3));
1383 doc.add_paragraph("Account: Tensorbee Inc. — 0847-2953-1120")
1384 .indent_left(Length::inches(0.3));
1385 doc.add_paragraph("Routing: 121140399")
1386 .indent_left(Length::inches(0.3));
1387 doc.add_paragraph("Swift: SVBKUS6S")
1388 .indent_left(Length::inches(0.3));
1389
1390 doc.add_paragraph("");
1391
1392 doc.add_paragraph("Please include invoice number INV-2026-0392 in the payment reference.")
1393 .shading("FAF0F0")
1394 .border_all(BorderStyle::Single, 2, "B22222");
1395
1396 doc
1397}
1398
1399// =============================================================================
1400// 5. REPORT — Forest green + earth tones, with images & hierarchical sections
1401// =============================================================================
1402fn generate_report(_samples_dir: &Path) -> Document {
1403 let mut doc = Document::new();
1404
1405 // Colors: Forest #2D5016, Sage #6B8E23, Earth #8B7355, Cream #FFFAF0
1406 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1407 doc.set_margins(
1408 Length::inches(1.0),
1409 Length::inches(1.0),
1410 Length::inches(1.0),
1411 Length::inches(1.0),
1412 );
1413 doc.set_title("Q4 2025 Environmental Impact Report");
1414 doc.set_author("Walter White");
1415 doc.set_subject("Quarterly Environmental Report");
1416 doc.set_keywords("environment, sustainability, report, Tensorbee");
1417
1418 doc.set_different_first_page(true);
1419 doc.set_first_page_header("");
1420 doc.set_header("Tensorbee — Q4 2025 Environmental Impact Report");
1421 doc.set_footer("CONFIDENTIAL — Page");
1422
1423 // ── Cover ──
1424 let cover_bg = create_sample_png(612, 792, [20, 50, 15]);
1425 doc.add_background_image(&cover_bg, "report_cover.png");
1426
1427 for _ in 0..5 {
1428 doc.add_paragraph("");
1429 }
1430 {
1431 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1432 p.add_run("Q4 2025")
1433 .bold(true)
1434 .size(48.0)
1435 .color("FFFFFF")
1436 .font("Georgia");
1437 }
1438 {
1439 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1440 p.add_run("Environmental Impact Report")
1441 .size(24.0)
1442 .color("90EE90")
1443 .font("Georgia")
1444 .italic(true);
1445 }
1446 doc.add_paragraph("");
1447 {
1448 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1449 p.add_run("Tensorbee — Sustainability Division")
1450 .size(14.0)
1451 .color("C0C0C0");
1452 }
1453 {
1454 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1455 p.add_run("Prepared by Walter White, Chief Sustainability Officer")
1456 .size(11.0)
1457 .color("AAAAAA");
1458 }
1459
1460 // ── TOC ──
1461 doc.add_paragraph("").page_break_before(true);
1462 doc.insert_toc(doc.content_count(), 3);
1463
1464 // ── Section 1: Executive Overview ──
1465 doc.add_paragraph("").page_break_before(true);
1466 doc.add_paragraph("Executive Overview").style("Heading1");
1467 doc.add_paragraph(
1468 "This report presents Tensorbee's environmental performance for Q4 2025. Our \
1469 sustainability initiatives have yielded a 34% reduction in carbon emissions compared \
1470 to Q4 2024, exceeding our target of 25%. Key achievements include the transition to \
1471 100% renewable energy in our primary data centers and the launch of our carbon offset \
1472 marketplace.",
1473 )
1474 .first_line_indent(Length::inches(0.3));
1475
1476 // Image: performance chart
1477 let chart_img = create_chart_png(400, 200);
1478 doc.add_paragraph("");
1479 doc.add_picture(
1480 &chart_img,
1481 "performance_chart.png",
1482 Length::inches(5.0),
1483 Length::inches(2.5),
1484 )
1485 .alignment(Alignment::Center);
1486 {
1487 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1488 p.add_run("Figure 1: Quarterly Carbon Emissions (tonnes CO2e)")
1489 .italic(true)
1490 .size(9.0)
1491 .color("666666");
1492 }
1493
1494 // ── Section 2: Energy Consumption ──
1495 doc.add_paragraph("").page_break_before(true);
1496 doc.add_paragraph("Energy Consumption").style("Heading1");
1497
1498 doc.add_paragraph("Data Center Operations")
1499 .style("Heading2");
1500 doc.add_paragraph(
1501 "Our three primary data centers consumed a combined 4.2 GWh in Q4 2025, \
1502 a 12% reduction from Q3 2025 driven by improved cooling efficiency and \
1503 server consolidation.",
1504 );
1505
1506 // Data table
1507 {
1508 let mut tbl = doc
1509 .add_table(5, 4)
1510 .borders(BorderStyle::Single, 4, "2D5016")
1511 .width_pct(100.0);
1512 let hdrs = ["Data Center", "Capacity (MW)", "Usage (GWh)", "PUE"];
1513 for (c, h) in hdrs.iter().enumerate() {
1514 tbl.cell(0, c).unwrap().set_text(h);
1515 tbl.cell(0, c).unwrap().shading("2D5016");
1516 }
1517 let rows = [
1518 ("San Francisco (Primary)", "3.2", "1.8", "1.12"),
1519 ("Dublin (EU)", "2.1", "1.2", "1.18"),
1520 ("Singapore (APAC)", "1.8", "1.2", "1.24"),
1521 ("TOTAL", "7.1", "4.2", "1.17 avg"),
1522 ];
1523 for (i, (dc, cap, use_, pue)) in rows.iter().enumerate() {
1524 tbl.cell(i + 1, 0).unwrap().set_text(dc);
1525 tbl.cell(i + 1, 1).unwrap().set_text(cap);
1526 tbl.cell(i + 1, 2).unwrap().set_text(use_);
1527 tbl.cell(i + 1, 3).unwrap().set_text(pue);
1528 if i == 3 {
1529 for c in 0..4 {
1530 tbl.cell(i + 1, c).unwrap().shading("E2EFDA");
1531 }
1532 }
1533 }
1534 }
1535
1536 doc.add_paragraph("");
1537
1538 doc.add_paragraph("Renewable Energy Mix").style("Heading2");
1539 doc.add_paragraph("Breakdown of energy sources across all facilities:");
1540
1541 doc.add_bullet_list_item("Solar PV: 42% (1.76 GWh)", 0);
1542 doc.add_bullet_list_item("Wind Power Purchase Agreements: 38% (1.60 GWh)", 0);
1543 doc.add_bullet_list_item("Hydroelectric: 12% (0.50 GWh)", 0);
1544 doc.add_bullet_list_item("Grid (non-renewable): 8% (0.34 GWh)", 0);
1545
1546 // ── Section 3: Water & Waste ──
1547 doc.add_paragraph("Water & Waste Management")
1548 .style("Heading1");
1549
1550 doc.add_paragraph("Water Usage").style("Heading2");
1551 doc.add_paragraph(
1552 "Total water consumption was 12.4 million gallons, a 15% reduction from Q3. \
1553 Our closed-loop cooling systems now recycle 78% of water used in cooling operations.",
1554 );
1555
1556 doc.add_paragraph("Waste Reduction").style("Heading2");
1557 doc.add_paragraph("E-waste management results for Q4:")
1558 .keep_with_next(true);
1559 {
1560 let mut tbl = doc
1561 .add_table(4, 3)
1562 .borders(BorderStyle::Single, 2, "6B8E23")
1563 .width_pct(80.0);
1564 tbl.cell(0, 0).unwrap().set_text("Category");
1565 tbl.cell(0, 0).unwrap().shading("6B8E23");
1566 tbl.cell(0, 1).unwrap().set_text("Weight (kg)");
1567 tbl.cell(0, 1).unwrap().shading("6B8E23");
1568 tbl.cell(0, 2).unwrap().set_text("Recycled %");
1569 tbl.cell(0, 2).unwrap().shading("6B8E23");
1570 let rows = [
1571 ("Server Hardware", "2,450", "94%"),
1572 ("Networking Equipment", "820", "91%"),
1573 ("Storage Media", "340", "99%"),
1574 ];
1575 for (i, (cat, wt, pct)) in rows.iter().enumerate() {
1576 tbl.cell(i + 1, 0).unwrap().set_text(cat);
1577 tbl.cell(i + 1, 1).unwrap().set_text(wt);
1578 tbl.cell(i + 1, 2).unwrap().set_text(pct);
1579 }
1580 }
1581
1582 // ── Section 4: Initiatives ──
1583 doc.add_paragraph("").page_break_before(true);
1584 doc.add_paragraph("Q1 2026 Initiatives").style("Heading1");
1585
1586 doc.add_paragraph("Planned Programs").style("Heading2");
1587 doc.add_numbered_list_item(
1588 "Deploy on-site battery storage at SF data center (2 MWh capacity)",
1589 0,
1590 );
1591 doc.add_numbered_list_item("Pilot immersion cooling in Dublin facility", 0);
1592 doc.add_numbered_list_item("Launch employee commute carbon offset program", 0);
1593 doc.add_numbered_list_item("Achieve ISO 14001 certification for Singapore facility", 0);
1594
1595 doc.add_paragraph("Investment Targets").style("Heading2");
1596 doc.add_paragraph("Sustainability CapEx allocation for FY2026:")
1597 .keep_with_next(true);
1598 {
1599 let mut tbl = doc
1600 .add_table(5, 2)
1601 .borders(BorderStyle::Single, 4, "2D5016");
1602 tbl.cell(0, 0).unwrap().set_text("Initiative");
1603 tbl.cell(0, 0).unwrap().shading("2D5016");
1604 tbl.cell(0, 1).unwrap().set_text("Budget");
1605 tbl.cell(0, 1).unwrap().shading("2D5016");
1606 let rows = [
1607 ("Battery Storage", "$1.2M"),
1608 ("Immersion Cooling Pilot", "$800K"),
1609 ("Solar Panel Expansion", "$2.1M"),
1610 ("Carbon Credits", "$500K"),
1611 ];
1612 for (i, (init, budget)) in rows.iter().enumerate() {
1613 tbl.cell(i + 1, 0).unwrap().set_text(init);
1614 tbl.cell(i + 1, 1).unwrap().set_text(budget);
1615 if i % 2 == 0 {
1616 tbl.cell(i + 1, 0).unwrap().shading("FFFAF0");
1617 tbl.cell(i + 1, 1).unwrap().shading("FFFAF0");
1618 }
1619 }
1620 }
1621
1622 // Image: sustainability roadmap
1623 let roadmap_img = create_sample_png(500, 100, [40, 80, 30]);
1624 doc.add_paragraph("");
1625 doc.add_picture(
1626 &roadmap_img,
1627 "roadmap.png",
1628 Length::inches(6.0),
1629 Length::inches(1.2),
1630 )
1631 .alignment(Alignment::Center);
1632 {
1633 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1634 p.add_run("Figure 2: 2026 Sustainability Roadmap")
1635 .italic(true)
1636 .size(9.0)
1637 .color("666666");
1638 }
1639
1640 doc.add_paragraph("");
1641 doc.add_paragraph(
1642 "For questions about this report, contact Walter White at sustainability@tensorbee.com.",
1643 )
1644 .shading("FFFAF0")
1645 .border_all(BorderStyle::Single, 2, "2D5016");
1646
1647 doc
1648}
1649
1650// =============================================================================
1651// 6. LETTER — Slate blue + silver scheme
1652// =============================================================================
1653fn generate_letter(_samples_dir: &Path) -> Document {
1654 let mut doc = Document::new();
1655
1656 // Colors: Slate #4A5568, Blue accent #3182CE
1657 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1658 doc.set_margins(
1659 Length::inches(1.25),
1660 Length::inches(1.25),
1661 Length::inches(1.25),
1662 Length::inches(1.25),
1663 );
1664 doc.set_title("Business Letter");
1665 doc.set_author("Walter White");
1666
1667 // Banner header using raw XML
1668 let logo_img = create_logo_png(220, 48);
1669 let banner = build_header_banner_xml(
1670 "rId1",
1671 &BannerOpts {
1672 bg_color: "4A5568",
1673 banner_width: 7772400,
1674 banner_height: 731520, // ~0.8"
1675 logo_width: 2011680,
1676 logo_height: 438912,
1677 logo_x_offset: 295125,
1678 logo_y_offset: 146304,
1679 },
1680 );
1681 doc.set_raw_header_with_images(
1682 banner,
1683 &[("rId1", &logo_img, "logo.png")],
1684 rdocx_oxml::header_footer::HdrFtrType::Default,
1685 );
1686 doc.set_margins(
1687 Length::twips(2000),
1688 Length::twips(1800),
1689 Length::twips(1440),
1690 Length::twips(1800),
1691 );
1692 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
1693
1694 // Footer with contact
1695 doc.set_footer(
1696 "Tensorbee | 123 Innovation Drive, Suite 400 | San Francisco, CA 94105 | tensorbee.com",
1697 );
1698
1699 // ── Sender Address ──
1700 doc.add_paragraph("Walter White");
1701 doc.add_paragraph("Chief Executive Officer");
1702 doc.add_paragraph("Tensorbee");
1703 doc.add_paragraph("123 Innovation Drive, Suite 400");
1704 doc.add_paragraph("San Francisco, CA 94105");
1705 doc.add_paragraph("");
1706 doc.add_paragraph("February 22, 2026");
1707
1708 doc.add_paragraph("");
1709
1710 // ── Recipient ──
1711 doc.add_paragraph("Ms. Sarah Chen");
1712 doc.add_paragraph("VP of Engineering");
1713 doc.add_paragraph("Meridian Dynamics LLC");
1714 doc.add_paragraph("456 Enterprise Boulevard");
1715 doc.add_paragraph("Austin, TX 78701");
1716
1717 doc.add_paragraph("");
1718
1719 // ── Subject ──
1720 {
1721 let mut p = doc.add_paragraph("");
1722 p.add_run("Re: Strategic Technology Partnership — Phase 2 Expansion")
1723 .bold(true);
1724 }
1725
1726 doc.add_paragraph("");
1727
1728 // ── Body ──
1729 doc.add_paragraph("Dear Ms. Chen,");
1730 doc.add_paragraph("");
1731
1732 doc.add_paragraph(
1733 "I am writing to express Tensorbee's enthusiasm for expanding our strategic technology \
1734 partnership with Meridian Dynamics. Following the successful completion of Phase 1, \
1735 which delivered a 40% improvement in your ML inference pipeline throughput, we believe \
1736 the foundation is firmly established for an ambitious Phase 2 engagement.",
1737 )
1738 .first_line_indent(Length::inches(0.5));
1739
1740 doc.add_paragraph(
1741 "During our recent executive review, your team highlighted three priority areas for \
1742 the next phase of collaboration:",
1743 )
1744 .first_line_indent(Length::inches(0.5));
1745
1746 doc.add_numbered_list_item(
1747 "Real-time anomaly detection for your financial transaction monitoring system, \
1748 targeting sub-100ms latency at 50,000 transactions per second.",
1749 0,
1750 );
1751 doc.add_numbered_list_item(
1752 "Federated learning infrastructure to enable model training across your distributed \
1753 data centers without centralizing sensitive financial data.",
1754 0,
1755 );
1756 doc.add_numbered_list_item(
1757 "MLOps automation to reduce your model deployment cycle from the current 5 days \
1758 to under 4 hours.",
1759 0,
1760 );
1761
1762 doc.add_paragraph(
1763 "Our engineering team has prepared a detailed technical proposal addressing each of \
1764 these areas. We have allocated a dedicated team of eight senior engineers, led by \
1765 our CTO, to ensure continuity with the Phase 1 team your organization has already \
1766 built a productive working relationship with.",
1767 )
1768 .first_line_indent(Length::inches(0.5));
1769
1770 doc.add_paragraph(
1771 "I would welcome the opportunity to present our Phase 2 proposal in person. My \
1772 assistant will reach out to coordinate a meeting at your convenience during the \
1773 first week of March.",
1774 )
1775 .first_line_indent(Length::inches(0.5));
1776
1777 doc.add_paragraph("");
1778
1779 doc.add_paragraph("Warm regards,");
1780 doc.add_paragraph("");
1781 doc.add_paragraph("");
1782
1783 // Signature line
1784 doc.add_paragraph("")
1785 .border_bottom(BorderStyle::Single, 4, "4A5568");
1786 {
1787 let mut p = doc.add_paragraph("");
1788 p.add_run("Walter White").bold(true).size(12.0);
1789 }
1790 {
1791 let mut p = doc.add_paragraph("");
1792 p.add_run("Chief Executive Officer, Tensorbee")
1793 .italic(true)
1794 .size(10.0)
1795 .color("666666");
1796 }
1797 {
1798 let mut p = doc.add_paragraph("");
1799 p.add_run("walter@tensorbee.com | +1 (415) 555-0199")
1800 .size(10.0)
1801 .color("888888");
1802 }
1803
1804 doc
1805}
1806
1807// =============================================================================
1808// 7. EMPLOYMENT CONTRACT — Purple + charcoal scheme
1809// =============================================================================
1810fn generate_contract(_samples_dir: &Path) -> Document {
1811 let mut doc = Document::new();
1812
1813 // Colors: Purple #5B2C6F, Plum #8E44AD, Gray #2C3E50
1814 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1815 doc.set_margins(
1816 Length::inches(1.25),
1817 Length::inches(1.0),
1818 Length::inches(1.0),
1819 Length::inches(1.0),
1820 );
1821 doc.set_title("Employment Agreement");
1822 doc.set_author("Tensorbee HR Department");
1823 doc.set_subject("Employment Contract — Walter White");
1824 doc.set_keywords("employment, contract, agreement, Tensorbee");
1825
1826 doc.set_header("TENSORBEE — Employment Agreement");
1827 doc.set_footer("Employment Agreement — Walter White — Page");
1828
1829 // ── Title Block ──
1830 doc.add_paragraph("")
1831 .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1832 {
1833 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1834 p.add_run("EMPLOYMENT AGREEMENT")
1835 .bold(true)
1836 .size(24.0)
1837 .color("5B2C6F")
1838 .font("Georgia");
1839 }
1840 doc.add_paragraph("")
1841 .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1842 doc.add_paragraph("");
1843
1844 // ── Parties ──
1845 doc.add_paragraph(
1846 "This Employment Agreement (\"Agreement\") is entered into as of February 22, 2026 \
1847 (\"Effective Date\"), by and between:",
1848 );
1849
1850 doc.add_paragraph("");
1851
1852 {
1853 let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1854 p.add_run("EMPLOYER: ").bold(true);
1855 p.add_run(
1856 "Tensorbee, Inc., a Delaware corporation with its principal place of business at \
1857 123 Innovation Drive, Suite 400, San Francisco, CA 94105 (\"Company\")",
1858 );
1859 }
1860 doc.add_paragraph("");
1861 {
1862 let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1863 p.add_run("EMPLOYEE: ").bold(true);
1864 p.add_run(
1865 "Walter White, residing at 308 Negra Arroyo Lane, Albuquerque, NM 87104 (\"Employee\")",
1866 );
1867 }
1868
1869 doc.add_paragraph("");
1870 doc.add_paragraph("The Company and Employee are collectively referred to as the \"Parties.\"");
1871 doc.add_paragraph("");
1872
1873 // ── Article 1: Position and Duties ──
1874 doc.add_paragraph("Article 1 — Position and Duties")
1875 .style("Heading1");
1876 {
1877 let mut p = doc.add_paragraph("");
1878 p.add_run("1.1 ").bold(true);
1879 p.add_run("The Company hereby employs the Employee as ");
1880 p.add_run("Chief Executive Officer (CEO)")
1881 .bold(true)
1882 .italic(true);
1883 p.add_run(", reporting directly to the Board of Directors.");
1884 }
1885 {
1886 let mut p = doc.add_paragraph("");
1887 p.add_run("1.2 ").bold(true);
1888 p.add_run("The Employee shall devote full working time, attention, and best efforts to \
1889 the performance of duties as reasonably assigned by the Board, including but not limited to:");
1890 }
1891 doc.add_bullet_list_item(
1892 "Setting and executing the Company's strategic vision and business plan",
1893 0,
1894 );
1895 doc.add_bullet_list_item(
1896 "Overseeing all operations, engineering, and commercial activities",
1897 0,
1898 );
1899 doc.add_bullet_list_item(
1900 "Representing the Company to investors, customers, and the public",
1901 0,
1902 );
1903 doc.add_bullet_list_item("Recruiting, developing, and retaining key talent", 0);
1904
1905 {
1906 let mut p = doc.add_paragraph("");
1907 p.add_run("1.3 ").bold(true);
1908 p.add_run(
1909 "The Employee's primary work location shall be the Company's San Francisco \
1910 headquarters, with reasonable travel as required by business needs.",
1911 );
1912 }
1913
1914 // ── Article 2: Compensation ──
1915 doc.add_paragraph("Article 2 — Compensation and Benefits")
1916 .style("Heading1");
1917 {
1918 let mut p = doc.add_paragraph("");
1919 p.add_run("2.1 Base Salary. ").bold(true);
1920 p.add_run("The Company shall pay the Employee an annual base salary of ");
1921 p.add_run("$375,000.00").bold(true);
1922 p.add_run(" (Three Hundred Seventy-Five Thousand Dollars), payable in accordance with the \
1923 Company's standard payroll schedule, less applicable withholdings and deductions.");
1924 }
1925 {
1926 let mut p = doc.add_paragraph("");
1927 p.add_run("2.2 Annual Bonus. ").bold(true);
1928 p.add_run("The Employee shall be eligible for an annual performance bonus of up to ");
1929 p.add_run("40%").bold(true);
1930 p.add_run(
1931 " of base salary, based on achievement of mutually agreed performance objectives.",
1932 );
1933 }
1934 {
1935 let mut p = doc.add_paragraph("");
1936 p.add_run("2.3 Equity. ").bold(true);
1937 p.add_run("Subject to Board approval, the Employee shall receive a stock option grant of ");
1938 p.add_run("500,000 shares").bold(true);
1939 p.add_run(
1940 " of the Company's common stock, vesting over four (4) years with a one-year cliff, \
1941 at an exercise price equal to the fair market value on the date of grant.",
1942 );
1943 }
1944
1945 // Compensation summary table
1946 doc.add_paragraph("");
1947 {
1948 let mut tbl = doc
1949 .add_table(5, 2)
1950 .borders(BorderStyle::Single, 4, "5B2C6F")
1951 .width_pct(70.0)
1952 .alignment(Alignment::Center);
1953 tbl.cell(0, 0).unwrap().set_text("Compensation Element");
1954 tbl.cell(0, 0).unwrap().shading("5B2C6F");
1955 tbl.cell(0, 1).unwrap().set_text("Value");
1956 tbl.cell(0, 1).unwrap().shading("5B2C6F");
1957 let items = [
1958 ("Base Salary", "$375,000/year"),
1959 ("Target Bonus", "Up to 40% ($150,000)"),
1960 ("Equity Grant", "500,000 shares (4yr vest)"),
1961 ("Total Target Comp", "$525,000 + equity"),
1962 ];
1963 for (i, (elem, val)) in items.iter().enumerate() {
1964 tbl.cell(i + 1, 0).unwrap().set_text(elem);
1965 tbl.cell(i + 1, 1).unwrap().set_text(val);
1966 if i == 3 {
1967 tbl.cell(i + 1, 0).unwrap().shading("E8DAEF");
1968 tbl.cell(i + 1, 1).unwrap().shading("E8DAEF");
1969 }
1970 }
1971 }
1972
1973 // ── Article 3: Benefits ──
1974 doc.add_paragraph("").page_break_before(true);
1975 doc.add_paragraph("Article 3 — Benefits").style("Heading1");
1976 {
1977 let mut p = doc.add_paragraph("");
1978 p.add_run("3.1 ").bold(true);
1979 p.add_run(
1980 "The Employee shall be entitled to participate in all benefit programs \
1981 generally available to senior executives, including:",
1982 );
1983 }
1984 doc.add_bullet_list_item(
1985 "Medical, dental, and vision insurance (100% premium coverage for employee and dependents)",
1986 0,
1987 );
1988 doc.add_bullet_list_item("401(k) retirement plan with 6% company match", 0);
1989 doc.add_bullet_list_item("Life insurance and long-term disability coverage", 0);
1990 doc.add_bullet_list_item("Annual professional development allowance of $10,000", 0);
1991
1992 {
1993 let mut p = doc.add_paragraph("");
1994 p.add_run("3.2 Paid Time Off. ").bold(true);
1995 p.add_run(
1996 "The Employee shall receive 25 days of paid vacation per year, plus Company holidays, \
1997 accruing on a monthly basis.",
1998 );
1999 }
2000
2001 // ── Article 4: Term and Termination ──
2002 doc.add_paragraph("Article 4 — Term and Termination")
2003 .style("Heading1");
2004 {
2005 let mut p = doc.add_paragraph("");
2006 p.add_run("4.1 At-Will Employment. ").bold(true);
2007 p.add_run(
2008 "This Agreement is for at-will employment and may be terminated by either Party \
2009 at any time, with or without cause, subject to the notice provisions herein.",
2010 );
2011 }
2012 {
2013 let mut p = doc.add_paragraph("");
2014 p.add_run("4.2 Notice Period. ").bold(true);
2015 p.add_run("Either Party shall provide ");
2016 p.add_run("ninety (90) days").bold(true);
2017 p.add_run(" written notice of termination, or pay in lieu thereof.");
2018 }
2019 {
2020 let mut p = doc.add_paragraph("");
2021 p.add_run("4.3 Severance. ").bold(true);
2022 p.add_run("In the event of termination by the Company without Cause, the Employee shall \
2023 receive (a) twelve (12) months of base salary continuation, (b) pro-rata bonus \
2024 for the year of termination, and (c) twelve (12) months of COBRA premium coverage.");
2025 }
2026
2027 // ── Article 5: Confidentiality ──
2028 doc.add_paragraph("Article 5 — Confidentiality and IP")
2029 .style("Heading1");
2030 {
2031 let mut p = doc.add_paragraph("");
2032 p.add_run("5.1 ").bold(true);
2033 p.add_run(
2034 "The Employee agrees to maintain strict confidentiality of all proprietary \
2035 information, trade secrets, and business plans of the Company during and after \
2036 employment.",
2037 );
2038 }
2039 {
2040 let mut p = doc.add_paragraph("");
2041 p.add_run("5.2 ").bold(true);
2042 p.add_run(
2043 "All intellectual property, inventions, and works of authorship created by the \
2044 Employee during the term of employment and within the scope of duties shall be \
2045 the sole and exclusive property of the Company.",
2046 );
2047 }
2048
2049 // ── Article 6: Non-Compete ──
2050 doc.add_paragraph("Article 6 — Non-Competition")
2051 .style("Heading1");
2052 {
2053 let mut p = doc.add_paragraph("");
2054 p.add_run("6.1 ").bold(true);
2055 p.add_run("For a period of twelve (12) months following termination, the Employee agrees \
2056 not to engage in any business that directly competes with the Company's core \
2057 business of AI infrastructure and ML operations services, within the United States.");
2058 }
2059
2060 // ── Article 7: General Provisions ──
2061 doc.add_paragraph("Article 7 — General Provisions")
2062 .style("Heading1");
2063 {
2064 let mut p = doc.add_paragraph("");
2065 p.add_run("7.1 Governing Law. ").bold(true);
2066 p.add_run(
2067 "This Agreement shall be governed by and construed in accordance with the laws of \
2068 the State of California.",
2069 );
2070 }
2071 {
2072 let mut p = doc.add_paragraph("");
2073 p.add_run("7.2 Entire Agreement. ").bold(true);
2074 p.add_run(
2075 "This Agreement constitutes the entire agreement between the Parties and supersedes \
2076 all prior negotiations, representations, and agreements.",
2077 );
2078 }
2079 {
2080 let mut p = doc.add_paragraph("");
2081 p.add_run("7.3 Amendment. ").bold(true);
2082 p.add_run(
2083 "This Agreement may only be amended by a written instrument signed by both Parties.",
2084 );
2085 }
2086
2087 // ── Signature Block ──
2088 doc.add_paragraph("").page_break_before(true);
2089 doc.add_paragraph("IN WITNESS WHEREOF, the Parties have executed this Employment Agreement as of the Effective Date.")
2090 .space_after(Length::pt(24.0));
2091
2092 // Signature table
2093 {
2094 let mut tbl = doc.add_table(6, 2).width_pct(100.0);
2095 tbl.cell(0, 0).unwrap().set_text("FOR THE COMPANY:");
2096 tbl.cell(0, 0).unwrap().shading("5B2C6F");
2097 tbl.cell(0, 1).unwrap().set_text("EMPLOYEE:");
2098 tbl.cell(0, 1).unwrap().shading("5B2C6F");
2099
2100 tbl.cell(1, 0).unwrap().set_text("");
2101 tbl.cell(1, 1).unwrap().set_text("");
2102 tbl.row(1).unwrap().height(Length::pt(40.0));
2103
2104 tbl.cell(2, 0)
2105 .unwrap()
2106 .set_text("Signature: ___________________________");
2107 tbl.cell(2, 1)
2108 .unwrap()
2109 .set_text("Signature: ___________________________");
2110 tbl.cell(3, 0)
2111 .unwrap()
2112 .set_text("Name: Board Representative");
2113 tbl.cell(3, 1).unwrap().set_text("Name: Walter White");
2114 tbl.cell(4, 0)
2115 .unwrap()
2116 .set_text("Title: Chair, Board of Directors");
2117 tbl.cell(4, 1)
2118 .unwrap()
2119 .set_text("Title: Chief Executive Officer");
2120 tbl.cell(5, 0).unwrap().set_text("Date: _______________");
2121 tbl.cell(5, 1).unwrap().set_text("Date: _______________");
2122 }
2123
2124 doc
2125}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
34fn generate_feature_showcase(path: &Path) {
35 let mut doc = Document::new();
36
37 // =========================================================================
38 // PAGE SETUP & METADATA
39 // =========================================================================
40 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
41 doc.set_margins(
42 Length::inches(1.0), // top
43 Length::inches(1.0), // right
44 Length::inches(1.0), // bottom
45 Length::inches(1.0), // left
46 );
47 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
48 doc.set_gutter(Length::twips(0));
49
50 doc.set_title("rdocx Feature Showcase");
51 doc.set_author("rdocx Sample Generator");
52 doc.set_subject("Comprehensive feature demonstration");
53 doc.set_keywords("rdocx, docx, rust, sample");
54
55 // Header & Footer
56 doc.set_header("rdocx Feature Showcase");
57 doc.set_footer("Generated by rdocx — Page");
58
59 // Different first page header
60 doc.set_different_first_page(true);
61 doc.set_first_page_header("rdocx");
62 doc.set_first_page_footer("Feature Showcase — Cover Page");
63
64 // =========================================================================
65 // PAGE 1: COVER PAGE — background image, run formatting
66 // =========================================================================
67 let bg_cover = create_sample_png(612, 792, [30, 60, 120]);
68 doc.add_background_image(&bg_cover, "cover_bg.png");
69
70 doc.add_paragraph(""); // spacer
71 doc.add_paragraph(""); // spacer
72 doc.add_paragraph(""); // spacer
73
74 {
75 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
76 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
77 }
78 {
79 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
80 p.add_run("Feature Showcase")
81 .size(28.0)
82 .color("FFFFFF")
83 .italic(true);
84 }
85
86 doc.add_paragraph(""); // spacer
87
88 {
89 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
90 p.add_run("A comprehensive demonstration of every feature")
91 .size(14.0)
92 .color("CCDDFF");
93 }
94 {
95 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
96 p.add_run("provided by the rdocx Rust crate for DOCX generation.")
97 .size(14.0)
98 .color("CCDDFF");
99 }
100
101 // =========================================================================
102 // PAGE 2: TEXT FORMATTING
103 // =========================================================================
104 doc.add_paragraph("").page_break_before(true);
105
106 doc.add_paragraph("1. Text Formatting").style("Heading1");
107
108 doc.add_paragraph("This section demonstrates paragraph and run-level formatting options.");
109 doc.add_paragraph("");
110
111 // --- Paragraph alignment ---
112 doc.add_paragraph("Paragraph Alignment").style("Heading2");
113
114 doc.add_paragraph("This paragraph is left-aligned (the default).")
115 .alignment(Alignment::Left);
116 doc.add_paragraph("This paragraph is center-aligned.")
117 .alignment(Alignment::Center);
118 doc.add_paragraph("This paragraph is right-aligned.")
119 .alignment(Alignment::Right);
120 doc.add_paragraph(
121 "This paragraph is justified. To demonstrate justified text properly, it needs \
122 to be long enough to span multiple lines so the word spacing adjustment is visible \
123 across the full width of the text area on the page.",
124 )
125 .alignment(Alignment::Justify);
126
127 doc.add_paragraph("");
128
129 // --- Run formatting ---
130 doc.add_paragraph("Run Formatting").style("Heading2");
131
132 {
133 let mut p = doc.add_paragraph("");
134 p.add_run("Bold text").bold(true);
135 p.add_run(" | ");
136 p.add_run("Italic text").italic(true);
137 p.add_run(" | ");
138 p.add_run("Bold + Italic").bold(true).italic(true);
139 }
140 {
141 let mut p = doc.add_paragraph("");
142 p.add_run("Single underline").underline(true);
143 p.add_run(" | ");
144 p.add_run("Strikethrough").strike(true);
145 p.add_run(" | ");
146 p.add_run("Double strikethrough").double_strike(true);
147 }
148 {
149 let mut p = doc.add_paragraph("");
150 p.add_run("Red text").color("FF0000");
151 p.add_run(" | ");
152 p.add_run("Blue text").color("0000FF");
153 p.add_run(" | ");
154 p.add_run("Green text").color("00AA00");
155 p.add_run(" | ");
156 p.add_run("Highlighted").highlight("FFFF00");
157 }
158 {
159 let mut p = doc.add_paragraph("");
160 p.add_run("8pt small").size(8.0);
161 p.add_run(" | ");
162 p.add_run("11pt normal").size(11.0);
163 p.add_run(" | ");
164 p.add_run("16pt large").size(16.0);
165 p.add_run(" | ");
166 p.add_run("24pt extra-large").size(24.0);
167 }
168 {
169 let mut p = doc.add_paragraph("");
170 p.add_run("Arial font").font("Arial");
171 p.add_run(" | ");
172 p.add_run("Times New Roman font").font("Times New Roman");
173 p.add_run(" | ");
174 p.add_run("Courier New font").font("Courier New");
175 }
176 {
177 let mut p = doc.add_paragraph("");
178 p.add_run("Normal");
179 p.add_run(" H").size(11.0);
180 p.add_run("2").subscript();
181 p.add_run("O (subscript)").size(11.0);
182 p.add_run(" | E = mc").size(11.0);
183 p.add_run("2").superscript();
184 p.add_run(" (superscript)").size(11.0);
185 }
186 {
187 let mut p = doc.add_paragraph("");
188 p.add_run("ALL CAPS").all_caps(true);
189 p.add_run(" | ");
190 p.add_run("Small Caps").small_caps(true);
191 p.add_run(" | ");
192 p.add_run("Expanded spacing")
193 .character_spacing(Length::twips(40));
194 }
195
196 doc.add_paragraph("");
197
198 // --- Paragraph formatting ---
199 doc.add_paragraph("Paragraph Formatting").style("Heading2");
200
201 doc.add_paragraph("Paragraph with shading (light green background)")
202 .shading("E2EFDA");
203
204 doc.add_paragraph("Paragraph with bottom border")
205 .border_bottom(BorderStyle::Single, 6, "2E75B6");
206
207 doc.add_paragraph("Paragraph with all borders")
208 .border_all(BorderStyle::Single, 4, "FF0000");
209
210 doc.add_paragraph("Paragraph with 1-inch left indent and hanging indent")
211 .indent_left(Length::inches(1.0))
212 .hanging_indent(Length::inches(0.5));
213
214 doc.add_paragraph("Paragraph with first-line indent of 0.5 inches")
215 .first_line_indent(Length::inches(0.5));
216
217 doc.add_paragraph("Paragraph with extra space before (24pt) and after (12pt)")
218 .space_before(Length::pt(24.0))
219 .space_after(Length::pt(12.0));
220
221 doc.add_paragraph(
222 "Paragraph with double line spacing. This text should have extra vertical \
223 space between lines to demonstrate the line_spacing_multiple setting.",
224 )
225 .line_spacing_multiple(2.0);
226
227 doc.add_paragraph("Paragraph with keep-with-next (won't break from the next paragraph)")
228 .keep_with_next(true);
229 doc.add_paragraph("(This stays with the paragraph above.)");
230
231 // =========================================================================
232 // PAGE 3: LISTS & TAB STOPS
233 // =========================================================================
234 doc.add_paragraph("").page_break_before(true);
235
236 doc.add_paragraph("2. Lists").style("Heading1");
237
238 doc.add_paragraph("Bullet List").style("Heading2");
239
240 doc.add_bullet_list_item("First bullet item", 0);
241 doc.add_bullet_list_item("Second bullet item", 0);
242 doc.add_bullet_list_item("Nested level 1", 1);
243 doc.add_bullet_list_item("Nested level 2", 2);
244 doc.add_bullet_list_item("Back to level 1", 1);
245 doc.add_bullet_list_item("Third bullet item", 0);
246
247 doc.add_paragraph("");
248
249 doc.add_paragraph("Numbered List").style("Heading2");
250
251 doc.add_numbered_list_item("First numbered item", 0);
252 doc.add_numbered_list_item("Second numbered item", 0);
253 doc.add_numbered_list_item("Sub-item A", 1);
254 doc.add_numbered_list_item("Sub-item B", 1);
255 doc.add_numbered_list_item("Third numbered item", 0);
256
257 doc.add_paragraph("");
258
259 // --- Tab stops ---
260 doc.add_paragraph("Tab Stops").style("Heading2");
261
262 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
263 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
264 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
265 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
266 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
267
268 doc.add_paragraph("Item\t........\tPrice")
269 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
270 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
271 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
272
273 doc.add_paragraph("Widget A\t........\t$19.99")
274 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
275 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
276 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
277
278 doc.add_paragraph("Gadget B\t________\t$249.50")
279 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
280 .add_tab_stop_with_leader(
281 TabAlignment::Right,
282 Length::inches(4.0),
283 TabLeader::Underscore,
284 )
285 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
286
287 // =========================================================================
288 // PAGE 4: TABLES
289 // =========================================================================
290 doc.add_paragraph("").page_break_before(true);
291
292 doc.add_paragraph("3. Tables").style("Heading1");
293
294 // --- Basic table with borders ---
295 doc.add_paragraph("Basic Table with Borders")
296 .style("Heading2");
297
298 {
299 let mut tbl = doc.add_table(4, 3);
300 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
301
302 // Header row
303 for col in 0..3 {
304 tbl.cell(0, col).unwrap().shading("2E75B6");
305 }
306 tbl.cell(0, 0).unwrap().set_text("Name");
307 tbl.cell(0, 1).unwrap().set_text("Role");
308 tbl.cell(0, 2).unwrap().set_text("Location");
309
310 tbl.cell(1, 0).unwrap().set_text("Alice Johnson");
311 tbl.cell(1, 1).unwrap().set_text("Engineering Lead");
312 tbl.cell(1, 2).unwrap().set_text("New York");
313
314 tbl.cell(2, 0).unwrap().set_text("Bob Smith");
315 tbl.cell(2, 1).unwrap().set_text("Product Manager");
316 tbl.cell(2, 2).unwrap().set_text("San Francisco");
317
318 tbl.cell(3, 0).unwrap().set_text("Carol Davis");
319 tbl.cell(3, 1).unwrap().set_text("Designer");
320 tbl.cell(3, 2).unwrap().set_text("London");
321 }
322
323 doc.add_paragraph("");
324
325 // --- Table with cell merging ---
326 doc.add_paragraph("Table with Cell Merging & Vertical Alignment")
327 .style("Heading2");
328
329 {
330 let mut tbl = doc.add_table(4, 4);
331 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
332 tbl = tbl.width_pct(100.0);
333
334 // Header spanning all columns
335 tbl.cell(0, 0).unwrap().set_text("Quarterly Revenue Report");
336 tbl.cell(0, 0).unwrap().shading("1F4E79");
337 tbl.cell(0, 0).unwrap().grid_span(4);
338
339 // Sub-header
340 tbl.cell(1, 0).unwrap().set_text("Region");
341 tbl.cell(1, 0).unwrap().shading("D6E4F0");
342 tbl.cell(1, 1).unwrap().set_text("Q1");
343 tbl.cell(1, 1).unwrap().shading("D6E4F0");
344 tbl.cell(1, 2).unwrap().set_text("Q2");
345 tbl.cell(1, 2).unwrap().shading("D6E4F0");
346 tbl.cell(1, 3).unwrap().set_text("Total");
347 tbl.cell(1, 3).unwrap().shading("D6E4F0");
348
349 // Data
350 tbl.cell(2, 0).unwrap().set_text("North America");
351 tbl.cell(2, 1).unwrap().set_text("$2.4M");
352 tbl.cell(2, 2).unwrap().set_text("$2.7M");
353 tbl.cell(2, 3).unwrap().set_text("$5.1M");
354
355 tbl.cell(3, 0).unwrap().set_text("Europe");
356 tbl.cell(3, 1).unwrap().set_text("$1.8M");
357 tbl.cell(3, 2).unwrap().set_text("$2.0M");
358 tbl.cell(3, 3).unwrap().set_text("$3.8M");
359
360 // Vertical alignment on data cells
361 tbl.cell(2, 3)
362 .unwrap()
363 .vertical_alignment(VerticalAlignment::Center);
364 tbl.cell(3, 3)
365 .unwrap()
366 .vertical_alignment(VerticalAlignment::Bottom);
367 }
368
369 doc.add_paragraph("");
370
371 // --- Table with vertical merge ---
372 doc.add_paragraph("Table with Vertical Merge")
373 .style("Heading2");
374
375 {
376 let mut tbl = doc.add_table(4, 3);
377 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
378
379 tbl.cell(0, 0).unwrap().set_text("Category");
380 tbl.cell(0, 0).unwrap().shading("E2EFDA");
381 tbl.cell(0, 1).unwrap().set_text("Item");
382 tbl.cell(0, 1).unwrap().shading("E2EFDA");
383 tbl.cell(0, 2).unwrap().set_text("Price");
384 tbl.cell(0, 2).unwrap().shading("E2EFDA");
385
386 // "Hardware" spans rows 1-2
387 tbl.cell(1, 0).unwrap().set_text("Hardware");
388 tbl.cell(1, 0).unwrap().v_merge_restart();
389 tbl.cell(1, 1).unwrap().set_text("Laptop");
390 tbl.cell(1, 2).unwrap().set_text("$1,200");
391
392 tbl.cell(2, 0).unwrap().v_merge_continue();
393 tbl.cell(2, 1).unwrap().set_text("Monitor");
394 tbl.cell(2, 2).unwrap().set_text("$450");
395
396 // "Software" on row 3
397 tbl.cell(3, 0).unwrap().set_text("Software");
398 tbl.cell(3, 1).unwrap().set_text("IDE License");
399 tbl.cell(3, 2).unwrap().set_text("$200/yr");
400 }
401
402 doc.add_paragraph("");
403
404 // --- Nested table ---
405 doc.add_paragraph("Nested Table").style("Heading2");
406
407 {
408 let mut tbl = doc.add_table(2, 2);
409 tbl = tbl.borders(BorderStyle::Single, 6, "2E75B6");
410
411 tbl.cell(0, 0).unwrap().set_text("Outer Cell (0,0)");
412 tbl.cell(0, 1).unwrap().set_text("Outer Cell (0,1)");
413 tbl.cell(1, 0).unwrap().set_text("Outer Cell (1,0)");
414
415 // Nested table inside cell (1,1)
416 {
417 let mut cell = tbl.cell(1, 1).unwrap();
418 cell.set_text("Contains nested table:");
419 let mut nested = cell.add_table(2, 2);
420 nested = nested.borders(BorderStyle::Single, 2, "FF6600");
421 nested.cell(0, 0).unwrap().set_text("Inner A");
422 nested.cell(0, 1).unwrap().set_text("Inner B");
423 nested.cell(1, 0).unwrap().set_text("Inner C");
424 nested.cell(1, 1).unwrap().set_text("Inner D");
425 }
426 }
427
428 // =========================================================================
429 // PAGE 5: IMAGES
430 // =========================================================================
431 doc.add_paragraph("").page_break_before(true);
432
433 doc.add_paragraph("4. Images").style("Heading1");
434
435 doc.add_paragraph("Inline Image").style("Heading2");
436
437 doc.add_paragraph("Below is an inline image (200x50 pixels, blue gradient):");
438 let inline_img = create_sample_png(200, 50, [0, 80, 200]);
439 doc.add_picture(
440 &inline_img,
441 "inline_chart.png",
442 Length::inches(3.0),
443 Length::inches(0.75),
444 );
445
446 doc.add_paragraph("");
447
448 doc.add_paragraph("Header Image").style("Heading2");
449
450 // Replace the text-only header with an image header
451 let header_img = create_sample_png(400, 40, [40, 40, 40]);
452 doc.set_header_image(
453 &header_img,
454 "header_logo.png",
455 Length::inches(2.0),
456 Length::inches(0.2),
457 );
458
459 doc.add_paragraph(
460 "The document header has been replaced with an inline image. \
461 Check the header area at the top of this page.",
462 );
463
464 doc.add_paragraph("");
465 doc.add_paragraph(
466 "Note: The cover page uses a full-page background image behind the text, \
467 demonstrated on page 1 via add_background_image().",
468 );
469
470 // =========================================================================
471 // PAGE 6: CONTENT MANIPULATION — placeholder replacement, insertion
472 // =========================================================================
473 doc.add_paragraph("").page_break_before(true);
474
475 doc.add_paragraph("5. Content Manipulation")
476 .style("Heading1");
477
478 // --- Placeholder replacement ---
479 doc.add_paragraph("Placeholder Replacement")
480 .style("Heading2");
481
482 doc.add_paragraph(
483 "Before replacement, this document contained {{customer}} and {{date}} placeholders.",
484 );
485
486 {
487 let mut p = doc.add_paragraph("");
488 p.add_run("Customer: ").bold(true);
489 p.add_run("{{customer}}");
490 }
491 {
492 let mut p = doc.add_paragraph("");
493 p.add_run("Date: ").bold(true);
494 p.add_run("{{date}}");
495 }
496 doc.add_paragraph("Reference: {{ref_number}}");
497
498 // Table with placeholders
499 {
500 let mut tbl = doc.add_table(3, 2);
501 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
502 tbl.cell(0, 0).unwrap().set_text("Field");
503 tbl.cell(0, 0).unwrap().shading("D6E4F0");
504 tbl.cell(0, 1).unwrap().set_text("Value");
505 tbl.cell(0, 1).unwrap().shading("D6E4F0");
506 tbl.cell(1, 0).unwrap().set_text("Project");
507 tbl.cell(1, 1).unwrap().set_text("{{project}}");
508 tbl.cell(2, 0).unwrap().set_text("Status");
509 tbl.cell(2, 1).unwrap().set_text("{{status}}");
510 }
511
512 // Perform replacements
513 let mut replacements = HashMap::new();
514 replacements.insert("{{customer}}", "Acme Corporation");
515 replacements.insert("{{date}}", "February 22, 2026");
516 replacements.insert("{{ref_number}}", "REF-2026-001");
517 replacements.insert("{{project}}", "Infrastructure Upgrade");
518 replacements.insert("{{status}}", "In Progress");
519 let replace_count = doc.replace_all(&replacements);
520
521 doc.add_paragraph("");
522 doc.add_paragraph(&format!(
523 "(Replaced {} placeholders above — in body text and table cells)",
524 replace_count
525 ));
526
527 doc.add_paragraph("");
528
529 // --- Content insertion ---
530 doc.add_paragraph("Content Insertion").style("Heading2");
531
532 doc.add_paragraph("Section A: First section of content.");
533 doc.add_paragraph("Section C: Third section of content.");
534
535 // Insert "Section B" between A and C
536 if let Some(idx) = doc.find_content_index("Section C") {
537 doc.insert_paragraph(
538 idx,
539 "Section B: Inserted between A and C using find_content_index().",
540 );
541 }
542
543 doc.add_paragraph("");
544 doc.add_paragraph(
545 "The paragraph above ('Section B') was inserted at a specific position \
546 using find_content_index() + insert_paragraph().",
547 );
548
549 // =========================================================================
550 // PAGE 7: LANDSCAPE — section break, wide table
551 // =========================================================================
552 doc.add_paragraph("").section_break(SectionBreak::NextPage);
553
554 doc.add_paragraph("6. Mixed Page Orientation")
555 .style("Heading1");
556
557 doc.add_paragraph(
558 "This page is in LANDSCAPE orientation. It was created using a section break \
559 followed by section_landscape(). This is useful for wide tables or charts.",
560 );
561
562 doc.add_paragraph("");
563
564 // Wide table for landscape
565 {
566 let mut tbl = doc.add_table(4, 7);
567 tbl = tbl.borders(BorderStyle::Single, 4, "2E75B6");
568
569 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
570 for (col, h) in headers.iter().enumerate() {
571 tbl.cell(0, col).unwrap().set_text(h);
572 tbl.cell(0, col).unwrap().shading("2E75B6");
573 }
574
575 let data = [
576 [
577 "North America",
578 "$1.2M",
579 "$1.3M",
580 "$1.4M",
581 "$1.5M",
582 "$1.6M",
583 "$7.0M",
584 ],
585 [
586 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
587 ],
588 [
589 "Asia Pacific",
590 "$0.5M",
591 "$0.6M",
592 "$0.7M",
593 "$0.7M",
594 "$0.8M",
595 "$3.3M",
596 ],
597 ];
598 for (row_idx, row_data) in data.iter().enumerate() {
599 for (col, val) in row_data.iter().enumerate() {
600 tbl.cell(row_idx + 1, col).unwrap().set_text(val);
601 }
602 }
603 }
604
605 // End landscape, return to portrait
606 doc.add_paragraph("")
607 .section_break(SectionBreak::NextPage)
608 .section_landscape();
609
610 // =========================================================================
611 // PAGE 8: BACK TO PORTRAIT — styles, final notes
612 // =========================================================================
613 doc.add_paragraph("7. Custom Styles & Summary")
614 .style("Heading1");
615
616 doc.add_paragraph(
617 "This final page is back in portrait orientation after a section break. \
618 The document has demonstrated:",
619 );
620
621 doc.add_bullet_list_item(
622 "Page setup: size, margins, header/footer distance, gutter",
623 0,
624 );
625 doc.add_bullet_list_item("Document metadata: title, author, subject, keywords", 0);
626 doc.add_bullet_list_item("Headers and footers: text, images, different first page", 0);
627 doc.add_bullet_list_item("Background images: full-page behind text", 0);
628 doc.add_bullet_list_item(
629 "Text formatting: bold, italic, underline, strike, color, size, font",
630 0,
631 );
632 doc.add_bullet_list_item(
633 "Advanced run formatting: superscript, subscript, caps, spacing",
634 0,
635 );
636 doc.add_bullet_list_item(
637 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
638 0,
639 );
640 doc.add_bullet_list_item("Bullet and numbered lists with nesting levels", 0);
641 doc.add_bullet_list_item("Tab stops with dot/underscore leaders", 0);
642 doc.add_bullet_list_item(
643 "Tables: borders, shading, column spans, row spans, nesting",
644 0,
645 );
646 doc.add_bullet_list_item("Vertical alignment in table cells", 0);
647 doc.add_bullet_list_item("Inline images", 0);
648 doc.add_bullet_list_item("Placeholder replacement in body and table cells", 0);
649 doc.add_bullet_list_item("Content insertion at specific positions", 0);
650 doc.add_bullet_list_item(
651 "Section breaks with mixed portrait/landscape orientation",
652 0,
653 );
654
655 doc.add_paragraph("");
656 doc.add_paragraph("All features above were built entirely from scratch using the rdocx API.")
657 .alignment(Alignment::Center)
658 .shading("E2EFDA")
659 .border_all(BorderStyle::Single, 2, "00AA00");
660
661 doc.save(path).unwrap();
662}86fn generate_feature_showcase(_samples_dir: &Path) -> Document {
87 let mut doc = Document::new();
88
89 // ── Page Setup & Metadata ──
90 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
91 doc.set_margins(
92 Length::inches(1.0),
93 Length::inches(1.0),
94 Length::inches(1.0),
95 Length::inches(1.0),
96 );
97 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
98 doc.set_gutter(Length::twips(0));
99 doc.set_title("rdocx Feature Showcase");
100 doc.set_author("rdocx Sample Generator");
101 doc.set_subject("Comprehensive feature demonstration");
102 doc.set_keywords("rdocx, docx, rust, sample, showcase");
103
104 // Headers & Footers with different first page
105 doc.set_different_first_page(true);
106 doc.set_first_page_header("rdocx");
107 doc.set_first_page_footer("Feature Showcase — Cover Page");
108 doc.set_header("rdocx Feature Showcase");
109 doc.set_footer("Generated by rdocx");
110
111 // ── COVER PAGE ──
112 let bg = create_sample_png(612, 792, [20, 45, 90]);
113 doc.add_background_image(&bg, "cover_bg.png");
114
115 for _ in 0..3 {
116 doc.add_paragraph("");
117 }
118 {
119 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
120 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
121 }
122 {
123 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
124 p.add_run("Complete Feature Showcase")
125 .size(28.0)
126 .color("FFFFFF")
127 .italic(true);
128 }
129 doc.add_paragraph("");
130 {
131 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
132 p.add_run("Every feature of the rdocx Rust library")
133 .size(13.0)
134 .color("B0C4DE");
135 }
136 {
137 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
138 p.add_run("demonstrated in a single document.")
139 .size(13.0)
140 .color("B0C4DE");
141 }
142
143 // ── TABLE OF CONTENTS ──
144 doc.add_paragraph("").page_break_before(true);
145 doc.insert_toc(doc.content_count(), 3);
146
147 // ── SECTION 1: TEXT FORMATTING ──
148 doc.add_paragraph("").page_break_before(true);
149 doc.add_paragraph("1. Text Formatting").style("Heading1");
150
151 // Paragraph Alignment
152 doc.add_paragraph("1.1 Paragraph Alignment")
153 .style("Heading2");
154 doc.add_paragraph("Left-aligned paragraph (default).")
155 .alignment(Alignment::Left);
156 doc.add_paragraph("Center-aligned paragraph.")
157 .alignment(Alignment::Center);
158 doc.add_paragraph("Right-aligned paragraph.")
159 .alignment(Alignment::Right);
160 doc.add_paragraph(
161 "Justified paragraph — this text is long enough to demonstrate how justified alignment \
162 distributes extra space across word gaps so lines fill the full width of the text area.",
163 )
164 .alignment(Alignment::Justify);
165
166 doc.add_paragraph("");
167
168 // Run Formatting
169 doc.add_paragraph("1.2 Run Formatting").style("Heading2");
170 {
171 let mut p = doc.add_paragraph("");
172 p.add_run("Bold").bold(true);
173 p.add_run(" | ");
174 p.add_run("Italic").italic(true);
175 p.add_run(" | ");
176 p.add_run("Bold Italic").bold(true).italic(true);
177 p.add_run(" | ");
178 p.add_run("Underline").underline(true);
179 p.add_run(" | ");
180 p.add_run("Strikethrough").strike(true);
181 p.add_run(" | ");
182 p.add_run("Double Strike").double_strike(true);
183 }
184 {
185 let mut p = doc.add_paragraph("");
186 p.add_run("Red").color("FF0000");
187 p.add_run(" | ");
188 p.add_run("Blue").color("0000FF");
189 p.add_run(" | ");
190 p.add_run("Green").color("00AA00");
191 p.add_run(" | ");
192 p.add_run("Highlighted").highlight("FFFF00");
193 }
194 {
195 let mut p = doc.add_paragraph("");
196 p.add_run("8pt").size(8.0);
197 p.add_run(" | ");
198 p.add_run("11pt").size(11.0);
199 p.add_run(" | ");
200 p.add_run("16pt").size(16.0);
201 p.add_run(" | ");
202 p.add_run("24pt").size(24.0);
203 }
204 {
205 let mut p = doc.add_paragraph("");
206 p.add_run("Arial").font("Arial");
207 p.add_run(" | ");
208 p.add_run("Times New Roman").font("Times New Roman");
209 p.add_run(" | ");
210 p.add_run("Courier New").font("Courier New");
211 }
212 {
213 let mut p = doc.add_paragraph("");
214 p.add_run("H");
215 p.add_run("2").subscript();
216 p.add_run("O (subscript) | E=mc");
217 p.add_run("2").superscript();
218 p.add_run(" (superscript)");
219 }
220 {
221 let mut p = doc.add_paragraph("");
222 p.add_run("ALL CAPS").all_caps(true);
223 p.add_run(" | ");
224 p.add_run("Small Caps").small_caps(true);
225 p.add_run(" | ");
226 p.add_run("Expanded").character_spacing(Length::twips(40));
227 p.add_run(" | ");
228 p.add_run("Hidden text (not visible)").hidden(true);
229 }
230
231 doc.add_paragraph("");
232
233 // Underline Styles
234 doc.add_paragraph("1.3 Underline Styles").style("Heading2");
235 {
236 let mut p = doc.add_paragraph("");
237 p.add_run("Single ")
238 .underline_style(rdocx::UnderlineStyle::Single);
239 p.add_run("Double ")
240 .underline_style(rdocx::UnderlineStyle::Double);
241 p.add_run("Thick ")
242 .underline_style(rdocx::UnderlineStyle::Thick);
243 p.add_run("Dotted ")
244 .underline_style(rdocx::UnderlineStyle::Dotted);
245 p.add_run("Dash ")
246 .underline_style(rdocx::UnderlineStyle::Dash);
247 p.add_run("Wave ")
248 .underline_style(rdocx::UnderlineStyle::Wave);
249 }
250
251 // ── SECTION 2: PARAGRAPH FORMATTING ──
252 doc.add_paragraph("").page_break_before(true);
253 doc.add_paragraph("2. Paragraph Formatting")
254 .style("Heading1");
255
256 doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
257 doc.add_paragraph("Paragraph with light green shading")
258 .shading("E2EFDA");
259 doc.add_paragraph("Paragraph with bottom border")
260 .border_bottom(BorderStyle::Single, 6, "2E75B6");
261 doc.add_paragraph("Paragraph with all borders (red)")
262 .border_all(BorderStyle::Single, 4, "FF0000");
263
264 doc.add_paragraph("2.2 Indentation").style("Heading2");
265 doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
266 .indent_left(Length::inches(1.0))
267 .hanging_indent(Length::inches(0.5));
268 doc.add_paragraph("0.5-inch first-line indent on this paragraph")
269 .first_line_indent(Length::inches(0.5));
270 doc.add_paragraph("Both left and right indent (0.75 inches each)")
271 .indent_left(Length::inches(0.75))
272 .indent_right(Length::inches(0.75));
273
274 doc.add_paragraph("2.3 Spacing & Line Height")
275 .style("Heading2");
276 doc.add_paragraph("Extra space before (24pt) and after (12pt)")
277 .space_before(Length::pt(24.0))
278 .space_after(Length::pt(12.0));
279 doc.add_paragraph(
280 "Double line spacing paragraph. This text should have extra vertical space between \
281 lines to demonstrate line_spacing_multiple(2.0).",
282 )
283 .line_spacing_multiple(2.0);
284 doc.add_paragraph("Exact 20pt line spacing (fixed height).")
285 .line_spacing(20.0);
286
287 doc.add_paragraph("2.4 Pagination Controls")
288 .style("Heading2");
289 doc.add_paragraph("keep_with_next — this paragraph stays with the next")
290 .keep_with_next(true);
291 doc.add_paragraph("(This paragraph was kept with the one above.)");
292 doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
293 .keep_together(true);
294 doc.add_paragraph("widow_control — prevents widow/orphan lines")
295 .widow_control(true);
296
297 // ── SECTION 3: LISTS ──
298 doc.add_paragraph("").page_break_before(true);
299 doc.add_paragraph("3. Lists").style("Heading1");
300
301 doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
302 doc.add_bullet_list_item("First item", 0);
303 doc.add_bullet_list_item("Second item", 0);
304 doc.add_bullet_list_item("Nested level 1", 1);
305 doc.add_bullet_list_item("Nested level 2", 2);
306 doc.add_bullet_list_item("Back to level 1", 1);
307 doc.add_bullet_list_item("Third item", 0);
308
309 doc.add_paragraph("");
310
311 doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
312 doc.add_numbered_list_item("First numbered item", 0);
313 doc.add_numbered_list_item("Second numbered item", 0);
314 doc.add_numbered_list_item("Sub-item A", 1);
315 doc.add_numbered_list_item("Sub-item B", 1);
316 doc.add_numbered_list_item("Third numbered item", 0);
317
318 // ── SECTION 4: TAB STOPS ──
319 doc.add_paragraph("");
320 doc.add_paragraph("4. Tab Stops").style("Heading1");
321
322 doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
323 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
324 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
325 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
326 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
327 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
328
329 doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
330 doc.add_paragraph("Item\t\tPrice")
331 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
332 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
333 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
334 doc.add_paragraph("Widget\t\t$19.99")
335 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
336 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
337 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
338 doc.add_paragraph("Gadget\t\t$249.50")
339 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
340 .add_tab_stop_with_leader(
341 TabAlignment::Right,
342 Length::inches(4.0),
343 TabLeader::Underscore,
344 )
345 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
346
347 // ── SECTION 5: TABLES ──
348 doc.add_paragraph("").page_break_before(true);
349 doc.add_paragraph("5. Tables").style("Heading1");
350
351 doc.add_paragraph("5.1 Basic Table").style("Heading2");
352 {
353 let mut tbl = doc
354 .add_table(4, 3)
355 .borders(BorderStyle::Single, 4, "000000");
356 for col in 0..3 {
357 tbl.cell(0, col).unwrap().shading("2E75B6");
358 }
359 tbl.cell(0, 0).unwrap().set_text("Name");
360 tbl.cell(0, 1).unwrap().set_text("Role");
361 tbl.cell(0, 2).unwrap().set_text("Location");
362 tbl.cell(1, 0).unwrap().set_text("Walter White");
363 tbl.cell(1, 1).unwrap().set_text("CEO");
364 tbl.cell(1, 2).unwrap().set_text("Albuquerque");
365 tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
366 tbl.cell(2, 1).unwrap().set_text("CTO");
367 tbl.cell(2, 2).unwrap().set_text("Remote");
368 tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
369 tbl.cell(3, 1).unwrap().set_text("Security");
370 tbl.cell(3, 2).unwrap().set_text("Washington");
371 }
372
373 doc.add_paragraph("");
374
375 doc.add_paragraph("5.2 Column Span & Cell Shading")
376 .style("Heading2");
377 {
378 let mut tbl = doc
379 .add_table(3, 4)
380 .borders(BorderStyle::Single, 4, "000000")
381 .width_pct(100.0);
382 tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
383 tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
384 tbl.cell(1, 0).unwrap().set_text("Region");
385 tbl.cell(1, 0).unwrap().shading("D6E4F0");
386 tbl.cell(1, 1).unwrap().set_text("Q1");
387 tbl.cell(1, 1).unwrap().shading("D6E4F0");
388 tbl.cell(1, 2).unwrap().set_text("Q2");
389 tbl.cell(1, 2).unwrap().shading("D6E4F0");
390 tbl.cell(1, 3).unwrap().set_text("Total");
391 tbl.cell(1, 3).unwrap().shading("D6E4F0");
392 tbl.cell(2, 0).unwrap().set_text("Americas");
393 tbl.cell(2, 1).unwrap().set_text("$2.4M");
394 tbl.cell(2, 2).unwrap().set_text("$2.7M");
395 tbl.cell(2, 3).unwrap().set_text("$5.1M");
396 }
397
398 doc.add_paragraph("");
399
400 doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
401 {
402 let mut tbl = doc
403 .add_table(4, 3)
404 .borders(BorderStyle::Single, 4, "000000");
405 tbl.cell(0, 0).unwrap().set_text("Category");
406 tbl.cell(0, 0).unwrap().shading("E2EFDA");
407 tbl.cell(0, 1).unwrap().set_text("Item");
408 tbl.cell(0, 1).unwrap().shading("E2EFDA");
409 tbl.cell(0, 2).unwrap().set_text("Price");
410 tbl.cell(0, 2).unwrap().shading("E2EFDA");
411 tbl.cell(1, 0).unwrap().set_text("Hardware");
412 tbl.cell(1, 0).unwrap().v_merge_restart();
413 tbl.cell(1, 1).unwrap().set_text("Laptop");
414 tbl.cell(1, 2).unwrap().set_text("$1,200");
415 tbl.cell(2, 0).unwrap().v_merge_continue();
416 tbl.cell(2, 1).unwrap().set_text("Monitor");
417 tbl.cell(2, 2).unwrap().set_text("$450");
418 tbl.cell(3, 0).unwrap().set_text("Software");
419 tbl.cell(3, 1).unwrap().set_text("IDE License");
420 tbl.cell(3, 2).unwrap().set_text("$200/yr");
421 }
422
423 doc.add_paragraph("");
424
425 doc.add_paragraph("5.4 Nested Table").style("Heading2");
426 {
427 let mut tbl = doc
428 .add_table(2, 2)
429 .borders(BorderStyle::Single, 6, "2E75B6");
430 tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
431 tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
432 tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
433 {
434 let mut cell = tbl.cell(1, 1).unwrap();
435 cell.set_text("Nested table below:");
436 let mut nested = cell
437 .add_table(2, 2)
438 .borders(BorderStyle::Single, 2, "FF6600");
439 nested.cell(0, 0).unwrap().set_text("A");
440 nested.cell(0, 1).unwrap().set_text("B");
441 nested.cell(1, 0).unwrap().set_text("C");
442 nested.cell(1, 1).unwrap().set_text("D");
443 }
444 }
445
446 doc.add_paragraph("");
447
448 doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
449 .style("Heading2");
450 {
451 let mut tbl = doc
452 .add_table(2, 3)
453 .borders(BorderStyle::Single, 4, "666666");
454 tbl.row(0).unwrap().height(Length::pt(50.0)).header();
455 tbl.cell(0, 0).unwrap().set_text("Top");
456 tbl.cell(0, 0)
457 .unwrap()
458 .vertical_alignment(VerticalAlignment::Top)
459 .shading("FFF2CC");
460 tbl.cell(0, 1).unwrap().set_text("Center");
461 tbl.cell(0, 1)
462 .unwrap()
463 .vertical_alignment(VerticalAlignment::Center)
464 .shading("D9E2F3");
465 tbl.cell(0, 2).unwrap().set_text("Bottom");
466 tbl.cell(0, 2)
467 .unwrap()
468 .vertical_alignment(VerticalAlignment::Bottom)
469 .shading("E2EFDA");
470 tbl.row(1).unwrap().cant_split();
471 tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
472 tbl.cell(1, 0).unwrap().no_wrap();
473 tbl.cell(1, 1).unwrap().set_text("Fixed width");
474 tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
475 tbl.cell(1, 2).unwrap().set_text("Normal");
476 }
477
478 // ── SECTION 6: IMAGES ──
479 doc.add_paragraph("").page_break_before(true);
480 doc.add_paragraph("6. Images").style("Heading1");
481
482 doc.add_paragraph("6.1 Inline Image").style("Heading2");
483 doc.add_paragraph("A blue gradient image below:");
484 let img = create_sample_png(200, 50, [0, 80, 200]);
485 doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
486
487 doc.add_paragraph("");
488 doc.add_paragraph("6.2 Header Image").style("Heading2");
489 let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
490 doc.set_header_image(
491 &hdr_img,
492 "header_logo.png",
493 Length::inches(2.0),
494 Length::inches(0.2),
495 );
496 doc.add_paragraph("The header now contains an inline image (check the top of this page).");
497
498 doc.add_paragraph("");
499 doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
500
501 // ── SECTION 7: CONTENT MANIPULATION ──
502 doc.add_paragraph("").page_break_before(true);
503 doc.add_paragraph("7. Content Manipulation")
504 .style("Heading1");
505
506 doc.add_paragraph("7.1 Placeholder Replacement")
507 .style("Heading2");
508 doc.add_paragraph("Customer: {{customer}}");
509 doc.add_paragraph("Date: {{date}}");
510 doc.add_paragraph("Reference: {{ref_number}}");
511 {
512 let mut tbl = doc
513 .add_table(2, 2)
514 .borders(BorderStyle::Single, 4, "000000");
515 tbl.cell(0, 0).unwrap().set_text("Project");
516 tbl.cell(0, 0).unwrap().shading("D6E4F0");
517 tbl.cell(0, 1).unwrap().set_text("{{project}}");
518 tbl.cell(1, 0).unwrap().set_text("Status");
519 tbl.cell(1, 0).unwrap().shading("D6E4F0");
520 tbl.cell(1, 1).unwrap().set_text("{{status}}");
521 }
522
523 let mut replacements = HashMap::new();
524 replacements.insert("{{customer}}", "Tensorbee Inc.");
525 replacements.insert("{{date}}", "February 22, 2026");
526 replacements.insert("{{ref_number}}", "TB-2026-001");
527 replacements.insert("{{project}}", "Infrastructure Upgrade");
528 replacements.insert("{{status}}", "In Progress");
529 let n = doc.replace_all(&replacements);
530 doc.add_paragraph(&format!("({n} placeholders replaced above)"));
531
532 doc.add_paragraph("");
533
534 doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
535 doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
536 let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
537 doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
538
539 doc.add_paragraph("");
540
541 doc.add_paragraph("7.3 Content Insertion").style("Heading2");
542 doc.add_paragraph("Section A: First content.");
543 doc.add_paragraph("Section C: Third content.");
544 if let Some(idx) = doc.find_content_index("Section C") {
545 doc.insert_paragraph(
546 idx,
547 "Section B: Inserted between A and C via find_content_index().",
548 );
549 }
550
551 // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
552 doc.add_paragraph("").section_break(SectionBreak::NextPage);
553 doc.add_paragraph("8. Section Breaks & Orientation")
554 .style("Heading1");
555 doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
556
557 {
558 let mut tbl = doc
559 .add_table(3, 7)
560 .borders(BorderStyle::Single, 4, "2E75B6");
561 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
562 for (c, h) in headers.iter().enumerate() {
563 tbl.cell(0, c).unwrap().set_text(h);
564 tbl.cell(0, c).unwrap().shading("2E75B6");
565 }
566 let data = [
567 [
568 "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
569 ],
570 [
571 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
572 ],
573 ];
574 for (r, row) in data.iter().enumerate() {
575 for (c, v) in row.iter().enumerate() {
576 tbl.cell(r + 1, c).unwrap().set_text(v);
577 }
578 }
579 }
580
581 doc.add_paragraph("")
582 .section_break(SectionBreak::NextPage)
583 .section_landscape();
584
585 // ── SECTION 9: CUSTOM STYLES ──
586 doc.add_paragraph("9. Custom Styles").style("Heading1");
587 doc.add_style(
588 StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
589 .based_on("Normal")
590 .paragraph_properties({
591 let mut ppr = rdocx_oxml::properties::CT_PPr::default();
592 ppr.shading = Some(rdocx_oxml::properties::CT_Shd {
593 val: "clear".to_string(),
594 color: None,
595 fill: Some("FFF2CC".to_string()),
596 });
597 ppr
598 })
599 .run_properties({
600 let mut rpr = rdocx_oxml::properties::CT_RPr::default();
601 rpr.bold = Some(true);
602 rpr.color = Some("C45911".to_string());
603 rpr
604 }),
605 );
606 doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
607 .style("CustomHighlight");
608
609 doc.add_paragraph("");
610
611 // ── SECTION 10: DOCUMENT INTELLIGENCE ──
612 doc.add_paragraph("10. Document Intelligence API")
613 .style("Heading1");
614
615 let wc = doc.word_count();
616 let hc = doc.headings().len();
617 let ic = doc.images().len();
618 let lc = doc.links().len();
619 doc.add_paragraph(&format!("Word count: {wc}"));
620 doc.add_paragraph(&format!("Heading count: {hc}"));
621 doc.add_paragraph(&format!("Image count: {ic}"));
622 doc.add_paragraph(&format!("Link count: {lc}"));
623
624 let outline = doc.document_outline();
625 doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
626
627 let issues = doc.audit_accessibility();
628 doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
629 for issue in &issues {
630 doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
631 }
632
633 // ── SECTION 11: DOCUMENT MERGING ──
634 doc.add_paragraph("");
635 doc.add_paragraph("11. Document Merging").style("Heading1");
636 {
637 let mut other = Document::new();
638 other.add_paragraph("This paragraph was merged from another document using append().");
639 doc.append(&other);
640 }
641
642 doc.add_paragraph("");
643
644 // ── SUMMARY ──
645 doc.add_paragraph("Summary of Demonstrated Features")
646 .style("Heading1");
647 let features = [
648 "Page setup: size, margins, header/footer distance, gutter",
649 "Document metadata: title, author, subject, keywords",
650 "Headers and footers: text, images, different first page",
651 "Background images (full-page behind text)",
652 "Table of Contents generation with bookmarks",
653 "Text formatting: bold, italic, underline styles, strike, color, size, font",
654 "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
655 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
656 "Pagination controls: keep-with-next, keep-together, widow control",
657 "Bullet and numbered lists with nesting",
658 "Tab stops with dot/underscore/hyphen leaders",
659 "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
660 "Row properties: header rows, exact height, cant-split",
661 "Cell properties: width, no-wrap, vertical alignment",
662 "Inline images and header images",
663 "Placeholder replacement (single and batch)",
664 "Regex-based find and replace",
665 "Content insertion at specific positions",
666 "Section breaks with mixed portrait/landscape",
667 "Custom paragraph and character styles",
668 "Document intelligence: word count, headings, outline, images, links",
669 "Accessibility audit",
670 "Document merging (append)",
671 "Export: DOCX, PDF, HTML, Markdown",
672 ];
673 for f in &features {
674 doc.add_bullet_list_item(f, 0);
675 }
676 doc.add_paragraph("");
677 doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
678 .alignment(Alignment::Center)
679 .shading("E2EFDA")
680 .border_all(BorderStyle::Single, 2, "00AA00");
681
682 doc
683}
684
685// =============================================================================
686// 2. PROPOSAL DOCUMENT — Deep navy + gold scheme
687// =============================================================================
688fn generate_proposal(_samples_dir: &Path) -> Document {
689 let mut doc = Document::new();
690
691 // Colors: Navy #1B2A4A, Gold #C5922E, Light #F4F1EB
692 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
693 doc.set_margins(
694 Length::inches(1.0),
695 Length::inches(1.0),
696 Length::inches(1.0),
697 Length::inches(1.0),
698 );
699 doc.set_title("AI Platform Modernization Proposal");
700 doc.set_author("Walter White");
701 doc.set_subject("Technology Proposal");
702 doc.set_keywords("proposal, AI, modernization, Tensorbee");
703
704 // Banner header
705 let logo_img = create_logo_png(220, 48);
706 let banner = build_header_banner_xml(
707 "rId1",
708 &BannerOpts {
709 bg_color: "1B2A4A",
710 banner_width: 7772400,
711 banner_height: 914400,
712 logo_width: 2011680,
713 logo_height: 438912,
714 logo_x_offset: 295125,
715 logo_y_offset: 237744,
716 },
717 );
718 doc.set_raw_header_with_images(
719 banner,
720 &[("rId1", &logo_img, "logo.png")],
721 rdocx_oxml::header_footer::HdrFtrType::Default,
722 );
723 doc.set_different_first_page(true);
724 let cover_banner = build_header_banner_xml(
725 "rId1",
726 &BannerOpts {
727 bg_color: "C5922E",
728 banner_width: 7772400,
729 banner_height: 914400,
730 logo_width: 2011680,
731 logo_height: 438912,
732 logo_x_offset: 295125,
733 logo_y_offset: 237744,
734 },
735 );
736 doc.set_raw_header_with_images(
737 cover_banner,
738 &[("rId1", &logo_img, "logo.png")],
739 rdocx_oxml::header_footer::HdrFtrType::First,
740 );
741 doc.set_margins(
742 Length::twips(2292),
743 Length::twips(1440),
744 Length::twips(1440),
745 Length::twips(1440),
746 );
747 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
748 doc.set_footer("Tensorbee — Confidential");
749
750 // Custom styles
751 doc.add_style(
752 StyleBuilder::paragraph("ProposalTitle", "Proposal Title")
753 .based_on("Normal")
754 .run_properties({
755 let mut rpr = rdocx_oxml::properties::CT_RPr::default();
756 rpr.bold = Some(true);
757 rpr.font_ascii = Some("Georgia".to_string());
758 rpr.font_hansi = Some("Georgia".to_string());
759 rpr.sz = Some(rdocx_oxml::HalfPoint(56)); // half-points → 28pt
760 rpr.color = Some("1B2A4A".to_string());
761 rpr
762 }),
763 );
764
765 // ── Cover Page ──
766 for _ in 0..4 {
767 doc.add_paragraph("");
768 }
769 doc.add_paragraph("AI Platform Modernization")
770 .style("ProposalTitle")
771 .alignment(Alignment::Center);
772 doc.add_paragraph("")
773 .alignment(Alignment::Center)
774 .border_bottom(BorderStyle::Single, 8, "C5922E");
775 {
776 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
777 p.add_run("Prepared for Global Financial Corp.")
778 .size(14.0)
779 .color("666666")
780 .italic(true);
781 }
782 {
783 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
784 p.add_run("Prepared by: Walter White, CEO — Tensorbee")
785 .size(12.0)
786 .color("888888");
787 }
788 {
789 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
790 p.add_run("February 22, 2026").size(12.0).color("888888");
791 }
792
793 // ── TOC ──
794 doc.add_paragraph("").page_break_before(true);
795 doc.add_paragraph("Table of Contents").style("Heading1");
796 // We'll add a manual-style TOC since the insert_toc goes at a specific position
797 doc.insert_toc(doc.content_count(), 2);
798
799 // ── Executive Summary ──
800 doc.add_paragraph("").page_break_before(true);
801 doc.add_paragraph("Executive Summary").style("Heading1");
802 doc.add_paragraph(
803 "Tensorbee proposes a comprehensive modernization of Global Financial Corp's AI platform, \
804 transitioning from legacy batch-processing systems to a real-time inference architecture. \
805 This transformation will reduce model deployment time from weeks to hours, improve \
806 prediction accuracy by an estimated 23%, and deliver $4.2M in annual operational savings.",
807 )
808 .first_line_indent(Length::inches(0.3));
809 doc.add_paragraph(
810 "The project spans three phases over 18 months, with a total investment of $2.8M. \
811 Tensorbee brings deep expertise in ML infrastructure, having successfully completed \
812 similar transformations for three Fortune 500 financial institutions.",
813 )
814 .first_line_indent(Length::inches(0.3));
815
816 // Key metrics table
817 doc.add_paragraph("");
818 {
819 let mut tbl = doc
820 .add_table(5, 2)
821 .borders(BorderStyle::Single, 4, "1B2A4A")
822 .width_pct(80.0);
823 tbl.cell(0, 0).unwrap().set_text("Key Metric");
824 tbl.cell(0, 0).unwrap().shading("1B2A4A");
825 tbl.cell(0, 1).unwrap().set_text("Value");
826 tbl.cell(0, 1).unwrap().shading("1B2A4A");
827 let rows = [
828 ("Total Investment", "$2.8M"),
829 ("Annual Savings", "$4.2M"),
830 ("ROI (Year 1)", "150%"),
831 ("Timeline", "18 months"),
832 ];
833 for (i, (k, v)) in rows.iter().enumerate() {
834 tbl.cell(i + 1, 0).unwrap().set_text(k);
835 if i % 2 == 0 {
836 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
837 }
838 tbl.cell(i + 1, 1).unwrap().set_text(v);
839 if i % 2 == 0 {
840 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
841 }
842 }
843 }
844
845 // ── Problem Statement ──
846 doc.add_paragraph("").page_break_before(true);
847 doc.add_paragraph("Problem Statement").style("Heading1");
848 doc.add_paragraph(
849 "Global Financial Corp's current AI infrastructure faces three critical challenges:",
850 );
851 doc.add_numbered_list_item("Deployment Latency — New models take 3-4 weeks to deploy to production, compared to the industry benchmark of 2-3 days.", 0);
852 doc.add_numbered_list_item("Scalability Constraints — The batch processing architecture cannot handle real-time inference demands during peak trading hours.", 0);
853 doc.add_numbered_list_item("Technical Debt — Legacy Python 2.7 codebases and manual deployment scripts create reliability risks and slow iteration speed.", 0);
854
855 // ── Proposed Solution ──
856 doc.add_paragraph("").page_break_before(true);
857 doc.add_paragraph("Proposed Solution").style("Heading1");
858
859 doc.add_paragraph("Phase 1: Foundation (Months 1-6)")
860 .style("Heading2");
861 doc.add_bullet_list_item("Deploy Kubernetes-based ML serving infrastructure", 0);
862 doc.add_bullet_list_item("Implement CI/CD pipeline for model deployment", 0);
863 doc.add_bullet_list_item("Migrate top 5 production models to new platform", 0);
864
865 doc.add_paragraph("Phase 2: Expansion (Months 7-12)")
866 .style("Heading2");
867 doc.add_bullet_list_item(
868 "Real-time feature engineering pipeline (Apache Kafka + Flink)",
869 0,
870 );
871 doc.add_bullet_list_item("A/B testing framework for model variants", 0);
872 doc.add_bullet_list_item("Automated model monitoring and drift detection", 0);
873
874 doc.add_paragraph("Phase 3: Optimization (Months 13-18)")
875 .style("Heading2");
876 doc.add_bullet_list_item("GPU inference optimization (TensorRT/ONNX Runtime)", 0);
877 doc.add_bullet_list_item("Multi-region deployment for latency reduction", 0);
878 doc.add_bullet_list_item(
879 "Self-service model deployment portal for data science teams",
880 0,
881 );
882
883 // ── Budget ──
884 doc.add_paragraph("Budget Breakdown").style("Heading1");
885 {
886 let mut tbl = doc
887 .add_table(6, 3)
888 .borders(BorderStyle::Single, 4, "1B2A4A")
889 .width_pct(100.0);
890 tbl.cell(0, 0).unwrap().set_text("Category");
891 tbl.cell(0, 0).unwrap().shading("1B2A4A");
892 tbl.cell(0, 1).unwrap().set_text("Cost");
893 tbl.cell(0, 1).unwrap().shading("1B2A4A");
894 tbl.cell(0, 2).unwrap().set_text("Notes");
895 tbl.cell(0, 2).unwrap().shading("1B2A4A");
896 let items = [
897 (
898 "Infrastructure",
899 "$450,000",
900 "Cloud compute + GPU instances",
901 ),
902 ("Engineering Services", "$1,600,000", "12 FTE x 18 months"),
903 (
904 "Software Licenses",
905 "$350,000",
906 "Kafka, monitoring, CI/CD tools",
907 ),
908 (
909 "Training & Enablement",
910 "$200,000",
911 "Team training, documentation",
912 ),
913 ("Contingency (10%)", "$200,000", "Risk buffer"),
914 ];
915 for (i, (cat, cost, note)) in items.iter().enumerate() {
916 tbl.cell(i + 1, 0).unwrap().set_text(cat);
917 tbl.cell(i + 1, 1).unwrap().set_text(cost);
918 tbl.cell(i + 1, 2).unwrap().set_text(note);
919 if i % 2 == 0 {
920 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
921 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
922 tbl.cell(i + 1, 2).unwrap().shading("F4F1EB");
923 }
924 }
925 }
926
927 // ── Closing ──
928 doc.add_paragraph("");
929 doc.add_paragraph("Next Steps").style("Heading1");
930 doc.add_numbered_list_item(
931 "Schedule technical deep-dive with GFC engineering team (Week 1)",
932 0,
933 );
934 doc.add_numbered_list_item("Finalize scope and sign SOW (Week 2-3)", 0);
935 doc.add_numbered_list_item("Kick off Phase 1 with joint planning session (Week 4)", 0);
936
937 doc.add_paragraph("");
938 {
939 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
940 p.add_run("Walter White")
941 .bold(true)
942 .size(14.0)
943 .color("1B2A4A");
944 }
945 {
946 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
947 p.add_run("CEO, Tensorbee | walter@tensorbee.com")
948 .size(10.0)
949 .color("888888");
950 }
951
952 doc
953}
954
955// =============================================================================
956// 3. QUOTE / BILL OF MATERIALS — Teal + orange scheme
957// =============================================================================
958fn generate_quote(_samples_dir: &Path) -> Document {
959 let mut doc = Document::new();
960
961 // Colors: Teal #008B8B, Dark #1A3C3C, Orange #E07020, Light #F0F8F8
962 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
963 doc.set_margins(
964 Length::inches(0.75),
965 Length::inches(0.75),
966 Length::inches(0.75),
967 Length::inches(0.75),
968 );
969 doc.set_title("Quotation QT-2026-0147");
970 doc.set_author("Walter White");
971 doc.set_keywords("quote, BOM, Tensorbee");
972
973 doc.set_header("Tensorbee — Quotation");
974 doc.set_footer("QT-2026-0147 | Page");
975
976 // ── Header Block ──
977 {
978 let mut p = doc.add_paragraph("").alignment(Alignment::Left);
979 p.add_run("TENSORBEE")
980 .bold(true)
981 .size(28.0)
982 .color("008B8B")
983 .font("Helvetica");
984 }
985 doc.add_paragraph("123 Innovation Drive, Suite 400")
986 .alignment(Alignment::Left);
987 doc.add_paragraph("San Francisco, CA 94105 | +1 (415) 555-0199")
988 .alignment(Alignment::Left);
989 doc.add_paragraph("")
990 .border_bottom(BorderStyle::Single, 8, "008B8B");
991
992 // Quote meta
993 doc.add_paragraph("");
994 {
995 let mut tbl = doc.add_table(4, 4).width_pct(100.0);
996 tbl.cell(0, 0).unwrap().set_text("QUOTATION");
997 tbl.cell(0, 0).unwrap().shading("008B8B").grid_span(2);
998 tbl.cell(0, 2).unwrap().set_text("DATE");
999 tbl.cell(0, 2).unwrap().shading("008B8B");
1000 tbl.cell(0, 3).unwrap().set_text("VALID UNTIL");
1001 tbl.cell(0, 3).unwrap().shading("008B8B");
1002 tbl.cell(1, 0).unwrap().set_text("Quote #:");
1003 tbl.cell(1, 1).unwrap().set_text("QT-2026-0147");
1004 tbl.cell(1, 2).unwrap().set_text("Feb 22, 2026");
1005 tbl.cell(1, 3).unwrap().set_text("Mar 22, 2026");
1006 tbl.cell(2, 0).unwrap().set_text("Prepared by:");
1007 tbl.cell(2, 1).unwrap().set_text("Walter White");
1008 tbl.cell(2, 2).unwrap().set_text("Payment:");
1009 tbl.cell(2, 3).unwrap().set_text("Net 30");
1010 tbl.cell(3, 0).unwrap().set_text("Customer:");
1011 tbl.cell(3, 1).unwrap().set_text("Meridian Dynamics LLC");
1012 tbl.cell(3, 2).unwrap().set_text("Currency:");
1013 tbl.cell(3, 3).unwrap().set_text("USD");
1014 }
1015
1016 doc.add_paragraph("");
1017
1018 // ── Bill of Materials ──
1019 {
1020 let mut p = doc.add_paragraph("");
1021 p.add_run("Bill of Materials")
1022 .bold(true)
1023 .size(16.0)
1024 .color("1A3C3C");
1025 }
1026 doc.add_paragraph("");
1027
1028 {
1029 let mut tbl = doc
1030 .add_table(10, 6)
1031 .borders(BorderStyle::Single, 2, "008B8B")
1032 .width_pct(100.0)
1033 .layout_fixed();
1034 // Headers
1035 let hdrs = [
1036 "#",
1037 "Part Number",
1038 "Description",
1039 "Qty",
1040 "Unit Price",
1041 "Total",
1042 ];
1043 for (c, h) in hdrs.iter().enumerate() {
1044 tbl.cell(0, c).unwrap().set_text(h);
1045 tbl.cell(0, c).unwrap().shading("008B8B");
1046 }
1047
1048 let items: Vec<(&str, &str, &str, &str, &str)> = vec![
1049 (
1050 "TB-GPU-A100",
1051 "NVIDIA A100 80GB GPU",
1052 "4",
1053 "$12,500.00",
1054 "$50,000.00",
1055 ),
1056 (
1057 "TB-SRV-R750",
1058 "Dell R750xa Server Chassis",
1059 "2",
1060 "$8,200.00",
1061 "$16,400.00",
1062 ),
1063 (
1064 "TB-RAM-256G",
1065 "256GB DDR5 ECC Memory Module",
1066 "8",
1067 "$890.00",
1068 "$7,120.00",
1069 ),
1070 (
1071 "TB-SSD-3840",
1072 "3.84TB NVMe U.2 SSD",
1073 "8",
1074 "$1,150.00",
1075 "$9,200.00",
1076 ),
1077 (
1078 "TB-NET-CX7",
1079 "ConnectX-7 200GbE NIC",
1080 "4",
1081 "$1,800.00",
1082 "$7,200.00",
1083 ),
1084 (
1085 "TB-SW-48P",
1086 "48-Port 100GbE Switch",
1087 "1",
1088 "$22,000.00",
1089 "$22,000.00",
1090 ),
1091 (
1092 "TB-CAB-RACK",
1093 "42U Server Rack + PDU",
1094 "1",
1095 "$4,500.00",
1096 "$4,500.00",
1097 ),
1098 (
1099 "TB-SVC-INST",
1100 "Installation & Configuration",
1101 "1",
1102 "$8,500.00",
1103 "$8,500.00",
1104 ),
1105 (
1106 "TB-SVC-SUPP",
1107 "3-Year Premium Support",
1108 "1",
1109 "$15,000.00",
1110 "$15,000.00",
1111 ),
1112 ];
1113
1114 for (i, (pn, desc, qty, unit, total)) in items.iter().enumerate() {
1115 let row = i + 1;
1116 tbl.cell(row, 0).unwrap().set_text(&format!("{}", i + 1));
1117 tbl.cell(row, 1).unwrap().set_text(pn);
1118 tbl.cell(row, 2).unwrap().set_text(desc);
1119 tbl.cell(row, 3).unwrap().set_text(qty);
1120 tbl.cell(row, 4).unwrap().set_text(unit);
1121 tbl.cell(row, 5).unwrap().set_text(total);
1122 if i % 2 == 0 {
1123 for c in 0..6 {
1124 tbl.cell(row, c).unwrap().shading("F0F8F8");
1125 }
1126 }
1127 }
1128 }
1129
1130 doc.add_paragraph("");
1131
1132 // ── Totals ──
1133 {
1134 let mut tbl = doc
1135 .add_table(4, 2)
1136 .borders(BorderStyle::Single, 2, "008B8B")
1137 .width(Length::inches(3.5))
1138 .alignment(Alignment::Right);
1139 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1140 tbl.cell(0, 1).unwrap().set_text("$139,920.00");
1141 tbl.cell(1, 0).unwrap().set_text("Shipping & Handling");
1142 tbl.cell(1, 1).unwrap().set_text("$2,500.00");
1143 tbl.cell(2, 0).unwrap().set_text("Tax (8.625%)");
1144 tbl.cell(2, 1).unwrap().set_text("$12,068.10");
1145 tbl.cell(3, 0).unwrap().set_text("TOTAL");
1146 tbl.cell(3, 0).unwrap().shading("E07020");
1147 tbl.cell(3, 1).unwrap().set_text("$154,488.10");
1148 tbl.cell(3, 1).unwrap().shading("E07020");
1149 }
1150
1151 doc.add_paragraph("");
1152
1153 // ── Terms & Conditions ──
1154 {
1155 let mut p = doc.add_paragraph("");
1156 p.add_run("Terms & Conditions")
1157 .bold(true)
1158 .size(14.0)
1159 .color("1A3C3C");
1160 }
1161 doc.add_numbered_list_item(
1162 "This quotation is valid for 30 calendar days from the date of issue.",
1163 0,
1164 );
1165 doc.add_numbered_list_item(
1166 "All prices are in USD and exclusive of applicable taxes unless stated.",
1167 0,
1168 );
1169 doc.add_numbered_list_item("Standard lead time is 4-6 weeks from PO receipt.", 0);
1170 doc.add_numbered_list_item(
1171 "Payment terms: Net 30 from invoice date. 2% discount for payment within 10 days.",
1172 0,
1173 );
1174 doc.add_numbered_list_item(
1175 "Warranty: 3-year manufacturer warranty on all hardware components.",
1176 0,
1177 );
1178 doc.add_numbered_list_item(
1179 "Returns subject to 15% restocking fee if initiated after 14 days.",
1180 0,
1181 );
1182
1183 doc.add_paragraph("");
1184 doc.add_paragraph("")
1185 .border_bottom(BorderStyle::Single, 4, "008B8B");
1186 doc.add_paragraph("");
1187 {
1188 let mut p = doc.add_paragraph("");
1189 p.add_run("Accepted by: ").bold(true);
1190 p.add_run("___________________________ Date: ___________");
1191 }
1192 {
1193 let mut p = doc.add_paragraph("");
1194 p.add_run("Print Name: ").bold(true);
1195 p.add_run("___________________________ Title: ___________");
1196 }
1197
1198 doc
1199}
1200
1201// =============================================================================
1202// 4. INVOICE — Crimson + charcoal scheme
1203// =============================================================================
1204fn generate_invoice(_samples_dir: &Path) -> Document {
1205 let mut doc = Document::new();
1206
1207 // Colors: Crimson #B22222, Charcoal #333333, Light #FAF0F0
1208 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1209 doc.set_margins(
1210 Length::inches(0.75),
1211 Length::inches(0.75),
1212 Length::inches(0.75),
1213 Length::inches(0.75),
1214 );
1215 doc.set_title("Invoice INV-2026-0392");
1216 doc.set_author("Walter White");
1217 doc.set_keywords("invoice, Tensorbee");
1218
1219 doc.set_footer("Tensorbee — Thank you for your business!");
1220
1221 // ── Company & Invoice Header ──
1222 {
1223 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1224 // Company name left, INVOICE right
1225 {
1226 let mut cell = tbl.cell(0, 0).unwrap();
1227 let mut p = cell.add_paragraph("");
1228 p.add_run("TENSORBEE")
1229 .bold(true)
1230 .size(32.0)
1231 .color("B22222")
1232 .font("Helvetica");
1233 }
1234 {
1235 let mut cell = tbl.cell(0, 1).unwrap();
1236 let mut p = cell.add_paragraph("");
1237 p.add_run("INVOICE").bold(true).size(32.0).color("333333");
1238 }
1239 tbl.cell(1, 0)
1240 .unwrap()
1241 .set_text("123 Innovation Drive, Suite 400");
1242 tbl.cell(1, 1).unwrap().set_text("Invoice #: INV-2026-0392");
1243 tbl.cell(2, 0).unwrap().set_text("San Francisco, CA 94105");
1244 tbl.cell(2, 1).unwrap().set_text("Date: February 22, 2026");
1245 tbl.cell(3, 0).unwrap().set_text("walter@tensorbee.com");
1246 tbl.cell(3, 1).unwrap().set_text("Due Date: March 24, 2026");
1247 }
1248
1249 doc.add_paragraph("")
1250 .border_bottom(BorderStyle::Thick, 8, "B22222");
1251 doc.add_paragraph("");
1252
1253 // ── Bill To / Ship To ──
1254 {
1255 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1256 tbl.cell(0, 0).unwrap().set_text("BILL TO");
1257 tbl.cell(0, 0).unwrap().shading("B22222");
1258 tbl.cell(0, 1).unwrap().set_text("SHIP TO");
1259 tbl.cell(0, 1).unwrap().shading("B22222");
1260 tbl.cell(1, 0).unwrap().set_text("Meridian Dynamics LLC");
1261 tbl.cell(1, 1).unwrap().set_text("Meridian Dynamics LLC");
1262 tbl.cell(2, 0).unwrap().set_text("456 Enterprise Blvd");
1263 tbl.cell(2, 1).unwrap().set_text("Attn: Server Room B");
1264 tbl.cell(3, 0).unwrap().set_text("Austin, TX 78701");
1265 tbl.cell(3, 1)
1266 .unwrap()
1267 .set_text("456 Enterprise Blvd, Austin, TX 78701");
1268 }
1269
1270 doc.add_paragraph("");
1271
1272 // ── Line Items ──
1273 {
1274 let mut tbl = doc
1275 .add_table(8, 5)
1276 .borders(BorderStyle::Single, 2, "B22222")
1277 .width_pct(100.0);
1278 let hdrs = ["Description", "Qty", "Unit Price", "Tax", "Amount"];
1279 for (c, h) in hdrs.iter().enumerate() {
1280 tbl.cell(0, c).unwrap().set_text(h);
1281 tbl.cell(0, c).unwrap().shading("B22222");
1282 }
1283 let items = [
1284 (
1285 "ML Infrastructure Setup — Phase 1",
1286 "1",
1287 "$45,000.00",
1288 "$3,881.25",
1289 "$48,881.25",
1290 ),
1291 (
1292 "Data Pipeline Development (160 hrs)",
1293 "160",
1294 "$225.00",
1295 "$3,105.00",
1296 "$39,105.00",
1297 ),
1298 (
1299 "GPU Cluster Configuration",
1300 "1",
1301 "$12,000.00",
1302 "$1,035.00",
1303 "$13,035.00",
1304 ),
1305 (
1306 "API Gateway Implementation",
1307 "1",
1308 "$18,500.00",
1309 "$1,595.63",
1310 "$20,095.63",
1311 ),
1312 (
1313 "Load Testing & QA (80 hrs)",
1314 "80",
1315 "$195.00",
1316 "$1,345.50",
1317 "$16,945.50",
1318 ),
1319 (
1320 "Documentation & Training",
1321 "1",
1322 "$8,000.00",
1323 "$690.00",
1324 "$8,690.00",
1325 ),
1326 (
1327 "Project Management (3 months)",
1328 "3",
1329 "$6,500.00",
1330 "$1,679.63",
1331 "$21,179.63",
1332 ),
1333 ];
1334 for (i, (desc, qty, unit, tax, amt)) in items.iter().enumerate() {
1335 let r = i + 1;
1336 tbl.cell(r, 0).unwrap().set_text(desc);
1337 tbl.cell(r, 1).unwrap().set_text(qty);
1338 tbl.cell(r, 2).unwrap().set_text(unit);
1339 tbl.cell(r, 3).unwrap().set_text(tax);
1340 tbl.cell(r, 4).unwrap().set_text(amt);
1341 if i % 2 == 0 {
1342 for c in 0..5 {
1343 tbl.cell(r, c).unwrap().shading("FAF0F0");
1344 }
1345 }
1346 }
1347 }
1348
1349 doc.add_paragraph("");
1350
1351 // ── Totals ──
1352 {
1353 let mut tbl = doc
1354 .add_table(4, 2)
1355 .borders(BorderStyle::Single, 2, "B22222")
1356 .width(Length::inches(3.0))
1357 .alignment(Alignment::Right);
1358 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1359 tbl.cell(0, 1).unwrap().set_text("$154,600.00");
1360 tbl.cell(1, 0).unwrap().set_text("Tax (8.625%)");
1361 tbl.cell(1, 1).unwrap().set_text("$13,334.25");
1362 tbl.cell(2, 0).unwrap().set_text("Discount (5%)");
1363 tbl.cell(2, 1).unwrap().set_text("-$7,730.00");
1364 tbl.cell(3, 0).unwrap().set_text("AMOUNT DUE");
1365 tbl.cell(3, 0).unwrap().shading("B22222");
1366 tbl.cell(3, 1).unwrap().set_text("$160,204.25");
1367 tbl.cell(3, 1).unwrap().shading("B22222");
1368 }
1369
1370 doc.add_paragraph("");
1371 doc.add_paragraph("");
1372
1373 // ── Payment Details ──
1374 {
1375 let mut p = doc.add_paragraph("");
1376 p.add_run("Payment Details")
1377 .bold(true)
1378 .size(14.0)
1379 .color("333333");
1380 }
1381 doc.add_paragraph("Bank: Silicon Valley Bank")
1382 .indent_left(Length::inches(0.3));
1383 doc.add_paragraph("Account: Tensorbee Inc. — 0847-2953-1120")
1384 .indent_left(Length::inches(0.3));
1385 doc.add_paragraph("Routing: 121140399")
1386 .indent_left(Length::inches(0.3));
1387 doc.add_paragraph("Swift: SVBKUS6S")
1388 .indent_left(Length::inches(0.3));
1389
1390 doc.add_paragraph("");
1391
1392 doc.add_paragraph("Please include invoice number INV-2026-0392 in the payment reference.")
1393 .shading("FAF0F0")
1394 .border_all(BorderStyle::Single, 2, "B22222");
1395
1396 doc
1397}
1398
1399// =============================================================================
1400// 5. REPORT — Forest green + earth tones, with images & hierarchical sections
1401// =============================================================================
1402fn generate_report(_samples_dir: &Path) -> Document {
1403 let mut doc = Document::new();
1404
1405 // Colors: Forest #2D5016, Sage #6B8E23, Earth #8B7355, Cream #FFFAF0
1406 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1407 doc.set_margins(
1408 Length::inches(1.0),
1409 Length::inches(1.0),
1410 Length::inches(1.0),
1411 Length::inches(1.0),
1412 );
1413 doc.set_title("Q4 2025 Environmental Impact Report");
1414 doc.set_author("Walter White");
1415 doc.set_subject("Quarterly Environmental Report");
1416 doc.set_keywords("environment, sustainability, report, Tensorbee");
1417
1418 doc.set_different_first_page(true);
1419 doc.set_first_page_header("");
1420 doc.set_header("Tensorbee — Q4 2025 Environmental Impact Report");
1421 doc.set_footer("CONFIDENTIAL — Page");
1422
1423 // ── Cover ──
1424 let cover_bg = create_sample_png(612, 792, [20, 50, 15]);
1425 doc.add_background_image(&cover_bg, "report_cover.png");
1426
1427 for _ in 0..5 {
1428 doc.add_paragraph("");
1429 }
1430 {
1431 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1432 p.add_run("Q4 2025")
1433 .bold(true)
1434 .size(48.0)
1435 .color("FFFFFF")
1436 .font("Georgia");
1437 }
1438 {
1439 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1440 p.add_run("Environmental Impact Report")
1441 .size(24.0)
1442 .color("90EE90")
1443 .font("Georgia")
1444 .italic(true);
1445 }
1446 doc.add_paragraph("");
1447 {
1448 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1449 p.add_run("Tensorbee — Sustainability Division")
1450 .size(14.0)
1451 .color("C0C0C0");
1452 }
1453 {
1454 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1455 p.add_run("Prepared by Walter White, Chief Sustainability Officer")
1456 .size(11.0)
1457 .color("AAAAAA");
1458 }
1459
1460 // ── TOC ──
1461 doc.add_paragraph("").page_break_before(true);
1462 doc.insert_toc(doc.content_count(), 3);
1463
1464 // ── Section 1: Executive Overview ──
1465 doc.add_paragraph("").page_break_before(true);
1466 doc.add_paragraph("Executive Overview").style("Heading1");
1467 doc.add_paragraph(
1468 "This report presents Tensorbee's environmental performance for Q4 2025. Our \
1469 sustainability initiatives have yielded a 34% reduction in carbon emissions compared \
1470 to Q4 2024, exceeding our target of 25%. Key achievements include the transition to \
1471 100% renewable energy in our primary data centers and the launch of our carbon offset \
1472 marketplace.",
1473 )
1474 .first_line_indent(Length::inches(0.3));
1475
1476 // Image: performance chart
1477 let chart_img = create_chart_png(400, 200);
1478 doc.add_paragraph("");
1479 doc.add_picture(
1480 &chart_img,
1481 "performance_chart.png",
1482 Length::inches(5.0),
1483 Length::inches(2.5),
1484 )
1485 .alignment(Alignment::Center);
1486 {
1487 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1488 p.add_run("Figure 1: Quarterly Carbon Emissions (tonnes CO2e)")
1489 .italic(true)
1490 .size(9.0)
1491 .color("666666");
1492 }
1493
1494 // ── Section 2: Energy Consumption ──
1495 doc.add_paragraph("").page_break_before(true);
1496 doc.add_paragraph("Energy Consumption").style("Heading1");
1497
1498 doc.add_paragraph("Data Center Operations")
1499 .style("Heading2");
1500 doc.add_paragraph(
1501 "Our three primary data centers consumed a combined 4.2 GWh in Q4 2025, \
1502 a 12% reduction from Q3 2025 driven by improved cooling efficiency and \
1503 server consolidation.",
1504 );
1505
1506 // Data table
1507 {
1508 let mut tbl = doc
1509 .add_table(5, 4)
1510 .borders(BorderStyle::Single, 4, "2D5016")
1511 .width_pct(100.0);
1512 let hdrs = ["Data Center", "Capacity (MW)", "Usage (GWh)", "PUE"];
1513 for (c, h) in hdrs.iter().enumerate() {
1514 tbl.cell(0, c).unwrap().set_text(h);
1515 tbl.cell(0, c).unwrap().shading("2D5016");
1516 }
1517 let rows = [
1518 ("San Francisco (Primary)", "3.2", "1.8", "1.12"),
1519 ("Dublin (EU)", "2.1", "1.2", "1.18"),
1520 ("Singapore (APAC)", "1.8", "1.2", "1.24"),
1521 ("TOTAL", "7.1", "4.2", "1.17 avg"),
1522 ];
1523 for (i, (dc, cap, use_, pue)) in rows.iter().enumerate() {
1524 tbl.cell(i + 1, 0).unwrap().set_text(dc);
1525 tbl.cell(i + 1, 1).unwrap().set_text(cap);
1526 tbl.cell(i + 1, 2).unwrap().set_text(use_);
1527 tbl.cell(i + 1, 3).unwrap().set_text(pue);
1528 if i == 3 {
1529 for c in 0..4 {
1530 tbl.cell(i + 1, c).unwrap().shading("E2EFDA");
1531 }
1532 }
1533 }
1534 }
1535
1536 doc.add_paragraph("");
1537
1538 doc.add_paragraph("Renewable Energy Mix").style("Heading2");
1539 doc.add_paragraph("Breakdown of energy sources across all facilities:");
1540
1541 doc.add_bullet_list_item("Solar PV: 42% (1.76 GWh)", 0);
1542 doc.add_bullet_list_item("Wind Power Purchase Agreements: 38% (1.60 GWh)", 0);
1543 doc.add_bullet_list_item("Hydroelectric: 12% (0.50 GWh)", 0);
1544 doc.add_bullet_list_item("Grid (non-renewable): 8% (0.34 GWh)", 0);
1545
1546 // ── Section 3: Water & Waste ──
1547 doc.add_paragraph("Water & Waste Management")
1548 .style("Heading1");
1549
1550 doc.add_paragraph("Water Usage").style("Heading2");
1551 doc.add_paragraph(
1552 "Total water consumption was 12.4 million gallons, a 15% reduction from Q3. \
1553 Our closed-loop cooling systems now recycle 78% of water used in cooling operations.",
1554 );
1555
1556 doc.add_paragraph("Waste Reduction").style("Heading2");
1557 doc.add_paragraph("E-waste management results for Q4:")
1558 .keep_with_next(true);
1559 {
1560 let mut tbl = doc
1561 .add_table(4, 3)
1562 .borders(BorderStyle::Single, 2, "6B8E23")
1563 .width_pct(80.0);
1564 tbl.cell(0, 0).unwrap().set_text("Category");
1565 tbl.cell(0, 0).unwrap().shading("6B8E23");
1566 tbl.cell(0, 1).unwrap().set_text("Weight (kg)");
1567 tbl.cell(0, 1).unwrap().shading("6B8E23");
1568 tbl.cell(0, 2).unwrap().set_text("Recycled %");
1569 tbl.cell(0, 2).unwrap().shading("6B8E23");
1570 let rows = [
1571 ("Server Hardware", "2,450", "94%"),
1572 ("Networking Equipment", "820", "91%"),
1573 ("Storage Media", "340", "99%"),
1574 ];
1575 for (i, (cat, wt, pct)) in rows.iter().enumerate() {
1576 tbl.cell(i + 1, 0).unwrap().set_text(cat);
1577 tbl.cell(i + 1, 1).unwrap().set_text(wt);
1578 tbl.cell(i + 1, 2).unwrap().set_text(pct);
1579 }
1580 }
1581
1582 // ── Section 4: Initiatives ──
1583 doc.add_paragraph("").page_break_before(true);
1584 doc.add_paragraph("Q1 2026 Initiatives").style("Heading1");
1585
1586 doc.add_paragraph("Planned Programs").style("Heading2");
1587 doc.add_numbered_list_item(
1588 "Deploy on-site battery storage at SF data center (2 MWh capacity)",
1589 0,
1590 );
1591 doc.add_numbered_list_item("Pilot immersion cooling in Dublin facility", 0);
1592 doc.add_numbered_list_item("Launch employee commute carbon offset program", 0);
1593 doc.add_numbered_list_item("Achieve ISO 14001 certification for Singapore facility", 0);
1594
1595 doc.add_paragraph("Investment Targets").style("Heading2");
1596 doc.add_paragraph("Sustainability CapEx allocation for FY2026:")
1597 .keep_with_next(true);
1598 {
1599 let mut tbl = doc
1600 .add_table(5, 2)
1601 .borders(BorderStyle::Single, 4, "2D5016");
1602 tbl.cell(0, 0).unwrap().set_text("Initiative");
1603 tbl.cell(0, 0).unwrap().shading("2D5016");
1604 tbl.cell(0, 1).unwrap().set_text("Budget");
1605 tbl.cell(0, 1).unwrap().shading("2D5016");
1606 let rows = [
1607 ("Battery Storage", "$1.2M"),
1608 ("Immersion Cooling Pilot", "$800K"),
1609 ("Solar Panel Expansion", "$2.1M"),
1610 ("Carbon Credits", "$500K"),
1611 ];
1612 for (i, (init, budget)) in rows.iter().enumerate() {
1613 tbl.cell(i + 1, 0).unwrap().set_text(init);
1614 tbl.cell(i + 1, 1).unwrap().set_text(budget);
1615 if i % 2 == 0 {
1616 tbl.cell(i + 1, 0).unwrap().shading("FFFAF0");
1617 tbl.cell(i + 1, 1).unwrap().shading("FFFAF0");
1618 }
1619 }
1620 }
1621
1622 // Image: sustainability roadmap
1623 let roadmap_img = create_sample_png(500, 100, [40, 80, 30]);
1624 doc.add_paragraph("");
1625 doc.add_picture(
1626 &roadmap_img,
1627 "roadmap.png",
1628 Length::inches(6.0),
1629 Length::inches(1.2),
1630 )
1631 .alignment(Alignment::Center);
1632 {
1633 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1634 p.add_run("Figure 2: 2026 Sustainability Roadmap")
1635 .italic(true)
1636 .size(9.0)
1637 .color("666666");
1638 }
1639
1640 doc.add_paragraph("");
1641 doc.add_paragraph(
1642 "For questions about this report, contact Walter White at sustainability@tensorbee.com.",
1643 )
1644 .shading("FFFAF0")
1645 .border_all(BorderStyle::Single, 2, "2D5016");
1646
1647 doc
1648}
1649
1650// =============================================================================
1651// 6. LETTER — Slate blue + silver scheme
1652// =============================================================================
1653fn generate_letter(_samples_dir: &Path) -> Document {
1654 let mut doc = Document::new();
1655
1656 // Colors: Slate #4A5568, Blue accent #3182CE
1657 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1658 doc.set_margins(
1659 Length::inches(1.25),
1660 Length::inches(1.25),
1661 Length::inches(1.25),
1662 Length::inches(1.25),
1663 );
1664 doc.set_title("Business Letter");
1665 doc.set_author("Walter White");
1666
1667 // Banner header using raw XML
1668 let logo_img = create_logo_png(220, 48);
1669 let banner = build_header_banner_xml(
1670 "rId1",
1671 &BannerOpts {
1672 bg_color: "4A5568",
1673 banner_width: 7772400,
1674 banner_height: 731520, // ~0.8"
1675 logo_width: 2011680,
1676 logo_height: 438912,
1677 logo_x_offset: 295125,
1678 logo_y_offset: 146304,
1679 },
1680 );
1681 doc.set_raw_header_with_images(
1682 banner,
1683 &[("rId1", &logo_img, "logo.png")],
1684 rdocx_oxml::header_footer::HdrFtrType::Default,
1685 );
1686 doc.set_margins(
1687 Length::twips(2000),
1688 Length::twips(1800),
1689 Length::twips(1440),
1690 Length::twips(1800),
1691 );
1692 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
1693
1694 // Footer with contact
1695 doc.set_footer(
1696 "Tensorbee | 123 Innovation Drive, Suite 400 | San Francisco, CA 94105 | tensorbee.com",
1697 );
1698
1699 // ── Sender Address ──
1700 doc.add_paragraph("Walter White");
1701 doc.add_paragraph("Chief Executive Officer");
1702 doc.add_paragraph("Tensorbee");
1703 doc.add_paragraph("123 Innovation Drive, Suite 400");
1704 doc.add_paragraph("San Francisco, CA 94105");
1705 doc.add_paragraph("");
1706 doc.add_paragraph("February 22, 2026");
1707
1708 doc.add_paragraph("");
1709
1710 // ── Recipient ──
1711 doc.add_paragraph("Ms. Sarah Chen");
1712 doc.add_paragraph("VP of Engineering");
1713 doc.add_paragraph("Meridian Dynamics LLC");
1714 doc.add_paragraph("456 Enterprise Boulevard");
1715 doc.add_paragraph("Austin, TX 78701");
1716
1717 doc.add_paragraph("");
1718
1719 // ── Subject ──
1720 {
1721 let mut p = doc.add_paragraph("");
1722 p.add_run("Re: Strategic Technology Partnership — Phase 2 Expansion")
1723 .bold(true);
1724 }
1725
1726 doc.add_paragraph("");
1727
1728 // ── Body ──
1729 doc.add_paragraph("Dear Ms. Chen,");
1730 doc.add_paragraph("");
1731
1732 doc.add_paragraph(
1733 "I am writing to express Tensorbee's enthusiasm for expanding our strategic technology \
1734 partnership with Meridian Dynamics. Following the successful completion of Phase 1, \
1735 which delivered a 40% improvement in your ML inference pipeline throughput, we believe \
1736 the foundation is firmly established for an ambitious Phase 2 engagement.",
1737 )
1738 .first_line_indent(Length::inches(0.5));
1739
1740 doc.add_paragraph(
1741 "During our recent executive review, your team highlighted three priority areas for \
1742 the next phase of collaboration:",
1743 )
1744 .first_line_indent(Length::inches(0.5));
1745
1746 doc.add_numbered_list_item(
1747 "Real-time anomaly detection for your financial transaction monitoring system, \
1748 targeting sub-100ms latency at 50,000 transactions per second.",
1749 0,
1750 );
1751 doc.add_numbered_list_item(
1752 "Federated learning infrastructure to enable model training across your distributed \
1753 data centers without centralizing sensitive financial data.",
1754 0,
1755 );
1756 doc.add_numbered_list_item(
1757 "MLOps automation to reduce your model deployment cycle from the current 5 days \
1758 to under 4 hours.",
1759 0,
1760 );
1761
1762 doc.add_paragraph(
1763 "Our engineering team has prepared a detailed technical proposal addressing each of \
1764 these areas. We have allocated a dedicated team of eight senior engineers, led by \
1765 our CTO, to ensure continuity with the Phase 1 team your organization has already \
1766 built a productive working relationship with.",
1767 )
1768 .first_line_indent(Length::inches(0.5));
1769
1770 doc.add_paragraph(
1771 "I would welcome the opportunity to present our Phase 2 proposal in person. My \
1772 assistant will reach out to coordinate a meeting at your convenience during the \
1773 first week of March.",
1774 )
1775 .first_line_indent(Length::inches(0.5));
1776
1777 doc.add_paragraph("");
1778
1779 doc.add_paragraph("Warm regards,");
1780 doc.add_paragraph("");
1781 doc.add_paragraph("");
1782
1783 // Signature line
1784 doc.add_paragraph("")
1785 .border_bottom(BorderStyle::Single, 4, "4A5568");
1786 {
1787 let mut p = doc.add_paragraph("");
1788 p.add_run("Walter White").bold(true).size(12.0);
1789 }
1790 {
1791 let mut p = doc.add_paragraph("");
1792 p.add_run("Chief Executive Officer, Tensorbee")
1793 .italic(true)
1794 .size(10.0)
1795 .color("666666");
1796 }
1797 {
1798 let mut p = doc.add_paragraph("");
1799 p.add_run("walter@tensorbee.com | +1 (415) 555-0199")
1800 .size(10.0)
1801 .color("888888");
1802 }
1803
1804 doc
1805}
1806
1807// =============================================================================
1808// 7. EMPLOYMENT CONTRACT — Purple + charcoal scheme
1809// =============================================================================
1810fn generate_contract(_samples_dir: &Path) -> Document {
1811 let mut doc = Document::new();
1812
1813 // Colors: Purple #5B2C6F, Plum #8E44AD, Gray #2C3E50
1814 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1815 doc.set_margins(
1816 Length::inches(1.25),
1817 Length::inches(1.0),
1818 Length::inches(1.0),
1819 Length::inches(1.0),
1820 );
1821 doc.set_title("Employment Agreement");
1822 doc.set_author("Tensorbee HR Department");
1823 doc.set_subject("Employment Contract — Walter White");
1824 doc.set_keywords("employment, contract, agreement, Tensorbee");
1825
1826 doc.set_header("TENSORBEE — Employment Agreement");
1827 doc.set_footer("Employment Agreement — Walter White — Page");
1828
1829 // ── Title Block ──
1830 doc.add_paragraph("")
1831 .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1832 {
1833 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1834 p.add_run("EMPLOYMENT AGREEMENT")
1835 .bold(true)
1836 .size(24.0)
1837 .color("5B2C6F")
1838 .font("Georgia");
1839 }
1840 doc.add_paragraph("")
1841 .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1842 doc.add_paragraph("");
1843
1844 // ── Parties ──
1845 doc.add_paragraph(
1846 "This Employment Agreement (\"Agreement\") is entered into as of February 22, 2026 \
1847 (\"Effective Date\"), by and between:",
1848 );
1849
1850 doc.add_paragraph("");
1851
1852 {
1853 let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1854 p.add_run("EMPLOYER: ").bold(true);
1855 p.add_run(
1856 "Tensorbee, Inc., a Delaware corporation with its principal place of business at \
1857 123 Innovation Drive, Suite 400, San Francisco, CA 94105 (\"Company\")",
1858 );
1859 }
1860 doc.add_paragraph("");
1861 {
1862 let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1863 p.add_run("EMPLOYEE: ").bold(true);
1864 p.add_run(
1865 "Walter White, residing at 308 Negra Arroyo Lane, Albuquerque, NM 87104 (\"Employee\")",
1866 );
1867 }
1868
1869 doc.add_paragraph("");
1870 doc.add_paragraph("The Company and Employee are collectively referred to as the \"Parties.\"");
1871 doc.add_paragraph("");
1872
1873 // ── Article 1: Position and Duties ──
1874 doc.add_paragraph("Article 1 — Position and Duties")
1875 .style("Heading1");
1876 {
1877 let mut p = doc.add_paragraph("");
1878 p.add_run("1.1 ").bold(true);
1879 p.add_run("The Company hereby employs the Employee as ");
1880 p.add_run("Chief Executive Officer (CEO)")
1881 .bold(true)
1882 .italic(true);
1883 p.add_run(", reporting directly to the Board of Directors.");
1884 }
1885 {
1886 let mut p = doc.add_paragraph("");
1887 p.add_run("1.2 ").bold(true);
1888 p.add_run("The Employee shall devote full working time, attention, and best efforts to \
1889 the performance of duties as reasonably assigned by the Board, including but not limited to:");
1890 }
1891 doc.add_bullet_list_item(
1892 "Setting and executing the Company's strategic vision and business plan",
1893 0,
1894 );
1895 doc.add_bullet_list_item(
1896 "Overseeing all operations, engineering, and commercial activities",
1897 0,
1898 );
1899 doc.add_bullet_list_item(
1900 "Representing the Company to investors, customers, and the public",
1901 0,
1902 );
1903 doc.add_bullet_list_item("Recruiting, developing, and retaining key talent", 0);
1904
1905 {
1906 let mut p = doc.add_paragraph("");
1907 p.add_run("1.3 ").bold(true);
1908 p.add_run(
1909 "The Employee's primary work location shall be the Company's San Francisco \
1910 headquarters, with reasonable travel as required by business needs.",
1911 );
1912 }
1913
1914 // ── Article 2: Compensation ──
1915 doc.add_paragraph("Article 2 — Compensation and Benefits")
1916 .style("Heading1");
1917 {
1918 let mut p = doc.add_paragraph("");
1919 p.add_run("2.1 Base Salary. ").bold(true);
1920 p.add_run("The Company shall pay the Employee an annual base salary of ");
1921 p.add_run("$375,000.00").bold(true);
1922 p.add_run(" (Three Hundred Seventy-Five Thousand Dollars), payable in accordance with the \
1923 Company's standard payroll schedule, less applicable withholdings and deductions.");
1924 }
1925 {
1926 let mut p = doc.add_paragraph("");
1927 p.add_run("2.2 Annual Bonus. ").bold(true);
1928 p.add_run("The Employee shall be eligible for an annual performance bonus of up to ");
1929 p.add_run("40%").bold(true);
1930 p.add_run(
1931 " of base salary, based on achievement of mutually agreed performance objectives.",
1932 );
1933 }
1934 {
1935 let mut p = doc.add_paragraph("");
1936 p.add_run("2.3 Equity. ").bold(true);
1937 p.add_run("Subject to Board approval, the Employee shall receive a stock option grant of ");
1938 p.add_run("500,000 shares").bold(true);
1939 p.add_run(
1940 " of the Company's common stock, vesting over four (4) years with a one-year cliff, \
1941 at an exercise price equal to the fair market value on the date of grant.",
1942 );
1943 }
1944
1945 // Compensation summary table
1946 doc.add_paragraph("");
1947 {
1948 let mut tbl = doc
1949 .add_table(5, 2)
1950 .borders(BorderStyle::Single, 4, "5B2C6F")
1951 .width_pct(70.0)
1952 .alignment(Alignment::Center);
1953 tbl.cell(0, 0).unwrap().set_text("Compensation Element");
1954 tbl.cell(0, 0).unwrap().shading("5B2C6F");
1955 tbl.cell(0, 1).unwrap().set_text("Value");
1956 tbl.cell(0, 1).unwrap().shading("5B2C6F");
1957 let items = [
1958 ("Base Salary", "$375,000/year"),
1959 ("Target Bonus", "Up to 40% ($150,000)"),
1960 ("Equity Grant", "500,000 shares (4yr vest)"),
1961 ("Total Target Comp", "$525,000 + equity"),
1962 ];
1963 for (i, (elem, val)) in items.iter().enumerate() {
1964 tbl.cell(i + 1, 0).unwrap().set_text(elem);
1965 tbl.cell(i + 1, 1).unwrap().set_text(val);
1966 if i == 3 {
1967 tbl.cell(i + 1, 0).unwrap().shading("E8DAEF");
1968 tbl.cell(i + 1, 1).unwrap().shading("E8DAEF");
1969 }
1970 }
1971 }
1972
1973 // ── Article 3: Benefits ──
1974 doc.add_paragraph("").page_break_before(true);
1975 doc.add_paragraph("Article 3 — Benefits").style("Heading1");
1976 {
1977 let mut p = doc.add_paragraph("");
1978 p.add_run("3.1 ").bold(true);
1979 p.add_run(
1980 "The Employee shall be entitled to participate in all benefit programs \
1981 generally available to senior executives, including:",
1982 );
1983 }
1984 doc.add_bullet_list_item(
1985 "Medical, dental, and vision insurance (100% premium coverage for employee and dependents)",
1986 0,
1987 );
1988 doc.add_bullet_list_item("401(k) retirement plan with 6% company match", 0);
1989 doc.add_bullet_list_item("Life insurance and long-term disability coverage", 0);
1990 doc.add_bullet_list_item("Annual professional development allowance of $10,000", 0);
1991
1992 {
1993 let mut p = doc.add_paragraph("");
1994 p.add_run("3.2 Paid Time Off. ").bold(true);
1995 p.add_run(
1996 "The Employee shall receive 25 days of paid vacation per year, plus Company holidays, \
1997 accruing on a monthly basis.",
1998 );
1999 }
2000
2001 // ── Article 4: Term and Termination ──
2002 doc.add_paragraph("Article 4 — Term and Termination")
2003 .style("Heading1");
2004 {
2005 let mut p = doc.add_paragraph("");
2006 p.add_run("4.1 At-Will Employment. ").bold(true);
2007 p.add_run(
2008 "This Agreement is for at-will employment and may be terminated by either Party \
2009 at any time, with or without cause, subject to the notice provisions herein.",
2010 );
2011 }
2012 {
2013 let mut p = doc.add_paragraph("");
2014 p.add_run("4.2 Notice Period. ").bold(true);
2015 p.add_run("Either Party shall provide ");
2016 p.add_run("ninety (90) days").bold(true);
2017 p.add_run(" written notice of termination, or pay in lieu thereof.");
2018 }
2019 {
2020 let mut p = doc.add_paragraph("");
2021 p.add_run("4.3 Severance. ").bold(true);
2022 p.add_run("In the event of termination by the Company without Cause, the Employee shall \
2023 receive (a) twelve (12) months of base salary continuation, (b) pro-rata bonus \
2024 for the year of termination, and (c) twelve (12) months of COBRA premium coverage.");
2025 }
2026
2027 // ── Article 5: Confidentiality ──
2028 doc.add_paragraph("Article 5 — Confidentiality and IP")
2029 .style("Heading1");
2030 {
2031 let mut p = doc.add_paragraph("");
2032 p.add_run("5.1 ").bold(true);
2033 p.add_run(
2034 "The Employee agrees to maintain strict confidentiality of all proprietary \
2035 information, trade secrets, and business plans of the Company during and after \
2036 employment.",
2037 );
2038 }
2039 {
2040 let mut p = doc.add_paragraph("");
2041 p.add_run("5.2 ").bold(true);
2042 p.add_run(
2043 "All intellectual property, inventions, and works of authorship created by the \
2044 Employee during the term of employment and within the scope of duties shall be \
2045 the sole and exclusive property of the Company.",
2046 );
2047 }
2048
2049 // ── Article 6: Non-Compete ──
2050 doc.add_paragraph("Article 6 — Non-Competition")
2051 .style("Heading1");
2052 {
2053 let mut p = doc.add_paragraph("");
2054 p.add_run("6.1 ").bold(true);
2055 p.add_run("For a period of twelve (12) months following termination, the Employee agrees \
2056 not to engage in any business that directly competes with the Company's core \
2057 business of AI infrastructure and ML operations services, within the United States.");
2058 }
2059
2060 // ── Article 7: General Provisions ──
2061 doc.add_paragraph("Article 7 — General Provisions")
2062 .style("Heading1");
2063 {
2064 let mut p = doc.add_paragraph("");
2065 p.add_run("7.1 Governing Law. ").bold(true);
2066 p.add_run(
2067 "This Agreement shall be governed by and construed in accordance with the laws of \
2068 the State of California.",
2069 );
2070 }
2071 {
2072 let mut p = doc.add_paragraph("");
2073 p.add_run("7.2 Entire Agreement. ").bold(true);
2074 p.add_run(
2075 "This Agreement constitutes the entire agreement between the Parties and supersedes \
2076 all prior negotiations, representations, and agreements.",
2077 );
2078 }
2079 {
2080 let mut p = doc.add_paragraph("");
2081 p.add_run("7.3 Amendment. ").bold(true);
2082 p.add_run(
2083 "This Agreement may only be amended by a written instrument signed by both Parties.",
2084 );
2085 }
2086
2087 // ── Signature Block ──
2088 doc.add_paragraph("").page_break_before(true);
2089 doc.add_paragraph("IN WITNESS WHEREOF, the Parties have executed this Employment Agreement as of the Effective Date.")
2090 .space_after(Length::pt(24.0));
2091
2092 // Signature table
2093 {
2094 let mut tbl = doc.add_table(6, 2).width_pct(100.0);
2095 tbl.cell(0, 0).unwrap().set_text("FOR THE COMPANY:");
2096 tbl.cell(0, 0).unwrap().shading("5B2C6F");
2097 tbl.cell(0, 1).unwrap().set_text("EMPLOYEE:");
2098 tbl.cell(0, 1).unwrap().shading("5B2C6F");
2099
2100 tbl.cell(1, 0).unwrap().set_text("");
2101 tbl.cell(1, 1).unwrap().set_text("");
2102 tbl.row(1).unwrap().height(Length::pt(40.0));
2103
2104 tbl.cell(2, 0)
2105 .unwrap()
2106 .set_text("Signature: ___________________________");
2107 tbl.cell(2, 1)
2108 .unwrap()
2109 .set_text("Signature: ___________________________");
2110 tbl.cell(3, 0)
2111 .unwrap()
2112 .set_text("Name: Board Representative");
2113 tbl.cell(3, 1).unwrap().set_text("Name: Walter White");
2114 tbl.cell(4, 0)
2115 .unwrap()
2116 .set_text("Title: Chair, Board of Directors");
2117 tbl.cell(4, 1)
2118 .unwrap()
2119 .set_text("Title: Chief Executive Officer");
2120 tbl.cell(5, 0).unwrap().set_text("Date: _______________");
2121 tbl.cell(5, 1).unwrap().set_text("Date: _______________");
2122 }
2123
2124 doc
2125}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
34fn generate_feature_showcase(path: &Path) {
35 let mut doc = Document::new();
36
37 // =========================================================================
38 // PAGE SETUP & METADATA
39 // =========================================================================
40 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
41 doc.set_margins(
42 Length::inches(1.0), // top
43 Length::inches(1.0), // right
44 Length::inches(1.0), // bottom
45 Length::inches(1.0), // left
46 );
47 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
48 doc.set_gutter(Length::twips(0));
49
50 doc.set_title("rdocx Feature Showcase");
51 doc.set_author("rdocx Sample Generator");
52 doc.set_subject("Comprehensive feature demonstration");
53 doc.set_keywords("rdocx, docx, rust, sample");
54
55 // Header & Footer
56 doc.set_header("rdocx Feature Showcase");
57 doc.set_footer("Generated by rdocx — Page");
58
59 // Different first page header
60 doc.set_different_first_page(true);
61 doc.set_first_page_header("rdocx");
62 doc.set_first_page_footer("Feature Showcase — Cover Page");
63
64 // =========================================================================
65 // PAGE 1: COVER PAGE — background image, run formatting
66 // =========================================================================
67 let bg_cover = create_sample_png(612, 792, [30, 60, 120]);
68 doc.add_background_image(&bg_cover, "cover_bg.png");
69
70 doc.add_paragraph(""); // spacer
71 doc.add_paragraph(""); // spacer
72 doc.add_paragraph(""); // spacer
73
74 {
75 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
76 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
77 }
78 {
79 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
80 p.add_run("Feature Showcase")
81 .size(28.0)
82 .color("FFFFFF")
83 .italic(true);
84 }
85
86 doc.add_paragraph(""); // spacer
87
88 {
89 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
90 p.add_run("A comprehensive demonstration of every feature")
91 .size(14.0)
92 .color("CCDDFF");
93 }
94 {
95 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
96 p.add_run("provided by the rdocx Rust crate for DOCX generation.")
97 .size(14.0)
98 .color("CCDDFF");
99 }
100
101 // =========================================================================
102 // PAGE 2: TEXT FORMATTING
103 // =========================================================================
104 doc.add_paragraph("").page_break_before(true);
105
106 doc.add_paragraph("1. Text Formatting").style("Heading1");
107
108 doc.add_paragraph("This section demonstrates paragraph and run-level formatting options.");
109 doc.add_paragraph("");
110
111 // --- Paragraph alignment ---
112 doc.add_paragraph("Paragraph Alignment").style("Heading2");
113
114 doc.add_paragraph("This paragraph is left-aligned (the default).")
115 .alignment(Alignment::Left);
116 doc.add_paragraph("This paragraph is center-aligned.")
117 .alignment(Alignment::Center);
118 doc.add_paragraph("This paragraph is right-aligned.")
119 .alignment(Alignment::Right);
120 doc.add_paragraph(
121 "This paragraph is justified. To demonstrate justified text properly, it needs \
122 to be long enough to span multiple lines so the word spacing adjustment is visible \
123 across the full width of the text area on the page.",
124 )
125 .alignment(Alignment::Justify);
126
127 doc.add_paragraph("");
128
129 // --- Run formatting ---
130 doc.add_paragraph("Run Formatting").style("Heading2");
131
132 {
133 let mut p = doc.add_paragraph("");
134 p.add_run("Bold text").bold(true);
135 p.add_run(" | ");
136 p.add_run("Italic text").italic(true);
137 p.add_run(" | ");
138 p.add_run("Bold + Italic").bold(true).italic(true);
139 }
140 {
141 let mut p = doc.add_paragraph("");
142 p.add_run("Single underline").underline(true);
143 p.add_run(" | ");
144 p.add_run("Strikethrough").strike(true);
145 p.add_run(" | ");
146 p.add_run("Double strikethrough").double_strike(true);
147 }
148 {
149 let mut p = doc.add_paragraph("");
150 p.add_run("Red text").color("FF0000");
151 p.add_run(" | ");
152 p.add_run("Blue text").color("0000FF");
153 p.add_run(" | ");
154 p.add_run("Green text").color("00AA00");
155 p.add_run(" | ");
156 p.add_run("Highlighted").highlight("FFFF00");
157 }
158 {
159 let mut p = doc.add_paragraph("");
160 p.add_run("8pt small").size(8.0);
161 p.add_run(" | ");
162 p.add_run("11pt normal").size(11.0);
163 p.add_run(" | ");
164 p.add_run("16pt large").size(16.0);
165 p.add_run(" | ");
166 p.add_run("24pt extra-large").size(24.0);
167 }
168 {
169 let mut p = doc.add_paragraph("");
170 p.add_run("Arial font").font("Arial");
171 p.add_run(" | ");
172 p.add_run("Times New Roman font").font("Times New Roman");
173 p.add_run(" | ");
174 p.add_run("Courier New font").font("Courier New");
175 }
176 {
177 let mut p = doc.add_paragraph("");
178 p.add_run("Normal");
179 p.add_run(" H").size(11.0);
180 p.add_run("2").subscript();
181 p.add_run("O (subscript)").size(11.0);
182 p.add_run(" | E = mc").size(11.0);
183 p.add_run("2").superscript();
184 p.add_run(" (superscript)").size(11.0);
185 }
186 {
187 let mut p = doc.add_paragraph("");
188 p.add_run("ALL CAPS").all_caps(true);
189 p.add_run(" | ");
190 p.add_run("Small Caps").small_caps(true);
191 p.add_run(" | ");
192 p.add_run("Expanded spacing")
193 .character_spacing(Length::twips(40));
194 }
195
196 doc.add_paragraph("");
197
198 // --- Paragraph formatting ---
199 doc.add_paragraph("Paragraph Formatting").style("Heading2");
200
201 doc.add_paragraph("Paragraph with shading (light green background)")
202 .shading("E2EFDA");
203
204 doc.add_paragraph("Paragraph with bottom border")
205 .border_bottom(BorderStyle::Single, 6, "2E75B6");
206
207 doc.add_paragraph("Paragraph with all borders")
208 .border_all(BorderStyle::Single, 4, "FF0000");
209
210 doc.add_paragraph("Paragraph with 1-inch left indent and hanging indent")
211 .indent_left(Length::inches(1.0))
212 .hanging_indent(Length::inches(0.5));
213
214 doc.add_paragraph("Paragraph with first-line indent of 0.5 inches")
215 .first_line_indent(Length::inches(0.5));
216
217 doc.add_paragraph("Paragraph with extra space before (24pt) and after (12pt)")
218 .space_before(Length::pt(24.0))
219 .space_after(Length::pt(12.0));
220
221 doc.add_paragraph(
222 "Paragraph with double line spacing. This text should have extra vertical \
223 space between lines to demonstrate the line_spacing_multiple setting.",
224 )
225 .line_spacing_multiple(2.0);
226
227 doc.add_paragraph("Paragraph with keep-with-next (won't break from the next paragraph)")
228 .keep_with_next(true);
229 doc.add_paragraph("(This stays with the paragraph above.)");
230
231 // =========================================================================
232 // PAGE 3: LISTS & TAB STOPS
233 // =========================================================================
234 doc.add_paragraph("").page_break_before(true);
235
236 doc.add_paragraph("2. Lists").style("Heading1");
237
238 doc.add_paragraph("Bullet List").style("Heading2");
239
240 doc.add_bullet_list_item("First bullet item", 0);
241 doc.add_bullet_list_item("Second bullet item", 0);
242 doc.add_bullet_list_item("Nested level 1", 1);
243 doc.add_bullet_list_item("Nested level 2", 2);
244 doc.add_bullet_list_item("Back to level 1", 1);
245 doc.add_bullet_list_item("Third bullet item", 0);
246
247 doc.add_paragraph("");
248
249 doc.add_paragraph("Numbered List").style("Heading2");
250
251 doc.add_numbered_list_item("First numbered item", 0);
252 doc.add_numbered_list_item("Second numbered item", 0);
253 doc.add_numbered_list_item("Sub-item A", 1);
254 doc.add_numbered_list_item("Sub-item B", 1);
255 doc.add_numbered_list_item("Third numbered item", 0);
256
257 doc.add_paragraph("");
258
259 // --- Tab stops ---
260 doc.add_paragraph("Tab Stops").style("Heading2");
261
262 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
263 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
264 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
265 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
266 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
267
268 doc.add_paragraph("Item\t........\tPrice")
269 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
270 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
271 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
272
273 doc.add_paragraph("Widget A\t........\t$19.99")
274 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
275 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
276 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
277
278 doc.add_paragraph("Gadget B\t________\t$249.50")
279 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
280 .add_tab_stop_with_leader(
281 TabAlignment::Right,
282 Length::inches(4.0),
283 TabLeader::Underscore,
284 )
285 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
286
287 // =========================================================================
288 // PAGE 4: TABLES
289 // =========================================================================
290 doc.add_paragraph("").page_break_before(true);
291
292 doc.add_paragraph("3. Tables").style("Heading1");
293
294 // --- Basic table with borders ---
295 doc.add_paragraph("Basic Table with Borders")
296 .style("Heading2");
297
298 {
299 let mut tbl = doc.add_table(4, 3);
300 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
301
302 // Header row
303 for col in 0..3 {
304 tbl.cell(0, col).unwrap().shading("2E75B6");
305 }
306 tbl.cell(0, 0).unwrap().set_text("Name");
307 tbl.cell(0, 1).unwrap().set_text("Role");
308 tbl.cell(0, 2).unwrap().set_text("Location");
309
310 tbl.cell(1, 0).unwrap().set_text("Alice Johnson");
311 tbl.cell(1, 1).unwrap().set_text("Engineering Lead");
312 tbl.cell(1, 2).unwrap().set_text("New York");
313
314 tbl.cell(2, 0).unwrap().set_text("Bob Smith");
315 tbl.cell(2, 1).unwrap().set_text("Product Manager");
316 tbl.cell(2, 2).unwrap().set_text("San Francisco");
317
318 tbl.cell(3, 0).unwrap().set_text("Carol Davis");
319 tbl.cell(3, 1).unwrap().set_text("Designer");
320 tbl.cell(3, 2).unwrap().set_text("London");
321 }
322
323 doc.add_paragraph("");
324
325 // --- Table with cell merging ---
326 doc.add_paragraph("Table with Cell Merging & Vertical Alignment")
327 .style("Heading2");
328
329 {
330 let mut tbl = doc.add_table(4, 4);
331 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
332 tbl = tbl.width_pct(100.0);
333
334 // Header spanning all columns
335 tbl.cell(0, 0).unwrap().set_text("Quarterly Revenue Report");
336 tbl.cell(0, 0).unwrap().shading("1F4E79");
337 tbl.cell(0, 0).unwrap().grid_span(4);
338
339 // Sub-header
340 tbl.cell(1, 0).unwrap().set_text("Region");
341 tbl.cell(1, 0).unwrap().shading("D6E4F0");
342 tbl.cell(1, 1).unwrap().set_text("Q1");
343 tbl.cell(1, 1).unwrap().shading("D6E4F0");
344 tbl.cell(1, 2).unwrap().set_text("Q2");
345 tbl.cell(1, 2).unwrap().shading("D6E4F0");
346 tbl.cell(1, 3).unwrap().set_text("Total");
347 tbl.cell(1, 3).unwrap().shading("D6E4F0");
348
349 // Data
350 tbl.cell(2, 0).unwrap().set_text("North America");
351 tbl.cell(2, 1).unwrap().set_text("$2.4M");
352 tbl.cell(2, 2).unwrap().set_text("$2.7M");
353 tbl.cell(2, 3).unwrap().set_text("$5.1M");
354
355 tbl.cell(3, 0).unwrap().set_text("Europe");
356 tbl.cell(3, 1).unwrap().set_text("$1.8M");
357 tbl.cell(3, 2).unwrap().set_text("$2.0M");
358 tbl.cell(3, 3).unwrap().set_text("$3.8M");
359
360 // Vertical alignment on data cells
361 tbl.cell(2, 3)
362 .unwrap()
363 .vertical_alignment(VerticalAlignment::Center);
364 tbl.cell(3, 3)
365 .unwrap()
366 .vertical_alignment(VerticalAlignment::Bottom);
367 }
368
369 doc.add_paragraph("");
370
371 // --- Table with vertical merge ---
372 doc.add_paragraph("Table with Vertical Merge")
373 .style("Heading2");
374
375 {
376 let mut tbl = doc.add_table(4, 3);
377 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
378
379 tbl.cell(0, 0).unwrap().set_text("Category");
380 tbl.cell(0, 0).unwrap().shading("E2EFDA");
381 tbl.cell(0, 1).unwrap().set_text("Item");
382 tbl.cell(0, 1).unwrap().shading("E2EFDA");
383 tbl.cell(0, 2).unwrap().set_text("Price");
384 tbl.cell(0, 2).unwrap().shading("E2EFDA");
385
386 // "Hardware" spans rows 1-2
387 tbl.cell(1, 0).unwrap().set_text("Hardware");
388 tbl.cell(1, 0).unwrap().v_merge_restart();
389 tbl.cell(1, 1).unwrap().set_text("Laptop");
390 tbl.cell(1, 2).unwrap().set_text("$1,200");
391
392 tbl.cell(2, 0).unwrap().v_merge_continue();
393 tbl.cell(2, 1).unwrap().set_text("Monitor");
394 tbl.cell(2, 2).unwrap().set_text("$450");
395
396 // "Software" on row 3
397 tbl.cell(3, 0).unwrap().set_text("Software");
398 tbl.cell(3, 1).unwrap().set_text("IDE License");
399 tbl.cell(3, 2).unwrap().set_text("$200/yr");
400 }
401
402 doc.add_paragraph("");
403
404 // --- Nested table ---
405 doc.add_paragraph("Nested Table").style("Heading2");
406
407 {
408 let mut tbl = doc.add_table(2, 2);
409 tbl = tbl.borders(BorderStyle::Single, 6, "2E75B6");
410
411 tbl.cell(0, 0).unwrap().set_text("Outer Cell (0,0)");
412 tbl.cell(0, 1).unwrap().set_text("Outer Cell (0,1)");
413 tbl.cell(1, 0).unwrap().set_text("Outer Cell (1,0)");
414
415 // Nested table inside cell (1,1)
416 {
417 let mut cell = tbl.cell(1, 1).unwrap();
418 cell.set_text("Contains nested table:");
419 let mut nested = cell.add_table(2, 2);
420 nested = nested.borders(BorderStyle::Single, 2, "FF6600");
421 nested.cell(0, 0).unwrap().set_text("Inner A");
422 nested.cell(0, 1).unwrap().set_text("Inner B");
423 nested.cell(1, 0).unwrap().set_text("Inner C");
424 nested.cell(1, 1).unwrap().set_text("Inner D");
425 }
426 }
427
428 // =========================================================================
429 // PAGE 5: IMAGES
430 // =========================================================================
431 doc.add_paragraph("").page_break_before(true);
432
433 doc.add_paragraph("4. Images").style("Heading1");
434
435 doc.add_paragraph("Inline Image").style("Heading2");
436
437 doc.add_paragraph("Below is an inline image (200x50 pixels, blue gradient):");
438 let inline_img = create_sample_png(200, 50, [0, 80, 200]);
439 doc.add_picture(
440 &inline_img,
441 "inline_chart.png",
442 Length::inches(3.0),
443 Length::inches(0.75),
444 );
445
446 doc.add_paragraph("");
447
448 doc.add_paragraph("Header Image").style("Heading2");
449
450 // Replace the text-only header with an image header
451 let header_img = create_sample_png(400, 40, [40, 40, 40]);
452 doc.set_header_image(
453 &header_img,
454 "header_logo.png",
455 Length::inches(2.0),
456 Length::inches(0.2),
457 );
458
459 doc.add_paragraph(
460 "The document header has been replaced with an inline image. \
461 Check the header area at the top of this page.",
462 );
463
464 doc.add_paragraph("");
465 doc.add_paragraph(
466 "Note: The cover page uses a full-page background image behind the text, \
467 demonstrated on page 1 via add_background_image().",
468 );
469
470 // =========================================================================
471 // PAGE 6: CONTENT MANIPULATION — placeholder replacement, insertion
472 // =========================================================================
473 doc.add_paragraph("").page_break_before(true);
474
475 doc.add_paragraph("5. Content Manipulation")
476 .style("Heading1");
477
478 // --- Placeholder replacement ---
479 doc.add_paragraph("Placeholder Replacement")
480 .style("Heading2");
481
482 doc.add_paragraph(
483 "Before replacement, this document contained {{customer}} and {{date}} placeholders.",
484 );
485
486 {
487 let mut p = doc.add_paragraph("");
488 p.add_run("Customer: ").bold(true);
489 p.add_run("{{customer}}");
490 }
491 {
492 let mut p = doc.add_paragraph("");
493 p.add_run("Date: ").bold(true);
494 p.add_run("{{date}}");
495 }
496 doc.add_paragraph("Reference: {{ref_number}}");
497
498 // Table with placeholders
499 {
500 let mut tbl = doc.add_table(3, 2);
501 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
502 tbl.cell(0, 0).unwrap().set_text("Field");
503 tbl.cell(0, 0).unwrap().shading("D6E4F0");
504 tbl.cell(0, 1).unwrap().set_text("Value");
505 tbl.cell(0, 1).unwrap().shading("D6E4F0");
506 tbl.cell(1, 0).unwrap().set_text("Project");
507 tbl.cell(1, 1).unwrap().set_text("{{project}}");
508 tbl.cell(2, 0).unwrap().set_text("Status");
509 tbl.cell(2, 1).unwrap().set_text("{{status}}");
510 }
511
512 // Perform replacements
513 let mut replacements = HashMap::new();
514 replacements.insert("{{customer}}", "Acme Corporation");
515 replacements.insert("{{date}}", "February 22, 2026");
516 replacements.insert("{{ref_number}}", "REF-2026-001");
517 replacements.insert("{{project}}", "Infrastructure Upgrade");
518 replacements.insert("{{status}}", "In Progress");
519 let replace_count = doc.replace_all(&replacements);
520
521 doc.add_paragraph("");
522 doc.add_paragraph(&format!(
523 "(Replaced {} placeholders above — in body text and table cells)",
524 replace_count
525 ));
526
527 doc.add_paragraph("");
528
529 // --- Content insertion ---
530 doc.add_paragraph("Content Insertion").style("Heading2");
531
532 doc.add_paragraph("Section A: First section of content.");
533 doc.add_paragraph("Section C: Third section of content.");
534
535 // Insert "Section B" between A and C
536 if let Some(idx) = doc.find_content_index("Section C") {
537 doc.insert_paragraph(
538 idx,
539 "Section B: Inserted between A and C using find_content_index().",
540 );
541 }
542
543 doc.add_paragraph("");
544 doc.add_paragraph(
545 "The paragraph above ('Section B') was inserted at a specific position \
546 using find_content_index() + insert_paragraph().",
547 );
548
549 // =========================================================================
550 // PAGE 7: LANDSCAPE — section break, wide table
551 // =========================================================================
552 doc.add_paragraph("").section_break(SectionBreak::NextPage);
553
554 doc.add_paragraph("6. Mixed Page Orientation")
555 .style("Heading1");
556
557 doc.add_paragraph(
558 "This page is in LANDSCAPE orientation. It was created using a section break \
559 followed by section_landscape(). This is useful for wide tables or charts.",
560 );
561
562 doc.add_paragraph("");
563
564 // Wide table for landscape
565 {
566 let mut tbl = doc.add_table(4, 7);
567 tbl = tbl.borders(BorderStyle::Single, 4, "2E75B6");
568
569 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
570 for (col, h) in headers.iter().enumerate() {
571 tbl.cell(0, col).unwrap().set_text(h);
572 tbl.cell(0, col).unwrap().shading("2E75B6");
573 }
574
575 let data = [
576 [
577 "North America",
578 "$1.2M",
579 "$1.3M",
580 "$1.4M",
581 "$1.5M",
582 "$1.6M",
583 "$7.0M",
584 ],
585 [
586 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
587 ],
588 [
589 "Asia Pacific",
590 "$0.5M",
591 "$0.6M",
592 "$0.7M",
593 "$0.7M",
594 "$0.8M",
595 "$3.3M",
596 ],
597 ];
598 for (row_idx, row_data) in data.iter().enumerate() {
599 for (col, val) in row_data.iter().enumerate() {
600 tbl.cell(row_idx + 1, col).unwrap().set_text(val);
601 }
602 }
603 }
604
605 // End landscape, return to portrait
606 doc.add_paragraph("")
607 .section_break(SectionBreak::NextPage)
608 .section_landscape();
609
610 // =========================================================================
611 // PAGE 8: BACK TO PORTRAIT — styles, final notes
612 // =========================================================================
613 doc.add_paragraph("7. Custom Styles & Summary")
614 .style("Heading1");
615
616 doc.add_paragraph(
617 "This final page is back in portrait orientation after a section break. \
618 The document has demonstrated:",
619 );
620
621 doc.add_bullet_list_item(
622 "Page setup: size, margins, header/footer distance, gutter",
623 0,
624 );
625 doc.add_bullet_list_item("Document metadata: title, author, subject, keywords", 0);
626 doc.add_bullet_list_item("Headers and footers: text, images, different first page", 0);
627 doc.add_bullet_list_item("Background images: full-page behind text", 0);
628 doc.add_bullet_list_item(
629 "Text formatting: bold, italic, underline, strike, color, size, font",
630 0,
631 );
632 doc.add_bullet_list_item(
633 "Advanced run formatting: superscript, subscript, caps, spacing",
634 0,
635 );
636 doc.add_bullet_list_item(
637 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
638 0,
639 );
640 doc.add_bullet_list_item("Bullet and numbered lists with nesting levels", 0);
641 doc.add_bullet_list_item("Tab stops with dot/underscore leaders", 0);
642 doc.add_bullet_list_item(
643 "Tables: borders, shading, column spans, row spans, nesting",
644 0,
645 );
646 doc.add_bullet_list_item("Vertical alignment in table cells", 0);
647 doc.add_bullet_list_item("Inline images", 0);
648 doc.add_bullet_list_item("Placeholder replacement in body and table cells", 0);
649 doc.add_bullet_list_item("Content insertion at specific positions", 0);
650 doc.add_bullet_list_item(
651 "Section breaks with mixed portrait/landscape orientation",
652 0,
653 );
654
655 doc.add_paragraph("");
656 doc.add_paragraph("All features above were built entirely from scratch using the rdocx API.")
657 .alignment(Alignment::Center)
658 .shading("E2EFDA")
659 .border_all(BorderStyle::Single, 2, "00AA00");
660
661 doc.save(path).unwrap();
662}86fn generate_feature_showcase(_samples_dir: &Path) -> Document {
87 let mut doc = Document::new();
88
89 // ── Page Setup & Metadata ──
90 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
91 doc.set_margins(
92 Length::inches(1.0),
93 Length::inches(1.0),
94 Length::inches(1.0),
95 Length::inches(1.0),
96 );
97 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
98 doc.set_gutter(Length::twips(0));
99 doc.set_title("rdocx Feature Showcase");
100 doc.set_author("rdocx Sample Generator");
101 doc.set_subject("Comprehensive feature demonstration");
102 doc.set_keywords("rdocx, docx, rust, sample, showcase");
103
104 // Headers & Footers with different first page
105 doc.set_different_first_page(true);
106 doc.set_first_page_header("rdocx");
107 doc.set_first_page_footer("Feature Showcase — Cover Page");
108 doc.set_header("rdocx Feature Showcase");
109 doc.set_footer("Generated by rdocx");
110
111 // ── COVER PAGE ──
112 let bg = create_sample_png(612, 792, [20, 45, 90]);
113 doc.add_background_image(&bg, "cover_bg.png");
114
115 for _ in 0..3 {
116 doc.add_paragraph("");
117 }
118 {
119 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
120 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
121 }
122 {
123 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
124 p.add_run("Complete Feature Showcase")
125 .size(28.0)
126 .color("FFFFFF")
127 .italic(true);
128 }
129 doc.add_paragraph("");
130 {
131 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
132 p.add_run("Every feature of the rdocx Rust library")
133 .size(13.0)
134 .color("B0C4DE");
135 }
136 {
137 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
138 p.add_run("demonstrated in a single document.")
139 .size(13.0)
140 .color("B0C4DE");
141 }
142
143 // ── TABLE OF CONTENTS ──
144 doc.add_paragraph("").page_break_before(true);
145 doc.insert_toc(doc.content_count(), 3);
146
147 // ── SECTION 1: TEXT FORMATTING ──
148 doc.add_paragraph("").page_break_before(true);
149 doc.add_paragraph("1. Text Formatting").style("Heading1");
150
151 // Paragraph Alignment
152 doc.add_paragraph("1.1 Paragraph Alignment")
153 .style("Heading2");
154 doc.add_paragraph("Left-aligned paragraph (default).")
155 .alignment(Alignment::Left);
156 doc.add_paragraph("Center-aligned paragraph.")
157 .alignment(Alignment::Center);
158 doc.add_paragraph("Right-aligned paragraph.")
159 .alignment(Alignment::Right);
160 doc.add_paragraph(
161 "Justified paragraph — this text is long enough to demonstrate how justified alignment \
162 distributes extra space across word gaps so lines fill the full width of the text area.",
163 )
164 .alignment(Alignment::Justify);
165
166 doc.add_paragraph("");
167
168 // Run Formatting
169 doc.add_paragraph("1.2 Run Formatting").style("Heading2");
170 {
171 let mut p = doc.add_paragraph("");
172 p.add_run("Bold").bold(true);
173 p.add_run(" | ");
174 p.add_run("Italic").italic(true);
175 p.add_run(" | ");
176 p.add_run("Bold Italic").bold(true).italic(true);
177 p.add_run(" | ");
178 p.add_run("Underline").underline(true);
179 p.add_run(" | ");
180 p.add_run("Strikethrough").strike(true);
181 p.add_run(" | ");
182 p.add_run("Double Strike").double_strike(true);
183 }
184 {
185 let mut p = doc.add_paragraph("");
186 p.add_run("Red").color("FF0000");
187 p.add_run(" | ");
188 p.add_run("Blue").color("0000FF");
189 p.add_run(" | ");
190 p.add_run("Green").color("00AA00");
191 p.add_run(" | ");
192 p.add_run("Highlighted").highlight("FFFF00");
193 }
194 {
195 let mut p = doc.add_paragraph("");
196 p.add_run("8pt").size(8.0);
197 p.add_run(" | ");
198 p.add_run("11pt").size(11.0);
199 p.add_run(" | ");
200 p.add_run("16pt").size(16.0);
201 p.add_run(" | ");
202 p.add_run("24pt").size(24.0);
203 }
204 {
205 let mut p = doc.add_paragraph("");
206 p.add_run("Arial").font("Arial");
207 p.add_run(" | ");
208 p.add_run("Times New Roman").font("Times New Roman");
209 p.add_run(" | ");
210 p.add_run("Courier New").font("Courier New");
211 }
212 {
213 let mut p = doc.add_paragraph("");
214 p.add_run("H");
215 p.add_run("2").subscript();
216 p.add_run("O (subscript) | E=mc");
217 p.add_run("2").superscript();
218 p.add_run(" (superscript)");
219 }
220 {
221 let mut p = doc.add_paragraph("");
222 p.add_run("ALL CAPS").all_caps(true);
223 p.add_run(" | ");
224 p.add_run("Small Caps").small_caps(true);
225 p.add_run(" | ");
226 p.add_run("Expanded").character_spacing(Length::twips(40));
227 p.add_run(" | ");
228 p.add_run("Hidden text (not visible)").hidden(true);
229 }
230
231 doc.add_paragraph("");
232
233 // Underline Styles
234 doc.add_paragraph("1.3 Underline Styles").style("Heading2");
235 {
236 let mut p = doc.add_paragraph("");
237 p.add_run("Single ")
238 .underline_style(rdocx::UnderlineStyle::Single);
239 p.add_run("Double ")
240 .underline_style(rdocx::UnderlineStyle::Double);
241 p.add_run("Thick ")
242 .underline_style(rdocx::UnderlineStyle::Thick);
243 p.add_run("Dotted ")
244 .underline_style(rdocx::UnderlineStyle::Dotted);
245 p.add_run("Dash ")
246 .underline_style(rdocx::UnderlineStyle::Dash);
247 p.add_run("Wave ")
248 .underline_style(rdocx::UnderlineStyle::Wave);
249 }
250
251 // ── SECTION 2: PARAGRAPH FORMATTING ──
252 doc.add_paragraph("").page_break_before(true);
253 doc.add_paragraph("2. Paragraph Formatting")
254 .style("Heading1");
255
256 doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
257 doc.add_paragraph("Paragraph with light green shading")
258 .shading("E2EFDA");
259 doc.add_paragraph("Paragraph with bottom border")
260 .border_bottom(BorderStyle::Single, 6, "2E75B6");
261 doc.add_paragraph("Paragraph with all borders (red)")
262 .border_all(BorderStyle::Single, 4, "FF0000");
263
264 doc.add_paragraph("2.2 Indentation").style("Heading2");
265 doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
266 .indent_left(Length::inches(1.0))
267 .hanging_indent(Length::inches(0.5));
268 doc.add_paragraph("0.5-inch first-line indent on this paragraph")
269 .first_line_indent(Length::inches(0.5));
270 doc.add_paragraph("Both left and right indent (0.75 inches each)")
271 .indent_left(Length::inches(0.75))
272 .indent_right(Length::inches(0.75));
273
274 doc.add_paragraph("2.3 Spacing & Line Height")
275 .style("Heading2");
276 doc.add_paragraph("Extra space before (24pt) and after (12pt)")
277 .space_before(Length::pt(24.0))
278 .space_after(Length::pt(12.0));
279 doc.add_paragraph(
280 "Double line spacing paragraph. This text should have extra vertical space between \
281 lines to demonstrate line_spacing_multiple(2.0).",
282 )
283 .line_spacing_multiple(2.0);
284 doc.add_paragraph("Exact 20pt line spacing (fixed height).")
285 .line_spacing(20.0);
286
287 doc.add_paragraph("2.4 Pagination Controls")
288 .style("Heading2");
289 doc.add_paragraph("keep_with_next — this paragraph stays with the next")
290 .keep_with_next(true);
291 doc.add_paragraph("(This paragraph was kept with the one above.)");
292 doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
293 .keep_together(true);
294 doc.add_paragraph("widow_control — prevents widow/orphan lines")
295 .widow_control(true);
296
297 // ── SECTION 3: LISTS ──
298 doc.add_paragraph("").page_break_before(true);
299 doc.add_paragraph("3. Lists").style("Heading1");
300
301 doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
302 doc.add_bullet_list_item("First item", 0);
303 doc.add_bullet_list_item("Second item", 0);
304 doc.add_bullet_list_item("Nested level 1", 1);
305 doc.add_bullet_list_item("Nested level 2", 2);
306 doc.add_bullet_list_item("Back to level 1", 1);
307 doc.add_bullet_list_item("Third item", 0);
308
309 doc.add_paragraph("");
310
311 doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
312 doc.add_numbered_list_item("First numbered item", 0);
313 doc.add_numbered_list_item("Second numbered item", 0);
314 doc.add_numbered_list_item("Sub-item A", 1);
315 doc.add_numbered_list_item("Sub-item B", 1);
316 doc.add_numbered_list_item("Third numbered item", 0);
317
318 // ── SECTION 4: TAB STOPS ──
319 doc.add_paragraph("");
320 doc.add_paragraph("4. Tab Stops").style("Heading1");
321
322 doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
323 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
324 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
325 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
326 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
327 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
328
329 doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
330 doc.add_paragraph("Item\t\tPrice")
331 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
332 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
333 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
334 doc.add_paragraph("Widget\t\t$19.99")
335 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
336 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
337 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
338 doc.add_paragraph("Gadget\t\t$249.50")
339 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
340 .add_tab_stop_with_leader(
341 TabAlignment::Right,
342 Length::inches(4.0),
343 TabLeader::Underscore,
344 )
345 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
346
347 // ── SECTION 5: TABLES ──
348 doc.add_paragraph("").page_break_before(true);
349 doc.add_paragraph("5. Tables").style("Heading1");
350
351 doc.add_paragraph("5.1 Basic Table").style("Heading2");
352 {
353 let mut tbl = doc
354 .add_table(4, 3)
355 .borders(BorderStyle::Single, 4, "000000");
356 for col in 0..3 {
357 tbl.cell(0, col).unwrap().shading("2E75B6");
358 }
359 tbl.cell(0, 0).unwrap().set_text("Name");
360 tbl.cell(0, 1).unwrap().set_text("Role");
361 tbl.cell(0, 2).unwrap().set_text("Location");
362 tbl.cell(1, 0).unwrap().set_text("Walter White");
363 tbl.cell(1, 1).unwrap().set_text("CEO");
364 tbl.cell(1, 2).unwrap().set_text("Albuquerque");
365 tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
366 tbl.cell(2, 1).unwrap().set_text("CTO");
367 tbl.cell(2, 2).unwrap().set_text("Remote");
368 tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
369 tbl.cell(3, 1).unwrap().set_text("Security");
370 tbl.cell(3, 2).unwrap().set_text("Washington");
371 }
372
373 doc.add_paragraph("");
374
375 doc.add_paragraph("5.2 Column Span & Cell Shading")
376 .style("Heading2");
377 {
378 let mut tbl = doc
379 .add_table(3, 4)
380 .borders(BorderStyle::Single, 4, "000000")
381 .width_pct(100.0);
382 tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
383 tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
384 tbl.cell(1, 0).unwrap().set_text("Region");
385 tbl.cell(1, 0).unwrap().shading("D6E4F0");
386 tbl.cell(1, 1).unwrap().set_text("Q1");
387 tbl.cell(1, 1).unwrap().shading("D6E4F0");
388 tbl.cell(1, 2).unwrap().set_text("Q2");
389 tbl.cell(1, 2).unwrap().shading("D6E4F0");
390 tbl.cell(1, 3).unwrap().set_text("Total");
391 tbl.cell(1, 3).unwrap().shading("D6E4F0");
392 tbl.cell(2, 0).unwrap().set_text("Americas");
393 tbl.cell(2, 1).unwrap().set_text("$2.4M");
394 tbl.cell(2, 2).unwrap().set_text("$2.7M");
395 tbl.cell(2, 3).unwrap().set_text("$5.1M");
396 }
397
398 doc.add_paragraph("");
399
400 doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
401 {
402 let mut tbl = doc
403 .add_table(4, 3)
404 .borders(BorderStyle::Single, 4, "000000");
405 tbl.cell(0, 0).unwrap().set_text("Category");
406 tbl.cell(0, 0).unwrap().shading("E2EFDA");
407 tbl.cell(0, 1).unwrap().set_text("Item");
408 tbl.cell(0, 1).unwrap().shading("E2EFDA");
409 tbl.cell(0, 2).unwrap().set_text("Price");
410 tbl.cell(0, 2).unwrap().shading("E2EFDA");
411 tbl.cell(1, 0).unwrap().set_text("Hardware");
412 tbl.cell(1, 0).unwrap().v_merge_restart();
413 tbl.cell(1, 1).unwrap().set_text("Laptop");
414 tbl.cell(1, 2).unwrap().set_text("$1,200");
415 tbl.cell(2, 0).unwrap().v_merge_continue();
416 tbl.cell(2, 1).unwrap().set_text("Monitor");
417 tbl.cell(2, 2).unwrap().set_text("$450");
418 tbl.cell(3, 0).unwrap().set_text("Software");
419 tbl.cell(3, 1).unwrap().set_text("IDE License");
420 tbl.cell(3, 2).unwrap().set_text("$200/yr");
421 }
422
423 doc.add_paragraph("");
424
425 doc.add_paragraph("5.4 Nested Table").style("Heading2");
426 {
427 let mut tbl = doc
428 .add_table(2, 2)
429 .borders(BorderStyle::Single, 6, "2E75B6");
430 tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
431 tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
432 tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
433 {
434 let mut cell = tbl.cell(1, 1).unwrap();
435 cell.set_text("Nested table below:");
436 let mut nested = cell
437 .add_table(2, 2)
438 .borders(BorderStyle::Single, 2, "FF6600");
439 nested.cell(0, 0).unwrap().set_text("A");
440 nested.cell(0, 1).unwrap().set_text("B");
441 nested.cell(1, 0).unwrap().set_text("C");
442 nested.cell(1, 1).unwrap().set_text("D");
443 }
444 }
445
446 doc.add_paragraph("");
447
448 doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
449 .style("Heading2");
450 {
451 let mut tbl = doc
452 .add_table(2, 3)
453 .borders(BorderStyle::Single, 4, "666666");
454 tbl.row(0).unwrap().height(Length::pt(50.0)).header();
455 tbl.cell(0, 0).unwrap().set_text("Top");
456 tbl.cell(0, 0)
457 .unwrap()
458 .vertical_alignment(VerticalAlignment::Top)
459 .shading("FFF2CC");
460 tbl.cell(0, 1).unwrap().set_text("Center");
461 tbl.cell(0, 1)
462 .unwrap()
463 .vertical_alignment(VerticalAlignment::Center)
464 .shading("D9E2F3");
465 tbl.cell(0, 2).unwrap().set_text("Bottom");
466 tbl.cell(0, 2)
467 .unwrap()
468 .vertical_alignment(VerticalAlignment::Bottom)
469 .shading("E2EFDA");
470 tbl.row(1).unwrap().cant_split();
471 tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
472 tbl.cell(1, 0).unwrap().no_wrap();
473 tbl.cell(1, 1).unwrap().set_text("Fixed width");
474 tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
475 tbl.cell(1, 2).unwrap().set_text("Normal");
476 }
477
478 // ── SECTION 6: IMAGES ──
479 doc.add_paragraph("").page_break_before(true);
480 doc.add_paragraph("6. Images").style("Heading1");
481
482 doc.add_paragraph("6.1 Inline Image").style("Heading2");
483 doc.add_paragraph("A blue gradient image below:");
484 let img = create_sample_png(200, 50, [0, 80, 200]);
485 doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
486
487 doc.add_paragraph("");
488 doc.add_paragraph("6.2 Header Image").style("Heading2");
489 let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
490 doc.set_header_image(
491 &hdr_img,
492 "header_logo.png",
493 Length::inches(2.0),
494 Length::inches(0.2),
495 );
496 doc.add_paragraph("The header now contains an inline image (check the top of this page).");
497
498 doc.add_paragraph("");
499 doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
500
501 // ── SECTION 7: CONTENT MANIPULATION ──
502 doc.add_paragraph("").page_break_before(true);
503 doc.add_paragraph("7. Content Manipulation")
504 .style("Heading1");
505
506 doc.add_paragraph("7.1 Placeholder Replacement")
507 .style("Heading2");
508 doc.add_paragraph("Customer: {{customer}}");
509 doc.add_paragraph("Date: {{date}}");
510 doc.add_paragraph("Reference: {{ref_number}}");
511 {
512 let mut tbl = doc
513 .add_table(2, 2)
514 .borders(BorderStyle::Single, 4, "000000");
515 tbl.cell(0, 0).unwrap().set_text("Project");
516 tbl.cell(0, 0).unwrap().shading("D6E4F0");
517 tbl.cell(0, 1).unwrap().set_text("{{project}}");
518 tbl.cell(1, 0).unwrap().set_text("Status");
519 tbl.cell(1, 0).unwrap().shading("D6E4F0");
520 tbl.cell(1, 1).unwrap().set_text("{{status}}");
521 }
522
523 let mut replacements = HashMap::new();
524 replacements.insert("{{customer}}", "Tensorbee Inc.");
525 replacements.insert("{{date}}", "February 22, 2026");
526 replacements.insert("{{ref_number}}", "TB-2026-001");
527 replacements.insert("{{project}}", "Infrastructure Upgrade");
528 replacements.insert("{{status}}", "In Progress");
529 let n = doc.replace_all(&replacements);
530 doc.add_paragraph(&format!("({n} placeholders replaced above)"));
531
532 doc.add_paragraph("");
533
534 doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
535 doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
536 let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
537 doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
538
539 doc.add_paragraph("");
540
541 doc.add_paragraph("7.3 Content Insertion").style("Heading2");
542 doc.add_paragraph("Section A: First content.");
543 doc.add_paragraph("Section C: Third content.");
544 if let Some(idx) = doc.find_content_index("Section C") {
545 doc.insert_paragraph(
546 idx,
547 "Section B: Inserted between A and C via find_content_index().",
548 );
549 }
550
551 // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
552 doc.add_paragraph("").section_break(SectionBreak::NextPage);
553 doc.add_paragraph("8. Section Breaks & Orientation")
554 .style("Heading1");
555 doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
556
557 {
558 let mut tbl = doc
559 .add_table(3, 7)
560 .borders(BorderStyle::Single, 4, "2E75B6");
561 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
562 for (c, h) in headers.iter().enumerate() {
563 tbl.cell(0, c).unwrap().set_text(h);
564 tbl.cell(0, c).unwrap().shading("2E75B6");
565 }
566 let data = [
567 [
568 "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
569 ],
570 [
571 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
572 ],
573 ];
574 for (r, row) in data.iter().enumerate() {
575 for (c, v) in row.iter().enumerate() {
576 tbl.cell(r + 1, c).unwrap().set_text(v);
577 }
578 }
579 }
580
581 doc.add_paragraph("")
582 .section_break(SectionBreak::NextPage)
583 .section_landscape();
584
585 // ── SECTION 9: CUSTOM STYLES ──
586 doc.add_paragraph("9. Custom Styles").style("Heading1");
587 doc.add_style(
588 StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
589 .based_on("Normal")
590 .paragraph_properties({
591 let mut ppr = rdocx_oxml::properties::CT_PPr::default();
592 ppr.shading = Some(rdocx_oxml::properties::CT_Shd {
593 val: "clear".to_string(),
594 color: None,
595 fill: Some("FFF2CC".to_string()),
596 });
597 ppr
598 })
599 .run_properties({
600 let mut rpr = rdocx_oxml::properties::CT_RPr::default();
601 rpr.bold = Some(true);
602 rpr.color = Some("C45911".to_string());
603 rpr
604 }),
605 );
606 doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
607 .style("CustomHighlight");
608
609 doc.add_paragraph("");
610
611 // ── SECTION 10: DOCUMENT INTELLIGENCE ──
612 doc.add_paragraph("10. Document Intelligence API")
613 .style("Heading1");
614
615 let wc = doc.word_count();
616 let hc = doc.headings().len();
617 let ic = doc.images().len();
618 let lc = doc.links().len();
619 doc.add_paragraph(&format!("Word count: {wc}"));
620 doc.add_paragraph(&format!("Heading count: {hc}"));
621 doc.add_paragraph(&format!("Image count: {ic}"));
622 doc.add_paragraph(&format!("Link count: {lc}"));
623
624 let outline = doc.document_outline();
625 doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
626
627 let issues = doc.audit_accessibility();
628 doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
629 for issue in &issues {
630 doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
631 }
632
633 // ── SECTION 11: DOCUMENT MERGING ──
634 doc.add_paragraph("");
635 doc.add_paragraph("11. Document Merging").style("Heading1");
636 {
637 let mut other = Document::new();
638 other.add_paragraph("This paragraph was merged from another document using append().");
639 doc.append(&other);
640 }
641
642 doc.add_paragraph("");
643
644 // ── SUMMARY ──
645 doc.add_paragraph("Summary of Demonstrated Features")
646 .style("Heading1");
647 let features = [
648 "Page setup: size, margins, header/footer distance, gutter",
649 "Document metadata: title, author, subject, keywords",
650 "Headers and footers: text, images, different first page",
651 "Background images (full-page behind text)",
652 "Table of Contents generation with bookmarks",
653 "Text formatting: bold, italic, underline styles, strike, color, size, font",
654 "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
655 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
656 "Pagination controls: keep-with-next, keep-together, widow control",
657 "Bullet and numbered lists with nesting",
658 "Tab stops with dot/underscore/hyphen leaders",
659 "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
660 "Row properties: header rows, exact height, cant-split",
661 "Cell properties: width, no-wrap, vertical alignment",
662 "Inline images and header images",
663 "Placeholder replacement (single and batch)",
664 "Regex-based find and replace",
665 "Content insertion at specific positions",
666 "Section breaks with mixed portrait/landscape",
667 "Custom paragraph and character styles",
668 "Document intelligence: word count, headings, outline, images, links",
669 "Accessibility audit",
670 "Document merging (append)",
671 "Export: DOCX, PDF, HTML, Markdown",
672 ];
673 for f in &features {
674 doc.add_bullet_list_item(f, 0);
675 }
676 doc.add_paragraph("");
677 doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
678 .alignment(Alignment::Center)
679 .shading("E2EFDA")
680 .border_all(BorderStyle::Single, 2, "00AA00");
681
682 doc
683}
684
685// =============================================================================
686// 2. PROPOSAL DOCUMENT — Deep navy + gold scheme
687// =============================================================================
688fn generate_proposal(_samples_dir: &Path) -> Document {
689 let mut doc = Document::new();
690
691 // Colors: Navy #1B2A4A, Gold #C5922E, Light #F4F1EB
692 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
693 doc.set_margins(
694 Length::inches(1.0),
695 Length::inches(1.0),
696 Length::inches(1.0),
697 Length::inches(1.0),
698 );
699 doc.set_title("AI Platform Modernization Proposal");
700 doc.set_author("Walter White");
701 doc.set_subject("Technology Proposal");
702 doc.set_keywords("proposal, AI, modernization, Tensorbee");
703
704 // Banner header
705 let logo_img = create_logo_png(220, 48);
706 let banner = build_header_banner_xml(
707 "rId1",
708 &BannerOpts {
709 bg_color: "1B2A4A",
710 banner_width: 7772400,
711 banner_height: 914400,
712 logo_width: 2011680,
713 logo_height: 438912,
714 logo_x_offset: 295125,
715 logo_y_offset: 237744,
716 },
717 );
718 doc.set_raw_header_with_images(
719 banner,
720 &[("rId1", &logo_img, "logo.png")],
721 rdocx_oxml::header_footer::HdrFtrType::Default,
722 );
723 doc.set_different_first_page(true);
724 let cover_banner = build_header_banner_xml(
725 "rId1",
726 &BannerOpts {
727 bg_color: "C5922E",
728 banner_width: 7772400,
729 banner_height: 914400,
730 logo_width: 2011680,
731 logo_height: 438912,
732 logo_x_offset: 295125,
733 logo_y_offset: 237744,
734 },
735 );
736 doc.set_raw_header_with_images(
737 cover_banner,
738 &[("rId1", &logo_img, "logo.png")],
739 rdocx_oxml::header_footer::HdrFtrType::First,
740 );
741 doc.set_margins(
742 Length::twips(2292),
743 Length::twips(1440),
744 Length::twips(1440),
745 Length::twips(1440),
746 );
747 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
748 doc.set_footer("Tensorbee — Confidential");
749
750 // Custom styles
751 doc.add_style(
752 StyleBuilder::paragraph("ProposalTitle", "Proposal Title")
753 .based_on("Normal")
754 .run_properties({
755 let mut rpr = rdocx_oxml::properties::CT_RPr::default();
756 rpr.bold = Some(true);
757 rpr.font_ascii = Some("Georgia".to_string());
758 rpr.font_hansi = Some("Georgia".to_string());
759 rpr.sz = Some(rdocx_oxml::HalfPoint(56)); // half-points → 28pt
760 rpr.color = Some("1B2A4A".to_string());
761 rpr
762 }),
763 );
764
765 // ── Cover Page ──
766 for _ in 0..4 {
767 doc.add_paragraph("");
768 }
769 doc.add_paragraph("AI Platform Modernization")
770 .style("ProposalTitle")
771 .alignment(Alignment::Center);
772 doc.add_paragraph("")
773 .alignment(Alignment::Center)
774 .border_bottom(BorderStyle::Single, 8, "C5922E");
775 {
776 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
777 p.add_run("Prepared for Global Financial Corp.")
778 .size(14.0)
779 .color("666666")
780 .italic(true);
781 }
782 {
783 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
784 p.add_run("Prepared by: Walter White, CEO — Tensorbee")
785 .size(12.0)
786 .color("888888");
787 }
788 {
789 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
790 p.add_run("February 22, 2026").size(12.0).color("888888");
791 }
792
793 // ── TOC ──
794 doc.add_paragraph("").page_break_before(true);
795 doc.add_paragraph("Table of Contents").style("Heading1");
796 // We'll add a manual-style TOC since the insert_toc goes at a specific position
797 doc.insert_toc(doc.content_count(), 2);
798
799 // ── Executive Summary ──
800 doc.add_paragraph("").page_break_before(true);
801 doc.add_paragraph("Executive Summary").style("Heading1");
802 doc.add_paragraph(
803 "Tensorbee proposes a comprehensive modernization of Global Financial Corp's AI platform, \
804 transitioning from legacy batch-processing systems to a real-time inference architecture. \
805 This transformation will reduce model deployment time from weeks to hours, improve \
806 prediction accuracy by an estimated 23%, and deliver $4.2M in annual operational savings.",
807 )
808 .first_line_indent(Length::inches(0.3));
809 doc.add_paragraph(
810 "The project spans three phases over 18 months, with a total investment of $2.8M. \
811 Tensorbee brings deep expertise in ML infrastructure, having successfully completed \
812 similar transformations for three Fortune 500 financial institutions.",
813 )
814 .first_line_indent(Length::inches(0.3));
815
816 // Key metrics table
817 doc.add_paragraph("");
818 {
819 let mut tbl = doc
820 .add_table(5, 2)
821 .borders(BorderStyle::Single, 4, "1B2A4A")
822 .width_pct(80.0);
823 tbl.cell(0, 0).unwrap().set_text("Key Metric");
824 tbl.cell(0, 0).unwrap().shading("1B2A4A");
825 tbl.cell(0, 1).unwrap().set_text("Value");
826 tbl.cell(0, 1).unwrap().shading("1B2A4A");
827 let rows = [
828 ("Total Investment", "$2.8M"),
829 ("Annual Savings", "$4.2M"),
830 ("ROI (Year 1)", "150%"),
831 ("Timeline", "18 months"),
832 ];
833 for (i, (k, v)) in rows.iter().enumerate() {
834 tbl.cell(i + 1, 0).unwrap().set_text(k);
835 if i % 2 == 0 {
836 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
837 }
838 tbl.cell(i + 1, 1).unwrap().set_text(v);
839 if i % 2 == 0 {
840 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
841 }
842 }
843 }
844
845 // ── Problem Statement ──
846 doc.add_paragraph("").page_break_before(true);
847 doc.add_paragraph("Problem Statement").style("Heading1");
848 doc.add_paragraph(
849 "Global Financial Corp's current AI infrastructure faces three critical challenges:",
850 );
851 doc.add_numbered_list_item("Deployment Latency — New models take 3-4 weeks to deploy to production, compared to the industry benchmark of 2-3 days.", 0);
852 doc.add_numbered_list_item("Scalability Constraints — The batch processing architecture cannot handle real-time inference demands during peak trading hours.", 0);
853 doc.add_numbered_list_item("Technical Debt — Legacy Python 2.7 codebases and manual deployment scripts create reliability risks and slow iteration speed.", 0);
854
855 // ── Proposed Solution ──
856 doc.add_paragraph("").page_break_before(true);
857 doc.add_paragraph("Proposed Solution").style("Heading1");
858
859 doc.add_paragraph("Phase 1: Foundation (Months 1-6)")
860 .style("Heading2");
861 doc.add_bullet_list_item("Deploy Kubernetes-based ML serving infrastructure", 0);
862 doc.add_bullet_list_item("Implement CI/CD pipeline for model deployment", 0);
863 doc.add_bullet_list_item("Migrate top 5 production models to new platform", 0);
864
865 doc.add_paragraph("Phase 2: Expansion (Months 7-12)")
866 .style("Heading2");
867 doc.add_bullet_list_item(
868 "Real-time feature engineering pipeline (Apache Kafka + Flink)",
869 0,
870 );
871 doc.add_bullet_list_item("A/B testing framework for model variants", 0);
872 doc.add_bullet_list_item("Automated model monitoring and drift detection", 0);
873
874 doc.add_paragraph("Phase 3: Optimization (Months 13-18)")
875 .style("Heading2");
876 doc.add_bullet_list_item("GPU inference optimization (TensorRT/ONNX Runtime)", 0);
877 doc.add_bullet_list_item("Multi-region deployment for latency reduction", 0);
878 doc.add_bullet_list_item(
879 "Self-service model deployment portal for data science teams",
880 0,
881 );
882
883 // ── Budget ──
884 doc.add_paragraph("Budget Breakdown").style("Heading1");
885 {
886 let mut tbl = doc
887 .add_table(6, 3)
888 .borders(BorderStyle::Single, 4, "1B2A4A")
889 .width_pct(100.0);
890 tbl.cell(0, 0).unwrap().set_text("Category");
891 tbl.cell(0, 0).unwrap().shading("1B2A4A");
892 tbl.cell(0, 1).unwrap().set_text("Cost");
893 tbl.cell(0, 1).unwrap().shading("1B2A4A");
894 tbl.cell(0, 2).unwrap().set_text("Notes");
895 tbl.cell(0, 2).unwrap().shading("1B2A4A");
896 let items = [
897 (
898 "Infrastructure",
899 "$450,000",
900 "Cloud compute + GPU instances",
901 ),
902 ("Engineering Services", "$1,600,000", "12 FTE x 18 months"),
903 (
904 "Software Licenses",
905 "$350,000",
906 "Kafka, monitoring, CI/CD tools",
907 ),
908 (
909 "Training & Enablement",
910 "$200,000",
911 "Team training, documentation",
912 ),
913 ("Contingency (10%)", "$200,000", "Risk buffer"),
914 ];
915 for (i, (cat, cost, note)) in items.iter().enumerate() {
916 tbl.cell(i + 1, 0).unwrap().set_text(cat);
917 tbl.cell(i + 1, 1).unwrap().set_text(cost);
918 tbl.cell(i + 1, 2).unwrap().set_text(note);
919 if i % 2 == 0 {
920 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
921 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
922 tbl.cell(i + 1, 2).unwrap().shading("F4F1EB");
923 }
924 }
925 }
926
927 // ── Closing ──
928 doc.add_paragraph("");
929 doc.add_paragraph("Next Steps").style("Heading1");
930 doc.add_numbered_list_item(
931 "Schedule technical deep-dive with GFC engineering team (Week 1)",
932 0,
933 );
934 doc.add_numbered_list_item("Finalize scope and sign SOW (Week 2-3)", 0);
935 doc.add_numbered_list_item("Kick off Phase 1 with joint planning session (Week 4)", 0);
936
937 doc.add_paragraph("");
938 {
939 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
940 p.add_run("Walter White")
941 .bold(true)
942 .size(14.0)
943 .color("1B2A4A");
944 }
945 {
946 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
947 p.add_run("CEO, Tensorbee | walter@tensorbee.com")
948 .size(10.0)
949 .color("888888");
950 }
951
952 doc
953}
954
955// =============================================================================
956// 3. QUOTE / BILL OF MATERIALS — Teal + orange scheme
957// =============================================================================
958fn generate_quote(_samples_dir: &Path) -> Document {
959 let mut doc = Document::new();
960
961 // Colors: Teal #008B8B, Dark #1A3C3C, Orange #E07020, Light #F0F8F8
962 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
963 doc.set_margins(
964 Length::inches(0.75),
965 Length::inches(0.75),
966 Length::inches(0.75),
967 Length::inches(0.75),
968 );
969 doc.set_title("Quotation QT-2026-0147");
970 doc.set_author("Walter White");
971 doc.set_keywords("quote, BOM, Tensorbee");
972
973 doc.set_header("Tensorbee — Quotation");
974 doc.set_footer("QT-2026-0147 | Page");
975
976 // ── Header Block ──
977 {
978 let mut p = doc.add_paragraph("").alignment(Alignment::Left);
979 p.add_run("TENSORBEE")
980 .bold(true)
981 .size(28.0)
982 .color("008B8B")
983 .font("Helvetica");
984 }
985 doc.add_paragraph("123 Innovation Drive, Suite 400")
986 .alignment(Alignment::Left);
987 doc.add_paragraph("San Francisco, CA 94105 | +1 (415) 555-0199")
988 .alignment(Alignment::Left);
989 doc.add_paragraph("")
990 .border_bottom(BorderStyle::Single, 8, "008B8B");
991
992 // Quote meta
993 doc.add_paragraph("");
994 {
995 let mut tbl = doc.add_table(4, 4).width_pct(100.0);
996 tbl.cell(0, 0).unwrap().set_text("QUOTATION");
997 tbl.cell(0, 0).unwrap().shading("008B8B").grid_span(2);
998 tbl.cell(0, 2).unwrap().set_text("DATE");
999 tbl.cell(0, 2).unwrap().shading("008B8B");
1000 tbl.cell(0, 3).unwrap().set_text("VALID UNTIL");
1001 tbl.cell(0, 3).unwrap().shading("008B8B");
1002 tbl.cell(1, 0).unwrap().set_text("Quote #:");
1003 tbl.cell(1, 1).unwrap().set_text("QT-2026-0147");
1004 tbl.cell(1, 2).unwrap().set_text("Feb 22, 2026");
1005 tbl.cell(1, 3).unwrap().set_text("Mar 22, 2026");
1006 tbl.cell(2, 0).unwrap().set_text("Prepared by:");
1007 tbl.cell(2, 1).unwrap().set_text("Walter White");
1008 tbl.cell(2, 2).unwrap().set_text("Payment:");
1009 tbl.cell(2, 3).unwrap().set_text("Net 30");
1010 tbl.cell(3, 0).unwrap().set_text("Customer:");
1011 tbl.cell(3, 1).unwrap().set_text("Meridian Dynamics LLC");
1012 tbl.cell(3, 2).unwrap().set_text("Currency:");
1013 tbl.cell(3, 3).unwrap().set_text("USD");
1014 }
1015
1016 doc.add_paragraph("");
1017
1018 // ── Bill of Materials ──
1019 {
1020 let mut p = doc.add_paragraph("");
1021 p.add_run("Bill of Materials")
1022 .bold(true)
1023 .size(16.0)
1024 .color("1A3C3C");
1025 }
1026 doc.add_paragraph("");
1027
1028 {
1029 let mut tbl = doc
1030 .add_table(10, 6)
1031 .borders(BorderStyle::Single, 2, "008B8B")
1032 .width_pct(100.0)
1033 .layout_fixed();
1034 // Headers
1035 let hdrs = [
1036 "#",
1037 "Part Number",
1038 "Description",
1039 "Qty",
1040 "Unit Price",
1041 "Total",
1042 ];
1043 for (c, h) in hdrs.iter().enumerate() {
1044 tbl.cell(0, c).unwrap().set_text(h);
1045 tbl.cell(0, c).unwrap().shading("008B8B");
1046 }
1047
1048 let items: Vec<(&str, &str, &str, &str, &str)> = vec![
1049 (
1050 "TB-GPU-A100",
1051 "NVIDIA A100 80GB GPU",
1052 "4",
1053 "$12,500.00",
1054 "$50,000.00",
1055 ),
1056 (
1057 "TB-SRV-R750",
1058 "Dell R750xa Server Chassis",
1059 "2",
1060 "$8,200.00",
1061 "$16,400.00",
1062 ),
1063 (
1064 "TB-RAM-256G",
1065 "256GB DDR5 ECC Memory Module",
1066 "8",
1067 "$890.00",
1068 "$7,120.00",
1069 ),
1070 (
1071 "TB-SSD-3840",
1072 "3.84TB NVMe U.2 SSD",
1073 "8",
1074 "$1,150.00",
1075 "$9,200.00",
1076 ),
1077 (
1078 "TB-NET-CX7",
1079 "ConnectX-7 200GbE NIC",
1080 "4",
1081 "$1,800.00",
1082 "$7,200.00",
1083 ),
1084 (
1085 "TB-SW-48P",
1086 "48-Port 100GbE Switch",
1087 "1",
1088 "$22,000.00",
1089 "$22,000.00",
1090 ),
1091 (
1092 "TB-CAB-RACK",
1093 "42U Server Rack + PDU",
1094 "1",
1095 "$4,500.00",
1096 "$4,500.00",
1097 ),
1098 (
1099 "TB-SVC-INST",
1100 "Installation & Configuration",
1101 "1",
1102 "$8,500.00",
1103 "$8,500.00",
1104 ),
1105 (
1106 "TB-SVC-SUPP",
1107 "3-Year Premium Support",
1108 "1",
1109 "$15,000.00",
1110 "$15,000.00",
1111 ),
1112 ];
1113
1114 for (i, (pn, desc, qty, unit, total)) in items.iter().enumerate() {
1115 let row = i + 1;
1116 tbl.cell(row, 0).unwrap().set_text(&format!("{}", i + 1));
1117 tbl.cell(row, 1).unwrap().set_text(pn);
1118 tbl.cell(row, 2).unwrap().set_text(desc);
1119 tbl.cell(row, 3).unwrap().set_text(qty);
1120 tbl.cell(row, 4).unwrap().set_text(unit);
1121 tbl.cell(row, 5).unwrap().set_text(total);
1122 if i % 2 == 0 {
1123 for c in 0..6 {
1124 tbl.cell(row, c).unwrap().shading("F0F8F8");
1125 }
1126 }
1127 }
1128 }
1129
1130 doc.add_paragraph("");
1131
1132 // ── Totals ──
1133 {
1134 let mut tbl = doc
1135 .add_table(4, 2)
1136 .borders(BorderStyle::Single, 2, "008B8B")
1137 .width(Length::inches(3.5))
1138 .alignment(Alignment::Right);
1139 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1140 tbl.cell(0, 1).unwrap().set_text("$139,920.00");
1141 tbl.cell(1, 0).unwrap().set_text("Shipping & Handling");
1142 tbl.cell(1, 1).unwrap().set_text("$2,500.00");
1143 tbl.cell(2, 0).unwrap().set_text("Tax (8.625%)");
1144 tbl.cell(2, 1).unwrap().set_text("$12,068.10");
1145 tbl.cell(3, 0).unwrap().set_text("TOTAL");
1146 tbl.cell(3, 0).unwrap().shading("E07020");
1147 tbl.cell(3, 1).unwrap().set_text("$154,488.10");
1148 tbl.cell(3, 1).unwrap().shading("E07020");
1149 }
1150
1151 doc.add_paragraph("");
1152
1153 // ── Terms & Conditions ──
1154 {
1155 let mut p = doc.add_paragraph("");
1156 p.add_run("Terms & Conditions")
1157 .bold(true)
1158 .size(14.0)
1159 .color("1A3C3C");
1160 }
1161 doc.add_numbered_list_item(
1162 "This quotation is valid for 30 calendar days from the date of issue.",
1163 0,
1164 );
1165 doc.add_numbered_list_item(
1166 "All prices are in USD and exclusive of applicable taxes unless stated.",
1167 0,
1168 );
1169 doc.add_numbered_list_item("Standard lead time is 4-6 weeks from PO receipt.", 0);
1170 doc.add_numbered_list_item(
1171 "Payment terms: Net 30 from invoice date. 2% discount for payment within 10 days.",
1172 0,
1173 );
1174 doc.add_numbered_list_item(
1175 "Warranty: 3-year manufacturer warranty on all hardware components.",
1176 0,
1177 );
1178 doc.add_numbered_list_item(
1179 "Returns subject to 15% restocking fee if initiated after 14 days.",
1180 0,
1181 );
1182
1183 doc.add_paragraph("");
1184 doc.add_paragraph("")
1185 .border_bottom(BorderStyle::Single, 4, "008B8B");
1186 doc.add_paragraph("");
1187 {
1188 let mut p = doc.add_paragraph("");
1189 p.add_run("Accepted by: ").bold(true);
1190 p.add_run("___________________________ Date: ___________");
1191 }
1192 {
1193 let mut p = doc.add_paragraph("");
1194 p.add_run("Print Name: ").bold(true);
1195 p.add_run("___________________________ Title: ___________");
1196 }
1197
1198 doc
1199}
1200
1201// =============================================================================
1202// 4. INVOICE — Crimson + charcoal scheme
1203// =============================================================================
1204fn generate_invoice(_samples_dir: &Path) -> Document {
1205 let mut doc = Document::new();
1206
1207 // Colors: Crimson #B22222, Charcoal #333333, Light #FAF0F0
1208 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1209 doc.set_margins(
1210 Length::inches(0.75),
1211 Length::inches(0.75),
1212 Length::inches(0.75),
1213 Length::inches(0.75),
1214 );
1215 doc.set_title("Invoice INV-2026-0392");
1216 doc.set_author("Walter White");
1217 doc.set_keywords("invoice, Tensorbee");
1218
1219 doc.set_footer("Tensorbee — Thank you for your business!");
1220
1221 // ── Company & Invoice Header ──
1222 {
1223 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1224 // Company name left, INVOICE right
1225 {
1226 let mut cell = tbl.cell(0, 0).unwrap();
1227 let mut p = cell.add_paragraph("");
1228 p.add_run("TENSORBEE")
1229 .bold(true)
1230 .size(32.0)
1231 .color("B22222")
1232 .font("Helvetica");
1233 }
1234 {
1235 let mut cell = tbl.cell(0, 1).unwrap();
1236 let mut p = cell.add_paragraph("");
1237 p.add_run("INVOICE").bold(true).size(32.0).color("333333");
1238 }
1239 tbl.cell(1, 0)
1240 .unwrap()
1241 .set_text("123 Innovation Drive, Suite 400");
1242 tbl.cell(1, 1).unwrap().set_text("Invoice #: INV-2026-0392");
1243 tbl.cell(2, 0).unwrap().set_text("San Francisco, CA 94105");
1244 tbl.cell(2, 1).unwrap().set_text("Date: February 22, 2026");
1245 tbl.cell(3, 0).unwrap().set_text("walter@tensorbee.com");
1246 tbl.cell(3, 1).unwrap().set_text("Due Date: March 24, 2026");
1247 }
1248
1249 doc.add_paragraph("")
1250 .border_bottom(BorderStyle::Thick, 8, "B22222");
1251 doc.add_paragraph("");
1252
1253 // ── Bill To / Ship To ──
1254 {
1255 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1256 tbl.cell(0, 0).unwrap().set_text("BILL TO");
1257 tbl.cell(0, 0).unwrap().shading("B22222");
1258 tbl.cell(0, 1).unwrap().set_text("SHIP TO");
1259 tbl.cell(0, 1).unwrap().shading("B22222");
1260 tbl.cell(1, 0).unwrap().set_text("Meridian Dynamics LLC");
1261 tbl.cell(1, 1).unwrap().set_text("Meridian Dynamics LLC");
1262 tbl.cell(2, 0).unwrap().set_text("456 Enterprise Blvd");
1263 tbl.cell(2, 1).unwrap().set_text("Attn: Server Room B");
1264 tbl.cell(3, 0).unwrap().set_text("Austin, TX 78701");
1265 tbl.cell(3, 1)
1266 .unwrap()
1267 .set_text("456 Enterprise Blvd, Austin, TX 78701");
1268 }
1269
1270 doc.add_paragraph("");
1271
1272 // ── Line Items ──
1273 {
1274 let mut tbl = doc
1275 .add_table(8, 5)
1276 .borders(BorderStyle::Single, 2, "B22222")
1277 .width_pct(100.0);
1278 let hdrs = ["Description", "Qty", "Unit Price", "Tax", "Amount"];
1279 for (c, h) in hdrs.iter().enumerate() {
1280 tbl.cell(0, c).unwrap().set_text(h);
1281 tbl.cell(0, c).unwrap().shading("B22222");
1282 }
1283 let items = [
1284 (
1285 "ML Infrastructure Setup — Phase 1",
1286 "1",
1287 "$45,000.00",
1288 "$3,881.25",
1289 "$48,881.25",
1290 ),
1291 (
1292 "Data Pipeline Development (160 hrs)",
1293 "160",
1294 "$225.00",
1295 "$3,105.00",
1296 "$39,105.00",
1297 ),
1298 (
1299 "GPU Cluster Configuration",
1300 "1",
1301 "$12,000.00",
1302 "$1,035.00",
1303 "$13,035.00",
1304 ),
1305 (
1306 "API Gateway Implementation",
1307 "1",
1308 "$18,500.00",
1309 "$1,595.63",
1310 "$20,095.63",
1311 ),
1312 (
1313 "Load Testing & QA (80 hrs)",
1314 "80",
1315 "$195.00",
1316 "$1,345.50",
1317 "$16,945.50",
1318 ),
1319 (
1320 "Documentation & Training",
1321 "1",
1322 "$8,000.00",
1323 "$690.00",
1324 "$8,690.00",
1325 ),
1326 (
1327 "Project Management (3 months)",
1328 "3",
1329 "$6,500.00",
1330 "$1,679.63",
1331 "$21,179.63",
1332 ),
1333 ];
1334 for (i, (desc, qty, unit, tax, amt)) in items.iter().enumerate() {
1335 let r = i + 1;
1336 tbl.cell(r, 0).unwrap().set_text(desc);
1337 tbl.cell(r, 1).unwrap().set_text(qty);
1338 tbl.cell(r, 2).unwrap().set_text(unit);
1339 tbl.cell(r, 3).unwrap().set_text(tax);
1340 tbl.cell(r, 4).unwrap().set_text(amt);
1341 if i % 2 == 0 {
1342 for c in 0..5 {
1343 tbl.cell(r, c).unwrap().shading("FAF0F0");
1344 }
1345 }
1346 }
1347 }
1348
1349 doc.add_paragraph("");
1350
1351 // ── Totals ──
1352 {
1353 let mut tbl = doc
1354 .add_table(4, 2)
1355 .borders(BorderStyle::Single, 2, "B22222")
1356 .width(Length::inches(3.0))
1357 .alignment(Alignment::Right);
1358 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1359 tbl.cell(0, 1).unwrap().set_text("$154,600.00");
1360 tbl.cell(1, 0).unwrap().set_text("Tax (8.625%)");
1361 tbl.cell(1, 1).unwrap().set_text("$13,334.25");
1362 tbl.cell(2, 0).unwrap().set_text("Discount (5%)");
1363 tbl.cell(2, 1).unwrap().set_text("-$7,730.00");
1364 tbl.cell(3, 0).unwrap().set_text("AMOUNT DUE");
1365 tbl.cell(3, 0).unwrap().shading("B22222");
1366 tbl.cell(3, 1).unwrap().set_text("$160,204.25");
1367 tbl.cell(3, 1).unwrap().shading("B22222");
1368 }
1369
1370 doc.add_paragraph("");
1371 doc.add_paragraph("");
1372
1373 // ── Payment Details ──
1374 {
1375 let mut p = doc.add_paragraph("");
1376 p.add_run("Payment Details")
1377 .bold(true)
1378 .size(14.0)
1379 .color("333333");
1380 }
1381 doc.add_paragraph("Bank: Silicon Valley Bank")
1382 .indent_left(Length::inches(0.3));
1383 doc.add_paragraph("Account: Tensorbee Inc. — 0847-2953-1120")
1384 .indent_left(Length::inches(0.3));
1385 doc.add_paragraph("Routing: 121140399")
1386 .indent_left(Length::inches(0.3));
1387 doc.add_paragraph("Swift: SVBKUS6S")
1388 .indent_left(Length::inches(0.3));
1389
1390 doc.add_paragraph("");
1391
1392 doc.add_paragraph("Please include invoice number INV-2026-0392 in the payment reference.")
1393 .shading("FAF0F0")
1394 .border_all(BorderStyle::Single, 2, "B22222");
1395
1396 doc
1397}
1398
1399// =============================================================================
1400// 5. REPORT — Forest green + earth tones, with images & hierarchical sections
1401// =============================================================================
1402fn generate_report(_samples_dir: &Path) -> Document {
1403 let mut doc = Document::new();
1404
1405 // Colors: Forest #2D5016, Sage #6B8E23, Earth #8B7355, Cream #FFFAF0
1406 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1407 doc.set_margins(
1408 Length::inches(1.0),
1409 Length::inches(1.0),
1410 Length::inches(1.0),
1411 Length::inches(1.0),
1412 );
1413 doc.set_title("Q4 2025 Environmental Impact Report");
1414 doc.set_author("Walter White");
1415 doc.set_subject("Quarterly Environmental Report");
1416 doc.set_keywords("environment, sustainability, report, Tensorbee");
1417
1418 doc.set_different_first_page(true);
1419 doc.set_first_page_header("");
1420 doc.set_header("Tensorbee — Q4 2025 Environmental Impact Report");
1421 doc.set_footer("CONFIDENTIAL — Page");
1422
1423 // ── Cover ──
1424 let cover_bg = create_sample_png(612, 792, [20, 50, 15]);
1425 doc.add_background_image(&cover_bg, "report_cover.png");
1426
1427 for _ in 0..5 {
1428 doc.add_paragraph("");
1429 }
1430 {
1431 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1432 p.add_run("Q4 2025")
1433 .bold(true)
1434 .size(48.0)
1435 .color("FFFFFF")
1436 .font("Georgia");
1437 }
1438 {
1439 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1440 p.add_run("Environmental Impact Report")
1441 .size(24.0)
1442 .color("90EE90")
1443 .font("Georgia")
1444 .italic(true);
1445 }
1446 doc.add_paragraph("");
1447 {
1448 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1449 p.add_run("Tensorbee — Sustainability Division")
1450 .size(14.0)
1451 .color("C0C0C0");
1452 }
1453 {
1454 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1455 p.add_run("Prepared by Walter White, Chief Sustainability Officer")
1456 .size(11.0)
1457 .color("AAAAAA");
1458 }
1459
1460 // ── TOC ──
1461 doc.add_paragraph("").page_break_before(true);
1462 doc.insert_toc(doc.content_count(), 3);
1463
1464 // ── Section 1: Executive Overview ──
1465 doc.add_paragraph("").page_break_before(true);
1466 doc.add_paragraph("Executive Overview").style("Heading1");
1467 doc.add_paragraph(
1468 "This report presents Tensorbee's environmental performance for Q4 2025. Our \
1469 sustainability initiatives have yielded a 34% reduction in carbon emissions compared \
1470 to Q4 2024, exceeding our target of 25%. Key achievements include the transition to \
1471 100% renewable energy in our primary data centers and the launch of our carbon offset \
1472 marketplace.",
1473 )
1474 .first_line_indent(Length::inches(0.3));
1475
1476 // Image: performance chart
1477 let chart_img = create_chart_png(400, 200);
1478 doc.add_paragraph("");
1479 doc.add_picture(
1480 &chart_img,
1481 "performance_chart.png",
1482 Length::inches(5.0),
1483 Length::inches(2.5),
1484 )
1485 .alignment(Alignment::Center);
1486 {
1487 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1488 p.add_run("Figure 1: Quarterly Carbon Emissions (tonnes CO2e)")
1489 .italic(true)
1490 .size(9.0)
1491 .color("666666");
1492 }
1493
1494 // ── Section 2: Energy Consumption ──
1495 doc.add_paragraph("").page_break_before(true);
1496 doc.add_paragraph("Energy Consumption").style("Heading1");
1497
1498 doc.add_paragraph("Data Center Operations")
1499 .style("Heading2");
1500 doc.add_paragraph(
1501 "Our three primary data centers consumed a combined 4.2 GWh in Q4 2025, \
1502 a 12% reduction from Q3 2025 driven by improved cooling efficiency and \
1503 server consolidation.",
1504 );
1505
1506 // Data table
1507 {
1508 let mut tbl = doc
1509 .add_table(5, 4)
1510 .borders(BorderStyle::Single, 4, "2D5016")
1511 .width_pct(100.0);
1512 let hdrs = ["Data Center", "Capacity (MW)", "Usage (GWh)", "PUE"];
1513 for (c, h) in hdrs.iter().enumerate() {
1514 tbl.cell(0, c).unwrap().set_text(h);
1515 tbl.cell(0, c).unwrap().shading("2D5016");
1516 }
1517 let rows = [
1518 ("San Francisco (Primary)", "3.2", "1.8", "1.12"),
1519 ("Dublin (EU)", "2.1", "1.2", "1.18"),
1520 ("Singapore (APAC)", "1.8", "1.2", "1.24"),
1521 ("TOTAL", "7.1", "4.2", "1.17 avg"),
1522 ];
1523 for (i, (dc, cap, use_, pue)) in rows.iter().enumerate() {
1524 tbl.cell(i + 1, 0).unwrap().set_text(dc);
1525 tbl.cell(i + 1, 1).unwrap().set_text(cap);
1526 tbl.cell(i + 1, 2).unwrap().set_text(use_);
1527 tbl.cell(i + 1, 3).unwrap().set_text(pue);
1528 if i == 3 {
1529 for c in 0..4 {
1530 tbl.cell(i + 1, c).unwrap().shading("E2EFDA");
1531 }
1532 }
1533 }
1534 }
1535
1536 doc.add_paragraph("");
1537
1538 doc.add_paragraph("Renewable Energy Mix").style("Heading2");
1539 doc.add_paragraph("Breakdown of energy sources across all facilities:");
1540
1541 doc.add_bullet_list_item("Solar PV: 42% (1.76 GWh)", 0);
1542 doc.add_bullet_list_item("Wind Power Purchase Agreements: 38% (1.60 GWh)", 0);
1543 doc.add_bullet_list_item("Hydroelectric: 12% (0.50 GWh)", 0);
1544 doc.add_bullet_list_item("Grid (non-renewable): 8% (0.34 GWh)", 0);
1545
1546 // ── Section 3: Water & Waste ──
1547 doc.add_paragraph("Water & Waste Management")
1548 .style("Heading1");
1549
1550 doc.add_paragraph("Water Usage").style("Heading2");
1551 doc.add_paragraph(
1552 "Total water consumption was 12.4 million gallons, a 15% reduction from Q3. \
1553 Our closed-loop cooling systems now recycle 78% of water used in cooling operations.",
1554 );
1555
1556 doc.add_paragraph("Waste Reduction").style("Heading2");
1557 doc.add_paragraph("E-waste management results for Q4:")
1558 .keep_with_next(true);
1559 {
1560 let mut tbl = doc
1561 .add_table(4, 3)
1562 .borders(BorderStyle::Single, 2, "6B8E23")
1563 .width_pct(80.0);
1564 tbl.cell(0, 0).unwrap().set_text("Category");
1565 tbl.cell(0, 0).unwrap().shading("6B8E23");
1566 tbl.cell(0, 1).unwrap().set_text("Weight (kg)");
1567 tbl.cell(0, 1).unwrap().shading("6B8E23");
1568 tbl.cell(0, 2).unwrap().set_text("Recycled %");
1569 tbl.cell(0, 2).unwrap().shading("6B8E23");
1570 let rows = [
1571 ("Server Hardware", "2,450", "94%"),
1572 ("Networking Equipment", "820", "91%"),
1573 ("Storage Media", "340", "99%"),
1574 ];
1575 for (i, (cat, wt, pct)) in rows.iter().enumerate() {
1576 tbl.cell(i + 1, 0).unwrap().set_text(cat);
1577 tbl.cell(i + 1, 1).unwrap().set_text(wt);
1578 tbl.cell(i + 1, 2).unwrap().set_text(pct);
1579 }
1580 }
1581
1582 // ── Section 4: Initiatives ──
1583 doc.add_paragraph("").page_break_before(true);
1584 doc.add_paragraph("Q1 2026 Initiatives").style("Heading1");
1585
1586 doc.add_paragraph("Planned Programs").style("Heading2");
1587 doc.add_numbered_list_item(
1588 "Deploy on-site battery storage at SF data center (2 MWh capacity)",
1589 0,
1590 );
1591 doc.add_numbered_list_item("Pilot immersion cooling in Dublin facility", 0);
1592 doc.add_numbered_list_item("Launch employee commute carbon offset program", 0);
1593 doc.add_numbered_list_item("Achieve ISO 14001 certification for Singapore facility", 0);
1594
1595 doc.add_paragraph("Investment Targets").style("Heading2");
1596 doc.add_paragraph("Sustainability CapEx allocation for FY2026:")
1597 .keep_with_next(true);
1598 {
1599 let mut tbl = doc
1600 .add_table(5, 2)
1601 .borders(BorderStyle::Single, 4, "2D5016");
1602 tbl.cell(0, 0).unwrap().set_text("Initiative");
1603 tbl.cell(0, 0).unwrap().shading("2D5016");
1604 tbl.cell(0, 1).unwrap().set_text("Budget");
1605 tbl.cell(0, 1).unwrap().shading("2D5016");
1606 let rows = [
1607 ("Battery Storage", "$1.2M"),
1608 ("Immersion Cooling Pilot", "$800K"),
1609 ("Solar Panel Expansion", "$2.1M"),
1610 ("Carbon Credits", "$500K"),
1611 ];
1612 for (i, (init, budget)) in rows.iter().enumerate() {
1613 tbl.cell(i + 1, 0).unwrap().set_text(init);
1614 tbl.cell(i + 1, 1).unwrap().set_text(budget);
1615 if i % 2 == 0 {
1616 tbl.cell(i + 1, 0).unwrap().shading("FFFAF0");
1617 tbl.cell(i + 1, 1).unwrap().shading("FFFAF0");
1618 }
1619 }
1620 }
1621
1622 // Image: sustainability roadmap
1623 let roadmap_img = create_sample_png(500, 100, [40, 80, 30]);
1624 doc.add_paragraph("");
1625 doc.add_picture(
1626 &roadmap_img,
1627 "roadmap.png",
1628 Length::inches(6.0),
1629 Length::inches(1.2),
1630 )
1631 .alignment(Alignment::Center);
1632 {
1633 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1634 p.add_run("Figure 2: 2026 Sustainability Roadmap")
1635 .italic(true)
1636 .size(9.0)
1637 .color("666666");
1638 }
1639
1640 doc.add_paragraph("");
1641 doc.add_paragraph(
1642 "For questions about this report, contact Walter White at sustainability@tensorbee.com.",
1643 )
1644 .shading("FFFAF0")
1645 .border_all(BorderStyle::Single, 2, "2D5016");
1646
1647 doc
1648}
1649
1650// =============================================================================
1651// 6. LETTER — Slate blue + silver scheme
1652// =============================================================================
1653fn generate_letter(_samples_dir: &Path) -> Document {
1654 let mut doc = Document::new();
1655
1656 // Colors: Slate #4A5568, Blue accent #3182CE
1657 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1658 doc.set_margins(
1659 Length::inches(1.25),
1660 Length::inches(1.25),
1661 Length::inches(1.25),
1662 Length::inches(1.25),
1663 );
1664 doc.set_title("Business Letter");
1665 doc.set_author("Walter White");
1666
1667 // Banner header using raw XML
1668 let logo_img = create_logo_png(220, 48);
1669 let banner = build_header_banner_xml(
1670 "rId1",
1671 &BannerOpts {
1672 bg_color: "4A5568",
1673 banner_width: 7772400,
1674 banner_height: 731520, // ~0.8"
1675 logo_width: 2011680,
1676 logo_height: 438912,
1677 logo_x_offset: 295125,
1678 logo_y_offset: 146304,
1679 },
1680 );
1681 doc.set_raw_header_with_images(
1682 banner,
1683 &[("rId1", &logo_img, "logo.png")],
1684 rdocx_oxml::header_footer::HdrFtrType::Default,
1685 );
1686 doc.set_margins(
1687 Length::twips(2000),
1688 Length::twips(1800),
1689 Length::twips(1440),
1690 Length::twips(1800),
1691 );
1692 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
1693
1694 // Footer with contact
1695 doc.set_footer(
1696 "Tensorbee | 123 Innovation Drive, Suite 400 | San Francisco, CA 94105 | tensorbee.com",
1697 );
1698
1699 // ── Sender Address ──
1700 doc.add_paragraph("Walter White");
1701 doc.add_paragraph("Chief Executive Officer");
1702 doc.add_paragraph("Tensorbee");
1703 doc.add_paragraph("123 Innovation Drive, Suite 400");
1704 doc.add_paragraph("San Francisco, CA 94105");
1705 doc.add_paragraph("");
1706 doc.add_paragraph("February 22, 2026");
1707
1708 doc.add_paragraph("");
1709
1710 // ── Recipient ──
1711 doc.add_paragraph("Ms. Sarah Chen");
1712 doc.add_paragraph("VP of Engineering");
1713 doc.add_paragraph("Meridian Dynamics LLC");
1714 doc.add_paragraph("456 Enterprise Boulevard");
1715 doc.add_paragraph("Austin, TX 78701");
1716
1717 doc.add_paragraph("");
1718
1719 // ── Subject ──
1720 {
1721 let mut p = doc.add_paragraph("");
1722 p.add_run("Re: Strategic Technology Partnership — Phase 2 Expansion")
1723 .bold(true);
1724 }
1725
1726 doc.add_paragraph("");
1727
1728 // ── Body ──
1729 doc.add_paragraph("Dear Ms. Chen,");
1730 doc.add_paragraph("");
1731
1732 doc.add_paragraph(
1733 "I am writing to express Tensorbee's enthusiasm for expanding our strategic technology \
1734 partnership with Meridian Dynamics. Following the successful completion of Phase 1, \
1735 which delivered a 40% improvement in your ML inference pipeline throughput, we believe \
1736 the foundation is firmly established for an ambitious Phase 2 engagement.",
1737 )
1738 .first_line_indent(Length::inches(0.5));
1739
1740 doc.add_paragraph(
1741 "During our recent executive review, your team highlighted three priority areas for \
1742 the next phase of collaboration:",
1743 )
1744 .first_line_indent(Length::inches(0.5));
1745
1746 doc.add_numbered_list_item(
1747 "Real-time anomaly detection for your financial transaction monitoring system, \
1748 targeting sub-100ms latency at 50,000 transactions per second.",
1749 0,
1750 );
1751 doc.add_numbered_list_item(
1752 "Federated learning infrastructure to enable model training across your distributed \
1753 data centers without centralizing sensitive financial data.",
1754 0,
1755 );
1756 doc.add_numbered_list_item(
1757 "MLOps automation to reduce your model deployment cycle from the current 5 days \
1758 to under 4 hours.",
1759 0,
1760 );
1761
1762 doc.add_paragraph(
1763 "Our engineering team has prepared a detailed technical proposal addressing each of \
1764 these areas. We have allocated a dedicated team of eight senior engineers, led by \
1765 our CTO, to ensure continuity with the Phase 1 team your organization has already \
1766 built a productive working relationship with.",
1767 )
1768 .first_line_indent(Length::inches(0.5));
1769
1770 doc.add_paragraph(
1771 "I would welcome the opportunity to present our Phase 2 proposal in person. My \
1772 assistant will reach out to coordinate a meeting at your convenience during the \
1773 first week of March.",
1774 )
1775 .first_line_indent(Length::inches(0.5));
1776
1777 doc.add_paragraph("");
1778
1779 doc.add_paragraph("Warm regards,");
1780 doc.add_paragraph("");
1781 doc.add_paragraph("");
1782
1783 // Signature line
1784 doc.add_paragraph("")
1785 .border_bottom(BorderStyle::Single, 4, "4A5568");
1786 {
1787 let mut p = doc.add_paragraph("");
1788 p.add_run("Walter White").bold(true).size(12.0);
1789 }
1790 {
1791 let mut p = doc.add_paragraph("");
1792 p.add_run("Chief Executive Officer, Tensorbee")
1793 .italic(true)
1794 .size(10.0)
1795 .color("666666");
1796 }
1797 {
1798 let mut p = doc.add_paragraph("");
1799 p.add_run("walter@tensorbee.com | +1 (415) 555-0199")
1800 .size(10.0)
1801 .color("888888");
1802 }
1803
1804 doc
1805}
1806
1807// =============================================================================
1808// 7. EMPLOYMENT CONTRACT — Purple + charcoal scheme
1809// =============================================================================
1810fn generate_contract(_samples_dir: &Path) -> Document {
1811 let mut doc = Document::new();
1812
1813 // Colors: Purple #5B2C6F, Plum #8E44AD, Gray #2C3E50
1814 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1815 doc.set_margins(
1816 Length::inches(1.25),
1817 Length::inches(1.0),
1818 Length::inches(1.0),
1819 Length::inches(1.0),
1820 );
1821 doc.set_title("Employment Agreement");
1822 doc.set_author("Tensorbee HR Department");
1823 doc.set_subject("Employment Contract — Walter White");
1824 doc.set_keywords("employment, contract, agreement, Tensorbee");
1825
1826 doc.set_header("TENSORBEE — Employment Agreement");
1827 doc.set_footer("Employment Agreement — Walter White — Page");
1828
1829 // ── Title Block ──
1830 doc.add_paragraph("")
1831 .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1832 {
1833 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1834 p.add_run("EMPLOYMENT AGREEMENT")
1835 .bold(true)
1836 .size(24.0)
1837 .color("5B2C6F")
1838 .font("Georgia");
1839 }
1840 doc.add_paragraph("")
1841 .border_bottom(BorderStyle::Thick, 12, "5B2C6F");
1842 doc.add_paragraph("");
1843
1844 // ── Parties ──
1845 doc.add_paragraph(
1846 "This Employment Agreement (\"Agreement\") is entered into as of February 22, 2026 \
1847 (\"Effective Date\"), by and between:",
1848 );
1849
1850 doc.add_paragraph("");
1851
1852 {
1853 let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1854 p.add_run("EMPLOYER: ").bold(true);
1855 p.add_run(
1856 "Tensorbee, Inc., a Delaware corporation with its principal place of business at \
1857 123 Innovation Drive, Suite 400, San Francisco, CA 94105 (\"Company\")",
1858 );
1859 }
1860 doc.add_paragraph("");
1861 {
1862 let mut p = doc.add_paragraph("").indent_left(Length::inches(0.5));
1863 p.add_run("EMPLOYEE: ").bold(true);
1864 p.add_run(
1865 "Walter White, residing at 308 Negra Arroyo Lane, Albuquerque, NM 87104 (\"Employee\")",
1866 );
1867 }
1868
1869 doc.add_paragraph("");
1870 doc.add_paragraph("The Company and Employee are collectively referred to as the \"Parties.\"");
1871 doc.add_paragraph("");
1872
1873 // ── Article 1: Position and Duties ──
1874 doc.add_paragraph("Article 1 — Position and Duties")
1875 .style("Heading1");
1876 {
1877 let mut p = doc.add_paragraph("");
1878 p.add_run("1.1 ").bold(true);
1879 p.add_run("The Company hereby employs the Employee as ");
1880 p.add_run("Chief Executive Officer (CEO)")
1881 .bold(true)
1882 .italic(true);
1883 p.add_run(", reporting directly to the Board of Directors.");
1884 }
1885 {
1886 let mut p = doc.add_paragraph("");
1887 p.add_run("1.2 ").bold(true);
1888 p.add_run("The Employee shall devote full working time, attention, and best efforts to \
1889 the performance of duties as reasonably assigned by the Board, including but not limited to:");
1890 }
1891 doc.add_bullet_list_item(
1892 "Setting and executing the Company's strategic vision and business plan",
1893 0,
1894 );
1895 doc.add_bullet_list_item(
1896 "Overseeing all operations, engineering, and commercial activities",
1897 0,
1898 );
1899 doc.add_bullet_list_item(
1900 "Representing the Company to investors, customers, and the public",
1901 0,
1902 );
1903 doc.add_bullet_list_item("Recruiting, developing, and retaining key talent", 0);
1904
1905 {
1906 let mut p = doc.add_paragraph("");
1907 p.add_run("1.3 ").bold(true);
1908 p.add_run(
1909 "The Employee's primary work location shall be the Company's San Francisco \
1910 headquarters, with reasonable travel as required by business needs.",
1911 );
1912 }
1913
1914 // ── Article 2: Compensation ──
1915 doc.add_paragraph("Article 2 — Compensation and Benefits")
1916 .style("Heading1");
1917 {
1918 let mut p = doc.add_paragraph("");
1919 p.add_run("2.1 Base Salary. ").bold(true);
1920 p.add_run("The Company shall pay the Employee an annual base salary of ");
1921 p.add_run("$375,000.00").bold(true);
1922 p.add_run(" (Three Hundred Seventy-Five Thousand Dollars), payable in accordance with the \
1923 Company's standard payroll schedule, less applicable withholdings and deductions.");
1924 }
1925 {
1926 let mut p = doc.add_paragraph("");
1927 p.add_run("2.2 Annual Bonus. ").bold(true);
1928 p.add_run("The Employee shall be eligible for an annual performance bonus of up to ");
1929 p.add_run("40%").bold(true);
1930 p.add_run(
1931 " of base salary, based on achievement of mutually agreed performance objectives.",
1932 );
1933 }
1934 {
1935 let mut p = doc.add_paragraph("");
1936 p.add_run("2.3 Equity. ").bold(true);
1937 p.add_run("Subject to Board approval, the Employee shall receive a stock option grant of ");
1938 p.add_run("500,000 shares").bold(true);
1939 p.add_run(
1940 " of the Company's common stock, vesting over four (4) years with a one-year cliff, \
1941 at an exercise price equal to the fair market value on the date of grant.",
1942 );
1943 }
1944
1945 // Compensation summary table
1946 doc.add_paragraph("");
1947 {
1948 let mut tbl = doc
1949 .add_table(5, 2)
1950 .borders(BorderStyle::Single, 4, "5B2C6F")
1951 .width_pct(70.0)
1952 .alignment(Alignment::Center);
1953 tbl.cell(0, 0).unwrap().set_text("Compensation Element");
1954 tbl.cell(0, 0).unwrap().shading("5B2C6F");
1955 tbl.cell(0, 1).unwrap().set_text("Value");
1956 tbl.cell(0, 1).unwrap().shading("5B2C6F");
1957 let items = [
1958 ("Base Salary", "$375,000/year"),
1959 ("Target Bonus", "Up to 40% ($150,000)"),
1960 ("Equity Grant", "500,000 shares (4yr vest)"),
1961 ("Total Target Comp", "$525,000 + equity"),
1962 ];
1963 for (i, (elem, val)) in items.iter().enumerate() {
1964 tbl.cell(i + 1, 0).unwrap().set_text(elem);
1965 tbl.cell(i + 1, 1).unwrap().set_text(val);
1966 if i == 3 {
1967 tbl.cell(i + 1, 0).unwrap().shading("E8DAEF");
1968 tbl.cell(i + 1, 1).unwrap().shading("E8DAEF");
1969 }
1970 }
1971 }
1972
1973 // ── Article 3: Benefits ──
1974 doc.add_paragraph("").page_break_before(true);
1975 doc.add_paragraph("Article 3 — Benefits").style("Heading1");
1976 {
1977 let mut p = doc.add_paragraph("");
1978 p.add_run("3.1 ").bold(true);
1979 p.add_run(
1980 "The Employee shall be entitled to participate in all benefit programs \
1981 generally available to senior executives, including:",
1982 );
1983 }
1984 doc.add_bullet_list_item(
1985 "Medical, dental, and vision insurance (100% premium coverage for employee and dependents)",
1986 0,
1987 );
1988 doc.add_bullet_list_item("401(k) retirement plan with 6% company match", 0);
1989 doc.add_bullet_list_item("Life insurance and long-term disability coverage", 0);
1990 doc.add_bullet_list_item("Annual professional development allowance of $10,000", 0);
1991
1992 {
1993 let mut p = doc.add_paragraph("");
1994 p.add_run("3.2 Paid Time Off. ").bold(true);
1995 p.add_run(
1996 "The Employee shall receive 25 days of paid vacation per year, plus Company holidays, \
1997 accruing on a monthly basis.",
1998 );
1999 }
2000
2001 // ── Article 4: Term and Termination ──
2002 doc.add_paragraph("Article 4 — Term and Termination")
2003 .style("Heading1");
2004 {
2005 let mut p = doc.add_paragraph("");
2006 p.add_run("4.1 At-Will Employment. ").bold(true);
2007 p.add_run(
2008 "This Agreement is for at-will employment and may be terminated by either Party \
2009 at any time, with or without cause, subject to the notice provisions herein.",
2010 );
2011 }
2012 {
2013 let mut p = doc.add_paragraph("");
2014 p.add_run("4.2 Notice Period. ").bold(true);
2015 p.add_run("Either Party shall provide ");
2016 p.add_run("ninety (90) days").bold(true);
2017 p.add_run(" written notice of termination, or pay in lieu thereof.");
2018 }
2019 {
2020 let mut p = doc.add_paragraph("");
2021 p.add_run("4.3 Severance. ").bold(true);
2022 p.add_run("In the event of termination by the Company without Cause, the Employee shall \
2023 receive (a) twelve (12) months of base salary continuation, (b) pro-rata bonus \
2024 for the year of termination, and (c) twelve (12) months of COBRA premium coverage.");
2025 }
2026
2027 // ── Article 5: Confidentiality ──
2028 doc.add_paragraph("Article 5 — Confidentiality and IP")
2029 .style("Heading1");
2030 {
2031 let mut p = doc.add_paragraph("");
2032 p.add_run("5.1 ").bold(true);
2033 p.add_run(
2034 "The Employee agrees to maintain strict confidentiality of all proprietary \
2035 information, trade secrets, and business plans of the Company during and after \
2036 employment.",
2037 );
2038 }
2039 {
2040 let mut p = doc.add_paragraph("");
2041 p.add_run("5.2 ").bold(true);
2042 p.add_run(
2043 "All intellectual property, inventions, and works of authorship created by the \
2044 Employee during the term of employment and within the scope of duties shall be \
2045 the sole and exclusive property of the Company.",
2046 );
2047 }
2048
2049 // ── Article 6: Non-Compete ──
2050 doc.add_paragraph("Article 6 — Non-Competition")
2051 .style("Heading1");
2052 {
2053 let mut p = doc.add_paragraph("");
2054 p.add_run("6.1 ").bold(true);
2055 p.add_run("For a period of twelve (12) months following termination, the Employee agrees \
2056 not to engage in any business that directly competes with the Company's core \
2057 business of AI infrastructure and ML operations services, within the United States.");
2058 }
2059
2060 // ── Article 7: General Provisions ──
2061 doc.add_paragraph("Article 7 — General Provisions")
2062 .style("Heading1");
2063 {
2064 let mut p = doc.add_paragraph("");
2065 p.add_run("7.1 Governing Law. ").bold(true);
2066 p.add_run(
2067 "This Agreement shall be governed by and construed in accordance with the laws of \
2068 the State of California.",
2069 );
2070 }
2071 {
2072 let mut p = doc.add_paragraph("");
2073 p.add_run("7.2 Entire Agreement. ").bold(true);
2074 p.add_run(
2075 "This Agreement constitutes the entire agreement between the Parties and supersedes \
2076 all prior negotiations, representations, and agreements.",
2077 );
2078 }
2079 {
2080 let mut p = doc.add_paragraph("");
2081 p.add_run("7.3 Amendment. ").bold(true);
2082 p.add_run(
2083 "This Agreement may only be amended by a written instrument signed by both Parties.",
2084 );
2085 }
2086
2087 // ── Signature Block ──
2088 doc.add_paragraph("").page_break_before(true);
2089 doc.add_paragraph("IN WITNESS WHEREOF, the Parties have executed this Employment Agreement as of the Effective Date.")
2090 .space_after(Length::pt(24.0));
2091
2092 // Signature table
2093 {
2094 let mut tbl = doc.add_table(6, 2).width_pct(100.0);
2095 tbl.cell(0, 0).unwrap().set_text("FOR THE COMPANY:");
2096 tbl.cell(0, 0).unwrap().shading("5B2C6F");
2097 tbl.cell(0, 1).unwrap().set_text("EMPLOYEE:");
2098 tbl.cell(0, 1).unwrap().shading("5B2C6F");
2099
2100 tbl.cell(1, 0).unwrap().set_text("");
2101 tbl.cell(1, 1).unwrap().set_text("");
2102 tbl.row(1).unwrap().height(Length::pt(40.0));
2103
2104 tbl.cell(2, 0)
2105 .unwrap()
2106 .set_text("Signature: ___________________________");
2107 tbl.cell(2, 1)
2108 .unwrap()
2109 .set_text("Signature: ___________________________");
2110 tbl.cell(3, 0)
2111 .unwrap()
2112 .set_text("Name: Board Representative");
2113 tbl.cell(3, 1).unwrap().set_text("Name: Walter White");
2114 tbl.cell(4, 0)
2115 .unwrap()
2116 .set_text("Title: Chair, Board of Directors");
2117 tbl.cell(4, 1)
2118 .unwrap()
2119 .set_text("Title: Chief Executive Officer");
2120 tbl.cell(5, 0).unwrap().set_text("Date: _______________");
2121 tbl.cell(5, 1).unwrap().set_text("Date: _______________");
2122 }
2123
2124 doc
2125}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?
86fn generate_feature_showcase(_samples_dir: &Path) -> Document {
87 let mut doc = Document::new();
88
89 // ── Page Setup & Metadata ──
90 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
91 doc.set_margins(
92 Length::inches(1.0),
93 Length::inches(1.0),
94 Length::inches(1.0),
95 Length::inches(1.0),
96 );
97 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
98 doc.set_gutter(Length::twips(0));
99 doc.set_title("rdocx Feature Showcase");
100 doc.set_author("rdocx Sample Generator");
101 doc.set_subject("Comprehensive feature demonstration");
102 doc.set_keywords("rdocx, docx, rust, sample, showcase");
103
104 // Headers & Footers with different first page
105 doc.set_different_first_page(true);
106 doc.set_first_page_header("rdocx");
107 doc.set_first_page_footer("Feature Showcase — Cover Page");
108 doc.set_header("rdocx Feature Showcase");
109 doc.set_footer("Generated by rdocx");
110
111 // ── COVER PAGE ──
112 let bg = create_sample_png(612, 792, [20, 45, 90]);
113 doc.add_background_image(&bg, "cover_bg.png");
114
115 for _ in 0..3 {
116 doc.add_paragraph("");
117 }
118 {
119 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
120 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
121 }
122 {
123 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
124 p.add_run("Complete Feature Showcase")
125 .size(28.0)
126 .color("FFFFFF")
127 .italic(true);
128 }
129 doc.add_paragraph("");
130 {
131 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
132 p.add_run("Every feature of the rdocx Rust library")
133 .size(13.0)
134 .color("B0C4DE");
135 }
136 {
137 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
138 p.add_run("demonstrated in a single document.")
139 .size(13.0)
140 .color("B0C4DE");
141 }
142
143 // ── TABLE OF CONTENTS ──
144 doc.add_paragraph("").page_break_before(true);
145 doc.insert_toc(doc.content_count(), 3);
146
147 // ── SECTION 1: TEXT FORMATTING ──
148 doc.add_paragraph("").page_break_before(true);
149 doc.add_paragraph("1. Text Formatting").style("Heading1");
150
151 // Paragraph Alignment
152 doc.add_paragraph("1.1 Paragraph Alignment")
153 .style("Heading2");
154 doc.add_paragraph("Left-aligned paragraph (default).")
155 .alignment(Alignment::Left);
156 doc.add_paragraph("Center-aligned paragraph.")
157 .alignment(Alignment::Center);
158 doc.add_paragraph("Right-aligned paragraph.")
159 .alignment(Alignment::Right);
160 doc.add_paragraph(
161 "Justified paragraph — this text is long enough to demonstrate how justified alignment \
162 distributes extra space across word gaps so lines fill the full width of the text area.",
163 )
164 .alignment(Alignment::Justify);
165
166 doc.add_paragraph("");
167
168 // Run Formatting
169 doc.add_paragraph("1.2 Run Formatting").style("Heading2");
170 {
171 let mut p = doc.add_paragraph("");
172 p.add_run("Bold").bold(true);
173 p.add_run(" | ");
174 p.add_run("Italic").italic(true);
175 p.add_run(" | ");
176 p.add_run("Bold Italic").bold(true).italic(true);
177 p.add_run(" | ");
178 p.add_run("Underline").underline(true);
179 p.add_run(" | ");
180 p.add_run("Strikethrough").strike(true);
181 p.add_run(" | ");
182 p.add_run("Double Strike").double_strike(true);
183 }
184 {
185 let mut p = doc.add_paragraph("");
186 p.add_run("Red").color("FF0000");
187 p.add_run(" | ");
188 p.add_run("Blue").color("0000FF");
189 p.add_run(" | ");
190 p.add_run("Green").color("00AA00");
191 p.add_run(" | ");
192 p.add_run("Highlighted").highlight("FFFF00");
193 }
194 {
195 let mut p = doc.add_paragraph("");
196 p.add_run("8pt").size(8.0);
197 p.add_run(" | ");
198 p.add_run("11pt").size(11.0);
199 p.add_run(" | ");
200 p.add_run("16pt").size(16.0);
201 p.add_run(" | ");
202 p.add_run("24pt").size(24.0);
203 }
204 {
205 let mut p = doc.add_paragraph("");
206 p.add_run("Arial").font("Arial");
207 p.add_run(" | ");
208 p.add_run("Times New Roman").font("Times New Roman");
209 p.add_run(" | ");
210 p.add_run("Courier New").font("Courier New");
211 }
212 {
213 let mut p = doc.add_paragraph("");
214 p.add_run("H");
215 p.add_run("2").subscript();
216 p.add_run("O (subscript) | E=mc");
217 p.add_run("2").superscript();
218 p.add_run(" (superscript)");
219 }
220 {
221 let mut p = doc.add_paragraph("");
222 p.add_run("ALL CAPS").all_caps(true);
223 p.add_run(" | ");
224 p.add_run("Small Caps").small_caps(true);
225 p.add_run(" | ");
226 p.add_run("Expanded").character_spacing(Length::twips(40));
227 p.add_run(" | ");
228 p.add_run("Hidden text (not visible)").hidden(true);
229 }
230
231 doc.add_paragraph("");
232
233 // Underline Styles
234 doc.add_paragraph("1.3 Underline Styles").style("Heading2");
235 {
236 let mut p = doc.add_paragraph("");
237 p.add_run("Single ")
238 .underline_style(rdocx::UnderlineStyle::Single);
239 p.add_run("Double ")
240 .underline_style(rdocx::UnderlineStyle::Double);
241 p.add_run("Thick ")
242 .underline_style(rdocx::UnderlineStyle::Thick);
243 p.add_run("Dotted ")
244 .underline_style(rdocx::UnderlineStyle::Dotted);
245 p.add_run("Dash ")
246 .underline_style(rdocx::UnderlineStyle::Dash);
247 p.add_run("Wave ")
248 .underline_style(rdocx::UnderlineStyle::Wave);
249 }
250
251 // ── SECTION 2: PARAGRAPH FORMATTING ──
252 doc.add_paragraph("").page_break_before(true);
253 doc.add_paragraph("2. Paragraph Formatting")
254 .style("Heading1");
255
256 doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
257 doc.add_paragraph("Paragraph with light green shading")
258 .shading("E2EFDA");
259 doc.add_paragraph("Paragraph with bottom border")
260 .border_bottom(BorderStyle::Single, 6, "2E75B6");
261 doc.add_paragraph("Paragraph with all borders (red)")
262 .border_all(BorderStyle::Single, 4, "FF0000");
263
264 doc.add_paragraph("2.2 Indentation").style("Heading2");
265 doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
266 .indent_left(Length::inches(1.0))
267 .hanging_indent(Length::inches(0.5));
268 doc.add_paragraph("0.5-inch first-line indent on this paragraph")
269 .first_line_indent(Length::inches(0.5));
270 doc.add_paragraph("Both left and right indent (0.75 inches each)")
271 .indent_left(Length::inches(0.75))
272 .indent_right(Length::inches(0.75));
273
274 doc.add_paragraph("2.3 Spacing & Line Height")
275 .style("Heading2");
276 doc.add_paragraph("Extra space before (24pt) and after (12pt)")
277 .space_before(Length::pt(24.0))
278 .space_after(Length::pt(12.0));
279 doc.add_paragraph(
280 "Double line spacing paragraph. This text should have extra vertical space between \
281 lines to demonstrate line_spacing_multiple(2.0).",
282 )
283 .line_spacing_multiple(2.0);
284 doc.add_paragraph("Exact 20pt line spacing (fixed height).")
285 .line_spacing(20.0);
286
287 doc.add_paragraph("2.4 Pagination Controls")
288 .style("Heading2");
289 doc.add_paragraph("keep_with_next — this paragraph stays with the next")
290 .keep_with_next(true);
291 doc.add_paragraph("(This paragraph was kept with the one above.)");
292 doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
293 .keep_together(true);
294 doc.add_paragraph("widow_control — prevents widow/orphan lines")
295 .widow_control(true);
296
297 // ── SECTION 3: LISTS ──
298 doc.add_paragraph("").page_break_before(true);
299 doc.add_paragraph("3. Lists").style("Heading1");
300
301 doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
302 doc.add_bullet_list_item("First item", 0);
303 doc.add_bullet_list_item("Second item", 0);
304 doc.add_bullet_list_item("Nested level 1", 1);
305 doc.add_bullet_list_item("Nested level 2", 2);
306 doc.add_bullet_list_item("Back to level 1", 1);
307 doc.add_bullet_list_item("Third item", 0);
308
309 doc.add_paragraph("");
310
311 doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
312 doc.add_numbered_list_item("First numbered item", 0);
313 doc.add_numbered_list_item("Second numbered item", 0);
314 doc.add_numbered_list_item("Sub-item A", 1);
315 doc.add_numbered_list_item("Sub-item B", 1);
316 doc.add_numbered_list_item("Third numbered item", 0);
317
318 // ── SECTION 4: TAB STOPS ──
319 doc.add_paragraph("");
320 doc.add_paragraph("4. Tab Stops").style("Heading1");
321
322 doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
323 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
324 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
325 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
326 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
327 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
328
329 doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
330 doc.add_paragraph("Item\t\tPrice")
331 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
332 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
333 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
334 doc.add_paragraph("Widget\t\t$19.99")
335 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
336 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
337 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
338 doc.add_paragraph("Gadget\t\t$249.50")
339 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
340 .add_tab_stop_with_leader(
341 TabAlignment::Right,
342 Length::inches(4.0),
343 TabLeader::Underscore,
344 )
345 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
346
347 // ── SECTION 5: TABLES ──
348 doc.add_paragraph("").page_break_before(true);
349 doc.add_paragraph("5. Tables").style("Heading1");
350
351 doc.add_paragraph("5.1 Basic Table").style("Heading2");
352 {
353 let mut tbl = doc
354 .add_table(4, 3)
355 .borders(BorderStyle::Single, 4, "000000");
356 for col in 0..3 {
357 tbl.cell(0, col).unwrap().shading("2E75B6");
358 }
359 tbl.cell(0, 0).unwrap().set_text("Name");
360 tbl.cell(0, 1).unwrap().set_text("Role");
361 tbl.cell(0, 2).unwrap().set_text("Location");
362 tbl.cell(1, 0).unwrap().set_text("Walter White");
363 tbl.cell(1, 1).unwrap().set_text("CEO");
364 tbl.cell(1, 2).unwrap().set_text("Albuquerque");
365 tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
366 tbl.cell(2, 1).unwrap().set_text("CTO");
367 tbl.cell(2, 2).unwrap().set_text("Remote");
368 tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
369 tbl.cell(3, 1).unwrap().set_text("Security");
370 tbl.cell(3, 2).unwrap().set_text("Washington");
371 }
372
373 doc.add_paragraph("");
374
375 doc.add_paragraph("5.2 Column Span & Cell Shading")
376 .style("Heading2");
377 {
378 let mut tbl = doc
379 .add_table(3, 4)
380 .borders(BorderStyle::Single, 4, "000000")
381 .width_pct(100.0);
382 tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
383 tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
384 tbl.cell(1, 0).unwrap().set_text("Region");
385 tbl.cell(1, 0).unwrap().shading("D6E4F0");
386 tbl.cell(1, 1).unwrap().set_text("Q1");
387 tbl.cell(1, 1).unwrap().shading("D6E4F0");
388 tbl.cell(1, 2).unwrap().set_text("Q2");
389 tbl.cell(1, 2).unwrap().shading("D6E4F0");
390 tbl.cell(1, 3).unwrap().set_text("Total");
391 tbl.cell(1, 3).unwrap().shading("D6E4F0");
392 tbl.cell(2, 0).unwrap().set_text("Americas");
393 tbl.cell(2, 1).unwrap().set_text("$2.4M");
394 tbl.cell(2, 2).unwrap().set_text("$2.7M");
395 tbl.cell(2, 3).unwrap().set_text("$5.1M");
396 }
397
398 doc.add_paragraph("");
399
400 doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
401 {
402 let mut tbl = doc
403 .add_table(4, 3)
404 .borders(BorderStyle::Single, 4, "000000");
405 tbl.cell(0, 0).unwrap().set_text("Category");
406 tbl.cell(0, 0).unwrap().shading("E2EFDA");
407 tbl.cell(0, 1).unwrap().set_text("Item");
408 tbl.cell(0, 1).unwrap().shading("E2EFDA");
409 tbl.cell(0, 2).unwrap().set_text("Price");
410 tbl.cell(0, 2).unwrap().shading("E2EFDA");
411 tbl.cell(1, 0).unwrap().set_text("Hardware");
412 tbl.cell(1, 0).unwrap().v_merge_restart();
413 tbl.cell(1, 1).unwrap().set_text("Laptop");
414 tbl.cell(1, 2).unwrap().set_text("$1,200");
415 tbl.cell(2, 0).unwrap().v_merge_continue();
416 tbl.cell(2, 1).unwrap().set_text("Monitor");
417 tbl.cell(2, 2).unwrap().set_text("$450");
418 tbl.cell(3, 0).unwrap().set_text("Software");
419 tbl.cell(3, 1).unwrap().set_text("IDE License");
420 tbl.cell(3, 2).unwrap().set_text("$200/yr");
421 }
422
423 doc.add_paragraph("");
424
425 doc.add_paragraph("5.4 Nested Table").style("Heading2");
426 {
427 let mut tbl = doc
428 .add_table(2, 2)
429 .borders(BorderStyle::Single, 6, "2E75B6");
430 tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
431 tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
432 tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
433 {
434 let mut cell = tbl.cell(1, 1).unwrap();
435 cell.set_text("Nested table below:");
436 let mut nested = cell
437 .add_table(2, 2)
438 .borders(BorderStyle::Single, 2, "FF6600");
439 nested.cell(0, 0).unwrap().set_text("A");
440 nested.cell(0, 1).unwrap().set_text("B");
441 nested.cell(1, 0).unwrap().set_text("C");
442 nested.cell(1, 1).unwrap().set_text("D");
443 }
444 }
445
446 doc.add_paragraph("");
447
448 doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
449 .style("Heading2");
450 {
451 let mut tbl = doc
452 .add_table(2, 3)
453 .borders(BorderStyle::Single, 4, "666666");
454 tbl.row(0).unwrap().height(Length::pt(50.0)).header();
455 tbl.cell(0, 0).unwrap().set_text("Top");
456 tbl.cell(0, 0)
457 .unwrap()
458 .vertical_alignment(VerticalAlignment::Top)
459 .shading("FFF2CC");
460 tbl.cell(0, 1).unwrap().set_text("Center");
461 tbl.cell(0, 1)
462 .unwrap()
463 .vertical_alignment(VerticalAlignment::Center)
464 .shading("D9E2F3");
465 tbl.cell(0, 2).unwrap().set_text("Bottom");
466 tbl.cell(0, 2)
467 .unwrap()
468 .vertical_alignment(VerticalAlignment::Bottom)
469 .shading("E2EFDA");
470 tbl.row(1).unwrap().cant_split();
471 tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
472 tbl.cell(1, 0).unwrap().no_wrap();
473 tbl.cell(1, 1).unwrap().set_text("Fixed width");
474 tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
475 tbl.cell(1, 2).unwrap().set_text("Normal");
476 }
477
478 // ── SECTION 6: IMAGES ──
479 doc.add_paragraph("").page_break_before(true);
480 doc.add_paragraph("6. Images").style("Heading1");
481
482 doc.add_paragraph("6.1 Inline Image").style("Heading2");
483 doc.add_paragraph("A blue gradient image below:");
484 let img = create_sample_png(200, 50, [0, 80, 200]);
485 doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
486
487 doc.add_paragraph("");
488 doc.add_paragraph("6.2 Header Image").style("Heading2");
489 let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
490 doc.set_header_image(
491 &hdr_img,
492 "header_logo.png",
493 Length::inches(2.0),
494 Length::inches(0.2),
495 );
496 doc.add_paragraph("The header now contains an inline image (check the top of this page).");
497
498 doc.add_paragraph("");
499 doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
500
501 // ── SECTION 7: CONTENT MANIPULATION ──
502 doc.add_paragraph("").page_break_before(true);
503 doc.add_paragraph("7. Content Manipulation")
504 .style("Heading1");
505
506 doc.add_paragraph("7.1 Placeholder Replacement")
507 .style("Heading2");
508 doc.add_paragraph("Customer: {{customer}}");
509 doc.add_paragraph("Date: {{date}}");
510 doc.add_paragraph("Reference: {{ref_number}}");
511 {
512 let mut tbl = doc
513 .add_table(2, 2)
514 .borders(BorderStyle::Single, 4, "000000");
515 tbl.cell(0, 0).unwrap().set_text("Project");
516 tbl.cell(0, 0).unwrap().shading("D6E4F0");
517 tbl.cell(0, 1).unwrap().set_text("{{project}}");
518 tbl.cell(1, 0).unwrap().set_text("Status");
519 tbl.cell(1, 0).unwrap().shading("D6E4F0");
520 tbl.cell(1, 1).unwrap().set_text("{{status}}");
521 }
522
523 let mut replacements = HashMap::new();
524 replacements.insert("{{customer}}", "Tensorbee Inc.");
525 replacements.insert("{{date}}", "February 22, 2026");
526 replacements.insert("{{ref_number}}", "TB-2026-001");
527 replacements.insert("{{project}}", "Infrastructure Upgrade");
528 replacements.insert("{{status}}", "In Progress");
529 let n = doc.replace_all(&replacements);
530 doc.add_paragraph(&format!("({n} placeholders replaced above)"));
531
532 doc.add_paragraph("");
533
534 doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
535 doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
536 let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
537 doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
538
539 doc.add_paragraph("");
540
541 doc.add_paragraph("7.3 Content Insertion").style("Heading2");
542 doc.add_paragraph("Section A: First content.");
543 doc.add_paragraph("Section C: Third content.");
544 if let Some(idx) = doc.find_content_index("Section C") {
545 doc.insert_paragraph(
546 idx,
547 "Section B: Inserted between A and C via find_content_index().",
548 );
549 }
550
551 // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
552 doc.add_paragraph("").section_break(SectionBreak::NextPage);
553 doc.add_paragraph("8. Section Breaks & Orientation")
554 .style("Heading1");
555 doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
556
557 {
558 let mut tbl = doc
559 .add_table(3, 7)
560 .borders(BorderStyle::Single, 4, "2E75B6");
561 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
562 for (c, h) in headers.iter().enumerate() {
563 tbl.cell(0, c).unwrap().set_text(h);
564 tbl.cell(0, c).unwrap().shading("2E75B6");
565 }
566 let data = [
567 [
568 "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
569 ],
570 [
571 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
572 ],
573 ];
574 for (r, row) in data.iter().enumerate() {
575 for (c, v) in row.iter().enumerate() {
576 tbl.cell(r + 1, c).unwrap().set_text(v);
577 }
578 }
579 }
580
581 doc.add_paragraph("")
582 .section_break(SectionBreak::NextPage)
583 .section_landscape();
584
585 // ── SECTION 9: CUSTOM STYLES ──
586 doc.add_paragraph("9. Custom Styles").style("Heading1");
587 doc.add_style(
588 StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
589 .based_on("Normal")
590 .paragraph_properties({
591 let mut ppr = rdocx_oxml::properties::CT_PPr::default();
592 ppr.shading = Some(rdocx_oxml::properties::CT_Shd {
593 val: "clear".to_string(),
594 color: None,
595 fill: Some("FFF2CC".to_string()),
596 });
597 ppr
598 })
599 .run_properties({
600 let mut rpr = rdocx_oxml::properties::CT_RPr::default();
601 rpr.bold = Some(true);
602 rpr.color = Some("C45911".to_string());
603 rpr
604 }),
605 );
606 doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
607 .style("CustomHighlight");
608
609 doc.add_paragraph("");
610
611 // ── SECTION 10: DOCUMENT INTELLIGENCE ──
612 doc.add_paragraph("10. Document Intelligence API")
613 .style("Heading1");
614
615 let wc = doc.word_count();
616 let hc = doc.headings().len();
617 let ic = doc.images().len();
618 let lc = doc.links().len();
619 doc.add_paragraph(&format!("Word count: {wc}"));
620 doc.add_paragraph(&format!("Heading count: {hc}"));
621 doc.add_paragraph(&format!("Image count: {ic}"));
622 doc.add_paragraph(&format!("Link count: {lc}"));
623
624 let outline = doc.document_outline();
625 doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
626
627 let issues = doc.audit_accessibility();
628 doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
629 for issue in &issues {
630 doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
631 }
632
633 // ── SECTION 11: DOCUMENT MERGING ──
634 doc.add_paragraph("");
635 doc.add_paragraph("11. Document Merging").style("Heading1");
636 {
637 let mut other = Document::new();
638 other.add_paragraph("This paragraph was merged from another document using append().");
639 doc.append(&other);
640 }
641
642 doc.add_paragraph("");
643
644 // ── SUMMARY ──
645 doc.add_paragraph("Summary of Demonstrated Features")
646 .style("Heading1");
647 let features = [
648 "Page setup: size, margins, header/footer distance, gutter",
649 "Document metadata: title, author, subject, keywords",
650 "Headers and footers: text, images, different first page",
651 "Background images (full-page behind text)",
652 "Table of Contents generation with bookmarks",
653 "Text formatting: bold, italic, underline styles, strike, color, size, font",
654 "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
655 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
656 "Pagination controls: keep-with-next, keep-together, widow control",
657 "Bullet and numbered lists with nesting",
658 "Tab stops with dot/underscore/hyphen leaders",
659 "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
660 "Row properties: header rows, exact height, cant-split",
661 "Cell properties: width, no-wrap, vertical alignment",
662 "Inline images and header images",
663 "Placeholder replacement (single and batch)",
664 "Regex-based find and replace",
665 "Content insertion at specific positions",
666 "Section breaks with mixed portrait/landscape",
667 "Custom paragraph and character styles",
668 "Document intelligence: word count, headings, outline, images, links",
669 "Accessibility audit",
670 "Document merging (append)",
671 "Export: DOCX, PDF, HTML, Markdown",
672 ];
673 for f in &features {
674 doc.add_bullet_list_item(f, 0);
675 }
676 doc.add_paragraph("");
677 doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
678 .alignment(Alignment::Center)
679 .shading("E2EFDA")
680 .border_all(BorderStyle::Single, 2, "00AA00");
681
682 doc
683}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.
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?
86fn generate_feature_showcase(_samples_dir: &Path) -> Document {
87 let mut doc = Document::new();
88
89 // ── Page Setup & Metadata ──
90 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
91 doc.set_margins(
92 Length::inches(1.0),
93 Length::inches(1.0),
94 Length::inches(1.0),
95 Length::inches(1.0),
96 );
97 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
98 doc.set_gutter(Length::twips(0));
99 doc.set_title("rdocx Feature Showcase");
100 doc.set_author("rdocx Sample Generator");
101 doc.set_subject("Comprehensive feature demonstration");
102 doc.set_keywords("rdocx, docx, rust, sample, showcase");
103
104 // Headers & Footers with different first page
105 doc.set_different_first_page(true);
106 doc.set_first_page_header("rdocx");
107 doc.set_first_page_footer("Feature Showcase — Cover Page");
108 doc.set_header("rdocx Feature Showcase");
109 doc.set_footer("Generated by rdocx");
110
111 // ── COVER PAGE ──
112 let bg = create_sample_png(612, 792, [20, 45, 90]);
113 doc.add_background_image(&bg, "cover_bg.png");
114
115 for _ in 0..3 {
116 doc.add_paragraph("");
117 }
118 {
119 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
120 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
121 }
122 {
123 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
124 p.add_run("Complete Feature Showcase")
125 .size(28.0)
126 .color("FFFFFF")
127 .italic(true);
128 }
129 doc.add_paragraph("");
130 {
131 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
132 p.add_run("Every feature of the rdocx Rust library")
133 .size(13.0)
134 .color("B0C4DE");
135 }
136 {
137 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
138 p.add_run("demonstrated in a single document.")
139 .size(13.0)
140 .color("B0C4DE");
141 }
142
143 // ── TABLE OF CONTENTS ──
144 doc.add_paragraph("").page_break_before(true);
145 doc.insert_toc(doc.content_count(), 3);
146
147 // ── SECTION 1: TEXT FORMATTING ──
148 doc.add_paragraph("").page_break_before(true);
149 doc.add_paragraph("1. Text Formatting").style("Heading1");
150
151 // Paragraph Alignment
152 doc.add_paragraph("1.1 Paragraph Alignment")
153 .style("Heading2");
154 doc.add_paragraph("Left-aligned paragraph (default).")
155 .alignment(Alignment::Left);
156 doc.add_paragraph("Center-aligned paragraph.")
157 .alignment(Alignment::Center);
158 doc.add_paragraph("Right-aligned paragraph.")
159 .alignment(Alignment::Right);
160 doc.add_paragraph(
161 "Justified paragraph — this text is long enough to demonstrate how justified alignment \
162 distributes extra space across word gaps so lines fill the full width of the text area.",
163 )
164 .alignment(Alignment::Justify);
165
166 doc.add_paragraph("");
167
168 // Run Formatting
169 doc.add_paragraph("1.2 Run Formatting").style("Heading2");
170 {
171 let mut p = doc.add_paragraph("");
172 p.add_run("Bold").bold(true);
173 p.add_run(" | ");
174 p.add_run("Italic").italic(true);
175 p.add_run(" | ");
176 p.add_run("Bold Italic").bold(true).italic(true);
177 p.add_run(" | ");
178 p.add_run("Underline").underline(true);
179 p.add_run(" | ");
180 p.add_run("Strikethrough").strike(true);
181 p.add_run(" | ");
182 p.add_run("Double Strike").double_strike(true);
183 }
184 {
185 let mut p = doc.add_paragraph("");
186 p.add_run("Red").color("FF0000");
187 p.add_run(" | ");
188 p.add_run("Blue").color("0000FF");
189 p.add_run(" | ");
190 p.add_run("Green").color("00AA00");
191 p.add_run(" | ");
192 p.add_run("Highlighted").highlight("FFFF00");
193 }
194 {
195 let mut p = doc.add_paragraph("");
196 p.add_run("8pt").size(8.0);
197 p.add_run(" | ");
198 p.add_run("11pt").size(11.0);
199 p.add_run(" | ");
200 p.add_run("16pt").size(16.0);
201 p.add_run(" | ");
202 p.add_run("24pt").size(24.0);
203 }
204 {
205 let mut p = doc.add_paragraph("");
206 p.add_run("Arial").font("Arial");
207 p.add_run(" | ");
208 p.add_run("Times New Roman").font("Times New Roman");
209 p.add_run(" | ");
210 p.add_run("Courier New").font("Courier New");
211 }
212 {
213 let mut p = doc.add_paragraph("");
214 p.add_run("H");
215 p.add_run("2").subscript();
216 p.add_run("O (subscript) | E=mc");
217 p.add_run("2").superscript();
218 p.add_run(" (superscript)");
219 }
220 {
221 let mut p = doc.add_paragraph("");
222 p.add_run("ALL CAPS").all_caps(true);
223 p.add_run(" | ");
224 p.add_run("Small Caps").small_caps(true);
225 p.add_run(" | ");
226 p.add_run("Expanded").character_spacing(Length::twips(40));
227 p.add_run(" | ");
228 p.add_run("Hidden text (not visible)").hidden(true);
229 }
230
231 doc.add_paragraph("");
232
233 // Underline Styles
234 doc.add_paragraph("1.3 Underline Styles").style("Heading2");
235 {
236 let mut p = doc.add_paragraph("");
237 p.add_run("Single ")
238 .underline_style(rdocx::UnderlineStyle::Single);
239 p.add_run("Double ")
240 .underline_style(rdocx::UnderlineStyle::Double);
241 p.add_run("Thick ")
242 .underline_style(rdocx::UnderlineStyle::Thick);
243 p.add_run("Dotted ")
244 .underline_style(rdocx::UnderlineStyle::Dotted);
245 p.add_run("Dash ")
246 .underline_style(rdocx::UnderlineStyle::Dash);
247 p.add_run("Wave ")
248 .underline_style(rdocx::UnderlineStyle::Wave);
249 }
250
251 // ── SECTION 2: PARAGRAPH FORMATTING ──
252 doc.add_paragraph("").page_break_before(true);
253 doc.add_paragraph("2. Paragraph Formatting")
254 .style("Heading1");
255
256 doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
257 doc.add_paragraph("Paragraph with light green shading")
258 .shading("E2EFDA");
259 doc.add_paragraph("Paragraph with bottom border")
260 .border_bottom(BorderStyle::Single, 6, "2E75B6");
261 doc.add_paragraph("Paragraph with all borders (red)")
262 .border_all(BorderStyle::Single, 4, "FF0000");
263
264 doc.add_paragraph("2.2 Indentation").style("Heading2");
265 doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
266 .indent_left(Length::inches(1.0))
267 .hanging_indent(Length::inches(0.5));
268 doc.add_paragraph("0.5-inch first-line indent on this paragraph")
269 .first_line_indent(Length::inches(0.5));
270 doc.add_paragraph("Both left and right indent (0.75 inches each)")
271 .indent_left(Length::inches(0.75))
272 .indent_right(Length::inches(0.75));
273
274 doc.add_paragraph("2.3 Spacing & Line Height")
275 .style("Heading2");
276 doc.add_paragraph("Extra space before (24pt) and after (12pt)")
277 .space_before(Length::pt(24.0))
278 .space_after(Length::pt(12.0));
279 doc.add_paragraph(
280 "Double line spacing paragraph. This text should have extra vertical space between \
281 lines to demonstrate line_spacing_multiple(2.0).",
282 )
283 .line_spacing_multiple(2.0);
284 doc.add_paragraph("Exact 20pt line spacing (fixed height).")
285 .line_spacing(20.0);
286
287 doc.add_paragraph("2.4 Pagination Controls")
288 .style("Heading2");
289 doc.add_paragraph("keep_with_next — this paragraph stays with the next")
290 .keep_with_next(true);
291 doc.add_paragraph("(This paragraph was kept with the one above.)");
292 doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
293 .keep_together(true);
294 doc.add_paragraph("widow_control — prevents widow/orphan lines")
295 .widow_control(true);
296
297 // ── SECTION 3: LISTS ──
298 doc.add_paragraph("").page_break_before(true);
299 doc.add_paragraph("3. Lists").style("Heading1");
300
301 doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
302 doc.add_bullet_list_item("First item", 0);
303 doc.add_bullet_list_item("Second item", 0);
304 doc.add_bullet_list_item("Nested level 1", 1);
305 doc.add_bullet_list_item("Nested level 2", 2);
306 doc.add_bullet_list_item("Back to level 1", 1);
307 doc.add_bullet_list_item("Third item", 0);
308
309 doc.add_paragraph("");
310
311 doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
312 doc.add_numbered_list_item("First numbered item", 0);
313 doc.add_numbered_list_item("Second numbered item", 0);
314 doc.add_numbered_list_item("Sub-item A", 1);
315 doc.add_numbered_list_item("Sub-item B", 1);
316 doc.add_numbered_list_item("Third numbered item", 0);
317
318 // ── SECTION 4: TAB STOPS ──
319 doc.add_paragraph("");
320 doc.add_paragraph("4. Tab Stops").style("Heading1");
321
322 doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
323 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
324 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
325 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
326 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
327 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
328
329 doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
330 doc.add_paragraph("Item\t\tPrice")
331 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
332 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
333 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
334 doc.add_paragraph("Widget\t\t$19.99")
335 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
336 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
337 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
338 doc.add_paragraph("Gadget\t\t$249.50")
339 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
340 .add_tab_stop_with_leader(
341 TabAlignment::Right,
342 Length::inches(4.0),
343 TabLeader::Underscore,
344 )
345 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
346
347 // ── SECTION 5: TABLES ──
348 doc.add_paragraph("").page_break_before(true);
349 doc.add_paragraph("5. Tables").style("Heading1");
350
351 doc.add_paragraph("5.1 Basic Table").style("Heading2");
352 {
353 let mut tbl = doc
354 .add_table(4, 3)
355 .borders(BorderStyle::Single, 4, "000000");
356 for col in 0..3 {
357 tbl.cell(0, col).unwrap().shading("2E75B6");
358 }
359 tbl.cell(0, 0).unwrap().set_text("Name");
360 tbl.cell(0, 1).unwrap().set_text("Role");
361 tbl.cell(0, 2).unwrap().set_text("Location");
362 tbl.cell(1, 0).unwrap().set_text("Walter White");
363 tbl.cell(1, 1).unwrap().set_text("CEO");
364 tbl.cell(1, 2).unwrap().set_text("Albuquerque");
365 tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
366 tbl.cell(2, 1).unwrap().set_text("CTO");
367 tbl.cell(2, 2).unwrap().set_text("Remote");
368 tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
369 tbl.cell(3, 1).unwrap().set_text("Security");
370 tbl.cell(3, 2).unwrap().set_text("Washington");
371 }
372
373 doc.add_paragraph("");
374
375 doc.add_paragraph("5.2 Column Span & Cell Shading")
376 .style("Heading2");
377 {
378 let mut tbl = doc
379 .add_table(3, 4)
380 .borders(BorderStyle::Single, 4, "000000")
381 .width_pct(100.0);
382 tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
383 tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
384 tbl.cell(1, 0).unwrap().set_text("Region");
385 tbl.cell(1, 0).unwrap().shading("D6E4F0");
386 tbl.cell(1, 1).unwrap().set_text("Q1");
387 tbl.cell(1, 1).unwrap().shading("D6E4F0");
388 tbl.cell(1, 2).unwrap().set_text("Q2");
389 tbl.cell(1, 2).unwrap().shading("D6E4F0");
390 tbl.cell(1, 3).unwrap().set_text("Total");
391 tbl.cell(1, 3).unwrap().shading("D6E4F0");
392 tbl.cell(2, 0).unwrap().set_text("Americas");
393 tbl.cell(2, 1).unwrap().set_text("$2.4M");
394 tbl.cell(2, 2).unwrap().set_text("$2.7M");
395 tbl.cell(2, 3).unwrap().set_text("$5.1M");
396 }
397
398 doc.add_paragraph("");
399
400 doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
401 {
402 let mut tbl = doc
403 .add_table(4, 3)
404 .borders(BorderStyle::Single, 4, "000000");
405 tbl.cell(0, 0).unwrap().set_text("Category");
406 tbl.cell(0, 0).unwrap().shading("E2EFDA");
407 tbl.cell(0, 1).unwrap().set_text("Item");
408 tbl.cell(0, 1).unwrap().shading("E2EFDA");
409 tbl.cell(0, 2).unwrap().set_text("Price");
410 tbl.cell(0, 2).unwrap().shading("E2EFDA");
411 tbl.cell(1, 0).unwrap().set_text("Hardware");
412 tbl.cell(1, 0).unwrap().v_merge_restart();
413 tbl.cell(1, 1).unwrap().set_text("Laptop");
414 tbl.cell(1, 2).unwrap().set_text("$1,200");
415 tbl.cell(2, 0).unwrap().v_merge_continue();
416 tbl.cell(2, 1).unwrap().set_text("Monitor");
417 tbl.cell(2, 2).unwrap().set_text("$450");
418 tbl.cell(3, 0).unwrap().set_text("Software");
419 tbl.cell(3, 1).unwrap().set_text("IDE License");
420 tbl.cell(3, 2).unwrap().set_text("$200/yr");
421 }
422
423 doc.add_paragraph("");
424
425 doc.add_paragraph("5.4 Nested Table").style("Heading2");
426 {
427 let mut tbl = doc
428 .add_table(2, 2)
429 .borders(BorderStyle::Single, 6, "2E75B6");
430 tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
431 tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
432 tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
433 {
434 let mut cell = tbl.cell(1, 1).unwrap();
435 cell.set_text("Nested table below:");
436 let mut nested = cell
437 .add_table(2, 2)
438 .borders(BorderStyle::Single, 2, "FF6600");
439 nested.cell(0, 0).unwrap().set_text("A");
440 nested.cell(0, 1).unwrap().set_text("B");
441 nested.cell(1, 0).unwrap().set_text("C");
442 nested.cell(1, 1).unwrap().set_text("D");
443 }
444 }
445
446 doc.add_paragraph("");
447
448 doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
449 .style("Heading2");
450 {
451 let mut tbl = doc
452 .add_table(2, 3)
453 .borders(BorderStyle::Single, 4, "666666");
454 tbl.row(0).unwrap().height(Length::pt(50.0)).header();
455 tbl.cell(0, 0).unwrap().set_text("Top");
456 tbl.cell(0, 0)
457 .unwrap()
458 .vertical_alignment(VerticalAlignment::Top)
459 .shading("FFF2CC");
460 tbl.cell(0, 1).unwrap().set_text("Center");
461 tbl.cell(0, 1)
462 .unwrap()
463 .vertical_alignment(VerticalAlignment::Center)
464 .shading("D9E2F3");
465 tbl.cell(0, 2).unwrap().set_text("Bottom");
466 tbl.cell(0, 2)
467 .unwrap()
468 .vertical_alignment(VerticalAlignment::Bottom)
469 .shading("E2EFDA");
470 tbl.row(1).unwrap().cant_split();
471 tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
472 tbl.cell(1, 0).unwrap().no_wrap();
473 tbl.cell(1, 1).unwrap().set_text("Fixed width");
474 tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
475 tbl.cell(1, 2).unwrap().set_text("Normal");
476 }
477
478 // ── SECTION 6: IMAGES ──
479 doc.add_paragraph("").page_break_before(true);
480 doc.add_paragraph("6. Images").style("Heading1");
481
482 doc.add_paragraph("6.1 Inline Image").style("Heading2");
483 doc.add_paragraph("A blue gradient image below:");
484 let img = create_sample_png(200, 50, [0, 80, 200]);
485 doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
486
487 doc.add_paragraph("");
488 doc.add_paragraph("6.2 Header Image").style("Heading2");
489 let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
490 doc.set_header_image(
491 &hdr_img,
492 "header_logo.png",
493 Length::inches(2.0),
494 Length::inches(0.2),
495 );
496 doc.add_paragraph("The header now contains an inline image (check the top of this page).");
497
498 doc.add_paragraph("");
499 doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
500
501 // ── SECTION 7: CONTENT MANIPULATION ──
502 doc.add_paragraph("").page_break_before(true);
503 doc.add_paragraph("7. Content Manipulation")
504 .style("Heading1");
505
506 doc.add_paragraph("7.1 Placeholder Replacement")
507 .style("Heading2");
508 doc.add_paragraph("Customer: {{customer}}");
509 doc.add_paragraph("Date: {{date}}");
510 doc.add_paragraph("Reference: {{ref_number}}");
511 {
512 let mut tbl = doc
513 .add_table(2, 2)
514 .borders(BorderStyle::Single, 4, "000000");
515 tbl.cell(0, 0).unwrap().set_text("Project");
516 tbl.cell(0, 0).unwrap().shading("D6E4F0");
517 tbl.cell(0, 1).unwrap().set_text("{{project}}");
518 tbl.cell(1, 0).unwrap().set_text("Status");
519 tbl.cell(1, 0).unwrap().shading("D6E4F0");
520 tbl.cell(1, 1).unwrap().set_text("{{status}}");
521 }
522
523 let mut replacements = HashMap::new();
524 replacements.insert("{{customer}}", "Tensorbee Inc.");
525 replacements.insert("{{date}}", "February 22, 2026");
526 replacements.insert("{{ref_number}}", "TB-2026-001");
527 replacements.insert("{{project}}", "Infrastructure Upgrade");
528 replacements.insert("{{status}}", "In Progress");
529 let n = doc.replace_all(&replacements);
530 doc.add_paragraph(&format!("({n} placeholders replaced above)"));
531
532 doc.add_paragraph("");
533
534 doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
535 doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
536 let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
537 doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
538
539 doc.add_paragraph("");
540
541 doc.add_paragraph("7.3 Content Insertion").style("Heading2");
542 doc.add_paragraph("Section A: First content.");
543 doc.add_paragraph("Section C: Third content.");
544 if let Some(idx) = doc.find_content_index("Section C") {
545 doc.insert_paragraph(
546 idx,
547 "Section B: Inserted between A and C via find_content_index().",
548 );
549 }
550
551 // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
552 doc.add_paragraph("").section_break(SectionBreak::NextPage);
553 doc.add_paragraph("8. Section Breaks & Orientation")
554 .style("Heading1");
555 doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
556
557 {
558 let mut tbl = doc
559 .add_table(3, 7)
560 .borders(BorderStyle::Single, 4, "2E75B6");
561 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
562 for (c, h) in headers.iter().enumerate() {
563 tbl.cell(0, c).unwrap().set_text(h);
564 tbl.cell(0, c).unwrap().shading("2E75B6");
565 }
566 let data = [
567 [
568 "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
569 ],
570 [
571 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
572 ],
573 ];
574 for (r, row) in data.iter().enumerate() {
575 for (c, v) in row.iter().enumerate() {
576 tbl.cell(r + 1, c).unwrap().set_text(v);
577 }
578 }
579 }
580
581 doc.add_paragraph("")
582 .section_break(SectionBreak::NextPage)
583 .section_landscape();
584
585 // ── SECTION 9: CUSTOM STYLES ──
586 doc.add_paragraph("9. Custom Styles").style("Heading1");
587 doc.add_style(
588 StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
589 .based_on("Normal")
590 .paragraph_properties({
591 let mut ppr = rdocx_oxml::properties::CT_PPr::default();
592 ppr.shading = Some(rdocx_oxml::properties::CT_Shd {
593 val: "clear".to_string(),
594 color: None,
595 fill: Some("FFF2CC".to_string()),
596 });
597 ppr
598 })
599 .run_properties({
600 let mut rpr = rdocx_oxml::properties::CT_RPr::default();
601 rpr.bold = Some(true);
602 rpr.color = Some("C45911".to_string());
603 rpr
604 }),
605 );
606 doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
607 .style("CustomHighlight");
608
609 doc.add_paragraph("");
610
611 // ── SECTION 10: DOCUMENT INTELLIGENCE ──
612 doc.add_paragraph("10. Document Intelligence API")
613 .style("Heading1");
614
615 let wc = doc.word_count();
616 let hc = doc.headings().len();
617 let ic = doc.images().len();
618 let lc = doc.links().len();
619 doc.add_paragraph(&format!("Word count: {wc}"));
620 doc.add_paragraph(&format!("Heading count: {hc}"));
621 doc.add_paragraph(&format!("Image count: {ic}"));
622 doc.add_paragraph(&format!("Link count: {lc}"));
623
624 let outline = doc.document_outline();
625 doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
626
627 let issues = doc.audit_accessibility();
628 doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
629 for issue in &issues {
630 doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
631 }
632
633 // ── SECTION 11: DOCUMENT MERGING ──
634 doc.add_paragraph("");
635 doc.add_paragraph("11. Document Merging").style("Heading1");
636 {
637 let mut other = Document::new();
638 other.add_paragraph("This paragraph was merged from another document using append().");
639 doc.append(&other);
640 }
641
642 doc.add_paragraph("");
643
644 // ── SUMMARY ──
645 doc.add_paragraph("Summary of Demonstrated Features")
646 .style("Heading1");
647 let features = [
648 "Page setup: size, margins, header/footer distance, gutter",
649 "Document metadata: title, author, subject, keywords",
650 "Headers and footers: text, images, different first page",
651 "Background images (full-page behind text)",
652 "Table of Contents generation with bookmarks",
653 "Text formatting: bold, italic, underline styles, strike, color, size, font",
654 "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
655 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
656 "Pagination controls: keep-with-next, keep-together, widow control",
657 "Bullet and numbered lists with nesting",
658 "Tab stops with dot/underscore/hyphen leaders",
659 "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
660 "Row properties: header rows, exact height, cant-split",
661 "Cell properties: width, no-wrap, vertical alignment",
662 "Inline images and header images",
663 "Placeholder replacement (single and batch)",
664 "Regex-based find and replace",
665 "Content insertion at specific positions",
666 "Section breaks with mixed portrait/landscape",
667 "Custom paragraph and character styles",
668 "Document intelligence: word count, headings, outline, images, links",
669 "Accessibility audit",
670 "Document merging (append)",
671 "Export: DOCX, PDF, HTML, Markdown",
672 ];
673 for f in &features {
674 doc.add_bullet_list_item(f, 0);
675 }
676 doc.add_paragraph("");
677 doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
678 .alignment(Alignment::Center)
679 .shading("E2EFDA")
680 .border_all(BorderStyle::Single, 2, "00AA00");
681
682 doc
683}
684
685// =============================================================================
686// 2. PROPOSAL DOCUMENT — Deep navy + gold scheme
687// =============================================================================
688fn generate_proposal(_samples_dir: &Path) -> Document {
689 let mut doc = Document::new();
690
691 // Colors: Navy #1B2A4A, Gold #C5922E, Light #F4F1EB
692 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
693 doc.set_margins(
694 Length::inches(1.0),
695 Length::inches(1.0),
696 Length::inches(1.0),
697 Length::inches(1.0),
698 );
699 doc.set_title("AI Platform Modernization Proposal");
700 doc.set_author("Walter White");
701 doc.set_subject("Technology Proposal");
702 doc.set_keywords("proposal, AI, modernization, Tensorbee");
703
704 // Banner header
705 let logo_img = create_logo_png(220, 48);
706 let banner = build_header_banner_xml(
707 "rId1",
708 &BannerOpts {
709 bg_color: "1B2A4A",
710 banner_width: 7772400,
711 banner_height: 914400,
712 logo_width: 2011680,
713 logo_height: 438912,
714 logo_x_offset: 295125,
715 logo_y_offset: 237744,
716 },
717 );
718 doc.set_raw_header_with_images(
719 banner,
720 &[("rId1", &logo_img, "logo.png")],
721 rdocx_oxml::header_footer::HdrFtrType::Default,
722 );
723 doc.set_different_first_page(true);
724 let cover_banner = build_header_banner_xml(
725 "rId1",
726 &BannerOpts {
727 bg_color: "C5922E",
728 banner_width: 7772400,
729 banner_height: 914400,
730 logo_width: 2011680,
731 logo_height: 438912,
732 logo_x_offset: 295125,
733 logo_y_offset: 237744,
734 },
735 );
736 doc.set_raw_header_with_images(
737 cover_banner,
738 &[("rId1", &logo_img, "logo.png")],
739 rdocx_oxml::header_footer::HdrFtrType::First,
740 );
741 doc.set_margins(
742 Length::twips(2292),
743 Length::twips(1440),
744 Length::twips(1440),
745 Length::twips(1440),
746 );
747 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
748 doc.set_footer("Tensorbee — Confidential");
749
750 // Custom styles
751 doc.add_style(
752 StyleBuilder::paragraph("ProposalTitle", "Proposal Title")
753 .based_on("Normal")
754 .run_properties({
755 let mut rpr = rdocx_oxml::properties::CT_RPr::default();
756 rpr.bold = Some(true);
757 rpr.font_ascii = Some("Georgia".to_string());
758 rpr.font_hansi = Some("Georgia".to_string());
759 rpr.sz = Some(rdocx_oxml::HalfPoint(56)); // half-points → 28pt
760 rpr.color = Some("1B2A4A".to_string());
761 rpr
762 }),
763 );
764
765 // ── Cover Page ──
766 for _ in 0..4 {
767 doc.add_paragraph("");
768 }
769 doc.add_paragraph("AI Platform Modernization")
770 .style("ProposalTitle")
771 .alignment(Alignment::Center);
772 doc.add_paragraph("")
773 .alignment(Alignment::Center)
774 .border_bottom(BorderStyle::Single, 8, "C5922E");
775 {
776 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
777 p.add_run("Prepared for Global Financial Corp.")
778 .size(14.0)
779 .color("666666")
780 .italic(true);
781 }
782 {
783 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
784 p.add_run("Prepared by: Walter White, CEO — Tensorbee")
785 .size(12.0)
786 .color("888888");
787 }
788 {
789 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
790 p.add_run("February 22, 2026").size(12.0).color("888888");
791 }
792
793 // ── TOC ──
794 doc.add_paragraph("").page_break_before(true);
795 doc.add_paragraph("Table of Contents").style("Heading1");
796 // We'll add a manual-style TOC since the insert_toc goes at a specific position
797 doc.insert_toc(doc.content_count(), 2);
798
799 // ── Executive Summary ──
800 doc.add_paragraph("").page_break_before(true);
801 doc.add_paragraph("Executive Summary").style("Heading1");
802 doc.add_paragraph(
803 "Tensorbee proposes a comprehensive modernization of Global Financial Corp's AI platform, \
804 transitioning from legacy batch-processing systems to a real-time inference architecture. \
805 This transformation will reduce model deployment time from weeks to hours, improve \
806 prediction accuracy by an estimated 23%, and deliver $4.2M in annual operational savings.",
807 )
808 .first_line_indent(Length::inches(0.3));
809 doc.add_paragraph(
810 "The project spans three phases over 18 months, with a total investment of $2.8M. \
811 Tensorbee brings deep expertise in ML infrastructure, having successfully completed \
812 similar transformations for three Fortune 500 financial institutions.",
813 )
814 .first_line_indent(Length::inches(0.3));
815
816 // Key metrics table
817 doc.add_paragraph("");
818 {
819 let mut tbl = doc
820 .add_table(5, 2)
821 .borders(BorderStyle::Single, 4, "1B2A4A")
822 .width_pct(80.0);
823 tbl.cell(0, 0).unwrap().set_text("Key Metric");
824 tbl.cell(0, 0).unwrap().shading("1B2A4A");
825 tbl.cell(0, 1).unwrap().set_text("Value");
826 tbl.cell(0, 1).unwrap().shading("1B2A4A");
827 let rows = [
828 ("Total Investment", "$2.8M"),
829 ("Annual Savings", "$4.2M"),
830 ("ROI (Year 1)", "150%"),
831 ("Timeline", "18 months"),
832 ];
833 for (i, (k, v)) in rows.iter().enumerate() {
834 tbl.cell(i + 1, 0).unwrap().set_text(k);
835 if i % 2 == 0 {
836 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
837 }
838 tbl.cell(i + 1, 1).unwrap().set_text(v);
839 if i % 2 == 0 {
840 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
841 }
842 }
843 }
844
845 // ── Problem Statement ──
846 doc.add_paragraph("").page_break_before(true);
847 doc.add_paragraph("Problem Statement").style("Heading1");
848 doc.add_paragraph(
849 "Global Financial Corp's current AI infrastructure faces three critical challenges:",
850 );
851 doc.add_numbered_list_item("Deployment Latency — New models take 3-4 weeks to deploy to production, compared to the industry benchmark of 2-3 days.", 0);
852 doc.add_numbered_list_item("Scalability Constraints — The batch processing architecture cannot handle real-time inference demands during peak trading hours.", 0);
853 doc.add_numbered_list_item("Technical Debt — Legacy Python 2.7 codebases and manual deployment scripts create reliability risks and slow iteration speed.", 0);
854
855 // ── Proposed Solution ──
856 doc.add_paragraph("").page_break_before(true);
857 doc.add_paragraph("Proposed Solution").style("Heading1");
858
859 doc.add_paragraph("Phase 1: Foundation (Months 1-6)")
860 .style("Heading2");
861 doc.add_bullet_list_item("Deploy Kubernetes-based ML serving infrastructure", 0);
862 doc.add_bullet_list_item("Implement CI/CD pipeline for model deployment", 0);
863 doc.add_bullet_list_item("Migrate top 5 production models to new platform", 0);
864
865 doc.add_paragraph("Phase 2: Expansion (Months 7-12)")
866 .style("Heading2");
867 doc.add_bullet_list_item(
868 "Real-time feature engineering pipeline (Apache Kafka + Flink)",
869 0,
870 );
871 doc.add_bullet_list_item("A/B testing framework for model variants", 0);
872 doc.add_bullet_list_item("Automated model monitoring and drift detection", 0);
873
874 doc.add_paragraph("Phase 3: Optimization (Months 13-18)")
875 .style("Heading2");
876 doc.add_bullet_list_item("GPU inference optimization (TensorRT/ONNX Runtime)", 0);
877 doc.add_bullet_list_item("Multi-region deployment for latency reduction", 0);
878 doc.add_bullet_list_item(
879 "Self-service model deployment portal for data science teams",
880 0,
881 );
882
883 // ── Budget ──
884 doc.add_paragraph("Budget Breakdown").style("Heading1");
885 {
886 let mut tbl = doc
887 .add_table(6, 3)
888 .borders(BorderStyle::Single, 4, "1B2A4A")
889 .width_pct(100.0);
890 tbl.cell(0, 0).unwrap().set_text("Category");
891 tbl.cell(0, 0).unwrap().shading("1B2A4A");
892 tbl.cell(0, 1).unwrap().set_text("Cost");
893 tbl.cell(0, 1).unwrap().shading("1B2A4A");
894 tbl.cell(0, 2).unwrap().set_text("Notes");
895 tbl.cell(0, 2).unwrap().shading("1B2A4A");
896 let items = [
897 (
898 "Infrastructure",
899 "$450,000",
900 "Cloud compute + GPU instances",
901 ),
902 ("Engineering Services", "$1,600,000", "12 FTE x 18 months"),
903 (
904 "Software Licenses",
905 "$350,000",
906 "Kafka, monitoring, CI/CD tools",
907 ),
908 (
909 "Training & Enablement",
910 "$200,000",
911 "Team training, documentation",
912 ),
913 ("Contingency (10%)", "$200,000", "Risk buffer"),
914 ];
915 for (i, (cat, cost, note)) in items.iter().enumerate() {
916 tbl.cell(i + 1, 0).unwrap().set_text(cat);
917 tbl.cell(i + 1, 1).unwrap().set_text(cost);
918 tbl.cell(i + 1, 2).unwrap().set_text(note);
919 if i % 2 == 0 {
920 tbl.cell(i + 1, 0).unwrap().shading("F4F1EB");
921 tbl.cell(i + 1, 1).unwrap().shading("F4F1EB");
922 tbl.cell(i + 1, 2).unwrap().shading("F4F1EB");
923 }
924 }
925 }
926
927 // ── Closing ──
928 doc.add_paragraph("");
929 doc.add_paragraph("Next Steps").style("Heading1");
930 doc.add_numbered_list_item(
931 "Schedule technical deep-dive with GFC engineering team (Week 1)",
932 0,
933 );
934 doc.add_numbered_list_item("Finalize scope and sign SOW (Week 2-3)", 0);
935 doc.add_numbered_list_item("Kick off Phase 1 with joint planning session (Week 4)", 0);
936
937 doc.add_paragraph("");
938 {
939 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
940 p.add_run("Walter White")
941 .bold(true)
942 .size(14.0)
943 .color("1B2A4A");
944 }
945 {
946 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
947 p.add_run("CEO, Tensorbee | walter@tensorbee.com")
948 .size(10.0)
949 .color("888888");
950 }
951
952 doc
953}
954
955// =============================================================================
956// 3. QUOTE / BILL OF MATERIALS — Teal + orange scheme
957// =============================================================================
958fn generate_quote(_samples_dir: &Path) -> Document {
959 let mut doc = Document::new();
960
961 // Colors: Teal #008B8B, Dark #1A3C3C, Orange #E07020, Light #F0F8F8
962 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
963 doc.set_margins(
964 Length::inches(0.75),
965 Length::inches(0.75),
966 Length::inches(0.75),
967 Length::inches(0.75),
968 );
969 doc.set_title("Quotation QT-2026-0147");
970 doc.set_author("Walter White");
971 doc.set_keywords("quote, BOM, Tensorbee");
972
973 doc.set_header("Tensorbee — Quotation");
974 doc.set_footer("QT-2026-0147 | Page");
975
976 // ── Header Block ──
977 {
978 let mut p = doc.add_paragraph("").alignment(Alignment::Left);
979 p.add_run("TENSORBEE")
980 .bold(true)
981 .size(28.0)
982 .color("008B8B")
983 .font("Helvetica");
984 }
985 doc.add_paragraph("123 Innovation Drive, Suite 400")
986 .alignment(Alignment::Left);
987 doc.add_paragraph("San Francisco, CA 94105 | +1 (415) 555-0199")
988 .alignment(Alignment::Left);
989 doc.add_paragraph("")
990 .border_bottom(BorderStyle::Single, 8, "008B8B");
991
992 // Quote meta
993 doc.add_paragraph("");
994 {
995 let mut tbl = doc.add_table(4, 4).width_pct(100.0);
996 tbl.cell(0, 0).unwrap().set_text("QUOTATION");
997 tbl.cell(0, 0).unwrap().shading("008B8B").grid_span(2);
998 tbl.cell(0, 2).unwrap().set_text("DATE");
999 tbl.cell(0, 2).unwrap().shading("008B8B");
1000 tbl.cell(0, 3).unwrap().set_text("VALID UNTIL");
1001 tbl.cell(0, 3).unwrap().shading("008B8B");
1002 tbl.cell(1, 0).unwrap().set_text("Quote #:");
1003 tbl.cell(1, 1).unwrap().set_text("QT-2026-0147");
1004 tbl.cell(1, 2).unwrap().set_text("Feb 22, 2026");
1005 tbl.cell(1, 3).unwrap().set_text("Mar 22, 2026");
1006 tbl.cell(2, 0).unwrap().set_text("Prepared by:");
1007 tbl.cell(2, 1).unwrap().set_text("Walter White");
1008 tbl.cell(2, 2).unwrap().set_text("Payment:");
1009 tbl.cell(2, 3).unwrap().set_text("Net 30");
1010 tbl.cell(3, 0).unwrap().set_text("Customer:");
1011 tbl.cell(3, 1).unwrap().set_text("Meridian Dynamics LLC");
1012 tbl.cell(3, 2).unwrap().set_text("Currency:");
1013 tbl.cell(3, 3).unwrap().set_text("USD");
1014 }
1015
1016 doc.add_paragraph("");
1017
1018 // ── Bill of Materials ──
1019 {
1020 let mut p = doc.add_paragraph("");
1021 p.add_run("Bill of Materials")
1022 .bold(true)
1023 .size(16.0)
1024 .color("1A3C3C");
1025 }
1026 doc.add_paragraph("");
1027
1028 {
1029 let mut tbl = doc
1030 .add_table(10, 6)
1031 .borders(BorderStyle::Single, 2, "008B8B")
1032 .width_pct(100.0)
1033 .layout_fixed();
1034 // Headers
1035 let hdrs = [
1036 "#",
1037 "Part Number",
1038 "Description",
1039 "Qty",
1040 "Unit Price",
1041 "Total",
1042 ];
1043 for (c, h) in hdrs.iter().enumerate() {
1044 tbl.cell(0, c).unwrap().set_text(h);
1045 tbl.cell(0, c).unwrap().shading("008B8B");
1046 }
1047
1048 let items: Vec<(&str, &str, &str, &str, &str)> = vec![
1049 (
1050 "TB-GPU-A100",
1051 "NVIDIA A100 80GB GPU",
1052 "4",
1053 "$12,500.00",
1054 "$50,000.00",
1055 ),
1056 (
1057 "TB-SRV-R750",
1058 "Dell R750xa Server Chassis",
1059 "2",
1060 "$8,200.00",
1061 "$16,400.00",
1062 ),
1063 (
1064 "TB-RAM-256G",
1065 "256GB DDR5 ECC Memory Module",
1066 "8",
1067 "$890.00",
1068 "$7,120.00",
1069 ),
1070 (
1071 "TB-SSD-3840",
1072 "3.84TB NVMe U.2 SSD",
1073 "8",
1074 "$1,150.00",
1075 "$9,200.00",
1076 ),
1077 (
1078 "TB-NET-CX7",
1079 "ConnectX-7 200GbE NIC",
1080 "4",
1081 "$1,800.00",
1082 "$7,200.00",
1083 ),
1084 (
1085 "TB-SW-48P",
1086 "48-Port 100GbE Switch",
1087 "1",
1088 "$22,000.00",
1089 "$22,000.00",
1090 ),
1091 (
1092 "TB-CAB-RACK",
1093 "42U Server Rack + PDU",
1094 "1",
1095 "$4,500.00",
1096 "$4,500.00",
1097 ),
1098 (
1099 "TB-SVC-INST",
1100 "Installation & Configuration",
1101 "1",
1102 "$8,500.00",
1103 "$8,500.00",
1104 ),
1105 (
1106 "TB-SVC-SUPP",
1107 "3-Year Premium Support",
1108 "1",
1109 "$15,000.00",
1110 "$15,000.00",
1111 ),
1112 ];
1113
1114 for (i, (pn, desc, qty, unit, total)) in items.iter().enumerate() {
1115 let row = i + 1;
1116 tbl.cell(row, 0).unwrap().set_text(&format!("{}", i + 1));
1117 tbl.cell(row, 1).unwrap().set_text(pn);
1118 tbl.cell(row, 2).unwrap().set_text(desc);
1119 tbl.cell(row, 3).unwrap().set_text(qty);
1120 tbl.cell(row, 4).unwrap().set_text(unit);
1121 tbl.cell(row, 5).unwrap().set_text(total);
1122 if i % 2 == 0 {
1123 for c in 0..6 {
1124 tbl.cell(row, c).unwrap().shading("F0F8F8");
1125 }
1126 }
1127 }
1128 }
1129
1130 doc.add_paragraph("");
1131
1132 // ── Totals ──
1133 {
1134 let mut tbl = doc
1135 .add_table(4, 2)
1136 .borders(BorderStyle::Single, 2, "008B8B")
1137 .width(Length::inches(3.5))
1138 .alignment(Alignment::Right);
1139 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1140 tbl.cell(0, 1).unwrap().set_text("$139,920.00");
1141 tbl.cell(1, 0).unwrap().set_text("Shipping & Handling");
1142 tbl.cell(1, 1).unwrap().set_text("$2,500.00");
1143 tbl.cell(2, 0).unwrap().set_text("Tax (8.625%)");
1144 tbl.cell(2, 1).unwrap().set_text("$12,068.10");
1145 tbl.cell(3, 0).unwrap().set_text("TOTAL");
1146 tbl.cell(3, 0).unwrap().shading("E07020");
1147 tbl.cell(3, 1).unwrap().set_text("$154,488.10");
1148 tbl.cell(3, 1).unwrap().shading("E07020");
1149 }
1150
1151 doc.add_paragraph("");
1152
1153 // ── Terms & Conditions ──
1154 {
1155 let mut p = doc.add_paragraph("");
1156 p.add_run("Terms & Conditions")
1157 .bold(true)
1158 .size(14.0)
1159 .color("1A3C3C");
1160 }
1161 doc.add_numbered_list_item(
1162 "This quotation is valid for 30 calendar days from the date of issue.",
1163 0,
1164 );
1165 doc.add_numbered_list_item(
1166 "All prices are in USD and exclusive of applicable taxes unless stated.",
1167 0,
1168 );
1169 doc.add_numbered_list_item("Standard lead time is 4-6 weeks from PO receipt.", 0);
1170 doc.add_numbered_list_item(
1171 "Payment terms: Net 30 from invoice date. 2% discount for payment within 10 days.",
1172 0,
1173 );
1174 doc.add_numbered_list_item(
1175 "Warranty: 3-year manufacturer warranty on all hardware components.",
1176 0,
1177 );
1178 doc.add_numbered_list_item(
1179 "Returns subject to 15% restocking fee if initiated after 14 days.",
1180 0,
1181 );
1182
1183 doc.add_paragraph("");
1184 doc.add_paragraph("")
1185 .border_bottom(BorderStyle::Single, 4, "008B8B");
1186 doc.add_paragraph("");
1187 {
1188 let mut p = doc.add_paragraph("");
1189 p.add_run("Accepted by: ").bold(true);
1190 p.add_run("___________________________ Date: ___________");
1191 }
1192 {
1193 let mut p = doc.add_paragraph("");
1194 p.add_run("Print Name: ").bold(true);
1195 p.add_run("___________________________ Title: ___________");
1196 }
1197
1198 doc
1199}
1200
1201// =============================================================================
1202// 4. INVOICE — Crimson + charcoal scheme
1203// =============================================================================
1204fn generate_invoice(_samples_dir: &Path) -> Document {
1205 let mut doc = Document::new();
1206
1207 // Colors: Crimson #B22222, Charcoal #333333, Light #FAF0F0
1208 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1209 doc.set_margins(
1210 Length::inches(0.75),
1211 Length::inches(0.75),
1212 Length::inches(0.75),
1213 Length::inches(0.75),
1214 );
1215 doc.set_title("Invoice INV-2026-0392");
1216 doc.set_author("Walter White");
1217 doc.set_keywords("invoice, Tensorbee");
1218
1219 doc.set_footer("Tensorbee — Thank you for your business!");
1220
1221 // ── Company & Invoice Header ──
1222 {
1223 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1224 // Company name left, INVOICE right
1225 {
1226 let mut cell = tbl.cell(0, 0).unwrap();
1227 let mut p = cell.add_paragraph("");
1228 p.add_run("TENSORBEE")
1229 .bold(true)
1230 .size(32.0)
1231 .color("B22222")
1232 .font("Helvetica");
1233 }
1234 {
1235 let mut cell = tbl.cell(0, 1).unwrap();
1236 let mut p = cell.add_paragraph("");
1237 p.add_run("INVOICE").bold(true).size(32.0).color("333333");
1238 }
1239 tbl.cell(1, 0)
1240 .unwrap()
1241 .set_text("123 Innovation Drive, Suite 400");
1242 tbl.cell(1, 1).unwrap().set_text("Invoice #: INV-2026-0392");
1243 tbl.cell(2, 0).unwrap().set_text("San Francisco, CA 94105");
1244 tbl.cell(2, 1).unwrap().set_text("Date: February 22, 2026");
1245 tbl.cell(3, 0).unwrap().set_text("walter@tensorbee.com");
1246 tbl.cell(3, 1).unwrap().set_text("Due Date: March 24, 2026");
1247 }
1248
1249 doc.add_paragraph("")
1250 .border_bottom(BorderStyle::Thick, 8, "B22222");
1251 doc.add_paragraph("");
1252
1253 // ── Bill To / Ship To ──
1254 {
1255 let mut tbl = doc.add_table(4, 2).width_pct(100.0);
1256 tbl.cell(0, 0).unwrap().set_text("BILL TO");
1257 tbl.cell(0, 0).unwrap().shading("B22222");
1258 tbl.cell(0, 1).unwrap().set_text("SHIP TO");
1259 tbl.cell(0, 1).unwrap().shading("B22222");
1260 tbl.cell(1, 0).unwrap().set_text("Meridian Dynamics LLC");
1261 tbl.cell(1, 1).unwrap().set_text("Meridian Dynamics LLC");
1262 tbl.cell(2, 0).unwrap().set_text("456 Enterprise Blvd");
1263 tbl.cell(2, 1).unwrap().set_text("Attn: Server Room B");
1264 tbl.cell(3, 0).unwrap().set_text("Austin, TX 78701");
1265 tbl.cell(3, 1)
1266 .unwrap()
1267 .set_text("456 Enterprise Blvd, Austin, TX 78701");
1268 }
1269
1270 doc.add_paragraph("");
1271
1272 // ── Line Items ──
1273 {
1274 let mut tbl = doc
1275 .add_table(8, 5)
1276 .borders(BorderStyle::Single, 2, "B22222")
1277 .width_pct(100.0);
1278 let hdrs = ["Description", "Qty", "Unit Price", "Tax", "Amount"];
1279 for (c, h) in hdrs.iter().enumerate() {
1280 tbl.cell(0, c).unwrap().set_text(h);
1281 tbl.cell(0, c).unwrap().shading("B22222");
1282 }
1283 let items = [
1284 (
1285 "ML Infrastructure Setup — Phase 1",
1286 "1",
1287 "$45,000.00",
1288 "$3,881.25",
1289 "$48,881.25",
1290 ),
1291 (
1292 "Data Pipeline Development (160 hrs)",
1293 "160",
1294 "$225.00",
1295 "$3,105.00",
1296 "$39,105.00",
1297 ),
1298 (
1299 "GPU Cluster Configuration",
1300 "1",
1301 "$12,000.00",
1302 "$1,035.00",
1303 "$13,035.00",
1304 ),
1305 (
1306 "API Gateway Implementation",
1307 "1",
1308 "$18,500.00",
1309 "$1,595.63",
1310 "$20,095.63",
1311 ),
1312 (
1313 "Load Testing & QA (80 hrs)",
1314 "80",
1315 "$195.00",
1316 "$1,345.50",
1317 "$16,945.50",
1318 ),
1319 (
1320 "Documentation & Training",
1321 "1",
1322 "$8,000.00",
1323 "$690.00",
1324 "$8,690.00",
1325 ),
1326 (
1327 "Project Management (3 months)",
1328 "3",
1329 "$6,500.00",
1330 "$1,679.63",
1331 "$21,179.63",
1332 ),
1333 ];
1334 for (i, (desc, qty, unit, tax, amt)) in items.iter().enumerate() {
1335 let r = i + 1;
1336 tbl.cell(r, 0).unwrap().set_text(desc);
1337 tbl.cell(r, 1).unwrap().set_text(qty);
1338 tbl.cell(r, 2).unwrap().set_text(unit);
1339 tbl.cell(r, 3).unwrap().set_text(tax);
1340 tbl.cell(r, 4).unwrap().set_text(amt);
1341 if i % 2 == 0 {
1342 for c in 0..5 {
1343 tbl.cell(r, c).unwrap().shading("FAF0F0");
1344 }
1345 }
1346 }
1347 }
1348
1349 doc.add_paragraph("");
1350
1351 // ── Totals ──
1352 {
1353 let mut tbl = doc
1354 .add_table(4, 2)
1355 .borders(BorderStyle::Single, 2, "B22222")
1356 .width(Length::inches(3.0))
1357 .alignment(Alignment::Right);
1358 tbl.cell(0, 0).unwrap().set_text("Subtotal");
1359 tbl.cell(0, 1).unwrap().set_text("$154,600.00");
1360 tbl.cell(1, 0).unwrap().set_text("Tax (8.625%)");
1361 tbl.cell(1, 1).unwrap().set_text("$13,334.25");
1362 tbl.cell(2, 0).unwrap().set_text("Discount (5%)");
1363 tbl.cell(2, 1).unwrap().set_text("-$7,730.00");
1364 tbl.cell(3, 0).unwrap().set_text("AMOUNT DUE");
1365 tbl.cell(3, 0).unwrap().shading("B22222");
1366 tbl.cell(3, 1).unwrap().set_text("$160,204.25");
1367 tbl.cell(3, 1).unwrap().shading("B22222");
1368 }
1369
1370 doc.add_paragraph("");
1371 doc.add_paragraph("");
1372
1373 // ── Payment Details ──
1374 {
1375 let mut p = doc.add_paragraph("");
1376 p.add_run("Payment Details")
1377 .bold(true)
1378 .size(14.0)
1379 .color("333333");
1380 }
1381 doc.add_paragraph("Bank: Silicon Valley Bank")
1382 .indent_left(Length::inches(0.3));
1383 doc.add_paragraph("Account: Tensorbee Inc. — 0847-2953-1120")
1384 .indent_left(Length::inches(0.3));
1385 doc.add_paragraph("Routing: 121140399")
1386 .indent_left(Length::inches(0.3));
1387 doc.add_paragraph("Swift: SVBKUS6S")
1388 .indent_left(Length::inches(0.3));
1389
1390 doc.add_paragraph("");
1391
1392 doc.add_paragraph("Please include invoice number INV-2026-0392 in the payment reference.")
1393 .shading("FAF0F0")
1394 .border_all(BorderStyle::Single, 2, "B22222");
1395
1396 doc
1397}
1398
1399// =============================================================================
1400// 5. REPORT — Forest green + earth tones, with images & hierarchical sections
1401// =============================================================================
1402fn generate_report(_samples_dir: &Path) -> Document {
1403 let mut doc = Document::new();
1404
1405 // Colors: Forest #2D5016, Sage #6B8E23, Earth #8B7355, Cream #FFFAF0
1406 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
1407 doc.set_margins(
1408 Length::inches(1.0),
1409 Length::inches(1.0),
1410 Length::inches(1.0),
1411 Length::inches(1.0),
1412 );
1413 doc.set_title("Q4 2025 Environmental Impact Report");
1414 doc.set_author("Walter White");
1415 doc.set_subject("Quarterly Environmental Report");
1416 doc.set_keywords("environment, sustainability, report, Tensorbee");
1417
1418 doc.set_different_first_page(true);
1419 doc.set_first_page_header("");
1420 doc.set_header("Tensorbee — Q4 2025 Environmental Impact Report");
1421 doc.set_footer("CONFIDENTIAL — Page");
1422
1423 // ── Cover ──
1424 let cover_bg = create_sample_png(612, 792, [20, 50, 15]);
1425 doc.add_background_image(&cover_bg, "report_cover.png");
1426
1427 for _ in 0..5 {
1428 doc.add_paragraph("");
1429 }
1430 {
1431 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1432 p.add_run("Q4 2025")
1433 .bold(true)
1434 .size(48.0)
1435 .color("FFFFFF")
1436 .font("Georgia");
1437 }
1438 {
1439 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1440 p.add_run("Environmental Impact Report")
1441 .size(24.0)
1442 .color("90EE90")
1443 .font("Georgia")
1444 .italic(true);
1445 }
1446 doc.add_paragraph("");
1447 {
1448 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1449 p.add_run("Tensorbee — Sustainability Division")
1450 .size(14.0)
1451 .color("C0C0C0");
1452 }
1453 {
1454 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1455 p.add_run("Prepared by Walter White, Chief Sustainability Officer")
1456 .size(11.0)
1457 .color("AAAAAA");
1458 }
1459
1460 // ── TOC ──
1461 doc.add_paragraph("").page_break_before(true);
1462 doc.insert_toc(doc.content_count(), 3);
1463
1464 // ── Section 1: Executive Overview ──
1465 doc.add_paragraph("").page_break_before(true);
1466 doc.add_paragraph("Executive Overview").style("Heading1");
1467 doc.add_paragraph(
1468 "This report presents Tensorbee's environmental performance for Q4 2025. Our \
1469 sustainability initiatives have yielded a 34% reduction in carbon emissions compared \
1470 to Q4 2024, exceeding our target of 25%. Key achievements include the transition to \
1471 100% renewable energy in our primary data centers and the launch of our carbon offset \
1472 marketplace.",
1473 )
1474 .first_line_indent(Length::inches(0.3));
1475
1476 // Image: performance chart
1477 let chart_img = create_chart_png(400, 200);
1478 doc.add_paragraph("");
1479 doc.add_picture(
1480 &chart_img,
1481 "performance_chart.png",
1482 Length::inches(5.0),
1483 Length::inches(2.5),
1484 )
1485 .alignment(Alignment::Center);
1486 {
1487 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1488 p.add_run("Figure 1: Quarterly Carbon Emissions (tonnes CO2e)")
1489 .italic(true)
1490 .size(9.0)
1491 .color("666666");
1492 }
1493
1494 // ── Section 2: Energy Consumption ──
1495 doc.add_paragraph("").page_break_before(true);
1496 doc.add_paragraph("Energy Consumption").style("Heading1");
1497
1498 doc.add_paragraph("Data Center Operations")
1499 .style("Heading2");
1500 doc.add_paragraph(
1501 "Our three primary data centers consumed a combined 4.2 GWh in Q4 2025, \
1502 a 12% reduction from Q3 2025 driven by improved cooling efficiency and \
1503 server consolidation.",
1504 );
1505
1506 // Data table
1507 {
1508 let mut tbl = doc
1509 .add_table(5, 4)
1510 .borders(BorderStyle::Single, 4, "2D5016")
1511 .width_pct(100.0);
1512 let hdrs = ["Data Center", "Capacity (MW)", "Usage (GWh)", "PUE"];
1513 for (c, h) in hdrs.iter().enumerate() {
1514 tbl.cell(0, c).unwrap().set_text(h);
1515 tbl.cell(0, c).unwrap().shading("2D5016");
1516 }
1517 let rows = [
1518 ("San Francisco (Primary)", "3.2", "1.8", "1.12"),
1519 ("Dublin (EU)", "2.1", "1.2", "1.18"),
1520 ("Singapore (APAC)", "1.8", "1.2", "1.24"),
1521 ("TOTAL", "7.1", "4.2", "1.17 avg"),
1522 ];
1523 for (i, (dc, cap, use_, pue)) in rows.iter().enumerate() {
1524 tbl.cell(i + 1, 0).unwrap().set_text(dc);
1525 tbl.cell(i + 1, 1).unwrap().set_text(cap);
1526 tbl.cell(i + 1, 2).unwrap().set_text(use_);
1527 tbl.cell(i + 1, 3).unwrap().set_text(pue);
1528 if i == 3 {
1529 for c in 0..4 {
1530 tbl.cell(i + 1, c).unwrap().shading("E2EFDA");
1531 }
1532 }
1533 }
1534 }
1535
1536 doc.add_paragraph("");
1537
1538 doc.add_paragraph("Renewable Energy Mix").style("Heading2");
1539 doc.add_paragraph("Breakdown of energy sources across all facilities:");
1540
1541 doc.add_bullet_list_item("Solar PV: 42% (1.76 GWh)", 0);
1542 doc.add_bullet_list_item("Wind Power Purchase Agreements: 38% (1.60 GWh)", 0);
1543 doc.add_bullet_list_item("Hydroelectric: 12% (0.50 GWh)", 0);
1544 doc.add_bullet_list_item("Grid (non-renewable): 8% (0.34 GWh)", 0);
1545
1546 // ── Section 3: Water & Waste ──
1547 doc.add_paragraph("Water & Waste Management")
1548 .style("Heading1");
1549
1550 doc.add_paragraph("Water Usage").style("Heading2");
1551 doc.add_paragraph(
1552 "Total water consumption was 12.4 million gallons, a 15% reduction from Q3. \
1553 Our closed-loop cooling systems now recycle 78% of water used in cooling operations.",
1554 );
1555
1556 doc.add_paragraph("Waste Reduction").style("Heading2");
1557 doc.add_paragraph("E-waste management results for Q4:")
1558 .keep_with_next(true);
1559 {
1560 let mut tbl = doc
1561 .add_table(4, 3)
1562 .borders(BorderStyle::Single, 2, "6B8E23")
1563 .width_pct(80.0);
1564 tbl.cell(0, 0).unwrap().set_text("Category");
1565 tbl.cell(0, 0).unwrap().shading("6B8E23");
1566 tbl.cell(0, 1).unwrap().set_text("Weight (kg)");
1567 tbl.cell(0, 1).unwrap().shading("6B8E23");
1568 tbl.cell(0, 2).unwrap().set_text("Recycled %");
1569 tbl.cell(0, 2).unwrap().shading("6B8E23");
1570 let rows = [
1571 ("Server Hardware", "2,450", "94%"),
1572 ("Networking Equipment", "820", "91%"),
1573 ("Storage Media", "340", "99%"),
1574 ];
1575 for (i, (cat, wt, pct)) in rows.iter().enumerate() {
1576 tbl.cell(i + 1, 0).unwrap().set_text(cat);
1577 tbl.cell(i + 1, 1).unwrap().set_text(wt);
1578 tbl.cell(i + 1, 2).unwrap().set_text(pct);
1579 }
1580 }
1581
1582 // ── Section 4: Initiatives ──
1583 doc.add_paragraph("").page_break_before(true);
1584 doc.add_paragraph("Q1 2026 Initiatives").style("Heading1");
1585
1586 doc.add_paragraph("Planned Programs").style("Heading2");
1587 doc.add_numbered_list_item(
1588 "Deploy on-site battery storage at SF data center (2 MWh capacity)",
1589 0,
1590 );
1591 doc.add_numbered_list_item("Pilot immersion cooling in Dublin facility", 0);
1592 doc.add_numbered_list_item("Launch employee commute carbon offset program", 0);
1593 doc.add_numbered_list_item("Achieve ISO 14001 certification for Singapore facility", 0);
1594
1595 doc.add_paragraph("Investment Targets").style("Heading2");
1596 doc.add_paragraph("Sustainability CapEx allocation for FY2026:")
1597 .keep_with_next(true);
1598 {
1599 let mut tbl = doc
1600 .add_table(5, 2)
1601 .borders(BorderStyle::Single, 4, "2D5016");
1602 tbl.cell(0, 0).unwrap().set_text("Initiative");
1603 tbl.cell(0, 0).unwrap().shading("2D5016");
1604 tbl.cell(0, 1).unwrap().set_text("Budget");
1605 tbl.cell(0, 1).unwrap().shading("2D5016");
1606 let rows = [
1607 ("Battery Storage", "$1.2M"),
1608 ("Immersion Cooling Pilot", "$800K"),
1609 ("Solar Panel Expansion", "$2.1M"),
1610 ("Carbon Credits", "$500K"),
1611 ];
1612 for (i, (init, budget)) in rows.iter().enumerate() {
1613 tbl.cell(i + 1, 0).unwrap().set_text(init);
1614 tbl.cell(i + 1, 1).unwrap().set_text(budget);
1615 if i % 2 == 0 {
1616 tbl.cell(i + 1, 0).unwrap().shading("FFFAF0");
1617 tbl.cell(i + 1, 1).unwrap().shading("FFFAF0");
1618 }
1619 }
1620 }
1621
1622 // Image: sustainability roadmap
1623 let roadmap_img = create_sample_png(500, 100, [40, 80, 30]);
1624 doc.add_paragraph("");
1625 doc.add_picture(
1626 &roadmap_img,
1627 "roadmap.png",
1628 Length::inches(6.0),
1629 Length::inches(1.2),
1630 )
1631 .alignment(Alignment::Center);
1632 {
1633 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
1634 p.add_run("Figure 2: 2026 Sustainability Roadmap")
1635 .italic(true)
1636 .size(9.0)
1637 .color("666666");
1638 }
1639
1640 doc.add_paragraph("");
1641 doc.add_paragraph(
1642 "For questions about this report, contact Walter White at sustainability@tensorbee.com.",
1643 )
1644 .shading("FFFAF0")
1645 .border_all(BorderStyle::Single, 2, "2D5016");
1646
1647 doc
1648}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, and footers. Handles placeholders split across multiple runs. Returns the total number of replacements made.
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.
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
34fn generate_feature_showcase(path: &Path) {
35 let mut doc = Document::new();
36
37 // =========================================================================
38 // PAGE SETUP & METADATA
39 // =========================================================================
40 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
41 doc.set_margins(
42 Length::inches(1.0), // top
43 Length::inches(1.0), // right
44 Length::inches(1.0), // bottom
45 Length::inches(1.0), // left
46 );
47 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
48 doc.set_gutter(Length::twips(0));
49
50 doc.set_title("rdocx Feature Showcase");
51 doc.set_author("rdocx Sample Generator");
52 doc.set_subject("Comprehensive feature demonstration");
53 doc.set_keywords("rdocx, docx, rust, sample");
54
55 // Header & Footer
56 doc.set_header("rdocx Feature Showcase");
57 doc.set_footer("Generated by rdocx — Page");
58
59 // Different first page header
60 doc.set_different_first_page(true);
61 doc.set_first_page_header("rdocx");
62 doc.set_first_page_footer("Feature Showcase — Cover Page");
63
64 // =========================================================================
65 // PAGE 1: COVER PAGE — background image, run formatting
66 // =========================================================================
67 let bg_cover = create_sample_png(612, 792, [30, 60, 120]);
68 doc.add_background_image(&bg_cover, "cover_bg.png");
69
70 doc.add_paragraph(""); // spacer
71 doc.add_paragraph(""); // spacer
72 doc.add_paragraph(""); // spacer
73
74 {
75 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
76 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
77 }
78 {
79 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
80 p.add_run("Feature Showcase")
81 .size(28.0)
82 .color("FFFFFF")
83 .italic(true);
84 }
85
86 doc.add_paragraph(""); // spacer
87
88 {
89 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
90 p.add_run("A comprehensive demonstration of every feature")
91 .size(14.0)
92 .color("CCDDFF");
93 }
94 {
95 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
96 p.add_run("provided by the rdocx Rust crate for DOCX generation.")
97 .size(14.0)
98 .color("CCDDFF");
99 }
100
101 // =========================================================================
102 // PAGE 2: TEXT FORMATTING
103 // =========================================================================
104 doc.add_paragraph("").page_break_before(true);
105
106 doc.add_paragraph("1. Text Formatting").style("Heading1");
107
108 doc.add_paragraph("This section demonstrates paragraph and run-level formatting options.");
109 doc.add_paragraph("");
110
111 // --- Paragraph alignment ---
112 doc.add_paragraph("Paragraph Alignment").style("Heading2");
113
114 doc.add_paragraph("This paragraph is left-aligned (the default).")
115 .alignment(Alignment::Left);
116 doc.add_paragraph("This paragraph is center-aligned.")
117 .alignment(Alignment::Center);
118 doc.add_paragraph("This paragraph is right-aligned.")
119 .alignment(Alignment::Right);
120 doc.add_paragraph(
121 "This paragraph is justified. To demonstrate justified text properly, it needs \
122 to be long enough to span multiple lines so the word spacing adjustment is visible \
123 across the full width of the text area on the page.",
124 )
125 .alignment(Alignment::Justify);
126
127 doc.add_paragraph("");
128
129 // --- Run formatting ---
130 doc.add_paragraph("Run Formatting").style("Heading2");
131
132 {
133 let mut p = doc.add_paragraph("");
134 p.add_run("Bold text").bold(true);
135 p.add_run(" | ");
136 p.add_run("Italic text").italic(true);
137 p.add_run(" | ");
138 p.add_run("Bold + Italic").bold(true).italic(true);
139 }
140 {
141 let mut p = doc.add_paragraph("");
142 p.add_run("Single underline").underline(true);
143 p.add_run(" | ");
144 p.add_run("Strikethrough").strike(true);
145 p.add_run(" | ");
146 p.add_run("Double strikethrough").double_strike(true);
147 }
148 {
149 let mut p = doc.add_paragraph("");
150 p.add_run("Red text").color("FF0000");
151 p.add_run(" | ");
152 p.add_run("Blue text").color("0000FF");
153 p.add_run(" | ");
154 p.add_run("Green text").color("00AA00");
155 p.add_run(" | ");
156 p.add_run("Highlighted").highlight("FFFF00");
157 }
158 {
159 let mut p = doc.add_paragraph("");
160 p.add_run("8pt small").size(8.0);
161 p.add_run(" | ");
162 p.add_run("11pt normal").size(11.0);
163 p.add_run(" | ");
164 p.add_run("16pt large").size(16.0);
165 p.add_run(" | ");
166 p.add_run("24pt extra-large").size(24.0);
167 }
168 {
169 let mut p = doc.add_paragraph("");
170 p.add_run("Arial font").font("Arial");
171 p.add_run(" | ");
172 p.add_run("Times New Roman font").font("Times New Roman");
173 p.add_run(" | ");
174 p.add_run("Courier New font").font("Courier New");
175 }
176 {
177 let mut p = doc.add_paragraph("");
178 p.add_run("Normal");
179 p.add_run(" H").size(11.0);
180 p.add_run("2").subscript();
181 p.add_run("O (subscript)").size(11.0);
182 p.add_run(" | E = mc").size(11.0);
183 p.add_run("2").superscript();
184 p.add_run(" (superscript)").size(11.0);
185 }
186 {
187 let mut p = doc.add_paragraph("");
188 p.add_run("ALL CAPS").all_caps(true);
189 p.add_run(" | ");
190 p.add_run("Small Caps").small_caps(true);
191 p.add_run(" | ");
192 p.add_run("Expanded spacing")
193 .character_spacing(Length::twips(40));
194 }
195
196 doc.add_paragraph("");
197
198 // --- Paragraph formatting ---
199 doc.add_paragraph("Paragraph Formatting").style("Heading2");
200
201 doc.add_paragraph("Paragraph with shading (light green background)")
202 .shading("E2EFDA");
203
204 doc.add_paragraph("Paragraph with bottom border")
205 .border_bottom(BorderStyle::Single, 6, "2E75B6");
206
207 doc.add_paragraph("Paragraph with all borders")
208 .border_all(BorderStyle::Single, 4, "FF0000");
209
210 doc.add_paragraph("Paragraph with 1-inch left indent and hanging indent")
211 .indent_left(Length::inches(1.0))
212 .hanging_indent(Length::inches(0.5));
213
214 doc.add_paragraph("Paragraph with first-line indent of 0.5 inches")
215 .first_line_indent(Length::inches(0.5));
216
217 doc.add_paragraph("Paragraph with extra space before (24pt) and after (12pt)")
218 .space_before(Length::pt(24.0))
219 .space_after(Length::pt(12.0));
220
221 doc.add_paragraph(
222 "Paragraph with double line spacing. This text should have extra vertical \
223 space between lines to demonstrate the line_spacing_multiple setting.",
224 )
225 .line_spacing_multiple(2.0);
226
227 doc.add_paragraph("Paragraph with keep-with-next (won't break from the next paragraph)")
228 .keep_with_next(true);
229 doc.add_paragraph("(This stays with the paragraph above.)");
230
231 // =========================================================================
232 // PAGE 3: LISTS & TAB STOPS
233 // =========================================================================
234 doc.add_paragraph("").page_break_before(true);
235
236 doc.add_paragraph("2. Lists").style("Heading1");
237
238 doc.add_paragraph("Bullet List").style("Heading2");
239
240 doc.add_bullet_list_item("First bullet item", 0);
241 doc.add_bullet_list_item("Second bullet item", 0);
242 doc.add_bullet_list_item("Nested level 1", 1);
243 doc.add_bullet_list_item("Nested level 2", 2);
244 doc.add_bullet_list_item("Back to level 1", 1);
245 doc.add_bullet_list_item("Third bullet item", 0);
246
247 doc.add_paragraph("");
248
249 doc.add_paragraph("Numbered List").style("Heading2");
250
251 doc.add_numbered_list_item("First numbered item", 0);
252 doc.add_numbered_list_item("Second numbered item", 0);
253 doc.add_numbered_list_item("Sub-item A", 1);
254 doc.add_numbered_list_item("Sub-item B", 1);
255 doc.add_numbered_list_item("Third numbered item", 0);
256
257 doc.add_paragraph("");
258
259 // --- Tab stops ---
260 doc.add_paragraph("Tab Stops").style("Heading2");
261
262 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
263 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
264 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
265 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
266 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
267
268 doc.add_paragraph("Item\t........\tPrice")
269 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
270 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
271 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
272
273 doc.add_paragraph("Widget A\t........\t$19.99")
274 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
275 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
276 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
277
278 doc.add_paragraph("Gadget B\t________\t$249.50")
279 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
280 .add_tab_stop_with_leader(
281 TabAlignment::Right,
282 Length::inches(4.0),
283 TabLeader::Underscore,
284 )
285 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
286
287 // =========================================================================
288 // PAGE 4: TABLES
289 // =========================================================================
290 doc.add_paragraph("").page_break_before(true);
291
292 doc.add_paragraph("3. Tables").style("Heading1");
293
294 // --- Basic table with borders ---
295 doc.add_paragraph("Basic Table with Borders")
296 .style("Heading2");
297
298 {
299 let mut tbl = doc.add_table(4, 3);
300 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
301
302 // Header row
303 for col in 0..3 {
304 tbl.cell(0, col).unwrap().shading("2E75B6");
305 }
306 tbl.cell(0, 0).unwrap().set_text("Name");
307 tbl.cell(0, 1).unwrap().set_text("Role");
308 tbl.cell(0, 2).unwrap().set_text("Location");
309
310 tbl.cell(1, 0).unwrap().set_text("Alice Johnson");
311 tbl.cell(1, 1).unwrap().set_text("Engineering Lead");
312 tbl.cell(1, 2).unwrap().set_text("New York");
313
314 tbl.cell(2, 0).unwrap().set_text("Bob Smith");
315 tbl.cell(2, 1).unwrap().set_text("Product Manager");
316 tbl.cell(2, 2).unwrap().set_text("San Francisco");
317
318 tbl.cell(3, 0).unwrap().set_text("Carol Davis");
319 tbl.cell(3, 1).unwrap().set_text("Designer");
320 tbl.cell(3, 2).unwrap().set_text("London");
321 }
322
323 doc.add_paragraph("");
324
325 // --- Table with cell merging ---
326 doc.add_paragraph("Table with Cell Merging & Vertical Alignment")
327 .style("Heading2");
328
329 {
330 let mut tbl = doc.add_table(4, 4);
331 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
332 tbl = tbl.width_pct(100.0);
333
334 // Header spanning all columns
335 tbl.cell(0, 0).unwrap().set_text("Quarterly Revenue Report");
336 tbl.cell(0, 0).unwrap().shading("1F4E79");
337 tbl.cell(0, 0).unwrap().grid_span(4);
338
339 // Sub-header
340 tbl.cell(1, 0).unwrap().set_text("Region");
341 tbl.cell(1, 0).unwrap().shading("D6E4F0");
342 tbl.cell(1, 1).unwrap().set_text("Q1");
343 tbl.cell(1, 1).unwrap().shading("D6E4F0");
344 tbl.cell(1, 2).unwrap().set_text("Q2");
345 tbl.cell(1, 2).unwrap().shading("D6E4F0");
346 tbl.cell(1, 3).unwrap().set_text("Total");
347 tbl.cell(1, 3).unwrap().shading("D6E4F0");
348
349 // Data
350 tbl.cell(2, 0).unwrap().set_text("North America");
351 tbl.cell(2, 1).unwrap().set_text("$2.4M");
352 tbl.cell(2, 2).unwrap().set_text("$2.7M");
353 tbl.cell(2, 3).unwrap().set_text("$5.1M");
354
355 tbl.cell(3, 0).unwrap().set_text("Europe");
356 tbl.cell(3, 1).unwrap().set_text("$1.8M");
357 tbl.cell(3, 2).unwrap().set_text("$2.0M");
358 tbl.cell(3, 3).unwrap().set_text("$3.8M");
359
360 // Vertical alignment on data cells
361 tbl.cell(2, 3)
362 .unwrap()
363 .vertical_alignment(VerticalAlignment::Center);
364 tbl.cell(3, 3)
365 .unwrap()
366 .vertical_alignment(VerticalAlignment::Bottom);
367 }
368
369 doc.add_paragraph("");
370
371 // --- Table with vertical merge ---
372 doc.add_paragraph("Table with Vertical Merge")
373 .style("Heading2");
374
375 {
376 let mut tbl = doc.add_table(4, 3);
377 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
378
379 tbl.cell(0, 0).unwrap().set_text("Category");
380 tbl.cell(0, 0).unwrap().shading("E2EFDA");
381 tbl.cell(0, 1).unwrap().set_text("Item");
382 tbl.cell(0, 1).unwrap().shading("E2EFDA");
383 tbl.cell(0, 2).unwrap().set_text("Price");
384 tbl.cell(0, 2).unwrap().shading("E2EFDA");
385
386 // "Hardware" spans rows 1-2
387 tbl.cell(1, 0).unwrap().set_text("Hardware");
388 tbl.cell(1, 0).unwrap().v_merge_restart();
389 tbl.cell(1, 1).unwrap().set_text("Laptop");
390 tbl.cell(1, 2).unwrap().set_text("$1,200");
391
392 tbl.cell(2, 0).unwrap().v_merge_continue();
393 tbl.cell(2, 1).unwrap().set_text("Monitor");
394 tbl.cell(2, 2).unwrap().set_text("$450");
395
396 // "Software" on row 3
397 tbl.cell(3, 0).unwrap().set_text("Software");
398 tbl.cell(3, 1).unwrap().set_text("IDE License");
399 tbl.cell(3, 2).unwrap().set_text("$200/yr");
400 }
401
402 doc.add_paragraph("");
403
404 // --- Nested table ---
405 doc.add_paragraph("Nested Table").style("Heading2");
406
407 {
408 let mut tbl = doc.add_table(2, 2);
409 tbl = tbl.borders(BorderStyle::Single, 6, "2E75B6");
410
411 tbl.cell(0, 0).unwrap().set_text("Outer Cell (0,0)");
412 tbl.cell(0, 1).unwrap().set_text("Outer Cell (0,1)");
413 tbl.cell(1, 0).unwrap().set_text("Outer Cell (1,0)");
414
415 // Nested table inside cell (1,1)
416 {
417 let mut cell = tbl.cell(1, 1).unwrap();
418 cell.set_text("Contains nested table:");
419 let mut nested = cell.add_table(2, 2);
420 nested = nested.borders(BorderStyle::Single, 2, "FF6600");
421 nested.cell(0, 0).unwrap().set_text("Inner A");
422 nested.cell(0, 1).unwrap().set_text("Inner B");
423 nested.cell(1, 0).unwrap().set_text("Inner C");
424 nested.cell(1, 1).unwrap().set_text("Inner D");
425 }
426 }
427
428 // =========================================================================
429 // PAGE 5: IMAGES
430 // =========================================================================
431 doc.add_paragraph("").page_break_before(true);
432
433 doc.add_paragraph("4. Images").style("Heading1");
434
435 doc.add_paragraph("Inline Image").style("Heading2");
436
437 doc.add_paragraph("Below is an inline image (200x50 pixels, blue gradient):");
438 let inline_img = create_sample_png(200, 50, [0, 80, 200]);
439 doc.add_picture(
440 &inline_img,
441 "inline_chart.png",
442 Length::inches(3.0),
443 Length::inches(0.75),
444 );
445
446 doc.add_paragraph("");
447
448 doc.add_paragraph("Header Image").style("Heading2");
449
450 // Replace the text-only header with an image header
451 let header_img = create_sample_png(400, 40, [40, 40, 40]);
452 doc.set_header_image(
453 &header_img,
454 "header_logo.png",
455 Length::inches(2.0),
456 Length::inches(0.2),
457 );
458
459 doc.add_paragraph(
460 "The document header has been replaced with an inline image. \
461 Check the header area at the top of this page.",
462 );
463
464 doc.add_paragraph("");
465 doc.add_paragraph(
466 "Note: The cover page uses a full-page background image behind the text, \
467 demonstrated on page 1 via add_background_image().",
468 );
469
470 // =========================================================================
471 // PAGE 6: CONTENT MANIPULATION — placeholder replacement, insertion
472 // =========================================================================
473 doc.add_paragraph("").page_break_before(true);
474
475 doc.add_paragraph("5. Content Manipulation")
476 .style("Heading1");
477
478 // --- Placeholder replacement ---
479 doc.add_paragraph("Placeholder Replacement")
480 .style("Heading2");
481
482 doc.add_paragraph(
483 "Before replacement, this document contained {{customer}} and {{date}} placeholders.",
484 );
485
486 {
487 let mut p = doc.add_paragraph("");
488 p.add_run("Customer: ").bold(true);
489 p.add_run("{{customer}}");
490 }
491 {
492 let mut p = doc.add_paragraph("");
493 p.add_run("Date: ").bold(true);
494 p.add_run("{{date}}");
495 }
496 doc.add_paragraph("Reference: {{ref_number}}");
497
498 // Table with placeholders
499 {
500 let mut tbl = doc.add_table(3, 2);
501 tbl = tbl.borders(BorderStyle::Single, 4, "000000");
502 tbl.cell(0, 0).unwrap().set_text("Field");
503 tbl.cell(0, 0).unwrap().shading("D6E4F0");
504 tbl.cell(0, 1).unwrap().set_text("Value");
505 tbl.cell(0, 1).unwrap().shading("D6E4F0");
506 tbl.cell(1, 0).unwrap().set_text("Project");
507 tbl.cell(1, 1).unwrap().set_text("{{project}}");
508 tbl.cell(2, 0).unwrap().set_text("Status");
509 tbl.cell(2, 1).unwrap().set_text("{{status}}");
510 }
511
512 // Perform replacements
513 let mut replacements = HashMap::new();
514 replacements.insert("{{customer}}", "Acme Corporation");
515 replacements.insert("{{date}}", "February 22, 2026");
516 replacements.insert("{{ref_number}}", "REF-2026-001");
517 replacements.insert("{{project}}", "Infrastructure Upgrade");
518 replacements.insert("{{status}}", "In Progress");
519 let replace_count = doc.replace_all(&replacements);
520
521 doc.add_paragraph("");
522 doc.add_paragraph(&format!(
523 "(Replaced {} placeholders above — in body text and table cells)",
524 replace_count
525 ));
526
527 doc.add_paragraph("");
528
529 // --- Content insertion ---
530 doc.add_paragraph("Content Insertion").style("Heading2");
531
532 doc.add_paragraph("Section A: First section of content.");
533 doc.add_paragraph("Section C: Third section of content.");
534
535 // Insert "Section B" between A and C
536 if let Some(idx) = doc.find_content_index("Section C") {
537 doc.insert_paragraph(
538 idx,
539 "Section B: Inserted between A and C using find_content_index().",
540 );
541 }
542
543 doc.add_paragraph("");
544 doc.add_paragraph(
545 "The paragraph above ('Section B') was inserted at a specific position \
546 using find_content_index() + insert_paragraph().",
547 );
548
549 // =========================================================================
550 // PAGE 7: LANDSCAPE — section break, wide table
551 // =========================================================================
552 doc.add_paragraph("").section_break(SectionBreak::NextPage);
553
554 doc.add_paragraph("6. Mixed Page Orientation")
555 .style("Heading1");
556
557 doc.add_paragraph(
558 "This page is in LANDSCAPE orientation. It was created using a section break \
559 followed by section_landscape(). This is useful for wide tables or charts.",
560 );
561
562 doc.add_paragraph("");
563
564 // Wide table for landscape
565 {
566 let mut tbl = doc.add_table(4, 7);
567 tbl = tbl.borders(BorderStyle::Single, 4, "2E75B6");
568
569 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
570 for (col, h) in headers.iter().enumerate() {
571 tbl.cell(0, col).unwrap().set_text(h);
572 tbl.cell(0, col).unwrap().shading("2E75B6");
573 }
574
575 let data = [
576 [
577 "North America",
578 "$1.2M",
579 "$1.3M",
580 "$1.4M",
581 "$1.5M",
582 "$1.6M",
583 "$7.0M",
584 ],
585 [
586 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
587 ],
588 [
589 "Asia Pacific",
590 "$0.5M",
591 "$0.6M",
592 "$0.7M",
593 "$0.7M",
594 "$0.8M",
595 "$3.3M",
596 ],
597 ];
598 for (row_idx, row_data) in data.iter().enumerate() {
599 for (col, val) in row_data.iter().enumerate() {
600 tbl.cell(row_idx + 1, col).unwrap().set_text(val);
601 }
602 }
603 }
604
605 // End landscape, return to portrait
606 doc.add_paragraph("")
607 .section_break(SectionBreak::NextPage)
608 .section_landscape();
609
610 // =========================================================================
611 // PAGE 8: BACK TO PORTRAIT — styles, final notes
612 // =========================================================================
613 doc.add_paragraph("7. Custom Styles & Summary")
614 .style("Heading1");
615
616 doc.add_paragraph(
617 "This final page is back in portrait orientation after a section break. \
618 The document has demonstrated:",
619 );
620
621 doc.add_bullet_list_item(
622 "Page setup: size, margins, header/footer distance, gutter",
623 0,
624 );
625 doc.add_bullet_list_item("Document metadata: title, author, subject, keywords", 0);
626 doc.add_bullet_list_item("Headers and footers: text, images, different first page", 0);
627 doc.add_bullet_list_item("Background images: full-page behind text", 0);
628 doc.add_bullet_list_item(
629 "Text formatting: bold, italic, underline, strike, color, size, font",
630 0,
631 );
632 doc.add_bullet_list_item(
633 "Advanced run formatting: superscript, subscript, caps, spacing",
634 0,
635 );
636 doc.add_bullet_list_item(
637 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
638 0,
639 );
640 doc.add_bullet_list_item("Bullet and numbered lists with nesting levels", 0);
641 doc.add_bullet_list_item("Tab stops with dot/underscore leaders", 0);
642 doc.add_bullet_list_item(
643 "Tables: borders, shading, column spans, row spans, nesting",
644 0,
645 );
646 doc.add_bullet_list_item("Vertical alignment in table cells", 0);
647 doc.add_bullet_list_item("Inline images", 0);
648 doc.add_bullet_list_item("Placeholder replacement in body and table cells", 0);
649 doc.add_bullet_list_item("Content insertion at specific positions", 0);
650 doc.add_bullet_list_item(
651 "Section breaks with mixed portrait/landscape orientation",
652 0,
653 );
654
655 doc.add_paragraph("");
656 doc.add_paragraph("All features above were built entirely from scratch using the rdocx API.")
657 .alignment(Alignment::Center)
658 .shading("E2EFDA")
659 .border_all(BorderStyle::Single, 2, "00AA00");
660
661 doc.save(path).unwrap();
662}86fn generate_feature_showcase(_samples_dir: &Path) -> Document {
87 let mut doc = Document::new();
88
89 // ── Page Setup & Metadata ──
90 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
91 doc.set_margins(
92 Length::inches(1.0),
93 Length::inches(1.0),
94 Length::inches(1.0),
95 Length::inches(1.0),
96 );
97 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
98 doc.set_gutter(Length::twips(0));
99 doc.set_title("rdocx Feature Showcase");
100 doc.set_author("rdocx Sample Generator");
101 doc.set_subject("Comprehensive feature demonstration");
102 doc.set_keywords("rdocx, docx, rust, sample, showcase");
103
104 // Headers & Footers with different first page
105 doc.set_different_first_page(true);
106 doc.set_first_page_header("rdocx");
107 doc.set_first_page_footer("Feature Showcase — Cover Page");
108 doc.set_header("rdocx Feature Showcase");
109 doc.set_footer("Generated by rdocx");
110
111 // ── COVER PAGE ──
112 let bg = create_sample_png(612, 792, [20, 45, 90]);
113 doc.add_background_image(&bg, "cover_bg.png");
114
115 for _ in 0..3 {
116 doc.add_paragraph("");
117 }
118 {
119 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
120 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
121 }
122 {
123 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
124 p.add_run("Complete Feature Showcase")
125 .size(28.0)
126 .color("FFFFFF")
127 .italic(true);
128 }
129 doc.add_paragraph("");
130 {
131 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
132 p.add_run("Every feature of the rdocx Rust library")
133 .size(13.0)
134 .color("B0C4DE");
135 }
136 {
137 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
138 p.add_run("demonstrated in a single document.")
139 .size(13.0)
140 .color("B0C4DE");
141 }
142
143 // ── TABLE OF CONTENTS ──
144 doc.add_paragraph("").page_break_before(true);
145 doc.insert_toc(doc.content_count(), 3);
146
147 // ── SECTION 1: TEXT FORMATTING ──
148 doc.add_paragraph("").page_break_before(true);
149 doc.add_paragraph("1. Text Formatting").style("Heading1");
150
151 // Paragraph Alignment
152 doc.add_paragraph("1.1 Paragraph Alignment")
153 .style("Heading2");
154 doc.add_paragraph("Left-aligned paragraph (default).")
155 .alignment(Alignment::Left);
156 doc.add_paragraph("Center-aligned paragraph.")
157 .alignment(Alignment::Center);
158 doc.add_paragraph("Right-aligned paragraph.")
159 .alignment(Alignment::Right);
160 doc.add_paragraph(
161 "Justified paragraph — this text is long enough to demonstrate how justified alignment \
162 distributes extra space across word gaps so lines fill the full width of the text area.",
163 )
164 .alignment(Alignment::Justify);
165
166 doc.add_paragraph("");
167
168 // Run Formatting
169 doc.add_paragraph("1.2 Run Formatting").style("Heading2");
170 {
171 let mut p = doc.add_paragraph("");
172 p.add_run("Bold").bold(true);
173 p.add_run(" | ");
174 p.add_run("Italic").italic(true);
175 p.add_run(" | ");
176 p.add_run("Bold Italic").bold(true).italic(true);
177 p.add_run(" | ");
178 p.add_run("Underline").underline(true);
179 p.add_run(" | ");
180 p.add_run("Strikethrough").strike(true);
181 p.add_run(" | ");
182 p.add_run("Double Strike").double_strike(true);
183 }
184 {
185 let mut p = doc.add_paragraph("");
186 p.add_run("Red").color("FF0000");
187 p.add_run(" | ");
188 p.add_run("Blue").color("0000FF");
189 p.add_run(" | ");
190 p.add_run("Green").color("00AA00");
191 p.add_run(" | ");
192 p.add_run("Highlighted").highlight("FFFF00");
193 }
194 {
195 let mut p = doc.add_paragraph("");
196 p.add_run("8pt").size(8.0);
197 p.add_run(" | ");
198 p.add_run("11pt").size(11.0);
199 p.add_run(" | ");
200 p.add_run("16pt").size(16.0);
201 p.add_run(" | ");
202 p.add_run("24pt").size(24.0);
203 }
204 {
205 let mut p = doc.add_paragraph("");
206 p.add_run("Arial").font("Arial");
207 p.add_run(" | ");
208 p.add_run("Times New Roman").font("Times New Roman");
209 p.add_run(" | ");
210 p.add_run("Courier New").font("Courier New");
211 }
212 {
213 let mut p = doc.add_paragraph("");
214 p.add_run("H");
215 p.add_run("2").subscript();
216 p.add_run("O (subscript) | E=mc");
217 p.add_run("2").superscript();
218 p.add_run(" (superscript)");
219 }
220 {
221 let mut p = doc.add_paragraph("");
222 p.add_run("ALL CAPS").all_caps(true);
223 p.add_run(" | ");
224 p.add_run("Small Caps").small_caps(true);
225 p.add_run(" | ");
226 p.add_run("Expanded").character_spacing(Length::twips(40));
227 p.add_run(" | ");
228 p.add_run("Hidden text (not visible)").hidden(true);
229 }
230
231 doc.add_paragraph("");
232
233 // Underline Styles
234 doc.add_paragraph("1.3 Underline Styles").style("Heading2");
235 {
236 let mut p = doc.add_paragraph("");
237 p.add_run("Single ")
238 .underline_style(rdocx::UnderlineStyle::Single);
239 p.add_run("Double ")
240 .underline_style(rdocx::UnderlineStyle::Double);
241 p.add_run("Thick ")
242 .underline_style(rdocx::UnderlineStyle::Thick);
243 p.add_run("Dotted ")
244 .underline_style(rdocx::UnderlineStyle::Dotted);
245 p.add_run("Dash ")
246 .underline_style(rdocx::UnderlineStyle::Dash);
247 p.add_run("Wave ")
248 .underline_style(rdocx::UnderlineStyle::Wave);
249 }
250
251 // ── SECTION 2: PARAGRAPH FORMATTING ──
252 doc.add_paragraph("").page_break_before(true);
253 doc.add_paragraph("2. Paragraph Formatting")
254 .style("Heading1");
255
256 doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
257 doc.add_paragraph("Paragraph with light green shading")
258 .shading("E2EFDA");
259 doc.add_paragraph("Paragraph with bottom border")
260 .border_bottom(BorderStyle::Single, 6, "2E75B6");
261 doc.add_paragraph("Paragraph with all borders (red)")
262 .border_all(BorderStyle::Single, 4, "FF0000");
263
264 doc.add_paragraph("2.2 Indentation").style("Heading2");
265 doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
266 .indent_left(Length::inches(1.0))
267 .hanging_indent(Length::inches(0.5));
268 doc.add_paragraph("0.5-inch first-line indent on this paragraph")
269 .first_line_indent(Length::inches(0.5));
270 doc.add_paragraph("Both left and right indent (0.75 inches each)")
271 .indent_left(Length::inches(0.75))
272 .indent_right(Length::inches(0.75));
273
274 doc.add_paragraph("2.3 Spacing & Line Height")
275 .style("Heading2");
276 doc.add_paragraph("Extra space before (24pt) and after (12pt)")
277 .space_before(Length::pt(24.0))
278 .space_after(Length::pt(12.0));
279 doc.add_paragraph(
280 "Double line spacing paragraph. This text should have extra vertical space between \
281 lines to demonstrate line_spacing_multiple(2.0).",
282 )
283 .line_spacing_multiple(2.0);
284 doc.add_paragraph("Exact 20pt line spacing (fixed height).")
285 .line_spacing(20.0);
286
287 doc.add_paragraph("2.4 Pagination Controls")
288 .style("Heading2");
289 doc.add_paragraph("keep_with_next — this paragraph stays with the next")
290 .keep_with_next(true);
291 doc.add_paragraph("(This paragraph was kept with the one above.)");
292 doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
293 .keep_together(true);
294 doc.add_paragraph("widow_control — prevents widow/orphan lines")
295 .widow_control(true);
296
297 // ── SECTION 3: LISTS ──
298 doc.add_paragraph("").page_break_before(true);
299 doc.add_paragraph("3. Lists").style("Heading1");
300
301 doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
302 doc.add_bullet_list_item("First item", 0);
303 doc.add_bullet_list_item("Second item", 0);
304 doc.add_bullet_list_item("Nested level 1", 1);
305 doc.add_bullet_list_item("Nested level 2", 2);
306 doc.add_bullet_list_item("Back to level 1", 1);
307 doc.add_bullet_list_item("Third item", 0);
308
309 doc.add_paragraph("");
310
311 doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
312 doc.add_numbered_list_item("First numbered item", 0);
313 doc.add_numbered_list_item("Second numbered item", 0);
314 doc.add_numbered_list_item("Sub-item A", 1);
315 doc.add_numbered_list_item("Sub-item B", 1);
316 doc.add_numbered_list_item("Third numbered item", 0);
317
318 // ── SECTION 4: TAB STOPS ──
319 doc.add_paragraph("");
320 doc.add_paragraph("4. Tab Stops").style("Heading1");
321
322 doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
323 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
324 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
325 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
326 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
327 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
328
329 doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
330 doc.add_paragraph("Item\t\tPrice")
331 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
332 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
333 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
334 doc.add_paragraph("Widget\t\t$19.99")
335 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
336 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
337 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
338 doc.add_paragraph("Gadget\t\t$249.50")
339 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
340 .add_tab_stop_with_leader(
341 TabAlignment::Right,
342 Length::inches(4.0),
343 TabLeader::Underscore,
344 )
345 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
346
347 // ── SECTION 5: TABLES ──
348 doc.add_paragraph("").page_break_before(true);
349 doc.add_paragraph("5. Tables").style("Heading1");
350
351 doc.add_paragraph("5.1 Basic Table").style("Heading2");
352 {
353 let mut tbl = doc
354 .add_table(4, 3)
355 .borders(BorderStyle::Single, 4, "000000");
356 for col in 0..3 {
357 tbl.cell(0, col).unwrap().shading("2E75B6");
358 }
359 tbl.cell(0, 0).unwrap().set_text("Name");
360 tbl.cell(0, 1).unwrap().set_text("Role");
361 tbl.cell(0, 2).unwrap().set_text("Location");
362 tbl.cell(1, 0).unwrap().set_text("Walter White");
363 tbl.cell(1, 1).unwrap().set_text("CEO");
364 tbl.cell(1, 2).unwrap().set_text("Albuquerque");
365 tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
366 tbl.cell(2, 1).unwrap().set_text("CTO");
367 tbl.cell(2, 2).unwrap().set_text("Remote");
368 tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
369 tbl.cell(3, 1).unwrap().set_text("Security");
370 tbl.cell(3, 2).unwrap().set_text("Washington");
371 }
372
373 doc.add_paragraph("");
374
375 doc.add_paragraph("5.2 Column Span & Cell Shading")
376 .style("Heading2");
377 {
378 let mut tbl = doc
379 .add_table(3, 4)
380 .borders(BorderStyle::Single, 4, "000000")
381 .width_pct(100.0);
382 tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
383 tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
384 tbl.cell(1, 0).unwrap().set_text("Region");
385 tbl.cell(1, 0).unwrap().shading("D6E4F0");
386 tbl.cell(1, 1).unwrap().set_text("Q1");
387 tbl.cell(1, 1).unwrap().shading("D6E4F0");
388 tbl.cell(1, 2).unwrap().set_text("Q2");
389 tbl.cell(1, 2).unwrap().shading("D6E4F0");
390 tbl.cell(1, 3).unwrap().set_text("Total");
391 tbl.cell(1, 3).unwrap().shading("D6E4F0");
392 tbl.cell(2, 0).unwrap().set_text("Americas");
393 tbl.cell(2, 1).unwrap().set_text("$2.4M");
394 tbl.cell(2, 2).unwrap().set_text("$2.7M");
395 tbl.cell(2, 3).unwrap().set_text("$5.1M");
396 }
397
398 doc.add_paragraph("");
399
400 doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
401 {
402 let mut tbl = doc
403 .add_table(4, 3)
404 .borders(BorderStyle::Single, 4, "000000");
405 tbl.cell(0, 0).unwrap().set_text("Category");
406 tbl.cell(0, 0).unwrap().shading("E2EFDA");
407 tbl.cell(0, 1).unwrap().set_text("Item");
408 tbl.cell(0, 1).unwrap().shading("E2EFDA");
409 tbl.cell(0, 2).unwrap().set_text("Price");
410 tbl.cell(0, 2).unwrap().shading("E2EFDA");
411 tbl.cell(1, 0).unwrap().set_text("Hardware");
412 tbl.cell(1, 0).unwrap().v_merge_restart();
413 tbl.cell(1, 1).unwrap().set_text("Laptop");
414 tbl.cell(1, 2).unwrap().set_text("$1,200");
415 tbl.cell(2, 0).unwrap().v_merge_continue();
416 tbl.cell(2, 1).unwrap().set_text("Monitor");
417 tbl.cell(2, 2).unwrap().set_text("$450");
418 tbl.cell(3, 0).unwrap().set_text("Software");
419 tbl.cell(3, 1).unwrap().set_text("IDE License");
420 tbl.cell(3, 2).unwrap().set_text("$200/yr");
421 }
422
423 doc.add_paragraph("");
424
425 doc.add_paragraph("5.4 Nested Table").style("Heading2");
426 {
427 let mut tbl = doc
428 .add_table(2, 2)
429 .borders(BorderStyle::Single, 6, "2E75B6");
430 tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
431 tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
432 tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
433 {
434 let mut cell = tbl.cell(1, 1).unwrap();
435 cell.set_text("Nested table below:");
436 let mut nested = cell
437 .add_table(2, 2)
438 .borders(BorderStyle::Single, 2, "FF6600");
439 nested.cell(0, 0).unwrap().set_text("A");
440 nested.cell(0, 1).unwrap().set_text("B");
441 nested.cell(1, 0).unwrap().set_text("C");
442 nested.cell(1, 1).unwrap().set_text("D");
443 }
444 }
445
446 doc.add_paragraph("");
447
448 doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
449 .style("Heading2");
450 {
451 let mut tbl = doc
452 .add_table(2, 3)
453 .borders(BorderStyle::Single, 4, "666666");
454 tbl.row(0).unwrap().height(Length::pt(50.0)).header();
455 tbl.cell(0, 0).unwrap().set_text("Top");
456 tbl.cell(0, 0)
457 .unwrap()
458 .vertical_alignment(VerticalAlignment::Top)
459 .shading("FFF2CC");
460 tbl.cell(0, 1).unwrap().set_text("Center");
461 tbl.cell(0, 1)
462 .unwrap()
463 .vertical_alignment(VerticalAlignment::Center)
464 .shading("D9E2F3");
465 tbl.cell(0, 2).unwrap().set_text("Bottom");
466 tbl.cell(0, 2)
467 .unwrap()
468 .vertical_alignment(VerticalAlignment::Bottom)
469 .shading("E2EFDA");
470 tbl.row(1).unwrap().cant_split();
471 tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
472 tbl.cell(1, 0).unwrap().no_wrap();
473 tbl.cell(1, 1).unwrap().set_text("Fixed width");
474 tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
475 tbl.cell(1, 2).unwrap().set_text("Normal");
476 }
477
478 // ── SECTION 6: IMAGES ──
479 doc.add_paragraph("").page_break_before(true);
480 doc.add_paragraph("6. Images").style("Heading1");
481
482 doc.add_paragraph("6.1 Inline Image").style("Heading2");
483 doc.add_paragraph("A blue gradient image below:");
484 let img = create_sample_png(200, 50, [0, 80, 200]);
485 doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
486
487 doc.add_paragraph("");
488 doc.add_paragraph("6.2 Header Image").style("Heading2");
489 let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
490 doc.set_header_image(
491 &hdr_img,
492 "header_logo.png",
493 Length::inches(2.0),
494 Length::inches(0.2),
495 );
496 doc.add_paragraph("The header now contains an inline image (check the top of this page).");
497
498 doc.add_paragraph("");
499 doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
500
501 // ── SECTION 7: CONTENT MANIPULATION ──
502 doc.add_paragraph("").page_break_before(true);
503 doc.add_paragraph("7. Content Manipulation")
504 .style("Heading1");
505
506 doc.add_paragraph("7.1 Placeholder Replacement")
507 .style("Heading2");
508 doc.add_paragraph("Customer: {{customer}}");
509 doc.add_paragraph("Date: {{date}}");
510 doc.add_paragraph("Reference: {{ref_number}}");
511 {
512 let mut tbl = doc
513 .add_table(2, 2)
514 .borders(BorderStyle::Single, 4, "000000");
515 tbl.cell(0, 0).unwrap().set_text("Project");
516 tbl.cell(0, 0).unwrap().shading("D6E4F0");
517 tbl.cell(0, 1).unwrap().set_text("{{project}}");
518 tbl.cell(1, 0).unwrap().set_text("Status");
519 tbl.cell(1, 0).unwrap().shading("D6E4F0");
520 tbl.cell(1, 1).unwrap().set_text("{{status}}");
521 }
522
523 let mut replacements = HashMap::new();
524 replacements.insert("{{customer}}", "Tensorbee Inc.");
525 replacements.insert("{{date}}", "February 22, 2026");
526 replacements.insert("{{ref_number}}", "TB-2026-001");
527 replacements.insert("{{project}}", "Infrastructure Upgrade");
528 replacements.insert("{{status}}", "In Progress");
529 let n = doc.replace_all(&replacements);
530 doc.add_paragraph(&format!("({n} placeholders replaced above)"));
531
532 doc.add_paragraph("");
533
534 doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
535 doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
536 let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
537 doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
538
539 doc.add_paragraph("");
540
541 doc.add_paragraph("7.3 Content Insertion").style("Heading2");
542 doc.add_paragraph("Section A: First content.");
543 doc.add_paragraph("Section C: Third content.");
544 if let Some(idx) = doc.find_content_index("Section C") {
545 doc.insert_paragraph(
546 idx,
547 "Section B: Inserted between A and C via find_content_index().",
548 );
549 }
550
551 // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
552 doc.add_paragraph("").section_break(SectionBreak::NextPage);
553 doc.add_paragraph("8. Section Breaks & Orientation")
554 .style("Heading1");
555 doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
556
557 {
558 let mut tbl = doc
559 .add_table(3, 7)
560 .borders(BorderStyle::Single, 4, "2E75B6");
561 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
562 for (c, h) in headers.iter().enumerate() {
563 tbl.cell(0, c).unwrap().set_text(h);
564 tbl.cell(0, c).unwrap().shading("2E75B6");
565 }
566 let data = [
567 [
568 "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
569 ],
570 [
571 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
572 ],
573 ];
574 for (r, row) in data.iter().enumerate() {
575 for (c, v) in row.iter().enumerate() {
576 tbl.cell(r + 1, c).unwrap().set_text(v);
577 }
578 }
579 }
580
581 doc.add_paragraph("")
582 .section_break(SectionBreak::NextPage)
583 .section_landscape();
584
585 // ── SECTION 9: CUSTOM STYLES ──
586 doc.add_paragraph("9. Custom Styles").style("Heading1");
587 doc.add_style(
588 StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
589 .based_on("Normal")
590 .paragraph_properties({
591 let mut ppr = rdocx_oxml::properties::CT_PPr::default();
592 ppr.shading = Some(rdocx_oxml::properties::CT_Shd {
593 val: "clear".to_string(),
594 color: None,
595 fill: Some("FFF2CC".to_string()),
596 });
597 ppr
598 })
599 .run_properties({
600 let mut rpr = rdocx_oxml::properties::CT_RPr::default();
601 rpr.bold = Some(true);
602 rpr.color = Some("C45911".to_string());
603 rpr
604 }),
605 );
606 doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
607 .style("CustomHighlight");
608
609 doc.add_paragraph("");
610
611 // ── SECTION 10: DOCUMENT INTELLIGENCE ──
612 doc.add_paragraph("10. Document Intelligence API")
613 .style("Heading1");
614
615 let wc = doc.word_count();
616 let hc = doc.headings().len();
617 let ic = doc.images().len();
618 let lc = doc.links().len();
619 doc.add_paragraph(&format!("Word count: {wc}"));
620 doc.add_paragraph(&format!("Heading count: {hc}"));
621 doc.add_paragraph(&format!("Image count: {ic}"));
622 doc.add_paragraph(&format!("Link count: {lc}"));
623
624 let outline = doc.document_outline();
625 doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
626
627 let issues = doc.audit_accessibility();
628 doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
629 for issue in &issues {
630 doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
631 }
632
633 // ── SECTION 11: DOCUMENT MERGING ──
634 doc.add_paragraph("");
635 doc.add_paragraph("11. Document Merging").style("Heading1");
636 {
637 let mut other = Document::new();
638 other.add_paragraph("This paragraph was merged from another document using append().");
639 doc.append(&other);
640 }
641
642 doc.add_paragraph("");
643
644 // ── SUMMARY ──
645 doc.add_paragraph("Summary of Demonstrated Features")
646 .style("Heading1");
647 let features = [
648 "Page setup: size, margins, header/footer distance, gutter",
649 "Document metadata: title, author, subject, keywords",
650 "Headers and footers: text, images, different first page",
651 "Background images (full-page behind text)",
652 "Table of Contents generation with bookmarks",
653 "Text formatting: bold, italic, underline styles, strike, color, size, font",
654 "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
655 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
656 "Pagination controls: keep-with-next, keep-together, widow control",
657 "Bullet and numbered lists with nesting",
658 "Tab stops with dot/underscore/hyphen leaders",
659 "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
660 "Row properties: header rows, exact height, cant-split",
661 "Cell properties: width, no-wrap, vertical alignment",
662 "Inline images and header images",
663 "Placeholder replacement (single and batch)",
664 "Regex-based find and replace",
665 "Content insertion at specific positions",
666 "Section breaks with mixed portrait/landscape",
667 "Custom paragraph and character styles",
668 "Document intelligence: word count, headings, outline, images, links",
669 "Accessibility audit",
670 "Document merging (append)",
671 "Export: DOCX, PDF, HTML, Markdown",
672 ];
673 for f in &features {
674 doc.add_bullet_list_item(f, 0);
675 }
676 doc.add_paragraph("");
677 doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
678 .alignment(Alignment::Center)
679 .shading("E2EFDA")
680 .border_all(BorderStyle::Single, 2, "00AA00");
681
682 doc
683}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?
86fn generate_feature_showcase(_samples_dir: &Path) -> Document {
87 let mut doc = Document::new();
88
89 // ── Page Setup & Metadata ──
90 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
91 doc.set_margins(
92 Length::inches(1.0),
93 Length::inches(1.0),
94 Length::inches(1.0),
95 Length::inches(1.0),
96 );
97 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
98 doc.set_gutter(Length::twips(0));
99 doc.set_title("rdocx Feature Showcase");
100 doc.set_author("rdocx Sample Generator");
101 doc.set_subject("Comprehensive feature demonstration");
102 doc.set_keywords("rdocx, docx, rust, sample, showcase");
103
104 // Headers & Footers with different first page
105 doc.set_different_first_page(true);
106 doc.set_first_page_header("rdocx");
107 doc.set_first_page_footer("Feature Showcase — Cover Page");
108 doc.set_header("rdocx Feature Showcase");
109 doc.set_footer("Generated by rdocx");
110
111 // ── COVER PAGE ──
112 let bg = create_sample_png(612, 792, [20, 45, 90]);
113 doc.add_background_image(&bg, "cover_bg.png");
114
115 for _ in 0..3 {
116 doc.add_paragraph("");
117 }
118 {
119 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
120 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
121 }
122 {
123 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
124 p.add_run("Complete Feature Showcase")
125 .size(28.0)
126 .color("FFFFFF")
127 .italic(true);
128 }
129 doc.add_paragraph("");
130 {
131 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
132 p.add_run("Every feature of the rdocx Rust library")
133 .size(13.0)
134 .color("B0C4DE");
135 }
136 {
137 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
138 p.add_run("demonstrated in a single document.")
139 .size(13.0)
140 .color("B0C4DE");
141 }
142
143 // ── TABLE OF CONTENTS ──
144 doc.add_paragraph("").page_break_before(true);
145 doc.insert_toc(doc.content_count(), 3);
146
147 // ── SECTION 1: TEXT FORMATTING ──
148 doc.add_paragraph("").page_break_before(true);
149 doc.add_paragraph("1. Text Formatting").style("Heading1");
150
151 // Paragraph Alignment
152 doc.add_paragraph("1.1 Paragraph Alignment")
153 .style("Heading2");
154 doc.add_paragraph("Left-aligned paragraph (default).")
155 .alignment(Alignment::Left);
156 doc.add_paragraph("Center-aligned paragraph.")
157 .alignment(Alignment::Center);
158 doc.add_paragraph("Right-aligned paragraph.")
159 .alignment(Alignment::Right);
160 doc.add_paragraph(
161 "Justified paragraph — this text is long enough to demonstrate how justified alignment \
162 distributes extra space across word gaps so lines fill the full width of the text area.",
163 )
164 .alignment(Alignment::Justify);
165
166 doc.add_paragraph("");
167
168 // Run Formatting
169 doc.add_paragraph("1.2 Run Formatting").style("Heading2");
170 {
171 let mut p = doc.add_paragraph("");
172 p.add_run("Bold").bold(true);
173 p.add_run(" | ");
174 p.add_run("Italic").italic(true);
175 p.add_run(" | ");
176 p.add_run("Bold Italic").bold(true).italic(true);
177 p.add_run(" | ");
178 p.add_run("Underline").underline(true);
179 p.add_run(" | ");
180 p.add_run("Strikethrough").strike(true);
181 p.add_run(" | ");
182 p.add_run("Double Strike").double_strike(true);
183 }
184 {
185 let mut p = doc.add_paragraph("");
186 p.add_run("Red").color("FF0000");
187 p.add_run(" | ");
188 p.add_run("Blue").color("0000FF");
189 p.add_run(" | ");
190 p.add_run("Green").color("00AA00");
191 p.add_run(" | ");
192 p.add_run("Highlighted").highlight("FFFF00");
193 }
194 {
195 let mut p = doc.add_paragraph("");
196 p.add_run("8pt").size(8.0);
197 p.add_run(" | ");
198 p.add_run("11pt").size(11.0);
199 p.add_run(" | ");
200 p.add_run("16pt").size(16.0);
201 p.add_run(" | ");
202 p.add_run("24pt").size(24.0);
203 }
204 {
205 let mut p = doc.add_paragraph("");
206 p.add_run("Arial").font("Arial");
207 p.add_run(" | ");
208 p.add_run("Times New Roman").font("Times New Roman");
209 p.add_run(" | ");
210 p.add_run("Courier New").font("Courier New");
211 }
212 {
213 let mut p = doc.add_paragraph("");
214 p.add_run("H");
215 p.add_run("2").subscript();
216 p.add_run("O (subscript) | E=mc");
217 p.add_run("2").superscript();
218 p.add_run(" (superscript)");
219 }
220 {
221 let mut p = doc.add_paragraph("");
222 p.add_run("ALL CAPS").all_caps(true);
223 p.add_run(" | ");
224 p.add_run("Small Caps").small_caps(true);
225 p.add_run(" | ");
226 p.add_run("Expanded").character_spacing(Length::twips(40));
227 p.add_run(" | ");
228 p.add_run("Hidden text (not visible)").hidden(true);
229 }
230
231 doc.add_paragraph("");
232
233 // Underline Styles
234 doc.add_paragraph("1.3 Underline Styles").style("Heading2");
235 {
236 let mut p = doc.add_paragraph("");
237 p.add_run("Single ")
238 .underline_style(rdocx::UnderlineStyle::Single);
239 p.add_run("Double ")
240 .underline_style(rdocx::UnderlineStyle::Double);
241 p.add_run("Thick ")
242 .underline_style(rdocx::UnderlineStyle::Thick);
243 p.add_run("Dotted ")
244 .underline_style(rdocx::UnderlineStyle::Dotted);
245 p.add_run("Dash ")
246 .underline_style(rdocx::UnderlineStyle::Dash);
247 p.add_run("Wave ")
248 .underline_style(rdocx::UnderlineStyle::Wave);
249 }
250
251 // ── SECTION 2: PARAGRAPH FORMATTING ──
252 doc.add_paragraph("").page_break_before(true);
253 doc.add_paragraph("2. Paragraph Formatting")
254 .style("Heading1");
255
256 doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
257 doc.add_paragraph("Paragraph with light green shading")
258 .shading("E2EFDA");
259 doc.add_paragraph("Paragraph with bottom border")
260 .border_bottom(BorderStyle::Single, 6, "2E75B6");
261 doc.add_paragraph("Paragraph with all borders (red)")
262 .border_all(BorderStyle::Single, 4, "FF0000");
263
264 doc.add_paragraph("2.2 Indentation").style("Heading2");
265 doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
266 .indent_left(Length::inches(1.0))
267 .hanging_indent(Length::inches(0.5));
268 doc.add_paragraph("0.5-inch first-line indent on this paragraph")
269 .first_line_indent(Length::inches(0.5));
270 doc.add_paragraph("Both left and right indent (0.75 inches each)")
271 .indent_left(Length::inches(0.75))
272 .indent_right(Length::inches(0.75));
273
274 doc.add_paragraph("2.3 Spacing & Line Height")
275 .style("Heading2");
276 doc.add_paragraph("Extra space before (24pt) and after (12pt)")
277 .space_before(Length::pt(24.0))
278 .space_after(Length::pt(12.0));
279 doc.add_paragraph(
280 "Double line spacing paragraph. This text should have extra vertical space between \
281 lines to demonstrate line_spacing_multiple(2.0).",
282 )
283 .line_spacing_multiple(2.0);
284 doc.add_paragraph("Exact 20pt line spacing (fixed height).")
285 .line_spacing(20.0);
286
287 doc.add_paragraph("2.4 Pagination Controls")
288 .style("Heading2");
289 doc.add_paragraph("keep_with_next — this paragraph stays with the next")
290 .keep_with_next(true);
291 doc.add_paragraph("(This paragraph was kept with the one above.)");
292 doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
293 .keep_together(true);
294 doc.add_paragraph("widow_control — prevents widow/orphan lines")
295 .widow_control(true);
296
297 // ── SECTION 3: LISTS ──
298 doc.add_paragraph("").page_break_before(true);
299 doc.add_paragraph("3. Lists").style("Heading1");
300
301 doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
302 doc.add_bullet_list_item("First item", 0);
303 doc.add_bullet_list_item("Second item", 0);
304 doc.add_bullet_list_item("Nested level 1", 1);
305 doc.add_bullet_list_item("Nested level 2", 2);
306 doc.add_bullet_list_item("Back to level 1", 1);
307 doc.add_bullet_list_item("Third item", 0);
308
309 doc.add_paragraph("");
310
311 doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
312 doc.add_numbered_list_item("First numbered item", 0);
313 doc.add_numbered_list_item("Second numbered item", 0);
314 doc.add_numbered_list_item("Sub-item A", 1);
315 doc.add_numbered_list_item("Sub-item B", 1);
316 doc.add_numbered_list_item("Third numbered item", 0);
317
318 // ── SECTION 4: TAB STOPS ──
319 doc.add_paragraph("");
320 doc.add_paragraph("4. Tab Stops").style("Heading1");
321
322 doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
323 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
324 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
325 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
326 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
327 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
328
329 doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
330 doc.add_paragraph("Item\t\tPrice")
331 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
332 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
333 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
334 doc.add_paragraph("Widget\t\t$19.99")
335 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
336 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
337 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
338 doc.add_paragraph("Gadget\t\t$249.50")
339 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
340 .add_tab_stop_with_leader(
341 TabAlignment::Right,
342 Length::inches(4.0),
343 TabLeader::Underscore,
344 )
345 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
346
347 // ── SECTION 5: TABLES ──
348 doc.add_paragraph("").page_break_before(true);
349 doc.add_paragraph("5. Tables").style("Heading1");
350
351 doc.add_paragraph("5.1 Basic Table").style("Heading2");
352 {
353 let mut tbl = doc
354 .add_table(4, 3)
355 .borders(BorderStyle::Single, 4, "000000");
356 for col in 0..3 {
357 tbl.cell(0, col).unwrap().shading("2E75B6");
358 }
359 tbl.cell(0, 0).unwrap().set_text("Name");
360 tbl.cell(0, 1).unwrap().set_text("Role");
361 tbl.cell(0, 2).unwrap().set_text("Location");
362 tbl.cell(1, 0).unwrap().set_text("Walter White");
363 tbl.cell(1, 1).unwrap().set_text("CEO");
364 tbl.cell(1, 2).unwrap().set_text("Albuquerque");
365 tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
366 tbl.cell(2, 1).unwrap().set_text("CTO");
367 tbl.cell(2, 2).unwrap().set_text("Remote");
368 tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
369 tbl.cell(3, 1).unwrap().set_text("Security");
370 tbl.cell(3, 2).unwrap().set_text("Washington");
371 }
372
373 doc.add_paragraph("");
374
375 doc.add_paragraph("5.2 Column Span & Cell Shading")
376 .style("Heading2");
377 {
378 let mut tbl = doc
379 .add_table(3, 4)
380 .borders(BorderStyle::Single, 4, "000000")
381 .width_pct(100.0);
382 tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
383 tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
384 tbl.cell(1, 0).unwrap().set_text("Region");
385 tbl.cell(1, 0).unwrap().shading("D6E4F0");
386 tbl.cell(1, 1).unwrap().set_text("Q1");
387 tbl.cell(1, 1).unwrap().shading("D6E4F0");
388 tbl.cell(1, 2).unwrap().set_text("Q2");
389 tbl.cell(1, 2).unwrap().shading("D6E4F0");
390 tbl.cell(1, 3).unwrap().set_text("Total");
391 tbl.cell(1, 3).unwrap().shading("D6E4F0");
392 tbl.cell(2, 0).unwrap().set_text("Americas");
393 tbl.cell(2, 1).unwrap().set_text("$2.4M");
394 tbl.cell(2, 2).unwrap().set_text("$2.7M");
395 tbl.cell(2, 3).unwrap().set_text("$5.1M");
396 }
397
398 doc.add_paragraph("");
399
400 doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
401 {
402 let mut tbl = doc
403 .add_table(4, 3)
404 .borders(BorderStyle::Single, 4, "000000");
405 tbl.cell(0, 0).unwrap().set_text("Category");
406 tbl.cell(0, 0).unwrap().shading("E2EFDA");
407 tbl.cell(0, 1).unwrap().set_text("Item");
408 tbl.cell(0, 1).unwrap().shading("E2EFDA");
409 tbl.cell(0, 2).unwrap().set_text("Price");
410 tbl.cell(0, 2).unwrap().shading("E2EFDA");
411 tbl.cell(1, 0).unwrap().set_text("Hardware");
412 tbl.cell(1, 0).unwrap().v_merge_restart();
413 tbl.cell(1, 1).unwrap().set_text("Laptop");
414 tbl.cell(1, 2).unwrap().set_text("$1,200");
415 tbl.cell(2, 0).unwrap().v_merge_continue();
416 tbl.cell(2, 1).unwrap().set_text("Monitor");
417 tbl.cell(2, 2).unwrap().set_text("$450");
418 tbl.cell(3, 0).unwrap().set_text("Software");
419 tbl.cell(3, 1).unwrap().set_text("IDE License");
420 tbl.cell(3, 2).unwrap().set_text("$200/yr");
421 }
422
423 doc.add_paragraph("");
424
425 doc.add_paragraph("5.4 Nested Table").style("Heading2");
426 {
427 let mut tbl = doc
428 .add_table(2, 2)
429 .borders(BorderStyle::Single, 6, "2E75B6");
430 tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
431 tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
432 tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
433 {
434 let mut cell = tbl.cell(1, 1).unwrap();
435 cell.set_text("Nested table below:");
436 let mut nested = cell
437 .add_table(2, 2)
438 .borders(BorderStyle::Single, 2, "FF6600");
439 nested.cell(0, 0).unwrap().set_text("A");
440 nested.cell(0, 1).unwrap().set_text("B");
441 nested.cell(1, 0).unwrap().set_text("C");
442 nested.cell(1, 1).unwrap().set_text("D");
443 }
444 }
445
446 doc.add_paragraph("");
447
448 doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
449 .style("Heading2");
450 {
451 let mut tbl = doc
452 .add_table(2, 3)
453 .borders(BorderStyle::Single, 4, "666666");
454 tbl.row(0).unwrap().height(Length::pt(50.0)).header();
455 tbl.cell(0, 0).unwrap().set_text("Top");
456 tbl.cell(0, 0)
457 .unwrap()
458 .vertical_alignment(VerticalAlignment::Top)
459 .shading("FFF2CC");
460 tbl.cell(0, 1).unwrap().set_text("Center");
461 tbl.cell(0, 1)
462 .unwrap()
463 .vertical_alignment(VerticalAlignment::Center)
464 .shading("D9E2F3");
465 tbl.cell(0, 2).unwrap().set_text("Bottom");
466 tbl.cell(0, 2)
467 .unwrap()
468 .vertical_alignment(VerticalAlignment::Bottom)
469 .shading("E2EFDA");
470 tbl.row(1).unwrap().cant_split();
471 tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
472 tbl.cell(1, 0).unwrap().no_wrap();
473 tbl.cell(1, 1).unwrap().set_text("Fixed width");
474 tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
475 tbl.cell(1, 2).unwrap().set_text("Normal");
476 }
477
478 // ── SECTION 6: IMAGES ──
479 doc.add_paragraph("").page_break_before(true);
480 doc.add_paragraph("6. Images").style("Heading1");
481
482 doc.add_paragraph("6.1 Inline Image").style("Heading2");
483 doc.add_paragraph("A blue gradient image below:");
484 let img = create_sample_png(200, 50, [0, 80, 200]);
485 doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
486
487 doc.add_paragraph("");
488 doc.add_paragraph("6.2 Header Image").style("Heading2");
489 let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
490 doc.set_header_image(
491 &hdr_img,
492 "header_logo.png",
493 Length::inches(2.0),
494 Length::inches(0.2),
495 );
496 doc.add_paragraph("The header now contains an inline image (check the top of this page).");
497
498 doc.add_paragraph("");
499 doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
500
501 // ── SECTION 7: CONTENT MANIPULATION ──
502 doc.add_paragraph("").page_break_before(true);
503 doc.add_paragraph("7. Content Manipulation")
504 .style("Heading1");
505
506 doc.add_paragraph("7.1 Placeholder Replacement")
507 .style("Heading2");
508 doc.add_paragraph("Customer: {{customer}}");
509 doc.add_paragraph("Date: {{date}}");
510 doc.add_paragraph("Reference: {{ref_number}}");
511 {
512 let mut tbl = doc
513 .add_table(2, 2)
514 .borders(BorderStyle::Single, 4, "000000");
515 tbl.cell(0, 0).unwrap().set_text("Project");
516 tbl.cell(0, 0).unwrap().shading("D6E4F0");
517 tbl.cell(0, 1).unwrap().set_text("{{project}}");
518 tbl.cell(1, 0).unwrap().set_text("Status");
519 tbl.cell(1, 0).unwrap().shading("D6E4F0");
520 tbl.cell(1, 1).unwrap().set_text("{{status}}");
521 }
522
523 let mut replacements = HashMap::new();
524 replacements.insert("{{customer}}", "Tensorbee Inc.");
525 replacements.insert("{{date}}", "February 22, 2026");
526 replacements.insert("{{ref_number}}", "TB-2026-001");
527 replacements.insert("{{project}}", "Infrastructure Upgrade");
528 replacements.insert("{{status}}", "In Progress");
529 let n = doc.replace_all(&replacements);
530 doc.add_paragraph(&format!("({n} placeholders replaced above)"));
531
532 doc.add_paragraph("");
533
534 doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
535 doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
536 let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
537 doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
538
539 doc.add_paragraph("");
540
541 doc.add_paragraph("7.3 Content Insertion").style("Heading2");
542 doc.add_paragraph("Section A: First content.");
543 doc.add_paragraph("Section C: Third content.");
544 if let Some(idx) = doc.find_content_index("Section C") {
545 doc.insert_paragraph(
546 idx,
547 "Section B: Inserted between A and C via find_content_index().",
548 );
549 }
550
551 // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
552 doc.add_paragraph("").section_break(SectionBreak::NextPage);
553 doc.add_paragraph("8. Section Breaks & Orientation")
554 .style("Heading1");
555 doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
556
557 {
558 let mut tbl = doc
559 .add_table(3, 7)
560 .borders(BorderStyle::Single, 4, "2E75B6");
561 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
562 for (c, h) in headers.iter().enumerate() {
563 tbl.cell(0, c).unwrap().set_text(h);
564 tbl.cell(0, c).unwrap().shading("2E75B6");
565 }
566 let data = [
567 [
568 "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
569 ],
570 [
571 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
572 ],
573 ];
574 for (r, row) in data.iter().enumerate() {
575 for (c, v) in row.iter().enumerate() {
576 tbl.cell(r + 1, c).unwrap().set_text(v);
577 }
578 }
579 }
580
581 doc.add_paragraph("")
582 .section_break(SectionBreak::NextPage)
583 .section_landscape();
584
585 // ── SECTION 9: CUSTOM STYLES ──
586 doc.add_paragraph("9. Custom Styles").style("Heading1");
587 doc.add_style(
588 StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
589 .based_on("Normal")
590 .paragraph_properties({
591 let mut ppr = rdocx_oxml::properties::CT_PPr::default();
592 ppr.shading = Some(rdocx_oxml::properties::CT_Shd {
593 val: "clear".to_string(),
594 color: None,
595 fill: Some("FFF2CC".to_string()),
596 });
597 ppr
598 })
599 .run_properties({
600 let mut rpr = rdocx_oxml::properties::CT_RPr::default();
601 rpr.bold = Some(true);
602 rpr.color = Some("C45911".to_string());
603 rpr
604 }),
605 );
606 doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
607 .style("CustomHighlight");
608
609 doc.add_paragraph("");
610
611 // ── SECTION 10: DOCUMENT INTELLIGENCE ──
612 doc.add_paragraph("10. Document Intelligence API")
613 .style("Heading1");
614
615 let wc = doc.word_count();
616 let hc = doc.headings().len();
617 let ic = doc.images().len();
618 let lc = doc.links().len();
619 doc.add_paragraph(&format!("Word count: {wc}"));
620 doc.add_paragraph(&format!("Heading count: {hc}"));
621 doc.add_paragraph(&format!("Image count: {ic}"));
622 doc.add_paragraph(&format!("Link count: {lc}"));
623
624 let outline = doc.document_outline();
625 doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
626
627 let issues = doc.audit_accessibility();
628 doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
629 for issue in &issues {
630 doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
631 }
632
633 // ── SECTION 11: DOCUMENT MERGING ──
634 doc.add_paragraph("");
635 doc.add_paragraph("11. Document Merging").style("Heading1");
636 {
637 let mut other = Document::new();
638 other.add_paragraph("This paragraph was merged from another document using append().");
639 doc.append(&other);
640 }
641
642 doc.add_paragraph("");
643
644 // ── SUMMARY ──
645 doc.add_paragraph("Summary of Demonstrated Features")
646 .style("Heading1");
647 let features = [
648 "Page setup: size, margins, header/footer distance, gutter",
649 "Document metadata: title, author, subject, keywords",
650 "Headers and footers: text, images, different first page",
651 "Background images (full-page behind text)",
652 "Table of Contents generation with bookmarks",
653 "Text formatting: bold, italic, underline styles, strike, color, size, font",
654 "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
655 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
656 "Pagination controls: keep-with-next, keep-together, widow control",
657 "Bullet and numbered lists with nesting",
658 "Tab stops with dot/underscore/hyphen leaders",
659 "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
660 "Row properties: header rows, exact height, cant-split",
661 "Cell properties: width, no-wrap, vertical alignment",
662 "Inline images and header images",
663 "Placeholder replacement (single and batch)",
664 "Regex-based find and replace",
665 "Content insertion at specific positions",
666 "Section breaks with mixed portrait/landscape",
667 "Custom paragraph and character styles",
668 "Document intelligence: word count, headings, outline, images, links",
669 "Accessibility audit",
670 "Document merging (append)",
671 "Export: DOCX, PDF, HTML, Markdown",
672 ];
673 for f in &features {
674 doc.add_bullet_list_item(f, 0);
675 }
676 doc.add_paragraph("");
677 doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
678 .alignment(Alignment::Center)
679 .shading("E2EFDA")
680 .border_all(BorderStyle::Single, 2, "00AA00");
681
682 doc
683}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
- Bundled fonts (if
bundled-fontsfeature is enabled)
Examples found in repository?
58fn export_all(dir: &Path, name: &str, mut doc: Document) {
59 let docx_path = dir.join(format!("{name}.docx"));
60 doc.save(&docx_path).unwrap();
61 println!(" {name}.docx");
62
63 match doc.to_pdf() {
64 Ok(pdf) => {
65 let pdf_path = dir.join(format!("{name}.pdf"));
66 std::fs::write(&pdf_path, &pdf).unwrap();
67 println!(" {name}.pdf ({} bytes)", pdf.len());
68 }
69 Err(e) => println!(" {name}.pdf — skipped: {e}"),
70 }
71
72 let html = doc.to_html();
73 let html_path = dir.join(format!("{name}.html"));
74 std::fs::write(&html_path, &html).unwrap();
75 println!(" {name}.html ({} bytes)", html.len());
76
77 let md = doc.to_markdown();
78 let md_path = dir.join(format!("{name}.md"));
79 std::fs::write(&md_path, &md).unwrap();
80 println!(" {name}.md ({} bytes)", md.len());
81}More examples
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_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
- Bundled fonts (if
bundled-fontsfeature is enabled)
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
58fn export_all(dir: &Path, name: &str, mut doc: Document) {
59 let docx_path = dir.join(format!("{name}.docx"));
60 doc.save(&docx_path).unwrap();
61 println!(" {name}.docx");
62
63 match doc.to_pdf() {
64 Ok(pdf) => {
65 let pdf_path = dir.join(format!("{name}.pdf"));
66 std::fs::write(&pdf_path, &pdf).unwrap();
67 println!(" {name}.pdf ({} bytes)", pdf.len());
68 }
69 Err(e) => println!(" {name}.pdf — skipped: {e}"),
70 }
71
72 let html = doc.to_html();
73 let html_path = dir.join(format!("{name}.html"));
74 std::fs::write(&html_path, &html).unwrap();
75 println!(" {name}.html ({} bytes)", html.len());
76
77 let md = doc.to_markdown();
78 let md_path = dir.join(format!("{name}.md"));
79 std::fs::write(&md_path, &md).unwrap();
80 println!(" {name}.md ({} bytes)", md.len());
81}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
58fn export_all(dir: &Path, name: &str, mut doc: Document) {
59 let docx_path = dir.join(format!("{name}.docx"));
60 doc.save(&docx_path).unwrap();
61 println!(" {name}.docx");
62
63 match doc.to_pdf() {
64 Ok(pdf) => {
65 let pdf_path = dir.join(format!("{name}.pdf"));
66 std::fs::write(&pdf_path, &pdf).unwrap();
67 println!(" {name}.pdf ({} bytes)", pdf.len());
68 }
69 Err(e) => println!(" {name}.pdf — skipped: {e}"),
70 }
71
72 let html = doc.to_html();
73 let html_path = dir.join(format!("{name}.html"));
74 std::fs::write(&html_path, &html).unwrap();
75 println!(" {name}.html ({} bytes)", html.len());
76
77 let md = doc.to_markdown();
78 let md_path = dir.join(format!("{name}.md"));
79 std::fs::write(&md_path, &md).unwrap();
80 println!(" {name}.md ({} bytes)", md.len());
81}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_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 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?
86fn generate_feature_showcase(_samples_dir: &Path) -> Document {
87 let mut doc = Document::new();
88
89 // ── Page Setup & Metadata ──
90 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
91 doc.set_margins(
92 Length::inches(1.0),
93 Length::inches(1.0),
94 Length::inches(1.0),
95 Length::inches(1.0),
96 );
97 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
98 doc.set_gutter(Length::twips(0));
99 doc.set_title("rdocx Feature Showcase");
100 doc.set_author("rdocx Sample Generator");
101 doc.set_subject("Comprehensive feature demonstration");
102 doc.set_keywords("rdocx, docx, rust, sample, showcase");
103
104 // Headers & Footers with different first page
105 doc.set_different_first_page(true);
106 doc.set_first_page_header("rdocx");
107 doc.set_first_page_footer("Feature Showcase — Cover Page");
108 doc.set_header("rdocx Feature Showcase");
109 doc.set_footer("Generated by rdocx");
110
111 // ── COVER PAGE ──
112 let bg = create_sample_png(612, 792, [20, 45, 90]);
113 doc.add_background_image(&bg, "cover_bg.png");
114
115 for _ in 0..3 {
116 doc.add_paragraph("");
117 }
118 {
119 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
120 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
121 }
122 {
123 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
124 p.add_run("Complete Feature Showcase")
125 .size(28.0)
126 .color("FFFFFF")
127 .italic(true);
128 }
129 doc.add_paragraph("");
130 {
131 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
132 p.add_run("Every feature of the rdocx Rust library")
133 .size(13.0)
134 .color("B0C4DE");
135 }
136 {
137 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
138 p.add_run("demonstrated in a single document.")
139 .size(13.0)
140 .color("B0C4DE");
141 }
142
143 // ── TABLE OF CONTENTS ──
144 doc.add_paragraph("").page_break_before(true);
145 doc.insert_toc(doc.content_count(), 3);
146
147 // ── SECTION 1: TEXT FORMATTING ──
148 doc.add_paragraph("").page_break_before(true);
149 doc.add_paragraph("1. Text Formatting").style("Heading1");
150
151 // Paragraph Alignment
152 doc.add_paragraph("1.1 Paragraph Alignment")
153 .style("Heading2");
154 doc.add_paragraph("Left-aligned paragraph (default).")
155 .alignment(Alignment::Left);
156 doc.add_paragraph("Center-aligned paragraph.")
157 .alignment(Alignment::Center);
158 doc.add_paragraph("Right-aligned paragraph.")
159 .alignment(Alignment::Right);
160 doc.add_paragraph(
161 "Justified paragraph — this text is long enough to demonstrate how justified alignment \
162 distributes extra space across word gaps so lines fill the full width of the text area.",
163 )
164 .alignment(Alignment::Justify);
165
166 doc.add_paragraph("");
167
168 // Run Formatting
169 doc.add_paragraph("1.2 Run Formatting").style("Heading2");
170 {
171 let mut p = doc.add_paragraph("");
172 p.add_run("Bold").bold(true);
173 p.add_run(" | ");
174 p.add_run("Italic").italic(true);
175 p.add_run(" | ");
176 p.add_run("Bold Italic").bold(true).italic(true);
177 p.add_run(" | ");
178 p.add_run("Underline").underline(true);
179 p.add_run(" | ");
180 p.add_run("Strikethrough").strike(true);
181 p.add_run(" | ");
182 p.add_run("Double Strike").double_strike(true);
183 }
184 {
185 let mut p = doc.add_paragraph("");
186 p.add_run("Red").color("FF0000");
187 p.add_run(" | ");
188 p.add_run("Blue").color("0000FF");
189 p.add_run(" | ");
190 p.add_run("Green").color("00AA00");
191 p.add_run(" | ");
192 p.add_run("Highlighted").highlight("FFFF00");
193 }
194 {
195 let mut p = doc.add_paragraph("");
196 p.add_run("8pt").size(8.0);
197 p.add_run(" | ");
198 p.add_run("11pt").size(11.0);
199 p.add_run(" | ");
200 p.add_run("16pt").size(16.0);
201 p.add_run(" | ");
202 p.add_run("24pt").size(24.0);
203 }
204 {
205 let mut p = doc.add_paragraph("");
206 p.add_run("Arial").font("Arial");
207 p.add_run(" | ");
208 p.add_run("Times New Roman").font("Times New Roman");
209 p.add_run(" | ");
210 p.add_run("Courier New").font("Courier New");
211 }
212 {
213 let mut p = doc.add_paragraph("");
214 p.add_run("H");
215 p.add_run("2").subscript();
216 p.add_run("O (subscript) | E=mc");
217 p.add_run("2").superscript();
218 p.add_run(" (superscript)");
219 }
220 {
221 let mut p = doc.add_paragraph("");
222 p.add_run("ALL CAPS").all_caps(true);
223 p.add_run(" | ");
224 p.add_run("Small Caps").small_caps(true);
225 p.add_run(" | ");
226 p.add_run("Expanded").character_spacing(Length::twips(40));
227 p.add_run(" | ");
228 p.add_run("Hidden text (not visible)").hidden(true);
229 }
230
231 doc.add_paragraph("");
232
233 // Underline Styles
234 doc.add_paragraph("1.3 Underline Styles").style("Heading2");
235 {
236 let mut p = doc.add_paragraph("");
237 p.add_run("Single ")
238 .underline_style(rdocx::UnderlineStyle::Single);
239 p.add_run("Double ")
240 .underline_style(rdocx::UnderlineStyle::Double);
241 p.add_run("Thick ")
242 .underline_style(rdocx::UnderlineStyle::Thick);
243 p.add_run("Dotted ")
244 .underline_style(rdocx::UnderlineStyle::Dotted);
245 p.add_run("Dash ")
246 .underline_style(rdocx::UnderlineStyle::Dash);
247 p.add_run("Wave ")
248 .underline_style(rdocx::UnderlineStyle::Wave);
249 }
250
251 // ── SECTION 2: PARAGRAPH FORMATTING ──
252 doc.add_paragraph("").page_break_before(true);
253 doc.add_paragraph("2. Paragraph Formatting")
254 .style("Heading1");
255
256 doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
257 doc.add_paragraph("Paragraph with light green shading")
258 .shading("E2EFDA");
259 doc.add_paragraph("Paragraph with bottom border")
260 .border_bottom(BorderStyle::Single, 6, "2E75B6");
261 doc.add_paragraph("Paragraph with all borders (red)")
262 .border_all(BorderStyle::Single, 4, "FF0000");
263
264 doc.add_paragraph("2.2 Indentation").style("Heading2");
265 doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
266 .indent_left(Length::inches(1.0))
267 .hanging_indent(Length::inches(0.5));
268 doc.add_paragraph("0.5-inch first-line indent on this paragraph")
269 .first_line_indent(Length::inches(0.5));
270 doc.add_paragraph("Both left and right indent (0.75 inches each)")
271 .indent_left(Length::inches(0.75))
272 .indent_right(Length::inches(0.75));
273
274 doc.add_paragraph("2.3 Spacing & Line Height")
275 .style("Heading2");
276 doc.add_paragraph("Extra space before (24pt) and after (12pt)")
277 .space_before(Length::pt(24.0))
278 .space_after(Length::pt(12.0));
279 doc.add_paragraph(
280 "Double line spacing paragraph. This text should have extra vertical space between \
281 lines to demonstrate line_spacing_multiple(2.0).",
282 )
283 .line_spacing_multiple(2.0);
284 doc.add_paragraph("Exact 20pt line spacing (fixed height).")
285 .line_spacing(20.0);
286
287 doc.add_paragraph("2.4 Pagination Controls")
288 .style("Heading2");
289 doc.add_paragraph("keep_with_next — this paragraph stays with the next")
290 .keep_with_next(true);
291 doc.add_paragraph("(This paragraph was kept with the one above.)");
292 doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
293 .keep_together(true);
294 doc.add_paragraph("widow_control — prevents widow/orphan lines")
295 .widow_control(true);
296
297 // ── SECTION 3: LISTS ──
298 doc.add_paragraph("").page_break_before(true);
299 doc.add_paragraph("3. Lists").style("Heading1");
300
301 doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
302 doc.add_bullet_list_item("First item", 0);
303 doc.add_bullet_list_item("Second item", 0);
304 doc.add_bullet_list_item("Nested level 1", 1);
305 doc.add_bullet_list_item("Nested level 2", 2);
306 doc.add_bullet_list_item("Back to level 1", 1);
307 doc.add_bullet_list_item("Third item", 0);
308
309 doc.add_paragraph("");
310
311 doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
312 doc.add_numbered_list_item("First numbered item", 0);
313 doc.add_numbered_list_item("Second numbered item", 0);
314 doc.add_numbered_list_item("Sub-item A", 1);
315 doc.add_numbered_list_item("Sub-item B", 1);
316 doc.add_numbered_list_item("Third numbered item", 0);
317
318 // ── SECTION 4: TAB STOPS ──
319 doc.add_paragraph("");
320 doc.add_paragraph("4. Tab Stops").style("Heading1");
321
322 doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
323 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
324 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
325 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
326 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
327 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
328
329 doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
330 doc.add_paragraph("Item\t\tPrice")
331 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
332 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
333 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
334 doc.add_paragraph("Widget\t\t$19.99")
335 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
336 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
337 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
338 doc.add_paragraph("Gadget\t\t$249.50")
339 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
340 .add_tab_stop_with_leader(
341 TabAlignment::Right,
342 Length::inches(4.0),
343 TabLeader::Underscore,
344 )
345 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
346
347 // ── SECTION 5: TABLES ──
348 doc.add_paragraph("").page_break_before(true);
349 doc.add_paragraph("5. Tables").style("Heading1");
350
351 doc.add_paragraph("5.1 Basic Table").style("Heading2");
352 {
353 let mut tbl = doc
354 .add_table(4, 3)
355 .borders(BorderStyle::Single, 4, "000000");
356 for col in 0..3 {
357 tbl.cell(0, col).unwrap().shading("2E75B6");
358 }
359 tbl.cell(0, 0).unwrap().set_text("Name");
360 tbl.cell(0, 1).unwrap().set_text("Role");
361 tbl.cell(0, 2).unwrap().set_text("Location");
362 tbl.cell(1, 0).unwrap().set_text("Walter White");
363 tbl.cell(1, 1).unwrap().set_text("CEO");
364 tbl.cell(1, 2).unwrap().set_text("Albuquerque");
365 tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
366 tbl.cell(2, 1).unwrap().set_text("CTO");
367 tbl.cell(2, 2).unwrap().set_text("Remote");
368 tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
369 tbl.cell(3, 1).unwrap().set_text("Security");
370 tbl.cell(3, 2).unwrap().set_text("Washington");
371 }
372
373 doc.add_paragraph("");
374
375 doc.add_paragraph("5.2 Column Span & Cell Shading")
376 .style("Heading2");
377 {
378 let mut tbl = doc
379 .add_table(3, 4)
380 .borders(BorderStyle::Single, 4, "000000")
381 .width_pct(100.0);
382 tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
383 tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
384 tbl.cell(1, 0).unwrap().set_text("Region");
385 tbl.cell(1, 0).unwrap().shading("D6E4F0");
386 tbl.cell(1, 1).unwrap().set_text("Q1");
387 tbl.cell(1, 1).unwrap().shading("D6E4F0");
388 tbl.cell(1, 2).unwrap().set_text("Q2");
389 tbl.cell(1, 2).unwrap().shading("D6E4F0");
390 tbl.cell(1, 3).unwrap().set_text("Total");
391 tbl.cell(1, 3).unwrap().shading("D6E4F0");
392 tbl.cell(2, 0).unwrap().set_text("Americas");
393 tbl.cell(2, 1).unwrap().set_text("$2.4M");
394 tbl.cell(2, 2).unwrap().set_text("$2.7M");
395 tbl.cell(2, 3).unwrap().set_text("$5.1M");
396 }
397
398 doc.add_paragraph("");
399
400 doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
401 {
402 let mut tbl = doc
403 .add_table(4, 3)
404 .borders(BorderStyle::Single, 4, "000000");
405 tbl.cell(0, 0).unwrap().set_text("Category");
406 tbl.cell(0, 0).unwrap().shading("E2EFDA");
407 tbl.cell(0, 1).unwrap().set_text("Item");
408 tbl.cell(0, 1).unwrap().shading("E2EFDA");
409 tbl.cell(0, 2).unwrap().set_text("Price");
410 tbl.cell(0, 2).unwrap().shading("E2EFDA");
411 tbl.cell(1, 0).unwrap().set_text("Hardware");
412 tbl.cell(1, 0).unwrap().v_merge_restart();
413 tbl.cell(1, 1).unwrap().set_text("Laptop");
414 tbl.cell(1, 2).unwrap().set_text("$1,200");
415 tbl.cell(2, 0).unwrap().v_merge_continue();
416 tbl.cell(2, 1).unwrap().set_text("Monitor");
417 tbl.cell(2, 2).unwrap().set_text("$450");
418 tbl.cell(3, 0).unwrap().set_text("Software");
419 tbl.cell(3, 1).unwrap().set_text("IDE License");
420 tbl.cell(3, 2).unwrap().set_text("$200/yr");
421 }
422
423 doc.add_paragraph("");
424
425 doc.add_paragraph("5.4 Nested Table").style("Heading2");
426 {
427 let mut tbl = doc
428 .add_table(2, 2)
429 .borders(BorderStyle::Single, 6, "2E75B6");
430 tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
431 tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
432 tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
433 {
434 let mut cell = tbl.cell(1, 1).unwrap();
435 cell.set_text("Nested table below:");
436 let mut nested = cell
437 .add_table(2, 2)
438 .borders(BorderStyle::Single, 2, "FF6600");
439 nested.cell(0, 0).unwrap().set_text("A");
440 nested.cell(0, 1).unwrap().set_text("B");
441 nested.cell(1, 0).unwrap().set_text("C");
442 nested.cell(1, 1).unwrap().set_text("D");
443 }
444 }
445
446 doc.add_paragraph("");
447
448 doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
449 .style("Heading2");
450 {
451 let mut tbl = doc
452 .add_table(2, 3)
453 .borders(BorderStyle::Single, 4, "666666");
454 tbl.row(0).unwrap().height(Length::pt(50.0)).header();
455 tbl.cell(0, 0).unwrap().set_text("Top");
456 tbl.cell(0, 0)
457 .unwrap()
458 .vertical_alignment(VerticalAlignment::Top)
459 .shading("FFF2CC");
460 tbl.cell(0, 1).unwrap().set_text("Center");
461 tbl.cell(0, 1)
462 .unwrap()
463 .vertical_alignment(VerticalAlignment::Center)
464 .shading("D9E2F3");
465 tbl.cell(0, 2).unwrap().set_text("Bottom");
466 tbl.cell(0, 2)
467 .unwrap()
468 .vertical_alignment(VerticalAlignment::Bottom)
469 .shading("E2EFDA");
470 tbl.row(1).unwrap().cant_split();
471 tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
472 tbl.cell(1, 0).unwrap().no_wrap();
473 tbl.cell(1, 1).unwrap().set_text("Fixed width");
474 tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
475 tbl.cell(1, 2).unwrap().set_text("Normal");
476 }
477
478 // ── SECTION 6: IMAGES ──
479 doc.add_paragraph("").page_break_before(true);
480 doc.add_paragraph("6. Images").style("Heading1");
481
482 doc.add_paragraph("6.1 Inline Image").style("Heading2");
483 doc.add_paragraph("A blue gradient image below:");
484 let img = create_sample_png(200, 50, [0, 80, 200]);
485 doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
486
487 doc.add_paragraph("");
488 doc.add_paragraph("6.2 Header Image").style("Heading2");
489 let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
490 doc.set_header_image(
491 &hdr_img,
492 "header_logo.png",
493 Length::inches(2.0),
494 Length::inches(0.2),
495 );
496 doc.add_paragraph("The header now contains an inline image (check the top of this page).");
497
498 doc.add_paragraph("");
499 doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
500
501 // ── SECTION 7: CONTENT MANIPULATION ──
502 doc.add_paragraph("").page_break_before(true);
503 doc.add_paragraph("7. Content Manipulation")
504 .style("Heading1");
505
506 doc.add_paragraph("7.1 Placeholder Replacement")
507 .style("Heading2");
508 doc.add_paragraph("Customer: {{customer}}");
509 doc.add_paragraph("Date: {{date}}");
510 doc.add_paragraph("Reference: {{ref_number}}");
511 {
512 let mut tbl = doc
513 .add_table(2, 2)
514 .borders(BorderStyle::Single, 4, "000000");
515 tbl.cell(0, 0).unwrap().set_text("Project");
516 tbl.cell(0, 0).unwrap().shading("D6E4F0");
517 tbl.cell(0, 1).unwrap().set_text("{{project}}");
518 tbl.cell(1, 0).unwrap().set_text("Status");
519 tbl.cell(1, 0).unwrap().shading("D6E4F0");
520 tbl.cell(1, 1).unwrap().set_text("{{status}}");
521 }
522
523 let mut replacements = HashMap::new();
524 replacements.insert("{{customer}}", "Tensorbee Inc.");
525 replacements.insert("{{date}}", "February 22, 2026");
526 replacements.insert("{{ref_number}}", "TB-2026-001");
527 replacements.insert("{{project}}", "Infrastructure Upgrade");
528 replacements.insert("{{status}}", "In Progress");
529 let n = doc.replace_all(&replacements);
530 doc.add_paragraph(&format!("({n} placeholders replaced above)"));
531
532 doc.add_paragraph("");
533
534 doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
535 doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
536 let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
537 doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
538
539 doc.add_paragraph("");
540
541 doc.add_paragraph("7.3 Content Insertion").style("Heading2");
542 doc.add_paragraph("Section A: First content.");
543 doc.add_paragraph("Section C: Third content.");
544 if let Some(idx) = doc.find_content_index("Section C") {
545 doc.insert_paragraph(
546 idx,
547 "Section B: Inserted between A and C via find_content_index().",
548 );
549 }
550
551 // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
552 doc.add_paragraph("").section_break(SectionBreak::NextPage);
553 doc.add_paragraph("8. Section Breaks & Orientation")
554 .style("Heading1");
555 doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
556
557 {
558 let mut tbl = doc
559 .add_table(3, 7)
560 .borders(BorderStyle::Single, 4, "2E75B6");
561 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
562 for (c, h) in headers.iter().enumerate() {
563 tbl.cell(0, c).unwrap().set_text(h);
564 tbl.cell(0, c).unwrap().shading("2E75B6");
565 }
566 let data = [
567 [
568 "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
569 ],
570 [
571 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
572 ],
573 ];
574 for (r, row) in data.iter().enumerate() {
575 for (c, v) in row.iter().enumerate() {
576 tbl.cell(r + 1, c).unwrap().set_text(v);
577 }
578 }
579 }
580
581 doc.add_paragraph("")
582 .section_break(SectionBreak::NextPage)
583 .section_landscape();
584
585 // ── SECTION 9: CUSTOM STYLES ──
586 doc.add_paragraph("9. Custom Styles").style("Heading1");
587 doc.add_style(
588 StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
589 .based_on("Normal")
590 .paragraph_properties({
591 let mut ppr = rdocx_oxml::properties::CT_PPr::default();
592 ppr.shading = Some(rdocx_oxml::properties::CT_Shd {
593 val: "clear".to_string(),
594 color: None,
595 fill: Some("FFF2CC".to_string()),
596 });
597 ppr
598 })
599 .run_properties({
600 let mut rpr = rdocx_oxml::properties::CT_RPr::default();
601 rpr.bold = Some(true);
602 rpr.color = Some("C45911".to_string());
603 rpr
604 }),
605 );
606 doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
607 .style("CustomHighlight");
608
609 doc.add_paragraph("");
610
611 // ── SECTION 10: DOCUMENT INTELLIGENCE ──
612 doc.add_paragraph("10. Document Intelligence API")
613 .style("Heading1");
614
615 let wc = doc.word_count();
616 let hc = doc.headings().len();
617 let ic = doc.images().len();
618 let lc = doc.links().len();
619 doc.add_paragraph(&format!("Word count: {wc}"));
620 doc.add_paragraph(&format!("Heading count: {hc}"));
621 doc.add_paragraph(&format!("Image count: {ic}"));
622 doc.add_paragraph(&format!("Link count: {lc}"));
623
624 let outline = doc.document_outline();
625 doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
626
627 let issues = doc.audit_accessibility();
628 doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
629 for issue in &issues {
630 doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
631 }
632
633 // ── SECTION 11: DOCUMENT MERGING ──
634 doc.add_paragraph("");
635 doc.add_paragraph("11. Document Merging").style("Heading1");
636 {
637 let mut other = Document::new();
638 other.add_paragraph("This paragraph was merged from another document using append().");
639 doc.append(&other);
640 }
641
642 doc.add_paragraph("");
643
644 // ── SUMMARY ──
645 doc.add_paragraph("Summary of Demonstrated Features")
646 .style("Heading1");
647 let features = [
648 "Page setup: size, margins, header/footer distance, gutter",
649 "Document metadata: title, author, subject, keywords",
650 "Headers and footers: text, images, different first page",
651 "Background images (full-page behind text)",
652 "Table of Contents generation with bookmarks",
653 "Text formatting: bold, italic, underline styles, strike, color, size, font",
654 "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
655 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
656 "Pagination controls: keep-with-next, keep-together, widow control",
657 "Bullet and numbered lists with nesting",
658 "Tab stops with dot/underscore/hyphen leaders",
659 "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
660 "Row properties: header rows, exact height, cant-split",
661 "Cell properties: width, no-wrap, vertical alignment",
662 "Inline images and header images",
663 "Placeholder replacement (single and batch)",
664 "Regex-based find and replace",
665 "Content insertion at specific positions",
666 "Section breaks with mixed portrait/landscape",
667 "Custom paragraph and character styles",
668 "Document intelligence: word count, headings, outline, images, links",
669 "Accessibility audit",
670 "Document merging (append)",
671 "Export: DOCX, PDF, HTML, Markdown",
672 ];
673 for f in &features {
674 doc.add_bullet_list_item(f, 0);
675 }
676 doc.add_paragraph("");
677 doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
678 .alignment(Alignment::Center)
679 .shading("E2EFDA")
680 .border_all(BorderStyle::Single, 2, "00AA00");
681
682 doc
683}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?
86fn generate_feature_showcase(_samples_dir: &Path) -> Document {
87 let mut doc = Document::new();
88
89 // ── Page Setup & Metadata ──
90 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
91 doc.set_margins(
92 Length::inches(1.0),
93 Length::inches(1.0),
94 Length::inches(1.0),
95 Length::inches(1.0),
96 );
97 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
98 doc.set_gutter(Length::twips(0));
99 doc.set_title("rdocx Feature Showcase");
100 doc.set_author("rdocx Sample Generator");
101 doc.set_subject("Comprehensive feature demonstration");
102 doc.set_keywords("rdocx, docx, rust, sample, showcase");
103
104 // Headers & Footers with different first page
105 doc.set_different_first_page(true);
106 doc.set_first_page_header("rdocx");
107 doc.set_first_page_footer("Feature Showcase — Cover Page");
108 doc.set_header("rdocx Feature Showcase");
109 doc.set_footer("Generated by rdocx");
110
111 // ── COVER PAGE ──
112 let bg = create_sample_png(612, 792, [20, 45, 90]);
113 doc.add_background_image(&bg, "cover_bg.png");
114
115 for _ in 0..3 {
116 doc.add_paragraph("");
117 }
118 {
119 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
120 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
121 }
122 {
123 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
124 p.add_run("Complete Feature Showcase")
125 .size(28.0)
126 .color("FFFFFF")
127 .italic(true);
128 }
129 doc.add_paragraph("");
130 {
131 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
132 p.add_run("Every feature of the rdocx Rust library")
133 .size(13.0)
134 .color("B0C4DE");
135 }
136 {
137 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
138 p.add_run("demonstrated in a single document.")
139 .size(13.0)
140 .color("B0C4DE");
141 }
142
143 // ── TABLE OF CONTENTS ──
144 doc.add_paragraph("").page_break_before(true);
145 doc.insert_toc(doc.content_count(), 3);
146
147 // ── SECTION 1: TEXT FORMATTING ──
148 doc.add_paragraph("").page_break_before(true);
149 doc.add_paragraph("1. Text Formatting").style("Heading1");
150
151 // Paragraph Alignment
152 doc.add_paragraph("1.1 Paragraph Alignment")
153 .style("Heading2");
154 doc.add_paragraph("Left-aligned paragraph (default).")
155 .alignment(Alignment::Left);
156 doc.add_paragraph("Center-aligned paragraph.")
157 .alignment(Alignment::Center);
158 doc.add_paragraph("Right-aligned paragraph.")
159 .alignment(Alignment::Right);
160 doc.add_paragraph(
161 "Justified paragraph — this text is long enough to demonstrate how justified alignment \
162 distributes extra space across word gaps so lines fill the full width of the text area.",
163 )
164 .alignment(Alignment::Justify);
165
166 doc.add_paragraph("");
167
168 // Run Formatting
169 doc.add_paragraph("1.2 Run Formatting").style("Heading2");
170 {
171 let mut p = doc.add_paragraph("");
172 p.add_run("Bold").bold(true);
173 p.add_run(" | ");
174 p.add_run("Italic").italic(true);
175 p.add_run(" | ");
176 p.add_run("Bold Italic").bold(true).italic(true);
177 p.add_run(" | ");
178 p.add_run("Underline").underline(true);
179 p.add_run(" | ");
180 p.add_run("Strikethrough").strike(true);
181 p.add_run(" | ");
182 p.add_run("Double Strike").double_strike(true);
183 }
184 {
185 let mut p = doc.add_paragraph("");
186 p.add_run("Red").color("FF0000");
187 p.add_run(" | ");
188 p.add_run("Blue").color("0000FF");
189 p.add_run(" | ");
190 p.add_run("Green").color("00AA00");
191 p.add_run(" | ");
192 p.add_run("Highlighted").highlight("FFFF00");
193 }
194 {
195 let mut p = doc.add_paragraph("");
196 p.add_run("8pt").size(8.0);
197 p.add_run(" | ");
198 p.add_run("11pt").size(11.0);
199 p.add_run(" | ");
200 p.add_run("16pt").size(16.0);
201 p.add_run(" | ");
202 p.add_run("24pt").size(24.0);
203 }
204 {
205 let mut p = doc.add_paragraph("");
206 p.add_run("Arial").font("Arial");
207 p.add_run(" | ");
208 p.add_run("Times New Roman").font("Times New Roman");
209 p.add_run(" | ");
210 p.add_run("Courier New").font("Courier New");
211 }
212 {
213 let mut p = doc.add_paragraph("");
214 p.add_run("H");
215 p.add_run("2").subscript();
216 p.add_run("O (subscript) | E=mc");
217 p.add_run("2").superscript();
218 p.add_run(" (superscript)");
219 }
220 {
221 let mut p = doc.add_paragraph("");
222 p.add_run("ALL CAPS").all_caps(true);
223 p.add_run(" | ");
224 p.add_run("Small Caps").small_caps(true);
225 p.add_run(" | ");
226 p.add_run("Expanded").character_spacing(Length::twips(40));
227 p.add_run(" | ");
228 p.add_run("Hidden text (not visible)").hidden(true);
229 }
230
231 doc.add_paragraph("");
232
233 // Underline Styles
234 doc.add_paragraph("1.3 Underline Styles").style("Heading2");
235 {
236 let mut p = doc.add_paragraph("");
237 p.add_run("Single ")
238 .underline_style(rdocx::UnderlineStyle::Single);
239 p.add_run("Double ")
240 .underline_style(rdocx::UnderlineStyle::Double);
241 p.add_run("Thick ")
242 .underline_style(rdocx::UnderlineStyle::Thick);
243 p.add_run("Dotted ")
244 .underline_style(rdocx::UnderlineStyle::Dotted);
245 p.add_run("Dash ")
246 .underline_style(rdocx::UnderlineStyle::Dash);
247 p.add_run("Wave ")
248 .underline_style(rdocx::UnderlineStyle::Wave);
249 }
250
251 // ── SECTION 2: PARAGRAPH FORMATTING ──
252 doc.add_paragraph("").page_break_before(true);
253 doc.add_paragraph("2. Paragraph Formatting")
254 .style("Heading1");
255
256 doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
257 doc.add_paragraph("Paragraph with light green shading")
258 .shading("E2EFDA");
259 doc.add_paragraph("Paragraph with bottom border")
260 .border_bottom(BorderStyle::Single, 6, "2E75B6");
261 doc.add_paragraph("Paragraph with all borders (red)")
262 .border_all(BorderStyle::Single, 4, "FF0000");
263
264 doc.add_paragraph("2.2 Indentation").style("Heading2");
265 doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
266 .indent_left(Length::inches(1.0))
267 .hanging_indent(Length::inches(0.5));
268 doc.add_paragraph("0.5-inch first-line indent on this paragraph")
269 .first_line_indent(Length::inches(0.5));
270 doc.add_paragraph("Both left and right indent (0.75 inches each)")
271 .indent_left(Length::inches(0.75))
272 .indent_right(Length::inches(0.75));
273
274 doc.add_paragraph("2.3 Spacing & Line Height")
275 .style("Heading2");
276 doc.add_paragraph("Extra space before (24pt) and after (12pt)")
277 .space_before(Length::pt(24.0))
278 .space_after(Length::pt(12.0));
279 doc.add_paragraph(
280 "Double line spacing paragraph. This text should have extra vertical space between \
281 lines to demonstrate line_spacing_multiple(2.0).",
282 )
283 .line_spacing_multiple(2.0);
284 doc.add_paragraph("Exact 20pt line spacing (fixed height).")
285 .line_spacing(20.0);
286
287 doc.add_paragraph("2.4 Pagination Controls")
288 .style("Heading2");
289 doc.add_paragraph("keep_with_next — this paragraph stays with the next")
290 .keep_with_next(true);
291 doc.add_paragraph("(This paragraph was kept with the one above.)");
292 doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
293 .keep_together(true);
294 doc.add_paragraph("widow_control — prevents widow/orphan lines")
295 .widow_control(true);
296
297 // ── SECTION 3: LISTS ──
298 doc.add_paragraph("").page_break_before(true);
299 doc.add_paragraph("3. Lists").style("Heading1");
300
301 doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
302 doc.add_bullet_list_item("First item", 0);
303 doc.add_bullet_list_item("Second item", 0);
304 doc.add_bullet_list_item("Nested level 1", 1);
305 doc.add_bullet_list_item("Nested level 2", 2);
306 doc.add_bullet_list_item("Back to level 1", 1);
307 doc.add_bullet_list_item("Third item", 0);
308
309 doc.add_paragraph("");
310
311 doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
312 doc.add_numbered_list_item("First numbered item", 0);
313 doc.add_numbered_list_item("Second numbered item", 0);
314 doc.add_numbered_list_item("Sub-item A", 1);
315 doc.add_numbered_list_item("Sub-item B", 1);
316 doc.add_numbered_list_item("Third numbered item", 0);
317
318 // ── SECTION 4: TAB STOPS ──
319 doc.add_paragraph("");
320 doc.add_paragraph("4. Tab Stops").style("Heading1");
321
322 doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
323 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
324 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
325 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
326 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
327 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
328
329 doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
330 doc.add_paragraph("Item\t\tPrice")
331 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
332 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
333 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
334 doc.add_paragraph("Widget\t\t$19.99")
335 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
336 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
337 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
338 doc.add_paragraph("Gadget\t\t$249.50")
339 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
340 .add_tab_stop_with_leader(
341 TabAlignment::Right,
342 Length::inches(4.0),
343 TabLeader::Underscore,
344 )
345 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
346
347 // ── SECTION 5: TABLES ──
348 doc.add_paragraph("").page_break_before(true);
349 doc.add_paragraph("5. Tables").style("Heading1");
350
351 doc.add_paragraph("5.1 Basic Table").style("Heading2");
352 {
353 let mut tbl = doc
354 .add_table(4, 3)
355 .borders(BorderStyle::Single, 4, "000000");
356 for col in 0..3 {
357 tbl.cell(0, col).unwrap().shading("2E75B6");
358 }
359 tbl.cell(0, 0).unwrap().set_text("Name");
360 tbl.cell(0, 1).unwrap().set_text("Role");
361 tbl.cell(0, 2).unwrap().set_text("Location");
362 tbl.cell(1, 0).unwrap().set_text("Walter White");
363 tbl.cell(1, 1).unwrap().set_text("CEO");
364 tbl.cell(1, 2).unwrap().set_text("Albuquerque");
365 tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
366 tbl.cell(2, 1).unwrap().set_text("CTO");
367 tbl.cell(2, 2).unwrap().set_text("Remote");
368 tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
369 tbl.cell(3, 1).unwrap().set_text("Security");
370 tbl.cell(3, 2).unwrap().set_text("Washington");
371 }
372
373 doc.add_paragraph("");
374
375 doc.add_paragraph("5.2 Column Span & Cell Shading")
376 .style("Heading2");
377 {
378 let mut tbl = doc
379 .add_table(3, 4)
380 .borders(BorderStyle::Single, 4, "000000")
381 .width_pct(100.0);
382 tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
383 tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
384 tbl.cell(1, 0).unwrap().set_text("Region");
385 tbl.cell(1, 0).unwrap().shading("D6E4F0");
386 tbl.cell(1, 1).unwrap().set_text("Q1");
387 tbl.cell(1, 1).unwrap().shading("D6E4F0");
388 tbl.cell(1, 2).unwrap().set_text("Q2");
389 tbl.cell(1, 2).unwrap().shading("D6E4F0");
390 tbl.cell(1, 3).unwrap().set_text("Total");
391 tbl.cell(1, 3).unwrap().shading("D6E4F0");
392 tbl.cell(2, 0).unwrap().set_text("Americas");
393 tbl.cell(2, 1).unwrap().set_text("$2.4M");
394 tbl.cell(2, 2).unwrap().set_text("$2.7M");
395 tbl.cell(2, 3).unwrap().set_text("$5.1M");
396 }
397
398 doc.add_paragraph("");
399
400 doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
401 {
402 let mut tbl = doc
403 .add_table(4, 3)
404 .borders(BorderStyle::Single, 4, "000000");
405 tbl.cell(0, 0).unwrap().set_text("Category");
406 tbl.cell(0, 0).unwrap().shading("E2EFDA");
407 tbl.cell(0, 1).unwrap().set_text("Item");
408 tbl.cell(0, 1).unwrap().shading("E2EFDA");
409 tbl.cell(0, 2).unwrap().set_text("Price");
410 tbl.cell(0, 2).unwrap().shading("E2EFDA");
411 tbl.cell(1, 0).unwrap().set_text("Hardware");
412 tbl.cell(1, 0).unwrap().v_merge_restart();
413 tbl.cell(1, 1).unwrap().set_text("Laptop");
414 tbl.cell(1, 2).unwrap().set_text("$1,200");
415 tbl.cell(2, 0).unwrap().v_merge_continue();
416 tbl.cell(2, 1).unwrap().set_text("Monitor");
417 tbl.cell(2, 2).unwrap().set_text("$450");
418 tbl.cell(3, 0).unwrap().set_text("Software");
419 tbl.cell(3, 1).unwrap().set_text("IDE License");
420 tbl.cell(3, 2).unwrap().set_text("$200/yr");
421 }
422
423 doc.add_paragraph("");
424
425 doc.add_paragraph("5.4 Nested Table").style("Heading2");
426 {
427 let mut tbl = doc
428 .add_table(2, 2)
429 .borders(BorderStyle::Single, 6, "2E75B6");
430 tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
431 tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
432 tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
433 {
434 let mut cell = tbl.cell(1, 1).unwrap();
435 cell.set_text("Nested table below:");
436 let mut nested = cell
437 .add_table(2, 2)
438 .borders(BorderStyle::Single, 2, "FF6600");
439 nested.cell(0, 0).unwrap().set_text("A");
440 nested.cell(0, 1).unwrap().set_text("B");
441 nested.cell(1, 0).unwrap().set_text("C");
442 nested.cell(1, 1).unwrap().set_text("D");
443 }
444 }
445
446 doc.add_paragraph("");
447
448 doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
449 .style("Heading2");
450 {
451 let mut tbl = doc
452 .add_table(2, 3)
453 .borders(BorderStyle::Single, 4, "666666");
454 tbl.row(0).unwrap().height(Length::pt(50.0)).header();
455 tbl.cell(0, 0).unwrap().set_text("Top");
456 tbl.cell(0, 0)
457 .unwrap()
458 .vertical_alignment(VerticalAlignment::Top)
459 .shading("FFF2CC");
460 tbl.cell(0, 1).unwrap().set_text("Center");
461 tbl.cell(0, 1)
462 .unwrap()
463 .vertical_alignment(VerticalAlignment::Center)
464 .shading("D9E2F3");
465 tbl.cell(0, 2).unwrap().set_text("Bottom");
466 tbl.cell(0, 2)
467 .unwrap()
468 .vertical_alignment(VerticalAlignment::Bottom)
469 .shading("E2EFDA");
470 tbl.row(1).unwrap().cant_split();
471 tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
472 tbl.cell(1, 0).unwrap().no_wrap();
473 tbl.cell(1, 1).unwrap().set_text("Fixed width");
474 tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
475 tbl.cell(1, 2).unwrap().set_text("Normal");
476 }
477
478 // ── SECTION 6: IMAGES ──
479 doc.add_paragraph("").page_break_before(true);
480 doc.add_paragraph("6. Images").style("Heading1");
481
482 doc.add_paragraph("6.1 Inline Image").style("Heading2");
483 doc.add_paragraph("A blue gradient image below:");
484 let img = create_sample_png(200, 50, [0, 80, 200]);
485 doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
486
487 doc.add_paragraph("");
488 doc.add_paragraph("6.2 Header Image").style("Heading2");
489 let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
490 doc.set_header_image(
491 &hdr_img,
492 "header_logo.png",
493 Length::inches(2.0),
494 Length::inches(0.2),
495 );
496 doc.add_paragraph("The header now contains an inline image (check the top of this page).");
497
498 doc.add_paragraph("");
499 doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
500
501 // ── SECTION 7: CONTENT MANIPULATION ──
502 doc.add_paragraph("").page_break_before(true);
503 doc.add_paragraph("7. Content Manipulation")
504 .style("Heading1");
505
506 doc.add_paragraph("7.1 Placeholder Replacement")
507 .style("Heading2");
508 doc.add_paragraph("Customer: {{customer}}");
509 doc.add_paragraph("Date: {{date}}");
510 doc.add_paragraph("Reference: {{ref_number}}");
511 {
512 let mut tbl = doc
513 .add_table(2, 2)
514 .borders(BorderStyle::Single, 4, "000000");
515 tbl.cell(0, 0).unwrap().set_text("Project");
516 tbl.cell(0, 0).unwrap().shading("D6E4F0");
517 tbl.cell(0, 1).unwrap().set_text("{{project}}");
518 tbl.cell(1, 0).unwrap().set_text("Status");
519 tbl.cell(1, 0).unwrap().shading("D6E4F0");
520 tbl.cell(1, 1).unwrap().set_text("{{status}}");
521 }
522
523 let mut replacements = HashMap::new();
524 replacements.insert("{{customer}}", "Tensorbee Inc.");
525 replacements.insert("{{date}}", "February 22, 2026");
526 replacements.insert("{{ref_number}}", "TB-2026-001");
527 replacements.insert("{{project}}", "Infrastructure Upgrade");
528 replacements.insert("{{status}}", "In Progress");
529 let n = doc.replace_all(&replacements);
530 doc.add_paragraph(&format!("({n} placeholders replaced above)"));
531
532 doc.add_paragraph("");
533
534 doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
535 doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
536 let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
537 doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
538
539 doc.add_paragraph("");
540
541 doc.add_paragraph("7.3 Content Insertion").style("Heading2");
542 doc.add_paragraph("Section A: First content.");
543 doc.add_paragraph("Section C: Third content.");
544 if let Some(idx) = doc.find_content_index("Section C") {
545 doc.insert_paragraph(
546 idx,
547 "Section B: Inserted between A and C via find_content_index().",
548 );
549 }
550
551 // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
552 doc.add_paragraph("").section_break(SectionBreak::NextPage);
553 doc.add_paragraph("8. Section Breaks & Orientation")
554 .style("Heading1");
555 doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
556
557 {
558 let mut tbl = doc
559 .add_table(3, 7)
560 .borders(BorderStyle::Single, 4, "2E75B6");
561 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
562 for (c, h) in headers.iter().enumerate() {
563 tbl.cell(0, c).unwrap().set_text(h);
564 tbl.cell(0, c).unwrap().shading("2E75B6");
565 }
566 let data = [
567 [
568 "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
569 ],
570 [
571 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
572 ],
573 ];
574 for (r, row) in data.iter().enumerate() {
575 for (c, v) in row.iter().enumerate() {
576 tbl.cell(r + 1, c).unwrap().set_text(v);
577 }
578 }
579 }
580
581 doc.add_paragraph("")
582 .section_break(SectionBreak::NextPage)
583 .section_landscape();
584
585 // ── SECTION 9: CUSTOM STYLES ──
586 doc.add_paragraph("9. Custom Styles").style("Heading1");
587 doc.add_style(
588 StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
589 .based_on("Normal")
590 .paragraph_properties({
591 let mut ppr = rdocx_oxml::properties::CT_PPr::default();
592 ppr.shading = Some(rdocx_oxml::properties::CT_Shd {
593 val: "clear".to_string(),
594 color: None,
595 fill: Some("FFF2CC".to_string()),
596 });
597 ppr
598 })
599 .run_properties({
600 let mut rpr = rdocx_oxml::properties::CT_RPr::default();
601 rpr.bold = Some(true);
602 rpr.color = Some("C45911".to_string());
603 rpr
604 }),
605 );
606 doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
607 .style("CustomHighlight");
608
609 doc.add_paragraph("");
610
611 // ── SECTION 10: DOCUMENT INTELLIGENCE ──
612 doc.add_paragraph("10. Document Intelligence API")
613 .style("Heading1");
614
615 let wc = doc.word_count();
616 let hc = doc.headings().len();
617 let ic = doc.images().len();
618 let lc = doc.links().len();
619 doc.add_paragraph(&format!("Word count: {wc}"));
620 doc.add_paragraph(&format!("Heading count: {hc}"));
621 doc.add_paragraph(&format!("Image count: {ic}"));
622 doc.add_paragraph(&format!("Link count: {lc}"));
623
624 let outline = doc.document_outline();
625 doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
626
627 let issues = doc.audit_accessibility();
628 doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
629 for issue in &issues {
630 doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
631 }
632
633 // ── SECTION 11: DOCUMENT MERGING ──
634 doc.add_paragraph("");
635 doc.add_paragraph("11. Document Merging").style("Heading1");
636 {
637 let mut other = Document::new();
638 other.add_paragraph("This paragraph was merged from another document using append().");
639 doc.append(&other);
640 }
641
642 doc.add_paragraph("");
643
644 // ── SUMMARY ──
645 doc.add_paragraph("Summary of Demonstrated Features")
646 .style("Heading1");
647 let features = [
648 "Page setup: size, margins, header/footer distance, gutter",
649 "Document metadata: title, author, subject, keywords",
650 "Headers and footers: text, images, different first page",
651 "Background images (full-page behind text)",
652 "Table of Contents generation with bookmarks",
653 "Text formatting: bold, italic, underline styles, strike, color, size, font",
654 "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
655 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
656 "Pagination controls: keep-with-next, keep-together, widow control",
657 "Bullet and numbered lists with nesting",
658 "Tab stops with dot/underscore/hyphen leaders",
659 "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
660 "Row properties: header rows, exact height, cant-split",
661 "Cell properties: width, no-wrap, vertical alignment",
662 "Inline images and header images",
663 "Placeholder replacement (single and batch)",
664 "Regex-based find and replace",
665 "Content insertion at specific positions",
666 "Section breaks with mixed portrait/landscape",
667 "Custom paragraph and character styles",
668 "Document intelligence: word count, headings, outline, images, links",
669 "Accessibility audit",
670 "Document merging (append)",
671 "Export: DOCX, PDF, HTML, Markdown",
672 ];
673 for f in &features {
674 doc.add_bullet_list_item(f, 0);
675 }
676 doc.add_paragraph("");
677 doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
678 .alignment(Alignment::Center)
679 .shading("E2EFDA")
680 .border_all(BorderStyle::Single, 2, "00AA00");
681
682 doc
683}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?
86fn generate_feature_showcase(_samples_dir: &Path) -> Document {
87 let mut doc = Document::new();
88
89 // ── Page Setup & Metadata ──
90 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
91 doc.set_margins(
92 Length::inches(1.0),
93 Length::inches(1.0),
94 Length::inches(1.0),
95 Length::inches(1.0),
96 );
97 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
98 doc.set_gutter(Length::twips(0));
99 doc.set_title("rdocx Feature Showcase");
100 doc.set_author("rdocx Sample Generator");
101 doc.set_subject("Comprehensive feature demonstration");
102 doc.set_keywords("rdocx, docx, rust, sample, showcase");
103
104 // Headers & Footers with different first page
105 doc.set_different_first_page(true);
106 doc.set_first_page_header("rdocx");
107 doc.set_first_page_footer("Feature Showcase — Cover Page");
108 doc.set_header("rdocx Feature Showcase");
109 doc.set_footer("Generated by rdocx");
110
111 // ── COVER PAGE ──
112 let bg = create_sample_png(612, 792, [20, 45, 90]);
113 doc.add_background_image(&bg, "cover_bg.png");
114
115 for _ in 0..3 {
116 doc.add_paragraph("");
117 }
118 {
119 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
120 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
121 }
122 {
123 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
124 p.add_run("Complete Feature Showcase")
125 .size(28.0)
126 .color("FFFFFF")
127 .italic(true);
128 }
129 doc.add_paragraph("");
130 {
131 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
132 p.add_run("Every feature of the rdocx Rust library")
133 .size(13.0)
134 .color("B0C4DE");
135 }
136 {
137 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
138 p.add_run("demonstrated in a single document.")
139 .size(13.0)
140 .color("B0C4DE");
141 }
142
143 // ── TABLE OF CONTENTS ──
144 doc.add_paragraph("").page_break_before(true);
145 doc.insert_toc(doc.content_count(), 3);
146
147 // ── SECTION 1: TEXT FORMATTING ──
148 doc.add_paragraph("").page_break_before(true);
149 doc.add_paragraph("1. Text Formatting").style("Heading1");
150
151 // Paragraph Alignment
152 doc.add_paragraph("1.1 Paragraph Alignment")
153 .style("Heading2");
154 doc.add_paragraph("Left-aligned paragraph (default).")
155 .alignment(Alignment::Left);
156 doc.add_paragraph("Center-aligned paragraph.")
157 .alignment(Alignment::Center);
158 doc.add_paragraph("Right-aligned paragraph.")
159 .alignment(Alignment::Right);
160 doc.add_paragraph(
161 "Justified paragraph — this text is long enough to demonstrate how justified alignment \
162 distributes extra space across word gaps so lines fill the full width of the text area.",
163 )
164 .alignment(Alignment::Justify);
165
166 doc.add_paragraph("");
167
168 // Run Formatting
169 doc.add_paragraph("1.2 Run Formatting").style("Heading2");
170 {
171 let mut p = doc.add_paragraph("");
172 p.add_run("Bold").bold(true);
173 p.add_run(" | ");
174 p.add_run("Italic").italic(true);
175 p.add_run(" | ");
176 p.add_run("Bold Italic").bold(true).italic(true);
177 p.add_run(" | ");
178 p.add_run("Underline").underline(true);
179 p.add_run(" | ");
180 p.add_run("Strikethrough").strike(true);
181 p.add_run(" | ");
182 p.add_run("Double Strike").double_strike(true);
183 }
184 {
185 let mut p = doc.add_paragraph("");
186 p.add_run("Red").color("FF0000");
187 p.add_run(" | ");
188 p.add_run("Blue").color("0000FF");
189 p.add_run(" | ");
190 p.add_run("Green").color("00AA00");
191 p.add_run(" | ");
192 p.add_run("Highlighted").highlight("FFFF00");
193 }
194 {
195 let mut p = doc.add_paragraph("");
196 p.add_run("8pt").size(8.0);
197 p.add_run(" | ");
198 p.add_run("11pt").size(11.0);
199 p.add_run(" | ");
200 p.add_run("16pt").size(16.0);
201 p.add_run(" | ");
202 p.add_run("24pt").size(24.0);
203 }
204 {
205 let mut p = doc.add_paragraph("");
206 p.add_run("Arial").font("Arial");
207 p.add_run(" | ");
208 p.add_run("Times New Roman").font("Times New Roman");
209 p.add_run(" | ");
210 p.add_run("Courier New").font("Courier New");
211 }
212 {
213 let mut p = doc.add_paragraph("");
214 p.add_run("H");
215 p.add_run("2").subscript();
216 p.add_run("O (subscript) | E=mc");
217 p.add_run("2").superscript();
218 p.add_run(" (superscript)");
219 }
220 {
221 let mut p = doc.add_paragraph("");
222 p.add_run("ALL CAPS").all_caps(true);
223 p.add_run(" | ");
224 p.add_run("Small Caps").small_caps(true);
225 p.add_run(" | ");
226 p.add_run("Expanded").character_spacing(Length::twips(40));
227 p.add_run(" | ");
228 p.add_run("Hidden text (not visible)").hidden(true);
229 }
230
231 doc.add_paragraph("");
232
233 // Underline Styles
234 doc.add_paragraph("1.3 Underline Styles").style("Heading2");
235 {
236 let mut p = doc.add_paragraph("");
237 p.add_run("Single ")
238 .underline_style(rdocx::UnderlineStyle::Single);
239 p.add_run("Double ")
240 .underline_style(rdocx::UnderlineStyle::Double);
241 p.add_run("Thick ")
242 .underline_style(rdocx::UnderlineStyle::Thick);
243 p.add_run("Dotted ")
244 .underline_style(rdocx::UnderlineStyle::Dotted);
245 p.add_run("Dash ")
246 .underline_style(rdocx::UnderlineStyle::Dash);
247 p.add_run("Wave ")
248 .underline_style(rdocx::UnderlineStyle::Wave);
249 }
250
251 // ── SECTION 2: PARAGRAPH FORMATTING ──
252 doc.add_paragraph("").page_break_before(true);
253 doc.add_paragraph("2. Paragraph Formatting")
254 .style("Heading1");
255
256 doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
257 doc.add_paragraph("Paragraph with light green shading")
258 .shading("E2EFDA");
259 doc.add_paragraph("Paragraph with bottom border")
260 .border_bottom(BorderStyle::Single, 6, "2E75B6");
261 doc.add_paragraph("Paragraph with all borders (red)")
262 .border_all(BorderStyle::Single, 4, "FF0000");
263
264 doc.add_paragraph("2.2 Indentation").style("Heading2");
265 doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
266 .indent_left(Length::inches(1.0))
267 .hanging_indent(Length::inches(0.5));
268 doc.add_paragraph("0.5-inch first-line indent on this paragraph")
269 .first_line_indent(Length::inches(0.5));
270 doc.add_paragraph("Both left and right indent (0.75 inches each)")
271 .indent_left(Length::inches(0.75))
272 .indent_right(Length::inches(0.75));
273
274 doc.add_paragraph("2.3 Spacing & Line Height")
275 .style("Heading2");
276 doc.add_paragraph("Extra space before (24pt) and after (12pt)")
277 .space_before(Length::pt(24.0))
278 .space_after(Length::pt(12.0));
279 doc.add_paragraph(
280 "Double line spacing paragraph. This text should have extra vertical space between \
281 lines to demonstrate line_spacing_multiple(2.0).",
282 )
283 .line_spacing_multiple(2.0);
284 doc.add_paragraph("Exact 20pt line spacing (fixed height).")
285 .line_spacing(20.0);
286
287 doc.add_paragraph("2.4 Pagination Controls")
288 .style("Heading2");
289 doc.add_paragraph("keep_with_next — this paragraph stays with the next")
290 .keep_with_next(true);
291 doc.add_paragraph("(This paragraph was kept with the one above.)");
292 doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
293 .keep_together(true);
294 doc.add_paragraph("widow_control — prevents widow/orphan lines")
295 .widow_control(true);
296
297 // ── SECTION 3: LISTS ──
298 doc.add_paragraph("").page_break_before(true);
299 doc.add_paragraph("3. Lists").style("Heading1");
300
301 doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
302 doc.add_bullet_list_item("First item", 0);
303 doc.add_bullet_list_item("Second item", 0);
304 doc.add_bullet_list_item("Nested level 1", 1);
305 doc.add_bullet_list_item("Nested level 2", 2);
306 doc.add_bullet_list_item("Back to level 1", 1);
307 doc.add_bullet_list_item("Third item", 0);
308
309 doc.add_paragraph("");
310
311 doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
312 doc.add_numbered_list_item("First numbered item", 0);
313 doc.add_numbered_list_item("Second numbered item", 0);
314 doc.add_numbered_list_item("Sub-item A", 1);
315 doc.add_numbered_list_item("Sub-item B", 1);
316 doc.add_numbered_list_item("Third numbered item", 0);
317
318 // ── SECTION 4: TAB STOPS ──
319 doc.add_paragraph("");
320 doc.add_paragraph("4. Tab Stops").style("Heading1");
321
322 doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
323 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
324 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
325 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
326 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
327 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
328
329 doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
330 doc.add_paragraph("Item\t\tPrice")
331 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
332 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
333 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
334 doc.add_paragraph("Widget\t\t$19.99")
335 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
336 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
337 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
338 doc.add_paragraph("Gadget\t\t$249.50")
339 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
340 .add_tab_stop_with_leader(
341 TabAlignment::Right,
342 Length::inches(4.0),
343 TabLeader::Underscore,
344 )
345 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
346
347 // ── SECTION 5: TABLES ──
348 doc.add_paragraph("").page_break_before(true);
349 doc.add_paragraph("5. Tables").style("Heading1");
350
351 doc.add_paragraph("5.1 Basic Table").style("Heading2");
352 {
353 let mut tbl = doc
354 .add_table(4, 3)
355 .borders(BorderStyle::Single, 4, "000000");
356 for col in 0..3 {
357 tbl.cell(0, col).unwrap().shading("2E75B6");
358 }
359 tbl.cell(0, 0).unwrap().set_text("Name");
360 tbl.cell(0, 1).unwrap().set_text("Role");
361 tbl.cell(0, 2).unwrap().set_text("Location");
362 tbl.cell(1, 0).unwrap().set_text("Walter White");
363 tbl.cell(1, 1).unwrap().set_text("CEO");
364 tbl.cell(1, 2).unwrap().set_text("Albuquerque");
365 tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
366 tbl.cell(2, 1).unwrap().set_text("CTO");
367 tbl.cell(2, 2).unwrap().set_text("Remote");
368 tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
369 tbl.cell(3, 1).unwrap().set_text("Security");
370 tbl.cell(3, 2).unwrap().set_text("Washington");
371 }
372
373 doc.add_paragraph("");
374
375 doc.add_paragraph("5.2 Column Span & Cell Shading")
376 .style("Heading2");
377 {
378 let mut tbl = doc
379 .add_table(3, 4)
380 .borders(BorderStyle::Single, 4, "000000")
381 .width_pct(100.0);
382 tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
383 tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
384 tbl.cell(1, 0).unwrap().set_text("Region");
385 tbl.cell(1, 0).unwrap().shading("D6E4F0");
386 tbl.cell(1, 1).unwrap().set_text("Q1");
387 tbl.cell(1, 1).unwrap().shading("D6E4F0");
388 tbl.cell(1, 2).unwrap().set_text("Q2");
389 tbl.cell(1, 2).unwrap().shading("D6E4F0");
390 tbl.cell(1, 3).unwrap().set_text("Total");
391 tbl.cell(1, 3).unwrap().shading("D6E4F0");
392 tbl.cell(2, 0).unwrap().set_text("Americas");
393 tbl.cell(2, 1).unwrap().set_text("$2.4M");
394 tbl.cell(2, 2).unwrap().set_text("$2.7M");
395 tbl.cell(2, 3).unwrap().set_text("$5.1M");
396 }
397
398 doc.add_paragraph("");
399
400 doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
401 {
402 let mut tbl = doc
403 .add_table(4, 3)
404 .borders(BorderStyle::Single, 4, "000000");
405 tbl.cell(0, 0).unwrap().set_text("Category");
406 tbl.cell(0, 0).unwrap().shading("E2EFDA");
407 tbl.cell(0, 1).unwrap().set_text("Item");
408 tbl.cell(0, 1).unwrap().shading("E2EFDA");
409 tbl.cell(0, 2).unwrap().set_text("Price");
410 tbl.cell(0, 2).unwrap().shading("E2EFDA");
411 tbl.cell(1, 0).unwrap().set_text("Hardware");
412 tbl.cell(1, 0).unwrap().v_merge_restart();
413 tbl.cell(1, 1).unwrap().set_text("Laptop");
414 tbl.cell(1, 2).unwrap().set_text("$1,200");
415 tbl.cell(2, 0).unwrap().v_merge_continue();
416 tbl.cell(2, 1).unwrap().set_text("Monitor");
417 tbl.cell(2, 2).unwrap().set_text("$450");
418 tbl.cell(3, 0).unwrap().set_text("Software");
419 tbl.cell(3, 1).unwrap().set_text("IDE License");
420 tbl.cell(3, 2).unwrap().set_text("$200/yr");
421 }
422
423 doc.add_paragraph("");
424
425 doc.add_paragraph("5.4 Nested Table").style("Heading2");
426 {
427 let mut tbl = doc
428 .add_table(2, 2)
429 .borders(BorderStyle::Single, 6, "2E75B6");
430 tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
431 tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
432 tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
433 {
434 let mut cell = tbl.cell(1, 1).unwrap();
435 cell.set_text("Nested table below:");
436 let mut nested = cell
437 .add_table(2, 2)
438 .borders(BorderStyle::Single, 2, "FF6600");
439 nested.cell(0, 0).unwrap().set_text("A");
440 nested.cell(0, 1).unwrap().set_text("B");
441 nested.cell(1, 0).unwrap().set_text("C");
442 nested.cell(1, 1).unwrap().set_text("D");
443 }
444 }
445
446 doc.add_paragraph("");
447
448 doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
449 .style("Heading2");
450 {
451 let mut tbl = doc
452 .add_table(2, 3)
453 .borders(BorderStyle::Single, 4, "666666");
454 tbl.row(0).unwrap().height(Length::pt(50.0)).header();
455 tbl.cell(0, 0).unwrap().set_text("Top");
456 tbl.cell(0, 0)
457 .unwrap()
458 .vertical_alignment(VerticalAlignment::Top)
459 .shading("FFF2CC");
460 tbl.cell(0, 1).unwrap().set_text("Center");
461 tbl.cell(0, 1)
462 .unwrap()
463 .vertical_alignment(VerticalAlignment::Center)
464 .shading("D9E2F3");
465 tbl.cell(0, 2).unwrap().set_text("Bottom");
466 tbl.cell(0, 2)
467 .unwrap()
468 .vertical_alignment(VerticalAlignment::Bottom)
469 .shading("E2EFDA");
470 tbl.row(1).unwrap().cant_split();
471 tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
472 tbl.cell(1, 0).unwrap().no_wrap();
473 tbl.cell(1, 1).unwrap().set_text("Fixed width");
474 tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
475 tbl.cell(1, 2).unwrap().set_text("Normal");
476 }
477
478 // ── SECTION 6: IMAGES ──
479 doc.add_paragraph("").page_break_before(true);
480 doc.add_paragraph("6. Images").style("Heading1");
481
482 doc.add_paragraph("6.1 Inline Image").style("Heading2");
483 doc.add_paragraph("A blue gradient image below:");
484 let img = create_sample_png(200, 50, [0, 80, 200]);
485 doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
486
487 doc.add_paragraph("");
488 doc.add_paragraph("6.2 Header Image").style("Heading2");
489 let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
490 doc.set_header_image(
491 &hdr_img,
492 "header_logo.png",
493 Length::inches(2.0),
494 Length::inches(0.2),
495 );
496 doc.add_paragraph("The header now contains an inline image (check the top of this page).");
497
498 doc.add_paragraph("");
499 doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
500
501 // ── SECTION 7: CONTENT MANIPULATION ──
502 doc.add_paragraph("").page_break_before(true);
503 doc.add_paragraph("7. Content Manipulation")
504 .style("Heading1");
505
506 doc.add_paragraph("7.1 Placeholder Replacement")
507 .style("Heading2");
508 doc.add_paragraph("Customer: {{customer}}");
509 doc.add_paragraph("Date: {{date}}");
510 doc.add_paragraph("Reference: {{ref_number}}");
511 {
512 let mut tbl = doc
513 .add_table(2, 2)
514 .borders(BorderStyle::Single, 4, "000000");
515 tbl.cell(0, 0).unwrap().set_text("Project");
516 tbl.cell(0, 0).unwrap().shading("D6E4F0");
517 tbl.cell(0, 1).unwrap().set_text("{{project}}");
518 tbl.cell(1, 0).unwrap().set_text("Status");
519 tbl.cell(1, 0).unwrap().shading("D6E4F0");
520 tbl.cell(1, 1).unwrap().set_text("{{status}}");
521 }
522
523 let mut replacements = HashMap::new();
524 replacements.insert("{{customer}}", "Tensorbee Inc.");
525 replacements.insert("{{date}}", "February 22, 2026");
526 replacements.insert("{{ref_number}}", "TB-2026-001");
527 replacements.insert("{{project}}", "Infrastructure Upgrade");
528 replacements.insert("{{status}}", "In Progress");
529 let n = doc.replace_all(&replacements);
530 doc.add_paragraph(&format!("({n} placeholders replaced above)"));
531
532 doc.add_paragraph("");
533
534 doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
535 doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
536 let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
537 doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
538
539 doc.add_paragraph("");
540
541 doc.add_paragraph("7.3 Content Insertion").style("Heading2");
542 doc.add_paragraph("Section A: First content.");
543 doc.add_paragraph("Section C: Third content.");
544 if let Some(idx) = doc.find_content_index("Section C") {
545 doc.insert_paragraph(
546 idx,
547 "Section B: Inserted between A and C via find_content_index().",
548 );
549 }
550
551 // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
552 doc.add_paragraph("").section_break(SectionBreak::NextPage);
553 doc.add_paragraph("8. Section Breaks & Orientation")
554 .style("Heading1");
555 doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
556
557 {
558 let mut tbl = doc
559 .add_table(3, 7)
560 .borders(BorderStyle::Single, 4, "2E75B6");
561 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
562 for (c, h) in headers.iter().enumerate() {
563 tbl.cell(0, c).unwrap().set_text(h);
564 tbl.cell(0, c).unwrap().shading("2E75B6");
565 }
566 let data = [
567 [
568 "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
569 ],
570 [
571 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
572 ],
573 ];
574 for (r, row) in data.iter().enumerate() {
575 for (c, v) in row.iter().enumerate() {
576 tbl.cell(r + 1, c).unwrap().set_text(v);
577 }
578 }
579 }
580
581 doc.add_paragraph("")
582 .section_break(SectionBreak::NextPage)
583 .section_landscape();
584
585 // ── SECTION 9: CUSTOM STYLES ──
586 doc.add_paragraph("9. Custom Styles").style("Heading1");
587 doc.add_style(
588 StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
589 .based_on("Normal")
590 .paragraph_properties({
591 let mut ppr = rdocx_oxml::properties::CT_PPr::default();
592 ppr.shading = Some(rdocx_oxml::properties::CT_Shd {
593 val: "clear".to_string(),
594 color: None,
595 fill: Some("FFF2CC".to_string()),
596 });
597 ppr
598 })
599 .run_properties({
600 let mut rpr = rdocx_oxml::properties::CT_RPr::default();
601 rpr.bold = Some(true);
602 rpr.color = Some("C45911".to_string());
603 rpr
604 }),
605 );
606 doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
607 .style("CustomHighlight");
608
609 doc.add_paragraph("");
610
611 // ── SECTION 10: DOCUMENT INTELLIGENCE ──
612 doc.add_paragraph("10. Document Intelligence API")
613 .style("Heading1");
614
615 let wc = doc.word_count();
616 let hc = doc.headings().len();
617 let ic = doc.images().len();
618 let lc = doc.links().len();
619 doc.add_paragraph(&format!("Word count: {wc}"));
620 doc.add_paragraph(&format!("Heading count: {hc}"));
621 doc.add_paragraph(&format!("Image count: {ic}"));
622 doc.add_paragraph(&format!("Link count: {lc}"));
623
624 let outline = doc.document_outline();
625 doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
626
627 let issues = doc.audit_accessibility();
628 doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
629 for issue in &issues {
630 doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
631 }
632
633 // ── SECTION 11: DOCUMENT MERGING ──
634 doc.add_paragraph("");
635 doc.add_paragraph("11. Document Merging").style("Heading1");
636 {
637 let mut other = Document::new();
638 other.add_paragraph("This paragraph was merged from another document using append().");
639 doc.append(&other);
640 }
641
642 doc.add_paragraph("");
643
644 // ── SUMMARY ──
645 doc.add_paragraph("Summary of Demonstrated Features")
646 .style("Heading1");
647 let features = [
648 "Page setup: size, margins, header/footer distance, gutter",
649 "Document metadata: title, author, subject, keywords",
650 "Headers and footers: text, images, different first page",
651 "Background images (full-page behind text)",
652 "Table of Contents generation with bookmarks",
653 "Text formatting: bold, italic, underline styles, strike, color, size, font",
654 "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
655 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
656 "Pagination controls: keep-with-next, keep-together, widow control",
657 "Bullet and numbered lists with nesting",
658 "Tab stops with dot/underscore/hyphen leaders",
659 "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
660 "Row properties: header rows, exact height, cant-split",
661 "Cell properties: width, no-wrap, vertical alignment",
662 "Inline images and header images",
663 "Placeholder replacement (single and batch)",
664 "Regex-based find and replace",
665 "Content insertion at specific positions",
666 "Section breaks with mixed portrait/landscape",
667 "Custom paragraph and character styles",
668 "Document intelligence: word count, headings, outline, images, links",
669 "Accessibility audit",
670 "Document merging (append)",
671 "Export: DOCX, PDF, HTML, Markdown",
672 ];
673 for f in &features {
674 doc.add_bullet_list_item(f, 0);
675 }
676 doc.add_paragraph("");
677 doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
678 .alignment(Alignment::Center)
679 .shading("E2EFDA")
680 .border_all(BorderStyle::Single, 2, "00AA00");
681
682 doc
683}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?
86fn generate_feature_showcase(_samples_dir: &Path) -> Document {
87 let mut doc = Document::new();
88
89 // ── Page Setup & Metadata ──
90 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
91 doc.set_margins(
92 Length::inches(1.0),
93 Length::inches(1.0),
94 Length::inches(1.0),
95 Length::inches(1.0),
96 );
97 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
98 doc.set_gutter(Length::twips(0));
99 doc.set_title("rdocx Feature Showcase");
100 doc.set_author("rdocx Sample Generator");
101 doc.set_subject("Comprehensive feature demonstration");
102 doc.set_keywords("rdocx, docx, rust, sample, showcase");
103
104 // Headers & Footers with different first page
105 doc.set_different_first_page(true);
106 doc.set_first_page_header("rdocx");
107 doc.set_first_page_footer("Feature Showcase — Cover Page");
108 doc.set_header("rdocx Feature Showcase");
109 doc.set_footer("Generated by rdocx");
110
111 // ── COVER PAGE ──
112 let bg = create_sample_png(612, 792, [20, 45, 90]);
113 doc.add_background_image(&bg, "cover_bg.png");
114
115 for _ in 0..3 {
116 doc.add_paragraph("");
117 }
118 {
119 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
120 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
121 }
122 {
123 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
124 p.add_run("Complete Feature Showcase")
125 .size(28.0)
126 .color("FFFFFF")
127 .italic(true);
128 }
129 doc.add_paragraph("");
130 {
131 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
132 p.add_run("Every feature of the rdocx Rust library")
133 .size(13.0)
134 .color("B0C4DE");
135 }
136 {
137 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
138 p.add_run("demonstrated in a single document.")
139 .size(13.0)
140 .color("B0C4DE");
141 }
142
143 // ── TABLE OF CONTENTS ──
144 doc.add_paragraph("").page_break_before(true);
145 doc.insert_toc(doc.content_count(), 3);
146
147 // ── SECTION 1: TEXT FORMATTING ──
148 doc.add_paragraph("").page_break_before(true);
149 doc.add_paragraph("1. Text Formatting").style("Heading1");
150
151 // Paragraph Alignment
152 doc.add_paragraph("1.1 Paragraph Alignment")
153 .style("Heading2");
154 doc.add_paragraph("Left-aligned paragraph (default).")
155 .alignment(Alignment::Left);
156 doc.add_paragraph("Center-aligned paragraph.")
157 .alignment(Alignment::Center);
158 doc.add_paragraph("Right-aligned paragraph.")
159 .alignment(Alignment::Right);
160 doc.add_paragraph(
161 "Justified paragraph — this text is long enough to demonstrate how justified alignment \
162 distributes extra space across word gaps so lines fill the full width of the text area.",
163 )
164 .alignment(Alignment::Justify);
165
166 doc.add_paragraph("");
167
168 // Run Formatting
169 doc.add_paragraph("1.2 Run Formatting").style("Heading2");
170 {
171 let mut p = doc.add_paragraph("");
172 p.add_run("Bold").bold(true);
173 p.add_run(" | ");
174 p.add_run("Italic").italic(true);
175 p.add_run(" | ");
176 p.add_run("Bold Italic").bold(true).italic(true);
177 p.add_run(" | ");
178 p.add_run("Underline").underline(true);
179 p.add_run(" | ");
180 p.add_run("Strikethrough").strike(true);
181 p.add_run(" | ");
182 p.add_run("Double Strike").double_strike(true);
183 }
184 {
185 let mut p = doc.add_paragraph("");
186 p.add_run("Red").color("FF0000");
187 p.add_run(" | ");
188 p.add_run("Blue").color("0000FF");
189 p.add_run(" | ");
190 p.add_run("Green").color("00AA00");
191 p.add_run(" | ");
192 p.add_run("Highlighted").highlight("FFFF00");
193 }
194 {
195 let mut p = doc.add_paragraph("");
196 p.add_run("8pt").size(8.0);
197 p.add_run(" | ");
198 p.add_run("11pt").size(11.0);
199 p.add_run(" | ");
200 p.add_run("16pt").size(16.0);
201 p.add_run(" | ");
202 p.add_run("24pt").size(24.0);
203 }
204 {
205 let mut p = doc.add_paragraph("");
206 p.add_run("Arial").font("Arial");
207 p.add_run(" | ");
208 p.add_run("Times New Roman").font("Times New Roman");
209 p.add_run(" | ");
210 p.add_run("Courier New").font("Courier New");
211 }
212 {
213 let mut p = doc.add_paragraph("");
214 p.add_run("H");
215 p.add_run("2").subscript();
216 p.add_run("O (subscript) | E=mc");
217 p.add_run("2").superscript();
218 p.add_run(" (superscript)");
219 }
220 {
221 let mut p = doc.add_paragraph("");
222 p.add_run("ALL CAPS").all_caps(true);
223 p.add_run(" | ");
224 p.add_run("Small Caps").small_caps(true);
225 p.add_run(" | ");
226 p.add_run("Expanded").character_spacing(Length::twips(40));
227 p.add_run(" | ");
228 p.add_run("Hidden text (not visible)").hidden(true);
229 }
230
231 doc.add_paragraph("");
232
233 // Underline Styles
234 doc.add_paragraph("1.3 Underline Styles").style("Heading2");
235 {
236 let mut p = doc.add_paragraph("");
237 p.add_run("Single ")
238 .underline_style(rdocx::UnderlineStyle::Single);
239 p.add_run("Double ")
240 .underline_style(rdocx::UnderlineStyle::Double);
241 p.add_run("Thick ")
242 .underline_style(rdocx::UnderlineStyle::Thick);
243 p.add_run("Dotted ")
244 .underline_style(rdocx::UnderlineStyle::Dotted);
245 p.add_run("Dash ")
246 .underline_style(rdocx::UnderlineStyle::Dash);
247 p.add_run("Wave ")
248 .underline_style(rdocx::UnderlineStyle::Wave);
249 }
250
251 // ── SECTION 2: PARAGRAPH FORMATTING ──
252 doc.add_paragraph("").page_break_before(true);
253 doc.add_paragraph("2. Paragraph Formatting")
254 .style("Heading1");
255
256 doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
257 doc.add_paragraph("Paragraph with light green shading")
258 .shading("E2EFDA");
259 doc.add_paragraph("Paragraph with bottom border")
260 .border_bottom(BorderStyle::Single, 6, "2E75B6");
261 doc.add_paragraph("Paragraph with all borders (red)")
262 .border_all(BorderStyle::Single, 4, "FF0000");
263
264 doc.add_paragraph("2.2 Indentation").style("Heading2");
265 doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
266 .indent_left(Length::inches(1.0))
267 .hanging_indent(Length::inches(0.5));
268 doc.add_paragraph("0.5-inch first-line indent on this paragraph")
269 .first_line_indent(Length::inches(0.5));
270 doc.add_paragraph("Both left and right indent (0.75 inches each)")
271 .indent_left(Length::inches(0.75))
272 .indent_right(Length::inches(0.75));
273
274 doc.add_paragraph("2.3 Spacing & Line Height")
275 .style("Heading2");
276 doc.add_paragraph("Extra space before (24pt) and after (12pt)")
277 .space_before(Length::pt(24.0))
278 .space_after(Length::pt(12.0));
279 doc.add_paragraph(
280 "Double line spacing paragraph. This text should have extra vertical space between \
281 lines to demonstrate line_spacing_multiple(2.0).",
282 )
283 .line_spacing_multiple(2.0);
284 doc.add_paragraph("Exact 20pt line spacing (fixed height).")
285 .line_spacing(20.0);
286
287 doc.add_paragraph("2.4 Pagination Controls")
288 .style("Heading2");
289 doc.add_paragraph("keep_with_next — this paragraph stays with the next")
290 .keep_with_next(true);
291 doc.add_paragraph("(This paragraph was kept with the one above.)");
292 doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
293 .keep_together(true);
294 doc.add_paragraph("widow_control — prevents widow/orphan lines")
295 .widow_control(true);
296
297 // ── SECTION 3: LISTS ──
298 doc.add_paragraph("").page_break_before(true);
299 doc.add_paragraph("3. Lists").style("Heading1");
300
301 doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
302 doc.add_bullet_list_item("First item", 0);
303 doc.add_bullet_list_item("Second item", 0);
304 doc.add_bullet_list_item("Nested level 1", 1);
305 doc.add_bullet_list_item("Nested level 2", 2);
306 doc.add_bullet_list_item("Back to level 1", 1);
307 doc.add_bullet_list_item("Third item", 0);
308
309 doc.add_paragraph("");
310
311 doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
312 doc.add_numbered_list_item("First numbered item", 0);
313 doc.add_numbered_list_item("Second numbered item", 0);
314 doc.add_numbered_list_item("Sub-item A", 1);
315 doc.add_numbered_list_item("Sub-item B", 1);
316 doc.add_numbered_list_item("Third numbered item", 0);
317
318 // ── SECTION 4: TAB STOPS ──
319 doc.add_paragraph("");
320 doc.add_paragraph("4. Tab Stops").style("Heading1");
321
322 doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
323 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
324 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
325 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
326 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
327 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
328
329 doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
330 doc.add_paragraph("Item\t\tPrice")
331 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
332 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
333 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
334 doc.add_paragraph("Widget\t\t$19.99")
335 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
336 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
337 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
338 doc.add_paragraph("Gadget\t\t$249.50")
339 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
340 .add_tab_stop_with_leader(
341 TabAlignment::Right,
342 Length::inches(4.0),
343 TabLeader::Underscore,
344 )
345 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
346
347 // ── SECTION 5: TABLES ──
348 doc.add_paragraph("").page_break_before(true);
349 doc.add_paragraph("5. Tables").style("Heading1");
350
351 doc.add_paragraph("5.1 Basic Table").style("Heading2");
352 {
353 let mut tbl = doc
354 .add_table(4, 3)
355 .borders(BorderStyle::Single, 4, "000000");
356 for col in 0..3 {
357 tbl.cell(0, col).unwrap().shading("2E75B6");
358 }
359 tbl.cell(0, 0).unwrap().set_text("Name");
360 tbl.cell(0, 1).unwrap().set_text("Role");
361 tbl.cell(0, 2).unwrap().set_text("Location");
362 tbl.cell(1, 0).unwrap().set_text("Walter White");
363 tbl.cell(1, 1).unwrap().set_text("CEO");
364 tbl.cell(1, 2).unwrap().set_text("Albuquerque");
365 tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
366 tbl.cell(2, 1).unwrap().set_text("CTO");
367 tbl.cell(2, 2).unwrap().set_text("Remote");
368 tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
369 tbl.cell(3, 1).unwrap().set_text("Security");
370 tbl.cell(3, 2).unwrap().set_text("Washington");
371 }
372
373 doc.add_paragraph("");
374
375 doc.add_paragraph("5.2 Column Span & Cell Shading")
376 .style("Heading2");
377 {
378 let mut tbl = doc
379 .add_table(3, 4)
380 .borders(BorderStyle::Single, 4, "000000")
381 .width_pct(100.0);
382 tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
383 tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
384 tbl.cell(1, 0).unwrap().set_text("Region");
385 tbl.cell(1, 0).unwrap().shading("D6E4F0");
386 tbl.cell(1, 1).unwrap().set_text("Q1");
387 tbl.cell(1, 1).unwrap().shading("D6E4F0");
388 tbl.cell(1, 2).unwrap().set_text("Q2");
389 tbl.cell(1, 2).unwrap().shading("D6E4F0");
390 tbl.cell(1, 3).unwrap().set_text("Total");
391 tbl.cell(1, 3).unwrap().shading("D6E4F0");
392 tbl.cell(2, 0).unwrap().set_text("Americas");
393 tbl.cell(2, 1).unwrap().set_text("$2.4M");
394 tbl.cell(2, 2).unwrap().set_text("$2.7M");
395 tbl.cell(2, 3).unwrap().set_text("$5.1M");
396 }
397
398 doc.add_paragraph("");
399
400 doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
401 {
402 let mut tbl = doc
403 .add_table(4, 3)
404 .borders(BorderStyle::Single, 4, "000000");
405 tbl.cell(0, 0).unwrap().set_text("Category");
406 tbl.cell(0, 0).unwrap().shading("E2EFDA");
407 tbl.cell(0, 1).unwrap().set_text("Item");
408 tbl.cell(0, 1).unwrap().shading("E2EFDA");
409 tbl.cell(0, 2).unwrap().set_text("Price");
410 tbl.cell(0, 2).unwrap().shading("E2EFDA");
411 tbl.cell(1, 0).unwrap().set_text("Hardware");
412 tbl.cell(1, 0).unwrap().v_merge_restart();
413 tbl.cell(1, 1).unwrap().set_text("Laptop");
414 tbl.cell(1, 2).unwrap().set_text("$1,200");
415 tbl.cell(2, 0).unwrap().v_merge_continue();
416 tbl.cell(2, 1).unwrap().set_text("Monitor");
417 tbl.cell(2, 2).unwrap().set_text("$450");
418 tbl.cell(3, 0).unwrap().set_text("Software");
419 tbl.cell(3, 1).unwrap().set_text("IDE License");
420 tbl.cell(3, 2).unwrap().set_text("$200/yr");
421 }
422
423 doc.add_paragraph("");
424
425 doc.add_paragraph("5.4 Nested Table").style("Heading2");
426 {
427 let mut tbl = doc
428 .add_table(2, 2)
429 .borders(BorderStyle::Single, 6, "2E75B6");
430 tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
431 tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
432 tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
433 {
434 let mut cell = tbl.cell(1, 1).unwrap();
435 cell.set_text("Nested table below:");
436 let mut nested = cell
437 .add_table(2, 2)
438 .borders(BorderStyle::Single, 2, "FF6600");
439 nested.cell(0, 0).unwrap().set_text("A");
440 nested.cell(0, 1).unwrap().set_text("B");
441 nested.cell(1, 0).unwrap().set_text("C");
442 nested.cell(1, 1).unwrap().set_text("D");
443 }
444 }
445
446 doc.add_paragraph("");
447
448 doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
449 .style("Heading2");
450 {
451 let mut tbl = doc
452 .add_table(2, 3)
453 .borders(BorderStyle::Single, 4, "666666");
454 tbl.row(0).unwrap().height(Length::pt(50.0)).header();
455 tbl.cell(0, 0).unwrap().set_text("Top");
456 tbl.cell(0, 0)
457 .unwrap()
458 .vertical_alignment(VerticalAlignment::Top)
459 .shading("FFF2CC");
460 tbl.cell(0, 1).unwrap().set_text("Center");
461 tbl.cell(0, 1)
462 .unwrap()
463 .vertical_alignment(VerticalAlignment::Center)
464 .shading("D9E2F3");
465 tbl.cell(0, 2).unwrap().set_text("Bottom");
466 tbl.cell(0, 2)
467 .unwrap()
468 .vertical_alignment(VerticalAlignment::Bottom)
469 .shading("E2EFDA");
470 tbl.row(1).unwrap().cant_split();
471 tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
472 tbl.cell(1, 0).unwrap().no_wrap();
473 tbl.cell(1, 1).unwrap().set_text("Fixed width");
474 tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
475 tbl.cell(1, 2).unwrap().set_text("Normal");
476 }
477
478 // ── SECTION 6: IMAGES ──
479 doc.add_paragraph("").page_break_before(true);
480 doc.add_paragraph("6. Images").style("Heading1");
481
482 doc.add_paragraph("6.1 Inline Image").style("Heading2");
483 doc.add_paragraph("A blue gradient image below:");
484 let img = create_sample_png(200, 50, [0, 80, 200]);
485 doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
486
487 doc.add_paragraph("");
488 doc.add_paragraph("6.2 Header Image").style("Heading2");
489 let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
490 doc.set_header_image(
491 &hdr_img,
492 "header_logo.png",
493 Length::inches(2.0),
494 Length::inches(0.2),
495 );
496 doc.add_paragraph("The header now contains an inline image (check the top of this page).");
497
498 doc.add_paragraph("");
499 doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
500
501 // ── SECTION 7: CONTENT MANIPULATION ──
502 doc.add_paragraph("").page_break_before(true);
503 doc.add_paragraph("7. Content Manipulation")
504 .style("Heading1");
505
506 doc.add_paragraph("7.1 Placeholder Replacement")
507 .style("Heading2");
508 doc.add_paragraph("Customer: {{customer}}");
509 doc.add_paragraph("Date: {{date}}");
510 doc.add_paragraph("Reference: {{ref_number}}");
511 {
512 let mut tbl = doc
513 .add_table(2, 2)
514 .borders(BorderStyle::Single, 4, "000000");
515 tbl.cell(0, 0).unwrap().set_text("Project");
516 tbl.cell(0, 0).unwrap().shading("D6E4F0");
517 tbl.cell(0, 1).unwrap().set_text("{{project}}");
518 tbl.cell(1, 0).unwrap().set_text("Status");
519 tbl.cell(1, 0).unwrap().shading("D6E4F0");
520 tbl.cell(1, 1).unwrap().set_text("{{status}}");
521 }
522
523 let mut replacements = HashMap::new();
524 replacements.insert("{{customer}}", "Tensorbee Inc.");
525 replacements.insert("{{date}}", "February 22, 2026");
526 replacements.insert("{{ref_number}}", "TB-2026-001");
527 replacements.insert("{{project}}", "Infrastructure Upgrade");
528 replacements.insert("{{status}}", "In Progress");
529 let n = doc.replace_all(&replacements);
530 doc.add_paragraph(&format!("({n} placeholders replaced above)"));
531
532 doc.add_paragraph("");
533
534 doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
535 doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
536 let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
537 doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
538
539 doc.add_paragraph("");
540
541 doc.add_paragraph("7.3 Content Insertion").style("Heading2");
542 doc.add_paragraph("Section A: First content.");
543 doc.add_paragraph("Section C: Third content.");
544 if let Some(idx) = doc.find_content_index("Section C") {
545 doc.insert_paragraph(
546 idx,
547 "Section B: Inserted between A and C via find_content_index().",
548 );
549 }
550
551 // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
552 doc.add_paragraph("").section_break(SectionBreak::NextPage);
553 doc.add_paragraph("8. Section Breaks & Orientation")
554 .style("Heading1");
555 doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
556
557 {
558 let mut tbl = doc
559 .add_table(3, 7)
560 .borders(BorderStyle::Single, 4, "2E75B6");
561 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
562 for (c, h) in headers.iter().enumerate() {
563 tbl.cell(0, c).unwrap().set_text(h);
564 tbl.cell(0, c).unwrap().shading("2E75B6");
565 }
566 let data = [
567 [
568 "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
569 ],
570 [
571 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
572 ],
573 ];
574 for (r, row) in data.iter().enumerate() {
575 for (c, v) in row.iter().enumerate() {
576 tbl.cell(r + 1, c).unwrap().set_text(v);
577 }
578 }
579 }
580
581 doc.add_paragraph("")
582 .section_break(SectionBreak::NextPage)
583 .section_landscape();
584
585 // ── SECTION 9: CUSTOM STYLES ──
586 doc.add_paragraph("9. Custom Styles").style("Heading1");
587 doc.add_style(
588 StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
589 .based_on("Normal")
590 .paragraph_properties({
591 let mut ppr = rdocx_oxml::properties::CT_PPr::default();
592 ppr.shading = Some(rdocx_oxml::properties::CT_Shd {
593 val: "clear".to_string(),
594 color: None,
595 fill: Some("FFF2CC".to_string()),
596 });
597 ppr
598 })
599 .run_properties({
600 let mut rpr = rdocx_oxml::properties::CT_RPr::default();
601 rpr.bold = Some(true);
602 rpr.color = Some("C45911".to_string());
603 rpr
604 }),
605 );
606 doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
607 .style("CustomHighlight");
608
609 doc.add_paragraph("");
610
611 // ── SECTION 10: DOCUMENT INTELLIGENCE ──
612 doc.add_paragraph("10. Document Intelligence API")
613 .style("Heading1");
614
615 let wc = doc.word_count();
616 let hc = doc.headings().len();
617 let ic = doc.images().len();
618 let lc = doc.links().len();
619 doc.add_paragraph(&format!("Word count: {wc}"));
620 doc.add_paragraph(&format!("Heading count: {hc}"));
621 doc.add_paragraph(&format!("Image count: {ic}"));
622 doc.add_paragraph(&format!("Link count: {lc}"));
623
624 let outline = doc.document_outline();
625 doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
626
627 let issues = doc.audit_accessibility();
628 doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
629 for issue in &issues {
630 doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
631 }
632
633 // ── SECTION 11: DOCUMENT MERGING ──
634 doc.add_paragraph("");
635 doc.add_paragraph("11. Document Merging").style("Heading1");
636 {
637 let mut other = Document::new();
638 other.add_paragraph("This paragraph was merged from another document using append().");
639 doc.append(&other);
640 }
641
642 doc.add_paragraph("");
643
644 // ── SUMMARY ──
645 doc.add_paragraph("Summary of Demonstrated Features")
646 .style("Heading1");
647 let features = [
648 "Page setup: size, margins, header/footer distance, gutter",
649 "Document metadata: title, author, subject, keywords",
650 "Headers and footers: text, images, different first page",
651 "Background images (full-page behind text)",
652 "Table of Contents generation with bookmarks",
653 "Text formatting: bold, italic, underline styles, strike, color, size, font",
654 "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
655 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
656 "Pagination controls: keep-with-next, keep-together, widow control",
657 "Bullet and numbered lists with nesting",
658 "Tab stops with dot/underscore/hyphen leaders",
659 "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
660 "Row properties: header rows, exact height, cant-split",
661 "Cell properties: width, no-wrap, vertical alignment",
662 "Inline images and header images",
663 "Placeholder replacement (single and batch)",
664 "Regex-based find and replace",
665 "Content insertion at specific positions",
666 "Section breaks with mixed portrait/landscape",
667 "Custom paragraph and character styles",
668 "Document intelligence: word count, headings, outline, images, links",
669 "Accessibility audit",
670 "Document merging (append)",
671 "Export: DOCX, PDF, HTML, Markdown",
672 ];
673 for f in &features {
674 doc.add_bullet_list_item(f, 0);
675 }
676 doc.add_paragraph("");
677 doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
678 .alignment(Alignment::Center)
679 .shading("E2EFDA")
680 .border_all(BorderStyle::Single, 2, "00AA00");
681
682 doc
683}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?
86fn generate_feature_showcase(_samples_dir: &Path) -> Document {
87 let mut doc = Document::new();
88
89 // ── Page Setup & Metadata ──
90 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
91 doc.set_margins(
92 Length::inches(1.0),
93 Length::inches(1.0),
94 Length::inches(1.0),
95 Length::inches(1.0),
96 );
97 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
98 doc.set_gutter(Length::twips(0));
99 doc.set_title("rdocx Feature Showcase");
100 doc.set_author("rdocx Sample Generator");
101 doc.set_subject("Comprehensive feature demonstration");
102 doc.set_keywords("rdocx, docx, rust, sample, showcase");
103
104 // Headers & Footers with different first page
105 doc.set_different_first_page(true);
106 doc.set_first_page_header("rdocx");
107 doc.set_first_page_footer("Feature Showcase — Cover Page");
108 doc.set_header("rdocx Feature Showcase");
109 doc.set_footer("Generated by rdocx");
110
111 // ── COVER PAGE ──
112 let bg = create_sample_png(612, 792, [20, 45, 90]);
113 doc.add_background_image(&bg, "cover_bg.png");
114
115 for _ in 0..3 {
116 doc.add_paragraph("");
117 }
118 {
119 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
120 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
121 }
122 {
123 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
124 p.add_run("Complete Feature Showcase")
125 .size(28.0)
126 .color("FFFFFF")
127 .italic(true);
128 }
129 doc.add_paragraph("");
130 {
131 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
132 p.add_run("Every feature of the rdocx Rust library")
133 .size(13.0)
134 .color("B0C4DE");
135 }
136 {
137 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
138 p.add_run("demonstrated in a single document.")
139 .size(13.0)
140 .color("B0C4DE");
141 }
142
143 // ── TABLE OF CONTENTS ──
144 doc.add_paragraph("").page_break_before(true);
145 doc.insert_toc(doc.content_count(), 3);
146
147 // ── SECTION 1: TEXT FORMATTING ──
148 doc.add_paragraph("").page_break_before(true);
149 doc.add_paragraph("1. Text Formatting").style("Heading1");
150
151 // Paragraph Alignment
152 doc.add_paragraph("1.1 Paragraph Alignment")
153 .style("Heading2");
154 doc.add_paragraph("Left-aligned paragraph (default).")
155 .alignment(Alignment::Left);
156 doc.add_paragraph("Center-aligned paragraph.")
157 .alignment(Alignment::Center);
158 doc.add_paragraph("Right-aligned paragraph.")
159 .alignment(Alignment::Right);
160 doc.add_paragraph(
161 "Justified paragraph — this text is long enough to demonstrate how justified alignment \
162 distributes extra space across word gaps so lines fill the full width of the text area.",
163 )
164 .alignment(Alignment::Justify);
165
166 doc.add_paragraph("");
167
168 // Run Formatting
169 doc.add_paragraph("1.2 Run Formatting").style("Heading2");
170 {
171 let mut p = doc.add_paragraph("");
172 p.add_run("Bold").bold(true);
173 p.add_run(" | ");
174 p.add_run("Italic").italic(true);
175 p.add_run(" | ");
176 p.add_run("Bold Italic").bold(true).italic(true);
177 p.add_run(" | ");
178 p.add_run("Underline").underline(true);
179 p.add_run(" | ");
180 p.add_run("Strikethrough").strike(true);
181 p.add_run(" | ");
182 p.add_run("Double Strike").double_strike(true);
183 }
184 {
185 let mut p = doc.add_paragraph("");
186 p.add_run("Red").color("FF0000");
187 p.add_run(" | ");
188 p.add_run("Blue").color("0000FF");
189 p.add_run(" | ");
190 p.add_run("Green").color("00AA00");
191 p.add_run(" | ");
192 p.add_run("Highlighted").highlight("FFFF00");
193 }
194 {
195 let mut p = doc.add_paragraph("");
196 p.add_run("8pt").size(8.0);
197 p.add_run(" | ");
198 p.add_run("11pt").size(11.0);
199 p.add_run(" | ");
200 p.add_run("16pt").size(16.0);
201 p.add_run(" | ");
202 p.add_run("24pt").size(24.0);
203 }
204 {
205 let mut p = doc.add_paragraph("");
206 p.add_run("Arial").font("Arial");
207 p.add_run(" | ");
208 p.add_run("Times New Roman").font("Times New Roman");
209 p.add_run(" | ");
210 p.add_run("Courier New").font("Courier New");
211 }
212 {
213 let mut p = doc.add_paragraph("");
214 p.add_run("H");
215 p.add_run("2").subscript();
216 p.add_run("O (subscript) | E=mc");
217 p.add_run("2").superscript();
218 p.add_run(" (superscript)");
219 }
220 {
221 let mut p = doc.add_paragraph("");
222 p.add_run("ALL CAPS").all_caps(true);
223 p.add_run(" | ");
224 p.add_run("Small Caps").small_caps(true);
225 p.add_run(" | ");
226 p.add_run("Expanded").character_spacing(Length::twips(40));
227 p.add_run(" | ");
228 p.add_run("Hidden text (not visible)").hidden(true);
229 }
230
231 doc.add_paragraph("");
232
233 // Underline Styles
234 doc.add_paragraph("1.3 Underline Styles").style("Heading2");
235 {
236 let mut p = doc.add_paragraph("");
237 p.add_run("Single ")
238 .underline_style(rdocx::UnderlineStyle::Single);
239 p.add_run("Double ")
240 .underline_style(rdocx::UnderlineStyle::Double);
241 p.add_run("Thick ")
242 .underline_style(rdocx::UnderlineStyle::Thick);
243 p.add_run("Dotted ")
244 .underline_style(rdocx::UnderlineStyle::Dotted);
245 p.add_run("Dash ")
246 .underline_style(rdocx::UnderlineStyle::Dash);
247 p.add_run("Wave ")
248 .underline_style(rdocx::UnderlineStyle::Wave);
249 }
250
251 // ── SECTION 2: PARAGRAPH FORMATTING ──
252 doc.add_paragraph("").page_break_before(true);
253 doc.add_paragraph("2. Paragraph Formatting")
254 .style("Heading1");
255
256 doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
257 doc.add_paragraph("Paragraph with light green shading")
258 .shading("E2EFDA");
259 doc.add_paragraph("Paragraph with bottom border")
260 .border_bottom(BorderStyle::Single, 6, "2E75B6");
261 doc.add_paragraph("Paragraph with all borders (red)")
262 .border_all(BorderStyle::Single, 4, "FF0000");
263
264 doc.add_paragraph("2.2 Indentation").style("Heading2");
265 doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
266 .indent_left(Length::inches(1.0))
267 .hanging_indent(Length::inches(0.5));
268 doc.add_paragraph("0.5-inch first-line indent on this paragraph")
269 .first_line_indent(Length::inches(0.5));
270 doc.add_paragraph("Both left and right indent (0.75 inches each)")
271 .indent_left(Length::inches(0.75))
272 .indent_right(Length::inches(0.75));
273
274 doc.add_paragraph("2.3 Spacing & Line Height")
275 .style("Heading2");
276 doc.add_paragraph("Extra space before (24pt) and after (12pt)")
277 .space_before(Length::pt(24.0))
278 .space_after(Length::pt(12.0));
279 doc.add_paragraph(
280 "Double line spacing paragraph. This text should have extra vertical space between \
281 lines to demonstrate line_spacing_multiple(2.0).",
282 )
283 .line_spacing_multiple(2.0);
284 doc.add_paragraph("Exact 20pt line spacing (fixed height).")
285 .line_spacing(20.0);
286
287 doc.add_paragraph("2.4 Pagination Controls")
288 .style("Heading2");
289 doc.add_paragraph("keep_with_next — this paragraph stays with the next")
290 .keep_with_next(true);
291 doc.add_paragraph("(This paragraph was kept with the one above.)");
292 doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
293 .keep_together(true);
294 doc.add_paragraph("widow_control — prevents widow/orphan lines")
295 .widow_control(true);
296
297 // ── SECTION 3: LISTS ──
298 doc.add_paragraph("").page_break_before(true);
299 doc.add_paragraph("3. Lists").style("Heading1");
300
301 doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
302 doc.add_bullet_list_item("First item", 0);
303 doc.add_bullet_list_item("Second item", 0);
304 doc.add_bullet_list_item("Nested level 1", 1);
305 doc.add_bullet_list_item("Nested level 2", 2);
306 doc.add_bullet_list_item("Back to level 1", 1);
307 doc.add_bullet_list_item("Third item", 0);
308
309 doc.add_paragraph("");
310
311 doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
312 doc.add_numbered_list_item("First numbered item", 0);
313 doc.add_numbered_list_item("Second numbered item", 0);
314 doc.add_numbered_list_item("Sub-item A", 1);
315 doc.add_numbered_list_item("Sub-item B", 1);
316 doc.add_numbered_list_item("Third numbered item", 0);
317
318 // ── SECTION 4: TAB STOPS ──
319 doc.add_paragraph("");
320 doc.add_paragraph("4. Tab Stops").style("Heading1");
321
322 doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
323 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
324 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
325 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
326 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
327 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
328
329 doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
330 doc.add_paragraph("Item\t\tPrice")
331 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
332 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
333 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
334 doc.add_paragraph("Widget\t\t$19.99")
335 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
336 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
337 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
338 doc.add_paragraph("Gadget\t\t$249.50")
339 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
340 .add_tab_stop_with_leader(
341 TabAlignment::Right,
342 Length::inches(4.0),
343 TabLeader::Underscore,
344 )
345 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
346
347 // ── SECTION 5: TABLES ──
348 doc.add_paragraph("").page_break_before(true);
349 doc.add_paragraph("5. Tables").style("Heading1");
350
351 doc.add_paragraph("5.1 Basic Table").style("Heading2");
352 {
353 let mut tbl = doc
354 .add_table(4, 3)
355 .borders(BorderStyle::Single, 4, "000000");
356 for col in 0..3 {
357 tbl.cell(0, col).unwrap().shading("2E75B6");
358 }
359 tbl.cell(0, 0).unwrap().set_text("Name");
360 tbl.cell(0, 1).unwrap().set_text("Role");
361 tbl.cell(0, 2).unwrap().set_text("Location");
362 tbl.cell(1, 0).unwrap().set_text("Walter White");
363 tbl.cell(1, 1).unwrap().set_text("CEO");
364 tbl.cell(1, 2).unwrap().set_text("Albuquerque");
365 tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
366 tbl.cell(2, 1).unwrap().set_text("CTO");
367 tbl.cell(2, 2).unwrap().set_text("Remote");
368 tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
369 tbl.cell(3, 1).unwrap().set_text("Security");
370 tbl.cell(3, 2).unwrap().set_text("Washington");
371 }
372
373 doc.add_paragraph("");
374
375 doc.add_paragraph("5.2 Column Span & Cell Shading")
376 .style("Heading2");
377 {
378 let mut tbl = doc
379 .add_table(3, 4)
380 .borders(BorderStyle::Single, 4, "000000")
381 .width_pct(100.0);
382 tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
383 tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
384 tbl.cell(1, 0).unwrap().set_text("Region");
385 tbl.cell(1, 0).unwrap().shading("D6E4F0");
386 tbl.cell(1, 1).unwrap().set_text("Q1");
387 tbl.cell(1, 1).unwrap().shading("D6E4F0");
388 tbl.cell(1, 2).unwrap().set_text("Q2");
389 tbl.cell(1, 2).unwrap().shading("D6E4F0");
390 tbl.cell(1, 3).unwrap().set_text("Total");
391 tbl.cell(1, 3).unwrap().shading("D6E4F0");
392 tbl.cell(2, 0).unwrap().set_text("Americas");
393 tbl.cell(2, 1).unwrap().set_text("$2.4M");
394 tbl.cell(2, 2).unwrap().set_text("$2.7M");
395 tbl.cell(2, 3).unwrap().set_text("$5.1M");
396 }
397
398 doc.add_paragraph("");
399
400 doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
401 {
402 let mut tbl = doc
403 .add_table(4, 3)
404 .borders(BorderStyle::Single, 4, "000000");
405 tbl.cell(0, 0).unwrap().set_text("Category");
406 tbl.cell(0, 0).unwrap().shading("E2EFDA");
407 tbl.cell(0, 1).unwrap().set_text("Item");
408 tbl.cell(0, 1).unwrap().shading("E2EFDA");
409 tbl.cell(0, 2).unwrap().set_text("Price");
410 tbl.cell(0, 2).unwrap().shading("E2EFDA");
411 tbl.cell(1, 0).unwrap().set_text("Hardware");
412 tbl.cell(1, 0).unwrap().v_merge_restart();
413 tbl.cell(1, 1).unwrap().set_text("Laptop");
414 tbl.cell(1, 2).unwrap().set_text("$1,200");
415 tbl.cell(2, 0).unwrap().v_merge_continue();
416 tbl.cell(2, 1).unwrap().set_text("Monitor");
417 tbl.cell(2, 2).unwrap().set_text("$450");
418 tbl.cell(3, 0).unwrap().set_text("Software");
419 tbl.cell(3, 1).unwrap().set_text("IDE License");
420 tbl.cell(3, 2).unwrap().set_text("$200/yr");
421 }
422
423 doc.add_paragraph("");
424
425 doc.add_paragraph("5.4 Nested Table").style("Heading2");
426 {
427 let mut tbl = doc
428 .add_table(2, 2)
429 .borders(BorderStyle::Single, 6, "2E75B6");
430 tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
431 tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
432 tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
433 {
434 let mut cell = tbl.cell(1, 1).unwrap();
435 cell.set_text("Nested table below:");
436 let mut nested = cell
437 .add_table(2, 2)
438 .borders(BorderStyle::Single, 2, "FF6600");
439 nested.cell(0, 0).unwrap().set_text("A");
440 nested.cell(0, 1).unwrap().set_text("B");
441 nested.cell(1, 0).unwrap().set_text("C");
442 nested.cell(1, 1).unwrap().set_text("D");
443 }
444 }
445
446 doc.add_paragraph("");
447
448 doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
449 .style("Heading2");
450 {
451 let mut tbl = doc
452 .add_table(2, 3)
453 .borders(BorderStyle::Single, 4, "666666");
454 tbl.row(0).unwrap().height(Length::pt(50.0)).header();
455 tbl.cell(0, 0).unwrap().set_text("Top");
456 tbl.cell(0, 0)
457 .unwrap()
458 .vertical_alignment(VerticalAlignment::Top)
459 .shading("FFF2CC");
460 tbl.cell(0, 1).unwrap().set_text("Center");
461 tbl.cell(0, 1)
462 .unwrap()
463 .vertical_alignment(VerticalAlignment::Center)
464 .shading("D9E2F3");
465 tbl.cell(0, 2).unwrap().set_text("Bottom");
466 tbl.cell(0, 2)
467 .unwrap()
468 .vertical_alignment(VerticalAlignment::Bottom)
469 .shading("E2EFDA");
470 tbl.row(1).unwrap().cant_split();
471 tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
472 tbl.cell(1, 0).unwrap().no_wrap();
473 tbl.cell(1, 1).unwrap().set_text("Fixed width");
474 tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
475 tbl.cell(1, 2).unwrap().set_text("Normal");
476 }
477
478 // ── SECTION 6: IMAGES ──
479 doc.add_paragraph("").page_break_before(true);
480 doc.add_paragraph("6. Images").style("Heading1");
481
482 doc.add_paragraph("6.1 Inline Image").style("Heading2");
483 doc.add_paragraph("A blue gradient image below:");
484 let img = create_sample_png(200, 50, [0, 80, 200]);
485 doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
486
487 doc.add_paragraph("");
488 doc.add_paragraph("6.2 Header Image").style("Heading2");
489 let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
490 doc.set_header_image(
491 &hdr_img,
492 "header_logo.png",
493 Length::inches(2.0),
494 Length::inches(0.2),
495 );
496 doc.add_paragraph("The header now contains an inline image (check the top of this page).");
497
498 doc.add_paragraph("");
499 doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
500
501 // ── SECTION 7: CONTENT MANIPULATION ──
502 doc.add_paragraph("").page_break_before(true);
503 doc.add_paragraph("7. Content Manipulation")
504 .style("Heading1");
505
506 doc.add_paragraph("7.1 Placeholder Replacement")
507 .style("Heading2");
508 doc.add_paragraph("Customer: {{customer}}");
509 doc.add_paragraph("Date: {{date}}");
510 doc.add_paragraph("Reference: {{ref_number}}");
511 {
512 let mut tbl = doc
513 .add_table(2, 2)
514 .borders(BorderStyle::Single, 4, "000000");
515 tbl.cell(0, 0).unwrap().set_text("Project");
516 tbl.cell(0, 0).unwrap().shading("D6E4F0");
517 tbl.cell(0, 1).unwrap().set_text("{{project}}");
518 tbl.cell(1, 0).unwrap().set_text("Status");
519 tbl.cell(1, 0).unwrap().shading("D6E4F0");
520 tbl.cell(1, 1).unwrap().set_text("{{status}}");
521 }
522
523 let mut replacements = HashMap::new();
524 replacements.insert("{{customer}}", "Tensorbee Inc.");
525 replacements.insert("{{date}}", "February 22, 2026");
526 replacements.insert("{{ref_number}}", "TB-2026-001");
527 replacements.insert("{{project}}", "Infrastructure Upgrade");
528 replacements.insert("{{status}}", "In Progress");
529 let n = doc.replace_all(&replacements);
530 doc.add_paragraph(&format!("({n} placeholders replaced above)"));
531
532 doc.add_paragraph("");
533
534 doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
535 doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
536 let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
537 doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
538
539 doc.add_paragraph("");
540
541 doc.add_paragraph("7.3 Content Insertion").style("Heading2");
542 doc.add_paragraph("Section A: First content.");
543 doc.add_paragraph("Section C: Third content.");
544 if let Some(idx) = doc.find_content_index("Section C") {
545 doc.insert_paragraph(
546 idx,
547 "Section B: Inserted between A and C via find_content_index().",
548 );
549 }
550
551 // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
552 doc.add_paragraph("").section_break(SectionBreak::NextPage);
553 doc.add_paragraph("8. Section Breaks & Orientation")
554 .style("Heading1");
555 doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
556
557 {
558 let mut tbl = doc
559 .add_table(3, 7)
560 .borders(BorderStyle::Single, 4, "2E75B6");
561 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
562 for (c, h) in headers.iter().enumerate() {
563 tbl.cell(0, c).unwrap().set_text(h);
564 tbl.cell(0, c).unwrap().shading("2E75B6");
565 }
566 let data = [
567 [
568 "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
569 ],
570 [
571 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
572 ],
573 ];
574 for (r, row) in data.iter().enumerate() {
575 for (c, v) in row.iter().enumerate() {
576 tbl.cell(r + 1, c).unwrap().set_text(v);
577 }
578 }
579 }
580
581 doc.add_paragraph("")
582 .section_break(SectionBreak::NextPage)
583 .section_landscape();
584
585 // ── SECTION 9: CUSTOM STYLES ──
586 doc.add_paragraph("9. Custom Styles").style("Heading1");
587 doc.add_style(
588 StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
589 .based_on("Normal")
590 .paragraph_properties({
591 let mut ppr = rdocx_oxml::properties::CT_PPr::default();
592 ppr.shading = Some(rdocx_oxml::properties::CT_Shd {
593 val: "clear".to_string(),
594 color: None,
595 fill: Some("FFF2CC".to_string()),
596 });
597 ppr
598 })
599 .run_properties({
600 let mut rpr = rdocx_oxml::properties::CT_RPr::default();
601 rpr.bold = Some(true);
602 rpr.color = Some("C45911".to_string());
603 rpr
604 }),
605 );
606 doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
607 .style("CustomHighlight");
608
609 doc.add_paragraph("");
610
611 // ── SECTION 10: DOCUMENT INTELLIGENCE ──
612 doc.add_paragraph("10. Document Intelligence API")
613 .style("Heading1");
614
615 let wc = doc.word_count();
616 let hc = doc.headings().len();
617 let ic = doc.images().len();
618 let lc = doc.links().len();
619 doc.add_paragraph(&format!("Word count: {wc}"));
620 doc.add_paragraph(&format!("Heading count: {hc}"));
621 doc.add_paragraph(&format!("Image count: {ic}"));
622 doc.add_paragraph(&format!("Link count: {lc}"));
623
624 let outline = doc.document_outline();
625 doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
626
627 let issues = doc.audit_accessibility();
628 doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
629 for issue in &issues {
630 doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
631 }
632
633 // ── SECTION 11: DOCUMENT MERGING ──
634 doc.add_paragraph("");
635 doc.add_paragraph("11. Document Merging").style("Heading1");
636 {
637 let mut other = Document::new();
638 other.add_paragraph("This paragraph was merged from another document using append().");
639 doc.append(&other);
640 }
641
642 doc.add_paragraph("");
643
644 // ── SUMMARY ──
645 doc.add_paragraph("Summary of Demonstrated Features")
646 .style("Heading1");
647 let features = [
648 "Page setup: size, margins, header/footer distance, gutter",
649 "Document metadata: title, author, subject, keywords",
650 "Headers and footers: text, images, different first page",
651 "Background images (full-page behind text)",
652 "Table of Contents generation with bookmarks",
653 "Text formatting: bold, italic, underline styles, strike, color, size, font",
654 "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
655 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
656 "Pagination controls: keep-with-next, keep-together, widow control",
657 "Bullet and numbered lists with nesting",
658 "Tab stops with dot/underscore/hyphen leaders",
659 "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
660 "Row properties: header rows, exact height, cant-split",
661 "Cell properties: width, no-wrap, vertical alignment",
662 "Inline images and header images",
663 "Placeholder replacement (single and batch)",
664 "Regex-based find and replace",
665 "Content insertion at specific positions",
666 "Section breaks with mixed portrait/landscape",
667 "Custom paragraph and character styles",
668 "Document intelligence: word count, headings, outline, images, links",
669 "Accessibility audit",
670 "Document merging (append)",
671 "Export: DOCX, PDF, HTML, Markdown",
672 ];
673 for f in &features {
674 doc.add_bullet_list_item(f, 0);
675 }
676 doc.add_paragraph("");
677 doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
678 .alignment(Alignment::Center)
679 .shading("E2EFDA")
680 .border_all(BorderStyle::Single, 2, "00AA00");
681
682 doc
683}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?
86fn generate_feature_showcase(_samples_dir: &Path) -> Document {
87 let mut doc = Document::new();
88
89 // ── Page Setup & Metadata ──
90 doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
91 doc.set_margins(
92 Length::inches(1.0),
93 Length::inches(1.0),
94 Length::inches(1.0),
95 Length::inches(1.0),
96 );
97 doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
98 doc.set_gutter(Length::twips(0));
99 doc.set_title("rdocx Feature Showcase");
100 doc.set_author("rdocx Sample Generator");
101 doc.set_subject("Comprehensive feature demonstration");
102 doc.set_keywords("rdocx, docx, rust, sample, showcase");
103
104 // Headers & Footers with different first page
105 doc.set_different_first_page(true);
106 doc.set_first_page_header("rdocx");
107 doc.set_first_page_footer("Feature Showcase — Cover Page");
108 doc.set_header("rdocx Feature Showcase");
109 doc.set_footer("Generated by rdocx");
110
111 // ── COVER PAGE ──
112 let bg = create_sample_png(612, 792, [20, 45, 90]);
113 doc.add_background_image(&bg, "cover_bg.png");
114
115 for _ in 0..3 {
116 doc.add_paragraph("");
117 }
118 {
119 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
120 p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
121 }
122 {
123 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
124 p.add_run("Complete Feature Showcase")
125 .size(28.0)
126 .color("FFFFFF")
127 .italic(true);
128 }
129 doc.add_paragraph("");
130 {
131 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
132 p.add_run("Every feature of the rdocx Rust library")
133 .size(13.0)
134 .color("B0C4DE");
135 }
136 {
137 let mut p = doc.add_paragraph("").alignment(Alignment::Center);
138 p.add_run("demonstrated in a single document.")
139 .size(13.0)
140 .color("B0C4DE");
141 }
142
143 // ── TABLE OF CONTENTS ──
144 doc.add_paragraph("").page_break_before(true);
145 doc.insert_toc(doc.content_count(), 3);
146
147 // ── SECTION 1: TEXT FORMATTING ──
148 doc.add_paragraph("").page_break_before(true);
149 doc.add_paragraph("1. Text Formatting").style("Heading1");
150
151 // Paragraph Alignment
152 doc.add_paragraph("1.1 Paragraph Alignment")
153 .style("Heading2");
154 doc.add_paragraph("Left-aligned paragraph (default).")
155 .alignment(Alignment::Left);
156 doc.add_paragraph("Center-aligned paragraph.")
157 .alignment(Alignment::Center);
158 doc.add_paragraph("Right-aligned paragraph.")
159 .alignment(Alignment::Right);
160 doc.add_paragraph(
161 "Justified paragraph — this text is long enough to demonstrate how justified alignment \
162 distributes extra space across word gaps so lines fill the full width of the text area.",
163 )
164 .alignment(Alignment::Justify);
165
166 doc.add_paragraph("");
167
168 // Run Formatting
169 doc.add_paragraph("1.2 Run Formatting").style("Heading2");
170 {
171 let mut p = doc.add_paragraph("");
172 p.add_run("Bold").bold(true);
173 p.add_run(" | ");
174 p.add_run("Italic").italic(true);
175 p.add_run(" | ");
176 p.add_run("Bold Italic").bold(true).italic(true);
177 p.add_run(" | ");
178 p.add_run("Underline").underline(true);
179 p.add_run(" | ");
180 p.add_run("Strikethrough").strike(true);
181 p.add_run(" | ");
182 p.add_run("Double Strike").double_strike(true);
183 }
184 {
185 let mut p = doc.add_paragraph("");
186 p.add_run("Red").color("FF0000");
187 p.add_run(" | ");
188 p.add_run("Blue").color("0000FF");
189 p.add_run(" | ");
190 p.add_run("Green").color("00AA00");
191 p.add_run(" | ");
192 p.add_run("Highlighted").highlight("FFFF00");
193 }
194 {
195 let mut p = doc.add_paragraph("");
196 p.add_run("8pt").size(8.0);
197 p.add_run(" | ");
198 p.add_run("11pt").size(11.0);
199 p.add_run(" | ");
200 p.add_run("16pt").size(16.0);
201 p.add_run(" | ");
202 p.add_run("24pt").size(24.0);
203 }
204 {
205 let mut p = doc.add_paragraph("");
206 p.add_run("Arial").font("Arial");
207 p.add_run(" | ");
208 p.add_run("Times New Roman").font("Times New Roman");
209 p.add_run(" | ");
210 p.add_run("Courier New").font("Courier New");
211 }
212 {
213 let mut p = doc.add_paragraph("");
214 p.add_run("H");
215 p.add_run("2").subscript();
216 p.add_run("O (subscript) | E=mc");
217 p.add_run("2").superscript();
218 p.add_run(" (superscript)");
219 }
220 {
221 let mut p = doc.add_paragraph("");
222 p.add_run("ALL CAPS").all_caps(true);
223 p.add_run(" | ");
224 p.add_run("Small Caps").small_caps(true);
225 p.add_run(" | ");
226 p.add_run("Expanded").character_spacing(Length::twips(40));
227 p.add_run(" | ");
228 p.add_run("Hidden text (not visible)").hidden(true);
229 }
230
231 doc.add_paragraph("");
232
233 // Underline Styles
234 doc.add_paragraph("1.3 Underline Styles").style("Heading2");
235 {
236 let mut p = doc.add_paragraph("");
237 p.add_run("Single ")
238 .underline_style(rdocx::UnderlineStyle::Single);
239 p.add_run("Double ")
240 .underline_style(rdocx::UnderlineStyle::Double);
241 p.add_run("Thick ")
242 .underline_style(rdocx::UnderlineStyle::Thick);
243 p.add_run("Dotted ")
244 .underline_style(rdocx::UnderlineStyle::Dotted);
245 p.add_run("Dash ")
246 .underline_style(rdocx::UnderlineStyle::Dash);
247 p.add_run("Wave ")
248 .underline_style(rdocx::UnderlineStyle::Wave);
249 }
250
251 // ── SECTION 2: PARAGRAPH FORMATTING ──
252 doc.add_paragraph("").page_break_before(true);
253 doc.add_paragraph("2. Paragraph Formatting")
254 .style("Heading1");
255
256 doc.add_paragraph("2.1 Shading & Borders").style("Heading2");
257 doc.add_paragraph("Paragraph with light green shading")
258 .shading("E2EFDA");
259 doc.add_paragraph("Paragraph with bottom border")
260 .border_bottom(BorderStyle::Single, 6, "2E75B6");
261 doc.add_paragraph("Paragraph with all borders (red)")
262 .border_all(BorderStyle::Single, 4, "FF0000");
263
264 doc.add_paragraph("2.2 Indentation").style("Heading2");
265 doc.add_paragraph("1-inch left indent + 0.5-inch hanging indent")
266 .indent_left(Length::inches(1.0))
267 .hanging_indent(Length::inches(0.5));
268 doc.add_paragraph("0.5-inch first-line indent on this paragraph")
269 .first_line_indent(Length::inches(0.5));
270 doc.add_paragraph("Both left and right indent (0.75 inches each)")
271 .indent_left(Length::inches(0.75))
272 .indent_right(Length::inches(0.75));
273
274 doc.add_paragraph("2.3 Spacing & Line Height")
275 .style("Heading2");
276 doc.add_paragraph("Extra space before (24pt) and after (12pt)")
277 .space_before(Length::pt(24.0))
278 .space_after(Length::pt(12.0));
279 doc.add_paragraph(
280 "Double line spacing paragraph. This text should have extra vertical space between \
281 lines to demonstrate line_spacing_multiple(2.0).",
282 )
283 .line_spacing_multiple(2.0);
284 doc.add_paragraph("Exact 20pt line spacing (fixed height).")
285 .line_spacing(20.0);
286
287 doc.add_paragraph("2.4 Pagination Controls")
288 .style("Heading2");
289 doc.add_paragraph("keep_with_next — this paragraph stays with the next")
290 .keep_with_next(true);
291 doc.add_paragraph("(This paragraph was kept with the one above.)");
292 doc.add_paragraph("keep_together — all lines of this paragraph stay on the same page")
293 .keep_together(true);
294 doc.add_paragraph("widow_control — prevents widow/orphan lines")
295 .widow_control(true);
296
297 // ── SECTION 3: LISTS ──
298 doc.add_paragraph("").page_break_before(true);
299 doc.add_paragraph("3. Lists").style("Heading1");
300
301 doc.add_paragraph("3.1 Bullet Lists").style("Heading2");
302 doc.add_bullet_list_item("First item", 0);
303 doc.add_bullet_list_item("Second item", 0);
304 doc.add_bullet_list_item("Nested level 1", 1);
305 doc.add_bullet_list_item("Nested level 2", 2);
306 doc.add_bullet_list_item("Back to level 1", 1);
307 doc.add_bullet_list_item("Third item", 0);
308
309 doc.add_paragraph("");
310
311 doc.add_paragraph("3.2 Numbered Lists").style("Heading2");
312 doc.add_numbered_list_item("First numbered item", 0);
313 doc.add_numbered_list_item("Second numbered item", 0);
314 doc.add_numbered_list_item("Sub-item A", 1);
315 doc.add_numbered_list_item("Sub-item B", 1);
316 doc.add_numbered_list_item("Third numbered item", 0);
317
318 // ── SECTION 4: TAB STOPS ──
319 doc.add_paragraph("");
320 doc.add_paragraph("4. Tab Stops").style("Heading1");
321
322 doc.add_paragraph("4.1 Alignment Tabs").style("Heading2");
323 doc.add_paragraph("Left\tCenter\tRight\tDecimal")
324 .add_tab_stop(TabAlignment::Left, Length::inches(0.0))
325 .add_tab_stop(TabAlignment::Center, Length::inches(2.5))
326 .add_tab_stop(TabAlignment::Right, Length::inches(5.0))
327 .add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
328
329 doc.add_paragraph("4.2 Tab Leaders").style("Heading2");
330 doc.add_paragraph("Item\t\tPrice")
331 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
332 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
333 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
334 doc.add_paragraph("Widget\t\t$19.99")
335 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
336 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
337 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
338 doc.add_paragraph("Gadget\t\t$249.50")
339 .add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
340 .add_tab_stop_with_leader(
341 TabAlignment::Right,
342 Length::inches(4.0),
343 TabLeader::Underscore,
344 )
345 .add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
346
347 // ── SECTION 5: TABLES ──
348 doc.add_paragraph("").page_break_before(true);
349 doc.add_paragraph("5. Tables").style("Heading1");
350
351 doc.add_paragraph("5.1 Basic Table").style("Heading2");
352 {
353 let mut tbl = doc
354 .add_table(4, 3)
355 .borders(BorderStyle::Single, 4, "000000");
356 for col in 0..3 {
357 tbl.cell(0, col).unwrap().shading("2E75B6");
358 }
359 tbl.cell(0, 0).unwrap().set_text("Name");
360 tbl.cell(0, 1).unwrap().set_text("Role");
361 tbl.cell(0, 2).unwrap().set_text("Location");
362 tbl.cell(1, 0).unwrap().set_text("Walter White");
363 tbl.cell(1, 1).unwrap().set_text("CEO");
364 tbl.cell(1, 2).unwrap().set_text("Albuquerque");
365 tbl.cell(2, 0).unwrap().set_text("Jesse Pinkman");
366 tbl.cell(2, 1).unwrap().set_text("CTO");
367 tbl.cell(2, 2).unwrap().set_text("Remote");
368 tbl.cell(3, 0).unwrap().set_text("Hank Schrader");
369 tbl.cell(3, 1).unwrap().set_text("Security");
370 tbl.cell(3, 2).unwrap().set_text("Washington");
371 }
372
373 doc.add_paragraph("");
374
375 doc.add_paragraph("5.2 Column Span & Cell Shading")
376 .style("Heading2");
377 {
378 let mut tbl = doc
379 .add_table(3, 4)
380 .borders(BorderStyle::Single, 4, "000000")
381 .width_pct(100.0);
382 tbl.cell(0, 0).unwrap().set_text("Quarterly Report");
383 tbl.cell(0, 0).unwrap().shading("1F4E79").grid_span(4);
384 tbl.cell(1, 0).unwrap().set_text("Region");
385 tbl.cell(1, 0).unwrap().shading("D6E4F0");
386 tbl.cell(1, 1).unwrap().set_text("Q1");
387 tbl.cell(1, 1).unwrap().shading("D6E4F0");
388 tbl.cell(1, 2).unwrap().set_text("Q2");
389 tbl.cell(1, 2).unwrap().shading("D6E4F0");
390 tbl.cell(1, 3).unwrap().set_text("Total");
391 tbl.cell(1, 3).unwrap().shading("D6E4F0");
392 tbl.cell(2, 0).unwrap().set_text("Americas");
393 tbl.cell(2, 1).unwrap().set_text("$2.4M");
394 tbl.cell(2, 2).unwrap().set_text("$2.7M");
395 tbl.cell(2, 3).unwrap().set_text("$5.1M");
396 }
397
398 doc.add_paragraph("");
399
400 doc.add_paragraph("5.3 Vertical Merge").style("Heading2");
401 {
402 let mut tbl = doc
403 .add_table(4, 3)
404 .borders(BorderStyle::Single, 4, "000000");
405 tbl.cell(0, 0).unwrap().set_text("Category");
406 tbl.cell(0, 0).unwrap().shading("E2EFDA");
407 tbl.cell(0, 1).unwrap().set_text("Item");
408 tbl.cell(0, 1).unwrap().shading("E2EFDA");
409 tbl.cell(0, 2).unwrap().set_text("Price");
410 tbl.cell(0, 2).unwrap().shading("E2EFDA");
411 tbl.cell(1, 0).unwrap().set_text("Hardware");
412 tbl.cell(1, 0).unwrap().v_merge_restart();
413 tbl.cell(1, 1).unwrap().set_text("Laptop");
414 tbl.cell(1, 2).unwrap().set_text("$1,200");
415 tbl.cell(2, 0).unwrap().v_merge_continue();
416 tbl.cell(2, 1).unwrap().set_text("Monitor");
417 tbl.cell(2, 2).unwrap().set_text("$450");
418 tbl.cell(3, 0).unwrap().set_text("Software");
419 tbl.cell(3, 1).unwrap().set_text("IDE License");
420 tbl.cell(3, 2).unwrap().set_text("$200/yr");
421 }
422
423 doc.add_paragraph("");
424
425 doc.add_paragraph("5.4 Nested Table").style("Heading2");
426 {
427 let mut tbl = doc
428 .add_table(2, 2)
429 .borders(BorderStyle::Single, 6, "2E75B6");
430 tbl.cell(0, 0).unwrap().set_text("Outer (0,0)");
431 tbl.cell(0, 1).unwrap().set_text("Outer (0,1)");
432 tbl.cell(1, 0).unwrap().set_text("Outer (1,0)");
433 {
434 let mut cell = tbl.cell(1, 1).unwrap();
435 cell.set_text("Nested table below:");
436 let mut nested = cell
437 .add_table(2, 2)
438 .borders(BorderStyle::Single, 2, "FF6600");
439 nested.cell(0, 0).unwrap().set_text("A");
440 nested.cell(0, 1).unwrap().set_text("B");
441 nested.cell(1, 0).unwrap().set_text("C");
442 nested.cell(1, 1).unwrap().set_text("D");
443 }
444 }
445
446 doc.add_paragraph("");
447
448 doc.add_paragraph("5.5 Vertical Alignment & Row Properties")
449 .style("Heading2");
450 {
451 let mut tbl = doc
452 .add_table(2, 3)
453 .borders(BorderStyle::Single, 4, "666666");
454 tbl.row(0).unwrap().height(Length::pt(50.0)).header();
455 tbl.cell(0, 0).unwrap().set_text("Top");
456 tbl.cell(0, 0)
457 .unwrap()
458 .vertical_alignment(VerticalAlignment::Top)
459 .shading("FFF2CC");
460 tbl.cell(0, 1).unwrap().set_text("Center");
461 tbl.cell(0, 1)
462 .unwrap()
463 .vertical_alignment(VerticalAlignment::Center)
464 .shading("D9E2F3");
465 tbl.cell(0, 2).unwrap().set_text("Bottom");
466 tbl.cell(0, 2)
467 .unwrap()
468 .vertical_alignment(VerticalAlignment::Bottom)
469 .shading("E2EFDA");
470 tbl.row(1).unwrap().cant_split();
471 tbl.cell(1, 0).unwrap().set_text("No-wrap cell");
472 tbl.cell(1, 0).unwrap().no_wrap();
473 tbl.cell(1, 1).unwrap().set_text("Fixed width");
474 tbl.cell(1, 1).unwrap().width(Length::inches(2.0));
475 tbl.cell(1, 2).unwrap().set_text("Normal");
476 }
477
478 // ── SECTION 6: IMAGES ──
479 doc.add_paragraph("").page_break_before(true);
480 doc.add_paragraph("6. Images").style("Heading1");
481
482 doc.add_paragraph("6.1 Inline Image").style("Heading2");
483 doc.add_paragraph("A blue gradient image below:");
484 let img = create_sample_png(200, 50, [0, 80, 200]);
485 doc.add_picture(&img, "chart.png", Length::inches(3.0), Length::inches(0.75));
486
487 doc.add_paragraph("");
488 doc.add_paragraph("6.2 Header Image").style("Heading2");
489 let hdr_img = create_sample_png(400, 40, [40, 40, 40]);
490 doc.set_header_image(
491 &hdr_img,
492 "header_logo.png",
493 Length::inches(2.0),
494 Length::inches(0.2),
495 );
496 doc.add_paragraph("The header now contains an inline image (check the top of this page).");
497
498 doc.add_paragraph("");
499 doc.add_paragraph("Note: Page 1 uses a full-page background image (add_background_image).");
500
501 // ── SECTION 7: CONTENT MANIPULATION ──
502 doc.add_paragraph("").page_break_before(true);
503 doc.add_paragraph("7. Content Manipulation")
504 .style("Heading1");
505
506 doc.add_paragraph("7.1 Placeholder Replacement")
507 .style("Heading2");
508 doc.add_paragraph("Customer: {{customer}}");
509 doc.add_paragraph("Date: {{date}}");
510 doc.add_paragraph("Reference: {{ref_number}}");
511 {
512 let mut tbl = doc
513 .add_table(2, 2)
514 .borders(BorderStyle::Single, 4, "000000");
515 tbl.cell(0, 0).unwrap().set_text("Project");
516 tbl.cell(0, 0).unwrap().shading("D6E4F0");
517 tbl.cell(0, 1).unwrap().set_text("{{project}}");
518 tbl.cell(1, 0).unwrap().set_text("Status");
519 tbl.cell(1, 0).unwrap().shading("D6E4F0");
520 tbl.cell(1, 1).unwrap().set_text("{{status}}");
521 }
522
523 let mut replacements = HashMap::new();
524 replacements.insert("{{customer}}", "Tensorbee Inc.");
525 replacements.insert("{{date}}", "February 22, 2026");
526 replacements.insert("{{ref_number}}", "TB-2026-001");
527 replacements.insert("{{project}}", "Infrastructure Upgrade");
528 replacements.insert("{{status}}", "In Progress");
529 let n = doc.replace_all(&replacements);
530 doc.add_paragraph(&format!("({n} placeholders replaced above)"));
531
532 doc.add_paragraph("");
533
534 doc.add_paragraph("7.2 Regex Replacement").style("Heading2");
535 doc.add_paragraph("Emails: user1@example.com and admin@tensorbee.com");
536 let _ = doc.replace_regex(r"\b\w+@\w+\.\w+\b", "[REDACTED]");
537 doc.add_paragraph("(Email addresses above were redacted using regex replacement)");
538
539 doc.add_paragraph("");
540
541 doc.add_paragraph("7.3 Content Insertion").style("Heading2");
542 doc.add_paragraph("Section A: First content.");
543 doc.add_paragraph("Section C: Third content.");
544 if let Some(idx) = doc.find_content_index("Section C") {
545 doc.insert_paragraph(
546 idx,
547 "Section B: Inserted between A and C via find_content_index().",
548 );
549 }
550
551 // ── SECTION 8: SECTION BREAKS & ORIENTATION ──
552 doc.add_paragraph("").section_break(SectionBreak::NextPage);
553 doc.add_paragraph("8. Section Breaks & Orientation")
554 .style("Heading1");
555 doc.add_paragraph("This page is LANDSCAPE. Useful for wide tables and charts.");
556
557 {
558 let mut tbl = doc
559 .add_table(3, 7)
560 .borders(BorderStyle::Single, 4, "2E75B6");
561 let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
562 for (c, h) in headers.iter().enumerate() {
563 tbl.cell(0, c).unwrap().set_text(h);
564 tbl.cell(0, c).unwrap().shading("2E75B6");
565 }
566 let data = [
567 [
568 "Americas", "$1.2M", "$1.3M", "$1.4M", "$1.5M", "$1.6M", "$7.0M",
569 ],
570 [
571 "Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
572 ],
573 ];
574 for (r, row) in data.iter().enumerate() {
575 for (c, v) in row.iter().enumerate() {
576 tbl.cell(r + 1, c).unwrap().set_text(v);
577 }
578 }
579 }
580
581 doc.add_paragraph("")
582 .section_break(SectionBreak::NextPage)
583 .section_landscape();
584
585 // ── SECTION 9: CUSTOM STYLES ──
586 doc.add_paragraph("9. Custom Styles").style("Heading1");
587 doc.add_style(
588 StyleBuilder::paragraph("CustomHighlight", "Custom Highlight")
589 .based_on("Normal")
590 .paragraph_properties({
591 let mut ppr = rdocx_oxml::properties::CT_PPr::default();
592 ppr.shading = Some(rdocx_oxml::properties::CT_Shd {
593 val: "clear".to_string(),
594 color: None,
595 fill: Some("FFF2CC".to_string()),
596 });
597 ppr
598 })
599 .run_properties({
600 let mut rpr = rdocx_oxml::properties::CT_RPr::default();
601 rpr.bold = Some(true);
602 rpr.color = Some("C45911".to_string());
603 rpr
604 }),
605 );
606 doc.add_paragraph("This paragraph uses a custom style: bold orange text on yellow background.")
607 .style("CustomHighlight");
608
609 doc.add_paragraph("");
610
611 // ── SECTION 10: DOCUMENT INTELLIGENCE ──
612 doc.add_paragraph("10. Document Intelligence API")
613 .style("Heading1");
614
615 let wc = doc.word_count();
616 let hc = doc.headings().len();
617 let ic = doc.images().len();
618 let lc = doc.links().len();
619 doc.add_paragraph(&format!("Word count: {wc}"));
620 doc.add_paragraph(&format!("Heading count: {hc}"));
621 doc.add_paragraph(&format!("Image count: {ic}"));
622 doc.add_paragraph(&format!("Link count: {lc}"));
623
624 let outline = doc.document_outline();
625 doc.add_paragraph(&format!("Top-level outline nodes: {}", outline.len()));
626
627 let issues = doc.audit_accessibility();
628 doc.add_paragraph(&format!("Accessibility issues: {}", issues.len()));
629 for issue in &issues {
630 doc.add_bullet_list_item(&format!("{:?}: {}", issue.severity, issue.message), 0);
631 }
632
633 // ── SECTION 11: DOCUMENT MERGING ──
634 doc.add_paragraph("");
635 doc.add_paragraph("11. Document Merging").style("Heading1");
636 {
637 let mut other = Document::new();
638 other.add_paragraph("This paragraph was merged from another document using append().");
639 doc.append(&other);
640 }
641
642 doc.add_paragraph("");
643
644 // ── SUMMARY ──
645 doc.add_paragraph("Summary of Demonstrated Features")
646 .style("Heading1");
647 let features = [
648 "Page setup: size, margins, header/footer distance, gutter",
649 "Document metadata: title, author, subject, keywords",
650 "Headers and footers: text, images, different first page",
651 "Background images (full-page behind text)",
652 "Table of Contents generation with bookmarks",
653 "Text formatting: bold, italic, underline styles, strike, color, size, font",
654 "Advanced run: superscript, subscript, caps, small caps, spacing, hidden",
655 "Paragraph formatting: alignment, borders, shading, spacing, indentation",
656 "Pagination controls: keep-with-next, keep-together, widow control",
657 "Bullet and numbered lists with nesting",
658 "Tab stops with dot/underscore/hyphen leaders",
659 "Tables: borders, shading, column spans, row spans, vertical alignment, nested tables",
660 "Row properties: header rows, exact height, cant-split",
661 "Cell properties: width, no-wrap, vertical alignment",
662 "Inline images and header images",
663 "Placeholder replacement (single and batch)",
664 "Regex-based find and replace",
665 "Content insertion at specific positions",
666 "Section breaks with mixed portrait/landscape",
667 "Custom paragraph and character styles",
668 "Document intelligence: word count, headings, outline, images, links",
669 "Accessibility audit",
670 "Document merging (append)",
671 "Export: DOCX, PDF, HTML, Markdown",
672 ];
673 for f in &features {
674 doc.add_bullet_list_item(f, 0);
675 }
676 doc.add_paragraph("");
677 doc.add_paragraph("All features built entirely with the rdocx Rust crate.")
678 .alignment(Alignment::Center)
679 .shading("E2EFDA")
680 .border_all(BorderStyle::Single, 2, "00AA00");
681
682 doc
683}Trait Implementations§
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.