use std::collections::HashMap;
use std::path::Path;
use rdocx::{
Alignment, BorderStyle, Document, Length, SectionBreak, TabAlignment, TabLeader,
VerticalAlignment,
};
fn main() {
let samples_dir = Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.unwrap()
.parent()
.unwrap()
.join("samples");
std::fs::create_dir_all(&samples_dir).unwrap();
println!(
"Generating comprehensive sample document in {}",
samples_dir.display()
);
let out = samples_dir.join("feature_showcase.docx");
generate_feature_showcase(&out);
println!(" feature_showcase.docx — all rdocx features in one document");
println!("\nDone!");
}
fn generate_feature_showcase(path: &Path) {
let mut doc = Document::new();
doc.set_page_size(Length::inches(8.5), Length::inches(11.0));
doc.set_margins(
Length::inches(1.0), Length::inches(1.0), Length::inches(1.0), Length::inches(1.0), );
doc.set_header_footer_distance(Length::twips(720), Length::twips(432));
doc.set_gutter(Length::twips(0));
doc.set_title("rdocx Feature Showcase");
doc.set_author("rdocx Sample Generator");
doc.set_subject("Comprehensive feature demonstration");
doc.set_keywords("rdocx, docx, rust, sample");
doc.set_header("rdocx Feature Showcase");
doc.set_footer("Generated by rdocx — Page");
doc.set_different_first_page(true);
doc.set_first_page_header("rdocx");
doc.set_first_page_footer("Feature Showcase — Cover Page");
let bg_cover = create_sample_png(612, 792, [30, 60, 120]);
doc.add_background_image(&bg_cover, "cover_bg.png");
doc.add_paragraph(""); doc.add_paragraph(""); doc.add_paragraph("");
{
let mut p = doc.add_paragraph("").alignment(Alignment::Center);
p.add_run("rdocx").bold(true).size(72.0).color("FFFFFF");
}
{
let mut p = doc.add_paragraph("").alignment(Alignment::Center);
p.add_run("Feature Showcase")
.size(28.0)
.color("FFFFFF")
.italic(true);
}
doc.add_paragraph("");
{
let mut p = doc.add_paragraph("").alignment(Alignment::Center);
p.add_run("A comprehensive demonstration of every feature")
.size(14.0)
.color("CCDDFF");
}
{
let mut p = doc.add_paragraph("").alignment(Alignment::Center);
p.add_run("provided by the rdocx Rust crate for DOCX generation.")
.size(14.0)
.color("CCDDFF");
}
doc.add_paragraph("").page_break_before(true);
doc.add_paragraph("1. Text Formatting").style("Heading1");
doc.add_paragraph("This section demonstrates paragraph and run-level formatting options.");
doc.add_paragraph("");
doc.add_paragraph("Paragraph Alignment").style("Heading2");
doc.add_paragraph("This paragraph is left-aligned (the default).")
.alignment(Alignment::Left);
doc.add_paragraph("This paragraph is center-aligned.")
.alignment(Alignment::Center);
doc.add_paragraph("This paragraph is right-aligned.")
.alignment(Alignment::Right);
doc.add_paragraph(
"This paragraph is justified. To demonstrate justified text properly, it needs \
to be long enough to span multiple lines so the word spacing adjustment is visible \
across the full width of the text area on the page.",
)
.alignment(Alignment::Justify);
doc.add_paragraph("");
doc.add_paragraph("Run Formatting").style("Heading2");
{
let mut p = doc.add_paragraph("");
p.add_run("Bold text").bold(true);
p.add_run(" | ");
p.add_run("Italic text").italic(true);
p.add_run(" | ");
p.add_run("Bold + Italic").bold(true).italic(true);
}
{
let mut p = doc.add_paragraph("");
p.add_run("Single underline").underline(true);
p.add_run(" | ");
p.add_run("Strikethrough").strike(true);
p.add_run(" | ");
p.add_run("Double strikethrough").double_strike(true);
}
{
let mut p = doc.add_paragraph("");
p.add_run("Red text").color("FF0000");
p.add_run(" | ");
p.add_run("Blue text").color("0000FF");
p.add_run(" | ");
p.add_run("Green text").color("00AA00");
p.add_run(" | ");
p.add_run("Highlighted").highlight("FFFF00");
}
{
let mut p = doc.add_paragraph("");
p.add_run("8pt small").size(8.0);
p.add_run(" | ");
p.add_run("11pt normal").size(11.0);
p.add_run(" | ");
p.add_run("16pt large").size(16.0);
p.add_run(" | ");
p.add_run("24pt extra-large").size(24.0);
}
{
let mut p = doc.add_paragraph("");
p.add_run("Arial font").font("Arial");
p.add_run(" | ");
p.add_run("Times New Roman font").font("Times New Roman");
p.add_run(" | ");
p.add_run("Courier New font").font("Courier New");
}
{
let mut p = doc.add_paragraph("");
p.add_run("Normal");
p.add_run(" H").size(11.0);
p.add_run("2").subscript();
p.add_run("O (subscript)").size(11.0);
p.add_run(" | E = mc").size(11.0);
p.add_run("2").superscript();
p.add_run(" (superscript)").size(11.0);
}
{
let mut p = doc.add_paragraph("");
p.add_run("ALL CAPS").all_caps(true);
p.add_run(" | ");
p.add_run("Small Caps").small_caps(true);
p.add_run(" | ");
p.add_run("Expanded spacing")
.character_spacing(Length::twips(40));
}
doc.add_paragraph("");
doc.add_paragraph("Paragraph Formatting").style("Heading2");
doc.add_paragraph("Paragraph with shading (light green background)")
.shading("E2EFDA");
doc.add_paragraph("Paragraph with bottom border")
.border_bottom(BorderStyle::Single, 6, "2E75B6");
doc.add_paragraph("Paragraph with all borders")
.border_all(BorderStyle::Single, 4, "FF0000");
doc.add_paragraph("Paragraph with 1-inch left indent and hanging indent")
.indent_left(Length::inches(1.0))
.hanging_indent(Length::inches(0.5));
doc.add_paragraph("Paragraph with first-line indent of 0.5 inches")
.first_line_indent(Length::inches(0.5));
doc.add_paragraph("Paragraph with extra space before (24pt) and after (12pt)")
.space_before(Length::pt(24.0))
.space_after(Length::pt(12.0));
doc.add_paragraph(
"Paragraph with double line spacing. This text should have extra vertical \
space between lines to demonstrate the line_spacing_multiple setting.",
)
.line_spacing_multiple(2.0);
doc.add_paragraph("Paragraph with keep-with-next (won't break from the next paragraph)")
.keep_with_next(true);
doc.add_paragraph("(This stays with the paragraph above.)");
doc.add_paragraph("").page_break_before(true);
doc.add_paragraph("2. Lists").style("Heading1");
doc.add_paragraph("Bullet List").style("Heading2");
doc.add_bullet_list_item("First bullet item", 0);
doc.add_bullet_list_item("Second bullet item", 0);
doc.add_bullet_list_item("Nested level 1", 1);
doc.add_bullet_list_item("Nested level 2", 2);
doc.add_bullet_list_item("Back to level 1", 1);
doc.add_bullet_list_item("Third bullet item", 0);
doc.add_paragraph("");
doc.add_paragraph("Numbered List").style("Heading2");
doc.add_numbered_list_item("First numbered item", 0);
doc.add_numbered_list_item("Second numbered item", 0);
doc.add_numbered_list_item("Sub-item A", 1);
doc.add_numbered_list_item("Sub-item B", 1);
doc.add_numbered_list_item("Third numbered item", 0);
doc.add_paragraph("");
doc.add_paragraph("Tab Stops").style("Heading2");
doc.add_paragraph("Left\tCenter\tRight\tDecimal")
.add_tab_stop(TabAlignment::Left, Length::inches(0.0))
.add_tab_stop(TabAlignment::Center, Length::inches(2.5))
.add_tab_stop(TabAlignment::Right, Length::inches(5.0))
.add_tab_stop(TabAlignment::Decimal, Length::inches(6.5));
doc.add_paragraph("Item\t........\tPrice")
.add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
.add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
.add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
doc.add_paragraph("Widget A\t........\t$19.99")
.add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
.add_tab_stop_with_leader(TabAlignment::Right, Length::inches(4.0), TabLeader::Dot)
.add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
doc.add_paragraph("Gadget B\t________\t$249.50")
.add_tab_stop_with_leader(TabAlignment::Left, Length::inches(0.0), TabLeader::None)
.add_tab_stop_with_leader(
TabAlignment::Right,
Length::inches(4.0),
TabLeader::Underscore,
)
.add_tab_stop_with_leader(TabAlignment::Right, Length::inches(5.0), TabLeader::None);
doc.add_paragraph("").page_break_before(true);
doc.add_paragraph("3. Tables").style("Heading1");
doc.add_paragraph("Basic Table with Borders")
.style("Heading2");
{
let mut tbl = doc.add_table(4, 3);
tbl = tbl.borders(BorderStyle::Single, 4, "000000");
for col in 0..3 {
tbl.cell(0, col).unwrap().shading("2E75B6");
}
tbl.cell(0, 0).unwrap().set_text("Name");
tbl.cell(0, 1).unwrap().set_text("Role");
tbl.cell(0, 2).unwrap().set_text("Location");
tbl.cell(1, 0).unwrap().set_text("Alice Johnson");
tbl.cell(1, 1).unwrap().set_text("Engineering Lead");
tbl.cell(1, 2).unwrap().set_text("New York");
tbl.cell(2, 0).unwrap().set_text("Bob Smith");
tbl.cell(2, 1).unwrap().set_text("Product Manager");
tbl.cell(2, 2).unwrap().set_text("San Francisco");
tbl.cell(3, 0).unwrap().set_text("Carol Davis");
tbl.cell(3, 1).unwrap().set_text("Designer");
tbl.cell(3, 2).unwrap().set_text("London");
}
doc.add_paragraph("");
doc.add_paragraph("Table with Cell Merging & Vertical Alignment")
.style("Heading2");
{
let mut tbl = doc.add_table(4, 4);
tbl = tbl.borders(BorderStyle::Single, 4, "000000");
tbl = tbl.width_pct(100.0);
tbl.cell(0, 0).unwrap().set_text("Quarterly Revenue Report");
tbl.cell(0, 0).unwrap().shading("1F4E79");
tbl.cell(0, 0).unwrap().grid_span(4);
tbl.cell(1, 0).unwrap().set_text("Region");
tbl.cell(1, 0).unwrap().shading("D6E4F0");
tbl.cell(1, 1).unwrap().set_text("Q1");
tbl.cell(1, 1).unwrap().shading("D6E4F0");
tbl.cell(1, 2).unwrap().set_text("Q2");
tbl.cell(1, 2).unwrap().shading("D6E4F0");
tbl.cell(1, 3).unwrap().set_text("Total");
tbl.cell(1, 3).unwrap().shading("D6E4F0");
tbl.cell(2, 0).unwrap().set_text("North America");
tbl.cell(2, 1).unwrap().set_text("$2.4M");
tbl.cell(2, 2).unwrap().set_text("$2.7M");
tbl.cell(2, 3).unwrap().set_text("$5.1M");
tbl.cell(3, 0).unwrap().set_text("Europe");
tbl.cell(3, 1).unwrap().set_text("$1.8M");
tbl.cell(3, 2).unwrap().set_text("$2.0M");
tbl.cell(3, 3).unwrap().set_text("$3.8M");
tbl.cell(2, 3)
.unwrap()
.vertical_alignment(VerticalAlignment::Center);
tbl.cell(3, 3)
.unwrap()
.vertical_alignment(VerticalAlignment::Bottom);
}
doc.add_paragraph("");
doc.add_paragraph("Table with Vertical Merge")
.style("Heading2");
{
let mut tbl = doc.add_table(4, 3);
tbl = tbl.borders(BorderStyle::Single, 4, "000000");
tbl.cell(0, 0).unwrap().set_text("Category");
tbl.cell(0, 0).unwrap().shading("E2EFDA");
tbl.cell(0, 1).unwrap().set_text("Item");
tbl.cell(0, 1).unwrap().shading("E2EFDA");
tbl.cell(0, 2).unwrap().set_text("Price");
tbl.cell(0, 2).unwrap().shading("E2EFDA");
tbl.cell(1, 0).unwrap().set_text("Hardware");
tbl.cell(1, 0).unwrap().v_merge_restart();
tbl.cell(1, 1).unwrap().set_text("Laptop");
tbl.cell(1, 2).unwrap().set_text("$1,200");
tbl.cell(2, 0).unwrap().v_merge_continue();
tbl.cell(2, 1).unwrap().set_text("Monitor");
tbl.cell(2, 2).unwrap().set_text("$450");
tbl.cell(3, 0).unwrap().set_text("Software");
tbl.cell(3, 1).unwrap().set_text("IDE License");
tbl.cell(3, 2).unwrap().set_text("$200/yr");
}
doc.add_paragraph("");
doc.add_paragraph("Nested Table").style("Heading2");
{
let mut tbl = doc.add_table(2, 2);
tbl = tbl.borders(BorderStyle::Single, 6, "2E75B6");
tbl.cell(0, 0).unwrap().set_text("Outer Cell (0,0)");
tbl.cell(0, 1).unwrap().set_text("Outer Cell (0,1)");
tbl.cell(1, 0).unwrap().set_text("Outer Cell (1,0)");
{
let mut cell = tbl.cell(1, 1).unwrap();
cell.set_text("Contains nested table:");
let mut nested = cell.add_table(2, 2);
nested = nested.borders(BorderStyle::Single, 2, "FF6600");
nested.cell(0, 0).unwrap().set_text("Inner A");
nested.cell(0, 1).unwrap().set_text("Inner B");
nested.cell(1, 0).unwrap().set_text("Inner C");
nested.cell(1, 1).unwrap().set_text("Inner D");
}
}
doc.add_paragraph("").page_break_before(true);
doc.add_paragraph("4. Images").style("Heading1");
doc.add_paragraph("Inline Image").style("Heading2");
doc.add_paragraph("Below is an inline image (200x50 pixels, blue gradient):");
let inline_img = create_sample_png(200, 50, [0, 80, 200]);
doc.add_picture(
&inline_img,
"inline_chart.png",
Length::inches(3.0),
Length::inches(0.75),
);
doc.add_paragraph("");
doc.add_paragraph("Header Image").style("Heading2");
let header_img = create_sample_png(400, 40, [40, 40, 40]);
doc.set_header_image(
&header_img,
"header_logo.png",
Length::inches(2.0),
Length::inches(0.2),
);
doc.add_paragraph(
"The document header has been replaced with an inline image. \
Check the header area at the top of this page.",
);
doc.add_paragraph("");
doc.add_paragraph(
"Note: The cover page uses a full-page background image behind the text, \
demonstrated on page 1 via add_background_image().",
);
doc.add_paragraph("").page_break_before(true);
doc.add_paragraph("5. Content Manipulation")
.style("Heading1");
doc.add_paragraph("Placeholder Replacement")
.style("Heading2");
doc.add_paragraph(
"Before replacement, this document contained {{customer}} and {{date}} placeholders.",
);
{
let mut p = doc.add_paragraph("");
p.add_run("Customer: ").bold(true);
p.add_run("{{customer}}");
}
{
let mut p = doc.add_paragraph("");
p.add_run("Date: ").bold(true);
p.add_run("{{date}}");
}
doc.add_paragraph("Reference: {{ref_number}}");
{
let mut tbl = doc.add_table(3, 2);
tbl = tbl.borders(BorderStyle::Single, 4, "000000");
tbl.cell(0, 0).unwrap().set_text("Field");
tbl.cell(0, 0).unwrap().shading("D6E4F0");
tbl.cell(0, 1).unwrap().set_text("Value");
tbl.cell(0, 1).unwrap().shading("D6E4F0");
tbl.cell(1, 0).unwrap().set_text("Project");
tbl.cell(1, 1).unwrap().set_text("{{project}}");
tbl.cell(2, 0).unwrap().set_text("Status");
tbl.cell(2, 1).unwrap().set_text("{{status}}");
}
let mut replacements = HashMap::new();
replacements.insert("{{customer}}", "Acme Corporation");
replacements.insert("{{date}}", "February 22, 2026");
replacements.insert("{{ref_number}}", "REF-2026-001");
replacements.insert("{{project}}", "Infrastructure Upgrade");
replacements.insert("{{status}}", "In Progress");
let replace_count = doc.replace_all(&replacements);
doc.add_paragraph("");
doc.add_paragraph(&format!(
"(Replaced {} placeholders above — in body text and table cells)",
replace_count
));
doc.add_paragraph("");
doc.add_paragraph("Content Insertion").style("Heading2");
doc.add_paragraph("Section A: First section of content.");
doc.add_paragraph("Section C: Third section of content.");
if let Some(idx) = doc.find_content_index("Section C") {
doc.insert_paragraph(
idx,
"Section B: Inserted between A and C using find_content_index().",
);
}
doc.add_paragraph("");
doc.add_paragraph(
"The paragraph above ('Section B') was inserted at a specific position \
using find_content_index() + insert_paragraph().",
);
doc.add_paragraph("").section_break(SectionBreak::NextPage);
doc.add_paragraph("6. Mixed Page Orientation")
.style("Heading1");
doc.add_paragraph(
"This page is in LANDSCAPE orientation. It was created using a section break \
followed by section_landscape(). This is useful for wide tables or charts.",
);
doc.add_paragraph("");
{
let mut tbl = doc.add_table(4, 7);
tbl = tbl.borders(BorderStyle::Single, 4, "2E75B6");
let headers = ["Region", "Jan", "Feb", "Mar", "Apr", "May", "Total"];
for (col, h) in headers.iter().enumerate() {
tbl.cell(0, col).unwrap().set_text(h);
tbl.cell(0, col).unwrap().shading("2E75B6");
}
let data = [
[
"North America",
"$1.2M",
"$1.3M",
"$1.4M",
"$1.5M",
"$1.6M",
"$7.0M",
],
[
"Europe", "$0.8M", "$0.9M", "$0.9M", "$1.0M", "$1.1M", "$4.7M",
],
[
"Asia Pacific",
"$0.5M",
"$0.6M",
"$0.7M",
"$0.7M",
"$0.8M",
"$3.3M",
],
];
for (row_idx, row_data) in data.iter().enumerate() {
for (col, val) in row_data.iter().enumerate() {
tbl.cell(row_idx + 1, col).unwrap().set_text(val);
}
}
}
doc.add_paragraph("")
.section_break(SectionBreak::NextPage)
.section_landscape();
doc.add_paragraph("7. Custom Styles & Summary")
.style("Heading1");
doc.add_paragraph(
"This final page is back in portrait orientation after a section break. \
The document has demonstrated:",
);
doc.add_bullet_list_item(
"Page setup: size, margins, header/footer distance, gutter",
0,
);
doc.add_bullet_list_item("Document metadata: title, author, subject, keywords", 0);
doc.add_bullet_list_item("Headers and footers: text, images, different first page", 0);
doc.add_bullet_list_item("Background images: full-page behind text", 0);
doc.add_bullet_list_item(
"Text formatting: bold, italic, underline, strike, color, size, font",
0,
);
doc.add_bullet_list_item(
"Advanced run formatting: superscript, subscript, caps, spacing",
0,
);
doc.add_bullet_list_item(
"Paragraph formatting: alignment, borders, shading, spacing, indentation",
0,
);
doc.add_bullet_list_item("Bullet and numbered lists with nesting levels", 0);
doc.add_bullet_list_item("Tab stops with dot/underscore leaders", 0);
doc.add_bullet_list_item(
"Tables: borders, shading, column spans, row spans, nesting",
0,
);
doc.add_bullet_list_item("Vertical alignment in table cells", 0);
doc.add_bullet_list_item("Inline images", 0);
doc.add_bullet_list_item("Placeholder replacement in body and table cells", 0);
doc.add_bullet_list_item("Content insertion at specific positions", 0);
doc.add_bullet_list_item(
"Section breaks with mixed portrait/landscape orientation",
0,
);
doc.add_paragraph("");
doc.add_paragraph("All features above were built entirely from scratch using the rdocx API.")
.alignment(Alignment::Center)
.shading("E2EFDA")
.border_all(BorderStyle::Single, 2, "00AA00");
doc.save(path).unwrap();
}
fn create_sample_png(width: u32, height: u32, base_color: [u8; 3]) -> Vec<u8> {
let mut pixels = Vec::with_capacity((width * height * 4) as usize);
for y in 0..height {
for x in 0..width {
let fy = y as f64 / height as f64;
let fx = x as f64 / width as f64;
let r = (base_color[0] as f64 + (1.0 - fy) * 40.0).min(255.0) as u8;
let g = (base_color[1] as f64 + fx * 30.0).min(255.0) as u8;
let b = (base_color[2] as f64 + fy * 60.0).min(255.0) as u8;
pixels.extend_from_slice(&[r, g, b, 255]);
}
}
let mut png_data = Vec::new();
{
use std::io::Write;
png_data
.write_all(&[137, 80, 78, 71, 13, 10, 26, 10])
.unwrap();
let mut ihdr = Vec::new();
ihdr.extend_from_slice(&width.to_be_bytes());
ihdr.extend_from_slice(&height.to_be_bytes());
ihdr.push(8); ihdr.push(6); ihdr.push(0); ihdr.push(0); ihdr.push(0); write_png_chunk(&mut png_data, b"IHDR", &ihdr);
let mut raw_data = Vec::new();
for y in 0..height {
raw_data.push(0); let row_start = (y * width * 4) as usize;
let row_end = row_start + (width * 4) as usize;
raw_data.extend_from_slice(&pixels[row_start..row_end]);
}
let compressed = zlib_store(&raw_data);
write_png_chunk(&mut png_data, b"IDAT", &compressed);
write_png_chunk(&mut png_data, b"IEND", &[]);
}
png_data
}
fn write_png_chunk(out: &mut Vec<u8>, chunk_type: &[u8; 4], data: &[u8]) {
use std::io::Write;
let len = data.len() as u32;
out.write_all(&len.to_be_bytes()).unwrap();
out.write_all(chunk_type).unwrap();
out.write_all(data).unwrap();
let crc = crc32(chunk_type, data);
out.write_all(&crc.to_be_bytes()).unwrap();
}
fn crc32(chunk_type: &[u8], data: &[u8]) -> u32 {
static CRC_TABLE: std::sync::LazyLock<[u32; 256]> = std::sync::LazyLock::new(|| {
let mut table = [0u32; 256];
for n in 0..256u32 {
let mut c = n;
for _ in 0..8 {
if c & 1 != 0 {
c = 0xEDB88320 ^ (c >> 1);
} else {
c >>= 1;
}
}
table[n as usize] = c;
}
table
});
let mut crc = 0xFFFFFFFF_u32;
for &byte in chunk_type.iter().chain(data.iter()) {
let index = ((crc ^ byte as u32) & 0xFF) as usize;
crc = CRC_TABLE[index] ^ (crc >> 8);
}
crc ^ 0xFFFFFFFF
}
fn zlib_store(data: &[u8]) -> Vec<u8> {
let mut out = Vec::new();
out.push(0x78);
out.push(0x01);
let chunks: Vec<&[u8]> = data.chunks(65535).collect();
for (i, chunk) in chunks.iter().enumerate() {
let is_last = i == chunks.len() - 1;
out.push(if is_last { 0x01 } else { 0x00 });
let len = chunk.len() as u16;
let nlen = !len;
out.extend_from_slice(&len.to_le_bytes());
out.extend_from_slice(&nlen.to_le_bytes());
out.extend_from_slice(chunk);
}
let adler = adler32(data);
out.extend_from_slice(&adler.to_be_bytes());
out
}
fn adler32(data: &[u8]) -> u32 {
let mut a: u32 = 1;
let mut b: u32 = 0;
for &byte in data {
a = (a + byte as u32) % 65521;
b = (b + a) % 65521;
}
(b << 16) | a
}