Skip to main content

rawler/formats/tiff/
ifd.rs

1// SPDX-License-Identifier: MIT
2// Copyright 2021 Daniel Vogelbacher <daniel@chaospixel.com>
3
4use super::{
5  Entry, Result, TiffError, Value, apply_corr,
6  entry::RawEntry,
7  read_from_file,
8  reader::{EndianReader, ReadByteOrder},
9};
10use crate::{
11  bits::Endian,
12  rawsource::RawSource,
13  tags::{ExifTag, TiffCommonTag, TiffTag},
14};
15use byteorder::{LittleEndian, ReadBytesExt};
16use log::debug;
17use serde::{Deserialize, Serialize};
18use std::{
19  collections::{BTreeMap, HashMap},
20  io::{Read, Seek, SeekFrom},
21};
22
23const MAX_IFD_ENTRIES: usize = 4096;
24
25#[derive(Debug)]
26pub enum OffsetMode {
27  Absolute,
28  RelativeToIFD,
29}
30
31#[derive(Debug)]
32pub enum DataMode {
33  Strips,
34  Tiles,
35}
36
37#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
38pub struct IFD {
39  pub offset: u32,
40  pub base: u32,
41  pub corr: i32,
42  pub next_ifd: u32,
43  pub entries: BTreeMap<u16, Entry>,
44  pub endian: Endian,
45  pub sub: HashMap<u16, Vec<IFD>>,
46  pub chain: Vec<IFD>,
47}
48
49// TODO: fixme
50impl IFD {
51  /// Construct new IFD from reader at specific base
52  pub fn new_root<R: Read + Seek>(reader: &mut R, base: u32) -> Result<IFD> {
53    Self::new_root_with_correction(reader, 0, base, 0, 10, &[TiffCommonTag::SubIFDs.into(), TiffCommonTag::ExifIFDPointer.into()])
54  }
55
56  pub fn new_root_with_correction<R: Read + Seek>(reader: &mut R, offset: u32, base: u32, corr: i32, max_chain: usize, sub_tags: &[u16]) -> Result<IFD> {
57    reader.seek(SeekFrom::Start((base + offset) as u64))?;
58    let endian = match reader.read_u16::<LittleEndian>()? {
59      0x4949 => Endian::Little,
60      0x4d4d => Endian::Big,
61      x => {
62        return Err(TiffError::General(format!("TIFF: don't know marker 0x{:x}", x)));
63      }
64    };
65    let mut reader = EndianReader::new(reader, endian);
66    let magic = reader.read_u16()?;
67    if magic != 42 {
68      return Err(TiffError::General(format!("Invalid magic marker for TIFF: {}", magic)));
69    }
70    let mut next_ifd = reader.read_u32()?;
71    if next_ifd == 0 {
72      return Err(TiffError::General("Invalid TIFF header, contains no root IFD".to_string()));
73    }
74
75    let reader = reader.into_inner();
76    let mut multi_sub_tags = vec![];
77    multi_sub_tags.extend_from_slice(sub_tags);
78
79    next_ifd = apply_corr(next_ifd, corr);
80    let mut root = IFD::new(reader, next_ifd, base, corr, endian, &multi_sub_tags)?;
81    if root.entries.is_empty() {
82      return Err(TiffError::General("TIFF is invalid, IFD must contain at least one entry".to_string()));
83    }
84    next_ifd = root.next_ifd;
85
86    while next_ifd != 0 {
87      next_ifd = apply_corr(next_ifd, corr);
88      let ifd = IFD::new(reader, next_ifd, base, corr, endian, &multi_sub_tags)?;
89      if ifd.entries.is_empty() {
90        return Err(TiffError::General("TIFF is invalid, IFD must contain at least one entry".to_string()));
91      }
92      next_ifd = ifd.next_ifd;
93      root.chain.push(ifd);
94
95      if root.chain.len() > max_chain && max_chain > 0 {
96        break;
97      }
98    }
99
100    Ok(root)
101  }
102
103  pub fn new<R: Read + Seek>(reader: &mut R, offset: u32, base: u32, corr: i32, endian: Endian, sub_tags: &[u16]) -> Result<IFD> {
104    reader.seek(SeekFrom::Start((base + offset) as u64))?;
105    let mut sub_ifd_offsets = HashMap::new();
106    let mut reader = EndianReader::new(reader, endian);
107    let entry_count = reader.read_u16()?;
108
109    if entry_count as usize > MAX_IFD_ENTRIES {
110      log::warn!(
111        "TIFF: IFD entry count {} is suspicious (limit {}). The file might be corrupt.",
112        entry_count,
113        MAX_IFD_ENTRIES
114      );
115    }
116
117    let mut entries = BTreeMap::new();
118    let mut sub = HashMap::new();
119    let mut next_pos = reader.position()?;
120    debug!("Parse entries");
121    let mut consecutive_errors = 0;
122
123    for i in 0..entry_count {
124      if i as usize >= MAX_IFD_ENTRIES {
125        log::warn!(
126          "TIFF: Reached maximum IFD entry limit ({}). Stopping parse to prevent infinite loops.",
127          MAX_IFD_ENTRIES
128        );
129        break;
130      }
131
132      if let Err(e) = reader.goto(next_pos) {
133        log::warn!("Truncated IFD: Could not seek to next entry position. Stopping parse. Error: {}", e);
134        break;
135      }
136
137      next_pos += 12;
138
139      let tag = match reader.read_u16() {
140        Ok(t) => t,
141        Err(e) => {
142          log::warn!("Truncated IFD: Could not read tag ID (Index {}). Stopping parse. Error: {}", i, e);
143          break;
144        }
145      };
146
147      match Entry::parse(&mut reader, base, corr, tag) {
148        Ok(entry) => {
149          consecutive_errors = 0;
150
151          if sub_tags.contains(&tag) {
152            match &entry.value {
153              Value::Long(offsets) => {
154                sub_ifd_offsets.insert(tag, offsets.clone());
155              }
156              Value::Unknown(tag, offsets) => {
157                sub_ifd_offsets.insert(*tag, vec![offsets[0] as u32]);
158              }
159              Value::Undefined(_) => {
160                sub_ifd_offsets.insert(tag, vec![entry.offset().unwrap() as u32]);
161              }
162              val => {
163                log::info!(
164                  "Found IFD offset tag, but type mismatch: {:?}. Ignoring SubIFD parsing for tag 0x{:X}",
165                  val,
166                  tag
167                );
168              }
169            }
170          }
171          entries.insert(entry.tag, entry);
172        }
173        Err(err) => {
174          consecutive_errors += 1;
175          log::warn!("Failed to parse TIFF tag 0x{:X} (Index {}). Error: {:?}", tag, i, err);
176
177          // If we fail 5 times in a row, the IFD is likely garbage or physically truncated.
178          if consecutive_errors >= 5 {
179            log::warn!("Too many consecutive parsing errors ({}). Stopping parse to prevent flood.", consecutive_errors);
180            break;
181          }
182        }
183      }
184    }
185
186    // Some TIFF writers skip the next ifd pointer
187    // If we get an I/O error, we fallback to 0, signaling the end of IFD chains.
188    let next_ifd = match reader.read_u32() {
189      Ok(ptr) => ptr,
190      Err(e) => {
191        debug!(
192          "TIFF IFD reader failed to get next IFD pointer, fallback to 0 and continue. Original error was: {}",
193          e
194        );
195        0
196      }
197    };
198
199    // Process SubIFDs
200    let pos = reader.position()?;
201    let reader = reader.into_inner();
202    for subs in sub_ifd_offsets {
203      let mut ifds = Vec::new();
204      for offset in subs.1 {
205        match Self::new(reader, apply_corr(offset, corr), base, corr, endian, &[]) {
206          Ok(ifd) => ifds.push(ifd),
207          Err(err) => {
208            log::warn!("Error while processing TIFF sub-IFD for tag 0x{:X}, ignoring it: {}", subs.0, err);
209          }
210        };
211      }
212      sub.insert(subs.0, ifds);
213    }
214    EndianReader::new(reader, endian).goto(pos)?; // restore
215    Ok(IFD {
216      offset,
217      base,
218      corr,
219      next_ifd: if next_ifd == 0 { 0 } else { apply_corr(next_ifd, corr) },
220      entries,
221      endian,
222      sub,
223      chain: vec![],
224    })
225  }
226
227  pub fn copy_tag(dst: &mut Self, src: &Self, tag: impl Into<u16>) {
228    if let Some(entry) = src.get_entry(tag.into()) {
229      dst.entries.insert(entry.tag, entry.clone());
230    }
231  }
232
233  pub fn value_iter(&self) -> impl Iterator<Item = (&u16, &Value)> {
234    self.entries().iter().map(|(tag, entry)| (tag, &entry.value))
235  }
236
237  /*
238  pub fn new<R: Read + Seek>(reader: &mut R, offset: u32, base: u32, corr: i32, endian: Endian, sub_tags: &[u16]) -> Result<Self> {
239    reader.seek(SeekFrom::Start((base + offset) as u64))?;
240    let mut sub_ifd_offsets = Vec::new();
241    let mut reader = EndianReader::new(reader, endian);
242    let entry_count = reader.read_u16()?;
243    let mut entries = BTreeMap::new();
244    let mut sub = Vec::new();
245    for _ in 0..entry_count {
246      //let embedded = reader.read_u32()?;
247      let tag = reader.read_u16()?;
248      if tag == LegacyTiffRootTag::SubIFDs.into() || sub_tags.contains(&tag) {
249        let entry = Entry::parse(&mut reader, base, corr, tag)?;
250        match entry.value {
251          Value::Long(offsets) => {
252            sub_ifd_offsets.extend_from_slice(&offsets);
253          }
254          _ => {
255            todo!()
256          }
257        }
258      } else {
259        let entry = Entry::parse(&mut reader, base, corr, tag)?;
260        entries.insert(entry.tag, entry);
261      }
262    }
263    let next_ifd = reader.read_u32()?;
264
265    // Process SubIFDs
266    let pos = reader.position()?;
267    let reader = reader.into_inner();
268    for offset in sub_ifd_offsets {
269      let ifd = IFD::new(reader, apply_corr(offset, corr), base, corr, endian, sub_tags)?;
270      sub.push(ifd);
271    }
272    EndianReader::new(reader, endian).goto(pos)?; // restore
273
274    Ok(Self {
275      offset,
276      base,
277      corr,
278      next_ifd: if next_ifd == 0 { 0 } else { apply_corr(next_ifd, corr) },
279      entries,
280      endian,
281      sub,
282    })
283  }
284   */
285
286  /// Extend the IFD with sub-IFDs from a specific tag.
287  /// The IFD corrections are used from current IFD.
288  pub fn extend_sub_ifds<R: Read + Seek>(&mut self, reader: &mut R, tag: u16) -> Result<Option<&Vec<Self>>> {
289    if let Some(entry) = self.get_entry(tag) {
290      let mut subs = Vec::new();
291      match &entry.value {
292        Value::Long(offsets) => {
293          for off in offsets {
294            let ifd = Self::new_root_with_correction(reader, *off, self.base, self.corr, 10, &[])?;
295            subs.push(ifd);
296          }
297          self.sub.insert(tag, subs);
298          Ok(self.sub.get(&tag))
299        }
300        val => {
301          debug!("Found IFD offset tag, but type mismatch: {:?}", val);
302          todo!()
303        }
304      }
305    } else {
306      Ok(None)
307    }
308  }
309
310  pub fn extend_sub_ifds_custom<R, F>(&mut self, reader: &mut R, tag: u16, op: F) -> Result<Option<&Vec<Self>>>
311  where
312    R: Read + Seek,
313    F: FnOnce(&mut R, &IFD, &Entry) -> Result<Option<Vec<IFD>>>,
314  {
315    if let Some(entry) = self.get_entry(tag) {
316      if let Some(subs) = op(reader, self, entry)? {
317        self.sub.insert(tag, subs);
318        Ok(self.sub.get(&tag))
319      } else {
320        Ok(None)
321      }
322    } else {
323      Ok(None)
324    }
325  }
326
327  pub fn sub_ifds(&self) -> &HashMap<u16, Vec<IFD>> {
328    &self.sub
329  }
330
331  pub fn entry_count(&self) -> u16 {
332    self.entries.len() as u16
333  }
334
335  pub fn next_ifd(&self) -> u32 {
336    self.next_ifd
337  }
338
339  pub fn entries(&self) -> &BTreeMap<u16, Entry> {
340    &self.entries
341  }
342
343  pub fn get_entry<T: TiffTag>(&self, tag: T) -> Option<&Entry> {
344    self.entries.get(&tag.into())
345  }
346
347  pub fn get_entry_subs<T: TiffTag>(&self, tag: T) -> Option<&Entry> {
348    for subs in &self.sub {
349      for ifd in subs.1 {
350        if let Some(entry) = ifd.get_entry_recursive(tag) {
351          return Some(entry);
352        }
353      }
354    }
355    None
356  }
357
358  pub fn get_entry_recursive<T: TiffTag>(&self, tag: T) -> Option<&Entry> {
359    self.entries.get(&tag.into()).or_else(|| self.get_entry_subs(tag))
360  }
361
362  pub fn get_entry_raw<'a, T: TiffTag, R: Read + Seek>(&'a self, tag: T, file: &mut R) -> Result<Option<RawEntry<'a>>> {
363    if let Some(entry) = self.get_entry(tag) {
364      return Ok(Some(RawEntry {
365        entry,
366        endian: self.endian,
367        data: read_from_file(file, self.base + entry.offset().unwrap() as u32, entry.byte_size())?,
368      }));
369    }
370    Ok(None)
371  }
372
373  /// Get the data of a tag by just reading as many `len` bytes from offet.
374  pub fn get_entry_raw_with_len<'a, T: TiffTag, R: Read + Seek>(&'a self, tag: T, file: &mut R, len: usize) -> Result<Option<RawEntry<'a>>> {
375    if let Some(entry) = self.get_entry(tag) {
376      return Ok(Some(RawEntry {
377        entry,
378        endian: self.endian,
379        data: read_from_file(file, self.base + entry.offset().unwrap() as u32, len)?,
380      }));
381    }
382    Ok(None)
383  }
384
385  pub fn get_sub_ifd_all<T: TiffTag>(&self, tag: T) -> Option<&Vec<IFD>> {
386    self.sub.get(&tag.into())
387  }
388
389  pub fn get_sub_ifd<T: TiffTag>(&self, tag: T) -> Option<&IFD> {
390    if let Some(ifds) = self.get_sub_ifd_all(tag) {
391      if ifds.len() == 1 {
392        ifds.get(0)
393      } else {
394        log::warn!(
395          "get_sub_ifd() for tag {:?} found more IFDs than expected: {}. Fallback to first IFD!",
396          tag,
397          ifds.len()
398        );
399        ifds.get(0)
400      }
401    } else {
402      None
403    }
404  }
405
406  pub fn get_new_sub_file_type(&self) -> Option<u16> {
407    if let Some(entry) = self.get_entry(TiffCommonTag::NewSubFileType) {
408      Some(entry.force_u16(0))
409    } else {
410      None
411    }
412  }
413
414  pub fn find_ifds_with_tag<T: TiffTag>(&self, tag: T) -> Vec<&IFD> {
415    let mut ifds = Vec::new();
416    if self.get_entry(tag).is_some() {
417      ifds.push(self);
418    }
419    // Now search in all sub IFDs
420    for subs in self.sub_ifds() {
421      for ifd in subs.1 {
422        ifds.append(&mut ifd.find_ifds_with_tag(tag));
423      }
424    }
425    ifds
426  }
427
428  pub fn find_first_ifd_with_tag<T: TiffTag>(&self, tag: T) -> Option<&IFD> {
429    self.find_ifds_with_tag(tag).get(0).copied()
430  }
431
432  /*
433  pub fn get_ifd<T: TiffTagEnum, R: Read + Seek>(&self, tag: T, reader: &mut R) -> Result<Option<IFD>> {
434    if let Some(offset) = self.get_entry(tag) {
435      match &offset.value {
436        Value::Long(v) => {
437          debug!("IFD offset: {}", v[0]);
438          Ok(Some(IFD::new(reader, apply_corr(v[0], self.corr), self.base, self.corr, self.endian, &[])?))
439        }
440        _ => {
441          return Err(TiffError::General(format!(
442            "TIFF tag {:?} is not of type LONG, thus can not be used as IFD offset in get_ifd().",
443            tag
444          )));
445        }
446      }
447    } else {
448      Ok(None)
449    }
450  }
451   */
452
453  pub fn has_entry<T: TiffTag>(&self, tag: T) -> bool {
454    self.get_entry(tag).is_some()
455  }
456
457  pub fn sub_buf<R: Read + Seek>(&self, reader: &mut R, offset: usize, len: usize) -> Result<Vec<u8>> {
458    //&buf[self.start_offset+offset..self.start_offset+offset+len]
459    let mut buf = vec![0; len];
460    reader.seek(SeekFrom::Start(self.base as u64 + offset as u64))?;
461    reader.read_exact(&mut buf)?;
462    Ok(buf)
463  }
464
465  pub fn contains_singlestrip_image(&self) -> bool {
466    self.get_entry(TiffCommonTag::StripOffsets).map(Entry::count).unwrap_or(0) == 1
467  }
468
469  pub fn singlestrip_data_rawsource<'a>(&self, rawsource: &'a RawSource) -> Result<&'a [u8]> {
470    assert!(self.contains_singlestrip_image());
471
472    let offset = self
473      .get_entry(TiffCommonTag::StripOffsets)
474      .ok_or_else(|| TiffError::General(("tag not found").to_string()))?
475      .value
476      .force_u32(0);
477    let len = self
478      .get_entry(TiffCommonTag::StripByteCounts)
479      .ok_or_else(|| TiffError::General(("tag not found").to_string()))?
480      .value
481      .force_usize(0);
482
483    Ok(rawsource.subview((self.base + offset) as u64, len as u64)?)
484  }
485
486  /// Return byte slices to strip data.
487  /// If there exists a single strip only or if all strips are continous,
488  /// the second return value contains the whole strip data in a single slice.
489  pub fn strip_data<'a>(&self, rawsource: &'a RawSource) -> Result<(Vec<&'a [u8]>, Option<&'a [u8]>)> {
490    if !self.has_entry(TiffCommonTag::StripOffsets) {
491      return Err(TiffError::General("IFD contains no strip data".into()));
492    }
493    let offsets = if let Some(Entry { value: Value::Long(data), .. }) = self.get_entry(TiffCommonTag::StripOffsets) {
494      data
495    } else {
496      return Err(TiffError::General("Invalid datatype for StripOffsets".to_string()));
497    };
498    let sizes = if let Some(Entry { value: Value::Long(data), .. }) = self.get_entry(TiffCommonTag::StripByteCounts) {
499      data
500    } else {
501      return Err(TiffError::General("Invalid datatype for StripByteCounts".to_string()));
502    };
503
504    if offsets.len() != sizes.len() {
505      return Err(TiffError::General(format!(
506        "Can't get data from strips: offsets has len {} but sizes has len {}",
507        offsets.len(),
508        sizes.len()
509      )));
510    }
511
512    // Check if all slices are continous
513    let (is_continous, end_off) =
514      offsets.iter().zip(sizes.iter()).fold(
515        (true, offsets[0]),
516        |acc, val| {
517          if acc.0 && acc.1 == *val.0 { (true, acc.1 + *val.1) } else { (false, 0) }
518        },
519      );
520
521    let mut subviews = Vec::with_capacity(offsets.len());
522    for (offset, size) in offsets.iter().zip(sizes.iter()) {
523      subviews.push(rawsource.subview((self.base + *offset) as u64, *size as u64)?);
524    }
525
526    let continous = if is_continous {
527      Some(rawsource.subview((self.base + offsets[0]) as u64, (end_off - offsets[0]) as u64)?)
528    } else {
529      None
530    };
531
532    Ok((subviews, continous))
533  }
534
535  pub fn tile_data<'a>(&self, rawsource: &'a RawSource) -> Result<Vec<&'a [u8]>> {
536    let offsets = if let Some(Entry { value: Value::Long(data), .. }) = self.get_entry(TiffCommonTag::TileOffsets) {
537      data
538    } else {
539      return Err(TiffError::General("Invalid datatype for TileOffsets".to_string()));
540    };
541
542    let byte_counts = if let Some(Entry { value: Value::Long(data), .. }) = self.get_entry(TiffCommonTag::TileByteCounts) {
543      data
544    } else {
545      return Err(TiffError::General("Invalid datatype for TileByteCounts".to_string()));
546    };
547
548    let mut tile_slices = Vec::with_capacity(offsets.len());
549    offsets.iter().zip(byte_counts.iter()).for_each(|(offset, size)| {
550      tile_slices.push(rawsource.subview(*offset as u64, *size as u64).map_err(TiffError::Io));
551    });
552    Ok(tile_slices.into_iter().collect::<Result<Vec<_>>>()?)
553  }
554
555  /// Check for the data mode (Strips or Tiles)
556  pub fn data_mode(&self) -> Result<DataMode> {
557    if self.has_entry(TiffCommonTag::StripOffsets) {
558      Ok(DataMode::Strips)
559    } else if self.has_entry(TiffCommonTag::TileOffsets) {
560      Ok(DataMode::Tiles)
561    } else {
562      Err(TiffError::General("IFD has no StripOffsets or TileOffsets tag".into()))
563    }
564  }
565
566  pub fn parse_makernote<R: Read + Seek>(&self, reader: &mut R, offset_mode: OffsetMode, sub_tags: &[u16]) -> Result<Option<IFD>> {
567    if let Some(exif) = self.get_entry(ExifTag::MakerNotes) {
568      let offset = exif.offset().unwrap() as u32;
569      debug!("Makernote offset: {}", offset);
570      match &exif.value {
571        Value::Undefined(data) => {
572          let mut off = 0;
573          let mut endian = self.endian;
574
575          // Olympus starts the makernote with their own name, sometimes truncated
576          if data[0..5] == b"OLYMP"[..] {
577            off += 8;
578            if data[0..7] == b"OLYMPUS"[..] {
579              off += 4;
580            }
581          }
582
583          // Epson starts the makernote with its own name
584          if data[0..5] == b"EPSON"[..] {
585            off += 8;
586          }
587
588          // Fujifilm has 12 extra bytes
589          if data[0..8] == b"FUJIFILM"[..] {
590            off += 12;
591          }
592
593          // Sony has 12 extra bytes
594          if data[0..9] == b"SONY DSC "[..] {
595            off += 12;
596          }
597
598          // Pentax makernote starts with AOC\0 - If it's there, skip it
599          if data[0..4] == b"AOC\0"[..] {
600            off += 4;
601          }
602
603          // Pentax can also start with PENTAX and in that case uses different offsets
604          if data[0..6] == b"PENTAX"[..] {
605            off += 8;
606            let endian = if data[off..off + 2] == b"II"[..] { Endian::Little } else { Endian::Big };
607            // All offsets in this IFD are relative to the start of this tag,
608            // so wie use the offset as correction value.
609            let corr = offset as i32;
610            // The IFD itself starts 10 bytes after tag offset.
611            return Ok(Some(IFD::new(reader, offset + 10, self.base, corr, endian, sub_tags)?));
612          }
613
614          if data[0..7] == b"Nikon\0\x02"[..] {
615            off += 10;
616            let endian = if data[off..off + 2] == b"II"[..] { Endian::Little } else { Endian::Big };
617            return Ok(Some(IFD::new(reader, 8, self.base + offset + 10, 0, endian, sub_tags)?));
618          }
619
620          // Some have MM or II to indicate endianness - read that
621          if data[off..off + 2] == b"II"[..] {
622            off += 2;
623            endian = Endian::Little;
624          }
625          if data[off..off + 2] == b"MM"[..] {
626            off += 2;
627            endian = Endian::Big;
628          }
629
630          match offset_mode {
631            OffsetMode::Absolute => Ok(Some(IFD::new(reader, offset + off as u32, self.base, self.corr, endian, sub_tags)?)),
632            OffsetMode::RelativeToIFD => {
633              // Value offsets are relative to IFD offset
634              let corr = offset + off as u32;
635              Ok(Some(IFD::new(reader, offset + off as u32, self.base, corr as i32, endian, sub_tags)?))
636            }
637          }
638        }
639        _ => Err(TiffError::General("EXIF makernote has unknown type".to_string())),
640      }
641    } else {
642      Ok(None)
643    }
644  }
645
646  pub fn dump<T: TiffTag>(&self, limit: usize) -> Vec<String> {
647    let mut out = Vec::new();
648    out.push(format!("IFD entries: {}\n", self.entries.len()));
649    out.push(format!("{0:<34}  | {1:<10} | {2:<6} | {3}\n", "Tag", "Type", "Count", "Data"));
650    for (tag, entry) in &self.entries {
651      let mut line = String::new();
652      let tag_name = {
653        if let Ok(name) = T::try_from(*tag) {
654          format!("{:?}", name)
655        } else {
656          format!("<?{}>", tag)
657        }
658      };
659      line.push_str(&format!(
660        "{0:#06x} : {0:<6} {1:<20}| {2:<10} | {3:<6} | ",
661        tag,
662        tag_name,
663        entry.type_name(),
664        entry.count()
665      ));
666      line.push_str(&entry.visual_rep(limit));
667      out.push(line);
668    }
669    for subs in self.sub_ifds().iter() {
670      for (i, sub) in subs.1.iter().enumerate() {
671        out.push(format!("SubIFD({}:{})", subs.0, i));
672        for line in sub.dump::<T>(limit) {
673          out.push(format!("   {}", line));
674        }
675      }
676    }
677    out
678  }
679}