From 4ecdcd78dace41e6098512a663cdf62d20e372e1 Mon Sep 17 00:00:00 2001 From: "Dylan M. Taylor" Date: Fri, 13 Nov 2026 00:03:41 -0500 Subject: [PATCH] Export common raster codec facade --- Cargo.lock | 8 ---- Cargo.toml | 1 - crates/raster-codecs/src/common.rs | 16 +++----- crates/raster-codecs/src/lib.rs | 2 + crates/versioned-json/Cargo.toml | 15 ------- crates/versioned-json/src/lib.rs | 63 ------------------------------ 6 files changed, 7 insertions(+), 98 deletions(-) delete mode 100644 crates/versioned-json/Cargo.toml delete mode 100644 crates/versioned-json/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 81cc73b2c..4b671fb2e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2344,14 +2344,6 @@ dependencies = [ "kurbo 0.13.1", ] -[[package]] -name = "craft-versioned-json" -version = "0.1.0" -dependencies = [ - "serde_json", - "thiserror 2.0.21", -] - [[package]] name = "craft-video" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 5519f216b..629bab64c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,6 @@ craft-aep = { path = "crates/aep" } craft-affinity = { path = "crates/affinity" } craft-ai-core = { path = "crates/ai-core" } craft-pdf = { path = "crates/pdf", version = "=0.1.0" } -craft-versioned-json = { path = "crates/versioned-json" } craft-pdn = { path = "crates/pdn" } craft-plate-test = { path = "crates/plate-test" } krilla = { version = "=0.8.2", default-features = false, features = ["raster-images"] } diff --git a/crates/raster-codecs/src/common.rs b/crates/raster-codecs/src/common.rs index b12fb8b0a..b653db687 100644 --- a/crates/raster-codecs/src/common.rs +++ b/crates/raster-codecs/src/common.rs @@ -24,8 +24,7 @@ pub struct DecodeLimits { impl Default for DecodeLimits { fn default() -> Self { - Self { max_file_bytes: 64 * 1024 * 1024, max_width: 10_000, max_height: 10_000, - max_pixels: 16_000_000, max_alloc_bytes: 256 * 1024 * 1024 } + Self { max_file_bytes: 64 * 1024 * 1024, max_width: 10_000, max_height: 10_000, max_pixels: 16_000_000, max_alloc_bytes: 256 * 1024 * 1024 } } } @@ -38,8 +37,7 @@ pub fn decode_rgba(bytes: &[u8], limits: DecodeLimits) -> Result Result>, CodecError> { - let mut reader = ImageReader::new(Cursor::new(bytes)).with_guessed_format() - .map_err(|e| CodecError::malformed("image", e))?; + let mut reader = ImageReader::new(Cursor::new(bytes)).with_guessed_format().map_err(|e| CodecError::malformed("image", e))?; if reader.format().is_none() { return Err(CodecError::unsupported("image", "unrecognised image format")); } @@ -71,22 +69,18 @@ pub fn encode_jpeg(pixels: &RgbaImage, quality: u8) -> Result, CodecErro } let rgb = image::DynamicImage::ImageRgba8(pixels.clone()).to_rgb8(); let mut out = Cursor::new(Vec::new()); - image::codecs::jpeg::JpegEncoder::new_with_quality(&mut out, quality) - .encode_image(&rgb).map_err(|e| CodecError::encode("JPEG", e))?; + image::codecs::jpeg::JpegEncoder::new_with_quality(&mut out, quality).encode_image(&rgb).map_err(|e| CodecError::encode("JPEG", e))?; Ok(out.into_inner()) } fn is_heif(bytes: &[u8]) -> bool { - bytes.get(4..8) == Some(b"ftyp") - && matches!(bytes.get(8..12), Some(b"heic" | b"heix" | b"hevc" | b"mif1" | b"msf1" | b"avif")) + bytes.get(4..8) == Some(b"ftyp") && matches!(bytes.get(8..12), Some(b"heic" | b"heix" | b"hevc" | b"mif1" | b"msf1" | b"avif")) } fn check_dimensions(width: u32, height: u32, limits: DecodeLimits) -> Result<(), CodecError> { - let pixels = u64::from(width).checked_mul(u64::from(height)) - .ok_or_else(|| CodecError::LimitExceeded("image dimensions overflow".into()))?; + let pixels = u64::from(width).checked_mul(u64::from(height)).ok_or_else(|| CodecError::LimitExceeded("image dimensions overflow".into()))?; if width == 0 || height == 0 || width > limits.max_width || height > limits.max_height || pixels > limits.max_pixels { return Err(CodecError::LimitExceeded(format!("image dimensions {width}x{height} exceed configured limits"))); } Ok(()) } - diff --git a/crates/raster-codecs/src/lib.rs b/crates/raster-codecs/src/lib.rs index 880a32c6c..5b335a30c 100644 --- a/crates/raster-codecs/src/lib.rs +++ b/crates/raster-codecs/src/lib.rs @@ -18,6 +18,8 @@ #![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)] +/// Common bounded decoding and PNG/JPEG encoding for common image formats. +pub mod common; pub mod dds; mod dds_bc; pub mod dicom; diff --git a/crates/versioned-json/Cargo.toml b/crates/versioned-json/Cargo.toml deleted file mode 100644 index 1b1bbd354..000000000 --- a/crates/versioned-json/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[package] -name = "craft-versioned-json" -description = "Reusable named and versioned JSON document envelopes" -version.workspace = true -edition.workspace = true -license.workspace = true -rust-version.workspace = true -repository.workspace = true - -[dependencies] -serde_json = { workspace = true } -thiserror = { workspace = true } - -[lints] -workspace = true diff --git a/crates/versioned-json/src/lib.rs b/crates/versioned-json/src/lib.rs deleted file mode 100644 index f100920fb..000000000 --- a/crates/versioned-json/src/lib.rs +++ /dev/null @@ -1,63 +0,0 @@ -//! Helpers for named, versioned JSON documents whose payload is an object. -//! -//! The library checks and writes the common envelope. Each application owns its payload schema, -//! migrations and validation. - -#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)] - -use serde_json::{Map, Value}; - -/// A recognized document header. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Header { - pub name: String, - pub version: u64, -} - -/// Header errors, kept independent of an application's schema and error type. -#[derive(Debug, thiserror::Error, PartialEq, Eq)] -pub enum Error { - #[error("missing or invalid `{0}` header")] - Missing(String), - #[error("expected format `{expected}`, found `{found}`")] - WrongName { expected: String, found: String }, - #[error("format version {found} is unsupported; this build reads {expected}")] - UnsupportedVersion { found: u64, expected: u64 }, -} - -/// Checks `root[header_key]` for the expected name and exact supported version. -pub fn read_header(root: &Value, header_key: &str, expected_name: &str, expected_version: u64) -> Result { - let header = root.get(header_key).ok_or_else(|| Error::Missing(header_key.to_owned()))?; - let name = header.get("name").and_then(Value::as_str).ok_or_else(|| Error::Missing(header_key.to_owned()))?; - let version = header.get("version").and_then(Value::as_u64).ok_or_else(|| Error::Missing(header_key.to_owned()))?; - if name != expected_name { - return Err(Error::WrongName { expected: expected_name.to_owned(), found: name.to_owned() }); - } - if version != expected_version { - return Err(Error::UnsupportedVersion { found: version, expected: expected_version }); - } - Ok(Header { name: name.to_owned(), version }) -} - -/// Adds a named/versioned header to a JSON object, creating the object if needed. -pub fn write_header(root: &mut Value, header_key: &str, name: &str, version: u64) -> Result<(), Error> { - if !root.is_object() { - *root = Value::Object(Map::new()); - } - let object = root.as_object_mut().ok_or_else(|| Error::Missing(header_key.to_owned()))?; - object.insert(header_key.to_owned(), serde_json::json!({ "name": name, "version": version })); - Ok(()) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn reads_and_writes_headers() { - let mut root = serde_json::json!({ "payload": true }); - write_header(&mut root, "format", "example", 3).unwrap(); - assert_eq!(read_header(&root, "format", "example", 3).unwrap().version, 3); - assert_eq!(read_header(&root, "format", "example", 4), Err(Error::UnsupportedVersion { found: 3, expected: 4 })); - } -}