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 /// 所属主题 part 路径(`/ppt/theme/themeN.xml`),用于 round-trip 保真。
46 ///
47 /// 新建场景为空字符串;`from_opc` 解析 master rels 的 Theme 关系时回填。
48 pub(crate) theme_partname: String,
49 /// 所属主题关系 id(在 `slideMasterN.xml.rels` 中使用)。
50 ///
51 /// 新建场景为空字符串(写路径兜底 `rId2`);`from_opc` 解析时回填。
52 pub(crate) theme_rid: String,
53}
54
55impl SlideMasterRef {
56 /// 取出 part 路径(如 `/ppt/slideMasters/slideMaster1.xml`)。
57 pub fn partname(&self) -> &str {
58 &self.partname
59 }
60 /// 取出关系 id(如 `rIdMaster1`)。
61 pub fn rid(&self) -> &str {
62 &self.rid
63 }
64
65 /// shape 不可变快照(python-pptx `slide_master.shapes` 风格)。
66 pub fn shapes(&self) -> Vec<OxmlSp> {
67 self.oxml.borrow().shapes.clone()
68 }
69 /// shape 可变视图(返回 `RefMut`)。
70 pub fn shapes_mut(&self) -> std::cell::RefMut<'_, Vec<OxmlSp>> {
71 std::cell::RefMut::map(self.oxml.borrow_mut(), |s| &mut s.shapes)
72 }
73
74 /// 占位符列表(母版的占位符会被版式继承)。
75 pub fn placeholders(&self) -> Vec<crate::slide_layouts::Placeholder> {
76 self.oxml
77 .borrow()
78 .shapes
79 .iter()
80 .filter(|s| s.is_placeholder)
81 .map(|s| crate::slide_layouts::Placeholder {
82 idx: s.ph_idx.unwrap_or(0),
83 ph_type: s.ph_type.clone().unwrap_or_else(|| "body".to_string()),
84 name: s.name.clone(),
85 })
86 .collect()
87 }
88
89 // --------------------- 背景编辑 API(TODO-049 高阶) ---------------------
90 //
91 // 对标 python-pptx `slide_master.background`。母版背景会被所有未设置
92 // 独立背景的 slide/layout 继承(OOXML 顺序:`<p:cSld>/<p:bg>` 在 `<p:spTree>` 之前)。
93
94 /// 读取母版背景的可变引用。`None` 表示未设置独立背景。
95 pub fn background(&self) -> Option<crate::oxml::slide::SlideBackground> {
96 self.oxml.borrow().background.clone()
97 }
98
99 /// 设置母版背景。`bg = None` 等价于 [`Self::clear_background`]。
100 pub fn set_background(&self, bg: Option<crate::oxml::slide::SlideBackground>) {
101 self.oxml.borrow_mut().background = bg;
102 }
103
104 /// 设置母版背景为纯色(便捷方法)。
105 ///
106 /// 对标 python-pptx `slide_master.background.fill.solid()` +
107 /// `slide_master.background.fill.fore_color.rgb = ...`。
108 pub fn set_background_solid(&self, color: crate::oxml::color::Color) {
109 self.oxml.borrow_mut().background = Some(crate::oxml::slide::SlideBackground::solid(color));
110 }
111
112 /// 清除母版背景(让母版走默认背景)。
113 pub fn clear_background(&self) {
114 self.oxml.borrow_mut().background = None;
115 }
116
117 /// 追加一个 shape 到母版 spTree 末尾。
118 ///
119 /// 对标 python-pptx `slide_master.shapes._spTree.append(sp)`。
120 /// 调用方需自行保证 `sp.id` 在母版内唯一。
121 pub fn add_shape(&self, sp: OxmlSp) {
122 self.oxml.borrow_mut().shapes.push(sp);
123 }
124
125 /// 移除母版中指定 ID 的 shape,返回被移除的 shape。`None` 表示未找到。
126 pub fn remove_shape(&self, id: u32) -> Option<OxmlSp> {
127 let mut oxml = self.oxml.borrow_mut();
128 let pos = oxml.shapes.iter().position(|s| s.id == id)?;
129 Some(oxml.shapes.remove(pos))
130 }
131}
132
133/// 全部母版的集合。
134///
135/// 在 [`crate::presentation::Presentation`] 中由 `slide_masters` 字段持有。
136/// 至少包含 1 个默认母版(由 [`crate::presentation::Presentation::new`] 自动创建)。
137#[derive(Debug, Default, Clone)]
138pub struct SlideMasters {
139 pub(crate) items: Vec<SlideMasterRef>,
140}
141
142impl SlideMasters {
143 /// 新建一个空集合。
144 pub fn new() -> Self {
145 SlideMasters::default()
146 }
147 /// 数量。
148 pub fn len(&self) -> usize {
149 self.items.len()
150 }
151 /// 是否为空。
152 pub fn is_empty(&self) -> bool {
153 self.items.is_empty()
154 }
155 /// 遍历所有母版引用。
156 pub fn iter(&self) -> std::slice::Iter<'_, SlideMasterRef> {
157 self.items.iter()
158 }
159 /// 按索引取不可变引用。
160 pub fn get(&self, idx: usize) -> Option<&SlideMasterRef> {
161 self.items.get(idx)
162 }
163 /// 按索引取可变引用。
164 pub fn get_mut(&mut self, idx: usize) -> Option<&mut SlideMasterRef> {
165 self.items.get_mut(idx)
166 }
167
168 /// 取一个母版(克隆为不可变轻量句柄 [`SlideMaster`])。
169 ///
170 /// 当前实现是空结构体 —— 真正的母版内容编辑需要走 [`SlideMasterRef`]。
171 pub fn at(&self, idx: usize) -> Option<SlideMaster> {
172 self.items.get(idx).map(|_| SlideMaster {})
173 }
174
175 /// 追加一个母版(**仅**内存模型;`presentation::to_opc_package` 会一并写出)。
176 pub fn push(&mut self, master: SlideMasterRef) {
177 self.items.push(master);
178 }
179}
180
181/// 母版(不可变视图)。
182///
183/// 当前为占位空结构体;后续会扩展为包含主题/占位符/背景的完整模型,
184/// 类似 python-pptx 的 `SlideMaster` 类。
185#[derive(Debug, Clone)]
186pub struct SlideMaster {}