Add an experimental Writer that encodes and writes Exif data.
This commit is contained in:
parent
132c9e9d18
commit
73bced6cbf
|
@ -63,6 +63,11 @@ pub use tiff::parse_exif;
|
||||||
pub use value::Value;
|
pub use value::Value;
|
||||||
pub use value::{Rational, SRational};
|
pub use value::{Rational, SRational};
|
||||||
|
|
||||||
|
/// The interfaces in this module is experimental and unstable.
|
||||||
|
pub mod experimental {
|
||||||
|
pub use writer::Writer;
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod tmacro;
|
mod tmacro;
|
||||||
|
|
|
@ -38,8 +38,8 @@ use util::atou16;
|
||||||
const TIFF_BE: u16 = 0x4d4d;
|
const TIFF_BE: u16 = 0x4d4d;
|
||||||
const TIFF_LE: u16 = 0x4949;
|
const TIFF_LE: u16 = 0x4949;
|
||||||
const TIFF_FORTY_TWO: u16 = 0x002a;
|
const TIFF_FORTY_TWO: u16 = 0x002a;
|
||||||
const TIFF_BE_SIG: [u8; 4] = [0x4d, 0x4d, 0x00, 0x2a];
|
pub const TIFF_BE_SIG: [u8; 4] = [0x4d, 0x4d, 0x00, 0x2a];
|
||||||
const TIFF_LE_SIG: [u8; 4] = [0x49, 0x49, 0x2a, 0x00];
|
pub const TIFF_LE_SIG: [u8; 4] = [0x49, 0x49, 0x2a, 0x00];
|
||||||
|
|
||||||
/// A TIFF field.
|
/// A TIFF field.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
414
src/writer.rs
414
src/writer.rs
|
@ -24,13 +24,389 @@
|
||||||
// SUCH DAMAGE.
|
// SUCH DAMAGE.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
use std::io;
|
||||||
|
use std::io::{Seek, SeekFrom, Write};
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::slice;
|
use std::slice;
|
||||||
|
|
||||||
use endian::{Endian, BigEndian, LittleEndian};
|
use endian::{Endian, BigEndian, LittleEndian};
|
||||||
use error::Error;
|
use error::Error;
|
||||||
|
use tag_priv::{Context, Tag, constants as tag};
|
||||||
|
use tiff::{Field, TIFF_BE_SIG, TIFF_LE_SIG};
|
||||||
use value::Value;
|
use value::Value;
|
||||||
|
|
||||||
|
/// The `Writer` struct is used to encode and write Exif data.
|
||||||
|
pub struct Writer<'a> {
|
||||||
|
tiff_fields: Vec<&'a Field<'a>>,
|
||||||
|
exif_fields: Vec<&'a Field<'a>>,
|
||||||
|
gps_fields: Vec<&'a Field<'a>>,
|
||||||
|
interop_fields: Vec<&'a Field<'a>>,
|
||||||
|
tn_tiff_fields: Vec<&'a Field<'a>>,
|
||||||
|
tn_exif_fields: Vec<&'a Field<'a>>,
|
||||||
|
tn_gps_fields: Vec<&'a Field<'a>>,
|
||||||
|
tn_interop_fields: Vec<&'a Field<'a>>,
|
||||||
|
strips: Option<&'a [&'a [u8]]>,
|
||||||
|
tn_strips: Option<&'a [&'a [u8]]>,
|
||||||
|
tn_jpeg: Option<&'a [u8]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct WriterState<'a> {
|
||||||
|
tiff_fields: Vec<&'a Field<'a>>,
|
||||||
|
exif_fields: Vec<&'a Field<'a>>,
|
||||||
|
gps_fields: Vec<&'a Field<'a>>,
|
||||||
|
interop_fields: Vec<&'a Field<'a>>,
|
||||||
|
tiff_ifd_offset: u32,
|
||||||
|
exif_ifd_offset: u32,
|
||||||
|
gps_ifd_offset: u32,
|
||||||
|
interop_ifd_offset: u32,
|
||||||
|
strips: Option<&'a [&'a [u8]]>,
|
||||||
|
jpeg: Option<&'a [u8]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Writer<'a> {
|
||||||
|
/// Constructs an empty `Writer`.
|
||||||
|
pub fn new() -> Writer<'a> {
|
||||||
|
Writer {
|
||||||
|
tiff_fields: Vec::new(),
|
||||||
|
exif_fields: Vec::new(),
|
||||||
|
gps_fields: Vec::new(),
|
||||||
|
interop_fields: Vec::new(),
|
||||||
|
tn_tiff_fields: Vec::new(),
|
||||||
|
tn_exif_fields: Vec::new(),
|
||||||
|
tn_gps_fields: Vec::new(),
|
||||||
|
tn_interop_fields: Vec::new(),
|
||||||
|
strips: None,
|
||||||
|
tn_strips: None,
|
||||||
|
tn_jpeg: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Appends a field to be written.
|
||||||
|
///
|
||||||
|
/// The fields can be appended in any order.
|
||||||
|
/// Duplicate fields must not be appended.
|
||||||
|
///
|
||||||
|
/// ExifIFDPointer, GPSInfoIFDPointer, InteropIFDPointer,
|
||||||
|
/// StripOffsets, StripByteCounts, JPEGInterchangeFormat, and
|
||||||
|
/// JPEGInterchangeFormatLength are ignored.
|
||||||
|
/// They are synthesized when needed.
|
||||||
|
pub fn push_field(&mut self, field: &'a Field) {
|
||||||
|
match *field {
|
||||||
|
// Ignore the tags for the internal data structure.
|
||||||
|
Field { tag: tag::ExifIFDPointer, .. } |
|
||||||
|
Field { tag: tag::GPSInfoIFDPointer, .. } |
|
||||||
|
Field { tag: tag::InteropIFDPointer, .. } => {},
|
||||||
|
// These tags are synthesized from the actual strip data.
|
||||||
|
Field { tag: tag::StripOffsets, .. } |
|
||||||
|
Field { tag: tag::StripByteCounts, .. } => {},
|
||||||
|
// These tags are synthesized from the actual JPEG thumbnail.
|
||||||
|
Field { tag: tag::JPEGInterchangeFormat, .. } |
|
||||||
|
Field { tag: tag::JPEGInterchangeFormatLength, .. } => {},
|
||||||
|
// Other normal tags.
|
||||||
|
Field { tag: Tag(Context::Tiff, _), thumbnail: false, .. } =>
|
||||||
|
self.tiff_fields.push(field),
|
||||||
|
Field { tag: Tag(Context::Exif, _), thumbnail: false, .. } =>
|
||||||
|
self.exif_fields.push(field),
|
||||||
|
Field { tag: Tag(Context::Gps, _), thumbnail: false, .. } =>
|
||||||
|
self.gps_fields.push(field),
|
||||||
|
Field { tag: Tag(Context::Interop, _), thumbnail: false, .. } =>
|
||||||
|
self.interop_fields.push(field),
|
||||||
|
Field { tag: Tag(Context::Tiff, _), thumbnail: true, .. } =>
|
||||||
|
self.tn_tiff_fields.push(field),
|
||||||
|
Field { tag: Tag(Context::Exif, _), thumbnail: true, .. } =>
|
||||||
|
self.tn_exif_fields.push(field),
|
||||||
|
Field { tag: Tag(Context::Gps, _), thumbnail: true, .. } =>
|
||||||
|
self.tn_gps_fields.push(field),
|
||||||
|
Field { tag: Tag(Context::Interop, _), thumbnail: true, .. } =>
|
||||||
|
self.tn_interop_fields.push(field),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets TIFF strips for the primary image.
|
||||||
|
/// If this method is called multiple times, the last one is used.
|
||||||
|
pub fn set_strips(&mut self, strips: &'a [&'a [u8]]) {
|
||||||
|
self.strips = Some(strips);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets TIFF strips for the thumbnail image.
|
||||||
|
/// If this method is called multiple times, the last one is used.
|
||||||
|
pub fn set_thumbnail_strips(&mut self, strips: &'a [&'a [u8]]) {
|
||||||
|
self.tn_strips = Some(strips);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets JPEG data for the thumbnail image.
|
||||||
|
/// If this method is called multiple times, the last one is used.
|
||||||
|
pub fn set_thumbnail_jpeg(&mut self, jpeg: &'a [u8]) {
|
||||||
|
self.tn_jpeg = Some(jpeg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Encodes Exif data and writes it into `w`.
|
||||||
|
///
|
||||||
|
/// The write position of `w` must be set to zero before calling
|
||||||
|
/// this method.
|
||||||
|
///
|
||||||
|
/// A new `exif::Error` variant will be introduced in the next
|
||||||
|
/// API-version bump to return write errors. `Error::InvalidFormat`
|
||||||
|
/// is used until then.
|
||||||
|
pub fn write<W>(&mut self, w: &mut W, little_endian: bool)
|
||||||
|
-> Result<(), Error> where W: Write + Seek {
|
||||||
|
// TIFF signature and the offset of the 0th IFD.
|
||||||
|
if little_endian {
|
||||||
|
try!(w.write_all(&TIFF_LE_SIG));
|
||||||
|
try!(LittleEndian::writeu32(w, 8));
|
||||||
|
} else {
|
||||||
|
try!(w.write_all(&TIFF_BE_SIG));
|
||||||
|
try!(BigEndian::writeu32(w, 8));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the primary image.
|
||||||
|
let ws = WriterState {
|
||||||
|
tiff_fields: self.tiff_fields.clone(),
|
||||||
|
exif_fields: self.exif_fields.clone(),
|
||||||
|
gps_fields: self.gps_fields.clone(),
|
||||||
|
interop_fields: self.interop_fields.clone(),
|
||||||
|
tiff_ifd_offset: 0,
|
||||||
|
exif_ifd_offset: 0,
|
||||||
|
gps_ifd_offset: 0,
|
||||||
|
interop_ifd_offset: 0,
|
||||||
|
strips: self.strips,
|
||||||
|
jpeg: None,
|
||||||
|
};
|
||||||
|
let next_ifd_offset_offset =
|
||||||
|
try!(synthesize_fields(w, ws, false, little_endian));
|
||||||
|
|
||||||
|
let next_ifd_offset = try!(pad_and_get_offset(w));
|
||||||
|
let origpos = try!(w.seek(SeekFrom::Current(0)));
|
||||||
|
try!(w.seek(SeekFrom::Start(next_ifd_offset_offset as u64)));
|
||||||
|
match little_endian {
|
||||||
|
false => try!(BigEndian::writeu32(w, next_ifd_offset)),
|
||||||
|
true => try!(LittleEndian::writeu32(w, next_ifd_offset)),
|
||||||
|
}
|
||||||
|
try!(w.seek(SeekFrom::Start(origpos)));
|
||||||
|
|
||||||
|
// Write the thumbnail image.
|
||||||
|
let ws = WriterState {
|
||||||
|
tiff_fields: self.tn_tiff_fields.clone(),
|
||||||
|
exif_fields: self.tn_exif_fields.clone(),
|
||||||
|
gps_fields: self.tn_gps_fields.clone(),
|
||||||
|
interop_fields: self.tn_interop_fields.clone(),
|
||||||
|
tiff_ifd_offset: 0,
|
||||||
|
exif_ifd_offset: 0,
|
||||||
|
gps_ifd_offset: 0,
|
||||||
|
interop_ifd_offset: 0,
|
||||||
|
strips: self.tn_strips,
|
||||||
|
jpeg: self.tn_jpeg,
|
||||||
|
};
|
||||||
|
try!(synthesize_fields(w, ws, true, little_endian));
|
||||||
|
|
||||||
|
try!(w.flush());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Synthesizes special fields, writes an image, and returns the offset
|
||||||
|
// of the next IFD offset.
|
||||||
|
fn synthesize_fields<W>(w: &mut W, ws: WriterState, thumbnail: bool,
|
||||||
|
little_endian: bool)
|
||||||
|
-> Result<u32, Error> where W: Write + Seek {
|
||||||
|
let exif_in_tiff;
|
||||||
|
let gps_in_tiff;
|
||||||
|
let interop_in_exif;
|
||||||
|
let strip_offsets;
|
||||||
|
let strip_byte_counts;
|
||||||
|
let jpeg_offset;
|
||||||
|
let jpeg_length;
|
||||||
|
// Shrink the scope so that referenced fields live longer than ws.
|
||||||
|
let mut ws = ws;
|
||||||
|
|
||||||
|
if let Some(strips) = ws.strips {
|
||||||
|
strip_offsets = Field {
|
||||||
|
tag: tag::StripOffsets,
|
||||||
|
thumbnail: thumbnail,
|
||||||
|
value: Value::Long(vec![0; strips.len()]),
|
||||||
|
};
|
||||||
|
ws.tiff_fields.push(&strip_offsets);
|
||||||
|
strip_byte_counts = Field {
|
||||||
|
tag: tag::StripByteCounts,
|
||||||
|
thumbnail: thumbnail,
|
||||||
|
value: Value::Long(
|
||||||
|
strips.iter().map(|s| s.len() as u32).collect()),
|
||||||
|
};
|
||||||
|
ws.tiff_fields.push(&strip_byte_counts);
|
||||||
|
}
|
||||||
|
if let Some(jpeg) = ws.jpeg {
|
||||||
|
jpeg_offset = Field {
|
||||||
|
tag: tag::JPEGInterchangeFormat,
|
||||||
|
thumbnail: thumbnail,
|
||||||
|
value: Value::Long(vec![0]),
|
||||||
|
};
|
||||||
|
ws.tiff_fields.push(&jpeg_offset);
|
||||||
|
jpeg_length = Field {
|
||||||
|
tag: tag::JPEGInterchangeFormatLength,
|
||||||
|
thumbnail: thumbnail,
|
||||||
|
value: Value::Long(vec![jpeg.len() as u32]),
|
||||||
|
};
|
||||||
|
ws.tiff_fields.push(&jpeg_length);
|
||||||
|
}
|
||||||
|
|
||||||
|
let interop_fields_len = ws.interop_fields.len();
|
||||||
|
let gps_fields_len = ws.gps_fields.len();
|
||||||
|
let exif_fields_len = ws.exif_fields.len() +
|
||||||
|
match interop_fields_len { 0 => 0, _ => 1 };
|
||||||
|
let tiff_fields_len = ws.tiff_fields.len() +
|
||||||
|
match gps_fields_len { 0 => 0, _ => 1 } +
|
||||||
|
match exif_fields_len { 0 => 0, _ => 1 };
|
||||||
|
|
||||||
|
ws.tiff_ifd_offset = try!(reserve_ifd(w, tiff_fields_len));
|
||||||
|
if exif_fields_len > 0 {
|
||||||
|
ws.exif_ifd_offset = try!(reserve_ifd(w, exif_fields_len));
|
||||||
|
exif_in_tiff = Field {
|
||||||
|
tag: tag::ExifIFDPointer,
|
||||||
|
thumbnail: thumbnail,
|
||||||
|
value: Value::Long(vec![ws.exif_ifd_offset]),
|
||||||
|
};
|
||||||
|
ws.tiff_fields.push(&exif_in_tiff);
|
||||||
|
}
|
||||||
|
if gps_fields_len > 0 {
|
||||||
|
ws.gps_ifd_offset = try!(reserve_ifd(w, gps_fields_len));
|
||||||
|
gps_in_tiff = Field {
|
||||||
|
tag: tag::GPSInfoIFDPointer,
|
||||||
|
thumbnail: thumbnail,
|
||||||
|
value: Value::Long(vec![ws.gps_ifd_offset]),
|
||||||
|
};
|
||||||
|
ws.tiff_fields.push(&gps_in_tiff);
|
||||||
|
}
|
||||||
|
if interop_fields_len > 0 {
|
||||||
|
ws.interop_ifd_offset = try!(reserve_ifd(w, interop_fields_len));
|
||||||
|
interop_in_exif = Field {
|
||||||
|
tag: tag::InteropIFDPointer,
|
||||||
|
thumbnail: thumbnail,
|
||||||
|
value: Value::Long(vec![ws.interop_ifd_offset]),
|
||||||
|
};
|
||||||
|
ws.exif_fields.push(&interop_in_exif);
|
||||||
|
}
|
||||||
|
|
||||||
|
ws.tiff_fields.sort_by_key(|f| f.tag.number());
|
||||||
|
ws.exif_fields.sort_by_key(|f| f.tag.number());
|
||||||
|
ws.gps_fields.sort_by_key(|f| f.tag.number());
|
||||||
|
ws.interop_fields.sort_by_key(|f| f.tag.number());
|
||||||
|
|
||||||
|
match little_endian {
|
||||||
|
false => write_image::<_, BigEndian>(w, ws),
|
||||||
|
true => write_image::<_, LittleEndian>(w, ws),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Writes an image and returns the offset of the next IFD offset.
|
||||||
|
fn write_image<W, E>(w: &mut W, ws: WriterState)
|
||||||
|
-> Result<u32, Error> where W: Write + Seek, E: Endian {
|
||||||
|
let (next_ifd_offset_offset, strip_offsets_offset, jpeg_offset) =
|
||||||
|
try!(write_ifd_and_fields::<_, E>(
|
||||||
|
w, &ws.tiff_fields, ws.tiff_ifd_offset));
|
||||||
|
if ws.exif_fields.len() > 0 {
|
||||||
|
try!(write_ifd_and_fields::<_, E>(
|
||||||
|
w, &ws.exif_fields, ws.exif_ifd_offset));
|
||||||
|
}
|
||||||
|
if ws.gps_fields.len() > 0 {
|
||||||
|
try!(write_ifd_and_fields::<_, E>(
|
||||||
|
w, &ws.gps_fields, ws.gps_ifd_offset));
|
||||||
|
}
|
||||||
|
if ws.interop_fields.len() > 0 {
|
||||||
|
try!(write_ifd_and_fields::<_, E>(
|
||||||
|
w, &ws.interop_fields, ws.interop_ifd_offset));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(strips) = ws.strips {
|
||||||
|
let mut strip_offsets = Vec::new();
|
||||||
|
for strip in strips {
|
||||||
|
strip_offsets.push(try!(get_offset(w)));
|
||||||
|
try!(w.write_all(strip));
|
||||||
|
}
|
||||||
|
let origpos = try!(w.seek(SeekFrom::Current(0)));
|
||||||
|
try!(w.seek(SeekFrom::Start(strip_offsets_offset as u64)));
|
||||||
|
for ofs in strip_offsets {
|
||||||
|
try!(E::writeu32(w, ofs));
|
||||||
|
}
|
||||||
|
try!(w.seek(SeekFrom::Start(origpos)));
|
||||||
|
}
|
||||||
|
if let Some(jpeg) = ws.jpeg {
|
||||||
|
let offset = try!(get_offset(w));
|
||||||
|
try!(w.write_all(jpeg));
|
||||||
|
let origpos = try!(w.seek(SeekFrom::Current(0)));
|
||||||
|
try!(w.seek(SeekFrom::Start(jpeg_offset as u64)));
|
||||||
|
try!(E::writeu32(w, offset));
|
||||||
|
try!(w.seek(SeekFrom::Start(origpos)));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(next_ifd_offset_offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Advances the write position to make a space for a new IFD and
|
||||||
|
// returns the offset of the IFD.
|
||||||
|
fn reserve_ifd<W>(w: &mut W, count: usize)
|
||||||
|
-> Result<u32, Error> where W: Write + Seek {
|
||||||
|
let ifdpos = try!(get_offset(w));
|
||||||
|
assert!(ifdpos % 2 == 0);
|
||||||
|
// The number of entries (2) + array of entries (12 * n) +
|
||||||
|
// the next IFD pointer (4).
|
||||||
|
try!(w.seek(SeekFrom::Current(2 + count as i64 * 12 + 4)));
|
||||||
|
Ok(ifdpos)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Writes an IFD and its fields, and
|
||||||
|
// returns the offsets of the next IFD offset, StripOffsets value, and
|
||||||
|
// JPEGInterchangeFormat value.
|
||||||
|
fn write_ifd_and_fields<W, E>(
|
||||||
|
w: &mut W, fields: &Vec<&Field>, ifd_offset: u32)
|
||||||
|
-> Result<(u32, u32, u32), Error> where W: Write + Seek, E: Endian
|
||||||
|
{
|
||||||
|
let mut strip_offsets_offset = 0;
|
||||||
|
let mut jpeg_offset = 0;
|
||||||
|
let mut ifd = Vec::new();
|
||||||
|
|
||||||
|
// Write the number of entries.
|
||||||
|
try!(E::writeu16(&mut ifd, fields.len() as u16));
|
||||||
|
// Write the fields.
|
||||||
|
for f in fields {
|
||||||
|
let (typ, cnt, mut valbuf) = try!(compose_value::<E>(&f.value));
|
||||||
|
if cnt as u32 as usize != cnt {
|
||||||
|
return Err(Error::InvalidFormat("Too long array"));
|
||||||
|
}
|
||||||
|
try!(E::writeu16(&mut ifd, f.tag.number()));
|
||||||
|
try!(E::writeu16(&mut ifd, typ));
|
||||||
|
try!(E::writeu32(&mut ifd, cnt as u32));
|
||||||
|
// Embed the value itself into the offset, or
|
||||||
|
// encode as an offset and the value.
|
||||||
|
if valbuf.len() <= 4 {
|
||||||
|
valbuf.resize(4, 0);
|
||||||
|
try!(ifd.write_all(&valbuf));
|
||||||
|
} else {
|
||||||
|
// The value must begin on a word boundary. [TIFF6, Section 2:
|
||||||
|
// TIFF Structure, Image File Directory, IFD Entry, p. 15]
|
||||||
|
let valofs = try!(pad_and_get_offset(w));
|
||||||
|
try!(E::writeu32(&mut ifd, valofs));
|
||||||
|
try!(w.write_all(&valbuf));
|
||||||
|
}
|
||||||
|
if f.tag == tag::StripOffsets {
|
||||||
|
strip_offsets_offset = match valbuf.len() {
|
||||||
|
0...4 => ifd_offset + ifd.len() as u32 - 4,
|
||||||
|
_ => try!(get_offset(w)) - valbuf.len() as u32,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if f.tag == tag::JPEGInterchangeFormat {
|
||||||
|
jpeg_offset = ifd_offset + ifd.len() as u32 - 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Write the next IFD pointer.
|
||||||
|
let next_ifd_offset_offset = ifd_offset + ifd.len() as u32;
|
||||||
|
try!(E::writeu32(&mut ifd, 0));
|
||||||
|
|
||||||
|
// Write the IFD.
|
||||||
|
try!(write_at(w, &ifd, ifd_offset));
|
||||||
|
|
||||||
|
Ok((next_ifd_offset_offset, strip_offsets_offset, jpeg_offset))
|
||||||
|
}
|
||||||
|
|
||||||
// Returns the type, count, and encoded value.
|
// Returns the type, count, and encoded value.
|
||||||
fn compose_value<E>(value: &Value)
|
fn compose_value<E>(value: &Value)
|
||||||
-> Result<(u16, usize, Vec<u8>), Error> where E: Endian {
|
-> Result<(u16, usize, Vec<u8>), Error> where E: Endian {
|
||||||
|
@ -115,6 +491,38 @@ fn compose_value<E>(value: &Value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn write_at<W>(w: &mut W, buf: &[u8], offset: u32)
|
||||||
|
-> io::Result<()> where W: Write + Seek {
|
||||||
|
let orig = try!(w.seek(SeekFrom::Current(0)));
|
||||||
|
try!(w.seek(SeekFrom::Start(offset as u64)));
|
||||||
|
try!(w.write_all(buf));
|
||||||
|
try!(w.seek(SeekFrom::Start(orig)));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aligns `w` to the two-byte (word) boundary and returns the new offset.
|
||||||
|
fn pad_and_get_offset<W>(w: &mut W)
|
||||||
|
-> Result<u32, Error> where W: Write + Seek {
|
||||||
|
let mut pos = try!(w.seek(SeekFrom::Current(0)));
|
||||||
|
if pos >= 1 << 32 - 1 {
|
||||||
|
return Err(Error::InvalidFormat("Offset too large"));
|
||||||
|
}
|
||||||
|
if pos % 2 != 0 {
|
||||||
|
try!(w.write_all(&[0]));
|
||||||
|
pos += 1;
|
||||||
|
}
|
||||||
|
Ok(pos as u32)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_offset<W>(w: &mut W)
|
||||||
|
-> Result<u32, Error> where W: Write + Seek {
|
||||||
|
let pos = try!(w.seek(SeekFrom::Current(0)));
|
||||||
|
if pos as u32 as u64 != pos {
|
||||||
|
return Err(Error::InvalidFormat("Offset too large"));
|
||||||
|
}
|
||||||
|
Ok(pos as u32)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use value::{Rational, SRational};
|
use value::{Rational, SRational};
|
||||||
|
@ -166,9 +574,9 @@ mod tests {
|
||||||
(12, 2, b"\x00\x00\x00\x00\x00\x00\x04\x40\
|
(12, 2, b"\x00\x00\x00\x00\x00\x00\x04\x40\
|
||||||
\x00\x00\x00\x00\x00\x00\xe0\xbf".to_vec())),
|
\x00\x00\x00\x00\x00\x00\xe0\xbf".to_vec())),
|
||||||
];
|
];
|
||||||
for p in patterns.into_iter() {
|
for (val, be, le) in patterns.into_iter() {
|
||||||
assert_eq!(compose_value::<BigEndian>(&p.0).unwrap(), p.1);
|
assert_eq!(compose_value::<BigEndian>(&val).unwrap(), be);
|
||||||
assert_eq!(compose_value::<LittleEndian>(&p.0).unwrap(), p.2);
|
assert_eq!(compose_value::<LittleEndian>(&val).unwrap(), le);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue