Skip to main content

ppt_rs/parts/
embedded_font.rs

1//! Embedded font part
2//!
3//! Represents fonts embedded in the presentation for consistent rendering.
4
5use super::base::{Part, PartType, ContentType};
6use crate::exc::PptxError;
7
8/// Font embedding type
9#[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/// Embedded font part (ppt/fonts/fontN.fntdata)
30#[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    /// Create a new embedded font part
43    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    /// Set embed type
56    pub fn embed_type(mut self, embed_type: FontEmbedType) -> Self {
57        self.embed_type = embed_type;
58        self
59    }
60
61    /// Set charset
62    pub fn charset(mut self, charset: impl Into<String>) -> Self {
63        self.charset = Some(charset.into());
64        self
65    }
66
67    /// Set pitch family
68    pub fn pitch_family(mut self, pitch_family: u8) -> Self {
69        self.pitch_family = Some(pitch_family);
70        self
71    }
72
73    /// Get font number
74    pub fn font_number(&self) -> usize {
75        self.font_number
76    }
77
78    /// Get font name
79    pub fn font_name(&self) -> &str {
80        &self.font_name
81    }
82
83    /// Get font data
84    pub fn data(&self) -> &[u8] {
85        &self.data
86    }
87
88    /// Get embed type
89    pub fn get_embed_type(&self) -> FontEmbedType {
90        self.embed_type
91    }
92
93    /// Generate font reference XML for presentation.xml
94    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 // Fonts are handled similarly to images (binary)
126    }
127
128    fn content_type(&self) -> ContentType {
129        ContentType::Xml // Actually binary font data
130    }
131
132    fn to_xml(&self) -> Result<String, PptxError> {
133        // Fonts are binary, not XML
134        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/// Font collection for managing embedded fonts
143#[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    /// Add a font
154    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    /// Add a font with specific embed type
161    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    /// Get all fonts
170    pub fn fonts(&self) -> &[EmbeddedFontPart] {
171        &self.fonts
172    }
173
174    /// Get font count
175    pub fn len(&self) -> usize {
176        self.fonts.len()
177    }
178
179    /// Check if empty
180    pub fn is_empty(&self) -> bool {
181        self.fonts.is_empty()
182    }
183
184    /// Generate embedded fonts XML for presentation.xml
185    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}