use crate::pdf_objects::{Dictionary, Object, ObjectId, Stream};
pub const MAX_FONT_STREAM_SIZE: usize = 10 * 1024 * 1024;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CIDFontSubtype {
Type0,
Type2,
}
pub fn detect_type0_font(dict: &Dictionary) -> bool {
if let Some(Object::Name(subtype)) = dict.get("Subtype") {
return subtype.as_str() == "Type0";
}
false
}
pub fn extract_descendant_fonts_ref(dict: &Dictionary) -> Option<Vec<ObjectId>> {
let descendants = dict.get("DescendantFonts")?;
match descendants {
Object::Array(arr) => {
let refs: Vec<ObjectId> = arr
.iter()
.filter_map(|obj| {
if let Object::Reference(id) = obj {
Some(*id)
} else {
None
}
})
.collect();
if refs.is_empty() {
None
} else {
Some(refs)
}
}
Object::Reference(id) => Some(vec![*id]),
_ => None,
}
}
pub fn detect_cidfont_subtype(dict: &Dictionary) -> Option<CIDFontSubtype> {
if let Some(Object::Name(subtype)) = dict.get("Subtype") {
match subtype.as_str() {
"CIDFontType0" => Some(CIDFontSubtype::Type0),
"CIDFontType2" => Some(CIDFontSubtype::Type2),
_ => None,
}
} else {
None
}
}
pub fn extract_tounicode_ref(dict: &Dictionary) -> Option<ObjectId> {
if let Some(Object::Reference(id)) = dict.get("ToUnicode") {
Some(*id)
} else {
None
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FontFileType {
Type1,
TrueType,
CFF,
}
#[derive(Debug, Clone)]
pub struct Type0FontInfo {
pub type0_dict: Dictionary,
pub cidfont_dict: Option<Dictionary>,
pub cidfont_subtype: Option<CIDFontSubtype>,
pub font_descriptor: Option<Dictionary>,
pub font_stream: Option<Stream>,
pub font_file_type: Option<FontFileType>,
pub tounicode_stream: Option<Stream>,
}
impl Type0FontInfo {
pub fn new(type0_dict: Dictionary) -> Self {
Self {
type0_dict,
cidfont_dict: None,
cidfont_subtype: None,
font_descriptor: None,
font_stream: None,
font_file_type: None,
tounicode_stream: None,
}
}
pub fn has_embedded_font(&self) -> bool {
self.font_stream.is_some()
}
pub fn has_tounicode(&self) -> bool {
self.tounicode_stream.is_some()
}
}
pub fn extract_font_descriptor_ref(dict: &Dictionary) -> Option<ObjectId> {
if let Some(Object::Reference(id)) = dict.get("FontDescriptor") {
Some(*id)
} else {
None
}
}
pub fn extract_font_file_ref(descriptor: &Dictionary) -> Option<(ObjectId, FontFileType)> {
if let Some(Object::Reference(id)) = descriptor.get("FontFile") {
return Some((*id, FontFileType::Type1));
}
if let Some(Object::Reference(id)) = descriptor.get("FontFile2") {
return Some((*id, FontFileType::TrueType));
}
if let Some(Object::Reference(id)) = descriptor.get("FontFile3") {
return Some((*id, FontFileType::CFF));
}
None
}
pub fn extract_widths_ref(cidfont: &Dictionary) -> Option<ObjectId> {
if let Some(Object::Reference(id)) = cidfont.get("W") {
Some(*id)
} else {
None
}
}
pub fn extract_default_width(cidfont: &Dictionary) -> i64 {
if let Some(obj) = cidfont.get("DW") {
obj.as_integer().unwrap_or(1000)
} else {
1000 }
}
pub fn resolve_type0_hierarchy<F>(type0_dict: &Dictionary, resolver: F) -> Option<Type0FontInfo>
where
F: Fn(ObjectId) -> Option<Object>,
{
use std::collections::HashSet;
if !detect_type0_font(type0_dict) {
return None;
}
let mut info = Type0FontInfo::new(type0_dict.clone());
let mut visited = HashSet::new();
if let Some(refs) = extract_descendant_fonts_ref(type0_dict) {
if let Some(cidfont_ref) = refs.first() {
if visited.contains(cidfont_ref) {
tracing::warn!("Circular reference detected at CIDFont {:?}", cidfont_ref);
return Some(info);
}
visited.insert(*cidfont_ref);
if let Some(Object::Dictionary(cidfont)) = resolver(*cidfont_ref) {
info.cidfont_subtype = detect_cidfont_subtype(&cidfont);
if let Some(desc_ref) = extract_font_descriptor_ref(&cidfont) {
if visited.contains(&desc_ref) {
tracing::warn!(
"Circular reference detected at FontDescriptor {:?}",
desc_ref
);
info.cidfont_dict = Some(cidfont);
return Some(info);
}
visited.insert(desc_ref);
if let Some(Object::Dictionary(descriptor)) = resolver(desc_ref) {
if let Some((file_ref, file_type)) = extract_font_file_ref(&descriptor) {
if visited.contains(&file_ref) {
tracing::warn!(
"Circular reference detected at FontFile {:?}",
file_ref
);
info.font_descriptor = Some(descriptor);
info.cidfont_dict = Some(cidfont);
return Some(info);
}
visited.insert(file_ref);
if let Some(Object::Stream(stream)) = resolver(file_ref) {
if stream.data.len() > MAX_FONT_STREAM_SIZE {
tracing::warn!(
"Font stream size {} exceeds limit {} for {:?}",
stream.data.len(),
MAX_FONT_STREAM_SIZE,
file_ref
);
} else {
info.font_stream = Some(stream);
info.font_file_type = Some(file_type);
}
}
}
info.font_descriptor = Some(descriptor);
}
}
info.cidfont_dict = Some(cidfont);
}
}
}
if let Some(tounicode_ref) = extract_tounicode_ref(type0_dict) {
if visited.contains(&tounicode_ref) {
tracing::warn!(
"Circular reference detected at ToUnicode {:?}",
tounicode_ref
);
return Some(info);
}
if let Some(Object::Stream(stream)) = resolver(tounicode_ref) {
info.tounicode_stream = Some(stream);
}
}
Some(info)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::pdf_objects::{Array, Name};
#[test]
fn test_detect_type0_font() {
let mut type0_dict = Dictionary::new();
type0_dict.set("Subtype", Name::new("Type0"));
assert!(detect_type0_font(&type0_dict));
let mut type1_dict = Dictionary::new();
type1_dict.set("Subtype", Name::new("Type1"));
assert!(!detect_type0_font(&type1_dict));
let empty_dict = Dictionary::new();
assert!(!detect_type0_font(&empty_dict));
}
#[test]
fn test_extract_descendant_fonts_ref() {
let mut dict = Dictionary::new();
let mut arr = Array::new();
arr.push(Object::Reference(ObjectId::new(10, 0)));
dict.set("DescendantFonts", Object::Array(arr));
let result = extract_descendant_fonts_ref(&dict);
assert!(result.is_some());
let refs = result.unwrap();
assert_eq!(refs.len(), 1);
assert_eq!(refs[0], ObjectId::new(10, 0));
let mut empty_arr_dict = Dictionary::new();
empty_arr_dict.set("DescendantFonts", Object::Array(Array::new()));
assert!(extract_descendant_fonts_ref(&empty_arr_dict).is_none());
let no_descendants = Dictionary::new();
assert!(extract_descendant_fonts_ref(&no_descendants).is_none());
}
#[test]
fn test_detect_cidfont_subtype() {
let mut type0_dict = Dictionary::new();
type0_dict.set("Subtype", Name::new("CIDFontType0"));
assert_eq!(
detect_cidfont_subtype(&type0_dict),
Some(CIDFontSubtype::Type0)
);
let mut type2_dict = Dictionary::new();
type2_dict.set("Subtype", Name::new("CIDFontType2"));
assert_eq!(
detect_cidfont_subtype(&type2_dict),
Some(CIDFontSubtype::Type2)
);
let mut truetype_dict = Dictionary::new();
truetype_dict.set("Subtype", Name::new("TrueType"));
assert_eq!(detect_cidfont_subtype(&truetype_dict), None);
let empty_dict = Dictionary::new();
assert_eq!(detect_cidfont_subtype(&empty_dict), None);
}
#[test]
fn test_extract_tounicode_ref() {
let mut dict = Dictionary::new();
dict.set("ToUnicode", Object::Reference(ObjectId::new(20, 0)));
assert_eq!(extract_tounicode_ref(&dict), Some(ObjectId::new(20, 0)));
let empty_dict = Dictionary::new();
assert!(extract_tounicode_ref(&empty_dict).is_none());
let mut wrong_type = Dictionary::new();
wrong_type.set("ToUnicode", Name::new("Identity-H"));
assert!(extract_tounicode_ref(&wrong_type).is_none());
}
#[test]
fn test_descendant_fonts_with_direct_reference() {
let mut dict = Dictionary::new();
dict.set("DescendantFonts", Object::Reference(ObjectId::new(15, 0)));
let result = extract_descendant_fonts_ref(&dict);
assert!(result.is_some());
let refs = result.unwrap();
assert_eq!(refs.len(), 1);
assert_eq!(refs[0], ObjectId::new(15, 0));
}
#[test]
fn test_type0_font_info_new() {
let mut dict = Dictionary::new();
dict.set("Subtype", Name::new("Type0"));
let info = Type0FontInfo::new(dict);
assert!(info.cidfont_dict.is_none());
assert!(info.cidfont_subtype.is_none());
assert!(info.font_descriptor.is_none());
assert!(info.font_stream.is_none());
assert!(info.font_file_type.is_none());
assert!(info.tounicode_stream.is_none());
assert!(!info.has_embedded_font());
assert!(!info.has_tounicode());
}
#[test]
fn test_extract_font_descriptor_ref() {
let mut dict = Dictionary::new();
dict.set("FontDescriptor", Object::Reference(ObjectId::new(25, 0)));
assert_eq!(
extract_font_descriptor_ref(&dict),
Some(ObjectId::new(25, 0))
);
let empty_dict = Dictionary::new();
assert!(extract_font_descriptor_ref(&empty_dict).is_none());
let mut wrong_type = Dictionary::new();
wrong_type.set("FontDescriptor", Name::new("SomeFont"));
assert!(extract_font_descriptor_ref(&wrong_type).is_none());
}
#[test]
fn test_extract_font_file_ref_truetype() {
let mut descriptor = Dictionary::new();
descriptor.set("FontFile2", Object::Reference(ObjectId::new(30, 0)));
let result = extract_font_file_ref(&descriptor);
assert!(result.is_some());
let (id, file_type) = result.unwrap();
assert_eq!(id, ObjectId::new(30, 0));
assert_eq!(file_type, FontFileType::TrueType);
}
#[test]
fn test_extract_font_file_ref_cff() {
let mut descriptor = Dictionary::new();
descriptor.set("FontFile3", Object::Reference(ObjectId::new(35, 0)));
let result = extract_font_file_ref(&descriptor);
assert!(result.is_some());
let (id, file_type) = result.unwrap();
assert_eq!(id, ObjectId::new(35, 0));
assert_eq!(file_type, FontFileType::CFF);
}
#[test]
fn test_extract_font_file_ref_type1() {
let mut descriptor = Dictionary::new();
descriptor.set("FontFile", Object::Reference(ObjectId::new(40, 0)));
let result = extract_font_file_ref(&descriptor);
assert!(result.is_some());
let (id, file_type) = result.unwrap();
assert_eq!(id, ObjectId::new(40, 0));
assert_eq!(file_type, FontFileType::Type1);
}
#[test]
fn test_extract_font_file_ref_precedence() {
let mut descriptor = Dictionary::new();
descriptor.set("FontFile", Object::Reference(ObjectId::new(1, 0)));
descriptor.set("FontFile2", Object::Reference(ObjectId::new(2, 0)));
descriptor.set("FontFile3", Object::Reference(ObjectId::new(3, 0)));
let result = extract_font_file_ref(&descriptor);
assert!(result.is_some());
let (id, file_type) = result.unwrap();
assert_eq!(id, ObjectId::new(1, 0));
assert_eq!(file_type, FontFileType::Type1);
}
#[test]
fn test_extract_font_file_ref_none() {
let empty_descriptor = Dictionary::new();
assert!(extract_font_file_ref(&empty_descriptor).is_none());
}
#[test]
fn test_extract_widths_ref() {
let mut cidfont = Dictionary::new();
cidfont.set("W", Object::Reference(ObjectId::new(50, 0)));
assert_eq!(extract_widths_ref(&cidfont), Some(ObjectId::new(50, 0)));
let empty_dict = Dictionary::new();
assert!(extract_widths_ref(&empty_dict).is_none());
let mut inline_w = Dictionary::new();
inline_w.set("W", Object::Array(Array::new()));
assert!(extract_widths_ref(&inline_w).is_none());
}
#[test]
fn test_extract_default_width() {
let mut cidfont = Dictionary::new();
cidfont.set("DW", Object::Integer(500));
assert_eq!(extract_default_width(&cidfont), 500);
let empty_dict = Dictionary::new();
assert_eq!(extract_default_width(&empty_dict), 1000);
let mut wrong_type = Dictionary::new();
wrong_type.set("DW", Name::new("invalid"));
assert_eq!(extract_default_width(&wrong_type), 1000);
}
#[test]
fn test_font_file_type_equality() {
assert_eq!(FontFileType::Type1, FontFileType::Type1);
assert_eq!(FontFileType::TrueType, FontFileType::TrueType);
assert_eq!(FontFileType::CFF, FontFileType::CFF);
assert_ne!(FontFileType::Type1, FontFileType::TrueType);
assert_ne!(FontFileType::TrueType, FontFileType::CFF);
}
use std::collections::HashMap;
fn create_mock_object_store() -> HashMap<ObjectId, Object> {
let mut store = HashMap::new();
let mut cidfont = Dictionary::new();
cidfont.set("Type", Name::new("Font"));
cidfont.set("Subtype", Name::new("CIDFontType2"));
cidfont.set("BaseFont", Name::new("Arial-Bold"));
cidfont.set("FontDescriptor", Object::Reference(ObjectId::new(16, 0)));
cidfont.set("DW", Object::Integer(1000));
store.insert(ObjectId::new(15, 0), Object::Dictionary(cidfont));
let mut descriptor = Dictionary::new();
descriptor.set("Type", Name::new("FontDescriptor"));
descriptor.set("FontName", Name::new("Arial-Bold"));
descriptor.set("FontFile2", Object::Reference(ObjectId::new(17, 0)));
store.insert(ObjectId::new(16, 0), Object::Dictionary(descriptor));
let font_stream = Stream::new(
Dictionary::new(),
vec![0x00, 0x01, 0x00, 0x00], );
store.insert(ObjectId::new(17, 0), Object::Stream(font_stream));
let tounicode_stream = Stream::new(
Dictionary::new(),
b"/CIDInit /ProcSet findresource begin".to_vec(),
);
store.insert(ObjectId::new(20, 0), Object::Stream(tounicode_stream));
store
}
fn create_test_type0_dict() -> Dictionary {
let mut dict = Dictionary::new();
dict.set("Type", Name::new("Font"));
dict.set("Subtype", Name::new("Type0"));
dict.set("BaseFont", Name::new("Arial-Bold"));
dict.set("Encoding", Name::new("Identity-H"));
let mut descendant_array = Array::new();
descendant_array.push(Object::Reference(ObjectId::new(15, 0)));
dict.set("DescendantFonts", Object::Array(descendant_array));
dict.set("ToUnicode", Object::Reference(ObjectId::new(20, 0)));
dict
}
#[test]
fn test_resolve_type0_hierarchy_complete() {
use super::resolve_type0_hierarchy;
let type0_dict = create_test_type0_dict();
let store = create_mock_object_store();
let resolver = |id: ObjectId| -> Option<Object> { store.get(&id).cloned() };
let result = resolve_type0_hierarchy(&type0_dict, resolver);
assert!(result.is_some(), "Should resolve complete hierarchy");
let info = result.unwrap();
assert!(
info.cidfont_dict.is_some(),
"Should have CIDFont dictionary"
);
assert_eq!(
info.cidfont_subtype,
Some(CIDFontSubtype::Type2),
"Should detect CIDFontType2"
);
assert!(info.font_descriptor.is_some(), "Should have FontDescriptor");
assert!(info.font_stream.is_some(), "Should have font stream");
assert_eq!(
info.font_file_type,
Some(FontFileType::TrueType),
"Should be TrueType"
);
assert!(
info.tounicode_stream.is_some(),
"Should have ToUnicode stream"
);
}
#[test]
fn test_resolve_type0_hierarchy_missing_cidfont() {
use super::resolve_type0_hierarchy;
let type0_dict = create_test_type0_dict();
let store: HashMap<ObjectId, Object> = HashMap::new();
let resolver = |id: ObjectId| -> Option<Object> { store.get(&id).cloned() };
let result = resolve_type0_hierarchy(&type0_dict, resolver);
assert!(
result.is_some(),
"Should return partial info even with missing refs"
);
let info = result.unwrap();
assert!(info.cidfont_dict.is_none(), "CIDFont should be None");
assert!(
info.font_descriptor.is_none(),
"FontDescriptor should be None"
);
assert!(info.font_stream.is_none(), "Font stream should be None");
}
#[test]
fn test_resolve_type0_hierarchy_partial_chain() {
use super::resolve_type0_hierarchy;
let type0_dict = create_test_type0_dict();
let mut store = HashMap::new();
let mut cidfont = Dictionary::new();
cidfont.set("Subtype", Name::new("CIDFontType2"));
cidfont.set("FontDescriptor", Object::Reference(ObjectId::new(16, 0)));
store.insert(ObjectId::new(15, 0), Object::Dictionary(cidfont));
let resolver = |id: ObjectId| -> Option<Object> { store.get(&id).cloned() };
let result = resolve_type0_hierarchy(&type0_dict, resolver);
assert!(result.is_some());
let info = result.unwrap();
assert!(info.cidfont_dict.is_some(), "CIDFont should be resolved");
assert_eq!(info.cidfont_subtype, Some(CIDFontSubtype::Type2));
assert!(
info.font_descriptor.is_none(),
"FontDescriptor should be None"
);
assert!(info.font_stream.is_none(), "Font stream should be None");
}
#[test]
fn test_resolve_type0_hierarchy_cff_font() {
use super::resolve_type0_hierarchy;
let type0_dict = create_test_type0_dict();
let mut store = HashMap::new();
let mut cidfont = Dictionary::new();
cidfont.set("Subtype", Name::new("CIDFontType0"));
cidfont.set("FontDescriptor", Object::Reference(ObjectId::new(16, 0)));
store.insert(ObjectId::new(15, 0), Object::Dictionary(cidfont));
let mut descriptor = Dictionary::new();
descriptor.set("FontFile3", Object::Reference(ObjectId::new(17, 0)));
store.insert(ObjectId::new(16, 0), Object::Dictionary(descriptor));
let cff_stream = Stream::new(Dictionary::new(), vec![0x01, 0x00, 0x04, 0x00]);
store.insert(ObjectId::new(17, 0), Object::Stream(cff_stream));
let resolver = |id: ObjectId| -> Option<Object> { store.get(&id).cloned() };
let result = resolve_type0_hierarchy(&type0_dict, resolver);
assert!(result.is_some());
let info = result.unwrap();
assert_eq!(info.cidfont_subtype, Some(CIDFontSubtype::Type0));
assert_eq!(info.font_file_type, Some(FontFileType::CFF));
assert!(info.font_stream.is_some());
}
#[test]
fn test_resolve_type0_hierarchy_not_type0_font() {
use super::resolve_type0_hierarchy;
let mut type1_dict = Dictionary::new();
type1_dict.set("Subtype", Name::new("Type1"));
type1_dict.set("BaseFont", Name::new("Helvetica"));
let store = create_mock_object_store();
let resolver = |id: ObjectId| -> Option<Object> { store.get(&id).cloned() };
let result = resolve_type0_hierarchy(&type1_dict, resolver);
assert!(result.is_none(), "Should return None for non-Type0 font");
}
#[test]
fn test_multiple_descendant_fonts() {
let mut dict = Dictionary::new();
dict.set("Subtype", Name::new("Type0"));
let mut arr = Array::new();
arr.push(Object::Reference(ObjectId::new(10, 0)));
arr.push(Object::Reference(ObjectId::new(11, 0)));
arr.push(Object::Reference(ObjectId::new(12, 0)));
dict.set("DescendantFonts", Object::Array(arr));
let result = extract_descendant_fonts_ref(&dict);
assert!(result.is_some());
let refs = result.unwrap();
assert_eq!(refs.len(), 3, "Should extract all descendant refs");
}
#[test]
fn test_descendant_fonts_with_inline_dict() {
let mut dict = Dictionary::new();
dict.set("Subtype", Name::new("Type0"));
let mut arr = Array::new();
let mut inline_cidfont = Dictionary::new();
inline_cidfont.set("Subtype", Name::new("CIDFontType2"));
arr.push(Object::Dictionary(inline_cidfont));
dict.set("DescendantFonts", Object::Array(arr));
let result = extract_descendant_fonts_ref(&dict);
assert!(
result.is_none(),
"Inline dicts should not be extracted as refs"
);
}
#[test]
fn test_wrong_object_type_in_subtype() {
let mut dict = Dictionary::new();
dict.set("Subtype", Object::Integer(0));
assert!(!detect_type0_font(&dict));
assert!(detect_cidfont_subtype(&dict).is_none());
}
#[test]
fn test_descendant_fonts_wrong_type() {
let mut dict = Dictionary::new();
dict.set("Subtype", Name::new("Type0"));
dict.set("DescendantFonts", Object::Integer(15));
assert!(extract_descendant_fonts_ref(&dict).is_none());
}
#[test]
fn test_resolver_returns_wrong_object_type() {
use super::resolve_type0_hierarchy;
let type0_dict = create_test_type0_dict();
let mut store = HashMap::new();
store.insert(ObjectId::new(15, 0), Object::Integer(42));
let resolver = |id: ObjectId| -> Option<Object> { store.get(&id).cloned() };
let result = resolve_type0_hierarchy(&type0_dict, resolver);
assert!(result.is_some());
let info = result.unwrap();
assert!(
info.cidfont_dict.is_none(),
"CIDFont should be None when resolved to wrong type"
);
}
#[test]
fn test_font_descriptor_returns_wrong_type() {
use super::resolve_type0_hierarchy;
let type0_dict = create_test_type0_dict();
let mut store = HashMap::new();
let mut cidfont = Dictionary::new();
cidfont.set("Subtype", Name::new("CIDFontType2"));
cidfont.set("FontDescriptor", Object::Reference(ObjectId::new(16, 0)));
store.insert(ObjectId::new(15, 0), Object::Dictionary(cidfont));
let stream = Stream::new(Dictionary::new(), vec![0x00]);
store.insert(ObjectId::new(16, 0), Object::Stream(stream));
let resolver = |id: ObjectId| -> Option<Object> { store.get(&id).cloned() };
let result = resolve_type0_hierarchy(&type0_dict, resolver);
assert!(result.is_some());
let info = result.unwrap();
assert!(info.cidfont_dict.is_some(), "CIDFont should be resolved");
assert!(
info.font_descriptor.is_none(),
"FontDescriptor should be None when wrong type"
);
}
#[test]
fn test_font_file_returns_wrong_type() {
use super::resolve_type0_hierarchy;
let type0_dict = create_test_type0_dict();
let mut store = HashMap::new();
let mut cidfont = Dictionary::new();
cidfont.set("Subtype", Name::new("CIDFontType2"));
cidfont.set("FontDescriptor", Object::Reference(ObjectId::new(16, 0)));
store.insert(ObjectId::new(15, 0), Object::Dictionary(cidfont));
let mut descriptor = Dictionary::new();
descriptor.set("FontFile2", Object::Reference(ObjectId::new(17, 0)));
store.insert(ObjectId::new(16, 0), Object::Dictionary(descriptor));
let wrong_dict = Dictionary::new();
store.insert(ObjectId::new(17, 0), Object::Dictionary(wrong_dict));
let resolver = |id: ObjectId| -> Option<Object> { store.get(&id).cloned() };
let result = resolve_type0_hierarchy(&type0_dict, resolver);
assert!(result.is_some());
let info = result.unwrap();
assert!(info.font_descriptor.is_some());
assert!(
info.font_stream.is_none(),
"Font stream should be None when wrong type"
);
assert!(info.font_file_type.is_none());
}
#[test]
fn test_tounicode_returns_wrong_type() {
use super::resolve_type0_hierarchy;
let type0_dict = create_test_type0_dict();
let mut store = HashMap::new();
let wrong_dict = Dictionary::new();
store.insert(ObjectId::new(20, 0), Object::Dictionary(wrong_dict));
let resolver = |id: ObjectId| -> Option<Object> { store.get(&id).cloned() };
let result = resolve_type0_hierarchy(&type0_dict, resolver);
assert!(result.is_some());
let info = result.unwrap();
assert!(
info.tounicode_stream.is_none(),
"ToUnicode should be None when wrong type"
);
}
#[test]
fn test_cidfont_subtype_clone_copy() {
let subtype = CIDFontSubtype::Type2;
let cloned = subtype;
assert_eq!(subtype, cloned);
let subtype2 = CIDFontSubtype::Type0;
assert_ne!(subtype, subtype2);
}
#[test]
fn test_font_file_type_clone_copy() {
let file_type = FontFileType::TrueType;
let cloned = file_type;
assert_eq!(file_type, cloned);
}
#[test]
fn test_type0_font_info_clone() {
let mut dict = Dictionary::new();
dict.set("Subtype", Name::new("Type0"));
let info = Type0FontInfo::new(dict);
let cloned = info.clone();
assert!(!cloned.has_embedded_font());
assert!(!cloned.has_tounicode());
}
#[test]
fn test_empty_descendant_array_edge_case() {
let mut dict = Dictionary::new();
dict.set("Subtype", Name::new("Type0"));
dict.set("DescendantFonts", Object::Array(Array::new()));
assert!(extract_descendant_fonts_ref(&dict).is_none());
}
#[test]
fn test_mixed_array_with_refs_and_other_types() {
let mut dict = Dictionary::new();
dict.set("Subtype", Name::new("Type0"));
let mut arr = Array::new();
arr.push(Object::Reference(ObjectId::new(10, 0)));
arr.push(Object::Integer(42)); arr.push(Object::Reference(ObjectId::new(11, 0)));
arr.push(Object::Name(Name::new("SomeName"))); dict.set("DescendantFonts", Object::Array(arr));
let result = extract_descendant_fonts_ref(&dict);
assert!(result.is_some());
let refs = result.unwrap();
assert_eq!(refs.len(), 2, "Should only extract valid references");
assert_eq!(refs[0], ObjectId::new(10, 0));
assert_eq!(refs[1], ObjectId::new(11, 0));
}
}