use crate::run_text::RunTexts;
use std::collections::HashMap;
use std::fmt;
use std::sync::Arc;
#[derive(Debug, Default)]
pub struct Sources {
pub texts: RunTexts,
pub images: ImageSources,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ImageCodec {
Jpeg,
Png,
}
impl ImageCodec {
pub fn sniff(bytes: &[u8]) -> Option<Self> {
match bytes {
[0xFF, 0xD8, 0xFF, ..] => Some(ImageCodec::Jpeg),
[0x89, b'P', b'N', b'G', b'\r', b'\n', 0x1A, b'\n', ..] => Some(ImageCodec::Png),
_ => None,
}
}
}
#[derive(Clone)]
pub struct EncodedImage {
codec: ImageCodec,
bytes: Arc<Vec<u8>>,
}
impl EncodedImage {
pub fn sniffed(bytes: Vec<u8>) -> Option<Self> {
let codec = ImageCodec::sniff(&bytes)?;
Some(EncodedImage {
codec,
bytes: Arc::new(bytes),
})
}
pub fn codec(&self) -> ImageCodec {
self.codec
}
pub fn bytes(&self) -> &Arc<Vec<u8>> {
&self.bytes
}
}
impl fmt::Debug for EncodedImage {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("EncodedImage")
.field("codec", &self.codec)
.field("len", &self.bytes.len())
.finish()
}
}
#[derive(Debug, Default)]
pub struct ImageSources {
by_blob: HashMap<u64, EncodedImage>,
}
impl ImageSources {
pub fn insert(&mut self, blob: u64, source: EncodedImage) {
self.by_blob.insert(blob, source);
}
pub fn get(&self, blob: u64) -> Option<&EncodedImage> {
self.by_blob.get(&blob)
}
pub fn len(&self) -> usize {
self.by_blob.len()
}
pub fn is_empty(&self) -> bool {
self.by_blob.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::ImageCodec;
const CASES: &[(&[u8], Option<ImageCodec>)] = &[
(&[0xFF, 0xD8, 0xFF, 0xE0], Some(ImageCodec::Jpeg)),
(b"\x89PNG\r\n\x1a\nrest", Some(ImageCodec::Png)),
(b"GIF89a", None),
(b"", None),
];
#[test]
fn codecs_are_sniffed_by_signature() {
for &(bytes, want) in CASES {
assert_eq!(ImageCodec::sniff(bytes), want, "{bytes:?}");
}
}
}