2026-01-07 20:42:28 +01:00
|
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
2026-01-07 20:22:49 +01:00
|
|
|
// 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;
|
2026-01-08 12:18:13 +01:00
|
|
|
use std::path::Path;
|
2026-01-07 20:22:49 +01:00
|
|
|
|
|
|
|
|
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<Self> {
|
|
|
|
|
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(),
|
|
|
|
|
}
|
|
|
|
|
}
|
Implement comprehensive metadata extraction for raster images with
EXIF support and display in the right panel.
New features:
- Extract basic metadata (filename, format, resolution, file size, color type)
- Parse EXIF data (camera, date, exposure, aperture, ISO, focal length, GPS)
- Display metadata in collapsible right panel (toggle with 'i' key)
- Auto-refresh metadata on document navigation
Changes by file:
Cargo.toml, Cargo.lock:
- Add kamadak-exif dependency for EXIF parsing
i18n/en/noctua.ftl:
- Add translation strings for all metadata labels
src/app/document/meta.rs:
- New module for metadata types (BasicMeta, ExifMeta, DocumentMeta)
- Extraction logic with EXIF parsing via kamadak-exif
- Helper methods for formatted display (resolution, file size, camera, GPS)
src/app/document/mod.rs:
- Re-export meta module
src/app/document/{raster,vector,portable}.rs:
- Add extract_metadata() method stubs (full impl for raster)
src/app/document/file.rs:
- Reset metadata on document change
src/app/message.rs:
- Add ToggleRightPanel and RefreshMetadata messages
src/app/model.rs:
- Add metadata: Option<DocumentMeta> field
- Add show_right_panel: bool field
src/app/update.rs:
- Handle panel toggle and metadata refresh
- Auto-refresh metadata on navigation when panel visible
src/app/view/panels.rs:
- Implement right_panel() with metadata display
- Conditional sections for basic info and EXIF data
src/app/view/canvas.rs:
- Integrate right panel into layout"
2026-01-10 11:46:07 +01:00
|
|
|
/// Extract metadata from the document.
|
|
|
|
|
/// This may involve file I/O for EXIF data, so call lazily.
|
|
|
|
|
pub fn extract_meta(&self) -> meta::DocumentMeta {
|
|
|
|
|
match self {
|
|
|
|
|
DocumentContent::Raster(doc) => doc.extract_meta(),
|
|
|
|
|
DocumentContent::Vector(doc) => doc.extract_meta(),
|
|
|
|
|
DocumentContent::Portable(doc) => doc.extract_meta(),
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-01-07 20:22:49 +01:00
|
|
|
}
|