// SPDX-License-Identifier: GPL-3.0-or-later // src/document/loader.rs // // Document loading: format detection, format dispatch, and directory scanning. use crate::document::model::{PortableModel, RasterModel, VectorModel}; use crate::document::session::data::{SessionData, SessionItem}; use crate::document::types::PageLayout; use crate::document::DocumentContent; use crate::error::{Error, Result}; use std::path::Path; /// The three document formats Noctua understands. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub(crate) enum DocumentFormat { /// Raster images – anything the image crate can decode. Raster, /// Vector graphics (SVG). Vector, /// Portable documents (PDF). Portable, } /// Detects the document format from the file extension. /// /// Returns `None` for unknown extensions so directory scans can skip /// unsupported files early. At load time the underlying library /// (`ImageReader`, `usvg`, `pdfium`) is the final authority. fn detect_format(path: &Path) -> Option { match path .extension() .and_then(|e| e.to_str()) .map(str::to_lowercase) .as_deref() { Some("pdf") => Some(DocumentFormat::Portable), Some("svg") => Some(DocumentFormat::Vector), Some("png" | "jpg" | "jpeg" | "webp" | "gif" | "bmp" | "tiff" | "tif") => { Some(DocumentFormat::Raster) } _ => None, } } /// Loads a document from a path and returns its content. /// /// # Errors /// /// Returns [`Error::NotFound`] if the path does not exist. /// Returns [`Error::Unsupported`] if the file extension is not recognised. pub(crate) fn load_document(path: &Path) -> Result { if !path.exists() { return Err(Error::NotFound(format!("{}", path.display()))); } match detect_format(path) { Some(DocumentFormat::Portable) => { // TODO: Replace with actual pdfium-render loading. Ok(DocumentContent::Portable(PortableModel { path: path.to_path_buf(), page_count: 3, layout: PageLayout::new(vec![(595.0, 842.0); 3], 10.0), })) } Some(DocumentFormat::Vector) => { // TODO: Replace with actual usvg parsing. Ok(DocumentContent::Vector(VectorModel { path: path.to_path_buf(), view_box: None, })) } Some(DocumentFormat::Raster) => { // TODO: Replace with actual image-crate loading. Ok(DocumentContent::Raster(RasterModel { path: path.to_path_buf(), dimensions: (1920, 1080), })) } None => Err(Error::Unsupported(format!( "extension: {}", path.extension() .and_then(|e| e.to_str()) .unwrap_or("(none)") ))), } } /// Scans a directory for supported files and populates the session. pub(crate) fn scan_directory_into(session: &mut SessionData, dir: &Path) { if let Ok(entries) = std::fs::read_dir(dir) { let mut paths: Vec<_> = entries .filter_map(std::result::Result::ok) .map(|e| e.path()) .filter(|p| p.is_file() && detect_format(p).is_some()) .collect(); paths.sort(); session.items = paths.into_iter().map(|p| SessionItem::new(p, 0)).collect(); } }