1use crate::svg::SvgRenderer;
7use fop_layout::AreaTree;
8use fop_types::{FopError, Result};
9use log::{debug, info};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum RasterFormat {
14 Png,
16
17 Jpeg { quality: u8 },
19}
20
21pub struct RasterRenderer {
27 dpi: u32,
30}
31
32impl RasterRenderer {
33 pub fn new(dpi: u32) -> Self {
45 Self { dpi }
46 }
47
48 pub fn render_to_raster(
61 &self,
62 area_tree: &AreaTree,
63 format: RasterFormat,
64 ) -> Result<Vec<Vec<u8>>> {
65 info!("Starting raster rendering at {} DPI", self.dpi);
66
67 debug!("Step 1: Converting area tree to SVG pages");
69 let svg_renderer = SvgRenderer::new();
70 let svg_pages = svg_renderer.render_to_svg_pages(area_tree)?;
71
72 debug!("Generated {} SVG page(s)", svg_pages.len());
73
74 debug!("Step 2: Rasterizing SVG pages to bitmaps");
76 let mut raster_pages = Vec::with_capacity(svg_pages.len());
77 for (i, svg_content) in svg_pages.iter().enumerate() {
78 debug!("Rasterizing page {} ({} bytes)", i + 1, svg_content.len());
79 let page_images = self.rasterize_svg(svg_content, format)?;
80 raster_pages.extend(page_images);
82 }
83
84 info!("Successfully rendered {} page(s)", raster_pages.len());
85 Ok(raster_pages)
86 }
87
88 fn rasterize_svg(&self, svg_content: &str, format: RasterFormat) -> Result<Vec<Vec<u8>>> {
90 let opt = usvg::Options::default();
92 let tree = usvg::Tree::from_str(svg_content, &opt)
93 .map_err(|e| FopError::Generic(format!("Failed to parse SVG: {}", e)))?;
94
95 let svg_size = tree.size();
97 debug!("SVG size: {}x{} pt", svg_size.width(), svg_size.height());
98
99 let scale = self.dpi as f32 / 72.0;
102 let width = (svg_size.width() * scale).ceil() as u32;
103 let height = (svg_size.height() * scale).ceil() as u32;
104
105 debug!(
106 "Raster size: {}x{} pixels (scale: {})",
107 width, height, scale
108 );
109
110 let mut pixmap = tiny_skia::Pixmap::new(width, height)
112 .ok_or_else(|| FopError::Generic("Failed to create pixmap".to_string()))?;
113
114 let render_transform = tiny_skia::Transform::from_scale(scale, scale);
116 resvg::render(&tree, render_transform, &mut pixmap.as_mut());
117
118 let encoded = match format {
120 RasterFormat::Png => self.encode_png(&pixmap)?,
121 RasterFormat::Jpeg { quality } => self.encode_jpeg(&pixmap, quality)?,
122 };
123
124 Ok(vec![encoded])
126 }
127
128 fn encode_png(&self, pixmap: &tiny_skia::Pixmap) -> Result<Vec<u8>> {
130 debug!("Encoding as PNG");
131
132 let mut buf = Vec::new();
134 {
135 let mut encoder = png::Encoder::new(
136 std::io::Cursor::new(&mut buf),
137 pixmap.width(),
138 pixmap.height(),
139 );
140 encoder.set_color(png::ColorType::Rgba);
141 encoder.set_depth(png::BitDepth::Eight);
142
143 let mut writer = encoder
144 .write_header()
145 .map_err(|e| FopError::Generic(format!("PNG encoding failed: {}", e)))?;
146
147 writer
148 .write_image_data(pixmap.data())
149 .map_err(|e| FopError::Generic(format!("PNG writing failed: {}", e)))?;
150 }
151
152 debug!("PNG encoded: {} bytes", buf.len());
153 Ok(buf)
154 }
155
156 fn encode_jpeg(&self, pixmap: &tiny_skia::Pixmap, quality: u8) -> Result<Vec<u8>> {
158 debug!("Encoding as JPEG with quality {}", quality);
159
160 let width = pixmap.width();
162 let height = pixmap.height();
163 let rgba_data = pixmap.data();
164
165 let mut rgb_data = Vec::with_capacity((width * height * 3) as usize);
166 for chunk in rgba_data.chunks_exact(4) {
167 let alpha = chunk[3] as f32 / 255.0;
169 let r = (chunk[0] as f32 * alpha + 255.0 * (1.0 - alpha)) as u8;
170 let g = (chunk[1] as f32 * alpha + 255.0 * (1.0 - alpha)) as u8;
171 let b = (chunk[2] as f32 * alpha + 255.0 * (1.0 - alpha)) as u8;
172 rgb_data.push(r);
173 rgb_data.push(g);
174 rgb_data.push(b);
175 }
176
177 let mut buf = Vec::new();
179 {
180 let encoder = jpeg_encoder::Encoder::new(&mut buf, quality);
181 encoder
182 .encode(
183 &rgb_data,
184 width as u16,
185 height as u16,
186 jpeg_encoder::ColorType::Rgb,
187 )
188 .map_err(|e| FopError::Generic(format!("JPEG encoding failed: {}", e)))?;
189 }
190
191 debug!("JPEG encoded: {} bytes", buf.len());
192 Ok(buf)
193 }
194}
195
196impl Default for RasterRenderer {
197 fn default() -> Self {
199 Self::new(96)
200 }
201}
202
203#[cfg(test)]
204mod tests {
205 use super::*;
206 use fop_layout::area::{Area, TraitSet};
207 use fop_layout::AreaType;
208 use fop_types::{Color, Length, Rect};
209
210 fn create_test_area_tree() -> AreaTree {
211 let mut tree = AreaTree::new();
212
213 let page_area = Area {
215 area_type: AreaType::Page,
216 geometry: Rect {
217 x: Length::ZERO,
218 y: Length::ZERO,
219 width: Length::from_mm(210.0), height: Length::from_mm(297.0), },
222 traits: TraitSet {
223 background_color: Some(Color::WHITE),
224 ..Default::default()
225 },
226 content: None,
227 keep_constraint: None,
228 break_before: None,
229 break_after: None,
230 widows: 2,
231 orphans: 2,
232 };
233
234 tree.add_area(page_area);
235 tree
236 }
237
238 #[test]
239 fn test_raster_renderer_new() {
240 let renderer = RasterRenderer::new(300);
241 assert_eq!(renderer.dpi, 300);
242 }
243
244 #[test]
245 fn test_raster_renderer_default() {
246 let renderer = RasterRenderer::default();
247 assert_eq!(renderer.dpi, 96);
248 }
249
250 #[test]
251 fn test_render_to_png() {
252 let renderer = RasterRenderer::new(72);
253 let area_tree = create_test_area_tree();
254
255 let result = renderer.render_to_raster(&area_tree, RasterFormat::Png);
256 assert!(result.is_ok());
257
258 let pages = result.expect("test: should succeed");
259 assert_eq!(pages.len(), 1);
260 assert!(!pages[0].is_empty());
261
262 assert_eq!(&pages[0][0..4], &[137, 80, 78, 71]);
264 }
265
266 #[test]
267 fn test_render_to_jpeg() {
268 let renderer = RasterRenderer::new(72);
269 let area_tree = create_test_area_tree();
270
271 let result = renderer.render_to_raster(&area_tree, RasterFormat::Jpeg { quality: 90 });
272 assert!(result.is_ok());
273
274 let pages = result.expect("test: should succeed");
275 assert_eq!(pages.len(), 1);
276 assert!(!pages[0].is_empty());
277
278 assert_eq!(&pages[0][0..3], &[0xFF, 0xD8, 0xFF]);
280 }
281
282 #[test]
283 fn test_different_dpi_values() {
284 let dpis = [72, 96, 150, 300];
285 let area_tree = create_test_area_tree();
286
287 for dpi in dpis {
288 let renderer = RasterRenderer::new(dpi);
289 let result = renderer.render_to_raster(&area_tree, RasterFormat::Png);
290 assert!(result.is_ok(), "Failed at {} DPI", dpi);
291 }
292 }
293
294 #[test]
295 fn test_jpeg_quality_range() {
296 let area_tree = create_test_area_tree();
297 let renderer = RasterRenderer::new(72);
298
299 for quality in [10, 50, 75, 90, 100] {
300 let result = renderer.render_to_raster(&area_tree, RasterFormat::Jpeg { quality });
301 assert!(result.is_ok(), "Failed at quality {}", quality);
302 }
303 }
304
305 #[test]
306 fn test_png_larger_than_jpeg() {
307 let area_tree = create_test_area_tree();
308 let renderer = RasterRenderer::new(72);
309
310 let png = renderer
311 .render_to_raster(&area_tree, RasterFormat::Png)
312 .expect("test: should succeed");
313 let jpeg = renderer
314 .render_to_raster(&area_tree, RasterFormat::Jpeg { quality: 75 })
315 .expect("test: should succeed");
316
317 assert!(!png[0].is_empty());
320 assert!(!jpeg[0].is_empty());
321 }
322
323 #[test]
324 fn test_high_dpi_produces_larger_output() {
325 let area_tree = create_test_area_tree();
326
327 let low_dpi = RasterRenderer::new(72);
328 let high_dpi = RasterRenderer::new(300);
329
330 let low_result = low_dpi
331 .render_to_raster(&area_tree, RasterFormat::Png)
332 .expect("test: should succeed");
333 let high_result = high_dpi
334 .render_to_raster(&area_tree, RasterFormat::Png)
335 .expect("test: should succeed");
336
337 assert!(high_result[0].len() > low_result[0].len());
339 }
340
341 #[test]
342 fn test_raster_format_debug() {
343 let png = RasterFormat::Png;
344 let jpeg = RasterFormat::Jpeg { quality: 80 };
345
346 assert_eq!(format!("{:?}", png), "Png");
347 assert_eq!(format!("{:?}", jpeg), "Jpeg { quality: 80 }");
348 }
349
350 #[test]
351 fn test_raster_format_equality() {
352 assert_eq!(RasterFormat::Png, RasterFormat::Png);
353 assert_eq!(
354 RasterFormat::Jpeg { quality: 80 },
355 RasterFormat::Jpeg { quality: 80 }
356 );
357 assert_ne!(RasterFormat::Png, RasterFormat::Jpeg { quality: 80 });
358 }
359
360 #[test]
361 fn test_invalid_svg_handling() {
362 let renderer = RasterRenderer::new(96);
363 let invalid_svg = "not valid svg content";
364
365 let result = renderer.rasterize_svg(invalid_svg, RasterFormat::Png);
366 assert!(result.is_err());
367 }
368}
369
370#[cfg(test)]
371mod tests_extended {
372 use super::*;
373 use fop_layout::area::{Area, TraitSet};
374 use fop_layout::AreaType;
375 use fop_types::{Color, Length, Rect};
376
377 #[allow(dead_code)]
378 fn two_page_area_tree() -> AreaTree {
379 let mut tree = AreaTree::new();
380 for _ in 0..2 {
381 let area = Area {
382 area_type: AreaType::Page,
383 geometry: Rect {
384 x: Length::ZERO,
385 y: Length::ZERO,
386 width: Length::from_mm(210.0),
387 height: Length::from_mm(297.0),
388 },
389 traits: TraitSet {
390 background_color: Some(Color::WHITE),
391 ..Default::default()
392 },
393 content: None,
394 keep_constraint: None,
395 break_before: None,
396 break_after: None,
397 widows: 2,
398 orphans: 2,
399 };
400 tree.add_area(area);
401 }
402 tree
403 }
404
405 fn single_page_tree() -> AreaTree {
406 let mut tree = AreaTree::new();
407 let area = Area {
408 area_type: AreaType::Page,
409 geometry: Rect {
410 x: Length::ZERO,
411 y: Length::ZERO,
412 width: Length::from_mm(210.0),
413 height: Length::from_mm(297.0),
414 },
415 traits: TraitSet {
416 background_color: Some(Color::WHITE),
417 ..Default::default()
418 },
419 content: None,
420 keep_constraint: None,
421 break_before: None,
422 break_after: None,
423 widows: 2,
424 orphans: 2,
425 };
426 tree.add_area(area);
427 tree
428 }
429
430 #[test]
431 fn test_png_magic_bytes() {
432 let renderer = RasterRenderer::new(72);
434 let tree = single_page_tree();
435 let pages = renderer
436 .render_to_raster(&tree, RasterFormat::Png)
437 .expect("test: should succeed");
438 assert_eq!(
439 &pages[0][..8],
440 &[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]
441 );
442 }
443
444 #[test]
445 fn test_jpeg_magic_bytes() {
446 let renderer = RasterRenderer::new(72);
447 let tree = single_page_tree();
448 let pages = renderer
449 .render_to_raster(&tree, RasterFormat::Jpeg { quality: 80 })
450 .expect("test: should succeed");
451 assert_eq!(pages[0][0], 0xFF);
453 assert_eq!(pages[0][1], 0xD8);
454 }
455
456 #[test]
457 fn test_raster_format_clone() {
458 let fmt = RasterFormat::Jpeg { quality: 70 };
459 let fmt2 = fmt;
460 assert_eq!(fmt, fmt2);
461 }
462
463 #[test]
464 fn test_raster_format_copy() {
465 let fmt1 = RasterFormat::Png;
466 let fmt2 = fmt1; assert_eq!(fmt1, fmt2);
468 }
469
470 #[test]
471 fn test_raster_renderer_dpi_stored() {
472 let r = RasterRenderer::new(150);
473 assert_eq!(r.dpi, 150);
474 }
475
476 #[test]
477 fn test_higher_jpeg_quality_produces_different_bytes() {
478 let tree = single_page_tree();
479 let renderer = RasterRenderer::new(72);
480
481 let low = renderer
482 .render_to_raster(&tree, RasterFormat::Jpeg { quality: 10 })
483 .expect("test: should succeed");
484 let high = renderer
485 .render_to_raster(&tree, RasterFormat::Jpeg { quality: 95 })
486 .expect("test: should succeed");
487
488 assert!(!low[0].is_empty());
491 assert!(!high[0].is_empty());
492 }
493
494 #[test]
495 fn test_empty_tree_produces_no_pages() {
496 let renderer = RasterRenderer::new(96);
497 let tree = AreaTree::new();
498 let result = renderer
499 .render_to_raster(&tree, RasterFormat::Png)
500 .expect("test: should succeed");
501 assert!(result.is_empty());
502 }
503}