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 6a2d0ff..0ed8701 100644 --- a/src/directory.rs +++ b/src/directory.rs @@ -104,6 +104,28 @@ impl Directory { } } } + + pub(crate) fn encoded_len(&self) -> u64 { + let (len_count, len_offset); + + match std::mem::size_of::() { + offset @ 4 => { + // non-BigTiff uses a `u16` here. + len_count = 2; + len_offset = offset as u64; + } + offset => { + let offset = offset as u64; + // assuming BigTIFF + len_count = offset; + len_offset = offset; + } + } + + // Each entry is 12 bytes in classic TIFF, 20 bytes in BigTIFF. + let entry_bytes = 4 + 2 * len_offset; + len_count + entry_bytes * self.entries.len() as u64 + len_offset + } } impl fmt::Debug for Directory { @@ -118,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 a7854fa..ffea51b 100644 --- a/src/encoder/mod.rs +++ b/src/encoder/mod.rs @@ -74,10 +74,10 @@ impl Compression { /// Encoder for Tiff and BigTiff files. /// -/// With this type you can get a `DirectoryEncoder` or a `ImageEncoder` +/// With this type you can get a [`DirectoryEncoder`] or a [`ImageEncoder`] /// to encode Tiff/BigTiff ifd directories with images. /// -/// See `DirectoryEncoder` and `ImageEncoder`. +/// See [`DirectoryEncoder`] and [`ImageEncoder`]. /// /// # Examples /// ``` @@ -171,7 +171,7 @@ impl TiffEncoder { Self::chain_directory(&mut self.writer, &mut self.last_ifd_chain) } - /// Create a [`DirectoryEncoder`] to encode an ifd directory. + /// Create a [`DirectoryEncoder`] to encode an ifd (directory). /// /// The caller is responsible for ensuring that the directory is a valid image in the main TIFF /// IFD sequence. To encode additional directories that are not linked into the sequence, use @@ -180,7 +180,7 @@ impl TiffEncoder { Self::chain_directory(&mut self.writer, &mut self.last_ifd_chain) } - /// Create a [`DirectoryEncoder`] to encode an ifd directory. + /// Create a [`DirectoryEncoder`] to encode a non-image directory. /// /// The directory is not linked into the sequence of directories. For instance, encode Exif /// directories or SubIfd directories with this method. @@ -244,6 +244,12 @@ pub struct DirectoryEncoder<'a, W: 'a + Write + Seek, K: TiffKind> { } /// The offset of an encoded directory in the file. +/// +/// Both the `offset` and `pointer` field are a complete description of the start offset of the +/// dictionary which is used to point to it. The struct also describes the extent covered by the +/// encoding of the directory. The constructors [`new`][DirectoryOffset::new] allow the checked +/// conversion. Note that even though the offsets are public do not expect their information to +/// propagate when modified. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct DirectoryOffset { /// The start of the directory as a Tiff value. @@ -255,7 +261,9 @@ pub struct DirectoryOffset { pub offset: K::OffsetType, /// The start of the directory as a pure offset. pub pointer: IfdPointer, - /// The offset of its sequence link field, in our private representation. + /// The offset of its sequence link field, in our private representation. Before exposing this + /// make sure that we don't want any invariants (e.g. pointer being smaller than this). The + /// type should be fine. ifd_chain: NonZeroU64, /// The kind of Tiff file the offset is for. kind: PhantomData, @@ -313,6 +321,17 @@ impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> { Ok(offset) } + /// Insert previously written key-value entries. + /// + /// It is the caller's responsibility to ensure that the data referred to by the entries is + /// actually (or will be) written to the file. Note that small values are necessarily inline in + /// the entry, the size limit depends on the Tiff kind. So do not confuse a directory from a + /// BigTiff in a standard tiff or the other way around. + pub fn extend_from(&mut self, dir: &Directory) { + let entries = dir.iter().map(|(tag, val)| (tag, val.clone())); + self.directory.extend(entries); + } + /// Define the parent directory. /// /// Each directory has an offset based link to its successor, forming a linked list in the @@ -329,6 +348,11 @@ impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> { Ok(()) } + /// Write the IFD to the file and return the offset it is written to. + /// + /// The offset can be used as the value, or as part of a list of values, in the entry of + /// another directory. If you're constructing the entry directly you'll want to use an + /// appropriate type variant for this such as [`Type::IFD`]. pub fn finish_with_offsets(mut self) -> TiffResult> { self.finish_internal() } @@ -786,6 +810,71 @@ pub trait TiffKind { fn convert_slice(slice: &[Self::OffsetType]) -> &Self::OffsetArrayType; } +impl DirectoryOffset { + /// Construct the directory pointer information from its raw offset. + /// + /// Fails in these cases: + /// + /// - if start offset is too large to be represented with the chosen TIFF format, i.e. a value + /// larger than [`u32::MAX`] with a standard tiff which is only valid in a BigTiff. This + /// returns [`TiffError::IntSizeError`]. + /// - 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)?; + /// + /// // … 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 reconstructed = DirectoryOffset::new(known_offset, &known_contents)?; + /// + /// // 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)?; + let encoded = dir.encoded_len::(); + let offset_field_offset = encoded - mem::size_of_val(&offset) as u64; + + let ifd_chain = pointer + .0 + .checked_add(offset_field_offset) + .and_then(NonZeroU64::new) + .ok_or(TiffError::UsageError(UsageError::ZeroIfdPointer))?; + + Ok(DirectoryOffset { + pointer, + offset, + ifd_chain, + kind: PhantomData, + }) + } +} + /// Create a standard Tiff file. pub struct TiffKindStandard; @@ -854,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); +} diff --git a/src/error.rs b/src/error.rs index c3ebefe..4fe7d3a 100644 --- a/src/error.rs +++ b/src/error.rs @@ -187,6 +187,9 @@ quick_error! { InsufficientOutputBufferSize { needed: usize, provided: usize } { display("the borrowed output buffer is not large enough for the decoded data, needed {needed} but have {provided}") } + ZeroIfdPointer { + display("the offset 0 can not point to a valid IFD") + } } }