Skip to main content

generate_all_samples/
generate_all_samples.rs

1//! Generates all sample documents, each exported to DOCX, PDF, HTML, and Markdown.
2//!
3//! Documents:
4//! 1. feature_showcase — Updated to cover ALL library features
5//! 2. proposal — Professional project proposal (Tensorbee)
6//! 3. quote — Quote with bill of materials
7//! 4. invoice — Professional invoice
8//! 5. report — Hierarchical report with images
9//! 6. letter — Formal business letter
10//! 7. contract — Employment contract
11//!
12//! Run with: cargo run --example generate_all_samples
13
14use std::collections::HashMap;
15use std::fmt::Write as FmtWrite;
16use std::path::Path;
17
18use rdocx::{
19    Alignment, BorderStyle, Document, Length, SectionBreak, StyleBuilder, TabAlignment, TabLeader,
20    VerticalAlignment,
21};
22
23fn main() {
24    let samples_dir = Path::new(env!("CARGO_MANIFEST_DIR"))
25        .parent()
26        .unwrap()
27        .parent()
28        .unwrap()
29        .join("samples");
30    std::fs::create_dir_all(&samples_dir).unwrap();
31
32    println!(
33        "Generating all sample documents in {}\n",
34        samples_dir.display()
35    );
36
37    let generators: Vec<(&str, fn(&Path) -> Document)> = vec![
38        ("feature_showcase", generate_feature_showcase),
39        ("proposal", generate_proposal),
40        ("quote", generate_quote),
41        ("invoice", generate_invoice),
42        ("report", generate_report),
43        ("letter", generate_letter),
44        ("contract", generate_contract),
45    ];
46
47    for (name, generator) in &generators {
48        println!("--- {} ---", name);
49        let doc = generator(&samples_dir);
50        export_all(&samples_dir, name, doc);
51        println!();
52    }
53
54    println!("All done!");
55}
56
57/// Export a document to DOCX, PDF, HTML, and Markdown.
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}
82
83// =============================================================================
84// 1. FEATURE SHOWCASE — Updated to cover ALL library features
85// =============================================================================
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}
2126
2127// =============================================================================
2128// PNG generation helpers
2129// =============================================================================
2130
2131/// Create a gradient sample PNG (used for backgrounds, charts, etc.)
2132fn create_sample_png(width: u32, height: u32, base_color: [u8; 3]) -> Vec<u8> {
2133    let mut pixels = Vec::with_capacity((width * height * 4) as usize);
2134    for y in 0..height {
2135        for x in 0..width {
2136            let fy = y as f64 / height as f64;
2137            let fx = x as f64 / width as f64;
2138            let r = (base_color[0] as f64 + (1.0 - fy) * 40.0).min(255.0) as u8;
2139            let g = (base_color[1] as f64 + fx * 30.0).min(255.0) as u8;
2140            let b = (base_color[2] as f64 + fy * 60.0).min(255.0) as u8;
2141            pixels.extend_from_slice(&[r, g, b, 255]);
2142        }
2143    }
2144    encode_png(width, height, &pixels)
2145}
2146
2147/// Create a simple bar chart PNG for the report.
2148fn create_chart_png(width: u32, height: u32) -> Vec<u8> {
2149    let mut pixels = vec![0u8; (width * height * 4) as usize];
2150    // White background
2151    for i in 0..pixels.len() / 4 {
2152        pixels[i * 4] = 255;
2153        pixels[i * 4 + 1] = 255;
2154        pixels[i * 4 + 2] = 255;
2155        pixels[i * 4 + 3] = 255;
2156    }
2157    // Draw simple bars
2158    let bars: Vec<(u32, u32, [u8; 3])> = vec![
2159        (30, 150, [45, 80, 22]),  // Q1
2160        (100, 120, [45, 80, 22]), // Q2
2161        (170, 100, [45, 80, 22]), // Q3
2162        (240, 70, [45, 80, 22]),  // Q4 (best)
2163    ];
2164    for (bx, bar_height, color) in &bars {
2165        let bar_width = 50;
2166        let y_start = height - bar_height;
2167        for y in y_start..height {
2168            for x in *bx..(*bx + bar_width).min(width) {
2169                let idx = ((y * width + x) * 4) as usize;
2170                if idx + 3 < pixels.len() {
2171                    pixels[idx] = color[0];
2172                    pixels[idx + 1] = color[1];
2173                    pixels[idx + 2] = color[2];
2174                    pixels[idx + 3] = 255;
2175                }
2176            }
2177        }
2178    }
2179    encode_png(width, height, &pixels)
2180}
2181
2182/// Create a logo placeholder PNG (white rectangle on transparent).
2183fn create_logo_png(width: u32, height: u32) -> Vec<u8> {
2184    let mut pixels = Vec::with_capacity((width * height * 4) as usize);
2185    for y in 0..height {
2186        for x in 0..width {
2187            let in_text_area =
2188                x > width / 8 && x < width * 7 / 8 && y > height / 4 && y < height * 3 / 4;
2189            if in_text_area {
2190                pixels.extend_from_slice(&[255, 255, 255, 255]);
2191            } else {
2192                pixels.extend_from_slice(&[255, 255, 255, 40]);
2193            }
2194        }
2195    }
2196    encode_png(width, height, &pixels)
2197}
2198
2199// =============================================================================
2200// PNG encoding (no external dependencies)
2201// =============================================================================
2202
2203fn encode_png(width: u32, height: u32, pixels: &[u8]) -> Vec<u8> {
2204    let mut png = Vec::new();
2205    {
2206        use std::io::Write as _;
2207        png.write_all(&[137, 80, 78, 71, 13, 10, 26, 10]).unwrap();
2208
2209        let mut ihdr = Vec::new();
2210        ihdr.extend_from_slice(&width.to_be_bytes());
2211        ihdr.extend_from_slice(&height.to_be_bytes());
2212        ihdr.extend_from_slice(&[8, 6, 0, 0, 0]); // 8-bit RGBA
2213        write_chunk(&mut png, b"IHDR", &ihdr);
2214
2215        let mut raw = Vec::new();
2216        for y in 0..height {
2217            raw.push(0); // filter: None
2218            let s = (y * width * 4) as usize;
2219            raw.extend_from_slice(&pixels[s..s + (width * 4) as usize]);
2220        }
2221        write_chunk(&mut png, b"IDAT", &zlib_store(&raw));
2222        write_chunk(&mut png, b"IEND", &[]);
2223    }
2224    png
2225}
2226
2227fn write_chunk(out: &mut Vec<u8>, ct: &[u8; 4], data: &[u8]) {
2228    use std::io::Write as _;
2229    out.write_all(&(data.len() as u32).to_be_bytes()).unwrap();
2230    out.write_all(ct).unwrap();
2231    out.write_all(data).unwrap();
2232    out.write_all(&crc32(ct, data).to_be_bytes()).unwrap();
2233}
2234
2235fn crc32(ct: &[u8], data: &[u8]) -> u32 {
2236    static T: std::sync::LazyLock<[u32; 256]> = std::sync::LazyLock::new(|| {
2237        let mut t = [0u32; 256];
2238        for n in 0..256u32 {
2239            let mut c = n;
2240            for _ in 0..8 {
2241                c = if c & 1 != 0 {
2242                    0xEDB88320 ^ (c >> 1)
2243                } else {
2244                    c >> 1
2245                };
2246            }
2247            t[n as usize] = c;
2248        }
2249        t
2250    });
2251    let mut c = 0xFFFFFFFF_u32;
2252    for &b in ct.iter().chain(data) {
2253        c = T[((c ^ b as u32) & 0xFF) as usize] ^ (c >> 8);
2254    }
2255    c ^ 0xFFFFFFFF
2256}
2257
2258fn zlib_store(data: &[u8]) -> Vec<u8> {
2259    let mut out = vec![0x78, 0x01];
2260    let chunks: Vec<&[u8]> = data.chunks(65535).collect();
2261    for (i, chunk) in chunks.iter().enumerate() {
2262        let last = i == chunks.len() - 1;
2263        out.push(if last { 0x01 } else { 0x00 });
2264        let len = chunk.len() as u16;
2265        out.extend_from_slice(&len.to_le_bytes());
2266        out.extend_from_slice(&(!len).to_le_bytes());
2267        out.extend_from_slice(chunk);
2268    }
2269    let (mut a, mut b) = (1u32, 0u32);
2270    for &byte in data {
2271        a = (a + byte as u32) % 65521;
2272        b = (b + a) % 65521;
2273    }
2274    out.extend_from_slice(&((b << 16) | a).to_be_bytes());
2275    out
2276}
2277
2278// =============================================================================
2279// Header banner builder (DrawingML group shape via raw XML)
2280// =============================================================================
2281
2282struct BannerOpts<'a> {
2283    bg_color: &'a str,
2284    banner_width: i64,
2285    banner_height: i64,
2286    logo_width: i64,
2287    logo_height: i64,
2288    logo_x_offset: i64,
2289    logo_y_offset: i64,
2290}
2291
2292fn build_header_banner_xml(image_rel_id: &str, opts: &BannerOpts) -> Vec<u8> {
2293    let mut xml = String::with_capacity(2048);
2294    write!(
2295        xml,
2296        r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>"#
2297    )
2298    .unwrap();
2299    write!(xml, r#"<w:hdr "#).unwrap();
2300    write!(
2301        xml,
2302        r#"xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" "#
2303    )
2304    .unwrap();
2305    write!(
2306        xml,
2307        r#"xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" "#
2308    )
2309    .unwrap();
2310    write!(
2311        xml,
2312        r#"xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" "#
2313    )
2314    .unwrap();
2315    write!(
2316        xml,
2317        r#"xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" "#
2318    )
2319    .unwrap();
2320    write!(
2321        xml,
2322        r#"xmlns:pic="http://schemas.openxmlformats.org/drawingml/2006/picture" "#
2323    )
2324    .unwrap();
2325    write!(
2326        xml,
2327        r#"xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" "#
2328    )
2329    .unwrap();
2330    write!(
2331        xml,
2332        r#"xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape" "#
2333    )
2334    .unwrap();
2335    write!(
2336        xml,
2337        r#"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006">"#
2338    )
2339    .unwrap();
2340    write!(xml, r#"<w:p><w:pPr><w:pStyle w:val="Header"/></w:pPr>"#).unwrap();
2341    write!(xml, r#"<w:r><w:rPr><w:noProof/></w:rPr>"#).unwrap();
2342    write!(
2343        xml,
2344        r#"<mc:AlternateContent><mc:Choice Requires="wpg"><w:drawing>"#
2345    )
2346    .unwrap();
2347    write!(
2348        xml,
2349        r#"<wp:anchor distT="0" distB="0" distL="0" distR="0" "#
2350    )
2351    .unwrap();
2352    write!(
2353        xml,
2354        r#"simplePos="0" relativeHeight="251658240" behindDoc="0" "#
2355    )
2356    .unwrap();
2357    write!(
2358        xml,
2359        r#"locked="0" layoutInCell="1" hidden="0" allowOverlap="1">"#
2360    )
2361    .unwrap();
2362    write!(xml, r#"<wp:simplePos x="0" y="0"/>"#).unwrap();
2363    write!(
2364        xml,
2365        r#"<wp:positionH relativeFrom="page"><wp:posOffset>0</wp:posOffset></wp:positionH>"#
2366    )
2367    .unwrap();
2368    write!(
2369        xml,
2370        r#"<wp:positionV relativeFrom="page"><wp:posOffset>0</wp:posOffset></wp:positionV>"#
2371    )
2372    .unwrap();
2373    write!(
2374        xml,
2375        r#"<wp:extent cx="{}" cy="{}"/>"#,
2376        opts.banner_width, opts.banner_height
2377    )
2378    .unwrap();
2379    write!(
2380        xml,
2381        r#"<wp:effectExtent l="0" t="0" r="0" b="0"/><wp:wrapNone/>"#
2382    )
2383    .unwrap();
2384    write!(
2385        xml,
2386        r#"<wp:docPr id="1" name="Header Banner"/><wp:cNvGraphicFramePr/>"#
2387    )
2388    .unwrap();
2389    write!(xml, r#"<a:graphic><a:graphicData uri="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup">"#).unwrap();
2390    write!(xml, r#"<wpg:wgp><wpg:cNvGrpSpPr/><wpg:grpSpPr><a:xfrm>"#).unwrap();
2391    write!(
2392        xml,
2393        r#"<a:off x="0" y="0"/><a:ext cx="{w}" cy="{h}"/>"#,
2394        w = opts.banner_width,
2395        h = opts.banner_height
2396    )
2397    .unwrap();
2398    write!(
2399        xml,
2400        r#"<a:chOff x="0" y="0"/><a:chExt cx="{w}" cy="{h}"/>"#,
2401        w = opts.banner_width,
2402        h = opts.banner_height
2403    )
2404    .unwrap();
2405    write!(xml, r#"</a:xfrm></wpg:grpSpPr>"#).unwrap();
2406    // Background rectangle
2407    write!(
2408        xml,
2409        r#"<wps:wsp><wps:cNvPr id="2" name="BG"/><wps:cNvSpPr/><wps:spPr>"#
2410    )
2411    .unwrap();
2412    write!(
2413        xml,
2414        r#"<a:xfrm><a:off x="0" y="0"/><a:ext cx="{w}" cy="{h}"/></a:xfrm>"#,
2415        w = opts.banner_width,
2416        h = opts.banner_height
2417    )
2418    .unwrap();
2419    write!(xml, r#"<a:prstGeom prst="rect"><a:avLst/></a:prstGeom>"#).unwrap();
2420    write!(
2421        xml,
2422        r#"<a:solidFill><a:srgbClr val="{}"/></a:solidFill>"#,
2423        opts.bg_color
2424    )
2425    .unwrap();
2426    write!(
2427        xml,
2428        r#"<a:ln><a:noFill/></a:ln></wps:spPr><wps:bodyPr/></wps:wsp>"#
2429    )
2430    .unwrap();
2431    // Logo image
2432    write!(
2433        xml,
2434        r#"<pic:pic><pic:nvPicPr><pic:cNvPr id="3" name="Logo"/><pic:cNvPicPr/></pic:nvPicPr>"#
2435    )
2436    .unwrap();
2437    write!(xml, r#"<pic:blipFill><a:blip r:embed="{}"/><a:stretch><a:fillRect/></a:stretch></pic:blipFill>"#, image_rel_id).unwrap();
2438    write!(
2439        xml,
2440        r#"<pic:spPr><a:xfrm><a:off x="{}" y="{}"/><a:ext cx="{}" cy="{}"/></a:xfrm>"#,
2441        opts.logo_x_offset, opts.logo_y_offset, opts.logo_width, opts.logo_height
2442    )
2443    .unwrap();
2444    write!(xml, r#"<a:prstGeom prst="rect"><a:avLst/></a:prstGeom><a:noFill/><a:ln><a:noFill/></a:ln></pic:spPr></pic:pic>"#).unwrap();
2445    write!(xml, r#"</wpg:wgp></a:graphicData></a:graphic>"#).unwrap();
2446    write!(
2447        xml,
2448        r#"</wp:anchor></w:drawing></mc:Choice></mc:AlternateContent>"#
2449    )
2450    .unwrap();
2451    write!(xml, r#"</w:r></w:p></w:hdr>"#).unwrap();
2452    xml.into_bytes()
2453}