use crate::{ pdf::{ PdfObjectAndParseCache, PdfObjects, parse::{ GetPdfInputPosition, PdfInputPosition, PdfInputPositionNoCompare, PdfParse, PdfParseError, }, stream_filters::PdfStreamFilter, }, pdf_parse, util::{ArcOrRef, DagDebugState}, }; use std::{ any::TypeId, borrow::{Borrow, Cow}, collections::BTreeMap, fmt::{self, Write}, iter::FusedIterator, num::NonZero, sync::{Arc, OnceLock}, }; #[derive(Clone, Default, PartialEq, Eq, PartialOrd, Ord)] pub struct PdfString { pos: PdfInputPositionNoCompare, bytes: ArcOrRef<'static, [u8]>, } impl std::fmt::Debug for PdfString { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let Self { pos, bytes: _ } = self; write!(f, "PdfString(at {pos}, {})", self.bytes_debug()) } } #[derive(Clone, Copy)] pub struct PdfStringBytesDebug<'a>(pub &'a [u8]); impl<'a> fmt::Display for PdfStringBytesDebug<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "b\"{}\"", self.0.escape_ascii()) } } impl<'a> fmt::Debug for PdfStringBytesDebug<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(self, f) } } impl PdfString { pub fn new(pos: impl Into, bytes: ArcOrRef<'static, [u8]>) -> Self { Self { pos: pos.into(), bytes, } } pub fn pos(&self) -> PdfInputPosition { self.pos.0 } pub fn bytes(&self) -> &ArcOrRef<'static, [u8]> { &self.bytes } pub fn bytes_debug(&self) -> PdfStringBytesDebug<'_> { PdfStringBytesDebug(&self.bytes) } } impl GetPdfInputPosition for PdfString { fn get_pdf_input_position(&self) -> PdfInputPosition { self.pos.0 } } #[derive(Clone, PartialEq, Eq)] pub struct PdfDate { text: PdfString, } impl fmt::Debug for PdfDate { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let Self { text } = self; let pos = text.pos(); write!(f, "PdfDate(at {pos}, {})", text.bytes_debug()) } } impl IsPdfNull for PdfDate { fn is_pdf_null(&self) -> bool { false } } impl PdfParse for PdfDate { fn type_name() -> Cow<'static, str> { Cow::Borrowed("date") } fn parse(object: PdfObject) -> Result { Self::try_new(PdfString::parse(object)?) } } impl PdfDate { pub fn try_new(text: PdfString) -> Result { // TODO: check syntax Ok(Self { text }) } pub fn text(&self) -> &PdfString { &self.text } } impl GetPdfInputPosition for PdfDate { fn get_pdf_input_position(&self) -> PdfInputPosition { self.text.pos() } } #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct PdfName { pos: PdfInputPositionNoCompare, bytes: ArcOrRef<'static, [u8]>, } impl Borrow<[u8]> for PdfName { fn borrow(&self) -> &[u8] { &self.bytes } } impl PdfName { pub fn try_new( pos: impl Into, bytes: impl Into>, ) -> Option { let bytes = bytes.into(); if bytes.contains(&0) { None } else { Some(Self { pos: pos.into(), bytes, }) } } #[track_caller] pub const fn new_static(bytes: &'static [u8]) -> Self { let mut i = 0; while i < bytes.len() { if bytes[i] == 0 { panic!("shouldn't contain any nul bytes"); } i += 1; } Self { pos: PdfInputPositionNoCompare::empty(), bytes: ArcOrRef::Ref(bytes), } } #[track_caller] pub fn new( pos: impl Into, bytes: impl Into>, ) -> Self { Self::try_new(pos, bytes).expect("shouldn't contain any nul bytes") } pub fn as_bytes(&self) -> &ArcOrRef<'static, [u8]> { &self.bytes } pub fn pos(&self) -> PdfInputPosition { self.pos.0 } } impl GetPdfInputPosition for PdfName { fn get_pdf_input_position(&self) -> PdfInputPosition { self.pos.0 } } impl fmt::Debug for PdfName { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "PdfName(at {}: {self})", self.pos) } } impl fmt::Display for PdfName { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("/")?; for &b in self.bytes.iter() { match b { 0x21..=0x7E if b != b'#' => f.write_char(b.into())?, _ => write!(f, "#{b:02X}")?, } } Ok(()) } } #[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, Default)] pub struct PdfBoolean { pos: PdfInputPositionNoCompare, value: bool, } impl fmt::Debug for PdfBoolean { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let Self { pos, value } = *self; write!(f, "PdfBoolean(at {pos}, {value})") } } impl PdfBoolean { pub fn new(pos: impl Into, value: bool) -> Self { Self { pos: pos.into(), value, } } pub fn pos(&self) -> PdfInputPosition { self.pos.0 } pub fn value(&self) -> bool { self.value } } impl GetPdfInputPosition for PdfBoolean { fn get_pdf_input_position(&self) -> PdfInputPosition { self.pos.0 } } #[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, Default)] pub struct PdfInteger { pos: PdfInputPositionNoCompare, value: i128, } impl fmt::Debug for PdfInteger { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let Self { pos, value } = *self; write!(f, "PdfInteger(at {pos}, {value})") } } impl PdfInteger { pub fn new(pos: impl Into, value: i128) -> Self { Self { pos: pos.into(), value, } } pub fn pos(&self) -> PdfInputPosition { self.pos.0 } pub fn value(&self) -> i128 { self.value } } impl GetPdfInputPosition for PdfInteger { fn get_pdf_input_position(&self) -> PdfInputPosition { self.pos.0 } } #[derive(Clone, Copy, PartialEq, PartialOrd, Default)] pub struct PdfReal { pos: PdfInputPositionNoCompare, value: f64, } impl fmt::Debug for PdfReal { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let Self { pos, value } = *self; write!(f, "PdfReal(at {pos}, {value})") } } impl PdfReal { pub fn new(pos: impl Into, value: f64) -> Self { Self { pos: pos.into(), value, } } pub fn pos(&self) -> PdfInputPosition { self.pos.0 } pub fn value(&self) -> f64 { self.value } } impl GetPdfInputPosition for PdfReal { fn get_pdf_input_position(&self) -> PdfInputPosition { self.pos.0 } } #[derive(Clone, Copy)] pub enum PdfNumber { Integer(PdfInteger), Real(PdfReal), } impl fmt::Debug for PdfNumber { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Integer(v) => v.fmt(f), Self::Real(v) => v.fmt(f), } } } impl PdfNumber { pub fn pos(self) -> PdfInputPosition { match self { Self::Integer(v) => v.pos(), Self::Real(v) => v.pos(), } } pub fn as_f64(self) -> f64 { match self { Self::Integer(v) => v.value as f64, Self::Real(v) => v.value, } } pub fn as_f32(self) -> f32 { match self { Self::Integer(v) => v.value as f32, Self::Real(v) => v.value as f32, } } } impl PartialOrd for PdfNumber { fn partial_cmp(&self, other: &Self) -> Option { match (self, other) { (Self::Integer(this), Self::Integer(other)) => Some(this.cmp(other)), _ => self.as_f64().partial_cmp(&other.as_f64()), } } } impl PartialEq for PdfNumber { fn eq(&self, other: &Self) -> bool { self.partial_cmp(other).is_some_and(|v| v.is_eq()) } } impl Default for PdfNumber { fn default() -> Self { PdfNumber::Integer(PdfInteger::default()) } } impl PdfObjectDirect { pub fn number(&self) -> Option { match *self { PdfObjectDirect::Integer(v) => Some(PdfNumber::Integer(v)), PdfObjectDirect::Real(v) => Some(PdfNumber::Real(v)), PdfObjectDirect::Boolean(_) | PdfObjectDirect::String(_) | PdfObjectDirect::Name(_) | PdfObjectDirect::Array(_) | PdfObjectDirect::Dictionary(_) | PdfObjectDirect::Stream(_) | PdfObjectDirect::Null(_) => None, } } } impl PdfObjectNonNull { pub fn number(&self) -> Option { match *self { PdfObjectNonNull::Integer(v) => Some(PdfNumber::Integer(v)), PdfObjectNonNull::Real(v) => Some(PdfNumber::Real(v)), PdfObjectNonNull::Boolean(_) | PdfObjectNonNull::String(_) | PdfObjectNonNull::Name(_) | PdfObjectNonNull::Array(_) | PdfObjectNonNull::Dictionary(_) | PdfObjectNonNull::Stream(_) => None, } } } impl IsPdfNull for PdfNumber { fn is_pdf_null(&self) -> bool { false } } impl PdfParse for PdfNumber { fn type_name() -> Cow<'static, str> { Cow::Borrowed("number") } fn parse(object: PdfObject) -> Result { let object = PdfObjectDirect::from(object); object.number().ok_or(PdfParseError::InvalidType { pos: object.pos(), ty: object.type_name(), expected_ty: "number", }) } } #[derive(Clone)] pub enum PdfStringOrNumber { String(PdfString), Number(PdfNumber), } impl fmt::Debug for PdfStringOrNumber { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::String(v) => v.fmt(f), Self::Number(v) => v.fmt(f), } } } impl PdfStringOrNumber { pub fn pos(self) -> PdfInputPosition { match self { Self::String(v) => v.pos(), Self::Number(v) => v.pos(), } } } impl PdfObjectDirect { pub fn string_or_number(&self) -> Option { match *self { PdfObjectDirect::Integer(v) => Some(PdfStringOrNumber::Number(PdfNumber::Integer(v))), PdfObjectDirect::Real(v) => Some(PdfStringOrNumber::Number(PdfNumber::Real(v))), PdfObjectDirect::String(ref v) => Some(PdfStringOrNumber::String(v.clone())), PdfObjectDirect::Boolean(_) | PdfObjectDirect::Name(_) | PdfObjectDirect::Array(_) | PdfObjectDirect::Dictionary(_) | PdfObjectDirect::Stream(_) | PdfObjectDirect::Null(_) => None, } } } impl PdfObjectNonNull { pub fn string_or_number(&self) -> Option { match *self { PdfObjectNonNull::Integer(v) => Some(PdfStringOrNumber::Number(PdfNumber::Integer(v))), PdfObjectNonNull::Real(v) => Some(PdfStringOrNumber::Number(PdfNumber::Real(v))), PdfObjectNonNull::String(ref v) => Some(PdfStringOrNumber::String(v.clone())), PdfObjectNonNull::Boolean(_) | PdfObjectNonNull::Name(_) | PdfObjectNonNull::Array(_) | PdfObjectNonNull::Dictionary(_) | PdfObjectNonNull::Stream(_) => None, } } } impl IsPdfNull for PdfStringOrNumber { fn is_pdf_null(&self) -> bool { false } } impl PdfParse for PdfStringOrNumber { fn type_name() -> Cow<'static, str> { Cow::Borrowed("string or number") } fn parse(object: PdfObject) -> Result { let object = PdfObjectDirect::from(object); object.string_or_number().ok_or(PdfParseError::InvalidType { pos: object.pos(), ty: object.type_name(), expected_ty: "string or number", }) } } macro_rules! make_pdf_object { ( $( #[parse = $($parse:ident)?, type_name = $type_name:literal] $Variant:ident($ty:ty), )+ ) => { #[derive(Clone)] pub enum PdfObjectNonNull { $($Variant($ty),)* } impl fmt::Debug for PdfObjectNonNull { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { $(Self::$Variant(v) => v.fmt(f),)* } } } impl IsPdfNull for PdfObjectNonNull { fn is_pdf_null(&self) -> bool { false } } #[derive(Clone)] pub enum PdfObjectDirect { $($Variant($ty),)* Null(PdfNull), } impl IsPdfNull for PdfObjectDirect { fn is_pdf_null(&self) -> bool { self.is_null() } } impl fmt::Debug for PdfObjectDirect { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { $(Self::$Variant(v) => v.fmt(f),)* Self::Null(v) => v.fmt(f), } } } #[derive(Clone)] pub enum PdfObject { $($Variant($ty),)* Null(PdfNull), Indirect(PdfObjectIndirect), } impl IsPdfNull for PdfObject { fn is_pdf_null(&self) -> bool { self.is_null() } } impl fmt::Debug for PdfObject { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { $(Self::$Variant(v) => v.fmt(f),)* Self::Null(v) => v.fmt(f), Self::Indirect(v) => v.fmt(f), } } } $( impl From<$ty> for PdfObjectNonNull { fn from(value: $ty) -> Self { Self::$Variant(value) } } impl From<$ty> for PdfObjectDirect { fn from(value: $ty) -> Self { Self::$Variant(value) } } impl From<$ty> for PdfObject { fn from(value: $ty) -> Self { Self::$Variant(value) } } impl From> for PdfObjectDirect { fn from(value: Option<$ty>) -> Self { match value { Some(value) => Self::$Variant(value), None => Self::Null(Default::default()), } } } impl From> for PdfObject { fn from(value: Option<$ty>) -> Self { match value { Some(value) => Self::$Variant(value), None => Self::Null(Default::default()), } } } $(impl IsPdfNull for $ty { fn is_pdf_null(&self) -> bool { false } } impl PdfParse for $ty { fn type_name() -> Cow<'static, str> { Cow::Borrowed($type_name) } fn $parse(object: PdfObject) -> Result { match PdfObjectDirect::from(object) { PdfObjectDirect::$Variant(v) => Ok(v), object => Err(PdfParseError::InvalidType { pos: object.get_pdf_input_position(), ty: object.type_name(), expected_ty: $type_name, }), } } })? )* impl From for PdfObjectDirect { fn from(value: PdfObjectNonNull) -> Self { match value { $(PdfObjectNonNull::$Variant(v) => Self::$Variant(v),)* } } } impl From for PdfObject { fn from(value: PdfObjectNonNull) -> Self { match value { $(PdfObjectNonNull::$Variant(v) => Self::$Variant(v),)* } } } impl From for PdfObject { fn from(value: PdfObjectDirect) -> Self { match value { $(PdfObjectDirect::$Variant(v) => Self::$Variant(v),)* PdfObjectDirect::Null(v) => Self::Null(v), } } } impl From for PdfObjectDirect { fn from(value: PdfObject) -> Self { match value { $(PdfObject::$Variant(v) => Self::$Variant(v),)* PdfObject::Null(v) => Self::Null(v), PdfObject::Indirect(v) => v.into(), } } } impl PdfObjectNonNull { pub fn type_name(&self) -> &'static str { match self { $(PdfObjectNonNull::$Variant(_) => $type_name,)* } } pub fn pos(&self) -> PdfInputPosition { self.get_pdf_input_position() } } impl GetPdfInputPosition for PdfObjectNonNull { fn get_pdf_input_position(&self) -> PdfInputPosition { match self { $(PdfObjectNonNull::$Variant(v) => <$ty as GetPdfInputPosition>::get_pdf_input_position(v),)* } } } impl From for Option { fn from(value: PdfObjectDirect) -> Self { match value { $(PdfObjectDirect::$Variant(v) => Some(PdfObjectNonNull::$Variant(v)),)* PdfObjectDirect::Null(_) => None, } } } impl From for Option { fn from(value: PdfObject) -> Self { PdfObjectDirect::from(value).into() } } impl PdfObjectDirect { pub fn is_null(&self) -> bool { matches!(self, PdfObjectDirect::Null(_)) } pub fn type_name(&self) -> &'static str { match self { $(PdfObjectDirect::$Variant(_) => $type_name,)* PdfObjectDirect::Null(_) => "null", } } pub fn pos(&self) -> PdfInputPosition { self.get_pdf_input_position() } } impl GetPdfInputPosition for PdfObjectDirect { fn get_pdf_input_position(&self) -> PdfInputPosition { match self { $(PdfObjectDirect::$Variant(v) => <$ty as GetPdfInputPosition>::get_pdf_input_position(v),)* PdfObjectDirect::Null(v) => ::get_pdf_input_position(v), } } } impl PdfObject { pub fn is_null(&self) -> bool { matches!(self, PdfObject::Null(_)) } pub fn type_name(&self) -> &'static str { match self { $(PdfObject::$Variant(_) => $type_name,)* PdfObject::Null(_) => "null", PdfObject::Indirect(_) => "indirect object", } } pub fn pos(&self) -> PdfInputPosition { self.get_pdf_input_position() } } impl GetPdfInputPosition for PdfObject { fn get_pdf_input_position(&self) -> PdfInputPosition { match self { $(PdfObject::$Variant(v) => <$ty as GetPdfInputPosition>::get_pdf_input_position(v),)* PdfObject::Null(v) => ::get_pdf_input_position(v), PdfObject::Indirect(v) => ::get_pdf_input_position(v), } } } const _: () = { fn _assert_parsable() {} $(let _ = _assert_parsable::<$ty>;)* let _ = _assert_parsable::; let _ = _assert_parsable::; let _ = _assert_parsable::; let _ = _assert_parsable::; let _ = _assert_parsable::; }; }; } make_pdf_object! { #[parse = parse, type_name = "boolean"] Boolean(PdfBoolean), #[parse = parse, type_name = "integer"] Integer(PdfInteger), #[parse = parse, type_name = "real"] Real(PdfReal), #[parse = parse, type_name = "string"] String(PdfString), #[parse = parse, type_name = "name"] Name(PdfName), #[parse = parse, type_name = "array"] Array(PdfArray), #[parse =, type_name = "dictionary"] Dictionary(PdfDictionary), #[parse =, type_name = "stream"] Stream(PdfStream), } #[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct PdfNull(PdfInputPositionNoCompare); impl fmt::Debug for PdfNull { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "PdfNull(at {})", self.0) } } impl PdfNull { pub fn new(pos: impl Into) -> Self { Self(pos.into()) } } impl GetPdfInputPosition for PdfNull { fn get_pdf_input_position(&self) -> PdfInputPosition { self.0.0 } } impl From for PdfObjectDirect { fn from(v: PdfNull) -> Self { Self::Null(v) } } impl Default for PdfObjectDirect { fn default() -> Self { Self::Null(PdfNull(PdfInputPositionNoCompare::empty())) } } impl From for PdfObject { fn from(v: PdfNull) -> Self { Self::Null(v) } } impl Default for PdfObject { fn default() -> Self { Self::Null(PdfNull(PdfInputPositionNoCompare::empty())) } } impl From for PdfObject { fn from(v: PdfObjectIndirect) -> Self { Self::Indirect(v) } } #[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct PdfObjectIdentifier { pub pos: PdfInputPositionNoCompare, pub object_number: NonZero, pub generation_number: u16, } impl fmt::Debug for PdfObjectIdentifier { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let Self { pos, object_number, generation_number, } = *self; write!( f, "PdfObjectIdentifier(at {pos}, {object_number}, {generation_number})" ) } } impl GetPdfInputPosition for PdfObjectIdentifier { fn get_pdf_input_position(&self) -> PdfInputPosition { self.pos.0 } } #[derive(Clone)] pub struct PdfObjectIndirect { objects: std::sync::Weak, id: PdfObjectIdentifier, final_id: Arc>, } impl fmt::Debug for PdfObjectIndirect { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let Self { objects: _, id: PdfObjectIdentifier { pos, object_number, generation_number, }, final_id: _, } = *self; write!( f, "PdfObjectIndirect(at {pos}, {object_number}, {generation_number})" ) } } impl GetPdfInputPosition for PdfObjectIndirect { fn get_pdf_input_position(&self) -> PdfInputPosition { self.id.get_pdf_input_position() } } impl PartialEq for PdfObjectIndirect { fn eq(&self, other: &Self) -> bool { let Self { objects, id, final_id: _, } = self; objects.ptr_eq(&other.objects) && *id == other.id } } impl PdfObjectIndirect { pub fn new(objects: &Arc, id: PdfObjectIdentifier) -> Self { Self { objects: Arc::downgrade(objects), id, final_id: Arc::new(OnceLock::new()), } } pub(crate) fn cache_parse( &self, parse_inner: impl FnOnce(PdfObjectDirect) -> Result, E>, ) -> Result, E> { self.get_object_and_parse_cache(|object, object_and_parse_cache| { match object_and_parse_cache { Some(object_and_parse_cache) => { if let Some(retval) = object_and_parse_cache.parse_cache_get::() { println!("cache reused for {object:?}"); return Ok(retval); } parse_inner(object) .map(|retval| object_and_parse_cache.parse_cache_get_or_insert::(retval)) } None => parse_inner(object), } }) } fn get_object_and_parse_cache_inner<'a>( &self, objects: &'a PdfObjects, ) -> (PdfObjectDirect, Option<&'a PdfObjectAndParseCache>) { if let Some(objects) = objects.inner.get() { let final_id = self.final_id.get().copied(); let limit = if final_id.is_some() { 1 } else { 1000usize }; let mut id = final_id.unwrap_or(self.id); for _ in 0..limit { if let Some(object_and_parse_cache) = objects.objects.get(&self.id) { let object = match &object_and_parse_cache.object { PdfObject::Boolean(v) => PdfObjectDirect::Boolean(*v), PdfObject::Integer(v) => PdfObjectDirect::Integer(*v), PdfObject::Real(v) => PdfObjectDirect::Real(*v), PdfObject::String(v) => PdfObjectDirect::String(v.clone()), PdfObject::Name(v) => PdfObjectDirect::Name(v.clone()), PdfObject::Array(v) => PdfObjectDirect::Array(v.clone()), PdfObject::Dictionary(v) => PdfObjectDirect::Dictionary(v.clone()), PdfObject::Stream(v) => PdfObjectDirect::Stream(v.clone()), PdfObject::Null(v) => PdfObjectDirect::Null(*v), PdfObject::Indirect(v) => { id = v.id; continue; } }; // we could be racing with another thread, so set can fail but that's not a problem let _ = self.final_id.set(id); return (object, Some(object_and_parse_cache)); } else { return (PdfNull::new(id.pos).into(), None); } } } (PdfNull::new(self.pos()).into(), None) } fn get_object_and_parse_cache( &self, f: impl FnOnce(PdfObjectDirect, Option<&PdfObjectAndParseCache>) -> R, ) -> R { let Some(objects) = self.objects.upgrade() else { panic!("PdfObjects is no longer available"); }; let (object, object_and_parse_cache) = self.get_object_and_parse_cache_inner(&objects); f(object, object_and_parse_cache) } pub fn get(&self) -> PdfObjectDirect { self.get_object_and_parse_cache(|object, _object_and_parse_cache| object) } pub fn id(&self) -> PdfObjectIdentifier { self.id } pub fn pos(&self) -> PdfInputPosition { self.id.pos.0 } } impl From for PdfObjectDirect { fn from(value: PdfObjectIndirect) -> Self { value.get() } } pub trait IsPdfNull { fn is_pdf_null(&self) -> bool; } impl IsPdfNull for Option { fn is_pdf_null(&self) -> bool { self.as_ref().is_none_or(IsPdfNull::is_pdf_null) } } pub struct PdfDictionary { pos: PdfInputPositionNoCompare, fields: Arc>, } impl Clone for PdfDictionary { fn clone(&self) -> Self { Self { pos: self.pos, fields: self.fields.clone(), } } } impl PdfDictionary { pub fn new(pos: impl Into) -> Self { Self { pos: pos.into(), fields: Arc::new(BTreeMap::new()), } } pub fn from_fields( pos: impl Into, mut fields: Arc>, ) -> Self where T: IsPdfNull + Clone, { if fields.values().any(T::is_pdf_null) { Arc::make_mut(&mut fields).retain(|_k, v| !v.is_pdf_null()); } Self { pos: pos.into(), fields, } } pub fn fields(&self) -> &Arc> { &self.fields } pub fn into_fields(self) -> Arc> { self.fields } pub fn iter(&self) -> std::collections::btree_map::Iter<'_, PdfName, T> { self.fields.iter() } pub fn contains_key(&self, key: &Q) -> bool where PdfName: std::borrow::Borrow, Q: Ord, { self.fields.contains_key(key) } pub fn get(&self, key: &Q) -> Option<&T> where PdfName: std::borrow::Borrow, Q: Ord, { self.fields.get(key) } pub fn get_or_null(&self, key: &Q) -> T where PdfName: std::borrow::Borrow, Q: Ord, T: Clone + From, { self.get(key) .cloned() .unwrap_or_else(|| PdfNull(self.pos).into()) } pub fn pos(&self) -> PdfInputPosition { self.pos.0 } } impl GetPdfInputPosition for PdfDictionary { fn get_pdf_input_position(&self) -> PdfInputPosition { self.pos.0 } } impl Default for PdfDictionary { fn default() -> Self { Self::new(PdfInputPosition::empty()) } } impl FromIterator<(PdfName, T)> for PdfDictionary { fn from_iter>(iter: I) -> Self { Self { pos: PdfInputPositionNoCompare::empty(), fields: Arc::new(BTreeMap::from_iter( iter.into_iter() .filter(|(_name, value)| !value.is_pdf_null()), )), } } } impl IntoIterator for PdfDictionary { type Item = (PdfName, T); type IntoIter = std::collections::btree_map::IntoIter; fn into_iter(self) -> Self::IntoIter { Arc::unwrap_or_clone(self.fields).into_iter() } } impl<'a, T> IntoIterator for &'a PdfDictionary { type Item = (&'a PdfName, &'a T); type IntoIter = std::collections::btree_map::Iter<'a, PdfName, T>; fn into_iter(self) -> Self::IntoIter { self.fields.iter() } } impl fmt::Debug for PdfDictionary { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { DagDebugState::scope(|state| { state .debug_or_id_with( &self.fields, |_, f| f.debug_map().entries(self).finish(), |f| f.write_str("{...}"), ) .fmt(f) }) } } impl IsPdfNull for PdfDictionary { fn is_pdf_null(&self) -> bool { false } } impl PdfParse for PdfDictionary { fn type_name() -> Cow<'static, str> { if TypeId::of::() == TypeId::of::() { Cow::Borrowed("dictionary") } else { Cow::Owned(format!("PdfDictionary<{}>", T::type_name())) } } fn parse(object: PdfObject) -> Result { let object = PdfObjectDirect::from(object); let PdfObjectDirect::Dictionary(object) = object else { return Err(PdfParseError::InvalidType { pos: object.pos(), ty: object.type_name(), expected_ty: "dictionary", }); }; if let Some(retval) = ::downcast_ref::(&object) { return Ok(retval.clone()); } let pos = object.pos; let fields = Result::from_iter(object.fields.iter().filter_map(|(name, value)| { match T::parse(value.clone()) { Ok(value) => { if value.is_pdf_null() { None } else { Some(Ok((name.clone(), value))) } } Err(e) => Some(Err(e)), } }))?; Ok(Self { pos, fields: Arc::new(fields), }) } } #[derive(Clone, Default)] pub struct PdfArray { pos: PdfInputPositionNoCompare, elements: Arc<[PdfObject]>, } impl PdfArray { pub fn new(pos: impl Into) -> Self { Self { pos: pos.into(), elements: Arc::default(), } } pub fn from_elements( pos: impl Into, elements: Arc<[PdfObject]>, ) -> Self { Self { pos: pos.into(), elements, } } pub fn pos(&self) -> PdfInputPosition { self.pos.0 } pub fn elements(&self) -> &Arc<[PdfObject]> { &self.elements } pub fn into_elements(self) -> Arc<[PdfObject]> { self.elements } pub fn iter(&self) -> std::slice::Iter<'_, PdfObject> { self.elements.iter() } } impl GetPdfInputPosition for PdfArray { fn get_pdf_input_position(&self) -> PdfInputPosition { self.pos.0 } } impl FromIterator for PdfArray { fn from_iter>(iter: T) -> Self { Self { pos: PdfInputPositionNoCompare::empty(), elements: Arc::from_iter(iter), } } } #[derive(Clone)] pub struct PdfArrayIntoIter { indexes: std::ops::Range, elements: Arc<[PdfObject]>, } impl Iterator for PdfArrayIntoIter { type Item = PdfObject; fn next(&mut self) -> Option { self.indexes.next().map(|i| self.elements[i].clone()) } fn size_hint(&self) -> (usize, Option) { self.indexes.size_hint() } fn nth(&mut self, n: usize) -> Option { self.indexes.nth(n).map(|i| self.elements[i].clone()) } fn last(self) -> Option { self.indexes.last().map(|i| self.elements[i].clone()) } fn fold(self, init: B, mut f: F) -> B where F: FnMut(B, Self::Item) -> B, { self.indexes .fold(init, |init, i| f(init, self.elements[i].clone())) } } impl std::iter::FusedIterator for PdfArrayIntoIter {} impl DoubleEndedIterator for PdfArrayIntoIter { fn next_back(&mut self) -> Option { self.indexes.next_back().map(|i| self.elements[i].clone()) } fn nth_back(&mut self, n: usize) -> Option { self.indexes.nth_back(n).map(|i| self.elements[i].clone()) } fn rfold(self, init: B, mut f: F) -> B where F: FnMut(B, Self::Item) -> B, { self.indexes .rfold(init, |init, i| f(init, self.elements[i].clone())) } } impl ExactSizeIterator for PdfArrayIntoIter {} impl IntoIterator for PdfArray { type Item = PdfObject; type IntoIter = PdfArrayIntoIter; fn into_iter(self) -> Self::IntoIter { PdfArrayIntoIter { indexes: 0..self.elements.len(), elements: self.elements, } } } impl<'a> IntoIterator for &'a PdfArray { type Item = &'a PdfObject; type IntoIter = std::slice::Iter<'a, PdfObject>; fn into_iter(self) -> Self::IntoIter { self.elements.iter() } } impl fmt::Debug for PdfArray { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.elements.fmt(f) } } #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct MaybeArray(pub Arc<[T]>); impl fmt::Debug for MaybeArray { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.fmt(f) } } impl std::ops::Deref for MaybeArray { type Target = Arc<[T]>; fn deref(&self) -> &Self::Target { &self.0 } } impl std::ops::DerefMut for MaybeArray { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } } impl Default for MaybeArray { fn default() -> Self { Self(Arc::default()) } } impl<'a, T> IntoIterator for &'a MaybeArray { type Item = &'a T; type IntoIter = std::slice::Iter<'a, T>; fn into_iter(self) -> Self::IntoIter { self.iter() } } #[derive(Clone)] pub enum NameOr { Name(PdfName), Value(T), } impl NameOr { pub fn into_resolved(self, resolve: impl FnOnce(PdfName) -> Result) -> Result { match self { Self::Name(name) => resolve(name), Self::Value(v) => Ok(v), } } pub fn replace_with_resolved( &mut self, resolve: impl FnOnce(&PdfName) -> Result, ) -> Result<&mut T, E> { match self { Self::Name(name) => { *self = Self::Value(resolve(name)?); let Self::Value(v) = self else { unreachable!(); }; Ok(v) } Self::Value(v) => Ok(v), } } } impl fmt::Debug for NameOr { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Name(v) => v.fmt(f), Self::Value(v) => v.fmt(f), } } } impl GetPdfInputPosition for NameOr { fn get_pdf_input_position(&self) -> PdfInputPosition { match self { Self::Name(v) => v.pos(), Self::Value(v) => v.get_pdf_input_position(), } } } impl IsPdfNull for NameOr { fn is_pdf_null(&self) -> bool { match self { Self::Name(_) => false, Self::Value(v) => v.is_pdf_null(), } } } impl PdfParse for NameOr { fn type_name() -> Cow<'static, str> { Cow::Owned(format!("NameOr<{}>", T::type_name())) } fn parse(object: PdfObject) -> Result { Ok(match PdfObjectDirect::from(object) { PdfObjectDirect::Name(name) => Self::Name(name), object => Self::Value(T::parse(object.into())?), }) } } #[derive(Copy, Clone, PartialEq)] pub struct PdfMatrix { pub pos: PdfInputPositionNoCompare, pub elements: [f32; 6], } impl PdfMatrix { pub fn identity(pos: impl Into) -> Self { Self { pos: pos.into(), elements: [1.0, 0.0, 0.0, 1.0, 0.0, 0.0], } } #[must_use] pub fn mul(self, other: PdfMatrix, new_pos: impl Into) -> Self { let [la, lb, lc, ld, le, lf] = self.elements; let [ra, rb, rc, rd, re, rf] = other.elements; Self { pos: new_pos.into(), elements: [ lb * rc + la * ra, lb * rd + la * rb, ld * rc + lc * ra, ld * rd + lc * rb, re + lf * rc + le * ra, rf + lf * rd + le * rb, ], } } } impl fmt::Debug for PdfMatrix { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let Self { pos, elements } = *self; write!(f, "PdfMatrix(at {pos}, {elements:?})") } } impl IsPdfNull for PdfMatrix { fn is_pdf_null(&self) -> bool { false } } impl PdfParse for PdfMatrix { fn type_name() -> Cow<'static, str> { Cow::Borrowed("matrix") } fn parse(object: PdfObject) -> Result { Ok(Self { pos: object.pos().into(), elements: PdfParse::parse(object)?, }) } } impl PdfMatrix { pub fn parse_flat( a: PdfObject, b: PdfObject, c: PdfObject, d: PdfObject, e: PdfObject, f: PdfObject, ) -> Result { Ok(Self { pos: a.pos().into(), elements: [ PdfParse::parse(a)?, PdfParse::parse(b)?, PdfParse::parse(c)?, PdfParse::parse(d)?, PdfParse::parse(e)?, PdfParse::parse(f)?, ], }) } } impl GetPdfInputPosition for PdfMatrix { fn get_pdf_input_position(&self) -> PdfInputPosition { self.pos.0 } } #[derive(Copy, Clone, PartialEq)] pub struct PdfVec2D { pub pos: PdfInputPositionNoCompare, pub x: f32, pub y: f32, } impl fmt::Debug for PdfVec2D { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let Self { pos, x, y } = *self; write!(f, "PdfVec2D(at {pos}, {x}, {y})") } } impl PdfVec2D { pub fn parse(x: PdfObject, y: PdfObject) -> Result { Ok(Self { pos: x.pos().into(), x: PdfParse::parse(x)?, y: PdfParse::parse(y)?, }) } } impl GetPdfInputPosition for PdfVec2D { fn get_pdf_input_position(&self) -> PdfInputPosition { self.pos.0 } } #[derive(Copy, Clone, Debug)] pub struct PdfRectangle { /// the corner with the smaller x and y coordinates smaller: PdfVec2D, /// the corner with the larger x and y coordinates larger: PdfVec2D, } impl PdfRectangle { pub fn new(mut smaller: PdfVec2D, mut larger: PdfVec2D) -> Self { // `pos` follows the `x` coordinate if smaller.x.is_nan() { smaller.pos = larger.pos; } else if larger.x.is_nan() { larger.pos = smaller.pos; } else if larger.x < smaller.x { std::mem::swap(&mut smaller.pos, &mut larger.pos); } Self { smaller: PdfVec2D { pos: smaller.pos, x: smaller.x.min(larger.x), y: smaller.y.min(larger.y), }, larger: PdfVec2D { pos: larger.pos, x: smaller.x.max(larger.x), y: smaller.y.max(larger.y), }, } } /// return the corner with the smaller x and y coordinates pub fn smaller(&self) -> PdfVec2D { self.smaller } /// return the corner with the larger x and y coordinates pub fn larger(&self) -> PdfVec2D { self.larger } pub fn parse_flat( lower_left_x: PdfObject, lower_left_y: PdfObject, upper_right_x: PdfObject, upper_right_y: PdfObject, ) -> Result { Ok(Self::new( PdfVec2D::parse(lower_left_x, lower_left_y)?, PdfVec2D::parse(upper_right_x, upper_right_y)?, )) } } impl GetPdfInputPosition for PdfRectangle { fn get_pdf_input_position(&self) -> PdfInputPosition { self.smaller.get_pdf_input_position() } } impl IsPdfNull for PdfRectangle { fn is_pdf_null(&self) -> bool { false } } impl PdfParse for PdfRectangle { fn type_name() -> Cow<'static, str> { Cow::Borrowed("rectangle") } fn parse(object: PdfObject) -> Result { let object = object.into(); let PdfObjectDirect::Array(array) = &object else { return Err(PdfParseError::InvalidType { pos: object.pos(), ty: object.type_name(), expected_ty: "rectangle", }); }; let [lower_left_x, lower_left_y, upper_right_x, upper_right_y] = &**array.elements() else { return Err(PdfParseError::InvalidType { pos: object.pos(), ty: object.type_name(), expected_ty: "rectangle", }); }; Self::parse_flat( lower_left_x.clone(), lower_left_y.clone(), upper_right_x.clone(), upper_right_y.clone(), ) } } #[derive(Clone)] pub enum PdfFileSpecification { String(PdfString), Dictionary(PdfDictionary), } impl fmt::Debug for PdfFileSpecification { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::String(v) => v.fmt(f), Self::Dictionary(v) => v.fmt(f), } } } impl IsPdfNull for PdfFileSpecification { fn is_pdf_null(&self) -> bool { false } } impl PdfParse for PdfFileSpecification { fn type_name() -> Cow<'static, str> { Cow::Borrowed("file specification") } fn parse(object: PdfObject) -> Result { match PdfObjectDirect::from(object) { PdfObjectDirect::String(v) => Ok(Self::String(v)), PdfObjectDirect::Dictionary(v) => Ok(Self::Dictionary(v)), object => Err(PdfParseError::InvalidType { pos: object.pos(), ty: object.type_name(), expected_ty: "PdfFileSpecification", }), } } } pdf_parse! { #[pdf] #[derive(Clone)] pub struct PdfStreamDictionary { #[pdf(name = "Length")] pub len: usize, #[pdf(name = "Filter")] pub filters: MaybeArray, #[pdf(name = "DecodeParms")] pub decode_parms: MaybeArray>, #[pdf(name = "F")] pub file: Option, #[pdf(name = "FFilter")] pub file_filters: MaybeArray, #[pdf(name = "FDecodeParms")] pub file_decode_parms: MaybeArray>, #[pdf(name = "DL")] pub decoded_len: Option, #[pdf(flatten)] pub rest: Rest, } } impl fmt::Debug for PdfStreamDictionary { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { DagDebugState::scope(|_state| { let Self { len, filters, decode_parms, file, file_filters, file_decode_parms, decoded_len, rest, } = self; f.debug_struct("PdfStreamDictionary") .field("len", len) .field("filters", filters) .field("decode_parms", decode_parms) .field("file", file) .field("file_filters", file_filters) .field("file_decode_parms", file_decode_parms) .field("decoded_len", decoded_len) .field("rest", rest) .finish() }) } } #[derive(Debug, Clone, Default)] pub struct PdfStreamDictionaryFiltersAndParms<'a> { filters: std::iter::Enumerate>, decode_parms: &'a [Option], } impl<'a> PdfStreamDictionaryFiltersAndParms<'a> { fn item_helper( filter: (usize, &'a PdfStreamFilter), decode_parms: &'a [Option], ) -> (&'a PdfStreamFilter, &'a PdfDictionary) { static EMPTY_DICTIONARY: OnceLock = OnceLock::new(); let (index, filter) = filter; ( filter, match decode_parms.get(index) { Some(Some(v)) => v, _ => EMPTY_DICTIONARY.get_or_init(PdfDictionary::default), }, ) } } impl<'a> Iterator for PdfStreamDictionaryFiltersAndParms<'a> { type Item = (&'a PdfStreamFilter, &'a PdfDictionary); fn next(&mut self) -> Option { self.filters .next() .map(|filter| Self::item_helper(filter, self.decode_parms)) } fn size_hint(&self) -> (usize, Option) { self.filters.size_hint() } fn nth(&mut self, n: usize) -> Option { self.filters .nth(n) .map(|filter| Self::item_helper(filter, self.decode_parms)) } fn fold(self, init: B, f: F) -> B where F: FnMut(B, Self::Item) -> B, { self.filters .map(|filter| Self::item_helper(filter, self.decode_parms)) .fold(init, f) } } impl<'a> FusedIterator for PdfStreamDictionaryFiltersAndParms<'a> {} impl<'a> ExactSizeIterator for PdfStreamDictionaryFiltersAndParms<'a> {} impl<'a> DoubleEndedIterator for PdfStreamDictionaryFiltersAndParms<'a> { fn next_back(&mut self) -> Option { self.filters .next_back() .map(|filter| Self::item_helper(filter, self.decode_parms)) } fn nth_back(&mut self, n: usize) -> Option { self.filters .nth_back(n) .map(|filter| Self::item_helper(filter, self.decode_parms)) } fn rfold(self, init: B, f: F) -> B where F: FnMut(B, Self::Item) -> B, { self.filters .map(|filter| Self::item_helper(filter, self.decode_parms)) .rfold(init, f) } } impl PdfStreamDictionary { pub fn filters_and_parms<'a>(&'a self) -> PdfStreamDictionaryFiltersAndParms<'a> { PdfStreamDictionaryFiltersAndParms { filters: self.filters.iter().enumerate(), decode_parms: &self.decode_parms, } } pub fn file_filters_and_parms<'a>(&'a self) -> PdfStreamDictionaryFiltersAndParms<'a> { PdfStreamDictionaryFiltersAndParms { filters: self.file_filters.iter().enumerate(), decode_parms: &self.file_decode_parms, } } } pub trait PdfStreamContents: Sized + fmt::Debug + 'static { fn parse( data: &[u8], stream_pos: PdfInputPosition, objects: Arc, ) -> Result; fn parse_arc( data: Arc<[u8]>, stream_pos: PdfInputPosition, objects: Arc, ) -> Result { Self::parse(&*data, stream_pos, objects) } } impl PdfStreamContents for Arc<[u8]> { fn parse( data: &[u8], _stream_pos: PdfInputPosition, _objects: Arc, ) -> Result { Ok(Arc::from(data)) } fn parse_arc( data: Arc<[u8]>, _stream_pos: PdfInputPosition, _objects: Arc, ) -> Result { Ok(data.clone()) } } #[derive(Clone)] pub struct PdfStream> { pos: PdfInputPositionNoCompare, objects: std::sync::Weak, dictionary: PdfStreamDictionary, encoded_data: Arc<[u8]>, decoded_data: Arc>>, } struct DumpBytes<'a>(&'a [u8]); impl<'a> fmt::Debug for DumpBytes<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(self, f) } } impl fmt::Display for DumpBytes<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut first = true; let mut fmt_chunk = |chunk: &[u8]| -> fmt::Result { if first { first = false; } else { f.write_str("\n")?; } write!(f, "\"{}\"", chunk.escape_ascii()) }; if self.0.is_empty() { return fmt_chunk(self.0); } for chunk in self.0.chunks(32) { fmt_chunk(chunk)?; } Ok(()) } } impl fmt::Debug for PdfStream { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { DagDebugState::scope(|state| { state .debug_or_id_with( &self.decoded_data, |_, f| { let Self { pos, objects: _, dictionary, encoded_data, decoded_data, } = self; let mut debug_struct = f.debug_struct("PdfStream"); debug_struct.field("pos", pos); debug_struct.field("dictionary", dictionary); debug_struct.field("encoded_data", &DumpBytes(encoded_data)); if let Some(decoded_data) = decoded_data.get() { match decoded_data { Ok(decoded_data) => { if let Some(decoded_data) = ::downcast_ref::>(decoded_data) { debug_struct .field("decoded_data", &DumpBytes(&**decoded_data)) } else { debug_struct.field("decoded_data", decoded_data) } } Err(e) => debug_struct.field("decoded_data", &Err::<(), _>(e)), }; } else { debug_struct.field("decoded_data", &format_args!("")); } debug_struct.finish() }, |f| f.write_str("PdfStream(...)"), ) .fmt(f) }) } } impl PdfStream { pub fn new( pos: impl Into, objects: &Arc, dictionary: PdfStreamDictionary, encoded_data: Arc<[u8]>, ) -> Self { Self { pos: pos.into(), objects: Arc::downgrade(objects), dictionary, encoded_data, decoded_data: Arc::new(OnceLock::new()), } } pub fn dictionary(&self) -> &PdfStreamDictionary { &self.dictionary } pub fn encoded_data(&self) -> &Arc<[u8]> { &self.encoded_data } fn try_decode_data(&self) -> Result { let Some(objects) = self.objects.upgrade() else { panic!("PdfObjects is no longer available"); }; let dictionary = self.dictionary(); let (data, filters) = if let Some(file) = &dictionary.file { todo!() } else { (&self.encoded_data, dictionary.filters_and_parms()) }; if filters.len() == 0 { return Data::parse_arc(data.clone(), self.pos.0, objects); } let mut data: &[u8] = data; let mut buffer; for (filter, filter_parms) in filters { buffer = filter.decode_stream_data(filter_parms.clone(), self.pos.0, &data)?; data = &buffer; } Data::parse(data, self.pos.0, objects) } pub fn decoded_data(&self) -> &Result { self.decoded_data.get_or_init(|| self.try_decode_data()) } } impl GetPdfInputPosition for PdfStream { fn get_pdf_input_position(&self) -> PdfInputPosition { self.pos.0 } } impl IsPdfNull for PdfStream { fn is_pdf_null(&self) -> bool { false } } impl PdfParse for PdfStream { fn type_name() -> Cow<'static, str> { if TypeId::of::() == TypeId::of::() { Cow::Borrowed("stream") } else { Cow::Owned(format!("PdfStream<{}>", Rest::type_name())) } } fn parse(object: PdfObject) -> Result { match PdfObjectDirect::from(object) { PdfObjectDirect::Stream(stream) => { Ok(PdfStream { pos: stream.pos, dictionary: { let PdfStreamDictionary { len, filters, decode_parms, file, file_filters, file_decode_parms, decoded_len, rest, } = stream.dictionary; PdfStreamDictionary { len, filters, decode_parms, file, file_filters, file_decode_parms, decoded_len, rest: Rest::parse(rest.into())?, } }, encoded_data: stream.encoded_data, decoded_data: if let Some(decoded_data) = ::downcast_ref(&stream.decoded_data) { Arc::clone(decoded_data) } else { let Some(objects) = stream.objects.upgrade() else { panic!("PdfObjects is no longer available"); }; Arc::new( stream .decoded_data .get() .cloned() .map(|data| { OnceLock::from(data.and_then(|data| { Data::parse_arc(data, stream.pos.0, objects) })) }) .unwrap_or_default(), ) }, objects: stream.objects, }) } object => Err(PdfParseError::InvalidType { pos: object.get_pdf_input_position(), ty: object.type_name(), expected_ty: "stream", }), } } } pdf_parse! { #[pdf(name)] #[derive(Clone, Copy, Debug, Hash, Default, PartialEq, Eq, PartialOrd, Ord)] pub enum PdfObjectStreamType { #[pdf(name = "ObjStm")] #[default] ObjStm, } } pdf_parse! { #[pdf] #[derive(Clone)] pub struct PdfObjectStreamDictionary { #[pdf(name = Self::TYPE_NAME)] pub ty: PdfObjectStreamType, #[pdf(name = "N")] pub n: usize, #[pdf(name = "First")] pub first: usize, #[pdf(name = "Extends")] pub extends: Option, #[pdf(flatten)] pub rest: PdfDictionary, } } impl PdfObjectStreamDictionary { pub const TYPE_NAME: &str = "Type"; pub(crate) fn parse_type_from_dictionary( dictionary: &PdfDictionary, ) -> Result { PdfParse::parse(dictionary.get_or_null(Self::TYPE_NAME.as_bytes())) } }