use super::*;
use std::collections::HashMap;
fn name(s: &str) -> Object {
Object::Name(s.to_string())
}
fn separation_cs(ink: &str) -> Object {
Object::Array(vec![name("Separation"), name(ink), name("DeviceCMYK"), Object::Null])
}
fn device_n_cs(inks: &[&str]) -> Object {
Object::Array(vec![
name("DeviceN"),
Object::Array(inks.iter().map(|s| name(s)).collect()),
name("DeviceCMYK"),
Object::Null,
])
}
#[test]
fn extracts_separation_ink_name() {
let mut cs_dict = HashMap::new();
cs_dict.insert("CS0".to_string(), separation_cs("Pantone-185"));
let mut out = Vec::new();
extract_inks_from_color_space_dict(&cs_dict, None, &mut out);
assert_eq!(out, vec!["Pantone-185".to_string()]);
}
#[test]
fn extracts_devicen_ink_names_in_declared_order() {
let mut cs_dict = HashMap::new();
cs_dict.insert("CS0".to_string(), device_n_cs(&["Cyan", "Magenta", "SpotGold"]));
let mut out = Vec::new();
extract_inks_from_color_space_dict(&cs_dict, None, &mut out);
assert_eq!(
out,
vec!["Cyan".to_string(), "Magenta".to_string(), "SpotGold".to_string()]
);
}
#[test]
fn skips_all_and_none_colorants() {
let mut cs_dict = HashMap::new();
cs_dict.insert("CS0".to_string(), separation_cs("All"));
cs_dict.insert("CS1".to_string(), separation_cs("None"));
cs_dict.insert("CS2".to_string(), device_n_cs(&["All", "Spot1", "None"]));
let mut out = Vec::new();
extract_inks_from_color_space_dict(&cs_dict, None, &mut out);
assert_eq!(out, vec!["Spot1".to_string()]);
}
#[test]
fn ignores_non_separation_color_spaces() {
let mut cs_dict = HashMap::new();
cs_dict.insert("CS0".to_string(), Object::Array(vec![name("ICCBased"), Object::Null]));
cs_dict.insert("CS1".to_string(), name("DeviceCMYK"));
let mut out = Vec::new();
extract_inks_from_color_space_dict(&cs_dict, None, &mut out);
assert!(out.is_empty());
}
fn build_pdf_with_self_referential_pattern_cs() -> Vec<u8> {
let mut pdf = b"%PDF-1.4\n".to_vec();
let off1 = pdf.len();
pdf.extend_from_slice(b"1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n");
let off2 = pdf.len();
pdf.extend_from_slice(b"2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 >>\nendobj\n");
let off3 = pdf.len();
pdf.extend_from_slice(
b"3 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Contents 4 0 R /Resources << /ColorSpace << /CS0 5 0 R >> >> >>\nendobj\n",
);
let off4 = pdf.len();
pdf.extend_from_slice(b"4 0 obj\n<< /Length 0 >>\nstream\n\nendstream\nendobj\n");
let off5 = pdf.len();
pdf.extend_from_slice(b"5 0 obj\n[/Pattern 5 0 R]\nendobj\n");
let xref_off = pdf.len();
pdf.extend_from_slice(b"xref\n0 6\n");
pdf.extend_from_slice(b"0000000000 65535 f \n");
pdf.extend_from_slice(format!("{:010} 00000 n \n", off1).as_bytes());
pdf.extend_from_slice(format!("{:010} 00000 n \n", off2).as_bytes());
pdf.extend_from_slice(format!("{:010} 00000 n \n", off3).as_bytes());
pdf.extend_from_slice(format!("{:010} 00000 n \n", off4).as_bytes());
pdf.extend_from_slice(format!("{:010} 00000 n \n", off5).as_bytes());
pdf.extend_from_slice(format!("trailer\n<< /Size 6 /Root 1 0 R >>\nstartxref\n{}\n%%EOF\n", xref_off).as_bytes());
pdf
}
#[test]
fn self_referential_pattern_cs_does_not_stack_overflow() {
let pdf = build_pdf_with_self_referential_pattern_cs();
let doc = PdfDocument::from_bytes(pdf).expect("synthetic PDF should parse");
let cs_def = doc
.load_object(ObjectRef { id: 5, generation: 0 })
.expect("object 5 should load");
let mut out = Vec::new();
let mut visited: std::collections::HashSet<ObjectRef> = std::collections::HashSet::new();
super::collect_inks_from_color_space(&cs_def, Some(&doc), &mut out, &mut visited, 0);
assert!(
out.is_empty(),
"self-referential Pattern colour space surfaces no concrete colorants"
);
}
#[test]
fn get_page_inks_handles_self_referential_pattern_cs() {
let pdf = build_pdf_with_self_referential_pattern_cs();
let doc = PdfDocument::from_bytes(pdf).expect("synthetic PDF should parse");
let inks = doc.get_page_inks(0).expect("page-inks walk must not panic");
assert!(inks.is_empty(), "self-cycle yields no plates");
}