Skip to main content

easyofd_core/text/
ct_cg_transform.rs

1//! CT_CGTransform 字形坐标变换。
2
3/// 对应 Java: org.ofdrw.core.text.CT_CGTransform
4///
5/// 变换描述。当存在字形变换时,TextCode 对象中使用 CGTransform 节点
6/// 描述字符编码和字形索引之间的关系。
7/// 对应 GB/T 33190-2016 第 11.4.1 节图 66 表 48。
8#[allow(non_camel_case_types)]
9#[derive(Debug, Clone)]
10pub struct CT_CGTransform {
11    /// 在 TextCode 文本内容中的起始位置(从 0 开始)。
12    pub code_position: Option<u32>,
13    /// 参与变换的字符数量。
14    pub code_count: Option<u32>,
15    /// 对应的字形数量。
16    pub glyph_count: Option<u32>,
17    /// 字形索引列表。
18    pub glyphs: Vec<u32>,
19}
20
21impl CT_CGTransform {
22    /// 创建空的字形变换。
23    #[must_use]
24    pub fn new() -> Self {
25        Self {
26            code_position: None,
27            code_count: None,
28            glyph_count: None,
29            glyphs: Vec::new(),
30        }
31    }
32
33    /// 设置字符起始位置。
34    #[must_use]
35    pub fn code_position(mut self, pos: u32) -> Self {
36        self.code_position = Some(pos);
37        self
38    }
39
40    /// 设置字符数量。
41    #[must_use]
42    pub fn code_count(mut self, count: u32) -> Self {
43        self.code_count = Some(count);
44        self
45    }
46
47    /// 设置字形数量。
48    #[must_use]
49    pub fn glyph_count(mut self, count: u32) -> Self {
50        self.glyph_count = Some(count);
51        self
52    }
53
54    /// 设置字形索引列表。
55    #[must_use]
56    pub fn glyphs(mut self, glyphs: Vec<u32>) -> Self {
57        self.glyphs = glyphs;
58        self
59    }
60
61    /// 添加字形索引。
62    pub fn add_glyph(&mut self, glyph: u32) {
63        self.glyphs.push(glyph);
64    }
65
66    /// 获取字符起始位置。
67    #[must_use]
68    pub fn get_code_position(&self) -> Option<u32> {
69        self.code_position
70    }
71
72    /// 获取字符数量。
73    #[must_use]
74    pub fn get_code_count(&self) -> Option<u32> {
75        self.code_count
76    }
77
78    /// 获取字形数量。
79    #[must_use]
80    pub fn get_glyph_count(&self) -> Option<u32> {
81        self.glyph_count
82    }
83
84    /// 获取字形索引列表。
85    #[must_use]
86    pub fn get_glyphs(&self) -> &[u32] {
87        &self.glyphs
88    }
89
90    /// 序列化为 OFD XML 字符串。
91    #[must_use]
92    pub fn to_xml_string(&self) -> String {
93        use std::fmt::Write;
94        let mut xml = String::from("<ofd:CGTransform");
95        if let Some(cp) = self.code_position {
96            write!(xml, " CodePosition=\"{cp}\"").expect("写入内存缓冲区不会失败");
97        }
98        if let Some(cc) = self.code_count {
99            write!(xml, " CodeCount=\"{cc}\"").expect("写入内存缓冲区不会失败");
100        }
101        if let Some(gc) = self.glyph_count {
102            write!(xml, " GlyphCount=\"{gc}\"").expect("写入内存缓冲区不会失败");
103        }
104        if !self.glyphs.is_empty() {
105            xml.push_str(" Glyphs=\"");
106            for (i, g) in self.glyphs.iter().enumerate() {
107                if i > 0 {
108                    xml.push(' ');
109                }
110                write!(xml, "{g}").expect("写入内存缓冲区不会失败");
111            }
112            xml.push('"');
113        }
114        xml.push_str(" />");
115        xml
116    }
117}
118
119impl Default for CT_CGTransform {
120    fn default() -> Self {
121        Self::new()
122    }
123}
124
125#[cfg(test)]
126mod tests {
127    use super::*;
128
129    #[test]
130    fn test_ct_cg_transform_new() {
131        let t = CT_CGTransform::new();
132        assert!(t.code_position.is_none());
133        assert!(t.code_count.is_none());
134        assert!(t.glyph_count.is_none());
135        assert!(t.glyphs.is_empty());
136    }
137
138    #[test]
139    fn test_ct_cg_transform_builder() {
140        let t = CT_CGTransform::new()
141            .code_position(0)
142            .code_count(2)
143            .glyph_count(2)
144            .glyphs(vec![100, 200]);
145        assert_eq!(t.get_code_position(), Some(0));
146        assert_eq!(t.get_code_count(), Some(2));
147        assert_eq!(t.get_glyph_count(), Some(2));
148        assert_eq!(t.get_glyphs(), &[100, 200]);
149    }
150
151    #[test]
152    fn test_ct_cg_transform_add_glyph() {
153        let mut t = CT_CGTransform::new();
154        t.add_glyph(50);
155        t.add_glyph(60);
156        t.add_glyph(70);
157        assert_eq!(t.get_glyphs().len(), 3);
158    }
159
160    #[test]
161    fn test_ct_cg_transform_to_xml_minimal() {
162        let t = CT_CGTransform::new();
163        let xml = t.to_xml_string();
164        assert!(xml.contains("<ofd:CGTransform"));
165        assert!(xml.ends_with(" />"));
166    }
167
168    #[test]
169    fn test_ct_cg_transform_to_xml_full() {
170        let t = CT_CGTransform::new()
171            .code_position(3)
172            .code_count(1)
173            .glyph_count(2)
174            .glyphs(vec![101, 202]);
175        let xml = t.to_xml_string();
176        assert!(xml.contains("CodePosition=\"3\""));
177        assert!(xml.contains("CodeCount=\"1\""));
178        assert!(xml.contains("GlyphCount=\"2\""));
179        assert!(xml.contains("Glyphs=\"101 202\""));
180    }
181
182    #[test]
183    fn test_ct_cg_transform_clone_debug() {
184        let t = CT_CGTransform::new().code_position(1);
185        let t2 = t.clone();
186        assert_eq!(t2.get_code_position(), Some(1));
187        assert!(format!("{t:?}").contains("CT_CGTransform"));
188    }
189}