// SPDX-License-Identifier: GPL-3.0-or-later // src/app/document/mod.rs // // Document module root: common enums and type erasure for document kinds. pub mod file; pub mod meta; pub mod portable; pub mod raster; pub mod transform; pub mod utils; pub mod vector; use cosmic::iced::widget::image as iced_image; use cosmic::iced_renderer::graphics::image::image_rs::ImageFormat as CosmicImageFormat; use std::fmt; use std::path::Path; use self::portable::PortableDocument; use self::raster::RasterDocument; use self::vector::VectorDocument; /// High-level classification of documents. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum DocumentKind { Raster, Vector, Portable, } /// Unified document type used by the application. pub enum DocumentContent { Raster(RasterDocument), Vector(VectorDocument), Portable(PortableDocument), } impl fmt::Debug for DocumentContent { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { DocumentContent::Raster(_) => f.write_str("DocumentContent::Raster(..)"), DocumentContent::Vector(_) => f.write_str("DocumentContent::Vector(..)"), DocumentContent::Portable(_) => f.write_str("DocumentContent::Portable(..)"), } } } impl DocumentKind { /// Derive document kind from file extension. /// /// - `pdf` => Portable /// - `svg` => Vector /// - supported image extensions (via libcosmic/image_rs ImageFormat) /// => Raster /// /// Returns `None` if the extension is not recognized as any supported kind. pub fn from_path(path: &Path) -> Option { let ext_os = path.extension()?; let ext_str = ext_os.to_str()?; let ext_lower = ext_str.to_ascii_lowercase(); match ext_lower.as_str() { "pdf" => return Some(DocumentKind::Portable), "svg" => return Some(DocumentKind::Vector), _ => {} } // Ask libcosmic/image_rs if this extension corresponds to a known image // format. If yes, we treat it as a raster document. if CosmicImageFormat::from_extension(ext_os).is_some() { return Some(DocumentKind::Raster); } None } } impl DocumentContent { /// Returns a cloneable image handle for rendering. /// /// This is intentionally linear: every concrete document type /// owns some kind of `iced_image::Handle`, and the canvas can /// just call `doc.handle()` without additional branching. pub fn handle(&self) -> iced_image::Handle { match self { DocumentContent::Raster(doc) => doc.handle.clone(), DocumentContent::Vector(doc) => doc.handle.clone(), DocumentContent::Portable(doc) => doc.handle.clone(), } } /// Returns the native dimensions (width, height) of the document in pixels. /// /// For raster images this is the actual pixel size. /// For vector/portable documents this is the rasterized size at default DPI. pub fn dimensions(&self) -> (u32, u32) { match self { DocumentContent::Raster(doc) => doc.dimensions(), DocumentContent::Vector(doc) => doc.dimensions(), DocumentContent::Portable(doc) => doc.dimensions(), } } }