2026-03-07 20:29:18 +01:00
|
|
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
|
|
// src/document/loader.rs
|
|
|
|
|
|
//
|
2026-03-08 17:41:05 +01:00
|
|
|
|
// Document loading: format detection, format dispatch, and directory scanning.
|
2026-03-07 20:29:18 +01:00
|
|
|
|
|
2026-03-08 17:41:05 +01:00
|
|
|
|
use crate::document::model::{PortableModel, RasterModel, VectorModel};
|
|
|
|
|
|
use crate::document::session::data::{SessionData, SessionItem};
|
|
|
|
|
|
use crate::document::types::PageLayout;
|
|
|
|
|
|
use crate::document::DocumentContent;
|
2026-03-07 20:29:18 +01:00
|
|
|
|
use crate::error::{Error, Result};
|
|
|
|
|
|
use std::path::Path;
|
|
|
|
|
|
|
2026-03-08 17:41:05 +01:00
|
|
|
|
/// 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<DocumentFormat> {
|
|
|
|
|
|
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,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-07 20:29:18 +01:00
|
|
|
|
/// Loads a document from a path and returns its content.
|
2026-03-08 17:41:05 +01:00
|
|
|
|
///
|
|
|
|
|
|
/// # 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<DocumentContent> {
|
2026-03-07 20:29:18 +01:00
|
|
|
|
if !path.exists() {
|
2026-03-08 17:41:05 +01:00
|
|
|
|
return Err(Error::NotFound(format!("{}", path.display())));
|
2026-03-07 20:29:18 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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.
|
2026-03-08 17:41:05 +01:00
|
|
|
|
pub(crate) fn scan_directory_into(session: &mut SessionData, dir: &Path) {
|
2026-03-07 20:29:18 +01:00
|
|
|
|
if let Ok(entries) = std::fs::read_dir(dir) {
|
|
|
|
|
|
let mut paths: Vec<_> = entries
|
2026-03-08 17:41:05 +01:00
|
|
|
|
.filter_map(std::result::Result::ok)
|
2026-03-07 20:29:18 +01:00
|
|
|
|
.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();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|