typlite/writer/docx/
image_processor.rs1use base64::Engine;
4use docx_rs::*;
5use std::io::Cursor;
6
7use crate::Result;
8
9pub struct DocxImageProcessor;
11
12impl DocxImageProcessor {
13 pub fn new() -> Self {
15 Self
16 }
17
18 pub fn convert_svg_to_png(&self, svg_data: &[u8]) -> Result<Vec<u8>> {
20 let svg_str = match std::str::from_utf8(svg_data) {
22 Ok(s) => s,
23 Err(_) => return Err("Unable to parse input data as UTF-8 string".into()),
24 };
25
26 let dpi = 300.0;
27 let scale_factor = dpi / 96.0;
28
29 let opt = resvg::usvg::Options {
30 dpi,
31 ..resvg::usvg::Options::default()
32 };
33
34 let rtree = match resvg::usvg::Tree::from_str(svg_str, &opt) {
36 Ok(tree) => tree,
37 Err(e) => return Err(format!("SVG parsing error: {e:?}").into()),
38 };
39
40 let size = rtree.size().to_int_size();
41 let width = (size.width() as f32 * scale_factor) as u32;
42 let height = (size.height() as f32 * scale_factor) as u32;
43
44 let mut pixmap = match resvg::tiny_skia::Pixmap::new(width, height) {
46 Some(pixmap) => pixmap,
47 None => return Err("Unable to create pixel buffer".into()),
48 };
49
50 resvg::render(
52 &rtree,
53 resvg::tiny_skia::Transform::from_scale(scale_factor, scale_factor),
54 &mut pixmap.as_mut(),
55 );
56
57 pixmap
59 .encode_png()
60 .map_err(|e| format!("PNG encoding error: {e:?}").into())
61 }
62
63 pub fn process_image_data(
65 &self,
66 docx: Docx,
67 data: &[u8],
68 alt_text: Option<&str>,
69 scale: Option<f32>,
70 ) -> Docx {
71 match image::guess_format(data) {
73 Ok(..) => {
74 let pic = match image::load_from_memory(data) {
78 Ok(img) => {
79 let (w, h) =
80 Self::image_dim(::image::GenericImageView::dimensions(&img), scale);
81 let mut buffer = Vec::new();
82 if img
83 .write_to(&mut Cursor::new(&mut buffer), image::ImageFormat::Png)
84 .is_ok()
85 {
86 Pic::new_with_dimensions(buffer, w, h)
87 } else {
88 let err_para = Paragraph::new().add_run(Run::new().add_text(
90 "[Image processing error: Unable to convert to supported format]".to_string(),
91 ));
92 return docx.add_paragraph(err_para);
93 }
94 }
95 Err(_) => {
96 let err_para = Paragraph::new().add_run(Run::new().add_text(
98 "[Image processing error: Unable to load image]".to_string(),
99 ));
100 return docx.add_paragraph(err_para);
101 }
102 };
103
104 let img_para = Paragraph::new().add_run(Run::new().add_image(pic));
105 let doc_with_img = docx.add_paragraph(img_para);
106
107 if let Some(alt) = alt_text {
108 if !alt.is_empty() {
109 let caption_para = Paragraph::new()
110 .style("Caption")
111 .add_run(Run::new().add_text(alt));
112 doc_with_img.add_paragraph(caption_para)
113 } else {
114 doc_with_img
115 }
116 } else {
117 doc_with_img
118 }
119 }
120 Err(_) => {
121 let err_para =
123 Paragraph::new()
124 .add_run(Run::new().add_text(
125 "[Image processing error: Unknown image format]".to_string(),
126 ));
127 docx.add_paragraph(err_para)
128 }
129 }
130 }
131
132 pub fn process_inline_image(&self, mut run: Run, data: &[u8]) -> Result<Run> {
134 match image::guess_format(data) {
135 Ok(..) => {
136 let pic = match image::load_from_memory(data) {
138 Ok(img) => {
139 let (w, h) = ::image::GenericImageView::dimensions(&img);
140 let mut buffer = Vec::new();
141 if img
142 .write_to(&mut Cursor::new(&mut buffer), image::ImageFormat::Png)
143 .is_ok()
144 {
145 Pic::new_with_dimensions(buffer, w, h)
146 } else {
147 run = run.add_text("[Image conversion error]");
148 return Ok(run);
149 }
150 }
151 Err(_) => {
152 run = run.add_text("[Image loading error]");
153 return Ok(run);
154 }
155 };
156
157 run = run.add_image(pic);
158 Ok(run)
159 }
160 Err(_) => {
161 run = run.add_text("[Unknown image format]");
162 Ok(run)
163 }
164 }
165 }
166
167 pub fn process_data_url_image(&self, run: Run, src: &str, is_typst_block: bool) -> Result<Run> {
169 if let Some(data_start) = src.find("base64,") {
170 let base64_data = &src[data_start + 7..];
171 if let Ok(img_data) = base64::engine::general_purpose::STANDARD.decode(base64_data) {
172 if is_typst_block {
174 if let Ok(png_data) = self.convert_svg_to_png(&img_data) {
176 let mut new_run = run;
177 new_run = self.process_inline_image(new_run, &png_data)?;
178 return Ok(new_run);
179 } else {
180 return Ok(run.add_text("[SVG conversion failed]"));
181 }
182 } else {
183 let mut new_run = run;
185 new_run = self.process_inline_image(new_run, &img_data)?;
186 return Ok(new_run);
187 }
188 }
189 }
190 Ok(run.add_text("[Invalid data URL]"))
191 }
192
193 pub fn image_dim((w, h): (u32, u32), scale_factor: Option<f32>) -> (u32, u32) {
195 let actual_scale = scale_factor.unwrap_or(1.0);
196
197 let max_width = 5486400;
198 let scaled_w = (w as f32 * actual_scale) as u32;
199 let scaled_h = (h as f32 * actual_scale) as u32;
200
201 if scaled_w > max_width {
202 let ratio = scaled_h as f32 / scaled_w as f32;
203 let new_width = max_width;
204 let new_height = (max_width as f32 * ratio) as u32;
205 (new_width, new_height)
206 } else {
207 (scaled_w, scaled_h)
208 }
209 }
210}