Skip to main content

pptx_rs/
slide_masters.rs

1//! # 幻灯片母版(Slide Master)—— 高阶 API
2//!
3//! 对应 OOXML 规范中的 `<p:sldMaster>` 元素。
4//!
5//! # 概念
6//!
7//! 母版是 PowerPoint "主-版-页"三层中的最上层:
8//!
9//! ```text
10//!   SlideMaster  (母版:定义全局主题、占位符、全局背景)
11//!        ↑ 引用
12//!   SlideLayout  (版式:基于母版,扩展为不同页面模板)
13//!        ↑ 引用
14//!   Slide        (页:实际内容)
15//! ```
16//!
17//! # 当前实现范围
18//!
19//! 本模块是**极简实现**,仅暴露 partname / rid / shapes 等元数据。
20//! 完整读取/编辑母版内主题、占位符、形状等是路线图任务。
21//!
22//! 完整 API 设计可参考 python-pptx 的 `SlideMasters` / `SlideMaster` 类。
23
24use std::cell::RefCell;
25use std::rc::Rc;
26
27use crate::oxml::shape::Sp as OxmlSp;
28use crate::oxml::slidemaster::SldMaster as OxmlSldMaster;
29
30/// 单个母版引用。
31///
32/// 与 [`crate::slide_layouts::SlideLayoutRef`] 同样使用 `Rc<RefCell<OxmlSldMaster>>`
33/// 共享 oxml 模型,方便母版与版式双向引用时仍能通过编译期借用检查。
34#[derive(Debug, Clone)]
35pub struct SlideMasterRef {
36    /// 在所属 `SlideMasters` 中的索引。
37    #[allow(dead_code)]
38    pub(crate) idx: usize,
39    /// OPC part 路径(`/ppt/slideMasters/slideMasterN.xml`)。
40    pub(crate) partname: String,
41    /// 关系 id(在 `presentation.xml.rels` 中使用)。
42    pub(crate) rid: String,
43    /// 内部 oxml 模型。
44    pub(crate) oxml: Rc<RefCell<OxmlSldMaster>>,
45}
46
47impl SlideMasterRef {
48    /// 取出 part 路径(如 `/ppt/slideMasters/slideMaster1.xml`)。
49    pub fn partname(&self) -> &str {
50        &self.partname
51    }
52    /// 取出关系 id(如 `rIdMaster1`)。
53    pub fn rid(&self) -> &str {
54        &self.rid
55    }
56
57    /// shape 不可变快照(python-pptx `slide_master.shapes` 风格)。
58    pub fn shapes(&self) -> Vec<OxmlSp> {
59        self.oxml.borrow().shapes.clone()
60    }
61    /// shape 可变视图(返回 `RefMut`)。
62    pub fn shapes_mut(&self) -> std::cell::RefMut<'_, Vec<OxmlSp>> {
63        std::cell::RefMut::map(self.oxml.borrow_mut(), |s| &mut s.shapes)
64    }
65
66    /// 占位符列表(母版的占位符会被版式继承)。
67    pub fn placeholders(&self) -> Vec<crate::slide_layouts::Placeholder> {
68        self.oxml
69            .borrow()
70            .shapes
71            .iter()
72            .filter(|s| s.is_placeholder)
73            .map(|s| crate::slide_layouts::Placeholder {
74                idx: s.ph_idx.unwrap_or(0),
75                ph_type: s.ph_type.clone().unwrap_or_else(|| "body".to_string()),
76                name: s.name.clone(),
77            })
78            .collect()
79    }
80
81    // --------------------- 背景编辑 API(TODO-049 高阶) ---------------------
82    //
83    // 对标 python-pptx `slide_master.background`。母版背景会被所有未设置
84    // 独立背景的 slide/layout 继承(OOXML 顺序:`<p:cSld>/<p:bg>` 在 `<p:spTree>` 之前)。
85
86    /// 读取母版背景的可变引用。`None` 表示未设置独立背景。
87    pub fn background(&self) -> Option<crate::oxml::slide::SlideBackground> {
88        self.oxml.borrow().background.clone()
89    }
90
91    /// 设置母版背景。`bg = None` 等价于 [`Self::clear_background`]。
92    pub fn set_background(&self, bg: Option<crate::oxml::slide::SlideBackground>) {
93        self.oxml.borrow_mut().background = bg;
94    }
95
96    /// 设置母版背景为纯色(便捷方法)。
97    ///
98    /// 对标 python-pptx `slide_master.background.fill.solid()` +
99    /// `slide_master.background.fill.fore_color.rgb = ...`。
100    pub fn set_background_solid(&self, color: crate::oxml::color::Color) {
101        self.oxml.borrow_mut().background = Some(crate::oxml::slide::SlideBackground::solid(color));
102    }
103
104    /// 清除母版背景(让母版走默认背景)。
105    pub fn clear_background(&self) {
106        self.oxml.borrow_mut().background = None;
107    }
108
109    /// 追加一个 shape 到母版 spTree 末尾。
110    ///
111    /// 对标 python-pptx `slide_master.shapes._spTree.append(sp)`。
112    /// 调用方需自行保证 `sp.id` 在母版内唯一。
113    pub fn add_shape(&self, sp: OxmlSp) {
114        self.oxml.borrow_mut().shapes.push(sp);
115    }
116
117    /// 移除母版中指定 ID 的 shape,返回被移除的 shape。`None` 表示未找到。
118    pub fn remove_shape(&self, id: u32) -> Option<OxmlSp> {
119        let mut oxml = self.oxml.borrow_mut();
120        let pos = oxml.shapes.iter().position(|s| s.id == id)?;
121        Some(oxml.shapes.remove(pos))
122    }
123}
124
125/// 全部母版的集合。
126///
127/// 在 [`crate::presentation::Presentation`] 中由 `slide_masters` 字段持有。
128/// 至少包含 1 个默认母版(由 [`crate::presentation::Presentation::new`] 自动创建)。
129#[derive(Debug, Default, Clone)]
130pub struct SlideMasters {
131    pub(crate) items: Vec<SlideMasterRef>,
132}
133
134impl SlideMasters {
135    /// 新建一个空集合。
136    pub fn new() -> Self {
137        SlideMasters::default()
138    }
139    /// 数量。
140    pub fn len(&self) -> usize {
141        self.items.len()
142    }
143    /// 是否为空。
144    pub fn is_empty(&self) -> bool {
145        self.items.is_empty()
146    }
147    /// 遍历所有母版引用。
148    pub fn iter(&self) -> std::slice::Iter<'_, SlideMasterRef> {
149        self.items.iter()
150    }
151    /// 按索引取不可变引用。
152    pub fn get(&self, idx: usize) -> Option<&SlideMasterRef> {
153        self.items.get(idx)
154    }
155    /// 按索引取可变引用。
156    pub fn get_mut(&mut self, idx: usize) -> Option<&mut SlideMasterRef> {
157        self.items.get_mut(idx)
158    }
159
160    /// 取一个母版(克隆为不可变轻量句柄 [`SlideMaster`])。
161    ///
162    /// 当前实现是空结构体 —— 真正的母版内容编辑需要走 [`SlideMasterRef`]。
163    pub fn at(&self, idx: usize) -> Option<SlideMaster> {
164        self.items.get(idx).map(|_| SlideMaster {})
165    }
166
167    /// 追加一个母版(**仅**内存模型;`presentation::to_opc_package` 会一并写出)。
168    pub fn push(&mut self, master: SlideMasterRef) {
169        self.items.push(master);
170    }
171}
172
173/// 母版(不可变视图)。
174///
175/// 当前为占位空结构体;后续会扩展为包含主题/占位符/背景的完整模型,
176/// 类似 python-pptx 的 `SlideMaster` 类。
177#[derive(Debug, Clone)]
178pub struct SlideMaster {}