easyofd_core/composite_obj/
content.rs1#[derive(Debug, Clone)]
12pub enum Content {
13 Path(PathContent),
15 Text(TextContent),
17 Image(ImageContent),
19}
20
21#[derive(Debug, Clone)]
23pub struct PathContent {
24 pub data: String,
26 pub stroke_color: u32,
28 pub line_width: f64,
30 pub fill_color: Option<u32>,
32}
33
34#[derive(Debug, Clone)]
36pub struct TextContent {
37 pub text: String,
39 pub x: f64,
41 pub y: f64,
43 pub font_size: f64,
45 pub font: String,
47 pub color: u32,
49}
50
51#[derive(Debug, Clone)]
53pub struct ImageContent {
54 pub data: Vec<u8>,
56 pub x: f64,
58 pub y: f64,
60 pub width: f64,
62 pub height: f64,
64}
65
66impl Content {
67 #[must_use]
69 pub fn path(data: impl Into<String>) -> Self {
70 Self::Path(PathContent {
71 data: data.into(),
72 stroke_color: 0x00_0000,
73 line_width: 0.35,
74 fill_color: None,
75 })
76 }
77
78 #[must_use]
80 pub fn text(text: impl Into<String>, x: f64, y: f64) -> Self {
81 Self::Text(TextContent {
82 text: text.into(),
83 x,
84 y,
85 font_size: 12.0,
86 font: "SimSun".into(),
87 color: 0x00_0000,
88 })
89 }
90
91 #[must_use]
93 pub fn image(data: Vec<u8>, x: f64, y: f64, width: f64, height: f64) -> Self {
94 Self::Image(ImageContent {
95 data,
96 x,
97 y,
98 width,
99 height,
100 })
101 }
102
103 #[must_use]
105 pub fn is_path(&self) -> bool {
106 matches!(self, Self::Path(_))
107 }
108
109 #[must_use]
111 pub fn is_text(&self) -> bool {
112 matches!(self, Self::Text(_))
113 }
114
115 #[must_use]
117 pub fn is_image(&self) -> bool {
118 matches!(self, Self::Image(_))
119 }
120
121 #[must_use]
123 pub fn to_xml_string(&self) -> String {
124 use std::fmt::Write;
125 match self {
126 Self::Path(p) => {
127 let mut xml = String::new();
128 write!(
129 xml,
130 "<ofd:PathObject StrokeColor=\"{}\" LineWidth=\"{}\"",
131 p.stroke_color, p.line_width
132 )
133 .expect("写入内存缓冲区不会失败");
134 if let Some(fc) = p.fill_color {
135 write!(xml, " FillColor=\"{fc}\"").expect("写入内存缓冲区不会失败");
136 }
137 writeln!(
138 xml,
139 "><ofd:AbbreviatedData>{}</ofd:AbbreviatedData></ofd:PathObject>",
140 p.data
141 )
142 .expect("写入内存缓冲区不会失败");
143 xml
144 }
145 Self::Text(t) => {
146 format!(
147 "<ofd:TextObject X=\"{}\" Y=\"{}\" FontSize=\"{}\" \
148 Font=\"{}\" Color=\"{}\">{}</ofd:TextObject>\n",
149 t.x, t.y, t.font_size, t.font, t.color, t.text
150 )
151 }
152 Self::Image(img) => {
153 format!(
154 "<ofd:ImageObject X=\"{}\" Y=\"{}\" Width=\"{}\" Height=\"{}\" />\n",
155 img.x, img.y, img.width, img.height
156 )
157 }
158 }
159 }
160}
161
162#[cfg(test)]
163mod tests {
164 use super::*;
165
166 #[test]
167 fn test_content_path() {
168 let c = Content::path("M0 0L10 10");
169 assert!(c.is_path());
170 assert!(!c.is_text());
171 assert!(!c.is_image());
172 if let Content::Path(p) = &c {
173 assert_eq!(p.data, "M0 0L10 10");
174 assert_eq!(p.stroke_color, 0x00_0000);
175 assert!((p.line_width - 0.35).abs() < f64::EPSILON);
176 assert!(p.fill_color.is_none());
177 }
178 }
179
180 #[test]
181 fn test_content_text() {
182 let c = Content::text("hello", 10.0, 20.0);
183 assert!(!c.is_path());
184 assert!(c.is_text());
185 assert!(!c.is_image());
186 if let Content::Text(t) = &c {
187 assert_eq!(t.text, "hello");
188 assert!((t.x - 10.0).abs() < f64::EPSILON);
189 assert!((t.y - 20.0).abs() < f64::EPSILON);
190 assert!((t.font_size - 12.0).abs() < f64::EPSILON);
191 assert_eq!(t.font, "SimSun");
192 assert_eq!(t.color, 0x00_0000);
193 }
194 }
195
196 #[test]
197 fn test_content_image() {
198 let c = Content::image(vec![0x89, 0x50], 0.0, 0.0, 50.0, 50.0);
199 assert!(!c.is_path());
200 assert!(!c.is_text());
201 assert!(c.is_image());
202 if let Content::Image(img) = &c {
203 assert_eq!(img.data, vec![0x89, 0x50]);
204 assert!((img.width - 50.0).abs() < f64::EPSILON);
205 }
206 }
207
208 #[test]
209 fn test_content_path_to_xml() {
210 let c = Content::path("M0 0Z");
211 let xml = c.to_xml_string();
212 assert!(xml.contains("ofd:PathObject"));
213 assert!(xml.contains("M0 0Z"));
214 assert!(xml.contains("StrokeColor=\"0\""));
215 }
216
217 #[test]
218 fn test_content_text_to_xml() {
219 let c = Content::text("test", 1.0, 2.0);
220 let xml = c.to_xml_string();
221 assert!(xml.contains("ofd:TextObject"));
222 assert!(xml.contains("test"));
223 assert!(xml.contains("X=\"1\""));
224 assert!(xml.contains("Y=\"2\""));
225 }
226
227 #[test]
228 fn test_content_image_to_xml() {
229 let c = Content::image(vec![1, 2, 3], 5.0, 10.0, 30.0, 40.0);
230 let xml = c.to_xml_string();
231 assert!(xml.contains("ofd:ImageObject"));
232 assert!(xml.contains("Width=\"30\""));
233 assert!(xml.contains("Height=\"40\""));
234 }
235
236 #[test]
237 fn test_content_clone_debug() {
238 let c = Content::path("M0 0");
239 let c2 = c.clone();
240 assert!(c2.is_path());
241 assert!(format!("{c:?}").contains("Path"));
242 }
243}