tiff_forge/ifd/
ifd.rs

1use std::collections::BTreeMap;
2use std::io;
3
4use crate::ifd::tags::{self, FieldTag};
5use crate::ifd::values::{AllocatedFieldValues, FieldValues, OffsetsToIfds};
6use crate::write::{Cursor, EndianFile, OffsetSize};
7
8/// An ordered list of [`Ifd`]s, each pointing to the next one.
9///
10/// The last `Ifd` doesn't point to any other.
11///
12/// Generic over `O: OffsetSize` to support both TIFF (u32) and BigTIFF (u64).
13///
14/// Because any IFD could technically point to a next one, in most
15/// functions that one would expect to input an `Ifd`, its parameters
16/// actually ask for an `IfdChain`.
17///
18/// [`Ifd`]: struct.Ifd.html
19pub struct IfdChain<O: OffsetSize = u32>(Vec<Ifd<O>>);
20
21impl<O: OffsetSize> IfdChain<O> {
22    /// Creates a new `IfdChain` from a vector of [`Ifd`]s.
23    ///  
24    /// # Panics
25    ///
26    /// The TIFF specification requires that each IFD must have at least one entry.
27    ///
28    /// Trying to create an `IfdChain` with one or more empty `Ifd`s will `panic`.
29    ///
30    /// [`Ifd`]: struct.Ifd.html
31    pub fn new(ifds: Vec<Ifd<O>>) -> IfdChain<O> {
32        if ifds.is_empty() {
33            panic!("Cannot create a chain without IFDs.")
34        }
35        for ifd in ifds.iter() {
36            if ifd.entry_count() == 0 {
37                panic!(
38                    "Tried to create a chain containing empty IFDs.\nEach IFD must have at least 1 entry."
39                )
40            }
41        }
42        IfdChain(ifds)
43    }
44
45    /// Creates a new `IfdChain` from a single [`Ifd`].
46    ///
47    /// # Panics
48    ///
49    /// The TIFF specification requires that each IFD must have at least one entry.
50    ///
51    /// Trying to create an `IfdChain` from an empty `Ifd` will `panic`.
52    ///
53    ///
54    /// [`Ifd`]: struct.Ifd.html
55    pub fn single(ifd: Ifd<O>) -> IfdChain<O> {
56        IfdChain::new(vec![ifd])
57    }
58
59    /// Allocates every `Ifd` in the chain, moving the given `Cursor` forwards.
60    ///
61    /// Calling this will transform `self` into an `AllocatedIfdChain`.
62    pub(crate) fn allocate(self, c: &mut Cursor<O>) -> AllocatedIfdChain<O> {
63        let len = self.0.len();
64        let mut ifds = Vec::with_capacity(len);
65        for (index, ifd) in self.0.into_iter().enumerate() {
66            ifds.push(ifd.allocate(c, index + 1 == len));
67        }
68        AllocatedIfdChain(ifds)
69    }
70}
71
72/// An `IfdChain` that called `allocate(&mut Cursor)` and is
73/// ready to write to a file.
74///
75/// Generic over `O: OffsetSize` to support both TIFF (u32) and BigTIFF (u64).
76pub(crate) struct AllocatedIfdChain<O: OffsetSize>(Vec<AllocatedIfd<O>>);
77
78impl AllocatedIfdChain<u32> {
79    /// Write all of the `IFD`s in this chain to the given `EndianFile`.
80    pub(crate) fn write_to(self, file: &mut EndianFile) -> io::Result<()> {
81        for ifd in self.0.into_iter() {
82            ifd.write_to(file)?;
83        }
84        Ok(())
85    }
86}
87
88impl AllocatedIfdChain<u64> {
89    /// Write all of the `IFD`s in this chain to the given `EndianFile`.
90    pub(crate) fn write_to(self, file: &mut EndianFile) -> io::Result<()> {
91        for ifd in self.0.into_iter() {
92            ifd.write_to(file)?;
93        }
94        Ok(())
95    }
96}
97
98/// A structure that holds both an IFD and all the values pointed at
99/// by its entries.
100///
101/// Generic over `O: OffsetSize` to support both TIFF (u32) and BigTIFF (u64).
102///
103/// An image file directory (IFD) contains information about the image, as
104/// well as pointers to the actual image data (both stored as entries).
105///
106/// In a TIFF file, an IFD may point to another IFD with its last 4
107/// bytes. To abstract the user of this crate from the position of each
108/// structure in the file, this link between `Ifd`s is represented by
109/// an [`IfdChain`]. Because any IFD could technically point to a next
110/// one, in most functions that one would expect to input an `Ifd`, its
111/// parameters actually ask for an `IfdChain`.
112///
113/// One can easily create an `IfdChain` of a single `Ifd` calling the
114/// method [`single()`] on that `Ifd`.
115///
116/// [`IfdChain`]: struct.IfdChain.html
117/// [`single()`]: #method.single
118pub struct Ifd<O: OffsetSize = u32> {
119    entries: BTreeMap<FieldTag, Box<dyn FieldValues<O>>>,
120}
121
122impl<O: OffsetSize> Ifd<O> {
123    /// Creates a new empty `Ifd`.
124    ///  
125    /// Note that an empty IFD is prohibited by the TIFF specification.
126    /// As such, it is not possible to directly use the resulting `Ifd`
127    /// alone in the creation of a TIFF file.
128    ///
129    /// However, one can chain this function with methods such as
130    /// [`with_entry(FieldTag, FieldValues)`] in order to build a valid `Ifd`.
131    ///
132    /// [`with_entry(FieldTag, FieldValues)`]: #method.with_entry
133    pub fn new() -> Ifd<O> {
134        Ifd {
135            entries: BTreeMap::new(),
136        }
137    }
138
139    /// Returns the same `Ifd`, but adding the given pair of Tag and Values.
140    ///
141    /// Because it returns `Self`, it is possible to chain this method.
142    ///
143    /// # Examples
144    ///
145    /// Creating a [`TiffFile`] with some arbitrary entries.
146    ///
147    /// Note that the order in which entries are added is irrelevant. Internally,
148    /// the `Ifd` will automatically arrange them by ascending order of tags, as
149    /// specified by the TIFF specification.
150    ///
151    /// ```
152    /// #[macro_use]
153    /// extern crate tiff_forge;
154    /// use tiff_forge::prelude::*;
155    ///
156    /// # fn main() {
157    /// let ifd = Ifd::new()
158    ///     .with_entry(0x0000, BYTE![0])
159    ///     .with_entry(0x00FF, LONG![500])
160    ///     .with_entry(0xA01F, SHORT![50, 2, 0, 3])
161    ///     .with_entry(0x0005, ASCII!["Hello TIFF!"])
162    ///     .with_entry(0x0100, UNDEFINED![0x42, 0x42, 0x42, 0x42]);
163    /// # }
164    /// ```
165    ///
166    /// # Panics
167    ///
168    /// In order to protect the user of this crate, trying to add a value
169    /// to an already existing entry with this method is considered a mistake
170    /// and will `panic`.
171    ///
172    /// Other functions that insert members to the `Ifd` will have an "Entries"
173    /// section, where they'll specify which entries are inserted.
174    ///
175    /// [`TiffFile`]: ../struct.TiffFile.html
176    pub fn with_entry<T: FieldValues<O> + 'static>(mut self, tag: FieldTag, value: T) -> Self {
177        if self.entries.insert(tag, Box::new(value)).is_some() {
178            panic!("Tried to add the same tag twice.");
179        }
180        self
181    }
182
183    /// Returns the same `Ifd`, after adding the specified pairs of Tags and Values.
184    ///
185    /// Because it returns `Self`, it is possible to chain this method.
186    ///
187    /// # Panics
188    ///
189    /// If the inserted entries already exist, this function will `panic`.
190    ///
191    pub fn with_entries<C: IntoIterator<Item = (FieldTag, Box<dyn FieldValues<O>>)>>(
192        mut self,
193        entries: C,
194    ) -> Self {
195        entries.into_iter().for_each(|(tag, value)| {
196            if self.entries.insert(tag, value).is_some() {
197                panic!("Tried to add the same tag twice.");
198            }
199        });
200
201        self
202    }
203
204    /// Returns the same `Ifd`, but adding the given subifds.
205    ///
206    /// Because it returns `Self`, it is possible to chain this method.
207    ///
208    /// # Entries
209    ///
210    /// Using this method will automatically insert the entry 0x014A (tag::SubIFDs).
211    ///
212    /// # Panics
213    ///
214    /// If the inserted entries already exist, this function will `panic`.
215    ///
216    /// [`TiffFile`]: ../struct.TiffFile.html
217    pub fn with_subifds(self, subifds: Vec<IfdChain<O>>) -> Self
218    where
219        OffsetsToIfds<O>: FieldValues<O>,
220    {
221        self.with_entry(tags::SubIFDs, OffsetsToIfds::new(subifds))
222    }
223
224    /// Returns an [`IfdChain`] containing solely this `Ifd`.
225    ///
226    /// In other words, it marks this `Ifd` as the single element
227    /// of its chain.
228    ///
229    /// [`IfdChain`]: struct.IfdChain.html
230    pub fn single(self) -> IfdChain<O> {
231        IfdChain::single(self)
232    }
233
234    /// Returns the number of entries present in this `Ifd`.
235    fn entry_count(&self) -> usize {
236        self.entries.len()
237    }
238
239    /// Returns the number of bytes occupied by this `Ifd` in its binary form.
240    ///
241    /// Note that this only includes the IFD itself, not the values associated
242    /// with it that don't fit in their entry nor the blocks of data pointed at by
243    /// some of the fields.
244    fn size(&self) -> O {
245        // For standard TIFF (u32):
246        //   Entry count: 2 bytes
247        //   Each entry: 12 bytes (tag:2 + type:2 + count:4 + offset/value:4)
248        //   Next IFD offset: 4 bytes
249        //   header_footer = 2 + 4 = 6 bytes
250        //
251        // For BigTIFF (u64):
252        //   Entry count: 8 bytes
253        //   Each entry: 20 bytes (tag:2 + type:2 + count:8 + offset/value:8)
254        //   Next IFD offset: 8 bytes
255        //   header_footer = 8 + 8 = 16 bytes
256        let (entry_size, header_footer) = if O::INLINE_THRESHOLD.to_u64() == 4 {
257            (12usize, 6usize)
258        } else {
259            (20usize, 16usize)
260        };
261        O::from_usize(self.entry_count() * entry_size + header_footer)
262    }
263
264    /// Allocates space in the given `Cursor` for this `Ifd`, as well as
265    /// the field values associated with it that don't fit in their entry.
266    ///
267    /// Becomes aware of the position of the next IFD in its chain (if
268    /// its not the last IFD), thus transforming into an `AllocatedIFd`.
269    fn allocate(self, c: &mut Cursor<O>, last_ifd: bool) -> AllocatedIfd<O> {
270        c.allocate(self.size());
271
272        let mut entries = BTreeMap::new();
273        for (tag, value) in self.entries {
274            entries.insert(tag, value.allocate(c));
275        }
276
277        let offset_to_next_ifd = if last_ifd {
278            None
279        } else {
280            Some(c.allocated_bytes())
281        };
282
283        AllocatedIfd {
284            entries,
285            offset_to_next_ifd,
286        }
287    }
288}
289
290impl<O: OffsetSize> Default for Ifd<O> {
291    fn default() -> Self {
292        Self::new()
293    }
294}
295
296/// Representation of a `Ifd` that called `allocate(&mut Cursor, bool)` and is
297/// ready to write to a file.
298///
299/// Generic over `O: OffsetSize` to support both TIFF (u32) and BigTIFF (u64).
300struct AllocatedIfd<O: OffsetSize> {
301    entries: BTreeMap<FieldTag, Box<dyn AllocatedFieldValues<O>>>,
302    offset_to_next_ifd: Option<O>,
303}
304
305impl AllocatedIfd<u32> {
306    /// Write this IFD to the given `EndianFile`, as well as any values
307    /// associated with its entries.
308    fn write_to(self, file: &mut EndianFile) -> io::Result<()> {
309        let mut big_values = Vec::new();
310
311        file.write_u16(self.entries.len() as u16)?;
312
313        for (tag, value) in self.entries.into_iter() {
314            let value = Self::write_entry_to((tag, value), file)?;
315            if let Some(value) = value {
316                big_values.push(value);
317            }
318        }
319        file.write_u32(self.offset_to_next_ifd.unwrap_or(0))?;
320
321        for value in big_values {
322            value.write_to(file)?;
323        }
324
325        Ok(())
326    }
327
328    /// Write a single entry of the IFD. If its value doesn't fit,
329    /// returns that value back so it can be written later, after
330    /// the IFD.
331    fn write_entry_to(
332        (tag, value): (FieldTag, Box<dyn AllocatedFieldValues<u32>>),
333        file: &mut EndianFile,
334    ) -> io::Result<Option<Box<dyn AllocatedFieldValues<u32>>>> {
335        file.write_u16(tag)?;
336        file.write_u16(value.type_id())?;
337        file.write_u32(value.count())?;
338
339        match value.position() {
340            Some(position) => {
341                file.write_u32(position)?;
342                Ok(Some(value))
343            }
344            None => {
345                let size = value.size();
346                value.write_to(file)?;
347                for _ in 0..(4 - size) {
348                    file.write_u8(0)?;
349                }
350                Ok(None)
351            }
352        }
353    }
354}
355
356impl AllocatedIfd<u64> {
357    /// Write this IFD to the given `EndianFile`, as well as any values
358    /// associated with its entries.
359    fn write_to(self, file: &mut EndianFile) -> io::Result<()> {
360        let mut big_values = Vec::new();
361
362        file.write_u64(self.entries.len() as u64)?;
363
364        for (tag, value) in self.entries.into_iter() {
365            let value = Self::write_entry_to((tag, value), file)?;
366            if let Some(value) = value {
367                big_values.push(value);
368            }
369        }
370        file.write_u64(self.offset_to_next_ifd.unwrap_or(0))?;
371
372        for value in big_values {
373            value.write_to(file)?;
374        }
375
376        Ok(())
377    }
378
379    /// Write a single entry of the IFD. If its value doesn't fit,
380    /// returns that value back so it can be written later, after
381    /// the IFD.
382    fn write_entry_to(
383        (tag, value): (FieldTag, Box<dyn AllocatedFieldValues<u64>>),
384        file: &mut EndianFile,
385    ) -> io::Result<Option<Box<dyn AllocatedFieldValues<u64>>>> {
386        file.write_u16(tag)?;
387        file.write_u16(value.type_id())?;
388        file.write_u64(value.count())?;
389
390        match value.position() {
391            Some(position) => {
392                file.write_u64(position)?;
393                Ok(Some(value))
394            }
395            None => {
396                let size = value.size();
397                value.write_to(file)?;
398                for _ in 0..(8 - size) {
399                    file.write_u8(0)?;
400                }
401                Ok(None)
402            }
403        }
404    }
405}