diff --git a/Cargo.lock b/Cargo.lock index 8891cb5c..4475a802 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2191,7 +2191,6 @@ name = "pdf-content-stream-operators" version = "0.1.0" dependencies = [ "bytes", - "num-traits", "pdf-graphics", "pdf-image", "pdf-object-reader", diff --git a/crates/pdf-canvas/src/canvas_text_ops.rs b/crates/pdf-canvas/src/canvas_text_ops.rs index bb029c77..cbbe895a 100644 --- a/crates/pdf-canvas/src/canvas_text_ops.rs +++ b/crates/pdf-canvas/src/canvas_text_ops.rs @@ -239,8 +239,8 @@ impl PdfCanvas<'_, B> { let mut filter = |operator: &PdfOperatorVariant| { matches!( operator, - PdfOperatorVariant::SetCharWidth(_) - | PdfOperatorVariant::SetCharWidthAndBoundingBox(_) + PdfOperatorVariant::SetCharWidth { .. } + | PdfOperatorVariant::SetCharWidthAndBoundingBox { .. } ) }; diff --git a/crates/pdf-canvas/src/pdf_canvas.rs b/crates/pdf-canvas/src/pdf_canvas.rs index 89ee22a1..e68934ab 100644 --- a/crates/pdf-canvas/src/pdf_canvas.rs +++ b/crates/pdf-canvas/src/pdf_canvas.rs @@ -423,12 +423,12 @@ impl<'a, B: CanvasBackend> PdfCanvas<'a, B> { op, PdfOperatorVariant::SetNonStrokingColor(_) | PdfOperatorVariant::SetStrokingColor(_) - | PdfOperatorVariant::SetGrayFill(_) - | PdfOperatorVariant::SetGrayStroke(_) - | PdfOperatorVariant::SetRGBFill(_) - | PdfOperatorVariant::SetRGBStroke(_) - | PdfOperatorVariant::SetCMYKFill(_) - | PdfOperatorVariant::SetCMYKStroke(_) + | PdfOperatorVariant::SetGrayFill { .. } + | PdfOperatorVariant::SetGrayStroke { .. } + | PdfOperatorVariant::SetRGBFill { .. } + | PdfOperatorVariant::SetRGBStroke { .. } + | PdfOperatorVariant::SetCMYKFill { .. } + | PdfOperatorVariant::SetCMYKStroke { .. } ) }; diff --git a/crates/pdf-content-stream-operators/Cargo.toml b/crates/pdf-content-stream-operators/Cargo.toml index ce6d0522..83910634 100644 --- a/crates/pdf-content-stream-operators/Cargo.toml +++ b/crates/pdf-content-stream-operators/Cargo.toml @@ -15,4 +15,3 @@ pdf-object-reader = { path = "../pdf-object-reader" } pdf-image = { path = "../pdf-image" } pdf-graphics = { path = "../pdf-graphics" } thiserror = "2.0.12" -num-traits = "0.2.19" diff --git a/crates/pdf-content-stream-operators/src/color_operators.rs b/crates/pdf-content-stream-operators/src/color_operators.rs index 340e520b..f9a24f28 100644 --- a/crates/pdf-content-stream-operators/src/color_operators.rs +++ b/crates/pdf-content-stream-operators/src/color_operators.rs @@ -1,288 +1,22 @@ use std::sync::Arc; -use crate::{ - OperandContext, - error::PdfOperatorError, - pdf_operator_backend::{BackendError, PdfOperatorBackend}, -}; +use crate::{OperandContext, error::PdfOperatorError}; use pdf_object_reader::{ReadResult, pdf_string::PdfString}; -/// Sets the fill color to a grayscale value. -/// The gray level applies to subsequent fill operations. +/// The operands of a color-setting operator whose color space takes any number +/// of components (`SC`, `SCN`, `sc`, `scn`). #[derive(Debug, Clone, PartialEq)] -pub struct SetGrayFill { - /// The gray level, a value between 0.0 (black) and 1.0 (white). - gray: f32, -} - -impl SetGrayFill { - pub fn new(gray: f32) -> Self { - Self { gray } - } - - /// Returns the gray component. - pub const fn gray(&self) -> f32 { - self.gray - } - - /// Reads the operator from its operands. - pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { - let gray = operands.at(0)?; - Ok(Self::new(gray)) - } - - /// Dispatches the operator to `backend`. - pub(crate) fn call( - &self, - backend: &mut T, - ) -> Result<(), BackendError> { - backend.set_non_stroking_gray(self.gray) - } -} - -/// Sets the stroke color to a grayscale value. -/// The gray level applies to subsequent stroke operations. -#[derive(Debug, Clone, PartialEq)] -pub struct SetGrayStroke { - /// The gray level, a value between 0.0 (black) and 1.0 (white). - gray: f32, -} - -impl SetGrayStroke { - pub fn new(gray: f32) -> Self { - Self { gray } - } - - /// Reads the operator from its operands. - pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { - let gray = operands.at(0)?; - Ok(Self::new(gray)) - } - - /// Dispatches the operator to `backend`. - pub(crate) fn call( - &self, - backend: &mut T, - ) -> Result<(), BackendError> { - backend.set_stroking_gray(self.gray) - } -} - -/// Sets the fill color to an RGB (Red, Green, Blue) value. -/// The RGB color applies to subsequent fill operations. -#[derive(Debug, Clone, PartialEq)] -pub struct SetRGBFill { - /// The red component, a value between 0.0 and 1.0. - r: f32, - /// The green component, a value between 0.0 and 1.0. - g: f32, - /// The blue component, a value between 0.0 and 1.0. - b: f32, -} - -impl SetRGBFill { - pub fn new(r: f32, g: f32, b: f32) -> Self { - Self { r, g, b } - } - - /// Returns the RGB components. - pub const fn components(&self) -> [f32; 3] { - [self.r, self.g, self.b] - } - - /// Reads the operator from its operands. - pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { - let [r, g, b] = operands.read_array()?; - Ok(Self::new(r, g, b)) - } - - /// 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) - } -} - -/// Sets the stroke color to an RGB (Red, Green, Blue) value. -/// The RGB color applies to subsequent stroke operations. -#[derive(Debug, Clone, PartialEq)] -pub struct SetRGBStroke { - /// The red component, a value between 0.0 and 1.0. - r: f32, - /// The green component, a value between 0.0 and 1.0. - g: f32, - /// The blue component, a value between 0.0 and 1.0. - b: f32, -} - -impl SetRGBStroke { - pub fn new(r: f32, g: f32, b: f32) -> Self { - Self { r, g, b } - } - - /// Reads the operator from its operands. - pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { - let [r, g, b] = operands.read_array()?; - Ok(Self::new(r, g, b)) - } - - /// 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) - } -} - -/// Sets the fill color to a CMYK (Cyan, Magenta, Yellow, Black/Key) value. -/// The CMYK color applies to subsequent fill operations. -#[derive(Debug, Clone, PartialEq)] -pub struct SetCMYKFill { - /// The cyan component, a value between 0.0 and 1.0. - c: f32, - /// The magenta component, a value between 0.0 and 1.0. - m: f32, - /// The yellow component, a value between 0.0 and 1.0. - y: f32, - /// The black (key) component, a value between 0.0 and 1.0. - k: f32, -} - -impl SetCMYKFill { - pub fn new(c: f32, m: f32, y: f32, k: f32) -> Self { - Self { c, m, y, k } - } - - /// Returns the CMYK components. - pub const fn components(&self) -> [f32; 4] { - [self.c, self.m, self.y, self.k] - } - - /// Reads the operator from its operands. - pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { - let [c, m, y, k] = operands.read_array()?; - Ok(Self::new(c, m, y, k)) - } - - /// 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) - } -} - -/// Sets the stroke color to a CMYK (Cyan, Magenta, Yellow, Black/Key) value. -/// The CMYK color applies to subsequent stroke operations. -#[derive(Debug, Clone, PartialEq)] -pub struct SetCMYKStroke { - /// The cyan component, a value between 0.0 and 1.0. - c: f32, - /// The magenta component, a value between 0.0 and 1.0. - m: f32, - /// The yellow component, a value between 0.0 and 1.0. - y: f32, - /// The black (key) component, a value between 0.0 and 1.0. - k: f32, -} - -impl SetCMYKStroke { - pub fn new(c: f32, m: f32, y: f32, k: f32) -> Self { - Self { c, m, y, k } - } - - /// Reads the operator from its operands. - pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { - let [c, m, y, k] = operands.read_array()?; - Ok(Self::new(c, m, y, k)) - } - - /// 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) - } -} - -/// Sets the stroke color space value. -#[derive(Debug, Clone, PartialEq)] -pub struct SetStrokeColorSpace { - /// The name of the color space. - name: Arc<[u8]>, -} - -impl SetStrokeColorSpace { - pub fn new(name: Arc<[u8]>) -> Self { - Self { name } - } - - /// Reads the operator from its operands. - pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { - let name = operands.at::(0)?.bytes; - Ok(Self::new(name)) - } - - /// Dispatches the operator to `backend`. - pub(crate) fn call( - &self, - backend: &mut T, - ) -> Result<(), BackendError> { - backend.set_stroking_color_space(&self.name) - } -} - -/// Sets the non-stroking (fill) color space value. -#[derive(Debug, Clone, PartialEq)] -pub struct SetNonStrokingColorSpace { - /// The name of the color space. - name: Arc<[u8]>, -} - -impl SetNonStrokingColorSpace { - pub fn new(name: Arc<[u8]>) -> Self { - Self { name } - } - - /// Reads the operator from its operands. - pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { - let name = operands.at::(0)?.bytes; - Ok(Self::new(name)) - } - - /// Dispatches the operator to `backend`. - pub(crate) fn call( - &self, - backend: &mut T, - ) -> Result<(), BackendError> { - backend.set_non_stroking_color_space(&self.name) - } -} - -/// Sets the stroking color when the color space requires -/// multiple color components. -#[derive(Debug, Clone, PartialEq)] -pub struct SetStrokingColor { +pub struct ColorOperands { /// Color component values. - components: Vec, + pub components: Vec, /// An optional name of a pattern. - pattern: Option>, + pub pattern: Option>, } -impl SetStrokingColor { - pub fn new(components: Vec, pattern: Option>) -> Self { - Self { - components, - pattern, - } - } - - /// Reads the operator from its operands. +impl ColorOperands { + /// Reads the operands of `SCN` or `scn`: the color components, optionally + /// followed by a pattern name. + #[inline] pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { // The pattern name should come last, after the numeric color components let count = operands @@ -290,102 +24,38 @@ impl SetStrokingColor { .iter() .take_while(|value| !value.is_name()) .count(); - let values = (0..count) + let components = (0..count) .map(|index| operands.at(index)) .collect::>()?; let pattern = operands.at::(count).ok().map(|name| name.bytes); - 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)) - } - - /// 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 { - backend.set_stroking_color(&self.components) - } - } -} - -/// Sets the non-stroking color when the color space requires -/// multiple color components. -#[derive(Debug, Clone, PartialEq)] -pub struct SetNonStrokingColor { - /// Color component values. - components: Vec, - /// An optional name of a pattern. - pattern: Option>, -} - -impl SetNonStrokingColor { - pub fn new(components: Vec, pattern: Option>) -> Self { - Self { + Ok(Self { components, pattern, - } + }) } - /// 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() - .iter() - .take_while(|value| !value.is_name()) - .count(); - let values = (0..count) - .map(|index| operands.at(index)) - .collect::>()?; - let pattern = operands.at::(count).ok().map(|name| name.bytes); - - 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()?; + /// Reads the operands of `operator` (`SC` or `sc`), which takes at least one + /// color component and no pattern. + #[inline] + pub(crate) fn read_without_pattern( + operands: &mut OperandContext<'_, '_>, + operator: &'static str, + ) -> ReadResult { + let components: Vec = operands.read_all()?; - if values.is_empty() { + if components.is_empty() { return Err(PdfOperatorError::OperandCountMismatch { - operator: "sc".to_string(), + operator: operator.to_string(), expected: 1, actual: 0, } .into()); } - Ok(Self::new(values, None)) - } - - /// 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(Self { + components, + pattern: None, + }) } } diff --git a/crates/pdf-content-stream-operators/src/graphics_state_operators.rs b/crates/pdf-content-stream-operators/src/graphics_state_operators.rs deleted file mode 100644 index f4863202..00000000 --- a/crates/pdf-content-stream-operators/src/graphics_state_operators.rs +++ /dev/null @@ -1,275 +0,0 @@ -use std::sync::Arc; - -use num_traits::FromPrimitive; -use pdf_graphics::{LineCap, LineJoin, transform::Transform}; -use pdf_object_reader::{ReadResult, pdf_string::PdfString}; - -use crate::{ - OperandContext, - error::PdfOperatorError, - pdf_operator_backend::{BackendError, PdfOperatorBackend}, -}; - -/// Sets the line width for path stroking. -#[derive(Debug, Clone, PartialEq)] -pub struct SetLineWidth { - /// The new line width in user space units. - width: f32, -} - -impl SetLineWidth { - pub fn new(width: f32) -> Self { - Self { width } - } - - /// Reads the operator from its operands. - pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { - let width = operands.at(0)?; - Ok(Self::new(width)) - } - - /// Dispatches the operator to `backend`. - pub(crate) fn call( - &self, - backend: &mut T, - ) -> Result<(), BackendError> { - backend.set_line_width(self.width) - } -} - -/// Sets the line cap style for path stroking. -#[derive(Debug, Clone, PartialEq)] -pub struct SetLineCapStyle { - /// The line cap style to apply. - style: LineCap, -} - -impl SetLineCapStyle { - pub fn new(style: u8) -> Result { - match LineCap::from_u8(style) { - Some(style_enum) => Ok(Self { style: style_enum }), - None => Err(PdfOperatorError::InvalidOperandValue { - expected: "line cap style (0 butt, 1 round, 2 projecting square)", - found: style.to_string(), - }), - } - } - - /// Reads the operator from its operands. - pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { - let style = operands.at(0)?; - Ok(Self::new(style)?) - } - - /// Dispatches the operator to `backend`. - pub(crate) fn call( - &self, - backend: &mut T, - ) -> Result<(), BackendError> { - backend.set_line_cap(self.style) - } -} - -/// Sets the line join style for path stroking. -#[derive(Debug, Clone, PartialEq)] -pub struct SetLineJoinStyle { - /// The line join style to apply. - style: LineJoin, -} - -impl SetLineJoinStyle { - pub fn new(style: u8) -> Result { - match LineJoin::from_u8(style) { - Some(style_enum) => Ok(Self { style: style_enum }), - None => Err(PdfOperatorError::InvalidOperandValue { - expected: "line join style (0 miter, 1 round, 2 bevel)", - found: style.to_string(), - }), - } - } - - /// Reads the operator from its operands. - pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { - let style = operands.at(0)?; - Ok(Self::new(style)?) - } - - /// Dispatches the operator to `backend`. - pub(crate) fn call( - &self, - backend: &mut T, - ) -> Result<(), BackendError> { - backend.set_line_join(self.style) - } -} - -/// Sets the miter limit for path stroking. -#[derive(Debug, Clone, PartialEq)] -pub struct SetMiterLimit { - /// The new miter limit. This controls when a miter join is automatically - /// converted to a bevel join to prevent excessively long spikes. - limit: f32, -} - -impl SetMiterLimit { - pub fn new(limit: f32) -> Self { - Self { limit } - } - - /// Reads the operator from its operands. - pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { - let limit = operands.at(0)?; - Ok(Self::new(limit)) - } - - /// Dispatches the operator to `backend`. - pub(crate) fn call( - &self, - backend: &mut T, - ) -> Result<(), BackendError> { - backend.set_miter_limit(self.limit) - } -} - -/// Sets the dash pattern for path stroking. -#[derive(Debug, Clone, PartialEq)] -pub struct SetDashPattern { - /// An array of numbers specifying the lengths of alternating dashes and gaps. - array: Vec, - /// The phase, specifying the distance into the dash pattern at which to start. - phase: f32, -} - -impl SetDashPattern { - pub fn new(array: Vec, phase: f32) -> Self { - Self { array, phase } - } - - /// 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(Self::new(array, phase)) - } - - /// Dispatches the operator to `backend`. - pub(crate) fn call( - &self, - backend: &mut T, - ) -> Result<(), BackendError> { - backend.set_dash_pattern(&self.array, self.phase) - } -} - -/// Sets the flatness tolerance, controlling the accuracy with which curves -/// are approximated. (PDF operator `i`) -#[derive(Debug, Clone, PartialEq)] -pub struct SetFlatnessTolerance { - /// The flatness tolerance value. Typical values range from 1 to 100. - tolerance: f32, -} - -impl SetFlatnessTolerance { - pub fn new(tolerance: f32) -> Self { - Self { tolerance } - } - - /// Reads the operator from its operands. - pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { - let tolerance = operands.at(0)?; - Ok(Self::new(tolerance)) - } - - /// Dispatches the operator to `backend`. - pub(crate) fn call( - &self, - backend: &mut T, - ) -> Result<(), BackendError> { - backend.set_flatness_tolerance(self.tolerance) - } -} - -/// Sets the rendering intent for color reproduction. (PDF operator `ri`) -/// -/// The operand is a name object indicating the intent, e.g., -/// /AbsoluteColorimetric, /RelativeColorimetric, /Saturation, /Perceptual. -#[derive(Debug, Clone, PartialEq)] -pub struct SetRenderingIntent { - intent: Arc<[u8]>, -} - -impl SetRenderingIntent { - pub fn new(intent: Arc<[u8]>) -> Self { - Self { intent } - } - - /// Reads the operator from its operands. - pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { - let intent = operands.at::(0)?.bytes; - Ok(Self::new(intent)) - } - - /// Dispatches the operator to `backend`. - pub(crate) fn call( - &self, - backend: &mut T, - ) -> Result<(), BackendError> { - backend.set_rendering_intent(&self.intent) - } -} - -/// Modifies the current transformation matrix (CTM) by concatenating the specified matrix. -#[derive(Debug, Clone, PartialEq)] -pub struct ConcatMatrix { - /// The matrix to concatenate with the CTM. - matrix: Transform, -} - -impl ConcatMatrix { - pub fn new(matrix: [f32; 6]) -> Self { - Self { - matrix: Transform::from(matrix), - } - } - - /// Reads the operator from its operands. - pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { - Ok(Self::new(operands.read_array()?)) - } - - /// Dispatches the operator to `backend`. - pub(crate) fn call( - &self, - backend: &mut T, - ) -> Result<(), BackendError> { - backend.concat_matrix(&self.matrix) - } -} - -/// Sets multiple graphics state parameters from a named graphics state parameter dictionary. -/// The dictionary is expected to be in the resource dictionary. (PDF operator `gs`) -#[derive(Debug, Clone, PartialEq)] -pub struct SetGraphicsStateFromDict { - /// The name of the graphics state parameter dictionary. - dict_name: Arc<[u8]>, -} - -impl SetGraphicsStateFromDict { - pub fn new(dict_name: Arc<[u8]>) -> Self { - Self { dict_name } - } - - /// Reads the operator from its operands. - pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { - let dict_name = operands.at::(0)?.bytes; - Ok(Self::new(dict_name)) - } - - /// 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 d36f18cd..7b8e6f12 100644 --- a/crates/pdf-content-stream-operators/src/lib.rs +++ b/crates/pdf-content-stream-operators/src/lib.rs @@ -1,18 +1,10 @@ pub mod color_operators; pub mod error; -pub mod graphics_state_operators; pub mod marked_content_operators; pub mod variants; -pub mod path_operators; pub mod pdf_operator_backend; pub mod recording_pdf_operator_backend; -pub mod shadings_operators; -pub mod text_positioning_operators; -pub mod text_showing_operators; -pub mod text_state_operators; -pub mod type3_font_operators; -pub mod xobject_and_image_operators; extern crate alloc; use std::sync::Arc; 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 70b8beeb..5a565284 100644 --- a/crates/pdf-content-stream-operators/src/marked_content_operators.rs +++ b/crates/pdf-content-stream-operators/src/marked_content_operators.rs @@ -7,34 +7,6 @@ use crate::{ pdf_operator_backend::{BackendError, PdfOperatorBackend}, }; -/// Begins a marked-content sequence. -/// Marked-content sequences associate a tag with a sequence of content stream operations. -#[derive(Debug, Clone, PartialEq)] -pub struct BeginMarkedContent { - /// The tag indicating the role or nature of the marked-content sequence. - tag: Arc<[u8]>, -} - -impl BeginMarkedContent { - pub fn new(tag: Arc<[u8]>) -> Self { - Self { tag } - } - - /// Reads the operator from its operands. - pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { - let tag = operands.at::(0)?.bytes; - Ok(Self::new(tag)) - } - - /// Dispatches the operator to `backend`. - pub(crate) fn call( - &self, - backend: &mut T, - ) -> Result<(), BackendError> { - backend.begin_marked_content(&self.tag) - } -} - /// Begins a marked-content sequence with an associated property list. /// The property list can be either a name (referring to an entry in the Properties subdictionary /// of the current resource dictionary) or an inline dictionary. diff --git a/crates/pdf-content-stream-operators/src/path_operators.rs b/crates/pdf-content-stream-operators/src/path_operators.rs deleted file mode 100644 index c51bbf96..00000000 --- a/crates/pdf-content-stream-operators/src/path_operators.rs +++ /dev/null @@ -1,237 +0,0 @@ -use crate::{ - OperandContext, - pdf_operator_backend::{BackendError, PdfOperatorBackend}, -}; -use pdf_object_reader::ReadResult; - -/// Begins a new subpath by moving the current point to coordinates (x, y), omitting any connecting line segment. -/// If the `m` operator is the first operator in a path, it sets the current point. -#[derive(Debug, Clone, PartialEq)] -pub struct MoveTo { - /// The x-coordinate of the new current point. - x: f32, - /// The y-coordinate of the new current point. - y: f32, -} - -impl MoveTo { - pub fn new(x: f32, y: f32) -> Self { - Self { x, y } - } - - /// Reads the operator from its operands. - pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { - let [x, y] = operands.read_array()?; - Ok(Self::new(x, y)) - } - - /// Dispatches the operator to `backend`. - pub(crate) fn call( - &self, - backend: &mut T, - ) -> Result<(), BackendError> { - backend.move_to(self.x, self.y) - } -} - -/// Appends a straight line segment from the current point to the specified point (x, y). -/// The new current point becomes (x, y). -#[derive(Debug, Clone, PartialEq)] -pub struct LineTo { - /// The x-coordinate of the line segment's end point. - x: f32, - /// The y-coordinate of the line segment's end point. - y: f32, -} - -impl LineTo { - pub fn new(x: f32, y: f32) -> Self { - Self { x, y } - } - - /// Reads the operator from its operands. - pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { - let [x, y] = operands.read_array()?; - Ok(Self::new(x, y)) - } - - /// Dispatches the operator to `backend`. - pub(crate) fn call( - &self, - backend: &mut T, - ) -> Result<(), BackendError> { - backend.line_to(self.x, self.y) - } -} - -/// Appends a cubic Bézier curve to the current path. -/// The curve extends from the current point to (x3, y3), using (x1, y1) and (x2, y2) as Bézier control points. -/// The new current point becomes (x3, y3). -#[derive(Debug, Clone, PartialEq)] -pub struct CurveTo { - /// The x-coordinate of the first Bézier control point. - x1: f32, - /// The y-coordinate of the first Bézier control point. - y1: f32, - /// The x-coordinate of the second Bézier control point. - x2: f32, - /// The y-coordinate of the second Bézier control point. - y2: f32, - /// The x-coordinate of the curve's end point. - x3: f32, - /// The y-coordinate of the curve's end point. - y3: f32, -} - -impl CurveTo { - pub fn new(x1: f32, y1: f32, x2: f32, y2: f32, x3: f32, y3: f32) -> Self { - Self { - x1, - y1, - x2, - y2, - x3, - y3, - } - } - - /// 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(Self::new(x1, y1, x2, y2, x3, y3)) - } - - /// 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) - } -} - -/// Appends a cubic Bézier curve to the current path. -/// The current point is used as the first control point (x1, y1). -/// (x2, y2) is the second Bézier control point, and (x3, y3) is the end point of the curve. -/// The new current point becomes (x3, y3). -#[derive(Debug, Clone, PartialEq)] -pub struct CurveToV { - /// The x-coordinate of the second Bézier control point. - x2: f32, - /// The y-coordinate of the second Bézier control point. - y2: f32, - /// The x-coordinate of the curve's end point. - x3: f32, - /// The y-coordinate of the curve's end point. - y3: f32, -} - -impl CurveToV { - pub fn new(x2: f32, y2: f32, x3: f32, y3: f32) -> Self { - Self { x2, y2, x3, y3 } - } - - /// Reads the operator from its operands. - pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { - let [x2, y2, x3, y3] = operands.read_array()?; - Ok(Self::new(x2, y2, x3, y3)) - } - - /// 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) - } -} - -/// Appends a cubic Bézier curve to the current path. -/// (x1, y1) is the first Bézier control point. The second control point (x2, y2) is the same as the curve's end point (x3, y3). -/// The new current point becomes (x3, y3). -#[derive(Debug, Clone, PartialEq)] -pub struct CurveToY { - /// The x-coordinate of the first Bézier control point. - x1: f32, - /// The y-coordinate of the first Bézier control point. - y1: f32, - /// The x-coordinate of the curve's end point (and second control point). - x3: f32, - /// The y-coordinate of the curve's end point (and second control point). - y3: f32, -} - -impl CurveToY { - pub fn new(x1: f32, y1: f32, x3: f32, y3: f32) -> Self { - Self { x1, y1, x3, y3 } - } - - /// Reads the operator from its operands. - pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { - let [x1, y1, x3, y3] = operands.read_array()?; - Ok(Self::new(x1, y1, x3, y3)) - } - - /// 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) - } -} - -/// 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. -#[derive(Debug, Clone, PartialEq)] -pub struct Rectangle { - /// The x-coordinate of the bottom-left corner of the rectangle. - x: f32, - /// The y-coordinate of the bottom-left corner of the rectangle. - y: f32, - /// The width of the rectangle. - width: f32, - /// The height of the rectangle. - height: f32, -} - -impl Rectangle { - pub fn new(x: f32, y: f32, width: f32, height: f32) -> Self { - Self { - x, - y, - width, - height, - } - } - - /// Reads the operator from its operands. - pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { - let [x, y, width, height] = operands.read_array()?; - Ok(Self::new(x, y, width, height)) - } - - /// 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) - } -} - -#[cfg(test)] -#[allow(clippy::unwrap_used, clippy::expect_used)] -mod tests { - use crate::recording_pdf_operator_backend::RecordingBackend; - - use super::*; - - #[test] - fn test_path_operations() { - let mut backend = RecordingBackend::default(); - let a = MoveTo::new(0.0, 0.0); - a.call(&mut backend).unwrap(); - } -} diff --git a/crates/pdf-content-stream-operators/src/shadings_operators.rs b/crates/pdf-content-stream-operators/src/shadings_operators.rs deleted file mode 100644 index d48e6dc2..00000000 --- a/crates/pdf-content-stream-operators/src/shadings_operators.rs +++ /dev/null @@ -1,36 +0,0 @@ -use std::sync::Arc; - -use crate::{ - OperandContext, - pdf_operator_backend::{BackendError, PdfOperatorBackend}, -}; -use pdf_object_reader::{ReadResult, pdf_string::PdfString}; - -/// Paints the shape and color shading defined by a shading dictionary resource. -/// The `sh` operator takes one operand, the name of a shading dictionary. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct PaintShading { - /// The name of the shading dictionary resource from the Shading subdictionary - /// of the current resource dictionary. - name: Arc<[u8]>, -} - -impl PaintShading { - pub fn new(name: Arc<[u8]>) -> Self { - Self { name } - } - - /// Reads the operator from its operands. - pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { - let name = operands.at::(0)?.bytes; - Ok(Self::new(name)) - } - - /// 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_positioning_operators.rs b/crates/pdf-content-stream-operators/src/text_positioning_operators.rs deleted file mode 100644 index 6ed8dc36..00000000 --- a/crates/pdf-content-stream-operators/src/text_positioning_operators.rs +++ /dev/null @@ -1,103 +0,0 @@ -use pdf_graphics::transform::Transform; - -use crate::{ - OperandContext, - pdf_operator_backend::{BackendError, PdfOperatorBackend}, -}; -use pdf_object_reader::ReadResult; - -/// Moves to the start of the next line, offset from the start of the current line by (`tx`, `ty`). -/// `tx` and `ty` are numbers expressed in unscaled text space units. -/// More precisely, this operator sets the text line matrix, T_lm, to: -/// `[1 0 0 1 tx ty] * T_lm` -#[derive(Debug, Clone, PartialEq)] -pub struct MoveTextPosition { - /// The horizontal offset. - tx: f32, - /// The vertical offset. - ty: f32, -} - -impl MoveTextPosition { - pub fn new(tx: f32, ty: f32) -> Self { - Self { tx, ty } - } - - /// Reads the operator from its operands. - pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { - let [tx, ty] = operands.read_array()?; - Ok(Self::new(tx, ty)) - } - - /// Dispatches the operator to `backend`. - pub(crate) fn call( - &self, - backend: &mut T, - ) -> Result<(), BackendError> { - backend.move_text_position(self.tx, self.ty) - } -} - -/// Moves to the start of the next line, offset from the start of the current line by (`tx`, `ty`), -/// and sets the text leading, `TL`, to `-ty`. (PDF operator `TD`) -/// This has the same effect as `SetLeading { leading: -ty }` followed by `MoveTextPosition { tx, ty }`. -#[derive(Debug, Clone, PartialEq)] -pub struct MoveTextPositionAndSetLeading { - /// The horizontal offset. - tx: f32, - /// The vertical offset. The new text leading will be set to the negative of this value. - ty: f32, -} - -impl MoveTextPositionAndSetLeading { - pub fn new(tx: f32, ty: f32) -> Self { - Self { tx, ty } - } - - /// Reads the operator from its operands. - pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { - let [tx, ty] = operands.read_array()?; - Ok(Self::new(tx, ty)) - } - - /// 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) - } -} - -/// Sets the text matrix, `Tm`, and the text line matrix, `Tlm`. -#[derive(Debug, Clone, PartialEq)] -pub struct SetTextMatrix { - /// The text matrix. - matrix: Transform, -} - -impl SetTextMatrix { - pub fn new(matrix: [f32; 6]) -> Self { - Self { - matrix: Transform::from(matrix), - } - } - - /// Returns the text matrix. - pub const fn matrix(&self) -> &Transform { - &self.matrix - } - - /// Reads the operator from its operands. - pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { - Ok(Self::new(operands.read_array()?)) - } - - /// Dispatches the operator to `backend`. - pub(crate) fn call( - &self, - backend: &mut T, - ) -> Result<(), BackendError> { - backend.set_text_matrix(&self.matrix) - } -} diff --git a/crates/pdf-content-stream-operators/src/text_showing_operators.rs b/crates/pdf-content-stream-operators/src/text_showing_operators.rs deleted file mode 100644 index 1085c5f1..00000000 --- a/crates/pdf-content-stream-operators/src/text_showing_operators.rs +++ /dev/null @@ -1,188 +0,0 @@ -use crate::OperandContext; -use crate::PdfTextItem; -use crate::pdf_operator_backend::{BackendError, PdfOperatorBackend}; -use pdf_object_reader::{ReadResult, pdf_string::PdfString}; - -/// Shows a text string. -#[derive(Debug, Clone, PartialEq)] -pub struct ShowText { - /// The text item to be shown. Its bytes are typically encoded according to the font's - /// encoding. - text: PdfTextItem, -} - -impl ShowText { - pub fn new(text: PdfTextItem) -> Self { - Self { text } - } - - /// Reads the operator from its operands. - pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { - let text = operands.at::(0)?.bytes; - Ok(Self::new(PdfTextItem::Text(text))) - } - - /// Dispatches the operator to `backend`. - pub(crate) fn call( - &self, - backend: &mut T, - ) -> Result<(), BackendError> { - backend.show_text(&self.text) - } -} - -/// Moves to the next line and shows a text string. -#[derive(Debug, Clone, PartialEq)] -pub struct MoveNextLineShowText { - /// The text string to be shown. - text: PdfTextItem, -} - -impl MoveNextLineShowText { - pub fn new(text: PdfTextItem) -> Self { - Self { text } - } - - /// Reads the operator from its operands. - pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { - let text = operands.at::(0)?.bytes; - Ok(Self::new(PdfTextItem::Text(text))) - } - - /// 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) - } -} - -/// Sets the word and character spacing, moves to the next line, and shows a text string. -/// This is equivalent to `SetWordSpacing`, `SetCharacterSpacing`, and `MoveNextLineShowText`. -#[derive(Debug, Clone, PartialEq)] -pub struct SetSpacingMoveShowText { - /// The new word spacing to set before showing the text. - word_spacing: f32, - /// The new character spacing to set before showing the text. - char_spacing: f32, - /// The text string to be shown. - text: PdfTextItem, -} - -impl SetSpacingMoveShowText { - pub fn new(word_spacing: f32, char_spacing: f32, text: PdfTextItem) -> Self { - Self { - word_spacing, - char_spacing, - text, - } - } - - /// 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(Self::new( - word_spacing, - char_spacing, - PdfTextItem::Text(text), - )) - } - - /// 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) - } -} - -/// Shows one or more text strings, allowing individual glyph positioning. -/// The array can contain strings and numbers. Numbers specify an additional horizontal or vertical -/// displacement (depending on the writing mode) to apply before showing the next string or glyph. -#[derive(Debug, Clone, PartialEq)] -pub struct ShowTextArray { - /// A vector of `PdfTextItem`s, where each item is either a string to show - /// or a numeric adjustment for positioning. - elements: Vec, -} - -impl ShowTextArray { - pub fn new(elements: Vec) -> Self { - Self { elements } - } - - /// Reads the operator from its operands. - pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { - Ok(Self::new(operands.at(0)?)) - } - - /// Dispatches the operator to `backend`. - pub(crate) fn call( - &self, - backend: &mut T, - ) -> Result<(), BackendError> { - backend.show_text_with_glyph_positioning(&self.elements) - } -} - -#[cfg(test)] -#[allow(clippy::expect_used, clippy::panic)] -mod tests { - use std::sync::Arc; - - use pdf_object_reader::{ - ArrayContext, ObjectReader, object_resolver::PassthroughResolver, - object_variant::ObjectVariant, - }; - - use super::*; - - #[test] - fn show_text_moves_the_operand_byte_allocation() { - let text: Arc<[u8]> = Arc::from(b"text without a copy".to_vec()); - - let original_pointer = text.as_ptr(); - - let operands = [ObjectVariant::String( - pdf_object_reader::pdf_string::PdfString { - bytes: text, - kind: pdf_object_reader::string_kind::StringKind::Literal, - }, - )]; - let reader = ObjectReader::new(PassthroughResolver); - let mut session = reader.session(); - - let operator = ShowText::read(&mut ArrayContext::new(&operands, &mut session)) - .expect("text should parse"); - let PdfTextItem::Text(text) = operator.text else { - panic!("expected text item"); - }; - assert_eq!(&*text, b"text without a copy"); - assert_eq!(text.as_ptr(), original_pointer); - } - - #[test] - fn spacing_move_show_text_groups_numeric_prefix_before_text() { - let operands = [ - ObjectVariant::Real(1.5), - ObjectVariant::Integer(2), - pdf_object_reader::pdf_string::PdfString::from( - b"text", - pdf_object_reader::string_kind::StringKind::Literal, - ), - ]; - let reader = ObjectReader::new(PassthroughResolver); - let mut session = reader.session(); - - let operator = - SetSpacingMoveShowText::read(&mut ArrayContext::new(&operands, &mut session)) - .expect("mixed operands should parse successfully"); - 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 deleted file mode 100644 index 4207de39..00000000 --- a/crates/pdf-content-stream-operators/src/text_state_operators.rs +++ /dev/null @@ -1,230 +0,0 @@ -use std::sync::Arc; - -use crate::{ - OperandContext, - error::PdfOperatorError, - pdf_operator_backend::{BackendError, PdfOperatorBackend}, -}; -use num_traits::FromPrimitive; -use pdf_graphics::TextRenderingMode; -use pdf_object_reader::{ReadResult, pdf_string::PdfString}; - -/// Sets the character spacing, `Tc`, which is a number expressed in unscaled text space units. -#[derive(Debug, Clone, PartialEq)] -pub struct SetCharacterSpacing { - /// The character spacing. Added to the horizontal displacement otherwise produced by showing a glyph. - spacing: f32, -} - -impl SetCharacterSpacing { - pub fn new(spacing: f32) -> Self { - Self { spacing } - } - - /// Reads the operator from its operands. - pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { - let spacing = operands.at(0)?; - Ok(Self::new(spacing)) - } - - /// Dispatches the operator to `backend`. - pub(crate) fn call( - &self, - backend: &mut T, - ) -> Result<(), BackendError> { - backend.set_character_spacing(self.spacing) - } -} - -/// Sets the word spacing, `Tw`, which is a number expressed in unscaled text space units. -/// Word spacing is used by the `Tj`, `'`, and `"` operators. -#[derive(Debug, Clone, PartialEq)] -pub struct SetWordSpacing { - /// The word spacing. Added to the character spacing when the character is a space (char code 32). - spacing: f32, -} - -impl SetWordSpacing { - pub fn new(spacing: f32) -> Self { - Self { spacing } - } - - /// Reads the operator from its operands. - pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { - let spacing = operands.at(0)?; - Ok(Self::new(spacing)) - } - - /// Dispatches the operator to `backend`. - pub(crate) fn call( - &self, - backend: &mut T, - ) -> Result<(), BackendError> { - backend.set_word_spacing(self.spacing) - } -} - -/// Sets the horizontal scaling, `Tz`, which adjusts the width of glyphs by -/// stretching or compressing them horizontally. -#[derive(Debug, Clone, PartialEq)] -pub struct SetHorizontalScaling { - /// The horizontal scaling factor as a percentage (e.g., 100.0 for 100% - no scaling). - scale: f32, -} - -impl SetHorizontalScaling { - pub fn new(scale: f32) -> Self { - Self { scale } - } - - /// Reads the operator from its operands. - pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { - let scale = operands.at(0)?; - Ok(Self::new(scale)) - } - - /// Dispatches the operator to `backend`. - pub(crate) fn call( - &self, - backend: &mut T, - ) -> Result<(), BackendError> { - backend.set_horizontal_text_scaling(self.scale) - } -} - -/// Sets the text leading, `TL`, which is the vertical distance between the baselines of -/// adjacent lines of text. -#[derive(Debug, Clone, PartialEq)] -pub struct SetLeading { - /// The text leading, in unscaled text space units. - leading: f32, -} - -impl SetLeading { - /// Creates a leading operator with the given baseline distance. - pub fn new(leading: f32) -> Self { - Self { leading } - } - - /// Returns the baseline distance in unscaled text-space units. - pub const fn leading(&self) -> f32 { - self.leading - } - - /// Reads the operator from its operands. - pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { - let leading = operands.at(0)?; - Ok(Self::new(leading)) - } - - /// Dispatches the operator to `backend`. - pub(crate) fn call( - &self, - backend: &mut T, - ) -> Result<(), BackendError> { - backend.set_text_leading(self.leading) - } -} - -/// Sets the text font, `Tf`, to a font resource in the resource dictionary and the text -/// font size, `Tfs`, in unscaled text space units. -#[derive(Debug, Clone, PartialEq)] -pub struct SetFont { - /// The name of the font resource. - name: Arc<[u8]>, - /// The font size. - size: f32, -} - -impl SetFont { - pub fn new(name: Arc<[u8]>, size: f32) -> Self { - Self { name, size } - } - - /// Returns the font resource name. - pub fn name(&self) -> &[u8] { - &self.name - } - - /// Returns the font size. - pub const fn size(&self) -> f32 { - self.size - } - - /// 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(Self::new(name, size)) - } - - /// Dispatches the operator to `backend`. - pub(crate) fn call( - &self, - backend: &mut T, - ) -> Result<(), BackendError> { - backend.set_font_and_size(&self.name, self.size) - } -} - -/// Sets the text rendering mode, which determines whether text is filled, stroked, -/// used as a clipping path, or some combination. -#[derive(Debug, Clone, PartialEq)] -pub struct SetRenderingMode { - /// The rendering mode. - mode: TextRenderingMode, -} - -impl SetRenderingMode { - pub fn new(mode: u8) -> Result { - match TextRenderingMode::from_u8(mode) { - Some(mode) => Ok(Self { mode }), - None => Err(PdfOperatorError::InvalidOperandValue { - expected: "One of the valid text rendering modes (0-7)", - found: mode.to_string(), - }), - } - } - - /// Reads the operator from its operands. - pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { - let mode = operands.at(0)?; - Ok(Self::new(mode)?) - } - - /// Dispatches the operator to `backend`. - pub(crate) fn call( - &self, - backend: &mut T, - ) -> Result<(), BackendError> { - backend.set_text_rendering_mode(self.mode) - } -} - -/// Sets the text rise, `Ts`, which specifies the vertical distance -/// to shift the baseline of text relative to the current baseline. -#[derive(Debug, Clone, PartialEq)] -pub struct SetTextRise { - /// The text rise, in unscaled text space units. A positive value moves the baseline up. - rise: f32, -} - -impl SetTextRise { - pub fn new(rise: f32) -> Self { - Self { rise } - } - - /// Reads the operator from its operands. - pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { - let rise = operands.at(0)?; - Ok(Self::new(rise)) - } - - /// 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 deleted file mode 100644 index 4db3084c..00000000 --- a/crates/pdf-content-stream-operators/src/type3_font_operators.rs +++ /dev/null @@ -1,70 +0,0 @@ -use crate::{ - OperandContext, - pdf_operator_backend::{BackendError, PdfOperatorBackend}, -}; -use pdf_object_reader::ReadResult; - -#[derive(Debug, Clone, PartialEq, Default)] -pub struct SetCharWidthAndBoundingBox { - /// The x-component of the character width vector. - pub wx: f32, - /// The y-component of the character width vector. - wy: f32, - /// The x-coordinate of the lower-left corner of the character bounding box. - llx: f32, - /// The y-coordinate of the lower-left corner of the character bounding box. - lly: f32, - /// The x-coordinate of the upper-right corner of the character bounding box. - urx: f32, - /// The y-coordinate of the upper-right corner of the character bounding box. - ury: f32, -} - -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(Self { - wx, - wy, - llx, - lly, - urx, - ury, - }) - } - - /// Dispatches the operator to `backend`. - pub(crate) fn call( - &self, - _backend: &mut T, - ) -> Result<(), BackendError> { - Ok(()) - } -} - -#[derive(Debug, Clone, PartialEq, Default)] -pub struct SetCharWidth { - /// The x-component of the character width vector. - pub wx: f32, - /// The y-component of the character width vector. - wy: f32, -} - -impl SetCharWidth { - /// Reads the operator from its operands. - pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { - let [wx, wy] = operands.read_array()?; - - Ok(Self { wx, wy }) - } - - /// 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 054ade50..b68386f4 100644 --- a/crates/pdf-content-stream-operators/src/variants.rs +++ b/crates/pdf-content-stream-operators/src/variants.rs @@ -1,35 +1,95 @@ use std::sync::Arc; +use pdf_graphics::{LineCap, LineJoin, TextRenderingMode, transform::Transform}; use pdf_image::InlineImage; -use pdf_object_reader::ReadResult; +use pdf_object_reader::{ReadResult, pdf_string::PdfString}; use crate::{ - OperandContext, - color_operators::*, - graphics_state_operators::*, - marked_content_operators::*, - path_operators::*, + OperandContext, PdfTextItem, + color_operators::ColorOperands, + marked_content_operators::BeginMarkedContentWithProps, pdf_operator_backend::{BackendError, PdfOperatorBackend}, - shadings_operators::PaintShading, - text_positioning_operators::*, - text_showing_operators::*, - text_state_operators::*, - type3_font_operators::{SetCharWidth, SetCharWidthAndBoundingBox}, - xobject_and_image_operators::*, }; /// One PDF content-stream operation with its decoded operands. #[derive(Debug, Clone)] pub enum PdfOperatorVariant { - LineTo(LineTo), - MoveTo(MoveTo), - CurveTo(CurveTo), - CurveToV(CurveToV), - CurveToY(CurveToY), + /// Appends a straight line segment from the current point to the specified point (x, y). + /// The new current point becomes (x, y). + LineTo { + /// The x-coordinate of the line segment's end point. + x: f32, + /// The y-coordinate of the line segment's end point. + y: f32, + }, + /// Begins a new subpath by moving the current point to coordinates (x, y), omitting any connecting line segment. + /// If the `m` operator is the first operator in a path, it sets the current point. + MoveTo { + /// The x-coordinate of the new current point. + x: f32, + /// The y-coordinate of the new current point. + y: f32, + }, + /// Appends a cubic Bézier curve to the current path. + /// The curve extends from the current point to (x3, y3), using (x1, y1) and (x2, y2) as Bézier control points. + /// The new current point becomes (x3, y3). + CurveTo { + /// The x-coordinate of the first Bézier control point. + x1: f32, + /// The y-coordinate of the first Bézier control point. + y1: f32, + /// The x-coordinate of the second Bézier control point. + x2: f32, + /// The y-coordinate of the second Bézier control point. + y2: f32, + /// The x-coordinate of the curve's end point. + x3: f32, + /// The y-coordinate of the curve's end point. + y3: f32, + }, + /// Appends a cubic Bézier curve to the current path. + /// The current point is used as the first control point (x1, y1). + /// (x2, y2) is the second Bézier control point, and (x3, y3) is the end point of the curve. + /// The new current point becomes (x3, y3). + CurveToV { + /// The x-coordinate of the second Bézier control point. + x2: f32, + /// The y-coordinate of the second Bézier control point. + y2: f32, + /// The x-coordinate of the curve's end point. + x3: f32, + /// The y-coordinate of the curve's end point. + y3: f32, + }, + /// Appends a cubic Bézier curve to the current path. + /// (x1, y1) is the first Bézier control point. The second control point (x2, y2) is the same as the curve's end point (x3, y3). + /// The new current point becomes (x3, y3). + CurveToY { + /// The x-coordinate of the first Bézier control point. + x1: f32, + /// The y-coordinate of the first Bézier control point. + y1: f32, + /// The x-coordinate of the curve's end point (and second control point). + x3: f32, + /// The y-coordinate of the curve's end point (and second control point). + y3: f32, + }, /// 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), + /// 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. + Rectangle { + /// The x-coordinate of the bottom-left corner of the rectangle. + x: f32, + /// The y-coordinate of the bottom-left corner of the rectangle. + y: f32, + /// The width of the rectangle. + width: f32, + /// The height of the rectangle. + height: f32, + }, /// Strokes the current path. StrokePath, /// Closes the current subpath and then strokes the path. @@ -58,25 +118,117 @@ pub enum PdfOperatorVariant { 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), - SetRGBStroke(SetRGBStroke), - SetCMYKFill(SetCMYKFill), - SetCMYKStroke(SetCMYKStroke), - SetLineWidth(SetLineWidth), - SetLineCapStyle(SetLineCapStyle), - SetLineJoinStyle(SetLineJoinStyle), - SetMiterLimit(SetMiterLimit), - SetDashPattern(SetDashPattern), - SetFlatnessTolerance(SetFlatnessTolerance), - SetGraphicsStateFromDict(SetGraphicsStateFromDict), + /// Sets the fill color to a grayscale value. + /// The gray level applies to subsequent fill operations. + SetGrayFill { + /// The gray level, a value between 0.0 (black) and 1.0 (white). + gray: f32, + }, + /// Sets the stroke color to a grayscale value. + /// The gray level applies to subsequent stroke operations. + SetGrayStroke { + /// The gray level, a value between 0.0 (black) and 1.0 (white). + gray: f32, + }, + /// Sets the fill color to an RGB (Red, Green, Blue) value. + /// The RGB color applies to subsequent fill operations. + SetRGBFill { + /// The red component, a value between 0.0 and 1.0. + r: f32, + /// The green component, a value between 0.0 and 1.0. + g: f32, + /// The blue component, a value between 0.0 and 1.0. + b: f32, + }, + /// Sets the stroke color to an RGB (Red, Green, Blue) value. + /// The RGB color applies to subsequent stroke operations. + SetRGBStroke { + /// The red component, a value between 0.0 and 1.0. + r: f32, + /// The green component, a value between 0.0 and 1.0. + g: f32, + /// The blue component, a value between 0.0 and 1.0. + b: f32, + }, + /// Sets the fill color to a CMYK (Cyan, Magenta, Yellow, Black/Key) value. + /// The CMYK color applies to subsequent fill operations. + SetCMYKFill { + /// The cyan component, a value between 0.0 and 1.0. + c: f32, + /// The magenta component, a value between 0.0 and 1.0. + m: f32, + /// The yellow component, a value between 0.0 and 1.0. + y: f32, + /// The black (key) component, a value between 0.0 and 1.0. + k: f32, + }, + /// Sets the stroke color to a CMYK (Cyan, Magenta, Yellow, Black/Key) value. + /// The CMYK color applies to subsequent stroke operations. + SetCMYKStroke { + /// The cyan component, a value between 0.0 and 1.0. + c: f32, + /// The magenta component, a value between 0.0 and 1.0. + m: f32, + /// The yellow component, a value between 0.0 and 1.0. + y: f32, + /// The black (key) component, a value between 0.0 and 1.0. + k: f32, + }, + /// Sets the line width for path stroking. + SetLineWidth { + /// The new line width in user space units. + width: f32, + }, + /// Sets the line cap style for path stroking. + SetLineCapStyle { + /// The line cap style to apply. + style: LineCap, + }, + /// Sets the line join style for path stroking. + SetLineJoinStyle { + /// The line join style to apply. + style: LineJoin, + }, + /// Sets the miter limit for path stroking. + SetMiterLimit { + /// The new miter limit. This controls when a miter join is automatically + /// converted to a bevel join to prevent excessively long spikes. + limit: f32, + }, + /// Sets the dash pattern for path stroking. + SetDashPattern { + /// An array of numbers specifying the lengths of alternating dashes and gaps. + array: Vec, + /// The phase, specifying the distance into the dash pattern at which to start. + phase: f32, + }, + /// Sets the flatness tolerance, controlling the accuracy with which curves + /// are approximated. (PDF operator `i`) + SetFlatnessTolerance { + /// The flatness tolerance value. Typical values range from 1 to 100. + tolerance: f32, + }, + /// Sets multiple graphics state parameters from a named graphics state parameter dictionary. + /// The dictionary is expected to be in the resource dictionary. (PDF operator `gs`) + SetGraphicsStateFromDict { + /// The name of the graphics state parameter dictionary. + dict_name: Arc<[u8]>, + }, /// 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), + /// Modifies the current transformation matrix (CTM) by concatenating the specified matrix. + ConcatMatrix { + /// The matrix to concatenate with the CTM. + matrix: Transform, + }, + /// Begins a marked-content sequence. + /// Marked-content sequences associate a tag with a sequence of content stream operations. + BeginMarkedContent { + /// The tag indicating the role or nature of the marked-content sequence. + tag: Arc<[u8]>, + }, BeginMarkedContentWithProps(BeginMarkedContentWithProps), /// Ends a marked-content sequence begun by a `BMC` or `BDC` operator. EndMarkedContent, @@ -85,25 +237,112 @@ pub enum PdfOperatorVariant { BeginText, /// Ends a text object, discarding the text matrix and text line matrix. EndText, - MoveTextPosition(MoveTextPosition), - MoveTextPositionAndSetLeading(MoveTextPositionAndSetLeading), - SetTextMatrix(SetTextMatrix), + /// Moves to the start of the next line, offset from the start of the current line by (`tx`, `ty`). + /// `tx` and `ty` are numbers expressed in unscaled text space units. + /// More precisely, this operator sets the text line matrix, T_lm, to: + /// `[1 0 0 1 tx ty] * T_lm` + MoveTextPosition { + /// The horizontal offset. + tx: f32, + /// The vertical offset. + ty: f32, + }, + /// Moves to the start of the next line, offset from the start of the current line by (`tx`, `ty`), + /// and sets the text leading, `TL`, to `-ty`. (PDF operator `TD`) + /// This has the same effect as `SetLeading { leading: -ty }` followed by `MoveTextPosition { tx, ty }`. + MoveTextPositionAndSetLeading { + /// The horizontal offset. + tx: f32, + /// The vertical offset. The new text leading will be set to the negative of this value. + ty: f32, + }, + /// Sets the text matrix, `Tm`, and the text line matrix, `Tlm`. + SetTextMatrix { + /// The text matrix. + matrix: Transform, + }, /// 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), - ShowTextArray(ShowTextArray), - SetCharacterSpacing(SetCharacterSpacing), - SetWordSpacing(SetWordSpacing), - SetHorizontalScaling(SetHorizontalScaling), - SetLeading(SetLeading), - SetFont(SetFont), - SetRenderingMode(SetRenderingMode), - SetTextRise(SetTextRise), - InvokeXObject(InvokeXObject), + /// Shows a text string. + ShowText { + /// The text item to be shown. Its bytes are typically encoded according to the font's + /// encoding. + text: PdfTextItem, + }, + /// Moves to the next line and shows a text string. + MoveNextLineShowText { + /// The text string to be shown. + text: PdfTextItem, + }, + /// Sets the word and character spacing, moves to the next line, and shows a text string. + /// This is equivalent to `SetWordSpacing`, `SetCharacterSpacing`, and `MoveNextLineShowText`. + SetSpacingMoveShowText { + /// The new word spacing to set before showing the text. + word_spacing: f32, + /// The new character spacing to set before showing the text. + char_spacing: f32, + /// The text string to be shown. + text: PdfTextItem, + }, + /// Shows one or more text strings, allowing individual glyph positioning. + /// The array can contain strings and numbers. Numbers specify an additional horizontal or vertical + /// displacement (depending on the writing mode) to apply before showing the next string or glyph. + ShowTextArray { + /// A vector of `PdfTextItem`s, where each item is either a string to show + /// or a numeric adjustment for positioning. + elements: Vec, + }, + /// Sets the character spacing, `Tc`, which is a number expressed in unscaled text space units. + SetCharacterSpacing { + /// The character spacing. Added to the horizontal displacement otherwise produced by showing a glyph. + spacing: f32, + }, + /// Sets the word spacing, `Tw`, which is a number expressed in unscaled text space units. + /// Word spacing is used by the `Tj`, `'`, and `"` operators. + SetWordSpacing { + /// The word spacing. Added to the character spacing when the character is a space (char code 32). + spacing: f32, + }, + /// Sets the horizontal scaling, `Tz`, which adjusts the width of glyphs by + /// stretching or compressing them horizontally. + SetHorizontalScaling { + /// The horizontal scaling factor as a percentage (e.g., 100.0 for 100% - no scaling). + scale: f32, + }, + /// Sets the text leading, `TL`, which is the vertical distance between the baselines of + /// adjacent lines of text. + SetLeading { + /// The text leading, in unscaled text space units. + leading: f32, + }, + /// Sets the text font, `Tf`, to a font resource in the resource dictionary and the text + /// font size, `Tfs`, in unscaled text space units. + SetFont { + /// The name of the font resource. + name: Arc<[u8]>, + /// The font size. + size: f32, + }, + /// Sets the text rendering mode, which determines whether text is filled, stroked, + /// used as a clipping path, or some combination. + SetRenderingMode { + /// The rendering mode. + mode: TextRenderingMode, + }, + /// Sets the text rise, `Ts`, which specifies the vertical distance + /// to shift the baseline of text relative to the current baseline. + SetTextRise { + /// The text rise, in unscaled text space units. A positive value moves the baseline up. + rise: f32, + }, + /// Invokes a named XObject. + /// XObjects are external objects such as images or self-contained page descriptions (Form XObjects). + InvokeXObject { + /// The name of the XObject resource to invoke, as defined in the resource dictionary. + name: Arc<[u8]>, + }, /// Begins a compatibility section (BX). /// /// According to the PDF specification, a compatibility section allows @@ -116,14 +355,56 @@ pub enum PdfOperatorVariant { InlineImage(Arc), /// Ends a compatibility section (EX). EndCompatibility, - PaintShading(PaintShading), - SetCharWidthAndBoundingBox(SetCharWidthAndBoundingBox), - SetCharWidth(SetCharWidth), - SetRenderingIntent(SetRenderingIntent), - SetStrokeColorSpace(SetStrokeColorSpace), - SetNonStrokingColorSpace(SetNonStrokingColorSpace), - SetStrokingColor(SetStrokingColor), - SetNonStrokingColor(SetNonStrokingColor), + /// Paints the shape and color shading defined by a shading dictionary resource. + /// The `sh` operator takes one operand, the name of a shading dictionary. + PaintShading { + /// The name of the shading dictionary resource from the Shading subdictionary + /// of the current resource dictionary. + name: Arc<[u8]>, + }, + SetCharWidthAndBoundingBox { + /// The x-component of the character width vector. + wx: f32, + /// The y-component of the character width vector. + wy: f32, + /// The x-coordinate of the lower-left corner of the character bounding box. + llx: f32, + /// The y-coordinate of the lower-left corner of the character bounding box. + lly: f32, + /// The x-coordinate of the upper-right corner of the character bounding box. + urx: f32, + /// The y-coordinate of the upper-right corner of the character bounding box. + ury: f32, + }, + SetCharWidth { + /// The x-component of the character width vector. + wx: f32, + /// The y-component of the character width vector. + wy: f32, + }, + /// Sets the rendering intent for color reproduction. (PDF operator `ri`) + /// + /// The operand is a name object indicating the intent, e.g., + /// /AbsoluteColorimetric, /RelativeColorimetric, /Saturation, /Perceptual. + SetRenderingIntent { + intent: Arc<[u8]>, + }, + /// Sets the stroke color space value. + SetStrokeColorSpace { + /// The name of the color space. + name: Arc<[u8]>, + }, + /// Sets the non-stroking (fill) color space value. + SetNonStrokingColorSpace { + /// The name of the color space. + name: Arc<[u8]>, + }, + /// Sets the stroking color when the color space requires + /// multiple color components. + SetStrokingColor(ColorOperands), + /// Sets the non-stroking color when the color space requires + /// multiple color components. + SetNonStrokingColor(ColorOperands), } impl PdfOperatorVariant { @@ -134,81 +415,182 @@ impl PdfOperatorVariant { /// 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"\"", 3) => Self::SetSpacingMoveShowText { + word_spacing: operands.at(0)?, + char_spacing: operands.at(1)?, + text: PdfTextItem::Text(operands.at::(2)?.bytes), + }, + (b"'", 1) => Self::MoveNextLineShowText { + text: PdfTextItem::Text(operands.at::(0)?.bytes), + }, (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"BMC", 1) => Self::BeginMarkedContent { + tag: operands.at::(0)?.bytes, + }, (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"CS", 1) => Self::SetStrokeColorSpace { + name: operands.at::(0)?.bytes, + }, + (b"Do", 1) => Self::InvokeXObject { + name: operands.at::(0)?.bytes, + }, (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"G", 1) => Self::SetGrayStroke { + gray: operands.at(0)?, + }, + (b"J", 1) => Self::SetLineCapStyle { + style: operands.at(0)?, + }, + (b"K", 4) => operands + .read_array() + .map(|[c, m, y, k]| Self::SetCMYKStroke { c, m, y, k })?, + (b"M", 1) => Self::SetMiterLimit { + limit: operands.at(0)?, + }, (b"Q", 0) => Self::RestoreGraphicsState, - (b"RG", 3) => Self::SetRGBStroke(SetRGBStroke::read(operands)?), + (b"RG", 3) => operands + .read_array() + .map(|[r, g, b]| Self::SetRGBStroke { r, g, b })?, (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"SC", _) => { + Self::SetStrokingColor(ColorOperands::read_without_pattern(operands, "SC")?) } - (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"SCN", _) => Self::SetStrokingColor(ColorOperands::read(operands)?), + (b"T*", 0) => Self::MoveToNextLine, + (b"TD", 2) => operands + .read_array() + .map(|[tx, ty]| Self::MoveTextPositionAndSetLeading { tx, ty })?, + (b"TJ", 1) => Self::ShowTextArray { + elements: operands.at(0)?, + }, + (b"TL", 1) => Self::SetLeading { + leading: operands.at(0)?, + }, + (b"Tc", 1) => Self::SetCharacterSpacing { + spacing: operands.at(0)?, + }, + (b"Td", 2) => operands + .read_array() + .map(|[tx, ty]| Self::MoveTextPosition { tx, ty })?, + (b"Tf", 2) => Self::SetFont { + name: operands.at::(0)?.bytes, + size: operands.at(1)?, + }, + (b"Tj", 1) => Self::ShowText { + text: PdfTextItem::Text(operands.at::(0)?.bytes), + }, + (b"Tm", 6) => Self::SetTextMatrix { + matrix: Transform::from(operands.read_array::()?), + }, + (b"Tr", 1) => Self::SetRenderingMode { + mode: operands.at(0)?, + }, + (b"Ts", 1) => Self::SetTextRise { + rise: operands.at(0)?, + }, + (b"Tw", 1) => Self::SetWordSpacing { + spacing: operands.at(0)?, + }, + (b"Tz", 1) => Self::SetHorizontalScaling { + scale: operands.at(0)?, + }, (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"c", 6) => operands + .read_array() + .map(|[x1, y1, x2, y2, x3, y3]| Self::CurveTo { + x1, + y1, + x2, + y2, + x3, + y3, + })?, + (b"cm", 6) => Self::ConcatMatrix { + matrix: Transform::from(operands.read_array::()?), + }, + (b"cs", 1) => Self::SetNonStrokingColorSpace { + name: operands.at::(0)?.bytes, + }, + (b"d", 2) => Self::SetDashPattern { + array: operands.at(0)?, + phase: operands.at(1)?, + }, + (b"d0", 2) => operands + .read_array() + .map(|[wx, wy]| Self::SetCharWidth { wx, wy })?, + (b"d1", 6) => operands.read_array().map(|[wx, wy, llx, lly, urx, ury]| { + Self::SetCharWidthAndBoundingBox { + wx, + wy, + llx, + lly, + urx, + ury, + } + })?, (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"g", 1) => Self::SetGrayFill { + gray: operands.at(0)?, + }, + (b"gs", 1) => Self::SetGraphicsStateFromDict { + dict_name: operands.at::(0)?.bytes, + }, (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"i", 1) => Self::SetFlatnessTolerance { + tolerance: operands.at(0)?, + }, + (b"j", 1) => Self::SetLineJoinStyle { + style: operands.at(0)?, + }, + (b"k", 4) => operands + .read_array() + .map(|[c, m, y, k]| Self::SetCMYKFill { c, m, y, k })?, + (b"l", 2) => operands.read_array().map(|[x, y]| Self::LineTo { x, y })?, + (b"m", 2) => operands.read_array().map(|[x, y]| Self::MoveTo { x, y })?, (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"re", 4) => operands + .read_array() + .map(|[x, y, width, height]| Self::Rectangle { + x, + y, + width, + height, + })?, + (b"rg", 3) => operands + .read_array() + .map(|[r, g, b]| Self::SetRGBFill { r, g, b })?, + (b"ri", 1) => Self::SetRenderingIntent { + intent: operands.at::(0)?.bytes, + }, (b"s", 0) => Self::CloseStrokePath, (b"sc", _) => { - Self::SetNonStrokingColor(SetNonStrokingColor::read_without_pattern(operands)?) + Self::SetNonStrokingColor(ColorOperands::read_without_pattern(operands, "sc")?) } - (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)?), + (b"scn", _) => Self::SetNonStrokingColor(ColorOperands::read(operands)?), + (b"sh", 1) => Self::PaintShading { + name: operands.at::(0)?.bytes, + }, + (b"v", 4) => operands + .read_array() + .map(|[x2, y2, x3, y3]| Self::CurveToV { x2, y2, x3, y3 })?, + (b"w", 1) => Self::SetLineWidth { + width: operands.at(0)?, + }, + (b"y", 4) => operands + .read_array() + .map(|[x1, y1, x3, y3]| Self::CurveToY { x1, y1, x3, y3 })?, _ => return Ok(None), }; Ok(Some(operator)) @@ -217,13 +599,25 @@ impl PdfOperatorVariant { /// Dispatches the operation to `backend`. pub fn call(&self, backend: &mut T) -> Result<(), BackendError> { match self { - 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::LineTo { x, y } => backend.line_to(*x, *y), + Self::MoveTo { x, y } => backend.move_to(*x, *y), + Self::CurveTo { + x1, + y1, + x2, + y2, + x3, + y3, + } => backend.curve_to(*x1, *y1, *x2, *y2, *x3, *y3), + Self::CurveToV { x2, y2, x3, y3 } => backend.curve_to_v(*x2, *y2, *x3, *y3), + Self::CurveToY { x1, y1, x3, y3 } => backend.curve_to_y(*x1, *y1, *x3, *y3), Self::ClosePath => backend.close_path(), - Self::Rectangle(op) => op.call(backend), + Self::Rectangle { + x, + y, + width, + height, + } => backend.rectangle(*x, *y, *width, *height), Self::StrokePath => backend.stroke_path(), Self::CloseStrokePath => backend.close_and_stroke_path(), Self::FillPathNonZero => backend.fill_path_nonzero_winding(), @@ -237,54 +631,183 @@ impl PdfOperatorVariant { 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::SetGrayFill { gray } => backend.set_non_stroking_gray(*gray), + Self::SetGrayStroke { gray } => backend.set_stroking_gray(*gray), + Self::SetRGBFill { r, g, b } => backend.set_non_stroking_rgb(*r, *g, *b), + Self::SetRGBStroke { r, g, b } => backend.set_stroking_rgb(*r, *g, *b), + Self::SetCMYKFill { c, m, y, k } => backend.set_non_stroking_cmyk(*c, *m, *y, *k), + Self::SetCMYKStroke { c, m, y, k } => backend.set_stroking_cmyk(*c, *m, *y, *k), + Self::SetLineWidth { width } => backend.set_line_width(*width), + Self::SetLineCapStyle { style } => backend.set_line_cap(*style), + Self::SetLineJoinStyle { style } => backend.set_line_join(*style), + Self::SetMiterLimit { limit } => backend.set_miter_limit(*limit), + Self::SetDashPattern { array, phase } => backend.set_dash_pattern(array, *phase), + Self::SetFlatnessTolerance { tolerance } => backend.set_flatness_tolerance(*tolerance), + Self::SetGraphicsStateFromDict { dict_name } => { + backend.set_graphics_state_from_dict(dict_name) + } 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::ConcatMatrix { matrix } => backend.concat_matrix(matrix), + Self::BeginMarkedContent { tag } => backend.begin_marked_content(tag), 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::MoveTextPosition { tx, ty } => backend.move_text_position(*tx, *ty), + Self::MoveTextPositionAndSetLeading { tx, ty } => { + backend.move_text_position_and_set_leading(*tx, *ty) + } + Self::SetTextMatrix { matrix } => backend.set_text_matrix(matrix), 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::ShowText { text } => backend.show_text(text), + Self::MoveNextLineShowText { text } => backend.move_to_next_line_and_show_text(text), + Self::SetSpacingMoveShowText { + word_spacing, + char_spacing, + text, + } => backend.set_spacing_and_show_text(*word_spacing, *char_spacing, text), + Self::ShowTextArray { elements } => backend.show_text_with_glyph_positioning(elements), + Self::SetCharacterSpacing { spacing } => backend.set_character_spacing(*spacing), + Self::SetWordSpacing { spacing } => backend.set_word_spacing(*spacing), + Self::SetHorizontalScaling { scale } => backend.set_horizontal_text_scaling(*scale), + Self::SetLeading { leading } => backend.set_text_leading(*leading), + Self::SetFont { name, size } => backend.set_font_and_size(name, *size), + Self::SetRenderingMode { mode } => backend.set_text_rendering_mode(*mode), + Self::SetTextRise { rise } => backend.set_text_rise(*rise), + Self::InvokeXObject { name } => backend.invoke_xobject(name), 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), + Self::PaintShading { name } => backend.paint_shading(name), + Self::SetCharWidthAndBoundingBox { .. } => Ok(()), + Self::SetCharWidth { .. } => Ok(()), + Self::SetRenderingIntent { intent } => backend.set_rendering_intent(intent), + Self::SetStrokeColorSpace { name } => backend.set_stroking_color_space(name), + Self::SetNonStrokingColorSpace { name } => backend.set_non_stroking_color_space(name), + Self::SetStrokingColor(color) => match &color.pattern { + Some(pattern) => backend.set_stroking_color_extended(&color.components, pattern), + None => backend.set_stroking_color(&color.components), + }, + Self::SetNonStrokingColor(color) => match &color.pattern { + Some(pattern) => { + backend.set_non_stroking_color_extended(&color.components, pattern) + } + None => backend.set_non_stroking_color(&color.components), + }, } } } + +#[cfg(test)] +#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)] +mod tests { + use std::collections::BTreeMap; + + use pdf_object_reader::{ + ArrayContext, ObjectReader, dictionary::Dictionary, object_resolver::PassthroughResolver, + object_variant::ObjectVariant, + }; + + use crate::recording_pdf_operator_backend::{RecordedOperation, RecordingBackend}; + + use super::*; + + #[test] + fn test_path_operations() { + let mut backend = RecordingBackend::default(); + let a = PdfOperatorVariant::MoveTo { x: 0.0, y: 0.0 }; + a.call(&mut backend).unwrap(); + } + + #[test] + fn show_text_moves_the_operand_byte_allocation() { + let text: Arc<[u8]> = Arc::from(b"text without a copy".to_vec()); + + let original_pointer = text.as_ptr(); + + let operands = [ObjectVariant::String( + pdf_object_reader::pdf_string::PdfString { + bytes: text, + kind: pdf_object_reader::string_kind::StringKind::Literal, + }, + )]; + let reader = ObjectReader::new(PassthroughResolver); + let mut session = reader.session(); + + let operator = + PdfOperatorVariant::read(b"Tj", &mut ArrayContext::new(&operands, &mut session)) + .expect("text should parse"); + let Some(PdfOperatorVariant::ShowText { + text: PdfTextItem::Text(text), + }) = operator + else { + panic!("expected a ShowText text item"); + }; + assert_eq!(&*text, b"text without a copy"); + assert_eq!(text.as_ptr(), original_pointer); + } + + #[test] + fn spacing_move_show_text_groups_numeric_prefix_before_text() { + let operands = [ + ObjectVariant::Real(1.5), + ObjectVariant::Integer(2), + pdf_object_reader::pdf_string::PdfString::from( + b"text", + pdf_object_reader::string_kind::StringKind::Literal, + ), + ]; + let reader = ObjectReader::new(PassthroughResolver); + let mut session = reader.session(); + + let operator = + PdfOperatorVariant::read(b"\"", &mut ArrayContext::new(&operands, &mut session)) + .expect("mixed operands should parse successfully"); + let Some(PdfOperatorVariant::SetSpacingMoveShowText { + word_spacing, + char_spacing, + text, + }) = operator + else { + panic!("expected SetSpacingMoveShowText"); + }; + assert_eq!(word_spacing, 1.5); + assert_eq!(char_spacing, 2.0); + assert_eq!(text, PdfTextItem::Text(Arc::from(&b"text"[..]))); + } + + #[test] + fn inline_image_call_dispatches_to_backend_hook() { + let image = InlineImage::new( + Dictionary::new(BTreeMap::from([ + (Vec::from(b"BPC"), ObjectVariant::Integer(8)), + ( + Vec::from(b"CS"), + pdf_object_reader::pdf_string::PdfString::from( + b"G", + pdf_object_reader::string_kind::StringKind::Name, + ), + ), + (Vec::from(b"H"), ObjectVariant::Integer(1)), + (Vec::from(b"W"), ObjectVariant::Integer(2)), + ])), + vec![0x01, 0x02], + &mut ObjectReader::new(PassthroughResolver).session(), + ) + .expect("unfiltered inline image should be constructed"); + let image = Arc::new(image); + let mut backend = RecordingBackend::default(); + + PdfOperatorVariant::InlineImage(Arc::clone(&image)) + .call(&mut backend) + .expect("inline image should dispatch"); + + assert_eq!( + backend.operations, + vec![RecordedOperation::PaintInlineImage { + data: image.shared_data() + }] + ); + } +} 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 deleted file mode 100644 index 8d4f2a5f..00000000 --- a/crates/pdf-content-stream-operators/src/xobject_and_image_operators.rs +++ /dev/null @@ -1,86 +0,0 @@ -use std::sync::Arc; - -use pdf_object_reader::{ReadResult, pdf_string::PdfString}; - -use crate::{ - OperandContext, - pdf_operator_backend::{BackendError, PdfOperatorBackend}, -}; - -/// Invokes a named XObject. -/// XObjects are external objects such as images or self-contained page descriptions (Form XObjects). -#[derive(Debug, Clone, PartialEq)] -pub struct InvokeXObject { - /// The name of the XObject resource to invoke, as defined in the resource dictionary. - name: Arc<[u8]>, -} - -impl InvokeXObject { - pub fn new(name: Arc<[u8]>) -> Self { - Self { name } - } - - /// Reads the operator from its operands. - pub(crate) fn read(operands: &mut OperandContext<'_, '_>) -> ReadResult { - let name = operands.at::(0)?.bytes; - Ok(Self::new(name)) - } - - /// Dispatches the operator to `backend`. - pub(crate) fn call( - &self, - backend: &mut T, - ) -> Result<(), BackendError> { - backend.invoke_xobject(&self.name) - } -} - -#[cfg(test)] -mod tests { - use std::{collections::BTreeMap, sync::Arc}; - - use pdf_image::InlineImage; - use pdf_object_reader::{ - ObjectReader, dictionary::Dictionary, object_resolver::PassthroughResolver, - object_variant::ObjectVariant, - }; - - use crate::{ - recording_pdf_operator_backend::{RecordedOperation, RecordingBackend}, - variants::PdfOperatorVariant, - }; - - #[test] - fn inline_image_call_dispatches_to_backend_hook() { - let image = InlineImage::new( - Dictionary::new(BTreeMap::from([ - (Vec::from(b"BPC"), ObjectVariant::Integer(8)), - ( - Vec::from(b"CS"), - pdf_object_reader::pdf_string::PdfString::from( - b"G", - pdf_object_reader::string_kind::StringKind::Name, - ), - ), - (Vec::from(b"H"), ObjectVariant::Integer(1)), - (Vec::from(b"W"), ObjectVariant::Integer(2)), - ])), - vec![0x01, 0x02], - &mut ObjectReader::new(PassthroughResolver).session(), - ) - .expect("unfiltered inline image should be constructed"); - let image = Arc::new(image); - let mut backend = RecordingBackend::default(); - - PdfOperatorVariant::InlineImage(Arc::clone(&image)) - .call(&mut backend) - .expect("inline image should dispatch"); - - assert_eq!( - backend.operations, - vec![RecordedOperation::PaintInlineImage { - data: image.shared_data() - }] - ); - } -} diff --git a/crates/pdf-content-stream/src/content_stream.rs b/crates/pdf-content-stream/src/content_stream.rs index 4c10ca56..ef9a2cc1 100644 --- a/crates/pdf-content-stream/src/content_stream.rs +++ b/crates/pdf-content-stream/src/content_stream.rs @@ -75,7 +75,6 @@ mod tests { use pdf_content_stream_operators::{ PdfTextItem, recording_pdf_operator_backend::{RecordedOperation, RecordingBackend}, - text_showing_operators::ShowTextArray, variants::PdfOperatorVariant, }; use pdf_object_reader::{ @@ -194,9 +193,9 @@ mod tests { assert!(matches!( parsed.operators.get(3), - Some(PdfOperatorVariant::ShowTextArray(op)) - if op - == &ShowTextArray::new(vec![ + Some(PdfOperatorVariant::ShowTextArray { elements }) + if elements + == &vec![ PdfTextItem::Text(Arc::from(&b"e"[..])), PdfTextItem::Adjustment(-4.0), PdfTextItem::Text(Arc::from(&b"x"[..])), @@ -218,7 +217,7 @@ mod tests { PdfTextItem::Text(Arc::from(&b"l"[..])), PdfTextItem::Adjustment(3.0), PdfTextItem::Text(Arc::from(&b"y"[..])), - ]) + ] )); } diff --git a/crates/pdf-content-stream/src/operator_stream_parser.rs b/crates/pdf-content-stream/src/operator_stream_parser.rs index a527a369..8fdb7f1b 100644 --- a/crates/pdf-content-stream/src/operator_stream_parser.rs +++ b/crates/pdf-content-stream/src/operator_stream_parser.rs @@ -219,7 +219,10 @@ mod tests { &mut ArrayContext::new(&operands, &mut reader.session()), ); - assert!(matches!(operator, Ok(Some(PdfOperatorVariant::MoveTo(_))))); + assert!(matches!( + operator, + Ok(Some(PdfOperatorVariant::MoveTo { .. })) + )); assert_eq!(operands.len(), 2); } @@ -330,11 +333,11 @@ mod tests { assert_eq!(parser.out.len(), 2); assert!(matches!( parser.out.first(), - Some(PdfOperatorVariant::SetLineJoinStyle(_)) + Some(PdfOperatorVariant::SetLineJoinStyle { .. }) )); assert!(matches!( parser.out.get(1), - Some(PdfOperatorVariant::SetLineCapStyle(_)) + Some(PdfOperatorVariant::SetLineCapStyle { .. }) )); } @@ -374,7 +377,7 @@ mod tests { assert_eq!(parser.out.len(), 1); assert!(matches!( parser.out.first(), - Some(PdfOperatorVariant::SetLineCapStyle(_)) + Some(PdfOperatorVariant::SetLineCapStyle { .. }) )); } diff --git a/crates/pdf-content-stream/tests/parser_api.rs b/crates/pdf-content-stream/tests/parser_api.rs index 0cb1abab..27d4dab1 100644 --- a/crates/pdf-content-stream/tests/parser_api.rs +++ b/crates/pdf-content-stream/tests/parser_api.rs @@ -5,7 +5,6 @@ use pdf_content_stream::ContentStream; use pdf_content_stream_operators::{ PdfTextItem, recording_pdf_operator_backend::{RecordedOperation, RecordingBackend}, - text_showing_operators::ShowTextArray, variants::PdfOperatorVariant, }; use pdf_object_reader::{ @@ -122,9 +121,9 @@ fn content_stream_read_handles_bare_sign_text_array_adjustment() { assert!(matches!( parsed.operators.get(3), - Some(PdfOperatorVariant::ShowTextArray(op)) - if op - == &ShowTextArray::new(vec![ + Some(PdfOperatorVariant::ShowTextArray { elements }) + if elements + == &vec![ PdfTextItem::Text(Arc::from(&b"e"[..])), PdfTextItem::Adjustment(-4.0), PdfTextItem::Text(Arc::from(&b"x"[..])), @@ -146,7 +145,7 @@ fn content_stream_read_handles_bare_sign_text_array_adjustment() { PdfTextItem::Text(Arc::from(&b"l"[..])), PdfTextItem::Adjustment(3.0), PdfTextItem::Text(Arc::from(&b"y"[..])), - ]) + ] )); } diff --git a/crates/pdf-object-reader/src/decode.rs b/crates/pdf-object-reader/src/decode.rs index 121f7c11..63440ddc 100644 --- a/crates/pdf-object-reader/src/decode.rs +++ b/crates/pdf-object-reader/src/decode.rs @@ -1,7 +1,11 @@ //! Extensible typed decoding with one object-bound context per invocation. +use num_traits::FromPrimitive; +use pdf_graphics::{LineCap, LineJoin, TextRenderingMode}; + use crate::context::ObjectContext; use crate::error::{ObjectReadError, ReadResult}; +use crate::object_error::ObjectError; use crate::object_kind::ObjectKind; use crate::object_variant::ObjectVariant; use crate::pdf_array::PdfArray; @@ -181,6 +185,31 @@ macro_rules! number_decoder { number_decoder!(f32, i32, i64, u8, u16, u32, usize); +macro_rules! enumerated_number_decoder { + ($($type:ty => $expected:literal),+ $(,)?) => { + $( + impl FromPdfObject for $type { + /// Decodes the number that names one value of this parameter. + fn from_pdf_object( + context: ObjectContext<'_, impl ObjectAccess + ?Sized>, + ) -> ReadResult { + let found = context.object().try_number::(context.source())?; + Ok(Self::from_i32(found).ok_or(ObjectError::InvalidEnumValue { + expected: $expected, + found, + })?) + } + } + )+ + }; +} + +enumerated_number_decoder!( + LineCap => "a line cap style (0 butt, 1 round, 2 projecting square)", + LineJoin => "a line join style (0 miter, 1 round, 2 bevel)", + TextRenderingMode => "a text rendering mode (0 to 7)", +); + impl FromPdfObject for bool { fn from_pdf_object(context: ObjectContext<'_, impl ObjectAccess + ?Sized>) -> ReadResult { match context.object() { diff --git a/crates/pdf-object-reader/src/object_error.rs b/crates/pdf-object-reader/src/object_error.rs index 78fe2450..7898da43 100644 --- a/crates/pdf-object-reader/src/object_error.rs +++ b/crates/pdf-object-reader/src/object_error.rs @@ -29,6 +29,9 @@ pub enum ObjectError { FailedResolveStreamObject { resolved_type: &'static str }, #[error("Invalid array length: expected {expected}, but found {found}")] InvalidArrayLength { expected: usize, found: usize }, + /// Indicates a number that does not name any value of an enumerated parameter. + #[error("Invalid value {found}; expected {expected}")] + InvalidEnumValue { expected: &'static str, found: i32 }, /// Indicates that an attempt was made to insert an object into a collection /// but the object does not have an associated object number (is not a reference, /// indirect object, or stream with object number). diff --git a/crates/pdf-resources/src/error.rs b/crates/pdf-resources/src/error.rs index 8b9ec524..ceb8701c 100644 --- a/crates/pdf-resources/src/error.rs +++ b/crates/pdf-resources/src/error.rs @@ -33,12 +33,6 @@ pub enum PdfPagesError { InvalidExtGStateArrayLength { entry: &'static str, found: usize }, #[error("invalid /ExtGState entry '/D': {0}")] InvalidExtGStateDashPattern(#[from] DashPatternError), - #[error("invalid /ExtGState entry '/LC': unsupported line cap value {0} (expected 0, 1, or 2)")] - InvalidExtGStateLineCap(i32), - #[error( - "invalid /ExtGState entry '/LJ': unsupported line join value {0} (expected 0, 1, or 2)" - )] - InvalidExtGStateLineJoin(i32), #[error( "invalid /ExtGState entry '/SMask': expected a soft mask dictionary or the name 'None'" )] diff --git a/crates/pdf-resources/src/external_graphics_state.rs b/crates/pdf-resources/src/external_graphics_state.rs index b4cfd84f..27e50421 100644 --- a/crates/pdf-resources/src/external_graphics_state.rs +++ b/crates/pdf-resources/src/external_graphics_state.rs @@ -4,7 +4,6 @@ use pdf_object_reader::{ }; use crate::{error::PdfPagesError, resource::Resource, soft_mask::SoftMask}; -use num_traits::FromPrimitive; use pdf_graphics::{BlendMode, DashPattern, LineCap, LineJoin}; /// One parsed entry of a graphics state parameter dictionary (`ExtGState`). @@ -138,18 +137,8 @@ fn parse_entry( } let parsed = match name { b"LW" => ExternalGraphicsStateKey::LineWidth(context.read(value)?), - b"LC" => { - let cap = context.read::(value)?; - ExternalGraphicsStateKey::LineCap( - LineCap::from_i32(cap).ok_or(PdfPagesError::InvalidExtGStateLineCap(cap))?, - ) - } - b"LJ" => { - let join = context.read::(value)?; - ExternalGraphicsStateKey::LineJoin( - LineJoin::from_i32(join).ok_or(PdfPagesError::InvalidExtGStateLineJoin(join))?, - ) - } + b"LC" => ExternalGraphicsStateKey::LineCap(context.read(value)?), + b"LJ" => ExternalGraphicsStateKey::LineJoin(context.read(value)?), b"ML" => ExternalGraphicsStateKey::MiterLimit(context.read(value)?), b"D" => match context.read::(value)? { DashEntry(Some(pattern)) => ExternalGraphicsStateKey::DashPattern(pattern),