1use crate::config::DisplayConfig;
4use crate::error::{ImageError, ImageResult};
5use crate::models::ImageMetadata;
6
7pub struct ImageDisplay {
15 pub(crate) config: DisplayConfig,
16}
17
18impl ImageDisplay {
19 pub fn new() -> Self {
21 Self {
22 config: DisplayConfig::default(),
23 }
24 }
25
26 pub fn with_config(config: DisplayConfig) -> Self {
28 Self { config }
29 }
30
31 pub fn render_image(&self, metadata: &ImageMetadata) -> ImageResult<String> {
41 let mut output = String::new();
42
43 output.push_str(&self.render_metadata(metadata)?);
45 output.push('\n');
46
47 output.push_str(&self.render_ascii_placeholder());
49
50 Ok(output)
51 }
52
53 fn render_metadata(&self, metadata: &ImageMetadata) -> ImageResult<String> {
63 let (width, height) = metadata.dimensions();
64 let size_mb = metadata.size_mb();
65 let format = metadata.format_str();
66
67 Ok(format!(
68 "[Image] {} | {}x{} | {:.1} MB",
69 format.to_uppercase(),
70 width,
71 height,
72 size_mb
73 ))
74 }
75
76 fn render_ascii_placeholder(&self) -> String {
82 let placeholder_char = &self.config.placeholder_char;
83 let width = self.config.max_width as usize;
84 let height = 10; let mut output = String::new();
87
88 for row in 0..height {
90 if row == 0 || row == height - 1 {
91 output.push_str(&placeholder_char.repeat(width));
93 } else if row == 1 || row == height - 2 {
94 output.push_str(placeholder_char);
96 output.push_str(&" ".repeat(width.saturating_sub(2)));
97 output.push_str(placeholder_char);
98 } else {
99 output.push_str(placeholder_char);
101 output.push_str(&" ".repeat(width.saturating_sub(2)));
102 output.push_str(placeholder_char);
103 }
104 output.push('\n');
105 }
106
107 output
108 }
109
110 pub fn calculate_resized_dimensions(
121 &self,
122 original_width: u32,
123 original_height: u32,
124 ) -> (u32, u32) {
125 if original_width == 0 || original_height == 0 {
126 return (self.config.max_width, self.config.max_height);
127 }
128
129 let max_width = self.config.max_width;
130 let max_height = self.config.max_height;
131
132 let aspect_ratio = original_width as f64 / original_height as f64;
134
135 let height_at_max_width = (max_width as f64 / aspect_ratio) as u32;
137
138 if height_at_max_width <= max_height {
140 (max_width, height_at_max_width.max(1))
141 } else {
142 let width_at_max_height = (max_height as f64 * aspect_ratio) as u32;
144 (width_at_max_height.max(1), max_height)
145 }
146 }
147
148 pub fn verify_metadata_present(
159 &self,
160 display_output: &str,
161 metadata: &ImageMetadata,
162 ) -> ImageResult<bool> {
163 let format = metadata.format_str().to_uppercase();
164 let (width, height) = metadata.dimensions();
165
166 if !display_output.contains(&format) {
168 return Err(ImageError::DisplayError(
169 "Format not found in display output".to_string(),
170 ));
171 }
172
173 let dimensions_str = format!("{}x{}", width, height);
175 if !display_output.contains(&dimensions_str) {
176 return Err(ImageError::DisplayError(
177 "Dimensions not found in display output".to_string(),
178 ));
179 }
180
181 Ok(true)
182 }
183
184 pub fn verify_fits_in_terminal(&self, display_output: &str) -> ImageResult<bool> {
194 let lines: Vec<&str> = display_output.lines().collect();
195 let height = lines.len() as u32;
196
197 if height > self.config.max_height {
198 return Err(ImageError::DisplayError(format!(
199 "Display height {} exceeds maximum {}",
200 height, self.config.max_height
201 )));
202 }
203
204 for line in lines {
205 let width = line.chars().count() as u32;
206 if width > self.config.max_width {
207 return Err(ImageError::DisplayError(format!(
208 "Display width {} exceeds maximum {}",
209 width, self.config.max_width
210 )));
211 }
212 }
213
214 Ok(true)
215 }
216
217 pub fn render_multiple_images(&self, metadata_list: &[ImageMetadata]) -> ImageResult<String> {
227 if metadata_list.is_empty() {
228 return Ok(String::new());
229 }
230
231 let mut output = String::new();
232
233 for (index, metadata) in metadata_list.iter().enumerate() {
234 if index > 0 {
236 output.push_str(&self.render_separator());
237 output.push('\n');
238 }
239
240 let image_display = self.render_image(metadata)?;
242 output.push_str(&image_display);
243
244 if index < metadata_list.len() - 1 {
246 output.push('\n');
247 }
248 }
249
250 Ok(output)
251 }
252
253 fn render_separator(&self) -> String {
259 let separator_char = "─";
260 separator_char.repeat(self.config.max_width as usize)
261 }
262
263 pub fn verify_multiple_images_organized(
274 &self,
275 display_output: &str,
276 metadata_list: &[ImageMetadata],
277 ) -> ImageResult<bool> {
278 if metadata_list.is_empty() {
279 return Ok(true);
280 }
281
282 for metadata in metadata_list {
284 let format = metadata.format_str().to_uppercase();
285 if !display_output.contains(&format) {
286 return Err(ImageError::DisplayError(
287 "Not all images present in display output".to_string(),
288 ));
289 }
290 }
291
292 if metadata_list.len() > 1 {
294 let separator_lines = display_output
296 .lines()
297 .filter(|line| line.chars().all(|c| c == '─' || c.is_whitespace()))
298 .count();
299
300 if separator_lines == 0 {
301 return Err(ImageError::DisplayError(
302 "No separators found between images".to_string(),
303 ));
304 }
305 }
306
307 self.verify_fits_in_terminal(display_output)?;
309
310 Ok(true)
311 }
312}
313
314impl Default for ImageDisplay {
315 fn default() -> Self {
316 Self::new()
317 }
318}
319
320#[cfg(test)]
321mod tests {
322 use super::*;
323 use crate::formats::ImageFormat;
324 use std::path::PathBuf;
325
326 #[test]
327 fn test_display_creation() {
328 let _display = ImageDisplay::new();
329 }
330
331 #[test]
332 fn test_display_with_config() {
333 let config = DisplayConfig {
334 max_width: 100,
335 max_height: 50,
336 placeholder_char: "█".to_string(),
337 };
338 let display = ImageDisplay::with_config(config);
339 assert_eq!(display.config.max_width, 100);
340 assert_eq!(display.config.max_height, 50);
341 }
342
343 #[test]
344 fn test_render_metadata() {
345 let display = ImageDisplay::new();
346 let metadata = ImageMetadata::new(
347 PathBuf::from("/path/to/image.png"),
348 ImageFormat::Png,
349 1024 * 1024,
350 800,
351 600,
352 "abc123".to_string(),
353 );
354
355 let metadata_str = display.render_metadata(&metadata).unwrap();
356 assert!(metadata_str.contains("PNG"));
357 assert!(metadata_str.contains("800x600"));
358 assert!(metadata_str.contains("1.0 MB"));
359 }
360
361 #[test]
362 fn test_render_ascii_placeholder() {
363 let display = ImageDisplay::new();
364 let placeholder = display.render_ascii_placeholder();
365
366 let lines: Vec<&str> = placeholder.lines().collect();
368 assert!(lines.len() > 0);
369
370 assert!(lines.len() as u32 <= display.config.max_height);
372 for line in lines {
373 assert!(line.chars().count() as u32 <= display.config.max_width);
374 }
375 }
376
377 #[test]
378 fn test_render_image() {
379 let display = ImageDisplay::new();
380 let metadata = ImageMetadata::new(
381 PathBuf::from("/path/to/image.png"),
382 ImageFormat::Png,
383 1024 * 1024,
384 800,
385 600,
386 "abc123".to_string(),
387 );
388
389 let rendered = display.render_image(&metadata).unwrap();
390 assert!(rendered.contains("PNG"));
391 assert!(rendered.contains("800x600"));
392 assert!(rendered.contains("█")); }
394
395 #[test]
396 fn test_calculate_resized_dimensions_width_limited() {
397 let display = ImageDisplay::new();
398 let (resized_width, resized_height) = display.calculate_resized_dimensions(800, 100);
400
401 assert_eq!(resized_width, display.config.max_width);
403 assert!(resized_height <= display.config.max_height);
405 }
406
407 #[test]
408 fn test_calculate_resized_dimensions_height_limited() {
409 let display = ImageDisplay::new();
410 let (resized_width, resized_height) = display.calculate_resized_dimensions(400, 1200);
411
412 assert_eq!(resized_height, display.config.max_height);
414 assert!(resized_width <= display.config.max_width);
416 }
417
418 #[test]
419 fn test_calculate_resized_dimensions_zero_dimensions() {
420 let display = ImageDisplay::new();
421 let (resized_width, resized_height) = display.calculate_resized_dimensions(0, 0);
422
423 assert_eq!(resized_width, display.config.max_width);
425 assert_eq!(resized_height, display.config.max_height);
426 }
427
428 #[test]
429 fn test_verify_metadata_present() {
430 let display = ImageDisplay::new();
431 let metadata = ImageMetadata::new(
432 PathBuf::from("/path/to/image.png"),
433 ImageFormat::Png,
434 1024 * 1024,
435 800,
436 600,
437 "abc123".to_string(),
438 );
439
440 let rendered = display.render_image(&metadata).unwrap();
441 let result = display.verify_metadata_present(&rendered, &metadata);
442 assert!(result.is_ok());
443 assert!(result.unwrap());
444 }
445
446 #[test]
447 fn test_verify_fits_in_terminal() {
448 let display = ImageDisplay::new();
449 let metadata = ImageMetadata::new(
450 PathBuf::from("/path/to/image.png"),
451 ImageFormat::Png,
452 1024 * 1024,
453 800,
454 600,
455 "abc123".to_string(),
456 );
457
458 let rendered = display.render_image(&metadata).unwrap();
459 let result = display.verify_fits_in_terminal(&rendered);
460 assert!(result.is_ok());
461 assert!(result.unwrap());
462 }
463
464 #[test]
465 fn test_render_separator() {
466 let display = ImageDisplay::new();
467 let separator = display.render_separator();
468
469 assert!(separator.contains("─"));
471 assert!(separator.len() > 0);
473 }
474
475 #[test]
476 fn test_render_multiple_images_empty() {
477 let display = ImageDisplay::new();
478 let metadata_list: Vec<ImageMetadata> = vec![];
479
480 let rendered = display.render_multiple_images(&metadata_list).unwrap();
481 assert_eq!(rendered, "");
482 }
483
484 #[test]
485 fn test_render_multiple_images_single() {
486 let display = ImageDisplay::new();
487 let metadata = ImageMetadata::new(
488 PathBuf::from("/path/to/image.png"),
489 ImageFormat::Png,
490 1024 * 1024,
491 800,
492 600,
493 "abc123".to_string(),
494 );
495
496 let rendered = display.render_multiple_images(&[metadata]).unwrap();
497 assert!(rendered.contains("PNG"));
498 assert!(rendered.contains("800x600"));
499 }
500
501 #[test]
502 fn test_render_multiple_images_multiple() {
503 let display = ImageDisplay::new();
504 let metadata1 = ImageMetadata::new(
505 PathBuf::from("/path/to/image1.png"),
506 ImageFormat::Png,
507 1024 * 1024,
508 800,
509 600,
510 "abc123".to_string(),
511 );
512 let metadata2 = ImageMetadata::new(
513 PathBuf::from("/path/to/image2.jpg"),
514 ImageFormat::Jpeg,
515 2048 * 1024,
516 1024,
517 768,
518 "def456".to_string(),
519 );
520
521 let rendered = display.render_multiple_images(&[metadata1, metadata2]).unwrap();
522
523 assert!(rendered.contains("PNG"));
525 assert!(rendered.contains("JPG"));
526 assert!(rendered.contains("800x600"));
527 assert!(rendered.contains("1024x768"));
528
529 assert!(rendered.contains("─"));
531 }
532
533 #[test]
534 fn test_verify_multiple_images_organized() {
535 let display = ImageDisplay::new();
536 let metadata1 = ImageMetadata::new(
537 PathBuf::from("/path/to/image1.png"),
538 ImageFormat::Png,
539 1024 * 1024,
540 800,
541 600,
542 "abc123".to_string(),
543 );
544 let metadata2 = ImageMetadata::new(
545 PathBuf::from("/path/to/image2.jpg"),
546 ImageFormat::Jpeg,
547 2048 * 1024,
548 1024,
549 768,
550 "def456".to_string(),
551 );
552
553 let rendered = display.render_multiple_images(&[metadata1.clone(), metadata2.clone()]).unwrap();
554 let result = display.verify_multiple_images_organized(&rendered, &[metadata1, metadata2]);
555 assert!(result.is_ok());
556 assert!(result.unwrap());
557 }
558}