Skip to main content

pptx_rs/shape/
oleshape.rs

1//! `OleObjectShape`:高阶 OLE 对象形状(TODO-043)。
2//!
3//! OLE 对象在 OOXML 中通过 `<p:graphicFrame>` + `<a:graphicData uri=".../ole">`
4//! + `<p:oleObj r:id="..."/>` 引用一个独立的 `/ppt/embeddings/oleObjectN.bin` part。
5//!   本高阶 API 把 graphicFrame 包装为 [`OleObjectShape`],提供 rid / image_rid /
6//!   prog_id / name / show_as_icon 的便捷访问,并让 [`Shape`] trait 直接可用。
7//!
8//! # 与 python-pptx 的对应
9//!
10//! - `pptx.parts.oleobject.OleObjectPart` ←→ [`crate::oxml::ole::OleObject`](数据模型);
11//! - `pptx.shapes.graphfrm.GraphicFrame` ←→ 本 [`OleObjectShape`](承载位置/尺寸 + 引用)。
12//!
13//! # 写出语义
14//!
15//! - `OleObjectShape` 序列化时只写出 `<p:graphicFrame>` + 引用元素 `<p:oleObj r:id="..."/>`;
16//! - 真正的 OLE 二进制数据由 [`crate::presentation::Presentation::save`] 在
17//!   `to_opc_package` 中遍历每张 slide 的 `ole_entries` 写出独立的 `oleObjectN.bin` part;
18//! - slide 的 `_rels/slideN.xml.rels` 中会添加 `oleObject` 关系指向该 part。
19//!
20//! # 限制
21//!
22//! - 当前仅支持嵌入(`<p:embed/>`),不支持链接(`<p:link/>`);
23//! - 图标图片可选(`image_rid` 为空时 PowerPoint 用默认图标);
24//! - 读取已有 OLE 对象的 graphicFrame 时,**仅保留外壳**,不解析 `<p:oleObj>` 内容。
25
26use crate::oxml::ole::OleObject as OxmlOleObject;
27use crate::oxml::shape::{Graphic as OxmlGraphic, GraphicFrame as OxmlFrame};
28use crate::shape::base::Shape;
29use crate::units::Emu;
30
31/// 高阶 OLE 对象形状(承载 `<p:graphicFrame>` + `<p:oleObj r:id="..."/>` 引用)。
32///
33/// 通过 [`OleObjectShape::ole`] / [`OleObjectShape::ole_mut`] 访问 OLE 数据模型;
34/// 通过 [`Shape`] trait 方法(`left` / `top` / `width` / `height`)调整位置与尺寸。
35///
36/// # 内部不变量
37///
38/// `frame.graphic` 始终保持为 `Graphic::OleObject(_)`。本类型所有便捷方法
39/// (`rid` / `set_rid` / `image_rid` / `prog_id` / `name` / `show_as_icon`)
40/// 在不变量被破坏时**静默忽略**或返回默认值,绝不 panic——
41/// 这与库整体"零 panic"约定一致(参见 `.trae/rules/project_rules.md` §5)。
42#[derive(Clone, Debug, Default)]
43pub struct OleObjectShape {
44    /// 内部 oxml 句柄(`GraphicFrame`,承载 `Graphic::OleObject`)。
45    pub(crate) frame: OxmlFrame,
46}
47
48impl OleObjectShape {
49    /// 构造一个指定 progId 与显示名的 OLE 对象形状(rid/image_rid 留空,由 presentation 层填充)。
50    ///
51    /// # 参数
52    /// - `prog_id`:OLE 程序标识符(如 `"Excel.Sheet.12"` / `"Package"`)。
53    /// - `name`:显示名(如 `"Worksheet"`)。
54    pub fn new(prog_id: impl Into<String>, name: impl Into<String>) -> Self {
55        let ole = OxmlOleObject::new(prog_id, name);
56        let frame = OxmlFrame {
57            graphic: OxmlGraphic::OleObject(ole),
58            ..Default::default()
59        };
60        OleObjectShape { frame }
61    }
62
63    /// 从 oxml Frame 构造(通常用于读取已有 OLE 对象时)。
64    pub fn from_frame(frame: OxmlFrame) -> Self {
65        OleObjectShape { frame }
66    }
67
68    /// 取内部 oxml OleObject 引用。
69    ///
70    /// 返回 `None` 仅在内部不变量被外部错误破坏时(`frame.graphic` 不是 `OleObject` 变体)。
71    pub fn ole(&self) -> Option<&OxmlOleObject> {
72        match &self.frame.graphic {
73            OxmlGraphic::OleObject(o) => Some(o),
74            _ => None,
75        }
76    }
77
78    /// 取内部 oxml OleObject 可变引用。
79    pub fn ole_mut(&mut self) -> Option<&mut OxmlOleObject> {
80        match &mut self.frame.graphic {
81            OxmlGraphic::OleObject(o) => Some(o),
82            _ => None,
83        }
84    }
85
86    /// 当前关联的 OLE 关系 id(指向 `/ppt/embeddings/oleObjectN.bin`)。
87    ///
88    /// 新建时为空字符串;由 `Presentation::to_opc_package` 在写出 ole part 时填充。
89    /// 不变量被破坏时返回空字符串。
90    pub fn rid(&self) -> &str {
91        match &self.frame.graphic {
92            OxmlGraphic::OleObject(o) => &o.rid,
93            _ => "",
94        }
95    }
96
97    /// 设置 OLE 关系 id(一般由 presentation 层自动调用,用户无需直接设置)。
98    /// 不变量被破坏时静默忽略。
99    pub fn set_rid(&mut self, rid: impl Into<String>) {
100        if let Some(o) = self.ole_mut() {
101            o.rid = rid.into();
102        }
103    }
104
105    /// 当前关联的图标图片关系 id(指向 `/ppt/media/imageN.{ext}`)。
106    ///
107    /// 空字符串表示无图标图片,PowerPoint 会用默认图标显示。
108    /// 不变量被破坏时返回空字符串。
109    pub fn image_rid(&self) -> &str {
110        match &self.frame.graphic {
111            OxmlGraphic::OleObject(o) => &o.image_rid,
112            _ => "",
113        }
114    }
115
116    /// 设置图标图片关系 id。不变量被破坏时静默忽略。
117    pub fn set_image_rid(&mut self, rid: impl Into<String>) {
118        if let Some(o) = self.ole_mut() {
119            o.image_rid = rid.into();
120        }
121    }
122
123    /// OLE 程序标识符(如 `"Excel.Sheet.12"`)。
124    ///
125    /// 不变量被破坏时返回 `"Package"` 作为兜底。
126    pub fn prog_id(&self) -> &str {
127        match &self.frame.graphic {
128            OxmlGraphic::OleObject(o) => &o.prog_id,
129            _ => "Package",
130        }
131    }
132
133    /// 修改 OLE 程序标识符。不变量被破坏时静默忽略。
134    pub fn set_prog_id(&mut self, prog_id: impl Into<String>) {
135        if let Some(o) = self.ole_mut() {
136            o.prog_id = prog_id.into();
137        }
138    }
139
140    /// 显示名(如 `"Worksheet"` / `"Document"`)。
141    ///
142    /// 不变量被破坏时返回空字符串。
143    pub fn ole_name(&self) -> &str {
144        match &self.frame.graphic {
145            OxmlGraphic::OleObject(o) => &o.name,
146            _ => "",
147        }
148    }
149
150    /// 修改显示名。不变量被破坏时静默忽略。
151    pub fn set_ole_name(&mut self, name: impl Into<String>) {
152        if let Some(o) = self.ole_mut() {
153            o.name = name.into();
154        }
155    }
156
157    /// **是否**以图标形式显示。
158    ///
159    /// 不变量被破坏时返回 `true`(与默认值一致)。
160    pub fn show_as_icon(&self) -> bool {
161        match &self.frame.graphic {
162            OxmlGraphic::OleObject(o) => o.show_as_icon,
163            _ => true,
164        }
165    }
166
167    /// 设置是否以图标形式显示。不变量被破坏时静默忽略。
168    pub fn set_show_as_icon(&mut self, show: bool) {
169        if let Some(o) = self.ole_mut() {
170            o.show_as_icon = show;
171        }
172    }
173
174    /// 设置图标显示尺寸(EMU)。
175    /// 不变量被破坏时静默忽略。
176    pub fn set_icon_size(&mut self, width: Emu, height: Emu) {
177        if let Some(o) = self.ole_mut() {
178            o.image_width = width;
179            o.image_height = height;
180        }
181    }
182
183    /// 设置图标 Pic 形状的 id 与 name(用于 `<p:oleObj spid="...">` 与 `<p:cNvPr name="...">`)。
184    /// 不变量被破坏时静默忽略。
185    pub fn set_pic_id_name(&mut self, id: u32, name: impl Into<String>) {
186        if let Some(o) = self.ole_mut() {
187            o.pic_id = id;
188            o.pic_name = name.into();
189        }
190    }
191
192    /// 将本 OLE 对象形状标记为占位符。
193    ///
194    /// 写出 XML 时会在 `<p:nvGraphicFramePr>/<p:nvPr>` 内插入
195    /// `<p:ph type="obj" idx="..."/>`,使 PowerPoint 把该 graphicFrame
196    /// 识别为对象占位符的填充实例。
197    ///
198    /// # 参数
199    /// - `ph_idx`:占位符索引(对应 `<p:ph idx="..."/>`)。
200    /// - `ph_type`:占位符类型字符串(如 `"obj"`),`None` 时省略 `type` 属性。
201    pub fn set_placeholder(&mut self, ph_idx: u32, ph_type: Option<&str>) {
202        self.frame.is_placeholder = true;
203        self.frame.ph_idx = Some(ph_idx);
204        self.frame.ph_type = ph_type.map(|s| s.to_string());
205    }
206
207    /// 清除占位符标记,使本 OLE 对象形状变为普通 graphicFrame。
208    pub fn clear_placeholder(&mut self) {
209        self.frame.is_placeholder = false;
210        self.frame.ph_idx = None;
211        self.frame.ph_type = None;
212    }
213
214    /// 是否被标记为占位符。
215    pub fn is_placeholder(&self) -> bool {
216        self.frame.is_placeholder
217    }
218
219    /// 占位符索引(若已标记)。
220    pub fn ph_idx(&self) -> Option<u32> {
221        self.frame.ph_idx
222    }
223
224    /// 占位符类型字符串(若已标记)。
225    pub fn ph_type(&self) -> Option<&str> {
226        self.frame.ph_type.as_deref()
227    }
228}
229
230impl Shape for OleObjectShape {
231    fn id(&self) -> u32 {
232        self.frame.id
233    }
234    fn set_id(&mut self, id: u32) {
235        self.frame.id = id;
236    }
237    fn name(&self) -> &str {
238        &self.frame.name
239    }
240    fn set_name(&mut self, name: String) {
241        self.frame.name = name;
242    }
243    fn shape_type(&self) -> &'static str {
244        "ole_object"
245    }
246
247    fn left(&self) -> Emu {
248        self.frame.properties.xfrm.off_x.unwrap_or_default()
249    }
250    fn set_left(&mut self, emu: Emu) {
251        self.frame.properties.xfrm.off_x = Some(emu);
252    }
253    fn top(&self) -> Emu {
254        self.frame.properties.xfrm.off_y.unwrap_or_default()
255    }
256    fn set_top(&mut self, emu: Emu) {
257        self.frame.properties.xfrm.off_y = Some(emu);
258    }
259    fn width(&self) -> Emu {
260        self.frame.properties.xfrm.ext_cx.unwrap_or_default()
261    }
262    fn set_width(&mut self, emu: Emu) {
263        self.frame.properties.xfrm.ext_cx = Some(emu);
264    }
265    fn height(&self) -> Emu {
266        self.frame.properties.xfrm.ext_cy.unwrap_or_default()
267    }
268    fn set_height(&mut self, emu: Emu) {
269        self.frame.properties.xfrm.ext_cy = Some(emu);
270    }
271
272    /// OLE 对象不支持旋转(OOXML 规范)。调用 [`Shape::set_rotation`] 会被忽略。
273    fn rotation(&self) -> f64 {
274        0.0
275    }
276    fn set_rotation(&mut self, _deg: f64) {}
277}
278
279#[cfg(test)]
280mod tests {
281    use super::*;
282
283    /// `new` 正确构造 OleObjectShape:progId/name 自定义,rid/image_rid 默认空。
284    #[test]
285    fn new_ole_shape_basics() {
286        let s = OleObjectShape::new("Excel.Sheet.12", "Worksheet");
287        assert_eq!(s.prog_id(), "Excel.Sheet.12");
288        assert_eq!(s.ole_name(), "Worksheet");
289        assert_eq!(s.rid(), "");
290        assert_eq!(s.image_rid(), "");
291        assert!(s.show_as_icon());
292    }
293
294    /// `set_rid` / `set_image_rid` 同步到内部 oxml OleObject。
295    #[test]
296    fn set_rids_propagate() {
297        let mut s = OleObjectShape::new("Package", "Object");
298        s.set_rid("rIdOle1");
299        s.set_image_rid("rIdImg1");
300        assert_eq!(s.rid(), "rIdOle1");
301        assert_eq!(s.image_rid(), "rIdImg1");
302    }
303
304    /// `set_show_as_icon(false)` 后 `show_as_icon()` 返回 false。
305    #[test]
306    fn set_show_as_icon() {
307        let mut s = OleObjectShape::new("Package", "Object");
308        s.set_show_as_icon(false);
309        assert!(!s.show_as_icon());
310    }
311
312    /// `set_placeholder` 标记后 `is_placeholder()` 返回 true。
313    #[test]
314    fn set_placeholder_works() {
315        let mut s = OleObjectShape::new("Package", "Object");
316        assert!(!s.is_placeholder());
317        s.set_placeholder(0, Some("obj"));
318        assert!(s.is_placeholder());
319        assert_eq!(s.ph_idx(), Some(0));
320        assert_eq!(s.ph_type(), Some("obj"));
321        s.clear_placeholder();
322        assert!(!s.is_placeholder());
323    }
324}