Skip to main content

pptx_rs/shape/
mod.rs

1//! 高阶形状:AutoShape / Picture / Group / Connector / Table / TextBox / Freeform。
2//!
3//! 对应 python-pptx 中 `pptx.shapes.*` 各类。
4//!
5//! 设计:所有形状都**借用**所属 Slide 上的 `spTree` 元素。本 crate 在第一版
6//! 采用了"对 oxml 类型做轻包装 + 直接修改"的策略——即一个 `AutoShape` 实质
7//! 上持有一个 `Rc<RefCell<Sp>>` 句柄;高阶方法直接 mutate 内部 oxml 模型。
8//!
9//! # 模块构成
10//!
11//! - [`base`]:所有形状共享的 trait [`Shape`];
12//! - [`autoshape`]:自选图形(矩形/椭圆/箭头/...);
13//! - [`textbox`]:纯文本框;
14//! - [`picture`]:图片;
15//! - [`group`]:组合 + 递归子形状;
16//! - [`connector`]:连接器(直线/折线/曲线);
17//! - [`table`]:高阶表格(封装 `<a:tbl>`);
18//! - [`chartshape`]:高阶图表(封装 `<p:graphicFrame>` + `<c:chart>` 引用);
19//! - [`oleshape`]:高阶 OLE 对象(封装 `<p:graphicFrame>` + `<p:oleObj>` 引用);
20//! - [`freeform`]:手绘自由形(custGeom 极简版)。
21//!
22//! # 在三层架构中的位置
23//!
24//! 本模块是 `shape::*` —— `oxml::shape` 的"高阶薄包装"。它不引入新数据,
25//! 全部为 `pub(crate) sp: OxmlSp` 等字段的引用/可变访问。
26//!
27//! # 与 python-pptx 的对应
28//!
29//! - `pptx.shapes.shapetree.SlideShapes` ←→ [`crate::slide::Shapes`] / [`crate::slide::ShapesMut`];
30//! - `pptx.shapes.autoshape.Shape` ←→ [`AutoShape`];
31//! - `pptx.shapes.textbox.TextBox` ←→ [`TextBox`];
32//! - `pptx.shapes.picture.Picture` ←→ [`Picture`];
33//! - `pptx.shapes.group.Group` ←→ [`Group`];
34//! - `pptx.shapes.connector.Connector` ←→ [`Connector`];
35//! - `pptx.table.Table` ←→ [`TableShape`]。
36
37pub mod autoshape;
38pub mod base;
39pub mod chartshape;
40pub mod connector;
41pub mod freeform;
42pub mod group;
43pub mod oleshape;
44pub mod picture;
45pub mod smartartshape;
46pub mod table;
47pub mod textbox;
48
49pub use autoshape::AutoShape;
50pub use base::Shape;
51pub use chartshape::ChartShape;
52pub use connector::Connector;
53pub use freeform::{Freeform, FreeformBuilder, Point};
54pub use group::{Group, GroupChild};
55pub use oleshape::OleObjectShape;
56pub use picture::Picture;
57pub use smartartshape::SmartArtShape;
58pub use table::{BorderSide, TableShape};
59pub use textbox::TextBox;
60
61// 重新导出常用枚举,路径短一点。
62pub use crate::oxml::simpletypes::{
63    Alignment, Cap, MsoAnchor, MsoAutoSize, MsoConnectorType, MsoShapeType, PresetGeometry,
64    TextDirection, TextWrapping, Underline,
65};
66
67use crate::oxml::SlideShape as OxmlSlideShape;
68
69/// 统一的形状枚举(`Slide.shapes().get(i)` 拿到的是这个)。
70///
71/// 该枚举**仅**作为"返回类型"——一旦拿到具体子类型,调用方可以转回
72/// [`AutoShape`] / [`Picture`] / ... 等高阶 API。
73#[derive(Clone, Debug)]
74pub enum ShapeKind {
75    /// 文本框。
76    TextBox(TextBox),
77    /// 自选图形(矩形、椭圆、箭头、…)。
78    AutoShape(AutoShape),
79    /// 图片。
80    Picture(Picture),
81    /// 组合。
82    Group(Group),
83    /// 连接器。
84    Connector(Connector),
85    /// 表格。
86    Table(TableShape),
87    /// 图表(TODO-004)。
88    Chart(ChartShape),
89    /// OLE 对象(TODO-043)。
90    OleObject(OleObjectShape),
91    /// SmartArt 图形(TODO-037 创建 API)。
92    SmartArt(SmartArtShape),
93    /// 占位符(标题、正文…)。
94    Placeholder(PlaceholderShape),
95}
96
97/// 占位符形状(暂时借用 AutoShape 表示 + 携带 ph 信息)。
98///
99/// 占位符与 [`AutoShape`] 的区别仅在于"是否带 `<p:ph>` 元素",所以本类型
100/// 直接包装 `AutoShape`;在序列化时 `AutoShape::write_xml`(实为 oxml `Sp::write_xml`)
101/// 会自动按 `is_placeholder` 字段决定是否写 `<p:ph>`。
102#[derive(Clone, Debug)]
103pub struct PlaceholderShape(
104    /// 内部包装的自选形状。
105    pub AutoShape,
106);
107
108impl ShapeKind {
109    /// 形状类型字符串(与 python-pptx 的 `shape_type` 对齐)。
110    pub fn shape_type(&self) -> &'static str {
111        match self {
112            ShapeKind::TextBox(_) => "text_box",
113            ShapeKind::AutoShape(_) => "auto_shape",
114            ShapeKind::Picture(_) => "picture",
115            ShapeKind::Group(_) => "group",
116            ShapeKind::Connector(_) => "connector",
117            ShapeKind::Table(_) => "table",
118            ShapeKind::Chart(_) => "chart",
119            ShapeKind::OleObject(_) => "ole_object",
120            ShapeKind::SmartArt(_) => "smart_art",
121            ShapeKind::Placeholder(_) => "placeholder",
122        }
123    }
124
125    /// 形状名(与 python-pptx 的 `shape.name` 对齐)。
126    pub fn name(&self) -> &str {
127        match self {
128            ShapeKind::TextBox(t) => t.name(),
129            ShapeKind::AutoShape(s) => s.name(),
130            ShapeKind::Picture(p) => p.name(),
131            ShapeKind::Group(g) => g.name(),
132            ShapeKind::Connector(c) => c.name(),
133            ShapeKind::Table(t) => t.name(),
134            ShapeKind::Chart(c) => c.name(),
135            ShapeKind::OleObject(o) => o.name(),
136            ShapeKind::SmartArt(s) => s.name(),
137            ShapeKind::Placeholder(p) => p.0.name(),
138        }
139    }
140
141    /// 形状 ID(与 python-pptx 的 `shape.shape_id` 对齐)。
142    pub fn shape_id(&self) -> u32 {
143        match self {
144            ShapeKind::TextBox(t) => t.id(),
145            ShapeKind::AutoShape(s) => s.id(),
146            ShapeKind::Picture(p) => p.id(),
147            ShapeKind::Group(g) => g.id(),
148            ShapeKind::Connector(c) => c.id(),
149            ShapeKind::Table(t) => t.id(),
150            ShapeKind::Chart(c) => c.id(),
151            ShapeKind::OleObject(o) => o.id(),
152            ShapeKind::SmartArt(s) => s.id(),
153            ShapeKind::Placeholder(p) => p.0.id(),
154        }
155    }
156
157    /// 左边坐标(EMU)。
158    pub fn left(&self) -> crate::units::Emu {
159        match self {
160            ShapeKind::TextBox(t) => t.left(),
161            ShapeKind::AutoShape(s) => s.left(),
162            ShapeKind::Picture(p) => p.left(),
163            ShapeKind::Group(g) => g.left(),
164            ShapeKind::Connector(c) => c.left(),
165            ShapeKind::Table(t) => t.left(),
166            ShapeKind::Chart(c) => c.left(),
167            ShapeKind::OleObject(o) => o.left(),
168            ShapeKind::SmartArt(s) => s.left(),
169            ShapeKind::Placeholder(p) => p.0.left(),
170        }
171    }
172    /// 顶边坐标(EMU)。
173    pub fn top(&self) -> crate::units::Emu {
174        match self {
175            ShapeKind::TextBox(t) => t.top(),
176            ShapeKind::AutoShape(s) => s.top(),
177            ShapeKind::Picture(p) => p.top(),
178            ShapeKind::Group(g) => g.top(),
179            ShapeKind::Connector(c) => c.top(),
180            ShapeKind::Table(t) => t.top(),
181            ShapeKind::Chart(c) => c.top(),
182            ShapeKind::OleObject(o) => o.top(),
183            ShapeKind::SmartArt(s) => s.top(),
184            ShapeKind::Placeholder(p) => p.0.top(),
185        }
186    }
187    /// 宽度(EMU)。
188    pub fn width(&self) -> crate::units::Emu {
189        match self {
190            ShapeKind::TextBox(t) => t.width(),
191            ShapeKind::AutoShape(s) => s.width(),
192            ShapeKind::Picture(p) => p.width(),
193            ShapeKind::Group(g) => g.width(),
194            ShapeKind::Connector(c) => c.width(),
195            ShapeKind::Table(t) => t.width(),
196            ShapeKind::Chart(c) => c.width(),
197            ShapeKind::OleObject(o) => o.width(),
198            ShapeKind::SmartArt(s) => s.width(),
199            ShapeKind::Placeholder(p) => p.0.width(),
200        }
201    }
202    /// 高度(EMU)。
203    pub fn height(&self) -> crate::units::Emu {
204        match self {
205            ShapeKind::TextBox(t) => t.height(),
206            ShapeKind::AutoShape(s) => s.height(),
207            ShapeKind::Picture(p) => p.height(),
208            ShapeKind::Group(g) => g.height(),
209            ShapeKind::Connector(c) => c.height(),
210            ShapeKind::Table(t) => t.height(),
211            ShapeKind::Chart(c) => c.height(),
212            ShapeKind::OleObject(o) => o.height(),
213            ShapeKind::SmartArt(s) => s.height(),
214            ShapeKind::Placeholder(p) => p.0.height(),
215        }
216    }
217}
218
219/// 从 oxml SlideShape 中**取名字**(不构造 [`ShapeKind`])。
220///
221/// 在 `SlideShapes::index(shape)` 等"按名字定位"场景下使用,避免无谓 clone。
222pub fn name_of(oxml: &OxmlSlideShape) -> &str {
223    match oxml {
224        OxmlSlideShape::Sp(sp) => &sp.name,
225        OxmlSlideShape::Pic(p) => &p.name,
226        OxmlSlideShape::CxnSp(c) => &c.name,
227        OxmlSlideShape::Group(g) => &g.name,
228        OxmlSlideShape::GraphicFrame(g) => &g.name,
229    }
230}
231
232/// 把 oxml SlideShape 转换为高阶 [`ShapeKind`]。
233///
234/// 在 [`crate::slide::Shapes::get`] 中调用;属于"边界转换",调用方不需要直接使用。
235pub fn wrap(oxml: &OxmlSlideShape) -> ShapeKind {
236    match oxml {
237        OxmlSlideShape::Sp(sp) => {
238            if sp.c_nv_sp_pr_tx_box {
239                ShapeKind::TextBox(TextBox::from_sp(sp.clone()))
240            } else {
241                // 占位符与普通自选图形统一用 AutoShape 表示;
242                // is_placeholder 仅影响序列化时是否写出 <p:ph> 元素。
243                ShapeKind::AutoShape(AutoShape::from_sp(sp.clone()))
244            }
245        }
246        OxmlSlideShape::Pic(p) => ShapeKind::Picture(Picture::from_pic(p.clone())),
247        OxmlSlideShape::CxnSp(c) => ShapeKind::Connector(Connector::from_cxn(c.clone())),
248        OxmlSlideShape::Group(g) => {
249            // 简化: 子形状全部转成 AutoShape / Picture / Connector(递归 group)
250            let ng = Group {
251                group: (**g).clone(),
252            };
253            ShapeKind::Group(ng)
254        }
255        OxmlSlideShape::GraphicFrame(g) => match &g.graphic {
256            crate::oxml::shape::Graphic::Table(_) => {
257                ShapeKind::Table(TableShape::from_frame(g.clone()))
258            }
259            crate::oxml::shape::Graphic::Chart(_) => {
260                ShapeKind::Chart(ChartShape::from_frame(g.clone()))
261            }
262            crate::oxml::shape::Graphic::OleObject(_) => {
263                ShapeKind::OleObject(OleObjectShape::from_frame(g.clone()))
264            }
265            crate::oxml::shape::Graphic::SmartArt(_) => {
266                ShapeKind::SmartArt(SmartArtShape::from_frame(g.clone()))
267            }
268        },
269    }
270}