1pub trait ToXml {
8 fn to_xml(&self) -> String;
10
11 fn write_xml(&self, writer: &mut String) {
13 writer.push_str(&self.to_xml());
14 }
15}
16
17pub trait XmlElement: ToXml {
19 fn tag_name(&self) -> &'static str;
21
22 fn namespace_prefix(&self) -> &'static str {
24 ""
25 }
26
27 fn qualified_name(&self) -> String {
29 let prefix = self.namespace_prefix();
30 if prefix.is_empty() {
31 self.tag_name().to_string()
32 } else {
33 format!("{}:{}", prefix, self.tag_name())
34 }
35 }
36}
37
38pub trait Positioned {
40 fn x(&self) -> u32;
42
43 fn y(&self) -> u32;
45
46 fn set_position(&mut self, x: u32, y: u32);
48}
49
50pub trait Sized {
52 fn width(&self) -> u32;
54
55 fn height(&self) -> u32;
57
58 fn set_size(&mut self, width: u32, height: u32);
60}
61
62pub trait Styled {
64 fn color(&self) -> Option<&str>;
66
67 fn set_color(&mut self, color: &str);
69}
70
71#[derive(Clone, Debug, PartialEq, Eq)]
73#[allow(dead_code)]
74pub struct RgbColor {
75 pub r: u8,
76 pub g: u8,
77 pub b: u8,
78}
79
80#[allow(dead_code)]
81impl RgbColor {
82 pub fn new(r: u8, g: u8, b: u8) -> Self {
83 Self { r, g, b }
84 }
85
86 pub fn from_hex(hex: &str) -> Option<Self> {
88 let hex = hex.trim_start_matches('#');
89 if hex.len() != 6 {
90 return None;
91 }
92 let r = u8::from_str_radix(&hex[0..2], 16).ok()?;
93 let g = u8::from_str_radix(&hex[2..4], 16).ok()?;
94 let b = u8::from_str_radix(&hex[4..6], 16).ok()?;
95 Some(Self { r, g, b })
96 }
97
98 pub fn to_hex(&self) -> String {
100 format!("{:02X}{:02X}{:02X}", self.r, self.g, self.b)
101 }
102}
103
104impl ToXml for RgbColor {
105 fn to_xml(&self) -> String {
106 format!(r#"<a:srgbClr val="{}"/>"#, self.to_hex())
107 }
108}
109
110#[cfg(test)]
111mod tests {
112 use super::*;
113
114 #[test]
115 fn test_rgb_color_from_hex() {
116 let color = RgbColor::from_hex("FF0000").unwrap();
117 assert_eq!(color.r, 255);
118 assert_eq!(color.g, 0);
119 assert_eq!(color.b, 0);
120
121 let color = RgbColor::from_hex("#00FF00").unwrap();
122 assert_eq!(color.to_hex(), "00FF00");
123 }
124
125 #[test]
126 fn test_rgb_color_to_xml() {
127 let color = RgbColor::new(255, 0, 0);
128 assert_eq!(color.to_xml(), r#"<a:srgbClr val="FF0000"/>"#);
129 }
130
131 #[test]
133 fn test_to_xml_trait_dispatch() {
134 use crate::generator::text::{Run, Paragraph, TextFrame};
135
136 let items: Vec<Box<dyn ToXml>> = vec![
137 Box::new(Run::new("hello")),
138 Box::new(Paragraph::with_text("world")),
139 Box::new(TextFrame::with_text("frame")),
140 Box::new(RgbColor::new(255, 0, 0)),
141 ];
142
143 for item in &items {
144 let xml = item.to_xml();
145 assert!(!xml.is_empty(), "ToXml dispatch should produce non-empty XML");
146 }
147
148 assert!(items[0].to_xml().contains("hello"));
149 assert!(items[1].to_xml().contains("world"));
150 assert!(items[2].to_xml().contains("frame"));
151 assert!(items[3].to_xml().contains("FF0000"));
152 }
153
154 #[test]
156 fn test_positioned_trait_dispatch() {
157 use crate::generator::shapes::{Shape, ShapeType};
158 use crate::generator::images::Image;
159
160 fn move_element(elem: &mut dyn Positioned, x: u32, y: u32) {
161 elem.set_position(x, y);
162 }
163
164 let mut shape = Shape::new(ShapeType::Rectangle, 0, 0, 1000, 1000);
165 let mut image = Image::new("test.png", 500, 500, "PNG");
166
167 move_element(&mut shape, 100, 200);
168 move_element(&mut image, 300, 400);
169
170 assert_eq!(shape.x(), 100);
171 assert_eq!(shape.y(), 200);
172 assert_eq!(image.x(), 300);
173 assert_eq!(image.y(), 400);
174 }
175
176 #[test]
178 fn test_element_sized_trait_dispatch() {
179 use crate::generator::shapes::{Shape, ShapeType};
180 use crate::generator::images::Image;
181
182 fn resize(elem: &mut dyn Sized, w: u32, h: u32) {
183 elem.set_size(w, h);
184 }
185
186 let mut shape = Shape::new(ShapeType::Rectangle, 0, 0, 1000, 1000);
187 let mut image = Image::new("test.png", 500, 500, "PNG");
188
189 resize(&mut shape, 2000, 3000);
190 resize(&mut image, 4000, 5000);
191
192 assert_eq!(shape.width(), 2000);
193 assert_eq!(shape.height(), 3000);
194 assert_eq!(image.width(), 4000);
195 assert_eq!(image.height(), 5000);
196 }
197}