const DOS_EPS_MAGIC: [u8; 4] = [0xC5, 0xD0, 0xD3, 0xC6];
pub fn strip_dos_eps_header(data: &[u8]) -> &[u8] {
if data.len() < 12 || data[..4] != DOS_EPS_MAGIC {
return data;
}
let ps_offset = u32::from_le_bytes([data[4], data[5], data[6], data[7]]) as usize;
let ps_length = u32::from_le_bytes([data[8], data[9], data[10], data[11]]) as usize;
let start = ps_offset.min(data.len());
let end = (ps_offset + ps_length).min(data.len());
&data[start..end]
}
pub fn content_is_epsf(data: &[u8]) -> bool {
let end = data
.iter()
.position(|&b| b == b'\n' || b == b'\r')
.unwrap_or(data.len().min(256));
let first_line = &data[..end];
first_line.windows(4).any(|w| w == b"EPSF")
}
pub fn read_eps_bounding_box(data: &[u8]) -> Option<(f64, f64, f64, f64)> {
let header_end = data.len().min(4096);
let header = &data[..header_end];
let mut bbox = None;
let mut hires_bbox = None;
let mut need_atend = false;
scan_for_bbox(header, &mut bbox, &mut hires_bbox, &mut need_atend);
if need_atend && data.len() > 4096 {
let trailer_start = data.len().saturating_sub(4096);
let trailer = &data[trailer_start..];
scan_for_bbox(trailer, &mut bbox, &mut hires_bbox, &mut need_atend);
}
hires_bbox.or(bbox)
}
fn scan_for_bbox(
data: &[u8],
bbox: &mut Option<(f64, f64, f64, f64)>,
hires_bbox: &mut Option<(f64, f64, f64, f64)>,
need_atend: &mut bool,
) {
for line in data.split(|&b| b == b'\n' || b == b'\r') {
if line.is_empty() {
continue;
}
if line.starts_with(b"%%HiResBoundingBox:") {
let rest = &line[b"%%HiResBoundingBox:".len()..];
if let Some(values) = parse_four_numbers(rest) {
*hires_bbox = Some(values);
}
} else if line.starts_with(b"%%BoundingBox:") {
let rest = &line[b"%%BoundingBox:".len()..];
let trimmed = trim_ascii(rest);
if trimmed == b"(atend)" {
*need_atend = true;
} else if let Some(values) = parse_four_numbers(rest) {
*bbox = Some(values);
}
}
}
}
fn parse_four_numbers(data: &[u8]) -> Option<(f64, f64, f64, f64)> {
let s = std::str::from_utf8(data).ok()?;
let mut nums = s.split_whitespace().filter_map(|w| w.parse::<f64>().ok());
let llx = nums.next()?;
let lly = nums.next()?;
let urx = nums.next()?;
let ury = nums.next()?;
Some((llx, lly, urx, ury))
}
fn trim_ascii(data: &[u8]) -> &[u8] {
let start = data
.iter()
.position(|b| !b.is_ascii_whitespace())
.unwrap_or(data.len());
let end = data
.iter()
.rposition(|b| !b.is_ascii_whitespace())
.map_or(start, |p| p + 1);
&data[start..end]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_strip_dos_header() {
let ps_content = b"%!PS-Adobe-3.0 EPSF-3.0\n%%BoundingBox: 0 0 100 100\n";
let ps_offset: u32 = 30; let ps_length = ps_content.len() as u32;
let mut data = Vec::new();
data.extend_from_slice(&DOS_EPS_MAGIC);
data.extend_from_slice(&ps_offset.to_le_bytes());
data.extend_from_slice(&ps_length.to_le_bytes());
data.resize(30, 0xFF);
data.extend_from_slice(ps_content);
let result = strip_dos_eps_header(&data);
assert_eq!(result, ps_content);
}
#[test]
fn test_strip_no_header() {
let ps_content = b"%!PS-Adobe-3.0\n%%BoundingBox: 0 0 200 300\n";
let result = strip_dos_eps_header(ps_content);
assert_eq!(result, ps_content);
}
#[test]
fn test_content_is_epsf() {
assert!(content_is_epsf(
b"%!PS-Adobe-2.0 EPSF-1.2\n%%BoundingBox: 0 0 100 100\n"
));
assert!(content_is_epsf(
b"%!PS-Adobe-3.0 EPSF-3.0\r%%BoundingBox: 0 0 100 100\r"
));
assert!(!content_is_epsf(
b"%!PS-Adobe-3.0\n%%BoundingBox: 0 0 100 100\n"
));
assert!(!content_is_epsf(b"/Helvetica findfont\n"));
}
#[test]
fn test_bbox_integer() {
let data = b"%!PS-Adobe-3.0 EPSF-3.0\n%%BoundingBox: 0 0 200 300\n";
let bbox = read_eps_bounding_box(data);
assert_eq!(bbox, Some((0.0, 0.0, 200.0, 300.0)));
}
#[test]
fn test_bbox_hires_preferred() {
let data = b"%!PS-Adobe-3.0 EPSF-3.0\n%%BoundingBox: 0 0 200 300\n%%HiResBoundingBox: 0.5 1.5 199.75 299.25\n";
let bbox = read_eps_bounding_box(data);
assert_eq!(bbox, Some((0.5, 1.5, 199.75, 299.25)));
}
#[test]
fn test_bbox_not_found() {
let data = b"%!PS-Adobe-3.0\n/Helvetica findfont 12 scalefont setfont\n";
let bbox = read_eps_bounding_box(data);
assert_eq!(bbox, None);
}
#[test]
fn test_bbox_negative_coords() {
let data = b"%!PS-Adobe-3.0 EPSF-3.0\n%%BoundingBox: -50 -25 150 275\n";
let bbox = read_eps_bounding_box(data);
assert_eq!(bbox, Some((-50.0, -25.0, 150.0, 275.0)));
}
#[test]
fn test_bbox_cr_line_endings() {
let data = b"%!PS-Adobe-3.0 EPSF-3.0\r%%BoundingBox: 0 9 360 135\r%%HiResBoundingBox: 0 9.001 360 135\r";
let bbox = read_eps_bounding_box(data);
assert_eq!(bbox, Some((0.0, 9.001, 360.0, 135.0)));
}
#[test]
fn test_bbox_atend() {
let mut data = Vec::new();
data.extend_from_slice(b"%!PS-Adobe-3.0 EPSF-3.0\n%%BoundingBox: (atend)\n");
data.resize(5000, b' ');
data.extend_from_slice(b"\n%%BoundingBox: 10 20 300 400\n%%EOF\n");
let bbox = read_eps_bounding_box(&data);
assert_eq!(bbox, Some((10.0, 20.0, 300.0, 400.0)));
}
}