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_encoder;
30 /// use tiff_encoder::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_encoder;
62 /// use tiff_encoder::prelude::*;
63 /// use tiff_encoder::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_encoder;
89 /// use tiff_encoder::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}