use docling_core::PictureImage;
pub(crate) struct RecordHeader {
pub(crate) version: u8,
pub(crate) instance: u16,
pub(crate) rec_type: u16,
}
pub(crate) struct Records<'a> {
data: &'a [u8],
pos: usize,
}
impl<'a> Records<'a> {
pub(crate) fn new(data: &'a [u8]) -> Self {
Self { data, pos: 0 }
}
}
impl<'a> Iterator for Records<'a> {
type Item = (RecordHeader, &'a [u8]);
fn next(&mut self) -> Option<Self::Item> {
let d = self.data.get(self.pos..)?;
if d.len() < 8 {
return None;
}
let ver_inst = u16::from_le_bytes([d[0], d[1]]);
let rec_type = u16::from_le_bytes([d[2], d[3]]);
let len = u32::from_le_bytes([d[4], d[5], d[6], d[7]]) as usize;
let body = d.get(8..8 + len)?;
self.pos += 8 + len;
Some((
RecordHeader {
version: (ver_inst & 0x0F) as u8,
instance: ver_inst >> 4,
rec_type,
},
body,
))
}
}
pub(crate) const RT_BLIP_EMF: u16 = 0xF01A;
pub(crate) const RT_BLIP_WMF: u16 = 0xF01B;
pub(crate) const RT_BLIP_PICT: u16 = 0xF01C;
pub(crate) const RT_BLIP_JPEG: u16 = 0xF01D;
pub(crate) const RT_BLIP_PNG: u16 = 0xF01E;
pub(crate) const RT_BLIP_DIB: u16 = 0xF01F;
pub(crate) const RT_BLIP_TIFF: u16 = 0xF029;
pub(crate) const RT_BLIP_JPEG2: u16 = 0xF02A;
pub(crate) fn is_blip(rec_type: u16) -> bool {
matches!(
rec_type,
RT_BLIP_EMF
| RT_BLIP_WMF
| RT_BLIP_PICT
| RT_BLIP_JPEG
| RT_BLIP_PNG
| RT_BLIP_DIB
| RT_BLIP_TIFF
| RT_BLIP_JPEG2
)
}
pub(crate) fn decode_blip(header: &RecordHeader, body: &[u8]) -> Option<PictureImage> {
let mime = match header.rec_type {
RT_BLIP_JPEG | RT_BLIP_JPEG2 => "image/jpeg",
RT_BLIP_PNG => "image/png",
RT_BLIP_TIFF => "image/tiff",
RT_BLIP_DIB => "image/bmp",
_ => return None, };
for uid_len in [16usize, 32] {
let Some(rest) = body.get(uid_len + 1..) else {
continue;
};
if header.rec_type == RT_BLIP_DIB {
if let Some(img) = decode_dib(rest) {
return Some(img);
}
if let Some(img) = body.get(uid_len..).and_then(decode_dib) {
return Some(img);
}
} else if let Some(img) = crate::backend::images::build_picture(mime, rest.to_vec()) {
return Some(img);
}
}
None
}
fn decode_dib(dib: &[u8]) -> Option<PictureImage> {
if dib.len() < 40 || u32::from_le_bytes([dib[0], dib[1], dib[2], dib[3]]) < 40 {
return None;
}
let mut bmp = Vec::with_capacity(dib.len() + 14);
bmp.extend_from_slice(b"BM");
bmp.extend_from_slice(&(14 + dib.len() as u32).to_le_bytes());
bmp.extend_from_slice(&[0; 4]);
bmp.extend_from_slice(&(14u32 + 40).to_le_bytes());
bmp.extend_from_slice(dib);
crate::backend::images::build_picture("image/bmp", bmp)
}
pub(crate) const RT_FBSE: u16 = 0xF007;
pub(crate) fn first_blip(data: &[u8], depth: usize) -> Option<PictureImage> {
if depth > 16 {
return None;
}
for (h, body) in Records::new(data) {
if is_blip(h.rec_type) {
if let Some(img) = decode_blip(&h, body) {
return Some(img);
}
} else if h.rec_type == RT_FBSE {
if let Some(img) = body.get(36..).and_then(|tail| first_blip(tail, depth + 1)) {
return Some(img);
}
} else if h.version == 0xF {
if let Some(img) = first_blip(body, depth + 1) {
return Some(img);
}
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn record_iterator_walks_headers() {
let data = [0x1Fu8, 0x00, 0x04, 0xF0, 0x00, 0x00, 0x00, 0x00];
let mut it = Records::new(&data);
let (h, body) = it.next().expect("one record");
assert_eq!(h.version, 0xF);
assert_eq!(h.instance, 1);
assert_eq!(h.rec_type, 0xF004);
assert!(body.is_empty());
assert!(it.next().is_none());
}
#[test]
fn truncated_record_is_none_not_panic() {
assert!(Records::new(&[0x00, 0x00, 0x1E]).next().is_none());
let data = [0x00u8, 0x00, 0x1E, 0xF0, 0xFF, 0x00, 0x00, 0x00];
assert!(Records::new(&data).next().is_none());
}
#[test]
fn decode_blip_finds_png_behind_single_uid() {
const RED_PNG: &[u8] = &[
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48,
0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x02, 0x00, 0x00,
0x00, 0x90, 0x77, 0x53, 0xde, 0x00, 0x00, 0x00, 0x0c, 0x49, 0x44, 0x41, 0x54, 0x08,
0xd7, 0x63, 0xf8, 0xcf, 0xc0, 0x00, 0x00, 0x00, 0x03, 0x00, 0x01, 0x6e, 0x2c, 0xdc,
0x33, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82,
];
let mut body = vec![0u8; 16]; body.push(0xFF); body.extend_from_slice(RED_PNG);
let h = RecordHeader {
version: 0,
instance: 0x6E0,
rec_type: RT_BLIP_PNG,
};
let img = decode_blip(&h, &body).expect("decodes");
assert_eq!((img.width, img.height), (1, 1));
}
}