tiff_encoder/
file.rs

1use std::fs;
2use std::io;
3use std::path::Path;
4
5use crate::ifd::{AllocatedIfdChain, IfdChain};
6use crate::write::{Cursor, EndianFile, Endianness};
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,
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) -> TiffFile {
44        TiffFile {
45            header: TiffHeader {
46                byte_order: Endianness::II,
47                magic_number: 42,
48            },
49
50            ifds: ifds,
51        }
52    }
53
54    /// Returns the same `TiffFile`, but with the specified `Endianness`.
55    ///
56    /// # Examples
57    ///
58    /// As this method returns `Self`, it can be chained when
59    /// building a `TiffFile`.
60    /// ```
61    /// #[macro_use]
62    /// extern crate tiff_encoder;
63    /// use tiff_encoder::prelude::*;
64    /// use tiff_encoder::write;
65    ///
66    /// # fn main() {
67    /// let tiff_file = TiffFile::new(
68    ///     Ifd::new()
69    ///         .with_entry(0x0000, BYTE![0])
70    ///         .single()
71    /// ).with_endianness(write::Endianness::MM);
72    /// # }
73    /// ```
74    pub fn with_endianness(mut self, endian: Endianness) -> Self {
75        self.header.byte_order = endian;
76        self
77    }
78
79    /// Writes the `TiffFile` content to a new file created at the given path.
80    ///
81    /// Doing so consumes the `TiffFile`. Returns the new `fs::File` wrapped in
82    /// an `io::Result`.
83    ///
84    /// # Examples
85    ///
86    /// Note that, in this example, `file` is a `fs::File`, not a `TiffFile`.
87    /// ```
88    /// #[macro_use]
89    /// extern crate tiff_encoder;
90    /// use tiff_encoder::prelude::*;
91    ///
92    /// # fn main() {
93    /// let file = TiffFile::new(
94    ///     Ifd::new()
95    ///         .with_entry(0x0000, BYTE![0])
96    ///         .single()
97    /// ).write_to("file.tif").unwrap();
98    /// # }
99    /// ```
100    ///
101    /// # Errors
102    ///
103    /// This method returns the same errors as [`Write::write_all`].
104    ///
105    /// [`Write::write_all`]: https://doc.rust-lang.org/std/io/trait.Write.html#method.write_all
106    ///
107    /// # Panics
108    ///
109    /// This function will `panic` if the file trying to be written would exceed
110    /// the maximum size of a TIFF file (2**32 bytes, or 4 GiB).
111    pub fn write_to<P: AsRef<Path>>(self, file_path: P) -> io::Result<fs::File> {
112        // Create all of the file's parent components if they are missing before
113        // trying to create the file itself.
114        if let Some(dir) = file_path.as_ref().parent() {
115            fs::create_dir_all(dir)?;
116        }
117
118        let file = fs::File::create(file_path)?;
119        // Writing to a file is comprised of two phases: the "Allocating Phase"
120        // and the "Writting Phase". During the first, all the components of the
121        // TiffFile allocate their space and become aware of the offsets to other
122        // components that they might need to know. In the "Writting Phase", the
123        // components actually write their information to the file they've been
124        // allocated to.
125        self.allocate(file).write()
126    }
127
128    /// Allocates all of its components to the given file, transforming
129    /// itself into an `AllocatedTiffFile`.
130    fn allocate(self, file: fs::File) -> AllocatedTiffFile {
131        let mut c = Cursor::new();
132        let header = self.header.allocate(&mut c);
133        let ifds = self.ifds.allocate(&mut c);
134        let file = EndianFile::new(file, header.byte_order);
135
136        AllocatedTiffFile { header, ifds, file }
137    }
138}
139
140/// Representation of a TiffFile that called `allocate(&str)` and is
141/// ready to `write()`.
142struct AllocatedTiffFile {
143    header: AllocatedTiffHeader,
144    ifds: AllocatedIfdChain,
145    file: EndianFile,
146}
147
148impl AllocatedTiffFile {
149    /// Writes all of its components to the file it has been allocated to.
150    fn write(mut self) -> io::Result<fs::File> {
151        self.header.write_to(&mut self.file)?;
152        self.ifds.write_to(&mut self.file)?;
153
154        Ok(self.file.into())
155    }
156}
157
158/// Representation of the Header of a TIFF file.
159struct TiffHeader {
160    byte_order: Endianness,
161    magic_number: u16,
162}
163
164impl TiffHeader {
165    /// Allocates its space, moving the given `Cursor` forwards, and becomes
166    /// aware of the offset to ifd0.
167    ///
168    /// Calling this will transform `self` into an `AllocatedTiffHeader`.
169    fn allocate(self, c: &mut Cursor) -> AllocatedTiffHeader {
170        c.allocate(8);
171        AllocatedTiffHeader {
172            byte_order: self.byte_order,
173            magic_number: self.magic_number,
174            offset_to_ifd0: c.allocated_bytes(),
175        }
176    }
177}
178
179/// Representation of a TiffHeader that called `allocate(&mut Cursor)` and is
180/// ready to write to a file.
181struct AllocatedTiffHeader {
182    byte_order: Endianness,
183    magic_number: u16,
184    offset_to_ifd0: u32,
185}
186
187impl AllocatedTiffHeader {
188    /// Write this header to the given `EndianFile`.
189    fn write_to(self, file: &mut EndianFile) -> io::Result<()> {
190        file.write_u16(self.byte_order.id())?;
191        file.write_u16(self.magic_number)?;
192        file.write_u32(self.offset_to_ifd0)?;
193
194        Ok(())
195    }
196}