From eb6275547d9a68c84311145f4b47aaed51b07a00 Mon Sep 17 00:00:00 2001 From: "A. Molzer" <5550310+197g@users.noreply.github.com> Date: Sat, 8 Nov 2025 01:11:39 +0100 Subject: [PATCH] Build Directory FromIterator This enables a test for the encoder's DirectoryOffset but it seems to fit nicely with the current design, too. --- CHANGES.md | 4 ++++ src/decoder/ifd.rs | 8 +++++++ src/directory.rs | 8 +++++++ src/encoder/mod.rs | 58 +++++++++++++++++++++++++++++++++++++++++++--- 4 files changed, 75 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index e0326c5..f816018 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,7 @@ +# Version 0.10.4 (unreleased) + +- `Directory` now implements `FromIterator<(Tag, Value)>`. + # Version 0.10.3 New features: diff --git a/src/decoder/ifd.rs b/src/decoder/ifd.rs index 46547b9..b1c63e5 100644 --- a/src/decoder/ifd.rs +++ b/src/decoder/ifd.rs @@ -332,6 +332,12 @@ impl Value { } } +/// A combination of type, count, and offset. +/// +/// In a TIFF the data offset portion of an entry is used for inline data in case the length of the +/// encoded value does not exceed the size of the offset field. Since the size of the offset field +/// depends on the file kind (4 bytes for standard TIFF, 8 bytes for BigTIFF) the interpretation of +/// this struct is only complete in combination with file metadata. #[derive(Clone)] pub struct Entry { type_: Type, @@ -349,12 +355,14 @@ impl ::std::fmt::Debug for Entry { } impl Entry { + /// Create a new entry fit to be added to a standard TIFF IFD. pub fn new(type_: Type, count: u32, offset: [u8; 4]) -> Entry { let mut entry_off = [0u8; 8]; entry_off[..4].copy_from_slice(&offset); Entry::new_u64(type_, count.into(), entry_off) } + /// Create a new entry with data for a Big TIFF IFD. pub fn new_u64(type_: Type, count: u64, offset: [u8; 8]) -> Entry { Entry { type_, diff --git a/src/directory.rs b/src/directory.rs index 0d7e308..0ed8701 100644 --- a/src/directory.rs +++ b/src/directory.rs @@ -140,6 +140,14 @@ impl fmt::Debug for Directory { } } +impl core::iter::FromIterator<(Tag, Entry)> for Directory { + fn from_iter>(iter: T) -> Self { + let mut dir = Directory::empty(); + dir.extend(iter); + dir + } +} + #[cfg(test)] mod tests { use super::Directory; diff --git a/src/encoder/mod.rs b/src/encoder/mod.rs index 302a814..ffea51b 100644 --- a/src/encoder/mod.rs +++ b/src/encoder/mod.rs @@ -821,17 +821,39 @@ impl DirectoryOffset { /// - the extent of the directory would overflow the file length. This returns a /// [`TiffError::UsageError`]. /// + /// The library does *not* validate the contents or offset in any way, it just performs the + /// necessary math assuming the data is an accurate representation of existing content. + /// /// # Examples /// + /// If you have any custom way of writing a directory to a file that can not go through the + /// usual [`TiffEncoder::extra_directory`] then you can reconstruct the offset information as + /// long as you provide all the necessary data to the library. You can then use it as a parent + /// directory, i.e. have the library overwrite its `next` field. + /// /// ``` + /// use tiff::{ + /// encoder::{TiffEncoder, DirectoryOffset}, + /// Directory, + /// tags::IfdPointer, + /// }; + /// /// # let mut file = std::io::Cursor::new(Vec::new()); /// let mut tiff = TiffEncoder::new(&mut file)?; /// - /// // … write custom data with + /// // … some custom data writes, assume we know where a directory was written + /// # fn reconstruction_of_your_directory() -> Directory { Directory::from_iter([]) } + /// let known_offset = IfdPointer(1024); + /// let known_contents: Directory = reconstruction_of_your_directory(); /// - /// let also_dir = DirectoryOffset::new(&known_)?; + /// let reconstructed = DirectoryOffset::new(known_offset, &known_contents)?; /// - /// # Ok::<_, crate::error::TiffError>(()) + /// // Now we can use it as if the directory was written by the `TiffEncoder` itself. + /// let mut chain = tiff.extra_directory()?; + /// chain.set_parent(&reconstructed); + /// chain.finish_with_offsets()?; + /// + /// # Ok::<_, tiff::TiffError>(()) /// ``` pub fn new(pointer: IfdPointer, dir: &Directory) -> TiffResult { let offset = K::convert_offset(pointer.0)?; @@ -921,3 +943,33 @@ impl TiffKind for TiffKindBig { slice } } + +#[test] +fn directory_offset_new_equivalent_to_writing() { + type K = TiffKindStandard; + + let dir = Directory::from_iter(vec![ + ( + Tag::ImageWidth, + Entry::new(Type::LONG, 1, 100u32.to_ne_bytes()), + ), + ( + Tag::ImageLength, + Entry::new(Type::LONG, 1, 200u32.to_ne_bytes()), + ), + ]); + + let data = std::io::Cursor::new(vec![]); + let mut file = TiffEncoder::new(data).unwrap(); + + let mut as_dir_encoder = file.extra_directory().unwrap(); + as_dir_encoder.extend_from(&dir); + let dir_extent = as_dir_encoder.finish_with_offsets().unwrap(); + + // Check we can recreate the extent had we written ourselves. + let synth = DirectoryOffset::::new(dir_extent.pointer, &dir).unwrap(); + + assert_eq!(synth.pointer, dir_extent.pointer); + assert_eq!(synth.offset, dir_extent.offset); + assert_eq!(synth.ifd_chain, dir_extent.ifd_chain); +}