1#[allow(non_camel_case_types)]
9#[derive(Debug, Clone)]
10pub struct CT_GraphicUnit {
11 pub id: u32,
13 pub boundary: String,
15 pub name: Option<String>,
17 pub visible: bool,
19 pub ctm: Option<[f64; 6]>,
21 pub draw_param: Option<u32>,
23 pub line_width: Option<f64>,
25 pub cap: Option<LineCapType>,
27 pub join: Option<LineJoinType>,
29 pub miter_limit: Option<f64>,
31 pub dash_offset: Option<f64>,
33 pub dash_pattern: Option<String>,
35 pub alpha: Option<u8>,
37}
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41pub enum LineCapType {
42 Butt,
44 Round,
46 Square,
48}
49
50impl LineCapType {
51 #[must_use]
53 pub fn as_str(&self) -> &'static str {
54 match self {
55 Self::Butt => "Butt",
56 Self::Round => "Round",
57 Self::Square => "Square",
58 }
59 }
60}
61
62impl std::fmt::Display for LineCapType {
63 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
64 f.write_str(self.as_str())
65 }
66}
67
68#[derive(Debug, Clone, Copy, PartialEq, Eq)]
70pub enum LineJoinType {
71 Miter,
73 Round,
75 Bevel,
77}
78
79impl LineJoinType {
80 #[must_use]
82 pub fn as_str(&self) -> &'static str {
83 match self {
84 Self::Miter => "Miter",
85 Self::Round => "Round",
86 Self::Bevel => "Bevel",
87 }
88 }
89}
90
91impl std::fmt::Display for LineJoinType {
92 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
93 f.write_str(self.as_str())
94 }
95}
96
97impl CT_GraphicUnit {
98 #[must_use]
100 pub fn new(id: u32, boundary: impl Into<String>) -> Self {
101 Self {
102 id,
103 boundary: boundary.into(),
104 name: None,
105 visible: true,
106 ctm: None,
107 draw_param: None,
108 line_width: None,
109 cap: None,
110 join: None,
111 miter_limit: None,
112 dash_offset: None,
113 dash_pattern: None,
114 alpha: None,
115 }
116 }
117
118 #[must_use]
120 pub fn name(mut self, name: impl Into<String>) -> Self {
121 self.name = Some(name.into());
122 self
123 }
124
125 #[must_use]
127 pub fn visible(mut self, visible: bool) -> Self {
128 self.visible = visible;
129 self
130 }
131
132 #[must_use]
134 pub fn ctm(mut self, ctm: [f64; 6]) -> Self {
135 self.ctm = Some(ctm);
136 self
137 }
138
139 #[must_use]
141 pub fn draw_param(mut self, id: u32) -> Self {
142 self.draw_param = Some(id);
143 self
144 }
145
146 #[must_use]
148 pub fn line_width(mut self, width: f64) -> Self {
149 self.line_width = Some(width);
150 self
151 }
152
153 #[must_use]
155 pub fn cap(mut self, cap: LineCapType) -> Self {
156 self.cap = Some(cap);
157 self
158 }
159
160 #[must_use]
162 pub fn join(mut self, join: LineJoinType) -> Self {
163 self.join = Some(join);
164 self
165 }
166
167 #[must_use]
169 pub fn miter_limit(mut self, limit: f64) -> Self {
170 self.miter_limit = Some(limit);
171 self
172 }
173
174 #[must_use]
176 pub fn dash_offset(mut self, offset: f64) -> Self {
177 self.dash_offset = Some(offset);
178 self
179 }
180
181 #[must_use]
183 pub fn dash_pattern(mut self, pattern: impl Into<String>) -> Self {
184 self.dash_pattern = Some(pattern.into());
185 self
186 }
187
188 #[must_use]
190 pub fn alpha(mut self, alpha: u8) -> Self {
191 self.alpha = Some(alpha);
192 self
193 }
194
195 #[must_use]
197 pub fn get_id(&self) -> u32 {
198 self.id
199 }
200
201 #[must_use]
203 pub fn get_boundary(&self) -> &str {
204 &self.boundary
205 }
206
207 #[must_use]
209 pub fn get_name(&self) -> Option<&str> {
210 self.name.as_deref()
211 }
212
213 #[must_use]
215 pub fn get_visible(&self) -> bool {
216 self.visible
217 }
218
219 #[must_use]
221 pub fn get_ctm(&self) -> Option<[f64; 6]> {
222 self.ctm
223 }
224
225 #[must_use]
227 pub fn get_alpha(&self) -> Option<u8> {
228 self.alpha
229 }
230
231 #[must_use]
233 pub fn to_xml_string(&self) -> String {
234 use std::fmt::Write;
235 let mut xml = format!(
236 "<ofd:CT_GraphicUnit ID=\"{}\" Boundary=\"{}\"",
237 self.id, self.boundary
238 );
239 if let Some(ref name) = self.name {
240 write!(xml, " Name=\"{name}\"").expect("写入内存缓冲区不会失败");
241 }
242 if !self.visible {
243 xml.push_str(" Visible=\"false\"");
244 }
245 if let Some(ctm) = self.ctm {
246 write!(
247 xml,
248 " CTM=\"{} {} {} {} {} {}\"",
249 ctm[0], ctm[1], ctm[2], ctm[3], ctm[4], ctm[5]
250 )
251 .expect("写入内存缓冲区不会失败");
252 }
253 if let Some(dp) = self.draw_param {
254 write!(xml, " DrawParam=\"{dp}\"").expect("写入内存缓冲区不会失败");
255 }
256 if let Some(lw) = self.line_width {
257 write!(xml, " LineWidth=\"{lw}\"").expect("写入内存缓冲区不会失败");
258 }
259 if let Some(cap) = self.cap {
260 write!(xml, " Cap=\"{}\"", cap.as_str()).expect("写入内存缓冲区不会失败");
261 }
262 if let Some(join) = self.join {
263 write!(xml, " Join=\"{}\"", join.as_str()).expect("写入内存缓冲区不会失败");
264 }
265 if let Some(ml) = self.miter_limit {
266 write!(xml, " MiterLimit=\"{ml}\"").expect("写入内存缓冲区不会失败");
267 }
268 if let Some(doff) = self.dash_offset {
269 write!(xml, " DashOffset=\"{doff}\"").expect("写入内存缓冲区不会失败");
270 }
271 if let Some(ref dp) = self.dash_pattern {
272 write!(xml, " DashPattern=\"{dp}\"").expect("写入内存缓冲区不会失败");
273 }
274 if let Some(a) = self.alpha {
275 write!(xml, " Alpha=\"{a}\"").expect("写入内存缓冲区不会失败");
276 }
277 xml.push_str(" />");
278 xml
279 }
280}
281
282#[cfg(test)]
283mod tests {
284 use super::*;
285
286 #[test]
287 fn test_ct_graphic_unit_new() {
288 let gu = CT_GraphicUnit::new(1, "0 0 100 50");
289 assert_eq!(gu.id, 1);
290 assert_eq!(gu.boundary, "0 0 100 50");
291 assert!(gu.name.is_none());
292 assert!(gu.visible);
293 assert!(gu.ctm.is_none());
294 assert!(gu.alpha.is_none());
295 }
296
297 #[test]
298 fn test_ct_graphic_unit_builder_chaining() {
299 let gu = CT_GraphicUnit::new(2, "10 20 50 50")
300 .name("rect1")
301 .visible(false)
302 .line_width(1.5)
303 .cap(LineCapType::Round)
304 .join(LineJoinType::Bevel)
305 .alpha(128);
306 assert_eq!(gu.get_name(), Some("rect1"));
307 assert!(!gu.get_visible());
308 assert!((gu.line_width.unwrap() - 1.5).abs() < f64::EPSILON);
309 assert_eq!(gu.cap, Some(LineCapType::Round));
310 assert_eq!(gu.join, Some(LineJoinType::Bevel));
311 assert_eq!(gu.get_alpha(), Some(128));
312 }
313
314 #[test]
315 fn test_ct_graphic_unit_ctm() {
316 let gu = CT_GraphicUnit::new(3, "0 0 10 10").ctm([1.0, 0.0, 0.0, 1.0, 5.0, 5.0]);
317 let ctm = gu.get_ctm().unwrap();
318 assert!((ctm[4] - 5.0).abs() < f64::EPSILON);
319 }
320
321 #[test]
322 fn test_line_cap_type_display() {
323 assert_eq!(LineCapType::Butt.to_string(), "Butt");
324 assert_eq!(LineCapType::Round.to_string(), "Round");
325 assert_eq!(LineCapType::Square.to_string(), "Square");
326 }
327
328 #[test]
329 fn test_line_join_type_display() {
330 assert_eq!(LineJoinType::Miter.to_string(), "Miter");
331 assert_eq!(LineJoinType::Round.to_string(), "Round");
332 assert_eq!(LineJoinType::Bevel.to_string(), "Bevel");
333 }
334
335 #[test]
336 fn test_ct_graphic_unit_to_xml_minimal() {
337 let gu = CT_GraphicUnit::new(1, "0 0 100 50");
338 let xml = gu.to_xml_string();
339 assert!(xml.contains("ID=\"1\""));
340 assert!(xml.contains("Boundary=\"0 0 100 50\""));
341 assert!(xml.contains("<ofd:CT_GraphicUnit"));
342 assert!(xml.ends_with(" />"));
343 }
344
345 #[test]
346 fn test_ct_graphic_unit_to_xml_full() {
347 let gu = CT_GraphicUnit::new(5, "0 0 200 100")
348 .name("myUnit")
349 .visible(false)
350 .ctm([2.0, 0.0, 0.0, 2.0, 10.0, 20.0])
351 .line_width(0.5)
352 .cap(LineCapType::Square)
353 .join(LineJoinType::Miter)
354 .miter_limit(4.0)
355 .dash_offset(1.0)
356 .dash_pattern("4 2")
357 .alpha(200);
358 let xml = gu.to_xml_string();
359 assert!(xml.contains("Name=\"myUnit\""));
360 assert!(xml.contains("Visible=\"false\""));
361 assert!(xml.contains("CTM=\"2 0 0 2 10 20\""));
362 assert!(xml.contains("LineWidth=\"0.5\""));
363 assert!(xml.contains("Cap=\"Square\""));
364 assert!(xml.contains("Join=\"Miter\""));
365 assert!(xml.contains("MiterLimit=\"4\""));
366 assert!(xml.contains("DashOffset=\"1\""));
367 assert!(xml.contains("DashPattern=\"4 2\""));
368 assert!(xml.contains("Alpha=\"200\""));
369 }
370
371 #[test]
372 fn test_ct_graphic_unit_clone_debug() {
373 let gu = CT_GraphicUnit::new(1, "0 0 1 1");
374 let gu2 = gu.clone();
375 assert_eq!(gu2.id, 1);
376 assert!(format!("{gu:?}").contains("CT_GraphicUnit"));
377 }
378}