diff --git a/crates/pdf-content-stream-operators/src/clipping_path_operators.rs b/crates/pdf-content-stream-operators/src/clipping_path_operators.rs deleted file mode 100644 index 70bc9878..00000000 --- a/crates/pdf-content-stream-operators/src/clipping_path_operators.rs +++ /dev/null @@ -1,42 +0,0 @@ -use crate::{ - operator_trait::{OperandContext, PdfOperator}, - pdf_operator_backend::{BackendError, PdfOperatorBackend}, - variants::PdfOperatorVariant, -}; -use pdf_object_reader::ReadResult; - -/// Modifies the current clipping path by intersecting it with the current path, using the non-zero winding number rule to determine the region to clip. -#[derive(Debug, Clone, PartialEq, Default)] -pub struct ClipNonZero; - -impl PdfOperator for ClipNonZero { - const NAME: &'static [u8] = b"W"; - - const OPERAND_COUNT: Option = Some(0); - - fn read(_operands: &mut OperandContext<'_, '_>) -> ReadResult { - Ok(PdfOperatorVariant::ClipNonZero(Self)) - } - - fn call(&self, backend: &mut T) -> Result<(), BackendError> { - backend.clip_path_nonzero_winding() - } -} - -/// Modifies the current clipping path by intersecting it with the current path, using the even-odd rule to determine the region to clip. -#[derive(Debug, Clone, PartialEq, Default)] -pub struct ClipEvenOdd; - -impl PdfOperator for ClipEvenOdd { - const NAME: &'static [u8] = b"W*"; - - const OPERAND_COUNT: Option = Some(0); - - fn read(_operands: &mut OperandContext<'_, '_>) -> ReadResult { - Ok(PdfOperatorVariant::ClipEvenOdd(Self)) - } - - fn call(&self, backend: &mut T) -> Result<(), BackendError> { - backend.clip_path_even_odd() - } -} diff --git a/crates/pdf-content-stream-operators/src/color_operators.rs b/crates/pdf-content-stream-operators/src/color_operators.rs index d7bb97d0..340e520b 100644 --- a/crates/pdf-content-stream-operators/src/color_operators.rs +++ b/crates/pdf-content-stream-operators/src/color_operators.rs @@ -1,10 +1,9 @@ use std::sync::Arc; use crate::{ + OperandContext, error::PdfOperatorError, - operator_trait::{OperandContext, PdfOperator}, pdf_operator_backend::{BackendError, PdfOperatorBackend}, - variants::PdfOperatorVariant, }; use pdf_object_reader::{ReadResult, pdf_string::PdfString}; @@ -25,19 +24,18 @@ impl SetGrayFill { pub const fn gray(&self) -> f32 { self.gray } -} - -impl PdfOperator for SetGrayFill { - const NAME: &'static [u8] = b"g"; - - const OPERAND_COUNT: Option = Some(1); - fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { + /// Reads the operator from its operands. + pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { let gray = operands.at(0)?; - Ok(PdfOperatorVariant::SetGrayFill(Self::new(gray))) + Ok(Self::new(gray)) } - fn call(&self, backend: &mut T) -> Result<(), BackendError> { + /// Dispatches the operator to `backend`. + pub(crate) fn call( + &self, + backend: &mut T, + ) -> Result<(), BackendError> { backend.set_non_stroking_gray(self.gray) } } @@ -54,19 +52,18 @@ impl SetGrayStroke { pub fn new(gray: f32) -> Self { Self { gray } } -} - -impl PdfOperator for SetGrayStroke { - const NAME: &'static [u8] = b"G"; - const OPERAND_COUNT: Option = Some(1); - - fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { + /// Reads the operator from its operands. + pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { let gray = operands.at(0)?; - Ok(PdfOperatorVariant::SetGrayStroke(Self::new(gray))) + Ok(Self::new(gray)) } - fn call(&self, backend: &mut T) -> Result<(), BackendError> { + /// Dispatches the operator to `backend`. + pub(crate) fn call( + &self, + backend: &mut T, + ) -> Result<(), BackendError> { backend.set_stroking_gray(self.gray) } } @@ -92,19 +89,18 @@ impl SetRGBFill { pub const fn components(&self) -> [f32; 3] { [self.r, self.g, self.b] } -} - -impl PdfOperator for SetRGBFill { - const NAME: &'static [u8] = b"rg"; - const OPERAND_COUNT: Option = Some(3); - - fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { + /// Reads the operator from its operands. + pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { let [r, g, b] = operands.read_array()?; - Ok(PdfOperatorVariant::SetRGBFill(Self::new(r, g, b))) + Ok(Self::new(r, g, b)) } - fn call(&self, backend: &mut T) -> Result<(), BackendError> { + /// Dispatches the operator to `backend`. + pub(crate) fn call( + &self, + backend: &mut T, + ) -> Result<(), BackendError> { backend.set_non_stroking_rgb(self.r, self.g, self.b) } } @@ -125,19 +121,18 @@ impl SetRGBStroke { pub fn new(r: f32, g: f32, b: f32) -> Self { Self { r, g, b } } -} -impl PdfOperator for SetRGBStroke { - const NAME: &'static [u8] = b"RG"; - - const OPERAND_COUNT: Option = Some(3); - - fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { + /// Reads the operator from its operands. + pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { let [r, g, b] = operands.read_array()?; - Ok(PdfOperatorVariant::SetRGBStroke(Self::new(r, g, b))) + Ok(Self::new(r, g, b)) } - fn call(&self, backend: &mut T) -> Result<(), BackendError> { + /// Dispatches the operator to `backend`. + pub(crate) fn call( + &self, + backend: &mut T, + ) -> Result<(), BackendError> { backend.set_stroking_rgb(self.r, self.g, self.b) } } @@ -165,19 +160,18 @@ impl SetCMYKFill { pub const fn components(&self) -> [f32; 4] { [self.c, self.m, self.y, self.k] } -} - -impl PdfOperator for SetCMYKFill { - const NAME: &'static [u8] = b"k"; - - const OPERAND_COUNT: Option = Some(4); - fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { + /// Reads the operator from its operands. + pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { let [c, m, y, k] = operands.read_array()?; - Ok(PdfOperatorVariant::SetCMYKFill(Self::new(c, m, y, k))) + Ok(Self::new(c, m, y, k)) } - fn call(&self, backend: &mut T) -> Result<(), BackendError> { + /// Dispatches the operator to `backend`. + pub(crate) fn call( + &self, + backend: &mut T, + ) -> Result<(), BackendError> { backend.set_non_stroking_cmyk(self.c, self.m, self.y, self.k) } } @@ -200,19 +194,18 @@ impl SetCMYKStroke { pub fn new(c: f32, m: f32, y: f32, k: f32) -> Self { Self { c, m, y, k } } -} - -impl PdfOperator for SetCMYKStroke { - const NAME: &'static [u8] = b"K"; - const OPERAND_COUNT: Option = Some(4); - - fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { + /// Reads the operator from its operands. + pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { let [c, m, y, k] = operands.read_array()?; - Ok(PdfOperatorVariant::SetCMYKStroke(Self::new(c, m, y, k))) + Ok(Self::new(c, m, y, k)) } - fn call(&self, backend: &mut T) -> Result<(), BackendError> { + /// Dispatches the operator to `backend`. + pub(crate) fn call( + &self, + backend: &mut T, + ) -> Result<(), BackendError> { backend.set_stroking_cmyk(self.c, self.m, self.y, self.k) } } @@ -228,18 +221,18 @@ impl SetStrokeColorSpace { pub fn new(name: Arc<[u8]>) -> Self { Self { name } } -} -impl PdfOperator for SetStrokeColorSpace { - const NAME: &'static [u8] = b"CS"; - const OPERAND_COUNT: Option = Some(1); - - fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { + /// Reads the operator from its operands. + pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { let name = operands.at::(0)?.bytes; - Ok(PdfOperatorVariant::SetStrokeColorSpace(Self::new(name))) + Ok(Self::new(name)) } - fn call(&self, backend: &mut T) -> Result<(), BackendError> { + /// Dispatches the operator to `backend`. + pub(crate) fn call( + &self, + backend: &mut T, + ) -> Result<(), BackendError> { backend.set_stroking_color_space(&self.name) } } @@ -255,20 +248,18 @@ impl SetNonStrokingColorSpace { pub fn new(name: Arc<[u8]>) -> Self { Self { name } } -} - -impl PdfOperator for SetNonStrokingColorSpace { - const NAME: &'static [u8] = b"cs"; - const OPERAND_COUNT: Option = Some(1); - fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { + /// Reads the operator from its operands. + pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { let name = operands.at::(0)?.bytes; - Ok(PdfOperatorVariant::SetNonStrokingColorSpace(Self::new( - name, - ))) + Ok(Self::new(name)) } - fn call(&self, backend: &mut T) -> Result<(), BackendError> { + /// Dispatches the operator to `backend`. + pub(crate) fn call( + &self, + backend: &mut T, + ) -> Result<(), BackendError> { backend.set_non_stroking_color_space(&self.name) } } @@ -290,13 +281,9 @@ impl SetStrokingColor { pattern, } } -} - -impl PdfOperator for SetStrokingColor { - const NAME: &'static [u8] = b"SCN"; - const OPERAND_COUNT: Option = None; - fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { + /// Reads the operator from its operands. + pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { // The pattern name should come last, after the numeric color components let count = operands .array() @@ -308,12 +295,30 @@ impl PdfOperator for SetStrokingColor { .collect::>()?; let pattern = operands.at::(count).ok().map(|name| name.bytes); - Ok(PdfOperatorVariant::SetStrokingColor(Self::new( - values, pattern, - ))) + Ok(Self::new(values, pattern)) + } + + /// Reads the operator from the color components of `SC`, which takes no pattern. + pub(crate) fn read_without_pattern(operands: &mut OperandContext<'_, '_>) -> ReadResult { + let values: Vec = operands.read_all()?; + + if values.is_empty() { + return Err(PdfOperatorError::OperandCountMismatch { + operator: "SC".to_string(), + expected: 1, + actual: 0, + } + .into()); + } + + Ok(Self::new(values, None)) } - fn call(&self, backend: &mut T) -> Result<(), BackendError> { + /// Dispatches the operator to `backend`. + pub(crate) fn call( + &self, + backend: &mut T, + ) -> Result<(), BackendError> { if let Some(pattern) = &self.pattern { backend.set_stroking_color_extended(&self.components, pattern) } else { @@ -339,13 +344,9 @@ impl SetNonStrokingColor { pattern, } } -} - -impl PdfOperator for SetNonStrokingColor { - const NAME: &'static [u8] = b"scn"; - const OPERAND_COUNT: Option = None; - fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { + /// Reads the operator from its operands. + pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { // The pattern name should come last, after the numeric color components let count = operands .array() @@ -357,37 +358,11 @@ impl PdfOperator for SetNonStrokingColor { .collect::>()?; let pattern = operands.at::(count).ok().map(|name| name.bytes); - Ok(PdfOperatorVariant::SetNonStrokingColor(Self::new( - values, pattern, - ))) - } - - fn call(&self, backend: &mut T) -> Result<(), BackendError> { - if let Some(pattern) = &self.pattern { - backend.set_non_stroking_color_extended(&self.components, pattern) - } else { - backend.set_non_stroking_color(&self.components) - } + Ok(Self::new(values, pattern)) } -} -/// Sets the non-stroking color when the color space requires -/// multiple color components, without pattern support. -/// This handles the "sc" operator and maps to `SetNonStrokingColor` internally. -#[derive(Debug, Clone, PartialEq)] -pub struct SetNonStrokingColorSc; - -impl PdfOperator for SetNonStrokingColorSc { - const NAME: &'static [u8] = b"sc"; - const OPERAND_COUNT: Option = None; - - fn call(&self, _backend: &mut T) -> Result<(), BackendError> { - // Unreachable: read() always produces a SetNonStrokingColor variant, - // so this type is never dispatched through PdfOperatorVariant::call. - Ok(()) - } - - fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { + /// Reads the operator from the color components of `sc`, which takes no pattern. + pub(crate) fn read_without_pattern(operands: &mut OperandContext<'_, '_>) -> ReadResult { let values: Vec = operands.read_all()?; if values.is_empty() { @@ -399,42 +374,18 @@ impl PdfOperator for SetNonStrokingColorSc { .into()); } - Ok(PdfOperatorVariant::SetNonStrokingColor( - SetNonStrokingColor::new(values, None), - )) + Ok(Self::new(values, None)) } -} -/// Sets the stroking color when the color space requires -/// multiple color components, without pattern support. -/// This handles the "SC" operator and maps to `SetStrokingColor` internally. -#[derive(Debug, Clone, PartialEq)] -pub struct SetStrokingColorSc; - -impl PdfOperator for SetStrokingColorSc { - const NAME: &'static [u8] = b"SC"; - const OPERAND_COUNT: Option = None; - - fn call(&self, _backend: &mut T) -> Result<(), BackendError> { - // Unreachable: read() always produces a SetStrokingColor variant, - // so this type is never dispatched through PdfOperatorVariant::call. - Ok(()) - } - - fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { - let values: Vec = operands.read_all()?; - - if values.is_empty() { - return Err(PdfOperatorError::OperandCountMismatch { - operator: "SC".to_string(), - expected: 1, - actual: 0, - } - .into()); + /// Dispatches the operator to `backend`. + pub(crate) fn call( + &self, + backend: &mut T, + ) -> Result<(), BackendError> { + if let Some(pattern) = &self.pattern { + backend.set_non_stroking_color_extended(&self.components, pattern) + } else { + backend.set_non_stroking_color(&self.components) } - - Ok(PdfOperatorVariant::SetStrokingColor(SetStrokingColor::new( - values, None, - ))) } } diff --git a/crates/pdf-content-stream-operators/src/compatibility_operators.rs b/crates/pdf-content-stream-operators/src/compatibility_operators.rs deleted file mode 100644 index 19641441..00000000 --- a/crates/pdf-content-stream-operators/src/compatibility_operators.rs +++ /dev/null @@ -1,49 +0,0 @@ -use crate::{ - operator_trait::{OperandContext, PdfOperator}, - pdf_operator_backend::{BackendError, PdfOperatorBackend}, - variants::PdfOperatorVariant, -}; -use pdf_object_reader::ReadResult; - -/// Begins a compatibility section (BX). -/// -/// According to the PDF specification, a compatibility section allows -/// consumers to ignore any operators they do not recognize until the -/// matching EX operator is encountered. For our purposes, we parse and -/// expose these operators so unknown operators within can be tolerated -/// by higher-level logic. Backends may choose to track nesting depth, -/// but by default these operators are no-ops. -#[derive(Debug, Clone, PartialEq, Default)] -pub struct BeginCompatibility; - -impl PdfOperator for BeginCompatibility { - const NAME: &'static [u8] = b"BX"; - - const OPERAND_COUNT: Option = Some(0); - - fn read(_operands: &mut OperandContext<'_, '_>) -> ReadResult { - Ok(PdfOperatorVariant::BeginCompatibility(BeginCompatibility)) - } - - fn call(&self, _backend: &mut T) -> Result<(), BackendError> { - Ok(()) - } -} - -/// Ends a compatibility section (EX). -#[derive(Debug, Clone, PartialEq, Default)] -pub struct EndCompatibility; - -impl PdfOperator for EndCompatibility { - const NAME: &'static [u8] = b"EX"; - - const OPERAND_COUNT: Option = Some(0); - - fn read(_operands: &mut OperandContext<'_, '_>) -> ReadResult { - Ok(PdfOperatorVariant::EndCompatibility(EndCompatibility)) - } - - fn call(&self, _backend: &mut T) -> Result<(), BackendError> { - Ok(()) - } -} diff --git a/crates/pdf-content-stream-operators/src/graphics_state_operators.rs b/crates/pdf-content-stream-operators/src/graphics_state_operators.rs index 02ceeaff..f4863202 100644 --- a/crates/pdf-content-stream-operators/src/graphics_state_operators.rs +++ b/crates/pdf-content-stream-operators/src/graphics_state_operators.rs @@ -5,10 +5,9 @@ use pdf_graphics::{LineCap, LineJoin, transform::Transform}; use pdf_object_reader::{ReadResult, pdf_string::PdfString}; use crate::{ + OperandContext, error::PdfOperatorError, - operator_trait::{OperandContext, PdfOperator}, pdf_operator_backend::{BackendError, PdfOperatorBackend}, - variants::PdfOperatorVariant, }; /// Sets the line width for path stroking. @@ -22,19 +21,18 @@ impl SetLineWidth { pub fn new(width: f32) -> Self { Self { width } } -} - -impl PdfOperator for SetLineWidth { - const NAME: &'static [u8] = b"w"; - - const OPERAND_COUNT: Option = Some(1); - fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { + /// Reads the operator from its operands. + pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { let width = operands.at(0)?; - Ok(PdfOperatorVariant::SetLineWidth(Self::new(width))) + Ok(Self::new(width)) } - fn call(&self, backend: &mut T) -> Result<(), BackendError> { + /// Dispatches the operator to `backend`. + pub(crate) fn call( + &self, + backend: &mut T, + ) -> Result<(), BackendError> { backend.set_line_width(self.width) } } @@ -56,19 +54,18 @@ impl SetLineCapStyle { }), } } -} - -impl PdfOperator for SetLineCapStyle { - const NAME: &'static [u8] = b"J"; - - const OPERAND_COUNT: Option = Some(1); - fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { + /// Reads the operator from its operands. + pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { let style = operands.at(0)?; - Ok(PdfOperatorVariant::SetLineCapStyle(Self::new(style)?)) + Ok(Self::new(style)?) } - fn call(&self, backend: &mut T) -> Result<(), BackendError> { + /// Dispatches the operator to `backend`. + pub(crate) fn call( + &self, + backend: &mut T, + ) -> Result<(), BackendError> { backend.set_line_cap(self.style) } } @@ -90,19 +87,18 @@ impl SetLineJoinStyle { }), } } -} - -impl PdfOperator for SetLineJoinStyle { - const NAME: &'static [u8] = b"j"; - - const OPERAND_COUNT: Option = Some(1); - fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { + /// Reads the operator from its operands. + pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { let style = operands.at(0)?; - Ok(PdfOperatorVariant::SetLineJoinStyle(Self::new(style)?)) + Ok(Self::new(style)?) } - fn call(&self, backend: &mut T) -> Result<(), BackendError> { + /// Dispatches the operator to `backend`. + pub(crate) fn call( + &self, + backend: &mut T, + ) -> Result<(), BackendError> { backend.set_line_join(self.style) } } @@ -119,19 +115,18 @@ impl SetMiterLimit { pub fn new(limit: f32) -> Self { Self { limit } } -} - -impl PdfOperator for SetMiterLimit { - const NAME: &'static [u8] = b"M"; - const OPERAND_COUNT: Option = Some(1); - - fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { + /// Reads the operator from its operands. + pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { let limit = operands.at(0)?; - Ok(PdfOperatorVariant::SetMiterLimit(Self::new(limit))) + Ok(Self::new(limit)) } - fn call(&self, backend: &mut T) -> Result<(), BackendError> { + /// Dispatches the operator to `backend`. + pub(crate) fn call( + &self, + backend: &mut T, + ) -> Result<(), BackendError> { backend.set_miter_limit(self.limit) } } @@ -149,20 +144,19 @@ impl SetDashPattern { pub fn new(array: Vec, phase: f32) -> Self { Self { array, phase } } -} - -impl PdfOperator for SetDashPattern { - const NAME: &'static [u8] = b"d"; - const OPERAND_COUNT: Option = Some(2); - - fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { + /// Reads the operator from its operands. + pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { let array = operands.at(0)?; let phase = operands.at(1)?; - Ok(PdfOperatorVariant::SetDashPattern(Self::new(array, phase))) + Ok(Self::new(array, phase)) } - fn call(&self, backend: &mut T) -> Result<(), BackendError> { + /// Dispatches the operator to `backend`. + pub(crate) fn call( + &self, + backend: &mut T, + ) -> Result<(), BackendError> { backend.set_dash_pattern(&self.array, self.phase) } } @@ -179,21 +173,18 @@ impl SetFlatnessTolerance { pub fn new(tolerance: f32) -> Self { Self { tolerance } } -} - -impl PdfOperator for SetFlatnessTolerance { - const NAME: &'static [u8] = b"i"; - const OPERAND_COUNT: Option = Some(1); - - fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { + /// Reads the operator from its operands. + pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { let tolerance = operands.at(0)?; - Ok(PdfOperatorVariant::SetFlatnessTolerance(Self::new( - tolerance, - ))) + Ok(Self::new(tolerance)) } - fn call(&self, backend: &mut T) -> Result<(), BackendError> { + /// Dispatches the operator to `backend`. + pub(crate) fn call( + &self, + backend: &mut T, + ) -> Result<(), BackendError> { backend.set_flatness_tolerance(self.tolerance) } } @@ -211,59 +202,22 @@ impl SetRenderingIntent { pub fn new(intent: Arc<[u8]>) -> Self { Self { intent } } -} - -impl PdfOperator for SetRenderingIntent { - const NAME: &'static [u8] = b"ri"; - const OPERAND_COUNT: Option = Some(1); - - fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { + /// Reads the operator from its operands. + pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { let intent = operands.at::(0)?.bytes; - Ok(PdfOperatorVariant::SetRenderingIntent(Self::new(intent))) + Ok(Self::new(intent)) } - fn call(&self, backend: &mut T) -> Result<(), BackendError> { + /// Dispatches the operator to `backend`. + pub(crate) fn call( + &self, + backend: &mut T, + ) -> Result<(), BackendError> { backend.set_rendering_intent(&self.intent) } } -/// Saves the current graphics state on the graphics state stack. -#[derive(Debug, Clone, PartialEq, Default)] -pub struct SaveGraphicsState; - -impl PdfOperator for SaveGraphicsState { - const NAME: &'static [u8] = b"q"; - - const OPERAND_COUNT: Option = Some(0); - - fn read(_operands: &mut OperandContext<'_, '_>) -> ReadResult { - Ok(PdfOperatorVariant::SaveGraphicsState(Self)) - } - - fn call(&self, backend: &mut T) -> Result<(), BackendError> { - backend.save_graphics_state() - } -} - -/// Restores the graphics state by removing the most recently saved state from the stack. -#[derive(Debug, Clone, PartialEq, Default)] -pub struct RestoreGraphicsState; - -impl PdfOperator for RestoreGraphicsState { - const NAME: &'static [u8] = b"Q"; - - const OPERAND_COUNT: Option = Some(0); - - fn read(_operands: &mut OperandContext<'_, '_>) -> ReadResult { - Ok(PdfOperatorVariant::RestoreGraphicsState(Self)) - } - - fn call(&self, backend: &mut T) -> Result<(), BackendError> { - backend.restore_graphics_state() - } -} - /// Modifies the current transformation matrix (CTM) by concatenating the specified matrix. #[derive(Debug, Clone, PartialEq)] pub struct ConcatMatrix { @@ -277,20 +231,17 @@ impl ConcatMatrix { matrix: Transform::from(matrix), } } -} -impl PdfOperator for ConcatMatrix { - const NAME: &'static [u8] = b"cm"; - - const OPERAND_COUNT: Option = Some(6); - - fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { - Ok(PdfOperatorVariant::ConcatMatrix(Self::new( - operands.read_array()?, - ))) + /// Reads the operator from its operands. + pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { + Ok(Self::new(operands.read_array()?)) } - fn call(&self, backend: &mut T) -> Result<(), BackendError> { + /// Dispatches the operator to `backend`. + pub(crate) fn call( + &self, + backend: &mut T, + ) -> Result<(), BackendError> { backend.concat_matrix(&self.matrix) } } @@ -307,21 +258,18 @@ impl SetGraphicsStateFromDict { pub fn new(dict_name: Arc<[u8]>) -> Self { Self { dict_name } } -} - -impl PdfOperator for SetGraphicsStateFromDict { - const NAME: &'static [u8] = b"gs"; - - const OPERAND_COUNT: Option = Some(1); - fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { + /// Reads the operator from its operands. + pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { let dict_name = operands.at::(0)?.bytes; - Ok(PdfOperatorVariant::SetGraphicsStateFromDict(Self::new( - dict_name, - ))) + Ok(Self::new(dict_name)) } - fn call(&self, backend: &mut T) -> Result<(), BackendError> { + /// Dispatches the operator to `backend`. + pub(crate) fn call( + &self, + backend: &mut T, + ) -> Result<(), BackendError> { backend.set_graphics_state_from_dict(&self.dict_name) } } diff --git a/crates/pdf-content-stream-operators/src/lib.rs b/crates/pdf-content-stream-operators/src/lib.rs index ad101c8d..d36f18cd 100644 --- a/crates/pdf-content-stream-operators/src/lib.rs +++ b/crates/pdf-content-stream-operators/src/lib.rs @@ -1,19 +1,13 @@ -pub mod clipping_path_operators; pub mod color_operators; -pub mod compatibility_operators; pub mod error; pub mod graphics_state_operators; pub mod marked_content_operators; -pub mod operation_map; pub mod variants; -pub mod operator_trait; pub mod path_operators; -pub mod path_paint_operators; pub mod pdf_operator_backend; pub mod recording_pdf_operator_backend; pub mod shadings_operators; -pub mod text_object_operators; pub mod text_positioning_operators; pub mod text_showing_operators; pub mod text_state_operators; @@ -24,9 +18,15 @@ extern crate alloc; use std::sync::Arc; use pdf_object_reader::{ - FromPdfObject, ObjectAccess, ObjectContext, ReadResult, object_variant::ObjectVariant, + ArrayContext, FromPdfObject, ObjectAccess, ObjectContext, ReadResult, ReadSession, + object_resolver::PassthroughResolver, object_variant::ObjectVariant, }; +/// The operands collected before an operator token, read in place through a +/// session that does not resolve references. +pub type OperandContext<'read, 'reader> = + ArrayContext<'read, ReadSession<'reader, PassthroughResolver>>; + /// One operand from a PDF `Tj` or `TJ` text-showing operation. #[derive(Debug, Clone, PartialEq)] pub enum PdfTextItem { diff --git a/crates/pdf-content-stream-operators/src/marked_content_operators.rs b/crates/pdf-content-stream-operators/src/marked_content_operators.rs index 0d9f63a6..70b8beeb 100644 --- a/crates/pdf-content-stream-operators/src/marked_content_operators.rs +++ b/crates/pdf-content-stream-operators/src/marked_content_operators.rs @@ -3,9 +3,8 @@ use std::sync::Arc; use pdf_object_reader::{ReadResult, object_variant::ObjectVariant, pdf_string::PdfString}; use crate::{ - operator_trait::{OperandContext, PdfOperator}, + OperandContext, pdf_operator_backend::{BackendError, PdfOperatorBackend}, - variants::PdfOperatorVariant, }; /// Begins a marked-content sequence. @@ -20,19 +19,18 @@ impl BeginMarkedContent { pub fn new(tag: Arc<[u8]>) -> Self { Self { tag } } -} - -impl PdfOperator for BeginMarkedContent { - const NAME: &'static [u8] = b"BMC"; - const OPERAND_COUNT: Option = Some(1); - - fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { + /// Reads the operator from its operands. + pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { let tag = operands.at::(0)?.bytes; - Ok(PdfOperatorVariant::BeginMarkedContent(Self::new(tag))) + Ok(Self::new(tag)) } - fn call(&self, backend: &mut T) -> Result<(), BackendError> { + /// Dispatches the operator to `backend`. + pub(crate) fn call( + &self, + backend: &mut T, + ) -> Result<(), BackendError> { backend.begin_marked_content(&self.tag) } } @@ -53,43 +51,22 @@ impl BeginMarkedContentWithProps { pub fn new(tag: Arc<[u8]>, properties: Option>) -> Self { Self { tag, properties } } -} -impl PdfOperator for BeginMarkedContentWithProps { - const NAME: &'static [u8] = b"BDC"; - - const OPERAND_COUNT: Option = Some(2); - - fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { + /// Reads the operator from its operands. + pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { let tag = operands.at::(0)?.bytes; let properties = match operands.array().get(1) { Some(ObjectVariant::String(_)) => Some(operands.at::(1)?.bytes), _ => None, }; - Ok(PdfOperatorVariant::BeginMarkedContentWithProps(Self::new( - tag, properties, - ))) + Ok(Self::new(tag, properties)) } - fn call(&self, backend: &mut T) -> Result<(), BackendError> { + /// Dispatches the operator to `backend`. + pub(crate) fn call( + &self, + backend: &mut T, + ) -> Result<(), BackendError> { backend.begin_marked_content_with_properties(&self.tag, self.properties.as_deref()) } } - -/// Ends a marked-content sequence begun by a `BMC` or `BDC` operator. -#[derive(Debug, Clone, PartialEq, Default)] -pub struct EndMarkedContent; - -impl PdfOperator for EndMarkedContent { - const NAME: &'static [u8] = b"EMC"; - - const OPERAND_COUNT: Option = Some(0); - - fn read(_operands: &mut OperandContext<'_, '_>) -> ReadResult { - Ok(PdfOperatorVariant::EndMarkedContent(Self)) - } - - fn call(&self, backend: &mut T) -> Result<(), BackendError> { - backend.end_marked_content() - } -} diff --git a/crates/pdf-content-stream-operators/src/operation_map.rs b/crates/pdf-content-stream-operators/src/operation_map.rs deleted file mode 100644 index d436b74e..00000000 --- a/crates/pdf-content-stream-operators/src/operation_map.rs +++ /dev/null @@ -1,132 +0,0 @@ -use crate::{ - clipping_path_operators::*, - color_operators::*, - compatibility_operators::*, - error::PdfOperatorError, - graphics_state_operators::*, - marked_content_operators::*, - operator_trait::{OperandContext, PdfOperator}, - path_operators::*, - path_paint_operators::*, - shadings_operators::PaintShading, - text_object_operators::*, - text_positioning_operators::*, - text_showing_operators::*, - text_state_operators::*, - type3_font_operators::{SetCharWidth, SetCharWidthAndBoundingBox}, - variants::PdfOperatorVariant, - xobject_and_image_operators::*, -}; - -use pdf_image::InlineImage; -use pdf_object_reader::ReadResult; -use pdf_parser::parser::PdfParser; - -/// Custom parser used by operators whose bytes are not represented by the -/// regular operand list. -pub type OperatorParseHook = - for<'a> fn(&mut PdfParser<'a>) -> Result, PdfOperatorError>; - -/// Defines a mapping between a PDF operator's string representation (e.g., "m" for MoveTo) -/// and a function that can construct that operator an array of operands. -/// This is used to dynamically dispatch to the correct parsing logic based on the operator -/// encountered in the PDF content. -#[derive(Clone, Copy)] -pub struct OpDescriptor { - pub operand_count: Option, - pub parser: fn(operands: &mut OperandContext<'_, '_>) -> ReadResult, - pub parse_hook: Option, -} - -impl OpDescriptor { - const fn from() -> Self { - Self { - operand_count: T::OPERAND_COUNT, - parser: T::read, - parse_hook: None, - } - } - - const fn with_parse_hook() -> Self { - Self { - operand_count: T::OPERAND_COUNT, - parser: T::read, - parse_hook: Some(T::parse), - } - } -} - -/// Returns the parser descriptor for a PDF content-stream operator. -pub fn get_operation_descriptor(name: &[u8]) -> Option { - match name { - b"\"" => Some(OpDescriptor::from::()), - b"'" => Some(OpDescriptor::from::()), - b"B" => Some(OpDescriptor::from::()), - b"B*" => Some(OpDescriptor::from::()), - b"BDC" => Some(OpDescriptor::from::()), - b"BI" => Some(OpDescriptor::with_parse_hook::()), - b"BMC" => Some(OpDescriptor::from::()), - b"BT" => Some(OpDescriptor::from::()), - b"BX" => Some(OpDescriptor::from::()), - b"CS" => Some(OpDescriptor::from::()), - b"Do" => Some(OpDescriptor::from::()), - b"EMC" => Some(OpDescriptor::from::()), - b"ET" => Some(OpDescriptor::from::()), - b"EX" => Some(OpDescriptor::from::()), - b"G" => Some(OpDescriptor::from::()), - b"J" => Some(OpDescriptor::from::()), - b"K" => Some(OpDescriptor::from::()), - b"M" => Some(OpDescriptor::from::()), - b"Q" => Some(OpDescriptor::from::()), - b"RG" => Some(OpDescriptor::from::()), - b"S" => Some(OpDescriptor::from::()), - b"SC" => Some(OpDescriptor::from::()), - b"SCN" => Some(OpDescriptor::from::()), - b"T*" => Some(OpDescriptor::from::()), - b"TD" => Some(OpDescriptor::from::()), - b"TJ" => Some(OpDescriptor::from::()), - b"TL" => Some(OpDescriptor::from::()), - b"Tc" => Some(OpDescriptor::from::()), - b"Td" => Some(OpDescriptor::from::()), - b"Tf" => Some(OpDescriptor::from::()), - b"Tj" => Some(OpDescriptor::from::()), - b"Tm" => Some(OpDescriptor::from::()), - b"Tr" => Some(OpDescriptor::from::()), - b"Ts" => Some(OpDescriptor::from::()), - b"Tw" => Some(OpDescriptor::from::()), - b"Tz" => Some(OpDescriptor::from::()), - b"W" => Some(OpDescriptor::from::()), - b"W*" => Some(OpDescriptor::from::()), - b"b" => Some(OpDescriptor::from::()), - b"b*" => Some(OpDescriptor::from::()), - b"c" => Some(OpDescriptor::from::()), - b"cm" => Some(OpDescriptor::from::()), - b"cs" => Some(OpDescriptor::from::()), - b"d" => Some(OpDescriptor::from::()), - b"d0" => Some(OpDescriptor::from::()), - b"d1" => Some(OpDescriptor::from::()), - b"f" => Some(OpDescriptor::from::()), - b"f*" => Some(OpDescriptor::from::()), - b"g" => Some(OpDescriptor::from::()), - b"gs" => Some(OpDescriptor::from::()), - b"h" => Some(OpDescriptor::from::()), - b"i" => Some(OpDescriptor::from::()), - b"j" => Some(OpDescriptor::from::()), - b"k" => Some(OpDescriptor::from::()), - b"l" => Some(OpDescriptor::from::()), - b"m" => Some(OpDescriptor::from::()), - b"n" => Some(OpDescriptor::from::()), - b"q" => Some(OpDescriptor::from::()), - b"re" => Some(OpDescriptor::from::()), - b"rg" => Some(OpDescriptor::from::()), - b"ri" => Some(OpDescriptor::from::()), - b"s" => Some(OpDescriptor::from::()), - b"sc" => Some(OpDescriptor::from::()), - b"scn" => Some(OpDescriptor::from::()), - b"sh" => Some(OpDescriptor::from::()), - b"v" => Some(OpDescriptor::from::()), - b"w" => Some(OpDescriptor::from::()), - b"y" => Some(OpDescriptor::from::()), - _ => None, - } -} diff --git a/crates/pdf-content-stream-operators/src/operator_trait.rs b/crates/pdf-content-stream-operators/src/operator_trait.rs deleted file mode 100644 index fb4bcce1..00000000 --- a/crates/pdf-content-stream-operators/src/operator_trait.rs +++ /dev/null @@ -1,56 +0,0 @@ -use pdf_object_reader::{ - ArrayContext, ReadResult, ReadSession, object_resolver::PassthroughResolver, -}; - -use crate::{ - error::PdfOperatorError, - pdf_operator_backend::{BackendError, PdfOperatorBackend}, - variants::PdfOperatorVariant, -}; - -/// The operands collected before an operator token, read in place through a -/// session that does not resolve references. -pub type OperandContext<'read, 'reader> = - ArrayContext<'read, ReadSession<'reader, PassthroughResolver>>; - -/// Represents a PDF content stream operator. -/// -/// This trait provides metadata about a PDF operator, such as its name -/// (the byte slice representation used in PDF content streams) and the number -/// of operands it expects. -/// -/// Implementors of this trait are typically structs that represent specific -/// PDF operators (e.g., `MoveTo`, `SetLineWidth`). -pub trait PdfOperator { - /// The byte slice representation of the PDF operator (e.g., b"m", b"BT", b"rg"). - const NAME: &'static [u8]; - - /// The number of operands this operator consumes from the operand stack. - const OPERAND_COUNT: Option; - - /// Decodes the operator's operands and constructs the specific `PdfOperatorVariant`. - fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult; - - /// Optional custom parsing hook. - /// - /// Operators that cannot be parsed solely from the pre-collected operands - /// (for example, operators that need to read additional bytes from the - /// content stream or use a non-standard grammar) should implement this - /// method so they can perform custom parsing using the low-level - /// `pdf_parser::parser::PdfParser`. - /// - /// The parser is positioned immediately after the operator token. If the - /// operator successfully parses itself it should return `Ok(Some(variant))`. - /// Returning `Ok(None)` (the default) signals that no custom parsing took - /// place and the parsing machinery should fall back to the normal - /// operands-based `read` implementation. - /// - /// Errors should be returned using `PdfOperatorError`. - fn parse<'a>( - _parser: &mut pdf_parser::parser::PdfParser<'a>, - ) -> Result, PdfOperatorError> { - Ok(None) - } - - fn call(&self, backend: &mut T) -> Result<(), BackendError>; -} diff --git a/crates/pdf-content-stream-operators/src/path_operators.rs b/crates/pdf-content-stream-operators/src/path_operators.rs index d694dc40..c51bbf96 100644 --- a/crates/pdf-content-stream-operators/src/path_operators.rs +++ b/crates/pdf-content-stream-operators/src/path_operators.rs @@ -1,7 +1,6 @@ use crate::{ - operator_trait::{OperandContext, PdfOperator}, + OperandContext, pdf_operator_backend::{BackendError, PdfOperatorBackend}, - variants::PdfOperatorVariant, }; use pdf_object_reader::ReadResult; @@ -19,19 +18,18 @@ impl MoveTo { pub fn new(x: f32, y: f32) -> Self { Self { x, y } } -} - -impl PdfOperator for MoveTo { - const NAME: &'static [u8] = b"m"; - const OPERAND_COUNT: Option = Some(2); - - fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { + /// Reads the operator from its operands. + pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { let [x, y] = operands.read_array()?; - Ok(PdfOperatorVariant::MoveTo(Self::new(x, y))) + Ok(Self::new(x, y)) } - fn call(&self, backend: &mut T) -> Result<(), BackendError> { + /// Dispatches the operator to `backend`. + pub(crate) fn call( + &self, + backend: &mut T, + ) -> Result<(), BackendError> { backend.move_to(self.x, self.y) } } @@ -50,19 +48,18 @@ impl LineTo { pub fn new(x: f32, y: f32) -> Self { Self { x, y } } -} -impl PdfOperator for LineTo { - const NAME: &'static [u8] = b"l"; - - const OPERAND_COUNT: Option = Some(2); - - fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { + /// Reads the operator from its operands. + pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { let [x, y] = operands.read_array()?; - Ok(PdfOperatorVariant::LineTo(Self::new(x, y))) + Ok(Self::new(x, y)) } - fn call(&self, backend: &mut T) -> Result<(), BackendError> { + /// Dispatches the operator to `backend`. + pub(crate) fn call( + &self, + backend: &mut T, + ) -> Result<(), BackendError> { backend.line_to(self.x, self.y) } } @@ -97,21 +94,18 @@ impl CurveTo { y3, } } -} -impl PdfOperator for CurveTo { - const NAME: &'static [u8] = b"c"; - - const OPERAND_COUNT: Option = Some(6); - - fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { + /// Reads the operator from its operands. + pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { let [x1, y1, x2, y2, x3, y3] = operands.read_array()?; - Ok(PdfOperatorVariant::CurveTo(Self::new( - x1, y1, x2, y2, x3, y3, - ))) + Ok(Self::new(x1, y1, x2, y2, x3, y3)) } - fn call(&self, backend: &mut T) -> Result<(), BackendError> { + /// Dispatches the operator to `backend`. + pub(crate) fn call( + &self, + backend: &mut T, + ) -> Result<(), BackendError> { backend.curve_to(self.x1, self.y1, self.x2, self.y2, self.x3, self.y3) } } @@ -136,19 +130,18 @@ impl CurveToV { pub fn new(x2: f32, y2: f32, x3: f32, y3: f32) -> Self { Self { x2, y2, x3, y3 } } -} -impl PdfOperator for CurveToV { - const NAME: &'static [u8] = b"v"; - - const OPERAND_COUNT: Option = Some(4); - - fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { + /// Reads the operator from its operands. + pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { let [x2, y2, x3, y3] = operands.read_array()?; - Ok(PdfOperatorVariant::CurveToV(Self::new(x2, y2, x3, y3))) + Ok(Self::new(x2, y2, x3, y3)) } - fn call(&self, backend: &mut T) -> Result<(), BackendError> { + /// Dispatches the operator to `backend`. + pub(crate) fn call( + &self, + backend: &mut T, + ) -> Result<(), BackendError> { backend.curve_to_v(self.x2, self.y2, self.x3, self.y3) } } @@ -172,42 +165,22 @@ impl CurveToY { pub fn new(x1: f32, y1: f32, x3: f32, y3: f32) -> Self { Self { x1, y1, x3, y3 } } -} -impl PdfOperator for CurveToY { - const NAME: &'static [u8] = b"y"; - - const OPERAND_COUNT: Option = Some(4); - - fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { + /// Reads the operator from its operands. + pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { let [x1, y1, x3, y3] = operands.read_array()?; - Ok(PdfOperatorVariant::CurveToY(Self::new(x1, y1, x3, y3))) + Ok(Self::new(x1, y1, x3, y3)) } - fn call(&self, backend: &mut T) -> Result<(), BackendError> { + /// Dispatches the operator to `backend`. + pub(crate) fn call( + &self, + backend: &mut T, + ) -> Result<(), BackendError> { backend.curve_to_y(self.x1, self.y1, self.x3, self.y3) } } -/// Closes the current subpath by appending a straight line segment from the current point -/// to the starting point of the subpath. (PDF operator `h`) -#[derive(Debug, Clone, PartialEq, Default)] -pub struct ClosePath; - -impl PdfOperator for ClosePath { - const NAME: &'static [u8] = b"h"; - - const OPERAND_COUNT: Option = Some(0); - - fn read(_operands: &mut OperandContext<'_, '_>) -> ReadResult { - Ok(PdfOperatorVariant::ClosePath(Self)) - } - - fn call(&self, backend: &mut T) -> Result<(), BackendError> { - backend.close_path() - } -} - /// Appends a complete rectangle, defined by its bottom-left corner (x, y), width, and height, /// to the current path as a complete subpath. /// The new current point is undefined after this operation. @@ -232,21 +205,18 @@ impl Rectangle { height, } } -} - -impl PdfOperator for Rectangle { - const NAME: &'static [u8] = b"re"; - - const OPERAND_COUNT: Option = Some(4); - fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { + /// Reads the operator from its operands. + pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { let [x, y, width, height] = operands.read_array()?; - Ok(PdfOperatorVariant::Rectangle(Self::new( - x, y, width, height, - ))) + Ok(Self::new(x, y, width, height)) } - fn call(&self, backend: &mut T) -> Result<(), BackendError> { + /// Dispatches the operator to `backend`. + pub(crate) fn call( + &self, + backend: &mut T, + ) -> Result<(), BackendError> { backend.rectangle(self.x, self.y, self.width, self.height) } } diff --git a/crates/pdf-content-stream-operators/src/path_paint_operators.rs b/crates/pdf-content-stream-operators/src/path_paint_operators.rs deleted file mode 100644 index 844e472a..00000000 --- a/crates/pdf-content-stream-operators/src/path_paint_operators.rs +++ /dev/null @@ -1,174 +0,0 @@ -use crate::{ - operator_trait::{OperandContext, PdfOperator}, - pdf_operator_backend::{BackendError, PdfOperatorBackend}, - variants::PdfOperatorVariant, -}; -use pdf_object_reader::ReadResult; - -/// Strokes the current path. -#[derive(Debug, Clone, PartialEq, Default)] -pub struct StrokePath; - -impl PdfOperator for StrokePath { - const NAME: &'static [u8] = b"S"; - - const OPERAND_COUNT: Option = Some(0); - - fn read(_operands: &mut OperandContext<'_, '_>) -> ReadResult { - Ok(PdfOperatorVariant::StrokePath(Self)) - } - - fn call(&self, backend: &mut T) -> Result<(), BackendError> { - backend.stroke_path() - } -} - -/// Closes the current subpath and then strokes the path. -/// This is equivalent to a `ClosePath` followed by a `StrokePath`. -#[derive(Debug, Clone, PartialEq, Default)] -pub struct CloseStrokePath; - -impl PdfOperator for CloseStrokePath { - const NAME: &'static [u8] = b"s"; - - const OPERAND_COUNT: Option = Some(0); - - fn read(_operands: &mut OperandContext<'_, '_>) -> ReadResult { - Ok(PdfOperatorVariant::CloseStrokePath(Self)) - } - - fn call(&self, backend: &mut T) -> Result<(), BackendError> { - backend.close_and_stroke_path() - } -} - -/// Fills the current path using the non-zero winding number rule. -#[derive(Debug, Clone, PartialEq, Default)] -pub struct FillPathNonZero; - -impl PdfOperator for FillPathNonZero { - const NAME: &'static [u8] = b"f"; // TODO: or "F" - - const OPERAND_COUNT: Option = Some(0); - - fn read(_operands: &mut OperandContext<'_, '_>) -> ReadResult { - Ok(PdfOperatorVariant::FillPathNonZero(Self)) - } - - fn call(&self, backend: &mut T) -> Result<(), BackendError> { - backend.fill_path_nonzero_winding() - } -} - -/// Fills the current path using the even-odd rule. -#[derive(Debug, Clone, PartialEq, Default)] -pub struct FillPathEvenOdd; - -impl PdfOperator for FillPathEvenOdd { - const NAME: &'static [u8] = b"f*"; - - const OPERAND_COUNT: Option = Some(0); - - fn read(_operands: &mut OperandContext<'_, '_>) -> ReadResult { - Ok(PdfOperatorVariant::FillPathEvenOdd(Self)) - } - - fn call(&self, backend: &mut T) -> Result<(), BackendError> { - backend.fill_path_even_odd() - } -} - -/// Fills and then strokes the current path, using the non-zero winding number rule -/// to determine the region to fill. -#[derive(Debug, Clone, PartialEq, Default)] -pub struct FillAndStrokePathNonZero; - -impl PdfOperator for FillAndStrokePathNonZero { - const NAME: &'static [u8] = b"B"; - - const OPERAND_COUNT: Option = Some(0); - - fn read(_operands: &mut OperandContext<'_, '_>) -> ReadResult { - Ok(PdfOperatorVariant::FillAndStrokePathNonZero(Self)) - } - - fn call(&self, backend: &mut T) -> Result<(), BackendError> { - backend.fill_and_stroke_path_nonzero_winding() - } -} - -/// Fills and then strokes the current path, using the even-odd rule to determine the -/// region to fill. -#[derive(Debug, Clone, PartialEq, Default)] -pub struct FillAndStrokePathEvenOdd; - -impl PdfOperator for FillAndStrokePathEvenOdd { - const NAME: &'static [u8] = b"B*"; - - const OPERAND_COUNT: Option = Some(0); - - fn read(_operands: &mut OperandContext<'_, '_>) -> ReadResult { - Ok(PdfOperatorVariant::FillAndStrokePathEvenOdd(Self)) - } - - fn call(&self, backend: &mut T) -> Result<(), BackendError> { - backend.fill_and_stroke_path_even_odd() - } -} - -/// Closes, fills, and then strokes the current path, using the non-zero winding number -/// rule to determine the region to fill. -#[derive(Debug, Clone, PartialEq, Default)] -pub struct CloseFillAndStrokePathNonZero; - -impl PdfOperator for CloseFillAndStrokePathNonZero { - const NAME: &'static [u8] = b"b"; - - const OPERAND_COUNT: Option = Some(0); - - fn read(_operands: &mut OperandContext<'_, '_>) -> ReadResult { - Ok(PdfOperatorVariant::CloseFillAndStrokePathNonZero(Self)) - } - - fn call(&self, backend: &mut T) -> Result<(), BackendError> { - backend.close_fill_and_stroke_path_nonzero_winding() - } -} - -/// Closes, fills, and then strokes the current path, using the even-odd rule to determine -/// the region to fill. -#[derive(Debug, Clone, PartialEq, Default)] -pub struct CloseFillAndStrokePathEvenOdd; - -impl PdfOperator for CloseFillAndStrokePathEvenOdd { - const NAME: &'static [u8] = b"b*"; - - const OPERAND_COUNT: Option = Some(0); - - fn read(_operands: &mut OperandContext<'_, '_>) -> ReadResult { - Ok(PdfOperatorVariant::CloseFillAndStrokePathEvenOdd(Self)) - } - - fn call(&self, backend: &mut T) -> Result<(), BackendError> { - backend.close_fill_and_stroke_path_even_odd() - } -} - -/// Ends the current path object without filling or stroking it. -/// This operator is a path-painting no-op, used to discard the current path. -#[derive(Debug, Clone, PartialEq, Default)] -pub struct EndPath; - -impl PdfOperator for EndPath { - const NAME: &'static [u8] = b"n"; - - const OPERAND_COUNT: Option = Some(0); - - fn read(_operands: &mut OperandContext<'_, '_>) -> ReadResult { - Ok(PdfOperatorVariant::EndPath(Self)) - } - - fn call(&self, backend: &mut T) -> Result<(), BackendError> { - backend.end_path_no_op() - } -} diff --git a/crates/pdf-content-stream-operators/src/shadings_operators.rs b/crates/pdf-content-stream-operators/src/shadings_operators.rs index 9e77019e..d48e6dc2 100644 --- a/crates/pdf-content-stream-operators/src/shadings_operators.rs +++ b/crates/pdf-content-stream-operators/src/shadings_operators.rs @@ -1,9 +1,8 @@ use std::sync::Arc; use crate::{ - operator_trait::{OperandContext, PdfOperator}, + OperandContext, pdf_operator_backend::{BackendError, PdfOperatorBackend}, - variants::PdfOperatorVariant, }; use pdf_object_reader::{ReadResult, pdf_string::PdfString}; @@ -20,19 +19,18 @@ impl PaintShading { pub fn new(name: Arc<[u8]>) -> Self { Self { name } } -} - -impl PdfOperator for PaintShading { - const NAME: &'static [u8] = b"sh"; - - const OPERAND_COUNT: Option = Some(1); - fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { + /// Reads the operator from its operands. + pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { let name = operands.at::(0)?.bytes; - Ok(PdfOperatorVariant::PaintShading(Self::new(name))) + Ok(Self::new(name)) } - fn call(&self, backend: &mut T) -> Result<(), BackendError> { + /// Dispatches the operator to `backend`. + pub(crate) fn call( + &self, + backend: &mut T, + ) -> Result<(), BackendError> { backend.paint_shading(&self.name) } } diff --git a/crates/pdf-content-stream-operators/src/text_object_operators.rs b/crates/pdf-content-stream-operators/src/text_object_operators.rs deleted file mode 100644 index e93b4622..00000000 --- a/crates/pdf-content-stream-operators/src/text_object_operators.rs +++ /dev/null @@ -1,43 +0,0 @@ -use crate::{ - operator_trait::{OperandContext, PdfOperator}, - pdf_operator_backend::{BackendError, PdfOperatorBackend}, - variants::PdfOperatorVariant, -}; -use pdf_object_reader::ReadResult; - -/// Begins a text object, initializing the text matrix and text line matrix to -/// the identity matrix. -#[derive(Debug, Clone, PartialEq, Default)] -pub struct BeginText; - -impl PdfOperator for BeginText { - const NAME: &'static [u8] = b"BT"; - - const OPERAND_COUNT: Option = Some(0); - - fn read(_operands: &mut OperandContext<'_, '_>) -> ReadResult { - Ok(PdfOperatorVariant::BeginText(Self)) - } - - fn call(&self, backend: &mut T) -> Result<(), BackendError> { - backend.begin_text_object() - } -} - -/// Ends a text object, discarding the text matrix and text line matrix. -#[derive(Debug, Clone, PartialEq, Default)] -pub struct EndText; - -impl PdfOperator for EndText { - const NAME: &'static [u8] = b"ET"; - - const OPERAND_COUNT: Option = Some(0); - - fn read(_operands: &mut OperandContext<'_, '_>) -> ReadResult { - Ok(PdfOperatorVariant::EndText(Self)) - } - - fn call(&self, backend: &mut T) -> Result<(), BackendError> { - backend.end_text_object() - } -} diff --git a/crates/pdf-content-stream-operators/src/text_positioning_operators.rs b/crates/pdf-content-stream-operators/src/text_positioning_operators.rs index fe84f3b1..6ed8dc36 100644 --- a/crates/pdf-content-stream-operators/src/text_positioning_operators.rs +++ b/crates/pdf-content-stream-operators/src/text_positioning_operators.rs @@ -1,9 +1,8 @@ use pdf_graphics::transform::Transform; use crate::{ - operator_trait::{OperandContext, PdfOperator}, + OperandContext, pdf_operator_backend::{BackendError, PdfOperatorBackend}, - variants::PdfOperatorVariant, }; use pdf_object_reader::ReadResult; @@ -23,19 +22,18 @@ impl MoveTextPosition { pub fn new(tx: f32, ty: f32) -> Self { Self { tx, ty } } -} - -impl PdfOperator for MoveTextPosition { - const NAME: &'static [u8] = b"Td"; - - const OPERAND_COUNT: Option = Some(2); - fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { + /// Reads the operator from its operands. + pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { let [tx, ty] = operands.read_array()?; - Ok(PdfOperatorVariant::MoveTextPosition(Self::new(tx, ty))) + Ok(Self::new(tx, ty)) } - fn call(&self, backend: &mut T) -> Result<(), BackendError> { + /// Dispatches the operator to `backend`. + pub(crate) fn call( + &self, + backend: &mut T, + ) -> Result<(), BackendError> { backend.move_text_position(self.tx, self.ty) } } @@ -55,21 +53,18 @@ impl MoveTextPositionAndSetLeading { pub fn new(tx: f32, ty: f32) -> Self { Self { tx, ty } } -} - -impl PdfOperator for MoveTextPositionAndSetLeading { - const NAME: &'static [u8] = b"TD"; - const OPERAND_COUNT: Option = Some(2); - - fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { + /// Reads the operator from its operands. + pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { let [tx, ty] = operands.read_array()?; - Ok(PdfOperatorVariant::MoveTextPositionAndSetLeading( - Self::new(tx, ty), - )) + Ok(Self::new(tx, ty)) } - fn call(&self, backend: &mut T) -> Result<(), BackendError> { + /// Dispatches the operator to `backend`. + pub(crate) fn call( + &self, + backend: &mut T, + ) -> Result<(), BackendError> { backend.move_text_position_and_set_leading(self.tx, self.ty) } } @@ -92,40 +87,17 @@ impl SetTextMatrix { pub const fn matrix(&self) -> &Transform { &self.matrix } -} - -impl PdfOperator for SetTextMatrix { - const NAME: &'static [u8] = b"Tm"; - - const OPERAND_COUNT: Option = Some(6); - fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { - Ok(PdfOperatorVariant::SetTextMatrix(Self::new( - operands.read_array()?, - ))) + /// Reads the operator from its operands. + pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { + Ok(Self::new(operands.read_array()?)) } - fn call(&self, backend: &mut T) -> Result<(), BackendError> { + /// Dispatches the operator to `backend`. + pub(crate) fn call( + &self, + backend: &mut T, + ) -> Result<(), BackendError> { backend.set_text_matrix(&self.matrix) } } - -/// Moves to the start of the next line. -/// This has the same effect as `MoveTextPosition { tx: 0.0, ty: -leading }`, -/// where `leading` is the current value of the text leading parameter in the text state. -#[derive(Debug, Clone, PartialEq, Default)] -pub struct MoveToNextLine; - -impl PdfOperator for MoveToNextLine { - const NAME: &'static [u8] = b"T*"; - - const OPERAND_COUNT: Option = Some(0); - - fn read(_operands: &mut OperandContext<'_, '_>) -> ReadResult { - Ok(PdfOperatorVariant::MoveToNextLine(Self)) - } - - fn call(&self, backend: &mut T) -> Result<(), BackendError> { - backend.move_to_start_of_next_line() - } -} diff --git a/crates/pdf-content-stream-operators/src/text_showing_operators.rs b/crates/pdf-content-stream-operators/src/text_showing_operators.rs index 4f62bb19..1085c5f1 100644 --- a/crates/pdf-content-stream-operators/src/text_showing_operators.rs +++ b/crates/pdf-content-stream-operators/src/text_showing_operators.rs @@ -1,9 +1,6 @@ +use crate::OperandContext; use crate::PdfTextItem; -use crate::operator_trait::{OperandContext, PdfOperator}; -use crate::{ - pdf_operator_backend::{BackendError, PdfOperatorBackend}, - variants::PdfOperatorVariant, -}; +use crate::pdf_operator_backend::{BackendError, PdfOperatorBackend}; use pdf_object_reader::{ReadResult, pdf_string::PdfString}; /// Shows a text string. @@ -18,21 +15,18 @@ impl ShowText { pub fn new(text: PdfTextItem) -> Self { Self { text } } -} - -impl PdfOperator for ShowText { - const NAME: &'static [u8] = b"Tj"; - - const OPERAND_COUNT: Option = Some(1); - fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { + /// Reads the operator from its operands. + pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { let text = operands.at::(0)?.bytes; - Ok(PdfOperatorVariant::ShowText(Self::new(PdfTextItem::Text( - text, - )))) + Ok(Self::new(PdfTextItem::Text(text))) } - fn call(&self, backend: &mut T) -> Result<(), BackendError> { + /// Dispatches the operator to `backend`. + pub(crate) fn call( + &self, + backend: &mut T, + ) -> Result<(), BackendError> { backend.show_text(&self.text) } } @@ -48,21 +42,18 @@ impl MoveNextLineShowText { pub fn new(text: PdfTextItem) -> Self { Self { text } } -} -impl PdfOperator for MoveNextLineShowText { - const NAME: &'static [u8] = b"'"; - - const OPERAND_COUNT: Option = Some(1); - - fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { + /// Reads the operator from its operands. + pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { let text = operands.at::(0)?.bytes; - Ok(PdfOperatorVariant::MoveNextLineShowText(Self::new( - PdfTextItem::Text(text), - ))) + Ok(Self::new(PdfTextItem::Text(text))) } - fn call(&self, backend: &mut T) -> Result<(), BackendError> { + /// Dispatches the operator to `backend`. + pub(crate) fn call( + &self, + backend: &mut T, + ) -> Result<(), BackendError> { backend.move_to_next_line_and_show_text(&self.text) } } @@ -87,25 +78,24 @@ impl SetSpacingMoveShowText { text, } } -} -impl PdfOperator for SetSpacingMoveShowText { - const NAME: &'static [u8] = b"\""; - - const OPERAND_COUNT: Option = Some(3); - - fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { + /// Reads the operator from its operands. + pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { let [word_spacing, char_spacing] = operands.read_array()?; let text = operands.at::(2)?.bytes; - Ok(PdfOperatorVariant::SetSpacingMoveShowText(Self::new( + Ok(Self::new( word_spacing, char_spacing, PdfTextItem::Text(text), - ))) + )) } - fn call(&self, backend: &mut T) -> Result<(), BackendError> { + /// Dispatches the operator to `backend`. + pub(crate) fn call( + &self, + backend: &mut T, + ) -> Result<(), BackendError> { backend.set_spacing_and_show_text(self.word_spacing, self.char_spacing, &self.text) } } @@ -124,20 +114,17 @@ impl ShowTextArray { pub fn new(elements: Vec) -> Self { Self { elements } } -} - -impl PdfOperator for ShowTextArray { - const NAME: &'static [u8] = b"TJ"; - - const OPERAND_COUNT: Option = Some(1); - fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { - Ok(PdfOperatorVariant::ShowTextArray(Self::new( - operands.at(0)?, - ))) + /// Reads the operator from its operands. + pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { + Ok(Self::new(operands.at(0)?)) } - fn call(&self, backend: &mut T) -> Result<(), BackendError> { + /// Dispatches the operator to `backend`. + pub(crate) fn call( + &self, + backend: &mut T, + ) -> Result<(), BackendError> { backend.show_text_with_glyph_positioning(&self.elements) } } @@ -171,10 +158,6 @@ mod tests { let operator = ShowText::read(&mut ArrayContext::new(&operands, &mut session)) .expect("text should parse"); - let PdfOperatorVariant::ShowText(operator) = operator else { - panic!("expected ShowText variant"); - }; - let PdfTextItem::Text(text) = operator.text else { panic!("expected text item"); }; @@ -198,10 +181,6 @@ mod tests { let operator = SetSpacingMoveShowText::read(&mut ArrayContext::new(&operands, &mut session)) .expect("mixed operands should parse successfully"); - let PdfOperatorVariant::SetSpacingMoveShowText(operator) = operator else { - panic!("expected SetSpacingMoveShowText variant"); - }; - assert_eq!(operator.word_spacing, 1.5); assert_eq!(operator.char_spacing, 2.0); assert_eq!(operator.text, PdfTextItem::Text(Arc::from(&b"text"[..]))); diff --git a/crates/pdf-content-stream-operators/src/text_state_operators.rs b/crates/pdf-content-stream-operators/src/text_state_operators.rs index ec9f6ab5..4207de39 100644 --- a/crates/pdf-content-stream-operators/src/text_state_operators.rs +++ b/crates/pdf-content-stream-operators/src/text_state_operators.rs @@ -1,10 +1,9 @@ use std::sync::Arc; use crate::{ + OperandContext, error::PdfOperatorError, - operator_trait::{OperandContext, PdfOperator}, pdf_operator_backend::{BackendError, PdfOperatorBackend}, - variants::PdfOperatorVariant, }; use num_traits::FromPrimitive; use pdf_graphics::TextRenderingMode; @@ -21,19 +20,18 @@ impl SetCharacterSpacing { pub fn new(spacing: f32) -> Self { Self { spacing } } -} - -impl PdfOperator for SetCharacterSpacing { - const NAME: &'static [u8] = b"Tc"; - - const OPERAND_COUNT: Option = Some(1); - fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { + /// Reads the operator from its operands. + pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { let spacing = operands.at(0)?; - Ok(PdfOperatorVariant::SetCharacterSpacing(Self::new(spacing))) + Ok(Self::new(spacing)) } - fn call(&self, backend: &mut T) -> Result<(), BackendError> { + /// Dispatches the operator to `backend`. + pub(crate) fn call( + &self, + backend: &mut T, + ) -> Result<(), BackendError> { backend.set_character_spacing(self.spacing) } } @@ -50,19 +48,18 @@ impl SetWordSpacing { pub fn new(spacing: f32) -> Self { Self { spacing } } -} - -impl PdfOperator for SetWordSpacing { - const NAME: &'static [u8] = b"Tw"; - - const OPERAND_COUNT: Option = Some(1); - fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { + /// Reads the operator from its operands. + pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { let spacing = operands.at(0)?; - Ok(PdfOperatorVariant::SetWordSpacing(Self::new(spacing))) + Ok(Self::new(spacing)) } - fn call(&self, backend: &mut T) -> Result<(), BackendError> { + /// Dispatches the operator to `backend`. + pub(crate) fn call( + &self, + backend: &mut T, + ) -> Result<(), BackendError> { backend.set_word_spacing(self.spacing) } } @@ -79,19 +76,18 @@ impl SetHorizontalScaling { pub fn new(scale: f32) -> Self { Self { scale } } -} - -impl PdfOperator for SetHorizontalScaling { - const NAME: &'static [u8] = b"Tz"; - - const OPERAND_COUNT: Option = Some(1); - fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { + /// Reads the operator from its operands. + pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { let scale = operands.at(0)?; - Ok(PdfOperatorVariant::SetHorizontalScaling(Self::new(scale))) + Ok(Self::new(scale)) } - fn call(&self, backend: &mut T) -> Result<(), BackendError> { + /// Dispatches the operator to `backend`. + pub(crate) fn call( + &self, + backend: &mut T, + ) -> Result<(), BackendError> { backend.set_horizontal_text_scaling(self.scale) } } @@ -114,19 +110,18 @@ impl SetLeading { pub const fn leading(&self) -> f32 { self.leading } -} - -impl PdfOperator for SetLeading { - const NAME: &'static [u8] = b"TL"; - const OPERAND_COUNT: Option = Some(1); - - fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { + /// Reads the operator from its operands. + pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { let leading = operands.at(0)?; - Ok(PdfOperatorVariant::SetLeading(Self::new(leading))) + Ok(Self::new(leading)) } - fn call(&self, backend: &mut T) -> Result<(), BackendError> { + /// Dispatches the operator to `backend`. + pub(crate) fn call( + &self, + backend: &mut T, + ) -> Result<(), BackendError> { backend.set_text_leading(self.leading) } } @@ -155,20 +150,19 @@ impl SetFont { pub const fn size(&self) -> f32 { self.size } -} - -impl PdfOperator for SetFont { - const NAME: &'static [u8] = b"Tf"; - const OPERAND_COUNT: Option = Some(2); - - fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { + /// Reads the operator from its operands. + pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { let name = operands.at::(0)?.bytes; let size = operands.at(1)?; - Ok(PdfOperatorVariant::SetFont(Self::new(name, size))) + Ok(Self::new(name, size)) } - fn call(&self, backend: &mut T) -> Result<(), BackendError> { + /// Dispatches the operator to `backend`. + pub(crate) fn call( + &self, + backend: &mut T, + ) -> Result<(), BackendError> { backend.set_font_and_size(&self.name, self.size) } } @@ -191,19 +185,18 @@ impl SetRenderingMode { }), } } -} -impl PdfOperator for SetRenderingMode { - const NAME: &'static [u8] = b"Tr"; - - const OPERAND_COUNT: Option = Some(1); - - fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { + /// Reads the operator from its operands. + pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { let mode = operands.at(0)?; - Ok(PdfOperatorVariant::SetRenderingMode(Self::new(mode)?)) + Ok(Self::new(mode)?) } - fn call(&self, backend: &mut T) -> Result<(), BackendError> { + /// Dispatches the operator to `backend`. + pub(crate) fn call( + &self, + backend: &mut T, + ) -> Result<(), BackendError> { backend.set_text_rendering_mode(self.mode) } } @@ -220,19 +213,18 @@ impl SetTextRise { pub fn new(rise: f32) -> Self { Self { rise } } -} - -impl PdfOperator for SetTextRise { - const NAME: &'static [u8] = b"Ts"; - - const OPERAND_COUNT: Option = Some(1); - fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { + /// Reads the operator from its operands. + pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { let rise = operands.at(0)?; - Ok(PdfOperatorVariant::SetTextRise(Self::new(rise))) + Ok(Self::new(rise)) } - fn call(&self, backend: &mut T) -> Result<(), BackendError> { + /// Dispatches the operator to `backend`. + pub(crate) fn call( + &self, + backend: &mut T, + ) -> Result<(), BackendError> { backend.set_text_rise(self.rise) } } diff --git a/crates/pdf-content-stream-operators/src/type3_font_operators.rs b/crates/pdf-content-stream-operators/src/type3_font_operators.rs index f7c77044..4db3084c 100644 --- a/crates/pdf-content-stream-operators/src/type3_font_operators.rs +++ b/crates/pdf-content-stream-operators/src/type3_font_operators.rs @@ -1,7 +1,6 @@ use crate::{ - operator_trait::{OperandContext, PdfOperator}, + OperandContext, pdf_operator_backend::{BackendError, PdfOperatorBackend}, - variants::PdfOperatorVariant, }; use pdf_object_reader::ReadResult; @@ -21,25 +20,26 @@ pub struct SetCharWidthAndBoundingBox { ury: f32, } -impl PdfOperator for SetCharWidthAndBoundingBox { - const NAME: &'static [u8] = b"d1"; - - const OPERAND_COUNT: Option = Some(6); - - fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { +impl SetCharWidthAndBoundingBox { + /// Reads the operator from its operands. + pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { let [wx, wy, llx, lly, urx, ury] = operands.read_array()?; - Ok(PdfOperatorVariant::SetCharWidthAndBoundingBox(Self { + Ok(Self { wx, wy, llx, lly, urx, ury, - })) + }) } - fn call(&self, _backend: &mut T) -> Result<(), BackendError> { + /// Dispatches the operator to `backend`. + pub(crate) fn call( + &self, + _backend: &mut T, + ) -> Result<(), BackendError> { Ok(()) } } @@ -52,18 +52,19 @@ pub struct SetCharWidth { wy: f32, } -impl PdfOperator for SetCharWidth { - const NAME: &'static [u8] = b"d0"; - - const OPERAND_COUNT: Option = Some(2); - - fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { +impl SetCharWidth { + /// Reads the operator from its operands. + pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { let [wx, wy] = operands.read_array()?; - Ok(PdfOperatorVariant::SetCharWidth(Self { wx, wy })) + Ok(Self { wx, wy }) } - fn call(&self, _backend: &mut T) -> Result<(), BackendError> { + /// Dispatches the operator to `backend`. + pub(crate) fn call( + &self, + _backend: &mut T, + ) -> Result<(), BackendError> { Ok(()) } } diff --git a/crates/pdf-content-stream-operators/src/variants.rs b/crates/pdf-content-stream-operators/src/variants.rs index 185d9ae5..054ade50 100644 --- a/crates/pdf-content-stream-operators/src/variants.rs +++ b/crates/pdf-content-stream-operators/src/variants.rs @@ -1,19 +1,16 @@ use std::sync::Arc; use pdf_image::InlineImage; +use pdf_object_reader::ReadResult; -use crate::compatibility_operators::{BeginCompatibility, EndCompatibility}; use crate::{ - clipping_path_operators::*, + OperandContext, color_operators::*, graphics_state_operators::*, marked_content_operators::*, - operator_trait::PdfOperator, path_operators::*, - path_paint_operators::*, pdf_operator_backend::{BackendError, PdfOperatorBackend}, shadings_operators::PaintShading, - text_object_operators::*, text_positioning_operators::*, text_showing_operators::*, text_state_operators::*, @@ -21,6 +18,7 @@ use crate::{ xobject_and_image_operators::*, }; +/// One PDF content-stream operation with its decoded operands. #[derive(Debug, Clone)] pub enum PdfOperatorVariant { LineTo(LineTo), @@ -28,19 +26,38 @@ pub enum PdfOperatorVariant { CurveTo(CurveTo), CurveToV(CurveToV), CurveToY(CurveToY), - ClosePath(ClosePath), + /// Closes the current subpath by appending a straight line segment from the current point + /// to the starting point of the subpath. (PDF operator `h`) + ClosePath, Rectangle(Rectangle), - StrokePath(StrokePath), - CloseStrokePath(CloseStrokePath), - FillPathNonZero(FillPathNonZero), - FillPathEvenOdd(FillPathEvenOdd), - FillAndStrokePathNonZero(FillAndStrokePathNonZero), - FillAndStrokePathEvenOdd(FillAndStrokePathEvenOdd), - CloseFillAndStrokePathNonZero(CloseFillAndStrokePathNonZero), - CloseFillAndStrokePathEvenOdd(CloseFillAndStrokePathEvenOdd), - EndPath(EndPath), - ClipNonZero(ClipNonZero), - ClipEvenOdd(ClipEvenOdd), + /// Strokes the current path. + StrokePath, + /// Closes the current subpath and then strokes the path. + /// This is equivalent to a `ClosePath` followed by a `StrokePath`. + CloseStrokePath, + /// Fills the current path using the non-zero winding number rule. + FillPathNonZero, + /// Fills the current path using the even-odd rule. + FillPathEvenOdd, + /// Fills and then strokes the current path, using the non-zero winding number rule + /// to determine the region to fill. + FillAndStrokePathNonZero, + /// Fills and then strokes the current path, using the even-odd rule to determine the + /// region to fill. + FillAndStrokePathEvenOdd, + /// Closes, fills, and then strokes the current path, using the non-zero winding number + /// rule to determine the region to fill. + CloseFillAndStrokePathNonZero, + /// Closes, fills, and then strokes the current path, using the even-odd rule to determine + /// the region to fill. + CloseFillAndStrokePathEvenOdd, + /// Ends the current path object without filling or stroking it. + /// This operator is a path-painting no-op, used to discard the current path. + EndPath, + /// Modifies the current clipping path by intersecting it with the current path, using the non-zero winding number rule to determine the region to clip. + ClipNonZero, + /// Modifies the current clipping path by intersecting it with the current path, using the even-odd rule to determine the region to clip. + ClipEvenOdd, SetGrayFill(SetGrayFill), SetGrayStroke(SetGrayStroke), SetRGBFill(SetRGBFill), @@ -54,18 +71,27 @@ pub enum PdfOperatorVariant { SetDashPattern(SetDashPattern), SetFlatnessTolerance(SetFlatnessTolerance), SetGraphicsStateFromDict(SetGraphicsStateFromDict), - SaveGraphicsState(SaveGraphicsState), - RestoreGraphicsState(RestoreGraphicsState), + /// Saves the current graphics state on the graphics state stack. + SaveGraphicsState, + /// Restores the graphics state by removing the most recently saved state from the stack. + RestoreGraphicsState, ConcatMatrix(ConcatMatrix), BeginMarkedContent(BeginMarkedContent), BeginMarkedContentWithProps(BeginMarkedContentWithProps), - EndMarkedContent(EndMarkedContent), - BeginText(BeginText), - EndText(EndText), + /// Ends a marked-content sequence begun by a `BMC` or `BDC` operator. + EndMarkedContent, + /// Begins a text object, initializing the text matrix and text line matrix to + /// the identity matrix. + BeginText, + /// Ends a text object, discarding the text matrix and text line matrix. + EndText, MoveTextPosition(MoveTextPosition), MoveTextPositionAndSetLeading(MoveTextPositionAndSetLeading), SetTextMatrix(SetTextMatrix), - MoveToNextLine(MoveToNextLine), + /// Moves to the start of the next line. + /// This has the same effect as `MoveTextPosition { tx: 0.0, ty: -leading }`, + /// where `leading` is the current value of the text leading parameter in the text state. + MoveToNextLine, ShowText(ShowText), MoveNextLineShowText(MoveNextLineShowText), SetSpacingMoveShowText(SetSpacingMoveShowText), @@ -78,9 +104,18 @@ pub enum PdfOperatorVariant { SetRenderingMode(SetRenderingMode), SetTextRise(SetTextRise), InvokeXObject(InvokeXObject), - BeginCompatibility(BeginCompatibility), + /// Begins a compatibility section (BX). + /// + /// According to the PDF specification, a compatibility section allows + /// consumers to ignore any operators they do not recognize until the + /// matching EX operator is encountered. For our purposes, we parse and + /// expose these operators so unknown operators within can be tolerated + /// by higher-level logic. Backends may choose to track nesting depth, + /// but by default these operators are no-ops. + BeginCompatibility, InlineImage(Arc), - EndCompatibility(EndCompatibility), + /// Ends a compatibility section (EX). + EndCompatibility, PaintShading(PaintShading), SetCharWidthAndBoundingBox(SetCharWidthAndBoundingBox), SetCharWidth(SetCharWidth), @@ -92,74 +127,164 @@ pub enum PdfOperatorVariant { } impl PdfOperatorVariant { + /// Reads the operator named `name` from the operands collected before it. + /// + /// Returns `Ok(None)` for an unknown operator or one given the wrong number + /// of operands. Inline images (`BI`) carry their own grammar and are parsed + /// from the content stream instead. + pub fn read(name: &[u8], operands: &mut OperandContext<'_, '_>) -> ReadResult> { + let operator = match (name, operands.array().len()) { + (b"\"", 3) => Self::SetSpacingMoveShowText(SetSpacingMoveShowText::read(operands)?), + (b"'", 1) => Self::MoveNextLineShowText(MoveNextLineShowText::read(operands)?), + (b"B", 0) => Self::FillAndStrokePathNonZero, + (b"B*", 0) => Self::FillAndStrokePathEvenOdd, + (b"BDC", 2) => { + Self::BeginMarkedContentWithProps(BeginMarkedContentWithProps::read(operands)?) + } + (b"BMC", 1) => Self::BeginMarkedContent(BeginMarkedContent::read(operands)?), + (b"BT", 0) => Self::BeginText, + (b"BX", 0) => Self::BeginCompatibility, + (b"CS", 1) => Self::SetStrokeColorSpace(SetStrokeColorSpace::read(operands)?), + (b"Do", 1) => Self::InvokeXObject(InvokeXObject::read(operands)?), + (b"EMC", 0) => Self::EndMarkedContent, + (b"ET", 0) => Self::EndText, + (b"EX", 0) => Self::EndCompatibility, + (b"G", 1) => Self::SetGrayStroke(SetGrayStroke::read(operands)?), + (b"J", 1) => Self::SetLineCapStyle(SetLineCapStyle::read(operands)?), + (b"K", 4) => Self::SetCMYKStroke(SetCMYKStroke::read(operands)?), + (b"M", 1) => Self::SetMiterLimit(SetMiterLimit::read(operands)?), + (b"Q", 0) => Self::RestoreGraphicsState, + (b"RG", 3) => Self::SetRGBStroke(SetRGBStroke::read(operands)?), + (b"S", 0) => Self::StrokePath, + (b"SC", _) => Self::SetStrokingColor(SetStrokingColor::read_without_pattern(operands)?), + (b"SCN", _) => Self::SetStrokingColor(SetStrokingColor::read(operands)?), + (b"T*", 0) => Self::MoveToNextLine, + (b"TD", 2) => { + Self::MoveTextPositionAndSetLeading(MoveTextPositionAndSetLeading::read(operands)?) + } + (b"TJ", 1) => Self::ShowTextArray(ShowTextArray::read(operands)?), + (b"TL", 1) => Self::SetLeading(SetLeading::read(operands)?), + (b"Tc", 1) => Self::SetCharacterSpacing(SetCharacterSpacing::read(operands)?), + (b"Td", 2) => Self::MoveTextPosition(MoveTextPosition::read(operands)?), + (b"Tf", 2) => Self::SetFont(SetFont::read(operands)?), + (b"Tj", 1) => Self::ShowText(ShowText::read(operands)?), + (b"Tm", 6) => Self::SetTextMatrix(SetTextMatrix::read(operands)?), + (b"Tr", 1) => Self::SetRenderingMode(SetRenderingMode::read(operands)?), + (b"Ts", 1) => Self::SetTextRise(SetTextRise::read(operands)?), + (b"Tw", 1) => Self::SetWordSpacing(SetWordSpacing::read(operands)?), + (b"Tz", 1) => Self::SetHorizontalScaling(SetHorizontalScaling::read(operands)?), + (b"W", 0) => Self::ClipNonZero, + (b"W*", 0) => Self::ClipEvenOdd, + (b"b", 0) => Self::CloseFillAndStrokePathNonZero, + (b"b*", 0) => Self::CloseFillAndStrokePathEvenOdd, + (b"c", 6) => Self::CurveTo(CurveTo::read(operands)?), + (b"cm", 6) => Self::ConcatMatrix(ConcatMatrix::read(operands)?), + (b"cs", 1) => Self::SetNonStrokingColorSpace(SetNonStrokingColorSpace::read(operands)?), + (b"d", 2) => Self::SetDashPattern(SetDashPattern::read(operands)?), + (b"d0", 2) => Self::SetCharWidth(SetCharWidth::read(operands)?), + (b"d1", 6) => { + Self::SetCharWidthAndBoundingBox(SetCharWidthAndBoundingBox::read(operands)?) + } + (b"f", 0) => Self::FillPathNonZero, + (b"f*", 0) => Self::FillPathEvenOdd, + (b"g", 1) => Self::SetGrayFill(SetGrayFill::read(operands)?), + (b"gs", 1) => Self::SetGraphicsStateFromDict(SetGraphicsStateFromDict::read(operands)?), + (b"h", 0) => Self::ClosePath, + (b"i", 1) => Self::SetFlatnessTolerance(SetFlatnessTolerance::read(operands)?), + (b"j", 1) => Self::SetLineJoinStyle(SetLineJoinStyle::read(operands)?), + (b"k", 4) => Self::SetCMYKFill(SetCMYKFill::read(operands)?), + (b"l", 2) => Self::LineTo(LineTo::read(operands)?), + (b"m", 2) => Self::MoveTo(MoveTo::read(operands)?), + (b"n", 0) => Self::EndPath, + (b"q", 0) => Self::SaveGraphicsState, + (b"re", 4) => Self::Rectangle(Rectangle::read(operands)?), + (b"rg", 3) => Self::SetRGBFill(SetRGBFill::read(operands)?), + (b"ri", 1) => Self::SetRenderingIntent(SetRenderingIntent::read(operands)?), + (b"s", 0) => Self::CloseStrokePath, + (b"sc", _) => { + Self::SetNonStrokingColor(SetNonStrokingColor::read_without_pattern(operands)?) + } + (b"scn", _) => Self::SetNonStrokingColor(SetNonStrokingColor::read(operands)?), + (b"sh", 1) => Self::PaintShading(PaintShading::read(operands)?), + (b"v", 4) => Self::CurveToV(CurveToV::read(operands)?), + (b"w", 1) => Self::SetLineWidth(SetLineWidth::read(operands)?), + (b"y", 4) => Self::CurveToY(CurveToY::read(operands)?), + _ => return Ok(None), + }; + Ok(Some(operator)) + } + + /// Dispatches the operation to `backend`. pub fn call(&self, backend: &mut T) -> Result<(), BackendError> { match self { - PdfOperatorVariant::LineTo(op) => op.call(backend), - PdfOperatorVariant::MoveTo(op) => op.call(backend), - PdfOperatorVariant::CurveTo(op) => op.call(backend), - PdfOperatorVariant::CurveToV(op) => op.call(backend), - PdfOperatorVariant::CurveToY(op) => op.call(backend), - PdfOperatorVariant::ClosePath(op) => op.call(backend), - PdfOperatorVariant::Rectangle(op) => op.call(backend), - PdfOperatorVariant::StrokePath(op) => op.call(backend), - PdfOperatorVariant::CloseStrokePath(op) => op.call(backend), - PdfOperatorVariant::FillPathNonZero(op) => op.call(backend), - PdfOperatorVariant::FillPathEvenOdd(op) => op.call(backend), - PdfOperatorVariant::FillAndStrokePathNonZero(op) => op.call(backend), - PdfOperatorVariant::FillAndStrokePathEvenOdd(op) => op.call(backend), - PdfOperatorVariant::CloseFillAndStrokePathNonZero(op) => op.call(backend), - PdfOperatorVariant::CloseFillAndStrokePathEvenOdd(op) => op.call(backend), - PdfOperatorVariant::EndPath(op) => op.call(backend), - PdfOperatorVariant::ClipNonZero(op) => op.call(backend), - PdfOperatorVariant::ClipEvenOdd(op) => op.call(backend), - PdfOperatorVariant::SetGrayFill(op) => op.call(backend), - PdfOperatorVariant::SetGrayStroke(op) => op.call(backend), - PdfOperatorVariant::SetRGBFill(op) => op.call(backend), - PdfOperatorVariant::SetRGBStroke(op) => op.call(backend), - PdfOperatorVariant::SetCMYKFill(op) => op.call(backend), - PdfOperatorVariant::SetCMYKStroke(op) => op.call(backend), - PdfOperatorVariant::SetLineWidth(op) => op.call(backend), - PdfOperatorVariant::SetLineCapStyle(op) => op.call(backend), - PdfOperatorVariant::SetLineJoinStyle(op) => op.call(backend), - PdfOperatorVariant::SetMiterLimit(op) => op.call(backend), - PdfOperatorVariant::SetDashPattern(op) => op.call(backend), - PdfOperatorVariant::SetFlatnessTolerance(op) => op.call(backend), - PdfOperatorVariant::SetGraphicsStateFromDict(op) => op.call(backend), - PdfOperatorVariant::SaveGraphicsState(op) => op.call(backend), - PdfOperatorVariant::RestoreGraphicsState(op) => op.call(backend), - PdfOperatorVariant::ConcatMatrix(op) => op.call(backend), - PdfOperatorVariant::BeginMarkedContent(op) => op.call(backend), - PdfOperatorVariant::BeginMarkedContentWithProps(op) => op.call(backend), - PdfOperatorVariant::EndMarkedContent(op) => op.call(backend), - PdfOperatorVariant::BeginText(op) => op.call(backend), - PdfOperatorVariant::EndText(op) => op.call(backend), - PdfOperatorVariant::MoveTextPosition(op) => op.call(backend), - PdfOperatorVariant::MoveTextPositionAndSetLeading(op) => op.call(backend), - PdfOperatorVariant::SetTextMatrix(op) => op.call(backend), - PdfOperatorVariant::MoveToNextLine(op) => op.call(backend), - PdfOperatorVariant::ShowText(op) => op.call(backend), - PdfOperatorVariant::MoveNextLineShowText(op) => op.call(backend), - PdfOperatorVariant::SetSpacingMoveShowText(op) => op.call(backend), - PdfOperatorVariant::ShowTextArray(op) => op.call(backend), - PdfOperatorVariant::SetCharacterSpacing(op) => op.call(backend), - PdfOperatorVariant::SetWordSpacing(op) => op.call(backend), - PdfOperatorVariant::SetHorizontalScaling(op) => op.call(backend), - PdfOperatorVariant::SetLeading(op) => op.call(backend), - PdfOperatorVariant::SetFont(op) => op.call(backend), - PdfOperatorVariant::SetRenderingMode(op) => op.call(backend), - PdfOperatorVariant::SetTextRise(op) => op.call(backend), - PdfOperatorVariant::InvokeXObject(op) => op.call(backend), - PdfOperatorVariant::BeginCompatibility(op) => op.call(backend), - PdfOperatorVariant::InlineImage(op) => op.call(backend), - PdfOperatorVariant::EndCompatibility(op) => op.call(backend), - PdfOperatorVariant::PaintShading(op) => op.call(backend), - PdfOperatorVariant::SetCharWidthAndBoundingBox(op) => op.call(backend), - PdfOperatorVariant::SetCharWidth(op) => op.call(backend), - PdfOperatorVariant::SetRenderingIntent(op) => op.call(backend), - PdfOperatorVariant::SetStrokeColorSpace(op) => op.call(backend), - PdfOperatorVariant::SetNonStrokingColorSpace(op) => op.call(backend), - PdfOperatorVariant::SetStrokingColor(op) => op.call(backend), - PdfOperatorVariant::SetNonStrokingColor(op) => op.call(backend), + Self::LineTo(op) => op.call(backend), + Self::MoveTo(op) => op.call(backend), + Self::CurveTo(op) => op.call(backend), + Self::CurveToV(op) => op.call(backend), + Self::CurveToY(op) => op.call(backend), + Self::ClosePath => backend.close_path(), + Self::Rectangle(op) => op.call(backend), + Self::StrokePath => backend.stroke_path(), + Self::CloseStrokePath => backend.close_and_stroke_path(), + Self::FillPathNonZero => backend.fill_path_nonzero_winding(), + Self::FillPathEvenOdd => backend.fill_path_even_odd(), + Self::FillAndStrokePathNonZero => backend.fill_and_stroke_path_nonzero_winding(), + Self::FillAndStrokePathEvenOdd => backend.fill_and_stroke_path_even_odd(), + Self::CloseFillAndStrokePathNonZero => { + backend.close_fill_and_stroke_path_nonzero_winding() + } + Self::CloseFillAndStrokePathEvenOdd => backend.close_fill_and_stroke_path_even_odd(), + Self::EndPath => backend.end_path_no_op(), + Self::ClipNonZero => backend.clip_path_nonzero_winding(), + Self::ClipEvenOdd => backend.clip_path_even_odd(), + Self::SetGrayFill(op) => op.call(backend), + Self::SetGrayStroke(op) => op.call(backend), + Self::SetRGBFill(op) => op.call(backend), + Self::SetRGBStroke(op) => op.call(backend), + Self::SetCMYKFill(op) => op.call(backend), + Self::SetCMYKStroke(op) => op.call(backend), + Self::SetLineWidth(op) => op.call(backend), + Self::SetLineCapStyle(op) => op.call(backend), + Self::SetLineJoinStyle(op) => op.call(backend), + Self::SetMiterLimit(op) => op.call(backend), + Self::SetDashPattern(op) => op.call(backend), + Self::SetFlatnessTolerance(op) => op.call(backend), + Self::SetGraphicsStateFromDict(op) => op.call(backend), + Self::SaveGraphicsState => backend.save_graphics_state(), + Self::RestoreGraphicsState => backend.restore_graphics_state(), + Self::ConcatMatrix(op) => op.call(backend), + Self::BeginMarkedContent(op) => op.call(backend), + Self::BeginMarkedContentWithProps(op) => op.call(backend), + Self::EndMarkedContent => backend.end_marked_content(), + Self::BeginText => backend.begin_text_object(), + Self::EndText => backend.end_text_object(), + Self::MoveTextPosition(op) => op.call(backend), + Self::MoveTextPositionAndSetLeading(op) => op.call(backend), + Self::SetTextMatrix(op) => op.call(backend), + Self::MoveToNextLine => backend.move_to_start_of_next_line(), + Self::ShowText(op) => op.call(backend), + Self::MoveNextLineShowText(op) => op.call(backend), + Self::SetSpacingMoveShowText(op) => op.call(backend), + Self::ShowTextArray(op) => op.call(backend), + Self::SetCharacterSpacing(op) => op.call(backend), + Self::SetWordSpacing(op) => op.call(backend), + Self::SetHorizontalScaling(op) => op.call(backend), + Self::SetLeading(op) => op.call(backend), + Self::SetFont(op) => op.call(backend), + Self::SetRenderingMode(op) => op.call(backend), + Self::SetTextRise(op) => op.call(backend), + Self::InvokeXObject(op) => op.call(backend), + Self::BeginCompatibility => Ok(()), + Self::InlineImage(image) => backend.paint_inline_image(image), + Self::EndCompatibility => Ok(()), + Self::PaintShading(op) => op.call(backend), + Self::SetCharWidthAndBoundingBox(op) => op.call(backend), + Self::SetCharWidth(op) => op.call(backend), + Self::SetRenderingIntent(op) => op.call(backend), + Self::SetStrokeColorSpace(op) => op.call(backend), + Self::SetNonStrokingColorSpace(op) => op.call(backend), + Self::SetStrokingColor(op) => op.call(backend), + Self::SetNonStrokingColor(op) => op.call(backend), } } } diff --git a/crates/pdf-content-stream-operators/src/xobject_and_image_operators.rs b/crates/pdf-content-stream-operators/src/xobject_and_image_operators.rs index 4abc4f72..8d4f2a5f 100644 --- a/crates/pdf-content-stream-operators/src/xobject_and_image_operators.rs +++ b/crates/pdf-content-stream-operators/src/xobject_and_image_operators.rs @@ -1,16 +1,10 @@ use std::sync::Arc; -use pdf_image::InlineImage; -use pdf_object_reader::{ - ObjectReader, ReadResult, object_resolver::PassthroughResolver, pdf_string::PdfString, -}; -use pdf_parser::parser::PdfParser; +use pdf_object_reader::{ReadResult, pdf_string::PdfString}; use crate::{ - error::PdfOperatorError, - operator_trait::{OperandContext, PdfOperator}, + OperandContext, pdf_operator_backend::{BackendError, PdfOperatorBackend}, - variants::PdfOperatorVariant, }; /// Invokes a named XObject. @@ -25,48 +19,25 @@ impl InvokeXObject { pub fn new(name: Arc<[u8]>) -> Self { Self { name } } -} - -impl PdfOperator for InvokeXObject { - const NAME: &'static [u8] = b"Do"; - - const OPERAND_COUNT: Option = Some(1); - fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { + /// Reads the operator from its operands. + pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { let name = operands.at::(0)?.bytes; - Ok(PdfOperatorVariant::InvokeXObject(Self::new(name))) + Ok(Self::new(name)) } - fn call(&self, backend: &mut T) -> Result<(), BackendError> { + /// Dispatches the operator to `backend`. + pub(crate) fn call( + &self, + backend: &mut T, + ) -> Result<(), BackendError> { backend.invoke_xobject(&self.name) } } -impl PdfOperator for InlineImage { - const NAME: &'static [u8] = b"BI"; - - const OPERAND_COUNT: Option = Some(0); - - fn read(_operands: &mut OperandContext<'_, '_>) -> ReadResult { - Err(PdfOperatorError::UnsupportedOperator("BI").into()) - } - - fn parse<'a>( - parser: &mut PdfParser<'a>, - ) -> Result, PdfOperatorError> { - let reader = ObjectReader::new(PassthroughResolver); - let image = parser.parse_inline_image(&mut reader.session())?; - Ok(Some(PdfOperatorVariant::InlineImage(Arc::new(image)))) - } - - fn call(&self, backend: &mut T) -> Result<(), BackendError> { - backend.paint_inline_image(self) - } -} - #[cfg(test)] mod tests { - use std::collections::BTreeMap; + use std::{collections::BTreeMap, sync::Arc}; use pdf_image::InlineImage; use pdf_object_reader::{ @@ -75,8 +46,8 @@ mod tests { }; use crate::{ - operator_trait::PdfOperator, recording_pdf_operator_backend::{RecordedOperation, RecordingBackend}, + variants::PdfOperatorVariant, }; #[test] @@ -98,9 +69,10 @@ mod tests { &mut ObjectReader::new(PassthroughResolver).session(), ) .expect("unfiltered inline image should be constructed"); + let image = Arc::new(image); let mut backend = RecordingBackend::default(); - image + PdfOperatorVariant::InlineImage(Arc::clone(&image)) .call(&mut backend) .expect("inline image should dispatch"); diff --git a/crates/pdf-content-stream/src/content_stream.rs b/crates/pdf-content-stream/src/content_stream.rs index 770fdfdf..4c10ca56 100644 --- a/crates/pdf-content-stream/src/content_stream.rs +++ b/crates/pdf-content-stream/src/content_stream.rs @@ -74,7 +74,6 @@ mod tests { use pdf_content_stream_operators::{ PdfTextItem, - graphics_state_operators::SaveGraphicsState, recording_pdf_operator_backend::{RecordedOperation, RecordingBackend}, text_showing_operators::ShowTextArray, variants::PdfOperatorVariant, @@ -167,11 +166,11 @@ mod tests { assert_eq!(parsed.id, 0); assert!(matches!( parsed.operators.first(), - Some(PdfOperatorVariant::BeginCompatibility(_)) + Some(PdfOperatorVariant::BeginCompatibility) )); assert!(matches!( parsed.operators.get(1), - Some(PdfOperatorVariant::EndCompatibility(_)) + Some(PdfOperatorVariant::EndCompatibility) )); assert_eq!( recorded_operations(&parsed.operators), @@ -413,7 +412,7 @@ mod tests { #[test] fn content_stream_is_plain_data_after_parsing() { let content_stream = ContentStream { - operators: vec![PdfOperatorVariant::SaveGraphicsState(SaveGraphicsState)], + operators: vec![PdfOperatorVariant::SaveGraphicsState], id: 9, }; diff --git a/crates/pdf-content-stream/src/lib.rs b/crates/pdf-content-stream/src/lib.rs index 917d8f0c..fe98cea0 100644 --- a/crates/pdf-content-stream/src/lib.rs +++ b/crates/pdf-content-stream/src/lib.rs @@ -3,5 +3,3 @@ mod operator_stream_parser; pub use content_stream::ContentStream; pub use pdf_object_reader::ContentStreamIdAllocator; - -pub use pdf_content_stream_operators::operator_trait::PdfOperator; diff --git a/crates/pdf-content-stream/src/operator_stream_parser.rs b/crates/pdf-content-stream/src/operator_stream_parser.rs index 99ad39a3..a527a369 100644 --- a/crates/pdf-content-stream/src/operator_stream_parser.rs +++ b/crates/pdf-content-stream/src/operator_stream_parser.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use pdf_object_reader::{ ArrayContext, ReadSession, object_resolver::PassthroughResolver, object_variant::ObjectVariant, }; @@ -5,11 +7,7 @@ use pdf_parser::error::ParserError; use pdf_parser::parser::PdfParser; use pdf_tokenizer::error::TokenizerError; -use pdf_content_stream_operators::{ - error::PdfOperatorError, - operation_map::{OpDescriptor, get_operation_descriptor}, - variants::PdfOperatorVariant, -}; +use pdf_content_stream_operators::{error::PdfOperatorError, variants::PdfOperatorVariant}; /// Incrementally parses one content stream into PDF operators while reusing the /// same operand buffer across operator boundaries. @@ -103,44 +101,26 @@ impl<'a, 'out, 'session, 'reader> OperatorStreamParser<'a, 'out, 'session, 'read } } - /// Parses the next operator token and dispatches it through the operation map. + /// Parses the next operator token and reads it from the collected operands. + /// + /// An inline image (`BI`) is parsed from the stream bytes that follow it. + /// An unknown operator, or one whose operands do not decode, is dropped. fn parse_operator_from_stream(&mut self) -> Result<(), PdfOperatorError> { - let descriptor = { - let name = self.read_operator_name()?; - get_operation_descriptor(name) - }; - - let Some(descriptor) = descriptor else { - self.operands.clear(); - return Ok(()); - }; - - let parsed = if let Some(parse_hook) = descriptor.parse_hook { - if let Some(operator) = parse_hook(&mut self.parser)? { - Some(operator) - } else { - parse_operator(&descriptor, &self.operands, self.session) - } + let name = self.parser.read_operator_name()?; + let operator = if name == b"BI" { + let image = self.parser.parse_inline_image(self.session)?; + Some(PdfOperatorVariant::InlineImage(Arc::new(image))) } else { - parse_operator(&descriptor, &self.operands, self.session) + PdfOperatorVariant::read(name, &mut ArrayContext::new(&self.operands, self.session)) + .ok() + .flatten() }; - self.push_if_parsed(parsed); - self.operands.clear(); - Ok(()) - } - - /// Reads a single operator token and normalizes low-level tokenization - /// failures into content-stream operator errors. - fn read_operator_name(&mut self) -> Result<&[u8], PdfOperatorError> { - Ok(self.parser.read_operator_name()?) - } - - /// Appends a parsed operator when dispatch produced one. - fn push_if_parsed(&mut self, operator: Option) { if let Some(operator) = operator { self.out.push(operator); } + self.operands.clear(); + Ok(()) } /// Drops malformed syntax and guarantees the parser can try the next byte. @@ -185,30 +165,9 @@ fn is_truncated_operand_error(error: &PdfOperatorError) -> bool { } } -/// Parses a single operator from its operands, read in place. -/// -/// Validates the operand count before parsing. An operator whose operands do -/// not decode is dropped, like any other malformed item. -fn parse_operator( - descriptor: &OpDescriptor, - operands: &[ObjectVariant], - session: &mut ReadSession<'_, PassthroughResolver>, -) -> Option { - if let Some(required_count) = descriptor.operand_count - && operands.len() != required_count - { - return None; - } - - (descriptor.parser)(&mut ArrayContext::new(operands, session)).ok() -} - #[cfg(test)] #[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)] mod tests { - use pdf_content_stream_operators::{ - graphics_state_operators::RestoreGraphicsState, variants::PdfOperatorVariant, - }; use pdf_object_reader::ObjectReader; use super::*; @@ -251,20 +210,21 @@ mod tests { } #[test] - fn parse_operator_reads_operands_in_place() { - let descriptor = get_operation_descriptor(b"m").expect("operator descriptor should exist"); + fn read_operator_reads_operands_in_place() { let operands = vec![ObjectVariant::Integer(10), ObjectVariant::Integer(20)]; let reader = ObjectReader::new(PassthroughResolver); - let operator = parse_operator(&descriptor, &operands, &mut reader.session()); + let operator = PdfOperatorVariant::read( + b"m", + &mut ArrayContext::new(&operands, &mut reader.session()), + ); - assert!(matches!(operator, Some(PdfOperatorVariant::MoveTo(_)))); + assert!(matches!(operator, Ok(Some(PdfOperatorVariant::MoveTo(_))))); assert_eq!(operands.len(), 2); } #[test] - fn parse_operator_skips_malformed_fixed_arity_operator_without_consuming_buffer() { - let descriptor = get_operation_descriptor(b"m").expect("operator descriptor should exist"); + fn read_operator_skips_malformed_fixed_arity_operator_without_consuming_buffer() { let operands = vec![ ObjectVariant::Integer(1), ObjectVariant::Integer(2), @@ -272,9 +232,12 @@ mod tests { ]; let reader = ObjectReader::new(PassthroughResolver); - let operator = parse_operator(&descriptor, &operands, &mut reader.session()); + let operator = PdfOperatorVariant::read( + b"m", + &mut ArrayContext::new(&operands, &mut reader.session()), + ); - assert!(operator.is_none()); + assert!(matches!(operator, Ok(None))); assert_eq!(operands.len(), 3); assert_eq!(operands.first(), Some(&ObjectVariant::Integer(1))); } @@ -303,12 +266,12 @@ mod tests { ); assert!(matches!( parser.out.first(), - Some(PdfOperatorVariant::SaveGraphicsState(_)) + Some(PdfOperatorVariant::SaveGraphicsState) )); } #[test] - fn parse_next_item_uses_parse_hook_and_resumes_after_inline_image() { + fn parse_next_item_parses_inline_image_and_resumes_after_it() { let mut out = Vec::new(); let reader = ObjectReader::new(PassthroughResolver); let mut session = reader.session(); @@ -347,9 +310,7 @@ mod tests { ); assert!(matches!( parser.out.get(1), - Some(PdfOperatorVariant::RestoreGraphicsState( - RestoreGraphicsState - )) + Some(PdfOperatorVariant::RestoreGraphicsState) )); } @@ -393,7 +354,7 @@ mod tests { assert_eq!(parser.out.len(), 1); assert!(matches!( parser.out.first(), - Some(PdfOperatorVariant::SaveGraphicsState(_)) + Some(PdfOperatorVariant::SaveGraphicsState) )); } @@ -432,7 +393,7 @@ mod tests { assert_eq!(parser.out.len(), 1); assert!(matches!( parser.out.first(), - Some(PdfOperatorVariant::SaveGraphicsState(_)) + Some(PdfOperatorVariant::SaveGraphicsState) )); } @@ -456,7 +417,7 @@ mod tests { )); assert!(matches!( parser.out.get(1), - Some(PdfOperatorVariant::EndMarkedContent(_)) + Some(PdfOperatorVariant::EndMarkedContent) )); } @@ -478,7 +439,7 @@ mod tests { assert_eq!(parser.out.len(), 1); assert!(matches!( parser.out.first(), - Some(PdfOperatorVariant::SaveGraphicsState(_)) + Some(PdfOperatorVariant::SaveGraphicsState) )); } @@ -499,7 +460,7 @@ mod tests { assert_eq!(parser.out.len(), 1); assert!(matches!( parser.out.first(), - Some(PdfOperatorVariant::RestoreGraphicsState(_)) + Some(PdfOperatorVariant::RestoreGraphicsState) )); } @@ -519,7 +480,7 @@ mod tests { assert_eq!(parser.out.len(), 1); assert!(matches!( parser.out.first(), - Some(PdfOperatorVariant::SaveGraphicsState(_)) + Some(PdfOperatorVariant::SaveGraphicsState) )); } @@ -547,7 +508,7 @@ mod tests { )); assert!(matches!( parser.out.get(1), - Some(PdfOperatorVariant::RestoreGraphicsState(_)) + Some(PdfOperatorVariant::RestoreGraphicsState) )); } } diff --git a/crates/pdf-content-stream/tests/parser_api.rs b/crates/pdf-content-stream/tests/parser_api.rs index 3c9bc631..0cb1abab 100644 --- a/crates/pdf-content-stream/tests/parser_api.rs +++ b/crates/pdf-content-stream/tests/parser_api.rs @@ -4,7 +4,6 @@ use std::sync::Arc; use pdf_content_stream::ContentStream; use pdf_content_stream_operators::{ PdfTextItem, - graphics_state_operators::SaveGraphicsState, recording_pdf_operator_backend::{RecordedOperation, RecordingBackend}, text_showing_operators::ShowTextArray, variants::PdfOperatorVariant, @@ -95,11 +94,11 @@ fn content_stream_read_returns_expected_operators_and_assigns_ids() { assert_eq!(parsed.id, 0); assert!(matches!( parsed.operators.first(), - Some(PdfOperatorVariant::BeginCompatibility(_)) + Some(PdfOperatorVariant::BeginCompatibility) )); assert!(matches!( parsed.operators.get(1), - Some(PdfOperatorVariant::EndCompatibility(_)) + Some(PdfOperatorVariant::EndCompatibility) )); assert_eq!( recorded_operations(&parsed.operators), @@ -326,7 +325,7 @@ fn content_stream_read_skips_malformed_inline_image_and_consumes_an_id() { #[test] fn content_stream_is_plain_data_after_parsing() { let content_stream = ContentStream { - operators: vec![PdfOperatorVariant::SaveGraphicsState(SaveGraphicsState)], + operators: vec![PdfOperatorVariant::SaveGraphicsState], id: 9, };