pub(crate) const MAGIC: &[u8] = b"GIF89a";
pub(crate) const OFFSET: usize = 0;
use crate::{
utils::{decode_tags, encode_tags, passthrough, read_byte, read_heap, skip},
Error,
};
use std::io::{Read, Seek, Write};
const IDENTIFIER: &[u8; 11] = b"MEMETAGS1.0";
fn color_table_size(byte: u8) -> u16 {
3 * 2u16.pow((byte & 0b00000111) as u32 + 1)
}
fn passthrough_blocks(src: &mut impl Read, dest: &mut impl Write) -> Result<(), std::io::Error> {
let mut n = read_byte(src)?;
dest.write_all(&[n])?;
loop {
if n == 0 {
return Ok(());
}
let buf = read_heap(src, n as usize + 1)?;
n = *buf.last().unwrap();
dest.write_all(&buf)?;
}
}
pub fn read_tags(src: &mut (impl Read + Seek)) -> Result<Vec<String>, Error> {
skip(src, MAGIC.len() as i64 + 4)?;
let packed = read_byte(src)?;
skip(src, 2)?;
if packed >> 7 == 1 {
skip(src, color_table_size(packed) as i64)?;
}
loop {
match read_byte(src)? {
0x21 => {
let label = read_byte(src)?;
if label == 0xFF {
let size = read_byte(src)?;
let identifier = read_heap(src, size as usize)?;
if identifier == IDENTIFIER {
let mut tags_bytes = Vec::new();
let mut n = read_byte(src)?;
loop {
if n == 0 {
break;
}
let buf = read_heap(src, n as usize + 1)?;
tags_bytes.extend(&buf[..n as usize]);
n = *buf.last().unwrap();
}
return decode_tags(&mut tags_bytes.as_slice());
}
}
passthrough_blocks(src, &mut std::io::sink())?;
}
0x2C => {
skip(src, 8)?;
let packed = read_byte(src)?;
if packed >> 7 == 1 {
skip(src, color_table_size(packed) as i64)?;
}
skip(src, 1)?;
passthrough_blocks(src, &mut std::io::sink())?;
}
0x3B => return Ok(Vec::new()),
byte => return Err(Error::GifUnknownBlock(byte)),
}
}
}
pub fn write_tags(
src: &mut (impl Read + Seek),
dest: &mut impl Write,
tags: impl IntoIterator<Item = impl AsRef<str>>,
) -> Result<(), Error> {
passthrough(src, dest, MAGIC.len() as u64 + 4)?;
let packed = read_byte(src)?;
dest.write_all(&[packed])?;
passthrough(src, dest, 2)?;
if packed >> 7 == 1 {
passthrough(src, dest, color_table_size(packed) as u64)?;
}
dest.write_all(&[0x21, 0xFF, IDENTIFIER.len() as u8])?;
dest.write_all(IDENTIFIER)?;
let mut tag_bytes = Vec::new();
encode_tags(tags, &mut tag_bytes)?;
let mut tag_slice = tag_bytes.as_slice();
while !tag_slice.is_empty() {
let sub_block_size = tag_slice.len().min(0xFF);
dest.write_all(&[sub_block_size as u8])?;
dest.write_all(&tag_slice[0..sub_block_size])?;
tag_slice = &tag_slice[sub_block_size..];
}
dest.write_all(&[0])?;
loop {
let byte = read_byte(src)?;
match byte {
0x21 => {
let label = read_byte(src)?;
if label == 0xFF {
let size = read_byte(src)?;
let identifier = read_heap(src, size as usize)?;
if identifier == IDENTIFIER {
passthrough_blocks(src, &mut std::io::sink())?;
} else {
dest.write_all(&[byte, label, size])?;
dest.write_all(&identifier)?;
passthrough_blocks(src, dest)?;
}
} else {
dest.write_all(&[byte, label])?;
passthrough_blocks(src, dest)?;
}
}
0x2C => {
dest.write_all(&[byte])?;
passthrough(src, dest, 8)?;
let packed = read_byte(src)?;
dest.write_all(&[packed])?;
if packed >> 7 == 1 {
passthrough(src, dest, color_table_size(packed) as u64)?;
}
passthrough(src, dest, 1)?;
passthrough_blocks(src, dest)?;
}
0x3B => {
dest.write_all(&[byte])?;
return Ok(());
}
byte => return Err(Error::GifUnknownBlock(byte)),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
const START: &[u8] = &[0u8; 13];
const END: &[u8] = &[0x3B];
const TAGS: &[&[u8]] = &[&[0x21, 0xFF, 0xB], IDENTIFIER, &[0x01, 0x00, 0x00]];
const DESCRIPTOR: &[&[u8]] = &[&[0x2C], &[0; 8], &[0x80], &[0; 8]];
#[test]
fn local_color_tables() {
let src = &[START, &DESCRIPTOR.concat(), &END].concat();
assert_eq!(read_tags(&mut Cursor::new(src)).unwrap(), Vec::<String>::new());
let mut dest = Vec::new();
write_tags(&mut Cursor::new(src), &mut dest, Vec::<String>::new()).unwrap();
let expected = &[START, &TAGS.concat(), &DESCRIPTOR.concat(), &END].concat();
assert_eq!(&dest, expected);
}
}
crate::utils::standard_tests!("gif");