office-toolkit 1.0.0

A Rust library to create, read, and modify Microsoft Office documents (Word/Excel/PowerPoint) in the modern Open XML (OOXML) format.
Documentation
//! PowerPoint media: an embedded picture, a picture with an external link
//! alongside its embedded preview, a video clip with a poster frame, and
//! an embedded chart. One slide per feature.
//!
//! Run with: `cargo run -p office-toolkit --example pptx_media`

use std::path::{Path, PathBuf};

use office_toolkit::SaveToFile;
use office_toolkit::chart::{
    Axis, AxisDataSource, AxisPosition, BarChart, BarDirection, BarSeries, CategoryAxis, Chart,
    ChartSpace, ChartType, NumericData, NumericDataSource, NumericPoint, PlotArea, StringData,
    StringDataSource, StringPoint, ValueAxis,
};
use office_toolkit::powerpoint::{
    MediaFormat, Picture, PictureFormat, Shape, SlideChart, SlideMedia,
};
use office_toolkit::prelude::*;

fn main() -> office_toolkit::Result<()> {
    let path = output_path("pptx_media.pptx");
    let image_bytes = std::fs::read(image_fixture_path())?;

    let embedded_picture = Picture::new(
        2,
        "Embedded picture",
        image_bytes.clone(),
        PictureFormat::Png,
        3_000_000,
        2_000_000,
    )
    .with_offset(838_200, 838_200)
    .with_description("Project test image");

    let linked_picture = Picture::new(
        2,
        "Externally linked picture",
        image_bytes,
        PictureFormat::Png,
        3_000_000,
        2_000_000,
    )
    .with_offset(838_200, 838_200)
    .with_external_link("https://example.com/original-image.png");

    // Placeholder bytes — a real .pptx would carry a genuine video file
    // here. This example only demonstrates the model/writer plumbing.
    let poster_image = vec![0x89, 0x50, 0x4E, 0x47]; // fake, just enough bytes to have *some* data
    let media = SlideMedia::new(
        2,
        "Video clip",
        vec![0u8; 16],
        MediaFormat::Mp4,
        4_000_000,
        2_250_000,
    )
    .with_offset(838_200, 838_200)
    .with_poster_image(poster_image, PictureFormat::Png);

    let chart = SlideChart::new(
        2,
        "Quarterly revenue",
        sample_chart_space(),
        6_000_000,
        3_500_000,
    )
    .with_offset(838_200, 838_200);

    let presentation = Presentation::new()
        .with_slide(Slide::new().with_shape(Shape::Picture(embedded_picture)))
        .with_slide(Slide::new().with_shape(Shape::Picture(linked_picture)))
        .with_slide(Slide::new().with_shape(Shape::Media(media)))
        .with_slide(Slide::new().with_shape(Shape::Chart(Box::new(chart))));

    presentation.save_to_file(&path)?;
    println!("Wrote {}", path.display());
    Ok(())
}

/// A minimal bar chart with literal (not cell-referenced) data.
fn sample_chart_space() -> ChartSpace {
    let categories = StringData::new()
        .with_point(StringPoint::new(0, "Q1"))
        .with_point(StringPoint::new(1, "Q2"))
        .with_point(StringPoint::new(2, "Q3"));
    let values = NumericData::new()
        .with_point(NumericPoint::new(0, "1200"))
        .with_point(NumericPoint::new(1, "1350"))
        .with_point(NumericPoint::new(2, "980"));

    let series = BarSeries::new(0, 0)
        .with_categories(AxisDataSource::String(StringDataSource::literal(
            categories,
        )))
        .with_values(NumericDataSource::literal(values));

    let bar_chart = BarChart::new(BarDirection::Column, 1, 2).with_series(series);
    let category_axis = CategoryAxis::new(1, AxisPosition::Bottom, 2);
    let value_axis = ValueAxis::new(2, AxisPosition::Left, 1);

    let plot_area = PlotArea::new()
        .with_chart(ChartType::Bar(bar_chart))
        .with_axis(Axis::Category(category_axis))
        .with_axis(Axis::Value(value_axis));

    ChartSpace::new(Chart::new(plot_area))
}

/// The project's own Word test fixture, reused here too since it's a
/// simple, small, real PNG.
fn image_fixture_path() -> PathBuf {
    Path::new(env!("CARGO_MANIFEST_DIR")).join("../../tests-data/word/test_image.png")
}

/// Every example in this directory writes its output under
/// `tests-data/output/`, resolved relative to this crate's own manifest so
/// it works no matter what directory `cargo run` was invoked from.
fn output_path(filename: &str) -> PathBuf {
    Path::new(env!("CARGO_MANIFEST_DIR"))
        .join("../../tests-data/output")
        .join(filename)
}