use indexmap::IndexMap;
use serde::Serialize;
use std::collections::{HashMap, HashSet};
use std::fs::File;
use std::io::{Cursor, Read, Write};
use std::path::Path;
use std::str::FromStr;
use crate::profile::{set_profile_id, Profile};
use crate::signatures::{DeviceClass, Pcs};
use crate::tag::tagdata::TagData;
use crate::tag::TagDataTraits;
use crate::tag::{Tag, TagSignature};
#[derive(Debug, Clone, Serialize, PartialEq)]
pub struct RawProfile {
#[serde(with = "serde_arrays")]
pub header: [u8; 128], pub tags: IndexMap<TagSignature, ProfileTagRecord>, #[serde(skip)]
shared_tags: bool,
}
#[derive(Debug, Serialize, Clone, PartialEq)]
pub struct ProfileTagRecord {
pub offset: u32,
pub size: i32,
pub tag: Tag,
}
impl ProfileTagRecord {
pub fn new(offset: u32, size: i32, tag: Tag) -> Self {
Self { offset, size, tag }
}
pub fn as_slice(&self) -> &[u8] {
self.tag.as_slice()
}
}
impl Default for RawProfile {
fn default() -> Self {
Self {
header: [0; 128],
tags: IndexMap::new(),
shared_tags: true,
}
.with_valid_file_signature()
.with_version(4, 3)
.unwrap()
.with_pcs(Pcs::XYZ)
.with_pcs_illuminant([0.9642, 1.0, 0.8249]) .with_now_as_creation_date() }
}
fn share_tags(tag_entries: &[(TagSignature, u32, u32)]) -> bool {
let mut seen_offsets = HashSet::new();
for &(_sig, offset, _size) in tag_entries {
if !seen_offsets.insert(offset) {
return true;
}
}
false
}
impl RawProfile {
pub fn read<P: AsRef<Path>>(path: P) -> Result<Self, Box<dyn std::error::Error>> {
let mut file = File::open(path)?;
let mut buf = Vec::new();
file.read_to_end(&mut buf)?;
Self::from_bytes(&buf)
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, Box<dyn std::error::Error>> {
let mut cursor = Cursor::new(bytes);
let mut header = [0u8; 128];
cursor.read_exact(&mut header)?;
let mut count_buf = [0u8; 4];
cursor.read_exact(&mut count_buf)?;
let tag_count = u32::from_be_bytes(count_buf);
let mut tag_entries = Vec::with_capacity(tag_count as usize);
let mut max_end = 0usize;
for _ in 0..tag_count {
let mut entry = [0u8; 12];
cursor.read_exact(&mut entry)?;
let signature_value = u32::from_be_bytes([entry[0], entry[1], entry[2], entry[3]]);
let signature = TagSignature::new(signature_value);
let offset = u32::from_be_bytes([entry[4], entry[5], entry[6], entry[7]]);
let size = u32::from_be_bytes([entry[8], entry[9], entry[10], entry[11]]);
let end = offset as usize + size as usize;
if end > max_end {
max_end = end;
}
tag_entries.push((signature, offset, size));
}
let shared_tags = share_tags(&tag_entries);
let mut tags = IndexMap::with_capacity(tag_count as usize);
for (signature, offset, size) in &tag_entries {
let end = *offset as usize + *size as usize;
if end > bytes.len() {
return Err(Box::new(std::io::Error::new(
std::io::ErrorKind::UnexpectedEof,
"tag data offset/size exceeds input length",
)));
}
cursor.set_position(*offset as u64);
let mut data = vec![0u8; *size as usize];
cursor.read_exact(&mut data)?;
if signature == &TagSignature::NamedColor2 {
let pcs = u32::from_be_bytes(header[20..24].try_into().unwrap()); if pcs == 0x4C616220 {
let mut flag = u32::from_be_bytes(data[8..12].try_into().unwrap()); flag |= 0x1_0000; data[8..12].copy_from_slice(&flag.to_be_bytes()); }
}
tags.insert(
*signature,
ProfileTagRecord {
offset: *offset,
size: *size as i32, tag: Tag::new(signature.to_u32(), TagData::new(data)),
},
);
}
Ok(RawProfile {
header,
tags,
shared_tags,
})
}
pub fn write<P: AsRef<Path>>(self, path: P) -> Result<(), Box<dyn std::error::Error>> {
let bytes = self.into_bytes()?;
let mut file = File::create(path)?;
file.write_all(&bytes)?;
Ok(())
}
pub fn into_bytes(mut self) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
self = self.with_updated_tagrecord_offsets_and_sizes();
let mut buf = Vec::new();
buf.extend_from_slice(&self.header);
debug_assert!(buf.len() == 128, "Header should be exactly 128 bytes long");
let tag_count = self.tags.len() as u32;
buf.extend_from_slice(&tag_count.to_be_bytes());
debug_assert!(
buf.len() == 128 + 4,
"Header + tag count should be 132 bytes long"
);
for (sig, tag) in &self.tags {
buf.extend_from_slice(&sig.to_u32().to_be_bytes());
buf.extend_from_slice(&tag.offset.to_be_bytes());
buf.extend_from_slice(&tag.size.abs().to_be_bytes());
}
debug_assert!(
buf.len() == 128 + 4 + self.tags.len() * 12,
"Header + tag count + tag table should be {} bytes long",
128 + 4 + self.tags.len() * 12
);
for (tag_signature, mut tag) in self.tags {
let should_write = if self.shared_tags { tag.size > 0 } else { true };
if should_write {
if buf.len() < tag.offset as usize {
buf.resize(tag.offset as usize, 0);
}
if tag_signature == TagSignature::NamedColor2 {
let pcs = u32::from_be_bytes(self.header[20..24].try_into().unwrap()); if pcs == 0x4C616220 {
let mut flag =
u32::from_be_bytes(tag.tag.as_slice()[8..12].try_into().unwrap()); flag &= !0x1_0000; tag.tag.data_mut().as_mut_slice()[8..12]
.copy_from_slice(&flag.to_be_bytes()); };
}
buf.extend_from_slice(tag.tag.as_slice());
} }
buf.extend(vec![0u8; crate::pad_size(buf.len())]);
let length = buf.len() as u32;
buf[0..4].copy_from_slice(&length.to_be_bytes());
if buf[99] > 0 {
set_profile_id(&mut buf);
} else {
buf[84..=99].fill(0); }
Ok(buf)
}
pub fn into_string(self) -> Result<String, Box<dyn std::error::Error>> {
let bytes = self.into_bytes()?;
Ok(String::from_utf8_lossy(&bytes).into_owned())
}
fn with_updated_tagrecord_offsets_and_sizes(mut self) -> Self {
let tag_count = self.tags.len();
let data_start = 128 + 4 + (tag_count * 12);
let mut offset_for_next_tag = crate::padded_size(data_start);
if !self.shared_tags {
for (_signature, tag_record) in self.tags.iter_mut() {
tag_record.offset = offset_for_next_tag as u32;
tag_record.size = tag_record.tag.len() as i32;
offset_for_next_tag += crate::padded_size(tag_record.tag.len());
}
debug_assert!(self.tags.values().all(|t| t.size > 0));
return self;
}
let mut shared_location: HashMap<&[u8], (u32, i32)> = HashMap::new();
for (_signature, tag_record) in self.tags.iter_mut() {
(tag_record.offset, tag_record.size) = if let Some(&(offset, size)) =
shared_location.get(tag_record.tag.data().as_slice())
{
(offset, -size)
} else {
let offset = offset_for_next_tag as u32;
let size = tag_record.tag.len() as i32;
shared_location.insert(tag_record.tag.data().as_slice(), (offset, size));
offset_for_next_tag += crate::padded_size(tag_record.tag.len());
(offset, size)
};
}
self
}
pub fn uses_shared_tags(&self) -> bool {
self.shared_tags
}
pub fn into_class_profile(self) -> Profile {
match self.device_class() {
DeviceClass::Input => Profile::Input(super::InputProfile(self)),
DeviceClass::Display => Profile::Display(super::DisplayProfile(self)),
DeviceClass::Output => Profile::Output(super::OutputProfile(self)),
DeviceClass::DeviceLink => Profile::DeviceLink(super::DeviceLinkProfile(self)),
DeviceClass::Abstract => Profile::Abstract(super::AbstractProfile(self)),
DeviceClass::ColorSpace => Profile::ColorSpace(super::ColorSpaceProfile(self)),
DeviceClass::NamedColor => Profile::NamedColor(super::NamedColorProfile(self)),
DeviceClass::Spectral => Profile::Spectral(super::SpectralProfile(self)),
DeviceClass::None => Profile::Raw(self),
}
}
}
impl FromStr for RawProfile {
type Err = Box<dyn std::error::Error>;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::from_bytes(s.as_bytes())
}
}