tiff_forge/
file.rs

1use std::fs;
2use std::io;
3use std::path::Path;
4
5use crate::ifd::{AllocatedIfdChain, IfdChain};
6use crate::write::{Cursor, EndianFile, Endianness, OffsetSize};
7
8/// Representation of a Tagged Image File.
9///
10/// This is the central structure of the crate. It holds all the other structures
11/// of the TIFF file and is responsible for writing them to a `fs::File`.
12pub struct TiffFile {
13    header: TiffHeader,
14    ifds: IfdChain<u32>,
15}
16
17impl TiffFile {
18    /// Creates a new `TiffFile` from an [`IfdChain`].
19    ///
20    /// By default, a `TiffFile` is little-endian and has 42 as the magic number.
21    /// If you want to change the endianness, consider chaining this function wih
22    /// [`with_endianness`].
23    ///
24    /// # Examples
25    ///
26    /// Creating the simplest valid `TiffFile`: a single [`Ifd`] with only one entry.
27    /// ```
28    /// #[macro_use]
29    /// extern crate tiff_forge;
30    /// use tiff_forge::prelude::*;
31    ///
32    /// # fn main() {
33    /// let tiff_file = TiffFile::new(
34    ///     Ifd::new()
35    ///         .with_entry(0x0000, BYTE![0])
36    ///         .single()
37    /// );
38    /// # }
39    /// ```
40    /// [`Ifd`]: ifd/struct.Ifd.html
41    /// [`IfdChain`]: ifd/struct.IfdChain.html
42    /// [`with_endianness`]: #method.with_endianness
43    pub fn new(ifds: IfdChain<u32>) -> TiffFile {
44        TiffFile {
45            header: TiffHeader {
46                byte_order: Endianness::II,
47                magic_number: 42,
48            },
49            ifds,
50        }
51    }
52
53    /// Returns the same `TiffFile`, but with the specified `Endianness`.
54    ///
55    /// # Examples
56    ///
57    /// As this method returns `Self`, it can be chained when
58    /// building a `TiffFile`.
59    /// ```
60    /// #[macro_use]
61    /// extern crate tiff_forge;
62    /// use tiff_forge::prelude::*;
63    /// use tiff_forge::write;
64    ///
65    /// # fn main() {
66    /// let tiff_file = TiffFile::new(
67    ///     Ifd::new()
68    ///         .with_entry(0x0000, BYTE![0])
69    ///         .single()
70    /// ).with_endianness(write::Endianness::MM);
71    /// # }
72    /// ```
73    pub fn with_endianness(mut self, endian: Endianness) -> Self {
74        self.header.byte_order = endian;
75        self
76    }
77
78    /// Writes the `TiffFile` content to a new file created at the given path.
79    ///
80    /// Doing so consumes the `TiffFile`. Returns the new `fs::File` wrapped in
81    /// an `io::Result`.
82    ///
83    /// # Examples
84    ///
85    /// Note that, in this example, `file` is a `fs::File`, not a `TiffFile`.
86    /// ```
87    /// #[macro_use]
88    /// extern crate tiff_forge;
89    /// use tiff_forge::prelude::*;
90    ///
91    /// # fn main() {
92    /// let file = TiffFile::new(
93    ///     Ifd::new()
94    ///         .with_entry(0x0000, BYTE![0])
95    ///         .single()
96    /// ).write_to("file.tif").unwrap();
97    /// # }
98    /// ```
99    ///
100    /// # Errors
101    ///
102    /// This method returns the same errors as [`Write::write_all`].
103    ///
104    /// [`Write::write_all`]: https://doc.rust-lang.org/std/io/trait.Write.html#method.write_all
105    ///
106    /// # Panics
107    ///
108    /// This function will `panic` if the file trying to be written would exceed
109    /// the maximum size of a TIFF file (2**32 bytes, or 4 GiB).
110    pub fn write_to<P: AsRef<Path>>(self, file_path: P) -> io::Result<fs::File> {
111        // Create all of the file's parent components if they are missing before
112        // trying to create the file itself.
113        if let Some(dir) = file_path.as_ref().parent() {
114            fs::create_dir_all(dir)?;
115        }
116
117        let file = fs::File::create(file_path)?;
118        // Writing to a file is comprised of two phases: the "Allocating Phase"
119        // and the "Writting Phase". During the first, all the components of the
120        // TiffFile allocate their space and become aware of the offsets to other
121        // components that they might need to know. In the "Writting Phase", the
122        // components actually write their information to the file they've been
123        // allocated to.
124        self.allocate(file).write()
125    }
126
127    /// Allocates all of its components to the given file, transforming
128    /// itself into an `AllocatedTiffFile`.
129    fn allocate(self, file: fs::File) -> AllocatedTiffFile<u32> {
130        let mut c = Cursor::<u32>::new();
131        let header = self.header.allocate(&mut c);
132        let ifds = self.ifds.allocate(&mut c);
133        let file = EndianFile::new(file, header.byte_order);
134
135        AllocatedTiffFile { header, ifds, file }
136    }
137}
138
139/// Representation of a BigTIFF file (64-bit offsets).
140///
141/// This is the central structure for BigTIFF files. It holds all the other structures
142/// of the BigTIFF file and is responsible for writing them to a `fs::File`.
143pub struct BigTiffFile {
144    header: BigTiffHeader,
145    ifds: IfdChain<u64>,
146}
147
148impl BigTiffFile {
149    /// Creates a new `BigTiffFile` from an [`IfdChain`].
150    ///
151    /// By default, a `BigTiffFile` is little-endian and has 43 as the magic number.
152    /// If you want to change the endianness, consider chaining this function with
153    /// [`with_endianness`].
154    ///
155    /// [`IfdChain`]: ifd/struct.IfdChain.html
156    /// [`with_endianness`]: #method.with_endianness
157    pub fn new(ifds: IfdChain<u64>) -> BigTiffFile {
158        BigTiffFile {
159            header: BigTiffHeader {
160                byte_order: Endianness::II,
161                magic_number: 43,
162            },
163            ifds,
164        }
165    }
166
167    /// Returns the same `BigTiffFile`, but with the specified `Endianness`.
168    pub fn with_endianness(mut self, endian: Endianness) -> Self {
169        self.header.byte_order = endian;
170        self
171    }
172
173    /// Writes the `BigTiffFile` content to a new file created at the given path.
174    ///
175    /// Doing so consumes the `BigTiffFile`. Returns the new `fs::File` wrapped in
176    /// an `io::Result`.
177    ///
178    /// # Errors
179    ///
180    /// This method returns the same errors as [`Write::write_all`].
181    ///
182    /// [`Write::write_all`]: https://doc.rust-lang.org/std/io/trait.Write.html#method.write_all
183    pub fn write_to<P: AsRef<Path>>(self, file_path: P) -> io::Result<fs::File> {
184        if let Some(dir) = file_path.as_ref().parent() {
185            fs::create_dir_all(dir)?;
186        }
187
188        let file = fs::File::create(file_path)?;
189        self.allocate(file).write()
190    }
191
192    /// Allocates all of its components to the given file, transforming
193    /// itself into an `AllocatedTiffFile`.
194    fn allocate(self, file: fs::File) -> AllocatedTiffFile<u64> {
195        let mut c = Cursor::<u64>::new();
196        let header = self.header.allocate(&mut c);
197        let ifds = self.ifds.allocate(&mut c);
198        let file = EndianFile::new(file, header.byte_order);
199
200        AllocatedTiffFile { header, ifds, file }
201    }
202}
203
204/// Representation of the Header of a TIFF file.
205struct TiffHeader {
206    byte_order: Endianness,
207    magic_number: u16,
208}
209
210impl TiffHeader {
211    /// Allocates its space, moving the given `Cursor` forwards, and becomes
212    /// aware of the offset to ifd0.
213    ///
214    /// Calling this will transform `self` into an `AllocatedHeader`.
215    fn allocate(self, c: &mut Cursor<u32>) -> AllocatedHeader<u32> {
216        c.allocate(8);
217        AllocatedHeader {
218            byte_order: self.byte_order,
219            magic_number: self.magic_number,
220            offset_to_ifd0: c.allocated_bytes(),
221        }
222    }
223}
224
225/// Representation of the Header of a BigTIFF file.
226struct BigTiffHeader {
227    byte_order: Endianness,
228    magic_number: u16,
229}
230
231impl BigTiffHeader {
232    /// Allocates its space, moving the given `Cursor` forwards, and becomes
233    /// aware of the offset to ifd0.
234    ///
235    /// Calling this will transform `self` into an `AllocatedHeader`.
236    fn allocate(self, c: &mut Cursor<u64>) -> AllocatedHeader<u64> {
237        // BigTIFF header: 8 bytes (byte order + magic + offset size + reserved) + 8 bytes (offset to IFD0)
238        c.allocate(16);
239        AllocatedHeader {
240            byte_order: self.byte_order,
241            magic_number: self.magic_number,
242            offset_to_ifd0: c.allocated_bytes(),
243        }
244    }
245}
246
247/// Representation of a TiffFile that called `allocate(&str)` and is
248/// ready to `write()`.
249///
250/// Generic over `O: OffsetSize` to support both TIFF (u32) and BigTIFF (u64).
251struct AllocatedTiffFile<O: OffsetSize> {
252    header: AllocatedHeader<O>,
253    ifds: AllocatedIfdChain<O>,
254    file: EndianFile,
255}
256
257impl AllocatedTiffFile<u32> {
258    /// Writes all of its components to the file it has been allocated to.
259    fn write(mut self) -> io::Result<fs::File> {
260        self.header.write_to(&mut self.file)?;
261        self.ifds.write_to(&mut self.file)?;
262
263        Ok(self.file.into())
264    }
265}
266
267impl AllocatedTiffFile<u64> {
268    /// Writes all of its components to the file it has been allocated to.
269    fn write(mut self) -> io::Result<fs::File> {
270        self.header.write_to(&mut self.file)?;
271        self.ifds.write_to(&mut self.file)?;
272
273        Ok(self.file.into())
274    }
275}
276
277/// Allocated header for TIFF files.
278///
279/// Generic over `O: OffsetSize` to support both TIFF (u32) and BigTIFF (u64).
280struct AllocatedHeader<O: OffsetSize> {
281    byte_order: Endianness,
282    magic_number: u16,
283    offset_to_ifd0: O,
284}
285
286impl AllocatedHeader<u32> {
287    /// Write this header to the given `EndianFile`.
288    fn write_to(&self, file: &mut EndianFile) -> io::Result<()> {
289        file.write_u16(self.byte_order.id())?;
290        file.write_u16(self.magic_number)?;
291        file.write_u32(self.offset_to_ifd0)?;
292
293        Ok(())
294    }
295}
296
297impl AllocatedHeader<u64> {
298    /// Write this header to the given `EndianFile`.
299    fn write_to(&self, file: &mut EndianFile) -> io::Result<()> {
300        file.write_u16(self.byte_order.id())?;
301        file.write_u16(self.magic_number)?; // 43 for BigTIFF
302        file.write_u16(8)?; // Offset byte size (always 8 for BigTIFF)
303        file.write_u16(0)?; // Reserved (always 0)
304        file.write_u64(self.offset_to_ifd0)?;
305
306        Ok(())
307    }
308}