Skip to main content

easyofd_core/text/
ct_font.rs

1//! CT_Font 字体描述。
2
3/// 对应 Java: org.ofdrw.core.text.font.CT_Font
4///
5/// 字体描述,定义文档中使用的字体属性。
6/// 对应 GB/T 33190-2016 第 11.1 节图 58 表 44。
7#[allow(non_camel_case_types, clippy::struct_excessive_bools)]
8#[derive(Debug, Clone)]
9pub struct CT_Font {
10    /// 字体 ID。
11    pub id: u32,
12    /// 字体名称(如 "SimSun")。
13    pub font_name: String,
14    /// 字体族名称(可选)。
15    pub family_name: Option<String>,
16    /// 字符集(可选,如 "GB2312"、"Unicode")。
17    pub charset: Option<String>,
18    /// 是否斜体。
19    pub italic: bool,
20    /// 是否粗体。
21    pub bold: bool,
22    /// 是否衬线字体。
23    pub serif: bool,
24    /// 是否等宽字体。
25    pub fixed_width: bool,
26    /// 字体文件路径(可选)。
27    pub font_file: Option<String>,
28}
29
30impl CT_Font {
31    /// 创建新的字体描述。
32    #[must_use]
33    pub fn new(id: u32, font_name: impl Into<String>) -> Self {
34        Self {
35            id,
36            font_name: font_name.into(),
37            family_name: None,
38            charset: None,
39            italic: false,
40            bold: false,
41            serif: false,
42            fixed_width: false,
43            font_file: None,
44        }
45    }
46
47    /// 设置字体名称。
48    #[must_use]
49    pub fn font_name(mut self, name: impl Into<String>) -> Self {
50        self.font_name = name.into();
51        self
52    }
53
54    /// 设置字体族名称。
55    #[must_use]
56    pub fn family_name(mut self, name: impl Into<String>) -> Self {
57        self.family_name = Some(name.into());
58        self
59    }
60
61    /// 设置字符集。
62    #[must_use]
63    pub fn charset(mut self, charset: impl Into<String>) -> Self {
64        self.charset = Some(charset.into());
65        self
66    }
67
68    /// 设置斜体。
69    #[must_use]
70    pub fn italic(mut self, italic: bool) -> Self {
71        self.italic = italic;
72        self
73    }
74
75    /// 设置粗体。
76    #[must_use]
77    pub fn bold(mut self, bold: bool) -> Self {
78        self.bold = bold;
79        self
80    }
81
82    /// 设置衬线字体。
83    #[must_use]
84    pub fn serif(mut self, serif: bool) -> Self {
85        self.serif = serif;
86        self
87    }
88
89    /// 设置等宽字体。
90    #[must_use]
91    pub fn fixed_width(mut self, fixed: bool) -> Self {
92        self.fixed_width = fixed;
93        self
94    }
95
96    /// 设置字体文件路径。
97    #[must_use]
98    pub fn font_file(mut self, path: impl Into<String>) -> Self {
99        self.font_file = Some(path.into());
100        self
101    }
102
103    /// 获取字体 ID。
104    #[must_use]
105    pub fn get_id(&self) -> u32 {
106        self.id
107    }
108
109    /// 获取字体名称。
110    #[must_use]
111    pub fn get_font_name(&self) -> &str {
112        &self.font_name
113    }
114
115    /// 获取字体族名称。
116    #[must_use]
117    pub fn get_family_name(&self) -> Option<&str> {
118        self.family_name.as_deref()
119    }
120
121    /// 获取字符集。
122    #[must_use]
123    pub fn get_charset(&self) -> Option<&str> {
124        self.charset.as_deref()
125    }
126
127    /// 是否斜体。
128    #[must_use]
129    pub fn is_italic(&self) -> bool {
130        self.italic
131    }
132
133    /// 是否粗体。
134    #[must_use]
135    pub fn is_bold(&self) -> bool {
136        self.bold
137    }
138
139    /// 是否衬线字体。
140    #[must_use]
141    pub fn is_serif(&self) -> bool {
142        self.serif
143    }
144
145    /// 是否等宽字体。
146    #[must_use]
147    pub fn is_fixed_width(&self) -> bool {
148        self.fixed_width
149    }
150
151    /// 序列化为 OFD XML 字符串。
152    #[must_use]
153    pub fn to_xml_string(&self) -> String {
154        use std::fmt::Write;
155        let mut xml = format!(
156            "<ofd:Font ID=\"{}\" FontName=\"{}\"",
157            self.id, self.font_name
158        );
159        if let Some(ref fn_) = self.family_name {
160            write!(xml, " FamilyName=\"{fn_}\"").expect("写入内存缓冲区不会失败");
161        }
162        if let Some(ref cs) = self.charset {
163            write!(xml, " Charset=\"{cs}\"").expect("写入内存缓冲区不会失败");
164        }
165        if self.italic {
166            xml.push_str(" Italic=\"true\"");
167        }
168        if self.bold {
169            xml.push_str(" Bold=\"true\"");
170        }
171        if self.serif {
172            xml.push_str(" Serif=\"true\"");
173        }
174        if self.fixed_width {
175            xml.push_str(" FixedWidth=\"true\"");
176        }
177        if let Some(ref ff) = self.font_file {
178            write!(xml, " FontFile=\"{ff}\"").expect("写入内存缓冲区不会失败");
179        }
180        xml.push_str(" />");
181        xml
182    }
183}
184
185#[cfg(test)]
186mod tests {
187    use super::*;
188
189    #[test]
190    fn test_ct_font_new() {
191        let font = CT_Font::new(1, "SimSun");
192        assert_eq!(font.id, 1);
193        assert_eq!(font.font_name, "SimSun");
194        assert!(!font.italic);
195        assert!(!font.bold);
196        assert!(!font.serif);
197        assert!(!font.fixed_width);
198        assert!(font.family_name.is_none());
199        assert!(font.font_file.is_none());
200    }
201
202    #[test]
203    fn test_ct_font_builder() {
204        let font = CT_Font::new(2, "SimHei")
205            .family_name("Sans-Serif")
206            .charset("Unicode")
207            .italic(true)
208            .bold(true)
209            .serif(false)
210            .fixed_width(true)
211            .font_file("simhei.ttf");
212        assert_eq!(font.get_font_name(), "SimHei");
213        assert_eq!(font.get_family_name(), Some("Sans-Serif"));
214        assert_eq!(font.get_charset(), Some("Unicode"));
215        assert!(font.is_italic());
216        assert!(font.is_bold());
217        assert!(!font.is_serif());
218        assert!(font.is_fixed_width());
219        assert_eq!(font.font_file.as_deref(), Some("simhei.ttf"));
220    }
221
222    #[test]
223    fn test_ct_font_to_xml_minimal() {
224        let font = CT_Font::new(1, "SimSun");
225        let xml = font.to_xml_string();
226        assert!(xml.contains("ID=\"1\""));
227        assert!(xml.contains("FontName=\"SimSun\""));
228        assert!(xml.contains("<ofd:Font"));
229        assert!(xml.ends_with(" />"));
230    }
231
232    #[test]
233    fn test_ct_font_to_xml_full() {
234        let font = CT_Font::new(5, "Arial")
235            .family_name("Sans-Serif")
236            .charset("Unicode")
237            .bold(true)
238            .italic(true)
239            .serif(true)
240            .fixed_width(true)
241            .font_file("arial.ttf");
242        let xml = font.to_xml_string();
243        assert!(xml.contains("ID=\"5\""));
244        assert!(xml.contains("FontName=\"Arial\""));
245        assert!(xml.contains("FamilyName=\"Sans-Serif\""));
246        assert!(xml.contains("Charset=\"Unicode\""));
247        assert!(xml.contains("Bold=\"true\""));
248        assert!(xml.contains("Italic=\"true\""));
249        assert!(xml.contains("Serif=\"true\""));
250        assert!(xml.contains("FixedWidth=\"true\""));
251        assert!(xml.contains("FontFile=\"arial.ttf\""));
252    }
253
254    #[test]
255    fn test_ct_font_clone_debug() {
256        let font = CT_Font::new(1, "x");
257        let font2 = font.clone();
258        assert_eq!(font2.get_font_name(), "x");
259        assert!(format!("{font:?}").contains("CT_Font"));
260    }
261}