pptx 0.1.0

A Rust library for creating and manipulating PowerPoint (.pptx) files
Documentation
//! XML generation methods for creating shape elements.

use crate::units::{Emu, ShapeId};
use crate::xml_util::xml_escape;

use super::ShapeTree;

/// Map a preset geometry name to a human-readable base name for auto-naming.
#[allow(clippy::redundant_pub_crate)]
pub(crate) fn shape_name_for_prst(prst: &str) -> &'static str {
    match prst {
        "rect" => "Rectangle",
        "roundRect" => "Rounded Rectangle",
        "ellipse" => "Oval",
        "diamond" => "Diamond",
        "triangle" => "Isosceles Triangle",
        "rtTriangle" => "Right Triangle",
        "parallelogram" => "Parallelogram",
        "trapezoid" => "Trapezoid",
        "pentagon" => "Regular Pentagon",
        "homePlate" => "Pentagon",
        "hexagon" => "Hexagon",
        "heptagon" => "Heptagon",
        "octagon" => "Octagon",
        "decagon" => "Decagon",
        "dodecagon" => "Dodecagon",
        "star4" => "4-Point Star",
        "star5" => "5-Point Star",
        "star6" => "6-Point Star",
        "star7" => "7-Point Star",
        "star8" => "8-Point Star",
        "star10" => "10-Point Star",
        "star12" => "12-Point Star",
        "star16" => "16-Point Star",
        "star24" => "24-Point Star",
        "star32" => "32-Point Star",
        "cloud" => "Cloud",
        "heart" => "Heart",
        "sun" => "Sun",
        "moon" => "Moon",
        "arc" => "Arc",
        "donut" => "Donut",
        "plus" => "Cross",
        "can" => "Can",
        "cube" => "Cube",
        "bevel" => "Bevel",
        "frame" => "Frame",
        "pie" => "Pie",
        "chord" => "Chord",
        "plaque" => "Plaque",
        "funnel" => "Funnel",
        "wave" => "Wave",
        "doubleWave" => "Double Wave",
        "smileyFace" => "Smiley Face",
        "noSmoking" => "No Symbol",
        "lightningBolt" => "Lightning Bolt",
        "teardrop" => "Tear",
        "foldedCorner" => "Folded Corner",
        "leftArrow" => "Left Arrow",
        "rightArrow" => "Right Arrow",
        "upArrow" => "Up Arrow",
        "downArrow" => "Down Arrow",
        "leftRightArrow" => "Left-Right Arrow",
        "upDownArrow" => "Up-Down Arrow",
        _ => "Freeform",
    }
}

impl ShapeTree {
    /// Generate XML for a new `<p:sp>` element (textbox).
    #[must_use]
    pub fn new_textbox_xml(
        shape_id: ShapeId,
        name: &str,
        left: Emu,
        top: Emu,
        width: Emu,
        height: Emu,
    ) -> String {
        format!(
            r#"<p:sp xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"><p:nvSpPr><p:cNvPr id="{}" name="{}"/><p:cNvSpPr txBox="1"/><p:nvPr/></p:nvSpPr><p:spPr><a:xfrm><a:off x="{}" y="{}"/><a:ext cx="{}" cy="{}"/></a:xfrm><a:prstGeom prst="rect"><a:avLst/></a:prstGeom><a:noFill/></p:spPr><p:txBody><a:bodyPr wrap="square" rtlCol="0"/><a:lstStyle/><a:p><a:endParaRPr lang="en-US"/></a:p></p:txBody></p:sp>"#,
            shape_id,
            xml_escape(name),
            left.0,
            top.0,
            width.0,
            height.0,
        )
    }

    /// Generate XML for a new `<p:sp>` element (auto shape with preset geometry).
    #[must_use]
    pub fn new_autoshape_xml(
        shape_id: ShapeId,
        name: &str,
        left: Emu,
        top: Emu,
        width: Emu,
        height: Emu,
        prst: &str,
    ) -> String {
        format!(
            r#"<p:sp xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"><p:nvSpPr><p:cNvPr id="{}" name="{}"/><p:cNvSpPr/><p:nvPr/></p:nvSpPr><p:spPr><a:xfrm><a:off x="{}" y="{}"/><a:ext cx="{}" cy="{}"/></a:xfrm><a:prstGeom prst="{}"><a:avLst/></a:prstGeom></p:spPr><p:txBody><a:bodyPr rtlCol="0" anchor="ctr"/><a:lstStyle/><a:p><a:pPr algn="ctr"/><a:endParaRPr lang="en-US"/></a:p></p:txBody></p:sp>"#,
            shape_id,
            xml_escape(name),
            left.0,
            top.0,
            width.0,
            height.0,
            prst,
        )
    }

    /// Generate XML for a new `<p:pic>` element.
    #[must_use]
    #[allow(clippy::too_many_arguments)]
    pub fn new_picture_xml(
        shape_id: ShapeId,
        name: &str,
        desc: &str,
        r_id: &str,
        left: Emu,
        top: Emu,
        width: Emu,
        height: Emu,
    ) -> String {
        format!(
            r#"<p:pic xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"><p:nvPicPr><p:cNvPr id="{}" name="{}" descr="{}"/><p:cNvPicPr><a:picLocks noChangeAspect="1"/></p:cNvPicPr><p:nvPr/></p:nvPicPr><p:blipFill><a:blip r:embed="{}"/><a:stretch><a:fillRect/></a:stretch></p:blipFill><p:spPr><a:xfrm><a:off x="{}" y="{}"/><a:ext cx="{}" cy="{}"/></a:xfrm><a:prstGeom prst="rect"><a:avLst/></a:prstGeom></p:spPr></p:pic>"#,
            shape_id,
            xml_escape(name),
            xml_escape(desc),
            r_id,
            left.0,
            top.0,
            width.0,
            height.0,
        )
    }

    /// Generate XML for a new `<p:cxnSp>` element (connector).
    #[must_use]
    pub fn new_connector_xml(
        shape_id: ShapeId,
        name: &str,
        left: Emu,
        top: Emu,
        width: Emu,
        height: Emu,
        prst: &str,
    ) -> String {
        format!(
            r#"<p:cxnSp xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"><p:nvCxnSpPr><p:cNvPr id="{}" name="{}"/><p:cNvCxnSpPr/><p:nvPr/></p:nvCxnSpPr><p:spPr><a:xfrm><a:off x="{}" y="{}"/><a:ext cx="{}" cy="{}"/></a:xfrm><a:prstGeom prst="{}"><a:avLst/></a:prstGeom></p:spPr></p:cxnSp>"#,
            shape_id,
            xml_escape(name),
            left.0,
            top.0,
            width.0,
            height.0,
            prst,
        )
    }

    /// Generate XML for a new `<p:graphicFrame>` element containing a chart.
    ///
    /// The `r_id` is the relationship ID linking the slide to the chart part.
    #[must_use]
    pub fn new_chart_graphic_frame_xml(
        shape_id: ShapeId,
        name: &str,
        r_id: &str,
        left: Emu,
        top: Emu,
        width: Emu,
        height: Emu,
    ) -> String {
        format!(
            r#"<p:graphicFrame xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"><p:nvGraphicFramePr><p:cNvPr id="{}" name="{}"/><p:cNvGraphicFramePr><a:graphicFrameLocks noGrp="1"/></p:cNvGraphicFramePr><p:nvPr/></p:nvGraphicFramePr><p:xfrm><a:off x="{}" y="{}"/><a:ext cx="{}" cy="{}"/></p:xfrm><a:graphic><a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/chart"><c:chart xmlns:c="http://schemas.openxmlformats.org/drawingml/2006/chart" r:id="{}"/></a:graphicData></a:graphic></p:graphicFrame>"#,
            shape_id,
            xml_escape(name),
            left.0,
            top.0,
            width.0,
            height.0,
            r_id,
        )
    }

    /// Generate XML for a new `<p:graphicFrame>` element containing a table.
    #[must_use]
    #[allow(clippy::too_many_arguments)]
    pub fn new_table_xml(
        shape_id: ShapeId,
        name: &str,
        rows: u32,
        cols: u32,
        left: Emu,
        top: Emu,
        width: Emu,
        height: Emu,
    ) -> String {
        let col_width = if cols > 0 {
            width.0 / i64::from(cols)
        } else {
            width.0
        };
        let row_height = if rows > 0 {
            height.0 / i64::from(rows)
        } else {
            height.0
        };

        let mut cols_xml = String::new();
        for _ in 0..cols {
            cols_xml.push_str(&format!(r#"<a:gridCol w="{col_width}"/>"#));
        }

        let mut rows_xml = String::new();
        for _ in 0..rows {
            let mut cells_xml = String::new();
            for _ in 0..cols {
                cells_xml.push_str(r#"<a:tc><a:txBody><a:bodyPr/><a:lstStyle/><a:p><a:endParaRPr lang="en-US"/></a:p></a:txBody><a:tcPr/></a:tc>"#);
            }
            rows_xml.push_str(&format!(r#"<a:tr h="{row_height}">{cells_xml}</a:tr>"#));
        }

        format!(
            r#"<p:graphicFrame xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"><p:nvGraphicFramePr><p:cNvPr id="{}" name="{}"/><p:cNvGraphicFramePr><a:graphicFrameLocks noGrp="1"/></p:cNvGraphicFramePr><p:nvPr/></p:nvGraphicFramePr><p:xfrm><a:off x="{}" y="{}"/><a:ext cx="{}" cy="{}"/></p:xfrm><a:graphic><a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/table"><a:tbl><a:tblPr firstRow="1" bandRow="1"><a:tableStyleId>{{5C22544A-7EE6-4342-B048-85BDC9FD1C3A}}</a:tableStyleId></a:tblPr><a:tblGrid>{}</a:tblGrid>{}</a:tbl></a:graphicData></a:graphic></p:graphicFrame>"#,
            shape_id,
            xml_escape(name),
            left.0,
            top.0,
            width.0,
            height.0,
            cols_xml,
            rows_xml,
        )
    }

    /// Generate XML for a new `<p:cxnSp>` element with flip attributes.
    #[must_use]
    #[allow(clippy::too_many_arguments)]
    pub fn new_connector_xml_with_flip(
        shape_id: ShapeId,
        name: &str,
        left: Emu,
        top: Emu,
        width: Emu,
        height: Emu,
        prst: &str,
        flip_h: bool,
        flip_v: bool,
    ) -> String {
        let mut flip_attrs = String::new();
        if flip_h {
            flip_attrs.push_str(r#" flipH="1""#);
        }
        if flip_v {
            flip_attrs.push_str(r#" flipV="1""#);
        }
        format!(
            r#"<p:cxnSp xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"><p:nvCxnSpPr><p:cNvPr id="{}" name="{}"/><p:cNvCxnSpPr/><p:nvPr/></p:nvCxnSpPr><p:spPr><a:xfrm{}><a:off x="{}" y="{}"/><a:ext cx="{}" cy="{}"/></a:xfrm><a:prstGeom prst="{}"><a:avLst/></a:prstGeom></p:spPr></p:cxnSp>"#,
            shape_id,
            xml_escape(name),
            flip_attrs,
            left.0,
            top.0,
            width.0,
            height.0,
            prst,
        )
    }

    /// Generate XML for a new `<p:pic>` element representing a movie (video) shape.
    ///
    /// The video is referenced via `<a:videoFile r:link="..."/>` inside `<p:nvPr>`,
    /// and the poster frame image is embedded via `<a:blip r:embed="..."/>`.
    #[must_use]
    #[allow(clippy::too_many_arguments)]
    pub fn new_movie_xml(
        shape_id: ShapeId,
        name: &str,
        video_r_id: &str,
        poster_r_id: &str,
        left: Emu,
        top: Emu,
        width: Emu,
        height: Emu,
    ) -> String {
        format!(
            r#"<p:pic xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"><p:nvPicPr><p:cNvPr id="{}" name="{}"/><p:cNvPicPr><a:picLocks noChangeAspect="1"/></p:cNvPicPr><p:nvPr><a:videoFile r:link="{}"/></p:nvPr></p:nvPicPr><p:blipFill><a:blip r:embed="{}"/><a:stretch><a:fillRect/></a:stretch></p:blipFill><p:spPr><a:xfrm><a:off x="{}" y="{}"/><a:ext cx="{}" cy="{}"/></a:xfrm><a:prstGeom prst="rect"><a:avLst/></a:prstGeom></p:spPr></p:pic>"#,
            shape_id,
            xml_escape(name),
            video_r_id,
            poster_r_id,
            left.0,
            top.0,
            width.0,
            height.0,
        )
    }

    /// Generate XML for a new `<p:grpSp>` element (empty group shape).
    #[must_use]
    pub fn new_group_shape_xml(
        shape_id: ShapeId,
        name: &str,
        left: Emu,
        top: Emu,
        width: Emu,
        height: Emu,
    ) -> String {
        format!(
            r#"<p:grpSp xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"><p:nvGrpSpPr><p:cNvPr id="{}" name="{}"/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr><p:grpSpPr><a:xfrm><a:off x="{}" y="{}"/><a:ext cx="{}" cy="{}"/><a:chOff x="{}" y="{}"/><a:chExt cx="{}" cy="{}"/></a:xfrm></p:grpSpPr></p:grpSp>"#,
            shape_id,
            xml_escape(name),
            left.0,
            top.0,
            width.0,
            height.0,
            left.0,
            top.0,
            width.0,
            height.0,
        )
    }
}