topdf 0.1.1

Convert png, jpg, or jpeg to one PDF
use crate::{
    unit::{MM, PX},
    DPI,
};
use image::GenericImageView;
use printpdf::{ColorBits, ColorSpace, Image, ImageTransform, ImageXObject, Mm, PdfDocument, Px};
use ratatui::widgets::ListState;
use std::{
    convert::From,
    error,
    fs::{self, File},
    io::BufWriter,
    path::{Path, PathBuf},
};

#[derive(Clone)]
pub struct FileList {
    pub root_dir: PathBuf,
    pub items: Vec<FileItem>,
    pub state: ListState,
}

#[derive(Clone)]
pub struct FileItem {
    pub path: PathBuf,
    pub status: Status,
}

#[derive(Clone)]
pub enum Status {
    Unchecked,
    Checked,
}

impl FileList {
    pub fn load_files(&mut self, root_path: &Path) -> std::io::Result<()> {
        self.items.clear();
        self.root_dir = root_path.to_path_buf();

        let extensions = ["jpg", "jpeg", "png"];

        self.load_files_recursive(root_path, &extensions)?;
        Ok(())
    }

    fn load_files_recursive(&mut self, path: &Path, extensions: &[&str]) -> std::io::Result<()> {
        if path.is_dir() {
            for entry in fs::read_dir(path)? {
                let entry = entry?;
                let path = entry.path();
                if path.is_dir() {
                    self.load_files_recursive(&path, extensions)?;
                } else if path.is_file() {
                    if let Some(ext) = path.extension() {
                        match ext.to_str() {
                            Some(ext_str) => {
                                if extensions.contains(&ext_str) {
                                    self.items.push(FileItem {
                                        path,
                                        status: Status::Unchecked,
                                    })
                                }
                            }
                            None => {
                                eprintln!(
                                    "Failed to convert extension to str for file: {:?}",
                                    path
                                );
                            }
                        }
                    }
                }
            }
        } else if path.is_file() {
            if let Some(ext) = path.extension() {
                match ext.to_str() {
                    Some(ext_str) => {
                        if extensions.contains(&ext_str) {
                            self.items.push(FileItem {
                                path: path.to_path_buf(),
                                status: Status::Unchecked,
                            });
                        }
                    }
                    None => {
                        eprintln!("Failed to convert extension to str for file: {:?}", path);
                    }
                }
            }
        }
        Ok(())
    }

    pub fn convert_to_pdf(
        &mut self,
        size_width: MM,
        size_height: MM,
        output_name: &str,
    ) -> Result<(), Box<dyn error::Error>> {
        let (doc, first_page, first_layer) =
            PdfDocument::new("PDF_Document_title", Mm(210.0), Mm(297.0), "Layer");

        let items: Vec<FileItem> = self
            .items
            .iter()
            .filter(|item| matches!(item.status, Status::Checked))
            .cloned()
            .collect();

        let page_width_mm = size_width;
        let page_height_mm = size_height;

        for (index, item) in items.iter().enumerate() {
            if let Status::Unchecked = item.status {
                continue;
            }

            let image = match image::open(item.path.clone()) {
                Ok(image) => image,
                Err(e) => {
                    eprintln!("Failed to open image: {:?} - {}", item.path, e);
                    continue;
                }
            };

            let (width, height) = image.dimensions();

            let current_layer = if index == 0 {
                doc.get_page(first_page).get_layer(first_layer)
            } else {
                let (page_index, layer_index) =
                    doc.add_page(page_width_mm.into(), page_height_mm.into(), "Layer");
                doc.get_page(page_index).get_layer(layer_index)
            };

            let image_data = image.to_rgb8().into_raw();

            let image_file = ImageXObject {
                width: Px(width as usize),
                height: Px(height as usize),
                color_space: ColorSpace::Rgb,
                bits_per_component: ColorBits::Bit8,
                interpolate: false,
                image_data,
                image_filter: None,
                clipping_bbox: None,
                smask: None,
            };
            let image = Image::from(image_file);

            let (img_width, img_height) = (width as f32, height as f32);

            let page_width_px: PX = page_width_mm.into();
            let page_height_px: PX = page_height_mm.into();

            let scale_x = page_width_px.value / img_width;
            let scale_y = page_height_px.value / img_height;
            let scale = scale_x.min(scale_y);

            let translate_x_px = PX::new(0., DPI);
            let translate_y_px = PX::new(
                (page_height_px.value - (img_height * scale)).abs() / 2.,
                DPI,
            );

            let image_transform = ImageTransform {
                translate_x: Some(MM::from(translate_x_px).into()),
                translate_y: Some(MM::from(translate_y_px).into()),
                scale_x: Some(scale),
                scale_y: Some(scale),
                rotate: None,
                dpi: Some(DPI),
            };
            image.add_to_layer(current_layer, image_transform);
        }

        let output_path = self.root_dir.join(format!("{}.pdf", output_name));

        doc.save(&mut BufWriter::new(File::create(output_path)?))?;
        Ok(())
    }

    pub fn convert_sizename_to_size() {}
}