1use crate::error::Result;
6use crate::xml::RawXmlNode;
7use quick_xml::events::{BytesEnd, BytesStart, Event};
8use quick_xml::Writer;
9
10#[derive(Clone, Debug)]
12pub struct InlineImage {
13 pub r_id: String,
15 pub width_emu: i64,
17 pub height_emu: i64,
19 pub description: String,
21 pub name: String,
23 pub raw_xml: Option<RawXmlNode>,
25}
26
27impl InlineImage {
28 pub fn new(r_id: impl Into<String>, width_emu: i64, height_emu: i64) -> Self {
30 InlineImage {
31 r_id: r_id.into(),
32 width_emu,
33 height_emu,
34 description: String::new(),
35 name: String::new(),
36 raw_xml: None,
37 }
38 }
39
40 pub fn from_cm(r_id: impl Into<String>, width_cm: f64, height_cm: f64) -> Self {
42 Self::new(
44 r_id,
45 (width_cm * 360000.0) as i64,
46 (height_cm * 360000.0) as i64,
47 )
48 }
49
50 pub fn from_inches(r_id: impl Into<String>, width_in: f64, height_in: f64) -> Self {
52 Self::new(
54 r_id,
55 (width_in * 914400.0) as i64,
56 (height_in * 914400.0) as i64,
57 )
58 }
59
60 pub fn with_description(mut self, desc: impl Into<String>) -> Self {
62 self.description = desc.into();
63 self
64 }
65
66 pub fn with_name(mut self, name: impl Into<String>) -> Self {
68 self.name = name.into();
69 self
70 }
71
72 pub fn to_drawing_xml<W: std::io::Write>(&self, writer: &mut Writer<W>) -> Result<()> {
74 if let Some(ref raw) = self.raw_xml {
76 raw.write_to(writer)?;
77 return Ok(());
78 }
79
80 writer.write_event(Event::Start(BytesStart::new("w:drawing")))?;
83
84 let mut inline = BytesStart::new("wp:inline");
86 inline.push_attribute(("distT", "0"));
87 inline.push_attribute(("distB", "0"));
88 inline.push_attribute(("distL", "0"));
89 inline.push_attribute(("distR", "0"));
90 writer.write_event(Event::Start(inline))?;
91
92 let mut extent = BytesStart::new("wp:extent");
94 extent.push_attribute(("cx", self.width_emu.to_string().as_str()));
95 extent.push_attribute(("cy", self.height_emu.to_string().as_str()));
96 writer.write_event(Event::Empty(extent))?;
97
98 let mut doc_pr = BytesStart::new("wp:docPr");
100 doc_pr.push_attribute(("id", "1"));
101 doc_pr.push_attribute(("name", self.name.as_str()));
102 doc_pr.push_attribute(("descr", self.description.as_str()));
103 writer.write_event(Event::Empty(doc_pr))?;
104
105 let mut graphic = BytesStart::new("a:graphic");
107 graphic.push_attribute(("xmlns:a", crate::xml::A));
108 writer.write_event(Event::Start(graphic))?;
109
110 let mut gd = BytesStart::new("a:graphicData");
112 gd.push_attribute(("uri", crate::xml::PIC));
113 writer.write_event(Event::Start(gd))?;
114
115 let mut pic = BytesStart::new("pic:pic");
117 pic.push_attribute(("xmlns:pic", crate::xml::PIC));
118 writer.write_event(Event::Start(pic))?;
119
120 writer.write_event(Event::Start(BytesStart::new("pic:nvPicPr")))?;
122 let mut cnvpr = BytesStart::new("pic:cNvPr");
123 cnvpr.push_attribute(("id", "0"));
124 cnvpr.push_attribute(("name", self.name.as_str()));
125 writer.write_event(Event::Empty(cnvpr))?;
126 writer.write_event(Event::Empty(BytesStart::new("pic:cNvPicPr")))?;
127 writer.write_event(Event::End(BytesEnd::new("pic:nvPicPr")))?;
128
129 writer.write_event(Event::Start(BytesStart::new("pic:blipFill")))?;
131 let mut blip = BytesStart::new("a:blip");
132 blip.push_attribute(("r:embed", self.r_id.as_str()));
133 writer.write_event(Event::Empty(blip))?;
134 writer.write_event(Event::Start(BytesStart::new("a:stretch")))?;
135 writer.write_event(Event::Empty(BytesStart::new("a:fillRect")))?;
136 writer.write_event(Event::End(BytesEnd::new("a:stretch")))?;
137 writer.write_event(Event::End(BytesEnd::new("pic:blipFill")))?;
138
139 writer.write_event(Event::Start(BytesStart::new("pic:spPr")))?;
141 writer.write_event(Event::Start(BytesStart::new("a:xfrm")))?;
142 let mut off = BytesStart::new("a:off");
143 off.push_attribute(("x", "0"));
144 off.push_attribute(("y", "0"));
145 writer.write_event(Event::Empty(off))?;
146 let mut ext = BytesStart::new("a:ext");
147 ext.push_attribute(("cx", self.width_emu.to_string().as_str()));
148 ext.push_attribute(("cy", self.height_emu.to_string().as_str()));
149 writer.write_event(Event::Empty(ext))?;
150 writer.write_event(Event::End(BytesEnd::new("a:xfrm")))?;
151 let mut prst = BytesStart::new("a:prstGeom");
152 prst.push_attribute(("prst", "rect"));
153 writer.write_event(Event::Start(prst))?;
154 writer.write_event(Event::Empty(BytesStart::new("a:avLst")))?;
155 writer.write_event(Event::End(BytesEnd::new("a:prstGeom")))?;
156 writer.write_event(Event::End(BytesEnd::new("pic:spPr")))?;
157
158 writer.write_event(Event::End(BytesEnd::new("pic:pic")))?;
160 writer.write_event(Event::End(BytesEnd::new("a:graphicData")))?;
162 writer.write_event(Event::End(BytesEnd::new("a:graphic")))?;
164 writer.write_event(Event::End(BytesEnd::new("wp:inline")))?;
166 writer.write_event(Event::End(BytesEnd::new("w:drawing")))?;
168
169 Ok(())
170 }
171}
172
173pub struct ImageData {
175 pub data: Vec<u8>,
177 pub content_type: String,
179 pub extension: String,
181}
182
183impl ImageData {
184 pub fn png(data: Vec<u8>) -> Self {
186 ImageData {
187 data,
188 content_type: "image/png".into(),
189 extension: "png".into(),
190 }
191 }
192
193 pub fn jpeg(data: Vec<u8>) -> Self {
195 ImageData {
196 data,
197 content_type: "image/jpeg".into(),
198 extension: "jpeg".into(),
199 }
200 }
201
202 pub fn from_file(path: &std::path::Path) -> std::io::Result<Self> {
204 let data = std::fs::read(path)?;
205 let ext = path
206 .extension()
207 .and_then(|e| e.to_str())
208 .unwrap_or("png")
209 .to_lowercase();
210 let content_type = match ext.as_str() {
211 "png" => "image/png",
212 "jpg" | "jpeg" => "image/jpeg",
213 "gif" => "image/gif",
214 "bmp" => "image/bmp",
215 "tiff" | "tif" => "image/tiff",
216 _ => "image/png",
217 };
218 Ok(ImageData {
219 data,
220 content_type: content_type.into(),
221 extension: ext,
222 })
223 }
224}
225
226#[cfg(test)]
227mod tests {
228 use super::*;
229
230 #[test]
231 fn test_inline_image_new() {
232 let img = InlineImage::new("rId5", 914400, 914400);
233 assert_eq!(img.r_id, "rId5");
234 assert_eq!(img.width_emu, 914400);
235 assert_eq!(img.height_emu, 914400);
236 }
237
238 #[test]
239 fn test_inline_image_from_cm() {
240 let img = InlineImage::from_cm("rId1", 10.0, 5.0);
241 assert_eq!(img.width_emu, 3600000);
242 assert_eq!(img.height_emu, 1800000);
243 }
244
245 #[test]
246 fn test_inline_image_from_inches() {
247 let img = InlineImage::from_inches("rId1", 1.0, 1.0);
248 assert_eq!(img.width_emu, 914400);
249 assert_eq!(img.height_emu, 914400);
250 }
251
252 #[test]
253 fn test_image_data_png() {
254 let data = ImageData::png(vec![0x89, 0x50, 0x4E, 0x47]);
255 assert_eq!(data.content_type, "image/png");
256 assert_eq!(data.extension, "png");
257 }
258
259 #[test]
260 fn test_generate_drawing_xml() {
261 let img = InlineImage::new("rId1", 914400, 914400)
262 .with_name("test.png")
263 .with_description("Test image");
264
265 let mut buf = Vec::new();
266 let mut writer = Writer::new(&mut buf);
267 img.to_drawing_xml(&mut writer).unwrap();
268
269 let xml = String::from_utf8(buf).unwrap();
270 assert!(xml.contains("w:drawing"));
271 assert!(xml.contains("wp:inline"));
272 assert!(xml.contains("r:embed=\"rId1\""));
273 assert!(xml.contains("cx=\"914400\""));
274 }
275}