use crate::{ pdf::{ PdfObjectOrStreamDictionaryOrOperator, PdfObjects, PdfParser, PdfTokenizer, object::{ NameOr, PdfDictionary, PdfMatrix, PdfName, PdfObject, PdfObjectDirect, PdfRectangle, PdfStream, PdfStreamContents, PdfString, PdfStringBytesDebug, PdfStringOrNumber, PdfVec2D, }, parse::{ GetPdfInputPosition, PdfInputPosition, PdfInputPositionKnown, PdfInputPositionNoCompare, PdfParse, PdfParseError, }, render::{ PdfColorDeviceGray, PdfColorDeviceRgb, PdfRenderOperator, PdfRenderState, PdfRenderingIntent, }, }, util::ArcOrRef, }; use std::{fmt, sync::Arc}; #[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct PdfOperatorUnparsed { pos: PdfInputPositionNoCompare, bytes: ArcOrRef<'static, [u8]>, } impl GetPdfInputPosition for PdfOperatorUnparsed { fn get_pdf_input_position(&self) -> PdfInputPosition { self.pos() } } impl fmt::Debug for PdfOperatorUnparsed { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { Self::debug_with_name("PdfOperatorUnparsed", &self.bytes, self.pos.0, f) } } trait PdfParseIter: Sized { fn parse_iter(iter: impl IntoIterator) -> Result; } impl PdfParseIter for Arc<[T]> { fn parse_iter(iter: impl IntoIterator) -> Result { FromIterator::from_iter(iter.into_iter().map(T::parse)) } } impl PdfOperatorUnparsed { pub fn new( pos: impl Into, bytes: impl Into>, ) -> Self { Self { pos: pos.into(), bytes: bytes.into(), } } pub const fn new_static(bytes: &'static [u8]) -> Self { Self { pos: PdfInputPositionNoCompare::empty(), bytes: ArcOrRef::Ref(bytes), } } pub fn pos(&self) -> PdfInputPosition { self.pos.0 } pub fn bytes(&self) -> &ArcOrRef<'static, [u8]> { &self.bytes } fn debug_with_name( name: &str, pdf_name: &[u8], pos: PdfInputPosition, f: &mut fmt::Formatter<'_>, ) -> fmt::Result { write!(f, "{name}(at {pos}, {})", PdfStringBytesDebug(pdf_name)) } pub fn bytes_debug(&self) -> PdfStringBytesDebug<'_> { PdfStringBytesDebug(&self.bytes) } } macro_rules! make_pdf_operator_enum { ( $(#[$($operator_meta:tt)*])* $operator_enum_vis:vis enum $PdfOperator:ident; $(#[$($operator_and_operands_meta:tt)*])* $enum_vis:vis enum $PdfOperatorAndOperands:ident { $(#[$($unknown_variant_meta:tt)*])* $Unknown:ident { $(#[$($unknown_operands_meta:tt)*])* $unknown_operands:ident: $unknown_operands_ty:ty, $(#[$($unknown_operator_meta:tt)*])* $unknown_operator:ident: $unknown_operator_ty:ty, }, $( #[kw = $kw:literal] $(#[$($variant_meta:tt)*])* $Variant:ident($VariantStruct:ident { $pos:ident: PdfInputPositionNoCompare, $( #[$field_parse:ident($($parse_args:tt)*)] $(#[$($field_meta:tt)*])* $field:ident: $field_ty:ty, )* }), )* } ) => { $(#[$($operator_meta)*])* $operator_enum_vis enum $PdfOperator { $(#[$($unknown_variant_meta)*])* $Unknown($unknown_operator_ty), $( $(#[$($variant_meta)*])* $Variant(PdfInputPositionNoCompare), )* } impl $PdfOperator { $operator_enum_vis fn parse(self, operands: impl IntoIterator) -> Result<$PdfOperatorAndOperands, PdfParseError> { let operands = operands.into_iter(); Ok(match self { Self::$Unknown(operator) => $PdfOperatorAndOperands::$Unknown { operands: FromIterator::from_iter(operands.map(Into::into)), operator, }, $(Self::$Variant(pos) => $VariantStruct::parse(pos, operands)?.into(),)* }) } $operator_enum_vis fn pos(&self) -> PdfInputPosition { match *self { Self::$Unknown(ref operator) => operator.pos(), $(Self::$Variant(pos) => pos.0,)* } } } impl fmt::Debug for $PdfOperator { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::$Unknown(operator) => PdfOperatorUnparsed::debug_with_name("Unknown", &operator.bytes, operator.pos.0, f), $(Self::$Variant(pos) => PdfOperatorUnparsed::debug_with_name(stringify!($Variant), $kw, pos.0, f),)* } } } impl From<$PdfOperator> for PdfOperatorUnparsed { fn from(v: $PdfOperator) -> PdfOperatorUnparsed { match v { $PdfOperator::$Unknown(operator) => operator, $($PdfOperator::$Variant(pos) => PdfOperatorUnparsed { pos, bytes: ArcOrRef::Ref($kw) },)* } } } impl From for $PdfOperator { fn from(v: PdfOperatorUnparsed) -> $PdfOperator { match &**v.bytes() { $($kw => Self::$Variant(v.pos),)* _ => Self::$Unknown(v), } } } $(#[derive(Clone)] $(#[$($variant_meta)*])* $enum_vis struct $VariantStruct { $enum_vis $pos: PdfInputPositionNoCompare, $( $(#[$($field_meta)*])* $enum_vis $field: $field_ty, )* } impl fmt::Debug for $VariantStruct { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct(stringify!($VariantStruct)).field("pos", &self.pos)$(.field(stringify!($field), &self.$field))*.finish() } } impl GetPdfInputPosition for $VariantStruct { fn get_pdf_input_position(&self) -> PdfInputPosition { self.pos() } } impl From<$VariantStruct> for $PdfOperatorAndOperands { fn from(v: $VariantStruct) -> Self { Self::$Variant(v) } } impl $VariantStruct { $enum_vis fn operator_from_pos(pos: impl Into) -> $PdfOperator { $PdfOperator::$Variant(pos.into()) } $enum_vis fn operator(&self) -> $PdfOperator { $PdfOperator::$Variant(self.pos) } $enum_vis fn pos(&self) -> PdfInputPosition { self.pos.0 } } make_pdf_operator_enum! { @impl_variant_parse $enum_vis enum; struct $VariantStruct { $pos: PdfInputPositionNoCompare, $( #[$field_parse($($parse_args)*)] $(#[$($field_meta)*])* $field: $field_ty, )* } })* $(#[$($operator_and_operands_meta)*])* $enum_vis enum $PdfOperatorAndOperands { $(#[$($unknown_variant_meta)*])* $Unknown { $(#[$($unknown_operands_meta)*])* $unknown_operands: $unknown_operands_ty, $(#[$($unknown_operator_meta)*])* $unknown_operator: $unknown_operator_ty, }, $( $(#[$($variant_meta)*])* $Variant($VariantStruct), )* } impl $PdfOperatorAndOperands { $enum_vis fn operator(&self) -> $PdfOperator { match self { Self::Unknown { operator, .. } => $PdfOperator::Unknown(operator.clone()), $(Self::$Variant(v) => v.operator(),)* } } $enum_vis fn pos(&self) -> PdfInputPosition { match self { Self::$Unknown { operator, .. } => operator.pos(), $(Self::$Variant(v) => v.pos(),)* } } } impl fmt::Debug for $PdfOperatorAndOperands { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::$Unknown { operands, operator, } => f.debug_struct("Unknown").field("operator", operator).field("operands", operands).finish(), $(Self::$Variant($VariantStruct { $pos, $($field,)* }) => f.debug_struct(stringify!($Variant)).field("pos", $pos)$(.field(stringify!($field), $field))*.finish(),)* } } } impl PdfRenderOperator for $PdfOperatorAndOperands { fn render(&self, state: &mut PdfRenderState) -> Result<(), PdfParseError> { match self { Self::$Unknown { operands, operator, } => state.handle_unknown_operator(operator, operands), $(Self::$Variant(v) => <$VariantStruct as PdfRenderOperator>::render(v, state),)* } } } }; ( @impl_variant_parse $enum_vis:vis enum; struct $VariantStruct:ident { $pos:ident: PdfInputPositionNoCompare, $( #[$field_parse:ident($($parse_args:ident),* $(,)?)] $(#[$($field_meta:tt)*])* $field:ident: $field_ty:ty, )* } ) => { impl $VariantStruct { $enum_vis fn parse(pos: impl Into, operands: impl IntoIterator) -> Result { let pos = pos.into(); let mut operands = operands.into_iter(); $($(let Some($parse_args) = operands.next() else { return Err(PdfParseError::OperatorHasTooFewOperands { operator: Self::operator_from_pos(pos) }); };)*)* if operands.next().is_some() { return Err(PdfParseError::OperatorHasTooManyOperands { operator: Self::operator_from_pos(pos) }); } Ok(Self { pos, $($field: <$field_ty>::$field_parse($($parse_args),*)?,)* }) } } }; ( @impl_variant_parse $enum_vis:vis enum; struct $VariantStruct:ident { $pos:ident: PdfInputPositionNoCompare, #[$field_parse:ident(...)] $(#[$($field_meta:tt)*])* $field:ident: $field_ty:ty, } ) => { impl $VariantStruct { $enum_vis fn parse(pos: impl Into, operands: impl IntoIterator) -> Result { let pos = pos.into(); let operands = operands.into_iter(); Ok(Self { pos, $field: <$field_ty>::$field_parse(operands)?, }) } } }; } make_pdf_operator_enum! { #[derive(Clone)] pub enum PdfOperator; #[derive(Clone)] pub enum PdfOperatorAndOperands { Unknown { operands: Arc<[PdfObjectDirect]>, operator: PdfOperatorUnparsed, }, #[kw = b"b"] CloseFillAndStrokePath(PdfOperatorCloseFillAndStrokePath { pos: PdfInputPositionNoCompare, }), #[kw = b"B"] FillAndStrokePath(PdfOperatorFillAndStrokePath { pos: PdfInputPositionNoCompare, }), #[kw = b"b*"] CloseFillAndStrokePathEvenOdd(PdfOperatorCloseFillAndStrokePathEvenOdd { pos: PdfInputPositionNoCompare, }), #[kw = b"B*"] FillAndStrokePathEvenOdd(PdfOperatorFillAndStrokePathEvenOdd { pos: PdfInputPositionNoCompare, }), #[kw = b"BDC"] BeginMarkedContentWithProperties(PdfOperatorBeginMarkedContentWithProperties { pos: PdfInputPositionNoCompare, #[parse(tag)] tag: PdfName, #[parse(properties)] properties: NameOr, }), #[kw = b"BI"] BeginInlineImage(PdfOperatorBeginInlineImage { pos: PdfInputPositionNoCompare, }), #[kw = b"BMC"] BeginMarkedContent(PdfOperatorBeginMarkedContent { pos: PdfInputPositionNoCompare, #[parse(tag)] tag: PdfName, }), #[kw = b"BT"] BeginText(PdfOperatorBeginText { pos: PdfInputPositionNoCompare, }), #[kw = b"BX"] BeginCompatibilitySection(PdfOperatorBeginCompatibilitySection { pos: PdfInputPositionNoCompare, }), #[kw = b"c"] CurveTo(PdfOperatorCurveTo { pos: PdfInputPositionNoCompare, #[parse(x1, y1)] p1: PdfVec2D, #[parse(x2, y2)] p2: PdfVec2D, #[parse(x3, y3)] p3: PdfVec2D, }), #[kw = b"cm"] ConcatMatrix(PdfOperatorConcatMatrix { pos: PdfInputPositionNoCompare, #[parse_flat(a, b, c, d, e, f)] matrix: PdfMatrix, }), #[kw = b"CS"] SetStrokeColorSpace(PdfOperatorSetStrokeColorSpace { pos: PdfInputPositionNoCompare, #[parse(name)] name: PdfName, }), #[kw = b"cs"] SetNonStrokeColorSpace(PdfOperatorSetNonStrokeColorSpace { pos: PdfInputPositionNoCompare, #[parse(name)] name: PdfName, }), #[kw = b"d"] SetLineDashPattern(PdfOperatorSetLineDashPattern { pos: PdfInputPositionNoCompare, #[parse(dash_array)] dash_array: PdfObject, // TODO: actually parse #[parse(dash_phase)] dash_phase: PdfObject, // TODO: actually parse }), #[kw = b"d0"] FontType3SetWidth(PdfOperatorFontType3SetWidth { pos: PdfInputPositionNoCompare, #[parse(x, y)] width: PdfVec2D, }), #[kw = b"d1"] FontType3SetWidthAndBBox(PdfOperatorFontType3SetWidthAndBBox { pos: PdfInputPositionNoCompare, #[parse(width_x, width_y)] width: PdfVec2D, #[parse_flat(lower_left_x, lower_left_y, upper_right_x, upper_right_y)] bbox: PdfRectangle, }), #[kw = b"Do"] PaintXObject(PdfOperatorPaintXObject { pos: PdfInputPositionNoCompare, #[parse(name)] name: PdfName, }), #[kw = b"DP"] DesignateMarkedContentPointWithProperties(PdfOperatorDesignateMarkedContentPointWithProperties { pos: PdfInputPositionNoCompare, #[parse(tag)] tag: PdfName, #[parse(properties)] properties: NameOr, }), #[kw = b"EI"] EndInlineImage(PdfOperatorEndInlineImage { pos: PdfInputPositionNoCompare, }), #[kw = b"EMC"] EndMarkedContent(PdfOperatorEndMarkedContent { pos: PdfInputPositionNoCompare, }), #[kw = b"ET"] EndText(PdfOperatorEndText { pos: PdfInputPositionNoCompare, }), #[kw = b"EX"] EndCompatibilitySection(PdfOperatorEndCompatibilitySection { pos: PdfInputPositionNoCompare, }), #[kw = b"f"] FillPath(PdfOperatorFillPath { pos: PdfInputPositionNoCompare, }), #[kw = b"F"] FillPathObsolete(PdfOperatorFillPathObsolete { pos: PdfInputPositionNoCompare, }), #[kw = b"f*"] FillPathEvenOdd(PdfOperatorFillPathEvenOdd { pos: PdfInputPositionNoCompare, }), #[kw = b"G"] SetStrokeGray(PdfOperatorSetStrokeGray { pos: PdfInputPositionNoCompare, #[parse(gray)] gray: PdfColorDeviceGray, }), #[kw = b"g"] SetNonStrokeGray(PdfOperatorSetNonStrokeGray { pos: PdfInputPositionNoCompare, #[parse(gray)] gray: PdfColorDeviceGray, }), #[kw = b"gs"] SetGraphicsState(PdfOperatorSetGraphicsState { pos: PdfInputPositionNoCompare, #[parse(dictionary_name)] dictionary_name: PdfName, }), #[kw = b"h"] CloseSubpath(PdfOperatorCloseSubpath { pos: PdfInputPositionNoCompare, }), #[kw = b"i"] SetFlatnessTolerance(PdfOperatorSetFlatnessTolerance { pos: PdfInputPositionNoCompare, #[parse(flatness)] flatness: f32, }), #[kw = b"ID"] BeginInlineImageData(PdfOperatorBeginInlineImageData { pos: PdfInputPositionNoCompare, }), #[kw = b"j"] SetLineJoinStyle(PdfOperatorSetLineJoinStyle { pos: PdfInputPositionNoCompare, #[parse(line_join_style)] line_join_style: u8, // TODO parse }), #[kw = b"J"] SetLineCapStyle(PdfOperatorSetLineCapStyle { pos: PdfInputPositionNoCompare, #[parse(line_cap_style)] line_cap_style: u8, // TODO parse }), #[kw = b"K"] SetStrokeCmyk(PdfOperatorSetStrokeCmyk { pos: PdfInputPositionNoCompare, #[parse(c)] c: f32, #[parse(m)] m: f32, #[parse(y)] y: f32, #[parse(k)] k: f32, }), #[kw = b"k"] SetNonStrokeCmyk(PdfOperatorSetNonStrokeCmyk { pos: PdfInputPositionNoCompare, #[parse(c)] c: f32, #[parse(m)] m: f32, #[parse(y)] y: f32, #[parse(k)] k: f32, }), #[kw = b"l"] LineTo(PdfOperatorLineTo { pos: PdfInputPositionNoCompare, #[parse(x, y)] to: PdfVec2D, }), #[kw = b"m"] MoveTo(PdfOperatorMoveTo { pos: PdfInputPositionNoCompare, #[parse(x, y)] to: PdfVec2D, }), #[kw = b"M"] SetMiterLimit(PdfOperatorSetMiterLimit { pos: PdfInputPositionNoCompare, #[parse(limit)] limit: f32, }), #[kw = b"MP"] DesignateMarkedContentPoint(PdfOperatorDesignateMarkedContentPoint { pos: PdfInputPositionNoCompare, #[parse(tag)] tag: PdfName, }), #[kw = b"n"] EndPath(PdfOperatorEndPath { pos: PdfInputPositionNoCompare, }), #[kw = b"q"] SaveGraphicsState(PdfOperatorSaveGraphicsState { pos: PdfInputPositionNoCompare, }), #[kw = b"Q"] RestoreGraphicsState(PdfOperatorRestoreGraphicsState { pos: PdfInputPositionNoCompare, }), #[kw = b"re"] Rectangle(PdfOperatorRectangle { pos: PdfInputPositionNoCompare, #[parse(x, y)] p: PdfVec2D, #[parse(width, height)] size: PdfVec2D, }), #[kw = b"RG"] SetStrokeRgb(PdfOperatorSetStrokeRgb { pos: PdfInputPositionNoCompare, #[parse_flat(r, g, b)] color: PdfColorDeviceRgb, }), #[kw = b"rg"] SetNonStrokeRgb(PdfOperatorSetNonStrokeRgb { pos: PdfInputPositionNoCompare, #[parse_flat(r, g, b)] color: PdfColorDeviceRgb, }), #[kw = b"ri"] SetColorRenderingIntent(PdfOperatorSetColorRenderingIntent { pos: PdfInputPositionNoCompare, #[parse(intent)] intent: PdfRenderingIntent, }), #[kw = b"s"] CloseAndStrokePath(PdfOperatorCloseAndStrokePath { pos: PdfInputPositionNoCompare, }), #[kw = b"S"] StrokePath(PdfOperatorStrokePath { pos: PdfInputPositionNoCompare, }), #[kw = b"SC"] SetStrokeColor(PdfOperatorSetStrokeColor { pos: PdfInputPositionNoCompare, #[parse_iter(...)] color: Arc<[f32]>, }), #[kw = b"sc"] SetNonStrokeColor(PdfOperatorSetNonStrokeColor { pos: PdfInputPositionNoCompare, #[parse_iter(...)] color: Arc<[f32]>, }), #[kw = b"SCN"] SetStrokeColorWithName(PdfOperatorSetStrokeColorWithName { pos: PdfInputPositionNoCompare, #[parse_iter(...)] color_and_name: Arc<[NameOr]>, }), #[kw = b"scn"] SetNonStrokeColorWithName(PdfOperatorSetNonStrokeColorWithName { pos: PdfInputPositionNoCompare, #[parse_iter(...)] color_and_name: Arc<[NameOr]>, }), #[kw = b"sh"] Shade(PdfOperatorShade { pos: PdfInputPositionNoCompare, }), #[kw = b"T*"] TextNextLine(PdfOperatorTextNextLine { pos: PdfInputPositionNoCompare, }), #[kw = b"Tc"] SetCharacterSpacing(PdfOperatorSetCharacterSpacing { pos: PdfInputPositionNoCompare, #[parse(char_space)] char_space: f32, }), #[kw = b"Td"] TextNextLineWithOffset(PdfOperatorTextNextLineWithOffset { pos: PdfInputPositionNoCompare, #[parse(x, y)] offset: PdfVec2D, }), #[kw = b"TD"] TextNextLineWithOffsetAndLeading(PdfOperatorTextNextLineWithOffsetAndLeading { pos: PdfInputPositionNoCompare, #[parse(x, y)] offset: PdfVec2D, }), #[kw = b"Tf"] SetFontAndSize(PdfOperatorSetFontAndSize { pos: PdfInputPositionNoCompare, #[parse(font)] font: PdfName, #[parse(size)] size: f32, }), #[kw = b"Tj"] ShowText(PdfOperatorShowText { pos: PdfInputPositionNoCompare, #[parse(text)] text: PdfString, }), #[kw = b"TJ"] ShowTextWithGlyphPositioning(PdfOperatorShowTextWithGlyphPositioning { pos: PdfInputPositionNoCompare, #[parse(text_and_positioning)] text_and_positioning: Arc<[PdfStringOrNumber]>, }), #[kw = b"TL"] SetTextLeading(PdfOperatorSetTextLeading { pos: PdfInputPositionNoCompare, #[parse(leading)] leading: f32, }), #[kw = b"Tm"] SetTextMatrix(PdfOperatorSetTextMatrix { pos: PdfInputPositionNoCompare, #[parse_flat(a, b, c, d, e, f)] matrix: PdfMatrix, }), #[kw = b"Tr"] SetTextRenderingMode(PdfOperatorSetTextRenderingMode { pos: PdfInputPositionNoCompare, #[parse(rendering_mode)] rendering_mode: u8, // TODO: parse }), #[kw = b"Ts"] SetTextRise(PdfOperatorSetTextRise { pos: PdfInputPositionNoCompare, #[parse(rise)] rise: f32, }), #[kw = b"Tw"] SetWordSpacing(PdfOperatorSetWordSpacing { pos: PdfInputPositionNoCompare, #[parse(word_space)] word_space: f32, }), #[kw = b"Tz"] SetTextHorizontalScaling(PdfOperatorSetTextHorizontalScaling { pos: PdfInputPositionNoCompare, #[parse(scale_percent)] scale_percent: f32, }), #[kw = b"v"] CurveTo23(PdfOperatorCurveTo23 { pos: PdfInputPositionNoCompare, }), #[kw = b"w"] SetLineWidth(PdfOperatorSetLineWidth { pos: PdfInputPositionNoCompare, #[parse(line_width)] line_width: f32, }), #[kw = b"W"] Clip(PdfOperatorClip { pos: PdfInputPositionNoCompare, }), #[kw = b"W*"] ClipEvenOdd(PdfOperatorClipEvenOdd { pos: PdfInputPositionNoCompare, }), #[kw = b"y"] CurveTo13(PdfOperatorCurveTo13 { pos: PdfInputPositionNoCompare, }), #[kw = b"'"] TextNextLineAndShow(PdfOperatorTextNextLineAndShow { pos: PdfInputPositionNoCompare, #[parse(text)] text: PdfString, }), #[kw = b"\""] SetSpacingThenTextNextLineAndShow(PdfOperatorSetSpacingThenTextNextLineAndShow { pos: PdfInputPositionNoCompare, #[parse(word_space)] word_space: f32, #[parse(char_space)] char_space: f32, #[parse(text)] text: PdfString, }), } } impl GetPdfInputPosition for PdfOperator { fn get_pdf_input_position(&self) -> PdfInputPosition { self.pos() } } impl GetPdfInputPosition for PdfOperatorAndOperands { fn get_pdf_input_position(&self) -> PdfInputPosition { self.pos() } } #[derive(Clone)] pub struct PdfContentStreamData { pub operators: Arc<[PdfOperatorAndOperands]>, } impl fmt::Debug for PdfContentStreamData { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("PdfContentStreamData") .field("operators", &self.operators) .finish() } } impl PdfStreamContents for PdfContentStreamData { fn parse( data: &[u8], stream_pos: PdfInputPosition, objects: Arc, ) -> Result { let mut parser = PdfParser { objects, tokenizer: PdfTokenizer::new( data, PdfInputPositionKnown { pos: 0, containing_streams_pos: stream_pos.get().map(|v| v.pos), }, ), }; let mut operands = Vec::new(); let mut operators = Vec::new(); loop { parser.skip_comments_and_whitespace(); if parser.tokenizer.peek().is_none() { break; } match parser.parse_object_or_operator()? { PdfObjectOrStreamDictionaryOrOperator::StreamDictionary { stream_kw_pos, .. } => return Err(PdfParseError::StreamNotAllowedHere { pos: stream_kw_pos }), PdfObjectOrStreamDictionaryOrOperator::Object(object) => operands.push(object), PdfObjectOrStreamDictionaryOrOperator::Operator(operator) => { operators.push(PdfOperator::from(operator).parse(operands.drain(..))?); } } } if operands.is_empty() { Ok(Self { operators: operators.into(), }) } else { Err(PdfParseError::MissingOperator { pos: parser.tokenizer.pos(), }) } } } pub type PdfContentStream = PdfStream;