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_encoder;
154 /// use tiff_encoder::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}