easyofd_core/composite_obj/
ct_composite.rs1#[allow(non_camel_case_types)]
12#[derive(Debug, Clone)]
13pub struct CT_Composite {
14 pub id: u32,
16 pub boundary: String,
18 pub name: Option<String>,
20 pub visible: bool,
22 pub children: Vec<CompositeChild>,
24}
25
26#[derive(Debug, Clone)]
30pub enum CompositeChild {
31 Text {
33 content: String,
35 x: f64,
37 y: f64,
39 font_size: f64,
41 },
42 Path {
44 data: String,
46 stroke_color: u32,
48 },
49 Composite(CT_Composite),
51}
52
53impl CT_Composite {
54 #[must_use]
56 pub fn new(id: u32, boundary: impl Into<String>) -> Self {
57 Self {
58 id,
59 boundary: boundary.into(),
60 name: None,
61 visible: true,
62 children: Vec::new(),
63 }
64 }
65
66 #[must_use]
68 pub fn name(mut self, name: impl Into<String>) -> Self {
69 self.name = Some(name.into());
70 self
71 }
72
73 #[must_use]
75 pub fn visible(mut self, visible: bool) -> Self {
76 self.visible = visible;
77 self
78 }
79
80 pub fn add_text(&mut self, content: impl Into<String>, x: f64, y: f64, font_size: f64) {
82 self.children.push(CompositeChild::Text {
83 content: content.into(),
84 x,
85 y,
86 font_size,
87 });
88 }
89
90 pub fn add_path(&mut self, data: impl Into<String>, stroke_color: u32) {
92 self.children.push(CompositeChild::Path {
93 data: data.into(),
94 stroke_color,
95 });
96 }
97
98 pub fn add_composite(&mut self, child: CT_Composite) {
100 self.children.push(CompositeChild::Composite(child));
101 }
102
103 #[must_use]
105 pub fn child_count(&self) -> usize {
106 self.children.len()
107 }
108
109 #[must_use]
111 pub fn to_xml_string(&self) -> String {
112 use std::fmt::Write;
113 let mut xml = format!(
114 "<ofd:CT_Composite ID=\"{}\" Boundary=\"{}\"",
115 self.id, self.boundary
116 );
117 if let Some(ref name) = self.name {
118 write!(xml, " Name=\"{name}\"").expect("写入内存缓冲区不会失败");
119 }
120 if !self.visible {
121 xml.push_str(" Visible=\"false\"");
122 }
123 xml.push_str(">\n");
124 for child in &self.children {
125 xml.push_str(&child.to_xml_string());
126 }
127 xml.push_str("</ofd:CT_Composite>\n");
128 xml
129 }
130}
131
132impl CompositeChild {
133 #[must_use]
135 pub fn to_xml_string(&self) -> String {
136 match self {
137 Self::Text {
138 content,
139 x,
140 y,
141 font_size,
142 } => {
143 format!(
144 " <ofd:TextObject X=\"{x}\" Y=\"{y}\" FontSize=\"{font_size}\">\
145 {content}</ofd:TextObject>\n"
146 )
147 }
148 Self::Path { data, stroke_color } => {
149 format!(
150 " <ofd:PathObject StrokeColor=\"{stroke_color}\">\
151 <ofd:AbbreviatedData>{data}</ofd:AbbreviatedData>\
152 </ofd:PathObject>\n"
153 )
154 }
155 Self::Composite(inner) => {
156 use std::fmt::Write;
158 let inner_xml = inner.to_xml_string();
159 let mut out = String::new();
160 for line in inner_xml.lines() {
161 writeln!(out, " {line}").expect("写入内存缓冲区不会失败");
162 }
163 out
164 }
165 }
166 }
167}
168
169#[cfg(test)]
170mod tests {
171 use super::*;
172
173 #[test]
174 fn test_ct_composite_new() {
175 let c = CT_Composite::new(1, "0 0 100 100");
176 assert_eq!(c.id, 1);
177 assert_eq!(c.boundary, "0 0 100 100");
178 assert!(c.name.is_none());
179 assert!(c.visible);
180 assert!(c.children.is_empty());
181 }
182
183 #[test]
184 fn test_ct_composite_builder() {
185 let c = CT_Composite::new(2, "10 20 50 50")
186 .name("group1")
187 .visible(false);
188 assert_eq!(c.name.as_deref(), Some("group1"));
189 assert!(!c.visible);
190 }
191
192 #[test]
193 fn test_ct_composite_add_children() {
194 let mut c = CT_Composite::new(3, "0 0 200 200");
195 c.add_text("hello", 10.0, 20.0, 12.0);
196 c.add_path("M0 0L10 10", 0x00_0000);
197 assert_eq!(c.child_count(), 2);
198 }
199
200 #[test]
201 fn test_ct_composite_nested() {
202 let inner = CT_Composite::new(4, "5 5 10 10");
203 let mut outer = CT_Composite::new(5, "0 0 100 100");
204 outer.add_composite(inner);
205 assert_eq!(outer.child_count(), 1);
206 }
207
208 #[test]
209 fn test_ct_composite_to_xml_string() {
210 let c = CT_Composite::new(10, "0 0 50 50").name("myGroup");
211 let xml = c.to_xml_string();
212 assert!(xml.contains("ID=\"10\""));
213 assert!(xml.contains("Boundary=\"0 0 50 50\""));
214 assert!(xml.contains("Name=\"myGroup\""));
215 assert!(xml.contains("<ofd:CT_Composite"));
216 assert!(xml.contains("</ofd:CT_Composite>"));
217 }
218
219 #[test]
220 fn test_ct_composite_to_xml_with_children() {
221 let mut c = CT_Composite::new(11, "0 0 100 100");
222 c.add_text("test", 1.0, 2.0, 14.0);
223 c.add_path("M0 0", 0xFF_0000);
224 let xml = c.to_xml_string();
225 assert!(xml.contains("ofd:TextObject"));
226 assert!(xml.contains("ofd:PathObject"));
227 assert!(xml.contains("test"));
228 }
229
230 #[test]
231 fn test_ct_composite_to_xml_hidden() {
232 let c = CT_Composite::new(12, "0 0 10 10").visible(false);
233 let xml = c.to_xml_string();
234 assert!(xml.contains("Visible=\"false\""));
235 }
236
237 #[test]
238 fn test_ct_composite_clone_debug() {
239 let c = CT_Composite::new(1, "0 0 1 1");
240 let c2 = c.clone();
241 assert_eq!(c2.id, 1);
242 assert!(format!("{c:?}").contains("CT_Composite"));
243 }
244}