mirror of
https://github.com/storytold/photocraft.git
synced 2026-11-01 05:47:31 -06:00
photocraft-affinity now reads native Affinity documents (container versions 8-12): the FAT and revision chain, stored/zlib/zstd entries with predictors and CRC checks, the tagged object stream in doc.dat, and the document model (pages, artboards, layers, groups, curves, geometric and compound shapes, fills, gradients, strokes, text, placed images, pixel layers, masks and embedded-document caches). Every size is bounded and malformed input returns an error. photocraft-io maps it to an RGB8 document with artboards, groups, shape layers, type layers, embedded smart objects, pixel layers and vector/layer masks; what can't be mapped is listed in the warnings. The embedded preview is now only a fallback when the native data can't be read, and only that preview is refused by Place and warningless imports. `cargo xtask corpus --affinity` pins 21 public CC0/MIT documents, sha256-verified; the io corpus test compares each import with the thumbnail Affinity saved in it (mean difference 0-4.6 of 255). Affinity export is not implemented: no Affinity installation was available to validate written files. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GQPh8UDgMyARR5Hat9UchC
52 lines
1.7 KiB
Rust
52 lines
1.7 KiB
Rust
//! Reader for native Affinity documents (`.af`, and `.afdesign`/`.afphoto`/`.afpub` from
|
|
//! Affinity 1 and 2): the archive container, the tagged object stream in `doc.dat`, and the
|
|
//! subset of the document model PhotoCraft imports.
|
|
//!
|
|
//! Layouts follow the MIT-licensed afread reference and were checked against public documents
|
|
//! saved by Affinity 1.x to 3.x (container versions 8 to 12); see the crate README for provenance
|
|
//! and the exact supported subset. Nothing here writes Affinity files.
|
|
#![forbid(unsafe_code)]
|
|
#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]
|
|
|
|
use std::fmt;
|
|
|
|
mod access;
|
|
pub mod container;
|
|
mod geometry;
|
|
pub mod model;
|
|
pub mod paint;
|
|
pub mod preview;
|
|
mod raster;
|
|
mod shapes;
|
|
pub mod stream;
|
|
#[cfg(any(test, feature = "synth"))]
|
|
pub mod synth;
|
|
mod text;
|
|
|
|
pub use container::{Archive, Limits, MAGIC, is_affinity};
|
|
pub use model::{Document, read};
|
|
pub use preview::{MAX_PREVIEW_BYTES, MAX_PREVIEW_DIMENSION, Preview, preview};
|
|
|
|
/// Why a file could not be read. Messages are short reasons; callers add advice.
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub enum Error {
|
|
Malformed(&'static str),
|
|
Unsupported(&'static str),
|
|
Limit(&'static str),
|
|
}
|
|
|
|
impl fmt::Display for Error {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
match self {
|
|
Self::Malformed(s) => write!(f, "damaged Affinity file: {s}"),
|
|
Self::Unsupported(s) => write!(f, "unsupported Affinity file: {s}"),
|
|
Self::Limit(s) => write!(f, "Affinity file exceeds a safety limit: {s}"),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl std::error::Error for Error {}
|
|
|
|
#[cfg(test)]
|
|
mod tests;
|