pptx_rs/notes_masters.rs
1//! # 备注母版(Notes Master)—— 高阶 API(TODO-045)。
2//!
3//! 对应 OOXML 规范中的 `<p:notesMaster>` 元素,以及 python-pptx 中
4//! `NotesMaster` / `NotesMasterPart`。
5//!
6//! # 概念
7//!
8//! 备注母版是所有备注页(`<p:notesSlide>`)的"模板"——
9//! 它定义了备注页的默认占位符(日期 / 页码 / 正文 / 幻灯片图像)与文本样式。
10//! 一个演示文稿通常只有 1 个备注母版。
11//!
12//! ```text
13//! NotesMaster (备注母版:定义备注页的默认布局)
14//! ↑ 引用
15//! NotesSlide (备注页:每张 slide 的演讲者备注)
16//! ```
17//!
18//! # 当前实现范围
19//!
20//! 本模块是**只读访问**——支持从已存在 `.pptx` 解析出备注母版并查询其形状。
21//! 写路径(创建/修改备注母版并持久化)暂未实现,与 python-pptx 行为一致
22//! (python-pptx 也仅暴露 `presentation.notes_master` 的只读访问)。
23//!
24//! # 与 python-pptx 的对应
25//!
26//! - `pptx.NotesMaster` ←→ [`NotesMasterRef`];
27//! - `presentation.notes_master` ←→ [`crate::presentation::Presentation::notes_master`]。
28
29use std::cell::RefCell;
30use std::rc::Rc;
31
32use crate::oxml::notesmaster::NotesMaster as OxmlNotesMaster;
33use crate::oxml::shape::Sp as OxmlSp;
34
35/// 单个备注母版引用(高阶句柄)。
36///
37/// 与 [`crate::slide_masters::SlideMasterRef`] 同样使用 `Rc<RefCell<OxmlNotesMaster>>`
38/// 共享 oxml 模型,方便在多视图间共享且通过编译期借用检查。
39#[derive(Debug, Clone)]
40pub struct NotesMasterRef {
41 /// 在所属 [`NotesMasters`] 中的索引。
42 #[allow(dead_code)]
43 pub(crate) idx: usize,
44 /// OPC part 路径(`/ppt/notesMasters/notesMasterN.xml`)。
45 pub(crate) partname: String,
46 /// 关系 id(在 `presentation.xml.rels` 中使用)。
47 pub(crate) rid: String,
48 /// 内部 oxml 模型。
49 pub(crate) oxml: Rc<RefCell<OxmlNotesMaster>>,
50}
51
52impl NotesMasterRef {
53 /// 取出 part 路径(如 `/ppt/notesMasters/notesMaster1.xml`)。
54 pub fn partname(&self) -> &str {
55 &self.partname
56 }
57 /// 取出关系 id(如 `rIdNotesMaster1`)。
58 pub fn rid(&self) -> &str {
59 &self.rid
60 }
61
62 /// shape 不可变快照(python-pptx `notes_master.shapes` 风格)。
63 ///
64 /// 返回克隆后的形状列表,调用方可自由操作而不影响母版内部状态。
65 pub fn shapes(&self) -> Vec<OxmlSp> {
66 self.oxml.borrow().shapes.clone()
67 }
68
69 /// shape 可变视图(返回 `RefMut`)。
70 ///
71 /// 通过 `RefMut` 可以原地修改母版内的形状;修改不会自动持久化到
72 /// `.pptx` 文件(写路径暂未实现)。
73 pub fn shapes_mut(&self) -> std::cell::RefMut<'_, Vec<OxmlSp>> {
74 std::cell::RefMut::map(self.oxml.borrow_mut(), |m| &mut m.shapes)
75 }
76
77 /// 备注母版中的形状数量。
78 pub fn len(&self) -> usize {
79 self.oxml.borrow().shapes.len()
80 }
81
82 /// 是否无形状。
83 pub fn is_empty(&self) -> bool {
84 self.oxml.borrow().shapes.is_empty()
85 }
86
87 /// 读取备注母版背景。`None` 表示未设置独立背景。
88 pub fn background(&self) -> Option<crate::oxml::slide::SlideBackground> {
89 self.oxml.borrow().background.clone()
90 }
91}
92
93/// 全部备注母版的集合。
94///
95/// 在 [`crate::presentation::Presentation`] 中由 `notes_masters` 字段持有。
96/// 一个演示文稿通常只有 0 或 1 个备注母版。
97#[derive(Debug, Default, Clone)]
98pub struct NotesMasters {
99 pub(crate) items: Vec<NotesMasterRef>,
100}
101
102impl NotesMasters {
103 /// 新建一个空集合。
104 pub fn new() -> Self {
105 NotesMasters::default()
106 }
107 /// 数量。
108 pub fn len(&self) -> usize {
109 self.items.len()
110 }
111 /// 是否为空。
112 pub fn is_empty(&self) -> bool {
113 self.items.is_empty()
114 }
115 /// 遍历所有备注母版引用。
116 pub fn iter(&self) -> std::slice::Iter<'_, NotesMasterRef> {
117 self.items.iter()
118 }
119 /// 按索引取不可变引用。
120 pub fn get(&self, idx: usize) -> Option<&NotesMasterRef> {
121 self.items.get(idx)
122 }
123
124 /// 取第一个备注母版(演示文稿通常只有 1 个)。
125 ///
126 /// `None` 表示该演示文稿无备注母版(`Presentation::new` 创建的空白文档无此 part)。
127 pub fn first(&self) -> Option<&NotesMasterRef> {
128 self.items.first()
129 }
130
131 /// 追加一个备注母版(仅内存模型;写路径暂未实现)。
132 pub fn push(&mut self, master: NotesMasterRef) {
133 self.items.push(master);
134 }
135}