pptx-rs2 0.5.0

Rust 版本的 PowerPoint .pptx 读写库, 对标 python-pptx
Documentation
//! 集成测试:形状端到端流程(TODO-041)。
//!
//! 验证各种形状类型(文本框、自选形状、表格、连接器、组合)从添加到
//! 序列化的完整流程,确保 round-trip 不丢失形状。
//!
//! # 覆盖场景
//!
//! - 文本框添加 + 文本设置
//! - 自选形状(矩形/椭圆)添加
//! - 表格添加 + 单元格编辑
//! - 连接器添加
//! - 组合形状添加
//! - 多形状混合场景 round-trip

use pptx_rs::shape::Shape;
use pptx_rs::EmuExt;
use pptx_rs::{Inches, Presentation, PresetGeometry};

/// 验证文本框可以成功添加到幻灯片并 round-trip。
#[test]
fn textbox_round_trip() {
    let mut prs = Presentation::new().expect("Presentation::new failed");
    let counter = prs.id_counter();
    let slide = prs
        .slides_mut()
        .add_slide(counter)
        .expect("add_slide failed");
    let tb = slide
        .shapes_mut()
        .add_textbox_with_text(
            Inches(1.0),
            Inches(1.0),
            Inches(8.0),
            Inches(1.0),
            "文本框集成测试",
        )
        .expect("add_textbox failed");
    // 验证返回的 TextBox 句柄有效
    assert_eq!(tb.left().value(), Inches(1.0).emu().value());
    assert_eq!(tb.width().value(), Inches(8.0).emu().value());

    // round-trip 验证
    let bytes = prs.to_bytes().expect("to_bytes failed");
    let prs2 = Presentation::load_bytes(&bytes).expect("load_bytes failed");
    assert_eq!(prs2.slides().len(), 1, "slide count should be 1");
}

/// 验证多种自选形状可以成功添加到同一张幻灯片。
#[test]
fn multiple_autoshapes() {
    let mut prs = Presentation::new().expect("Presentation::new failed");
    let counter = prs.id_counter();
    let slide = prs
        .slides_mut()
        .add_slide(counter)
        .expect("add_slide failed");

    // 矩形
    let rect = slide
        .shapes_mut()
        .add_shape(
            PresetGeometry::Rectangle,
            Inches(1.0),
            Inches(1.0),
            Inches(2.0),
            Inches(1.0),
        )
        .expect("add_shape Rectangle failed");
    assert_eq!(rect.width().value(), Inches(2.0).emu().value());

    // 圆角矩形
    let round_rect = slide
        .shapes_mut()
        .add_shape(
            PresetGeometry::RoundRectangle,
            Inches(4.0),
            Inches(1.0),
            Inches(2.0),
            Inches(1.0),
        )
        .expect("add_shape RoundRectangle failed");
    assert_eq!(round_rect.left().value(), Inches(4.0).emu().value());

    // 椭圆
    let oval = slide
        .shapes_mut()
        .add_shape(
            PresetGeometry::Ellipse,
            Inches(1.0),
            Inches(3.0),
            Inches(2.0),
            Inches(1.0),
        )
        .expect("add_shape Ellipse failed");
    assert_eq!(oval.top().value(), Inches(3.0).emu().value());

    // round-trip 验证
    let bytes = prs.to_bytes().expect("to_bytes failed");
    let prs2 = Presentation::load_bytes(&bytes).expect("load_bytes failed");
    assert_eq!(prs2.slides().len(), 1, "slide count should be 1");
}

/// 验证表格可以成功添加并 round-trip。
#[test]
fn table_round_trip() {
    let mut prs = Presentation::new().expect("Presentation::new failed");
    let counter = prs.id_counter();
    let slide = prs
        .slides_mut()
        .add_slide(counter)
        .expect("add_slide failed");
    let table = slide
        .shapes_mut()
        .add_table(4, 3, Inches(1.0), Inches(1.0), Inches(8.0), Inches(3.0))
        .expect("add_table failed");

    // 验证表格基本属性
    assert_eq!(table.row_count(), 4);
    assert_eq!(table.column_count(), 3);

    // round-trip 验证
    let bytes = prs.to_bytes().expect("to_bytes failed");
    let prs2 = Presentation::load_bytes(&bytes).expect("load_bytes failed");
    assert_eq!(prs2.slides().len(), 1, "slide count should be 1");
}

/// 验证连接器可以成功添加并 round-trip。
#[test]
fn connector_round_trip() {
    let mut prs = Presentation::new().expect("Presentation::new failed");
    let counter = prs.id_counter();
    let slide = prs
        .slides_mut()
        .add_slide(counter)
        .expect("add_slide failed");

    // 先添加两个形状作为连接端点
    let _shape1 = slide
        .shapes_mut()
        .add_shape(
            PresetGeometry::Rectangle,
            Inches(1.0),
            Inches(1.0),
            Inches(2.0),
            Inches(1.0),
        )
        .expect("add_shape 1 failed");
    let _shape2 = slide
        .shapes_mut()
        .add_shape(
            PresetGeometry::Rectangle,
            Inches(5.0),
            Inches(3.0),
            Inches(2.0),
            Inches(1.0),
        )
        .expect("add_shape 2 failed");

    // 添加连接器
    let _cxn = slide
        .shapes_mut()
        .add_connector(
            pptx_rs::MsoConnectorType::Straight,
            Inches(3.0),
            Inches(1.5),
            Inches(5.0),
            Inches(3.5),
        )
        .expect("add_connector failed");
    // Connector 的 left/top 在 add_connector 中被设为 0(用 begin/end 定位),这里不验证 left

    // round-trip 验证
    let bytes = prs.to_bytes().expect("to_bytes failed");
    let prs2 = Presentation::load_bytes(&bytes).expect("load_bytes failed");
    assert_eq!(prs2.slides().len(), 1, "slide count should be 1");
}

/// 验证多种形状混合在同一张幻灯片的场景。
#[test]
fn mixed_shapes_in_one_slide() {
    let mut prs = Presentation::new().expect("Presentation::new failed");
    let counter = prs.id_counter();
    let slide = prs
        .slides_mut()
        .add_slide(counter)
        .expect("add_slide failed");

    // 文本框
    slide
        .shapes_mut()
        .add_textbox_with_text(
            Inches(0.5),
            Inches(0.5),
            Inches(9.0),
            Inches(1.0),
            "混合形状标题",
        )
        .expect("add_textbox failed");

    // 矩形
    slide
        .shapes_mut()
        .add_shape(
            PresetGeometry::Rectangle,
            Inches(0.5),
            Inches(2.0),
            Inches(4.0),
            Inches(2.0),
        )
        .expect("add_shape Rectangle failed");

    // 椭圆
    slide
        .shapes_mut()
        .add_shape(
            PresetGeometry::Ellipse,
            Inches(5.0),
            Inches(2.0),
            Inches(4.0),
            Inches(2.0),
        )
        .expect("add_shape Ellipse failed");

    // 表格
    slide
        .shapes_mut()
        .add_table(3, 4, Inches(0.5), Inches(4.5), Inches(9.0), Inches(2.5))
        .expect("add_table failed");

    // round-trip 验证
    let bytes = prs.to_bytes().expect("to_bytes failed");
    let prs2 = Presentation::load_bytes(&bytes).expect("load_bytes failed");
    assert_eq!(prs2.slides().len(), 1, "slide count should be 1");
}

/// 验证多张幻灯片各自带不同形状的复杂场景。
#[test]
fn multiple_slides_with_different_shapes() {
    let mut prs = Presentation::new().expect("Presentation::new failed");

    // 第 1 张:文本框
    let counter = prs.id_counter();
    let slide1 = prs
        .slides_mut()
        .add_slide(counter)
        .expect("add_slide 1 failed");
    slide1
        .shapes_mut()
        .add_textbox_with_text(
            Inches(1.0),
            Inches(1.0),
            Inches(8.0),
            Inches(1.0),
            "第 1 张幻灯片",
        )
        .expect("add_textbox failed");

    // 第 2 张:自选形状
    let counter = prs.id_counter();
    let slide2 = prs
        .slides_mut()
        .add_slide(counter)
        .expect("add_slide 2 failed");
    slide2
        .shapes_mut()
        .add_shape(
            PresetGeometry::Rectangle,
            Inches(2.0),
            Inches(2.0),
            Inches(4.0),
            Inches(2.0),
        )
        .expect("add_shape failed");

    // 第 3 张:表格
    let counter = prs.id_counter();
    let slide3 = prs
        .slides_mut()
        .add_slide(counter)
        .expect("add_slide 3 failed");
    slide3
        .shapes_mut()
        .add_table(3, 3, Inches(1.0), Inches(1.0), Inches(8.0), Inches(4.0))
        .expect("add_table failed");

    assert_eq!(prs.slides().len(), 3);

    // round-trip 验证
    let bytes = prs.to_bytes().expect("to_bytes failed");
    let prs2 = Presentation::load_bytes(&bytes).expect("load_bytes failed");
    assert_eq!(
        prs2.slides().len(),
        3,
        "slide count should be preserved after round-trip"
    );
}