ppt_rs/parts/
embedded_font.rs1use super::base::{Part, PartType, ContentType};
6use crate::exc::PptxError;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
10pub enum FontEmbedType {
11 #[default]
12 Regular,
13 Bold,
14 Italic,
15 BoldItalic,
16}
17
18impl FontEmbedType {
19 pub fn as_str(&self) -> &'static str {
20 match self {
21 FontEmbedType::Regular => "regular",
22 FontEmbedType::Bold => "bold",
23 FontEmbedType::Italic => "italic",
24 FontEmbedType::BoldItalic => "boldItalic",
25 }
26 }
27}
28
29#[derive(Debug, Clone)]
31pub struct EmbeddedFontPart {
32 path: String,
33 font_number: usize,
34 font_name: String,
35 embed_type: FontEmbedType,
36 data: Vec<u8>,
37 charset: Option<String>,
38 pitch_family: Option<u8>,
39}
40
41impl EmbeddedFontPart {
42 pub fn new(font_number: usize, font_name: impl Into<String>, data: Vec<u8>) -> Self {
44 EmbeddedFontPart {
45 path: format!("ppt/fonts/font{}.fntdata", font_number),
46 font_number,
47 font_name: font_name.into(),
48 embed_type: FontEmbedType::default(),
49 data,
50 charset: None,
51 pitch_family: None,
52 }
53 }
54
55 pub fn embed_type(mut self, embed_type: FontEmbedType) -> Self {
57 self.embed_type = embed_type;
58 self
59 }
60
61 pub fn charset(mut self, charset: impl Into<String>) -> Self {
63 self.charset = Some(charset.into());
64 self
65 }
66
67 pub fn pitch_family(mut self, pitch_family: u8) -> Self {
69 self.pitch_family = Some(pitch_family);
70 self
71 }
72
73 pub fn font_number(&self) -> usize {
75 self.font_number
76 }
77
78 pub fn font_name(&self) -> &str {
80 &self.font_name
81 }
82
83 pub fn data(&self) -> &[u8] {
85 &self.data
86 }
87
88 pub fn get_embed_type(&self) -> FontEmbedType {
90 self.embed_type
91 }
92
93 pub fn to_font_ref_xml(&self) -> String {
95 let charset_attr = self.charset.as_ref()
96 .map(|c| format!(r#" charset="{}""#, c))
97 .unwrap_or_default();
98 let pitch_attr = self.pitch_family
99 .map(|p| format!(r#" pitchFamily="{}""#, p))
100 .unwrap_or_default();
101
102 format!(
103 r#"<p:embeddedFont>
104 <p:font typeface="{}"{}{}>
105 <p:{}/>
106 </p:font>
107 <p:{}><a:extLst><a:ext uri="{{28A0092B-C50C-407E-A947-70E740481C1C}}"><a14:useLocalDpi xmlns:a14="http://schemas.microsoft.com/office/drawing/2010/main" val="0"/></a:ext></a:extLst></p:{}>
108</p:embeddedFont>"#,
109 self.font_name,
110 charset_attr,
111 pitch_attr,
112 self.embed_type.as_str(),
113 self.embed_type.as_str(),
114 self.embed_type.as_str()
115 )
116 }
117}
118
119impl Part for EmbeddedFontPart {
120 fn path(&self) -> &str {
121 &self.path
122 }
123
124 fn part_type(&self) -> PartType {
125 PartType::Image }
127
128 fn content_type(&self) -> ContentType {
129 ContentType::Xml }
131
132 fn to_xml(&self) -> Result<String, PptxError> {
133 Err(PptxError::InvalidOperation("Embedded fonts are binary, not XML".to_string()))
135 }
136
137 fn from_xml(_xml: &str) -> Result<Self, PptxError> {
138 Err(PptxError::InvalidOperation("Embedded fonts cannot be created from XML".to_string()))
139 }
140}
141
142#[derive(Debug, Clone, Default)]
144pub struct EmbeddedFontCollection {
145 fonts: Vec<EmbeddedFontPart>,
146}
147
148impl EmbeddedFontCollection {
149 pub fn new() -> Self {
150 EmbeddedFontCollection::default()
151 }
152
153 pub fn add(&mut self, font_name: impl Into<String>, data: Vec<u8>) -> &mut EmbeddedFontPart {
155 let font_number = self.fonts.len() + 1;
156 self.fonts.push(EmbeddedFontPart::new(font_number, font_name, data));
157 self.fonts.last_mut().unwrap()
158 }
159
160 pub fn add_with_type(&mut self, font_name: impl Into<String>, data: Vec<u8>, embed_type: FontEmbedType) -> &mut EmbeddedFontPart {
162 let font_number = self.fonts.len() + 1;
163 let mut font = EmbeddedFontPart::new(font_number, font_name, data);
164 font.embed_type = embed_type;
165 self.fonts.push(font);
166 self.fonts.last_mut().unwrap()
167 }
168
169 pub fn fonts(&self) -> &[EmbeddedFontPart] {
171 &self.fonts
172 }
173
174 pub fn len(&self) -> usize {
176 self.fonts.len()
177 }
178
179 pub fn is_empty(&self) -> bool {
181 self.fonts.is_empty()
182 }
183
184 pub fn to_xml(&self) -> String {
186 if self.fonts.is_empty() {
187 return String::new();
188 }
189
190 let fonts_xml: String = self.fonts.iter()
191 .map(|f| f.to_font_ref_xml())
192 .collect::<Vec<_>>()
193 .join("\n");
194
195 format!("<p:embeddedFontLst>\n{}\n</p:embeddedFontLst>", fonts_xml)
196 }
197}
198
199#[cfg(test)]
200mod tests {
201 use super::*;
202
203 #[test]
204 fn test_embedded_font_new() {
205 let font = EmbeddedFontPart::new(1, "Arial", vec![0, 1, 2]);
206 assert_eq!(font.font_number(), 1);
207 assert_eq!(font.font_name(), "Arial");
208 assert_eq!(font.path(), "ppt/fonts/font1.fntdata");
209 }
210
211 #[test]
212 fn test_embedded_font_builder() {
213 let font = EmbeddedFontPart::new(1, "Times New Roman", vec![])
214 .embed_type(FontEmbedType::Bold)
215 .charset("00")
216 .pitch_family(18);
217 assert_eq!(font.get_embed_type(), FontEmbedType::Bold);
218 }
219
220 #[test]
221 fn test_font_embed_type() {
222 assert_eq!(FontEmbedType::Regular.as_str(), "regular");
223 assert_eq!(FontEmbedType::BoldItalic.as_str(), "boldItalic");
224 }
225
226 #[test]
227 fn test_font_collection() {
228 let mut collection = EmbeddedFontCollection::new();
229 collection.add("Arial", vec![0, 1, 2]);
230 collection.add("Times New Roman", vec![3, 4, 5]);
231 assert_eq!(collection.len(), 2);
232 }
233
234 #[test]
235 fn test_font_collection_to_xml() {
236 let mut collection = EmbeddedFontCollection::new();
237 collection.add("Arial", vec![]);
238 let xml = collection.to_xml();
239 assert!(xml.contains("p:embeddedFontLst"));
240 assert!(xml.contains("Arial"));
241 }
242}