tiff/directory.rs
1use core::fmt;
2use std::{collections::BTreeMap, num::NonZeroU64};
3
4use crate::{
5 decoder::ifd::Entry,
6 tags::{IfdPointer, Tag},
7};
8
9/// An Image File Directory (IFD).
10///
11/// A directory is a map of [`Tag`]s to [`Value`](crate::decoder::ifd::Value)s. The values are
12/// stored anywhere in the file, with the directory containing the offsets and length of the
13/// associated values for each tag present in the directory.
14///
15/// A directory can be created with with
16/// [`Decoder::read_directory`](crate::decoder::Decoder::read_directory) or as an empty directory
17/// to be extended with entries. A directory may be used with
18/// [`Decoder::read_directory_tags`](crate::decoder::Decoder::read_directory_tags) to read the
19/// values associated with tags from an underlying file.
20#[doc(alias = "IFD")]
21pub struct Directory {
22 /// There are at most `u16::MAX` entries in any single directory, the count is stored as a
23 /// 2-byte value. The order in the file is implied to be ascending by tag value (the decoder
24 /// does not mind unordered entries).
25 pub(crate) entries: BTreeMap<u16, Entry>,
26 pub(crate) next_ifd: Option<NonZeroU64>,
27}
28
29impl Directory {
30 /// Create a directory in an initial state without entries. Note that an empty directory can
31 /// not be encoded in a file, it must contain at least one entry.
32 pub fn empty() -> Self {
33 Directory {
34 entries: BTreeMap::new(),
35 next_ifd: None,
36 }
37 }
38
39 /// Retrieve the value associated with a tag.
40 pub fn get(&self, tag: Tag) -> Option<&Entry> {
41 self.entries.get(&tag.to_u16())
42 }
43
44 /// Check if the directory contains a specified tag.
45 pub fn contains(&self, tag: Tag) -> bool {
46 self.entries.contains_key(&tag.to_u16())
47 }
48
49 /// Iterate over all known and unknown tags in this directory.
50 pub fn iter(&self) -> impl Iterator<Item = (Tag, &Entry)> + '_ {
51 self.entries
52 .iter()
53 .map(|(k, v)| (Tag::from_u16_exhaustive(*k), v))
54 }
55
56 /// Insert additional entries into the directory.
57 ///
58 /// Note that a directory can contain at most `u16::MAX` values. There may be one entry that
59 /// does not fit into the directory. This entry is silently ignored (please check [`Self::len`]
60 /// to detect the condition). Providing a tag multiple times or a tag that already exists
61 /// within this directory overwrites the entry.
62 pub fn extend(&mut self, iter: impl IntoIterator<Item = (Tag, Entry)>) {
63 // Code size conscious extension, avoid monomorphic extensions with the assumption of these
64 // not being performance sensitive in practice. (Maybe we have a polymorphic interface for
65 // the crate usage in the future.
66 self.extend_inner(iter.into_iter().by_ref())
67 }
68
69 /// Get the number of entries.
70 pub fn len(&self) -> usize {
71 // The keys are `u16`. Since IFDs are required to have at least one entry this would have
72 // been a trivial thing to do in the specification by storing it minus one but alas. In
73 // BigTIFF the count is stored as 8-bit anyways.
74 self.entries.len()
75 }
76
77 /// Check if there are any entries in this directory. Note that an empty directory can not be
78 /// encoded in the file, it must contain at least one entry.
79 pub fn is_empty(&self) -> bool {
80 self.entries.is_empty()
81 }
82
83 /// Get the pointer to the next IFD, if it was defined.
84 pub fn next(&self) -> Option<IfdPointer> {
85 self.next_ifd.map(|n| IfdPointer(n.get()))
86 }
87
88 pub fn set_next(&mut self, next: Option<IfdPointer>) {
89 self.next_ifd = next.and_then(|n| NonZeroU64::new(n.0));
90 }
91
92 fn extend_inner(&mut self, iter: &mut dyn Iterator<Item = (Tag, Entry)>) {
93 for (tag, entry) in iter {
94 // If the tag is already present, it will be overwritten.
95 let map_entry = self.entries.entry(tag.to_u16());
96
97 match map_entry {
98 std::collections::btree_map::Entry::Vacant(vacant_entry) => {
99 vacant_entry.insert(entry);
100 }
101 std::collections::btree_map::Entry::Occupied(mut occupied_entry) => {
102 occupied_entry.insert(entry);
103 }
104 }
105 }
106 }
107}
108
109impl fmt::Debug for Directory {
110 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
111 f.debug_struct("Directory")
112 .field(
113 "entries",
114 &self.entries.iter().map(|(k, v)| (Tag::from_u16(*k), v)),
115 )
116 .field("next_ifd", &self.next_ifd)
117 .finish()
118 }
119}
120
121#[cfg(test)]
122mod tests {
123 use super::Directory;
124 use crate::{decoder::ifd::Entry, tags::Tag};
125
126 #[test]
127 fn directory_multiple_entries() {
128 let mut dir = Directory::empty();
129 assert_eq!(dir.len(), 0);
130
131 dir.extend((0..=u16::MAX).map(|i| {
132 let tag = Tag::Unknown(1);
133 let entry = Entry::new_u64(crate::tags::Type::BYTE, i.into(), [0; 8]);
134 (tag, entry)
135 }));
136
137 assert_eq!(dir.len(), 1, "Only one tag was ever modified");
138
139 assert_eq!(
140 dir.get(Tag::Unknown(1))
141 .expect("tag 1 should be present after this chain")
142 .count(),
143 u16::MAX.into()
144 );
145 }
146
147 #[test]
148 fn iteration_order() {
149 let mut dir = Directory::empty();
150 assert_eq!(dir.len(), 0);
151
152 let fake_entry = Entry::new_u64(crate::tags::Type::BYTE, 0, [0; 8]);
153 dir.extend((0..32).map(|i| {
154 let tag = Tag::Unknown(i);
155 let entry = fake_entry.clone();
156 (tag, entry)
157 }));
158
159 let iter_order: Vec<u16> = dir.iter().map(|(tag, _e)| tag.to_u16()).collect();
160 assert_eq!(
161 iter_order,
162 (0..32).collect::<Vec<_>>(),
163 "Tags must be in ascending order according to the specification"
164 );
165 }
166}