From ae19a98b4302d8c9790f0193bdb1a437c0ed777f Mon Sep 17 00:00:00 2001 From: Velli20 Date: Wed, 21 Oct 2026 22:38:30 +0300 Subject: [PATCH] Dispatch content stream operators by matching on PdfOperatorVariant The operator backend traits (twelve per-category traits plus the PdfOperatorBackend supertrait) had one production implementor, PdfCanvas, and a test-only recorder that rebuilt a near-copy of PdfOperatorVariant. Nothing was generic over a single category trait, so the layer only added indirection over an enum that already describes every operator. PdfCanvas now dispatches through apply_operator, and its handlers are inherent methods. Tests check the parsed operators directly instead of replaying them through the recorder. Co-Authored-By: Claude Opus 5.5 --- AGENTS.md | 2 +- crates/pdf-canvas/src/canvas_clip_ops.rs | 10 +- crates/pdf-canvas/src/canvas_color_ops.rs | 68 +- .../src/canvas_external_object_ops.rs | 8 +- .../src/canvas_graphics_state_ops.rs | 43 +- .../src/canvas_marked_content_ops.rs | 28 +- .../src/canvas_operator_dispatch.rs | 131 +++ crates/pdf-canvas/src/canvas_path_ops.rs | 75 +- crates/pdf-canvas/src/canvas_text_ops.rs | 84 +- crates/pdf-canvas/src/lib.rs | 7 +- crates/pdf-canvas/src/pdf_canvas.rs | 3 +- crates/pdf-canvas/src/shading.rs | 6 +- .../pdf-content-stream-operators/src/lib.rs | 3 - .../src/marked_content_operators.rs | 20 +- .../src/pdf_operator_backend.rs | 849 ------------------ .../src/recording_pdf_operator_backend.rs | 752 ---------------- .../src/variants.rs | 153 +--- .../pdf-content-stream/src/content_stream.rs | 90 +- crates/pdf-content-stream/tests/parser_api.rs | 87 +- 19 files changed, 374 insertions(+), 2045 deletions(-) create mode 100644 crates/pdf-canvas/src/canvas_operator_dispatch.rs delete mode 100644 crates/pdf-content-stream-operators/src/pdf_operator_backend.rs delete mode 100644 crates/pdf-content-stream-operators/src/recording_pdf_operator_backend.rs diff --git a/AGENTS.md b/AGENTS.md index e044d122..e8b860c7 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -60,7 +60,7 @@ PDF bytes → pdf-tokenizer → pdf-parser → pdf-object → pdf-document Key architectural traits: - **`CanvasBackend`** (in `pdf-canvas`): Abstracts rendering. Skia and FemtoVG are current implementations. -- **`PdfOperatorBackend`**: Content stream operators dispatched via traits, enabling substitution with analyzers/exporters without modifying core logic. +- **`PdfOperatorVariant`** (in `pdf-content-stream-operators`): A closed enum of parsed content stream operators. Consumers (the canvas, analyzers, exporters) match on it directly; `PdfCanvas::apply_operator` dispatches it for rendering. Supporting crates: - `pdf-font`: Font decoding (Type1/TrueType/Type3), isolated from rendering diff --git a/crates/pdf-canvas/src/canvas_clip_ops.rs b/crates/pdf-canvas/src/canvas_clip_ops.rs index 5dcde4ad..ac02defc 100644 --- a/crates/pdf-canvas/src/canvas_clip_ops.rs +++ b/crates/pdf-canvas/src/canvas_clip_ops.rs @@ -1,15 +1,15 @@ -use pdf_content_stream_operators::pdf_operator_backend::ClippingPathOps; use pdf_graphics::PathFillType; use crate::{canvas_backend::CanvasBackend, error::PdfCanvasError, pdf_canvas::PdfCanvas}; -impl ClippingPathOps for PdfCanvas<'_, B> { - type ErrorType = PdfCanvasError; - fn clip_path_nonzero_winding(&mut self) -> Result<(), Self::ErrorType> { +impl PdfCanvas<'_, B> { + /// Handles `W`: marks the current path as a nonzero-winding clip. + pub(crate) fn clip_path_nonzero_winding(&mut self) -> Result<(), PdfCanvasError> { self.mark_clip_path(PathFillType::Winding) } - fn clip_path_even_odd(&mut self) -> Result<(), Self::ErrorType> { + /// Handles `W*`: marks the current path as an even-odd clip. + pub(crate) fn clip_path_even_odd(&mut self) -> Result<(), PdfCanvasError> { self.mark_clip_path(PathFillType::EvenOdd) } } diff --git a/crates/pdf-canvas/src/canvas_color_ops.rs b/crates/pdf-canvas/src/canvas_color_ops.rs index be38e4fa..cd71cb24 100644 --- a/crates/pdf-canvas/src/canvas_color_ops.rs +++ b/crates/pdf-canvas/src/canvas_color_ops.rs @@ -1,6 +1,5 @@ use pdf_color_space::color_space::ColorSpace; use pdf_color_space::error::ColorSpaceError; -use pdf_content_stream_operators::pdf_operator_backend::ColorOps; use std::sync::Arc; use crate::{ @@ -28,17 +27,22 @@ fn apply_content_stream_color( } } -impl ColorOps for PdfCanvas<'_, B> { - type ErrorType = PdfCanvasError; - fn set_stroking_color_space(&mut self, name: &[u8]) -> Result<(), Self::ErrorType> { +impl PdfCanvas<'_, B> { + /// Handles `CS`: selects the stroking color space. + pub(crate) fn set_stroking_color_space(&mut self, name: &[u8]) -> Result<(), PdfCanvasError> { self.set_color_space(name, true) } - fn set_non_stroking_color_space(&mut self, name: &[u8]) -> Result<(), Self::ErrorType> { + /// Handles `cs`: selects the non-stroking color space. + pub(crate) fn set_non_stroking_color_space( + &mut self, + name: &[u8], + ) -> Result<(), PdfCanvasError> { self.set_color_space(name, false) } - fn set_stroking_color(&mut self, components: &[f32]) -> Result<(), Self::ErrorType> { + /// Handles `SC`/`SCN` without a pattern: sets the stroking color. + pub(crate) fn set_stroking_color(&mut self, components: &[f32]) -> Result<(), PdfCanvasError> { let state = self.current_state_mut()?; state.stroke_pattern = None; @@ -52,7 +56,11 @@ impl ColorOps for PdfCanvas<'_, B> { Ok(()) } - fn set_non_stroking_color(&mut self, components: &[f32]) -> Result<(), Self::ErrorType> { + /// Handles `sc`/`scn` without a pattern: sets the non-stroking color. + pub(crate) fn set_non_stroking_color( + &mut self, + components: &[f32], + ) -> Result<(), PdfCanvasError> { let state = self.current_state_mut()?; state.fill_pattern = None; @@ -66,11 +74,12 @@ impl ColorOps for PdfCanvas<'_, B> { Ok(()) } - fn set_non_stroking_color_extended( + /// Handles `scn` with a pattern name: sets the non-stroking pattern. + pub(crate) fn set_non_stroking_color_extended( &mut self, components: &[f32], pattern_name: &[u8], - ) -> Result<(), Self::ErrorType> { + ) -> Result<(), PdfCanvasError> { if !components.is_empty() { let state = self.current_state_mut()?; state.fill_pattern = None; @@ -91,11 +100,12 @@ impl ColorOps for PdfCanvas<'_, B> { self.set_fill_pattern(pattern_name) } - fn set_stroking_color_extended( + /// Handles `SCN` with a pattern name: sets the stroking pattern. + pub(crate) fn set_stroking_color_extended( &mut self, components: &[f32], pattern_name: &[u8], - ) -> Result<(), Self::ErrorType> { + ) -> Result<(), PdfCanvasError> { if !components.is_empty() { let state = self.current_state_mut()?; state.stroke_pattern = None; @@ -116,7 +126,8 @@ impl ColorOps for PdfCanvas<'_, B> { self.set_stroke_pattern(pattern_name) } - fn set_stroking_gray(&mut self, gray: f32) -> Result<(), Self::ErrorType> { + /// Handles `G`: sets a DeviceGray stroking color. + pub(crate) fn set_stroking_gray(&mut self, gray: f32) -> Result<(), PdfCanvasError> { let state = self.current_state_mut()?; state.paint.set_stroke_color(Color::from_gray(gray)); state.stroke_pattern = None; @@ -124,7 +135,8 @@ impl ColorOps for PdfCanvas<'_, B> { Ok(()) } - fn set_non_stroking_gray(&mut self, gray: f32) -> Result<(), Self::ErrorType> { + /// Handles `g`: sets a DeviceGray non-stroking color. + pub(crate) fn set_non_stroking_gray(&mut self, gray: f32) -> Result<(), PdfCanvasError> { let state = self.current_state_mut()?; state.paint.set_fill_color(Color::from_gray(gray)); state.fill_pattern = None; @@ -132,7 +144,13 @@ impl ColorOps for PdfCanvas<'_, B> { Ok(()) } - fn set_stroking_rgb(&mut self, r: f32, g: f32, b: f32) -> Result<(), Self::ErrorType> { + /// Handles `RG`: sets a DeviceRGB stroking color. + pub(crate) fn set_stroking_rgb( + &mut self, + r: f32, + g: f32, + b: f32, + ) -> Result<(), PdfCanvasError> { let state = self.current_state_mut()?; state.paint.set_stroke_color(Color::from_rgb(r, g, b)); state.stroke_pattern = None; @@ -140,7 +158,13 @@ impl ColorOps for PdfCanvas<'_, B> { Ok(()) } - fn set_non_stroking_rgb(&mut self, r: f32, g: f32, b: f32) -> Result<(), Self::ErrorType> { + /// Handles `rg`: sets a DeviceRGB non-stroking color. + pub(crate) fn set_non_stroking_rgb( + &mut self, + r: f32, + g: f32, + b: f32, + ) -> Result<(), PdfCanvasError> { let state = self.current_state_mut()?; state.paint.set_fill_color(Color::from_rgb(r, g, b)); state.fill_pattern = None; @@ -148,7 +172,14 @@ impl ColorOps for PdfCanvas<'_, B> { Ok(()) } - fn set_stroking_cmyk(&mut self, c: f32, m: f32, y: f32, k: f32) -> Result<(), Self::ErrorType> { + /// Handles `K`: sets a DeviceCMYK stroking color. + pub(crate) fn set_stroking_cmyk( + &mut self, + c: f32, + m: f32, + y: f32, + k: f32, + ) -> Result<(), PdfCanvasError> { let state = self.current_state_mut()?; state.paint.set_stroke_color(Color::from_cmyk(c, m, y, k)); state.stroke_pattern = None; @@ -156,13 +187,14 @@ impl ColorOps for PdfCanvas<'_, B> { Ok(()) } - fn set_non_stroking_cmyk( + /// Handles `k`: sets a DeviceCMYK non-stroking color. + pub(crate) fn set_non_stroking_cmyk( &mut self, c: f32, m: f32, y: f32, k: f32, - ) -> Result<(), Self::ErrorType> { + ) -> Result<(), PdfCanvasError> { let state = self.current_state_mut()?; state.paint.set_fill_color(Color::from_cmyk(c, m, y, k)); state.fill_pattern = None; diff --git a/crates/pdf-canvas/src/canvas_external_object_ops.rs b/crates/pdf-canvas/src/canvas_external_object_ops.rs index 94a11a35..255833da 100644 --- a/crates/pdf-canvas/src/canvas_external_object_ops.rs +++ b/crates/pdf-canvas/src/canvas_external_object_ops.rs @@ -7,7 +7,6 @@ //! Images are placed by handing the backend the full image-space to device-space //! matrix, so rotation, shear and mirroring in the CTM are all preserved. -use pdf_content_stream_operators::pdf_operator_backend::XObjectOps; use pdf_graphics::{Image, PixelFormat, transform::Transform}; use pdf_image::{PdfImage, raster::stencil_image}; use pdf_resources::resource::Resource; @@ -22,8 +21,7 @@ use crate::{canvas_backend::CanvasBackend, error::PdfCanvasError, pdf_canvas::Pd /// lands on the top edge of the placed image. const IMAGE_SPACE_Y_FLIP: Transform = Transform::from_row(1.0, 0.0, 0.0, -1.0, 0.0, 1.0); -impl XObjectOps for PdfCanvas<'_, B> { - type ErrorType = PdfCanvasError; +impl PdfCanvas<'_, B> { /// Invokes (renders) an XObject by name from the current resource dictionary. /// /// This method handles two types of XObjects: @@ -41,7 +39,7 @@ impl XObjectOps for PdfCanvas<'_, B> { /// Form XObjects are self-contained content streams that can include /// their own resources. They are rendered by recursively processing /// the form's content stream with the form's transformation matrix. - fn invoke_xobject(&mut self, xobject_name: &[u8]) -> Result<(), Self::ErrorType> { + pub(crate) fn invoke_xobject(&mut self, xobject_name: &[u8]) -> Result<(), PdfCanvasError> { let resources = self .current_state()? .resources @@ -79,7 +77,7 @@ impl XObjectOps for PdfCanvas<'_, B> { } /// Decodes inline image samples before applying the current painting state. - fn paint_inline_image(&mut self, image: &PdfImage) -> Result<(), Self::ErrorType> { + pub(crate) fn paint_inline_image(&mut self, image: &PdfImage) -> Result<(), PdfCanvasError> { let decoded = image .decode(None) .map_err(|e| PdfCanvasError::InvalidImageData(e.to_string()))?; diff --git a/crates/pdf-canvas/src/canvas_graphics_state_ops.rs b/crates/pdf-canvas/src/canvas_graphics_state_ops.rs index b5d45f99..175456f2 100644 --- a/crates/pdf-canvas/src/canvas_graphics_state_ops.rs +++ b/crates/pdf-canvas/src/canvas_graphics_state_ops.rs @@ -1,26 +1,14 @@ //! PDF graphics-state selection and save/restore operators. use std::sync::Arc; -use pdf_content_stream_operators::pdf_operator_backend::GraphicsStateOps; use pdf_graphics::{DashPattern, LineCap, LineJoin, MaskMode, transform::Transform}; use pdf_resources::{external_graphics_state::ExternalGraphicsStateKey, resource::Resource}; use crate::{canvas_backend::CanvasBackend, error::PdfCanvasError, pdf_canvas::PdfCanvas}; -impl GraphicsStateOps for PdfCanvas<'_, B> { - type ErrorType = PdfCanvasError; - /// Saves PDF graphics state together with the corresponding backend state. - fn save_graphics_state(&mut self) -> Result<(), Self::ErrorType> { - self.save() - } - - /// Restores PDF graphics state and its corresponding backend save. - fn restore_graphics_state(&mut self) -> Result<(), Self::ErrorType> { - self.restore() - } - +impl PdfCanvas<'_, B> { /// Concatenates the supplied matrix with the current PDF transformation. - fn concat_matrix(&mut self, transform: &Transform) -> Result<(), Self::ErrorType> { + pub(crate) fn concat_matrix(&mut self, transform: &Transform) -> Result<(), PdfCanvasError> { // PDF 'cm' operator: update the current transformation matrix (CTM) by // concatenating the provided matrix [a b c d e f] onto the current CTM. // @@ -31,51 +19,44 @@ impl GraphicsStateOps for PdfCanvas<'_, B> { } /// Updates the width used for subsequent path strokes. - fn set_line_width(&mut self, width: f32) -> Result<(), Self::ErrorType> { + pub(crate) fn set_line_width(&mut self, width: f32) -> Result<(), PdfCanvasError> { self.current_state_mut()?.paint.line_width = width; Ok(()) } /// Updates the end-cap style used for subsequent path strokes. - fn set_line_cap(&mut self, cap_style: LineCap) -> Result<(), Self::ErrorType> { + pub(crate) fn set_line_cap(&mut self, cap_style: LineCap) -> Result<(), PdfCanvasError> { self.current_state_mut()?.paint.line_cap = cap_style; Ok(()) } /// Updates the corner-join style used for subsequent path strokes. - fn set_line_join(&mut self, line_join: LineJoin) -> Result<(), Self::ErrorType> { + pub(crate) fn set_line_join(&mut self, line_join: LineJoin) -> Result<(), PdfCanvasError> { self.current_state_mut()?.paint.line_join = line_join; Ok(()) } /// Updates the maximum miter length relative to stroke width. - fn set_miter_limit(&mut self, miter_limit: f32) -> Result<(), Self::ErrorType> { + pub(crate) fn set_miter_limit(&mut self, miter_limit: f32) -> Result<(), PdfCanvasError> { self.current_state_mut()?.paint.miter_limit = miter_limit; Ok(()) } /// Validates and stores dash intervals and their starting phase. - fn set_dash_pattern( + pub(crate) fn set_dash_pattern( &mut self, dash_array: &[f32], dash_phase: f32, - ) -> Result<(), Self::ErrorType> { + ) -> Result<(), PdfCanvasError> { self.current_state_mut()?.paint.dash_pattern = DashPattern::new(dash_array, dash_phase)?; Ok(()) } - /// Handles the rendering-intent operator; color conversion currently uses its default intent. - fn set_rendering_intent(&mut self, _intent: &[u8]) -> Result<(), Self::ErrorType> { - Ok(()) - } - - /// Handles the flatness operator; path approximation remains backend-controlled. - fn set_flatness_tolerance(&mut self, _tolerance: f32) -> Result<(), Self::ErrorType> { - Ok(()) - } - /// Applies extended graphics-state entries, preparing soft-mask recordings when selected. - fn set_graphics_state_from_dict(&mut self, dict_name: &[u8]) -> Result<(), Self::ErrorType> { + pub(crate) fn set_graphics_state_from_dict( + &mut self, + dict_name: &[u8], + ) -> Result<(), PdfCanvasError> { let resources = self .current_state()? .resources diff --git a/crates/pdf-canvas/src/canvas_marked_content_ops.rs b/crates/pdf-canvas/src/canvas_marked_content_ops.rs index f8ce7321..c721df4f 100644 --- a/crates/pdf-canvas/src/canvas_marked_content_ops.rs +++ b/crates/pdf-canvas/src/canvas_marked_content_ops.rs @@ -1,33 +1,20 @@ -use pdf_content_stream_operators::pdf_operator_backend::MarkedContentOps; - use crate::{canvas_backend::CanvasBackend, error::PdfCanvasError, pdf_canvas::PdfCanvas}; -impl MarkedContentOps for PdfCanvas<'_, B> { - type ErrorType = PdfCanvasError; - fn mark_point(&mut self, _tag: &[u8]) -> Result<(), Self::ErrorType> { - Ok(()) - } - - fn mark_point_with_properties( - &mut self, - _tag: &[u8], - _properties_name_or_dict: &[u8], - ) -> Result<(), Self::ErrorType> { - Ok(()) - } - - fn begin_marked_content(&mut self, tag: &[u8]) -> Result<(), Self::ErrorType> { +impl PdfCanvas<'_, B> { + /// Handles `BMC`: begins a marked-content sequence. + pub(crate) fn begin_marked_content(&mut self, tag: &[u8]) -> Result<(), PdfCanvasError> { if let Some(filter) = &mut self.optional_content { filter.begin(tag, || None); } Ok(()) } - fn begin_marked_content_with_properties( + /// Handles `BDC`: begins a marked-content sequence with a property list. + pub(crate) fn begin_marked_content_with_properties( &mut self, tag: &[u8], properties: Option<&[u8]>, - ) -> Result<(), Self::ErrorType> { + ) -> Result<(), PdfCanvasError> { let Some(filter) = &mut self.optional_content else { return Ok(()); }; @@ -40,7 +27,8 @@ impl MarkedContentOps for PdfCanvas<'_, B> { Ok(()) } - fn end_marked_content(&mut self) -> Result<(), Self::ErrorType> { + /// Handles `EMC`: ends a marked-content sequence. + pub(crate) fn end_marked_content(&mut self) -> Result<(), PdfCanvasError> { if let Some(filter) = &mut self.optional_content { filter.end(); } diff --git a/crates/pdf-canvas/src/canvas_operator_dispatch.rs b/crates/pdf-canvas/src/canvas_operator_dispatch.rs new file mode 100644 index 00000000..8c4f26b4 --- /dev/null +++ b/crates/pdf-canvas/src/canvas_operator_dispatch.rs @@ -0,0 +1,131 @@ +//! Dispatch of parsed content-stream operators to their canvas handlers. +use pdf_content_stream_operators::variants::PdfOperatorVariant; + +use crate::{canvas_backend::CanvasBackend, error::PdfCanvasError, pdf_canvas::PdfCanvas}; + +impl PdfCanvas<'_, B> { + /// Applies one content-stream operator to the canvas. + /// + /// Operators without a visual effect on this canvas (compatibility sections, Type 3 glyph + /// metrics, rendering intent and flatness) are accepted and ignored. + pub(crate) fn apply_operator(&mut self, op: &PdfOperatorVariant) -> Result<(), PdfCanvasError> { + match op { + PdfOperatorVariant::LineTo { x, y } => self.line_to(*x, *y), + PdfOperatorVariant::MoveTo { x, y } => self.move_to(*x, *y), + PdfOperatorVariant::CurveTo { + x1, + y1, + x2, + y2, + x3, + y3, + } => self.curve_to(*x1, *y1, *x2, *y2, *x3, *y3), + PdfOperatorVariant::CurveToV { x2, y2, x3, y3 } => self.curve_to_v(*x2, *y2, *x3, *y3), + PdfOperatorVariant::CurveToY { x1, y1, x3, y3 } => self.curve_to_y(*x1, *y1, *x3, *y3), + PdfOperatorVariant::ClosePath => self.close_path(), + PdfOperatorVariant::Rectangle { + x, + y, + width, + height, + } => self.rectangle(*x, *y, *width, *height), + PdfOperatorVariant::StrokePath => self.stroke_path(), + PdfOperatorVariant::CloseStrokePath => self.close_and_stroke_path(), + PdfOperatorVariant::FillPathNonZero => self.fill_path_nonzero_winding(), + PdfOperatorVariant::FillPathEvenOdd => self.fill_path_even_odd(), + PdfOperatorVariant::FillAndStrokePathNonZero => { + self.fill_and_stroke_path_nonzero_winding() + } + PdfOperatorVariant::FillAndStrokePathEvenOdd => self.fill_and_stroke_path_even_odd(), + PdfOperatorVariant::CloseFillAndStrokePathNonZero => { + self.close_fill_and_stroke_path_nonzero_winding() + } + PdfOperatorVariant::CloseFillAndStrokePathEvenOdd => { + self.close_fill_and_stroke_path_even_odd() + } + PdfOperatorVariant::EndPath => self.end_path_no_op(), + PdfOperatorVariant::ClipNonZero => self.clip_path_nonzero_winding(), + PdfOperatorVariant::ClipEvenOdd => self.clip_path_even_odd(), + PdfOperatorVariant::SetGrayFill { gray } => self.set_non_stroking_gray(*gray), + PdfOperatorVariant::SetGrayStroke { gray } => self.set_stroking_gray(*gray), + PdfOperatorVariant::SetRGBFill { r, g, b } => self.set_non_stroking_rgb(*r, *g, *b), + PdfOperatorVariant::SetRGBStroke { r, g, b } => self.set_stroking_rgb(*r, *g, *b), + PdfOperatorVariant::SetCMYKFill { c, m, y, k } => { + self.set_non_stroking_cmyk(*c, *m, *y, *k) + } + PdfOperatorVariant::SetCMYKStroke { c, m, y, k } => { + self.set_stroking_cmyk(*c, *m, *y, *k) + } + PdfOperatorVariant::SetLineWidth { width } => self.set_line_width(*width), + PdfOperatorVariant::SetLineCapStyle { style } => self.set_line_cap(*style), + PdfOperatorVariant::SetLineJoinStyle { style } => self.set_line_join(*style), + PdfOperatorVariant::SetMiterLimit { limit } => self.set_miter_limit(*limit), + PdfOperatorVariant::SetDashPattern { array, phase } => { + self.set_dash_pattern(array, *phase) + } + PdfOperatorVariant::SetGraphicsStateFromDict { dict_name } => { + self.set_graphics_state_from_dict(dict_name) + } + PdfOperatorVariant::SaveGraphicsState => self.save(), + PdfOperatorVariant::RestoreGraphicsState => self.restore(), + PdfOperatorVariant::ConcatMatrix { matrix } => self.concat_matrix(matrix), + PdfOperatorVariant::BeginMarkedContent { tag } => self.begin_marked_content(tag), + PdfOperatorVariant::BeginMarkedContentWithProps(op) => { + self.begin_marked_content_with_properties(op.tag(), op.properties()) + } + PdfOperatorVariant::EndMarkedContent => self.end_marked_content(), + PdfOperatorVariant::BeginText => self.begin_text_object(), + PdfOperatorVariant::EndText => self.end_text_object(), + PdfOperatorVariant::MoveTextPosition { tx, ty } => self.move_text_position(*tx, *ty), + PdfOperatorVariant::MoveTextPositionAndSetLeading { tx, ty } => { + self.move_text_position_and_set_leading(*tx, *ty) + } + PdfOperatorVariant::SetTextMatrix { matrix } => self.set_text_matrix(matrix), + PdfOperatorVariant::MoveToNextLine => self.move_to_start_of_next_line(), + PdfOperatorVariant::ShowText { text } => self.show_text(text), + PdfOperatorVariant::MoveNextLineShowText { text } => { + self.move_to_next_line_and_show_text(text) + } + PdfOperatorVariant::SetSpacingMoveShowText { + word_spacing, + char_spacing, + text, + } => self.set_spacing_and_show_text(*word_spacing, *char_spacing, text), + PdfOperatorVariant::ShowTextArray { elements } => { + self.show_text_with_glyph_positioning(elements) + } + PdfOperatorVariant::SetCharacterSpacing { spacing } => { + self.set_character_spacing(*spacing) + } + PdfOperatorVariant::SetWordSpacing { spacing } => self.set_word_spacing(*spacing), + PdfOperatorVariant::SetHorizontalScaling { scale } => { + self.set_horizontal_text_scaling(*scale) + } + PdfOperatorVariant::SetLeading { leading } => self.set_text_leading(*leading), + PdfOperatorVariant::SetFont { name, size } => self.set_font_and_size(name, *size), + PdfOperatorVariant::SetRenderingMode { mode } => self.set_text_rendering_mode(*mode), + PdfOperatorVariant::SetTextRise { rise } => self.set_text_rise(*rise), + PdfOperatorVariant::InvokeXObject { name } => self.invoke_xobject(name), + PdfOperatorVariant::InlineImage(image) => self.paint_inline_image(image), + PdfOperatorVariant::PaintShading { name } => self.paint_shading(name), + PdfOperatorVariant::SetStrokeColorSpace { name } => self.set_stroking_color_space(name), + PdfOperatorVariant::SetNonStrokingColorSpace { name } => { + self.set_non_stroking_color_space(name) + } + PdfOperatorVariant::SetStrokingColor(color) => match &color.pattern { + Some(pattern) => self.set_stroking_color_extended(&color.components, pattern), + None => self.set_stroking_color(&color.components), + }, + PdfOperatorVariant::SetNonStrokingColor(color) => match &color.pattern { + Some(pattern) => self.set_non_stroking_color_extended(&color.components, pattern), + None => self.set_non_stroking_color(&color.components), + }, + PdfOperatorVariant::BeginCompatibility + | PdfOperatorVariant::EndCompatibility + | PdfOperatorVariant::SetCharWidthAndBoundingBox { .. } + | PdfOperatorVariant::SetCharWidth { .. } + | PdfOperatorVariant::SetRenderingIntent { .. } + | PdfOperatorVariant::SetFlatnessTolerance { .. } => Ok(()), + } + } +} diff --git a/crates/pdf-canvas/src/canvas_path_ops.rs b/crates/pdf-canvas/src/canvas_path_ops.rs index fe21c2a4..437833b2 100644 --- a/crates/pdf-canvas/src/canvas_path_ops.rs +++ b/crates/pdf-canvas/src/canvas_path_ops.rs @@ -1,25 +1,26 @@ -use pdf_content_stream_operators::pdf_operator_backend::{PathConstructionOps, PathPaintingOps}; use pdf_graphics::{PaintMode, PathFillType, pdf_path::PdfPath}; use crate::{canvas_backend::CanvasBackend, error::PdfCanvasError, pdf_canvas::PdfCanvas}; -impl PathConstructionOps for PdfCanvas<'_, B> { - type ErrorType = PdfCanvasError; - fn move_to(&mut self, x: f32, y: f32) -> Result<(), Self::ErrorType> { +impl PdfCanvas<'_, B> { + /// Handles `m`: begins a new subpath at (x, y). + pub(crate) fn move_to(&mut self, x: f32, y: f32) -> Result<(), PdfCanvasError> { self.current_path .get_or_insert_with(PdfPath::default) .move_to(x, y); Ok(()) } - fn line_to(&mut self, x: f32, y: f32) -> Result<(), Self::ErrorType> { + /// Handles `l`: appends a line segment to (x, y). + pub(crate) fn line_to(&mut self, x: f32, y: f32) -> Result<(), PdfCanvasError> { self.current_path .get_or_insert_with(PdfPath::default) .line_to(x, y); Ok(()) } - fn curve_to( + /// Handles `c`: appends a cubic Bézier curve. + pub(crate) fn curve_to( &mut self, x1: f32, y1: f32, @@ -27,14 +28,21 @@ impl PathConstructionOps for PdfCanvas<'_, B> { y2: f32, x3: f32, y3: f32, - ) -> Result<(), Self::ErrorType> { + ) -> Result<(), PdfCanvasError> { self.current_path .get_or_insert_with(PdfPath::default) .curve_to(x1, y1, x2, y2, x3, y3); Ok(()) } - fn curve_to_v(&mut self, x2: f32, y2: f32, x3: f32, y3: f32) -> Result<(), Self::ErrorType> { + /// Handles `v`: appends a Bézier curve whose first control point is the current point. + pub(crate) fn curve_to_v( + &mut self, + x2: f32, + y2: f32, + x3: f32, + y3: f32, + ) -> Result<(), PdfCanvasError> { let path = self.current_path.get_or_insert_with(PdfPath::default); if let Some((x, y)) = path.current_point() { path.curve_to(x, y, x2, y2, x3, y3); @@ -44,27 +52,36 @@ impl PathConstructionOps for PdfCanvas<'_, B> { } } - fn curve_to_y(&mut self, x1: f32, y1: f32, x3: f32, y3: f32) -> Result<(), Self::ErrorType> { + /// Handles `y`: appends a Bézier curve whose second control point is its end point. + pub(crate) fn curve_to_y( + &mut self, + x1: f32, + y1: f32, + x3: f32, + y3: f32, + ) -> Result<(), PdfCanvasError> { self.current_path .get_or_insert_with(PdfPath::default) .curve_to(x1, y1, x3, y3, x3, y3); Ok(()) } - fn close_path(&mut self) -> Result<(), Self::ErrorType> { + /// Handles `h`: closes the current subpath. + pub(crate) fn close_path(&mut self) -> Result<(), PdfCanvasError> { self.current_path .get_or_insert_with(PdfPath::default) .close(); Ok(()) } - fn rectangle( + /// Handles `re`: appends a closed rectangle subpath. + pub(crate) fn rectangle( &mut self, x: f32, y: f32, width: f32, height: f32, - ) -> Result<(), Self::ErrorType> { + ) -> Result<(), PdfCanvasError> { let path = self.current_path.get_or_insert_with(PdfPath::default); path.move_to(x, y); @@ -76,44 +93,54 @@ impl PathConstructionOps for PdfCanvas<'_, B> { } } -impl PathPaintingOps for PdfCanvas<'_, B> { - type ErrorType = PdfCanvasError; - fn stroke_path(&mut self) -> Result<(), Self::ErrorType> { +impl PdfCanvas<'_, B> { + /// Handles `S`: strokes the current path. + pub(crate) fn stroke_path(&mut self) -> Result<(), PdfCanvasError> { self.end_path(Some((PaintMode::Stroke, PathFillType::default()))) } - fn close_and_stroke_path(&mut self) -> Result<(), Self::ErrorType> { + /// Handles `s`: closes and strokes the current path. + pub(crate) fn close_and_stroke_path(&mut self) -> Result<(), PdfCanvasError> { self.close_path()?; - PathPaintingOps::stroke_path(self) + self.stroke_path() } - fn fill_path_nonzero_winding(&mut self) -> Result<(), Self::ErrorType> { + /// Handles `f`/`F`: fills the current path with the nonzero winding rule. + pub(crate) fn fill_path_nonzero_winding(&mut self) -> Result<(), PdfCanvasError> { self.end_path(Some((PaintMode::Fill, PathFillType::Winding))) } - fn fill_path_even_odd(&mut self) -> Result<(), Self::ErrorType> { + /// Handles `f*`: fills the current path with the even-odd rule. + pub(crate) fn fill_path_even_odd(&mut self) -> Result<(), PdfCanvasError> { self.end_path(Some((PaintMode::Fill, PathFillType::EvenOdd))) } - fn fill_and_stroke_path_nonzero_winding(&mut self) -> Result<(), Self::ErrorType> { + /// Handles `B`: fills (nonzero winding) and strokes the current path. + pub(crate) fn fill_and_stroke_path_nonzero_winding(&mut self) -> Result<(), PdfCanvasError> { self.end_path(Some((PaintMode::FillAndStroke, PathFillType::Winding))) } - fn fill_and_stroke_path_even_odd(&mut self) -> Result<(), Self::ErrorType> { + /// Handles `B*`: fills (even-odd) and strokes the current path. + pub(crate) fn fill_and_stroke_path_even_odd(&mut self) -> Result<(), PdfCanvasError> { self.end_path(Some((PaintMode::FillAndStroke, PathFillType::EvenOdd))) } - fn close_fill_and_stroke_path_nonzero_winding(&mut self) -> Result<(), Self::ErrorType> { + /// Handles `b`: closes, fills (nonzero winding) and strokes the current path. + pub(crate) fn close_fill_and_stroke_path_nonzero_winding( + &mut self, + ) -> Result<(), PdfCanvasError> { self.close_path()?; self.fill_and_stroke_path_nonzero_winding() } - fn close_fill_and_stroke_path_even_odd(&mut self) -> Result<(), Self::ErrorType> { + /// Handles `b*`: closes, fills (even-odd) and strokes the current path. + pub(crate) fn close_fill_and_stroke_path_even_odd(&mut self) -> Result<(), PdfCanvasError> { self.close_path()?; self.fill_and_stroke_path_even_odd() } - fn end_path_no_op(&mut self) -> Result<(), Self::ErrorType> { + /// Handles `n`: ends the current path without painting it. + pub(crate) fn end_path_no_op(&mut self) -> Result<(), PdfCanvasError> { self.end_path(None) } } diff --git a/crates/pdf-canvas/src/canvas_text_ops.rs b/crates/pdf-canvas/src/canvas_text_ops.rs index cbbe895a..54622d89 100644 --- a/crates/pdf-canvas/src/canvas_text_ops.rs +++ b/crates/pdf-canvas/src/canvas_text_ops.rs @@ -2,52 +2,52 @@ use crate::CanvasPath; use crate::canvas_backend::CanvasBackend; use crate::error::PdfCanvasError; use crate::pdf_canvas::PdfCanvas; -use pdf_content_stream_operators::pdf_operator_backend::{ - TextObjectOps, TextPositioningOps, TextShowingOps, TextStateOps, -}; use pdf_content_stream_operators::variants::PdfOperatorVariant; use pdf_graphics::pdf_path::PdfPath; use pdf_graphics::transform::Transform; use pdf_graphics::{PaintMode, PathFillType, TextRenderingMode}; use pdf_text_engine::text::{PdfTextItem, PdfTextRun}; -impl TextPositioningOps for PdfCanvas<'_, B> { - type ErrorType = PdfCanvasError; - fn move_text_position(&mut self, tx: f32, ty: f32) -> Result<(), Self::ErrorType> { +impl PdfCanvas<'_, B> { + /// Handles `Td`: offsets the start of the next line. + pub(crate) fn move_text_position(&mut self, tx: f32, ty: f32) -> Result<(), PdfCanvasError> { self.current_state_mut()? .text_state .move_line_position(tx, ty); Ok(()) } - fn move_text_position_and_set_leading( + /// Handles `TD`: sets the leading to `-ty` and offsets the next line. + pub(crate) fn move_text_position_and_set_leading( &mut self, tx: f32, ty: f32, - ) -> Result<(), Self::ErrorType> { + ) -> Result<(), PdfCanvasError> { // TD: Set leading to -ty, then perform Td(tx, ty) let neg_ty = -ty; self.current_state_mut()?.text_state.leading = neg_ty; self.move_text_position(tx, ty) } - fn set_text_matrix(&mut self, transform: &Transform) -> Result<(), Self::ErrorType> { + /// Handles `Tm`: sets the text and text line matrices. + pub(crate) fn set_text_matrix(&mut self, transform: &Transform) -> Result<(), PdfCanvasError> { self.current_state_mut()? .text_state .set_matrices(*transform); Ok(()) } - fn move_to_start_of_next_line(&mut self) -> Result<(), Self::ErrorType> { + /// Handles `T*`: moves to the start of the next line. + pub(crate) fn move_to_start_of_next_line(&mut self) -> Result<(), PdfCanvasError> { // T*: Move to start of next line using current leading: Tlm = Tlm * T(0, -Tl); Tm = Tlm. self.current_state_mut()?.text_state.move_to_next_line(); Ok(()) } } -impl TextObjectOps for PdfCanvas<'_, B> { - type ErrorType = PdfCanvasError; - fn begin_text_object(&mut self) -> Result<(), Self::ErrorType> { +impl PdfCanvas<'_, B> { + /// Handles `BT`: resets the text matrices. + pub(crate) fn begin_text_object(&mut self) -> Result<(), PdfCanvasError> { let state = self.current_state_mut()?; state.text_state.matrix = Transform::identity(); state.text_state.line_matrix = Transform::identity(); @@ -56,7 +56,8 @@ impl TextObjectOps for PdfCanvas<'_, B> { Ok(()) } - fn end_text_object(&mut self) -> Result<(), Self::ErrorType> { + /// Handles `ET`: applies any pending text clip. + pub(crate) fn end_text_object(&mut self) -> Result<(), PdfCanvasError> { // Apply any glyph outlines accumulated for clip-mode text rendering (modes 4–7). // Per ISO 32000 §9.3.6, the clip path is set at the end of the text object. if let Some(clip_path) = self.current_state_mut()?.pending_text_clip.take() { @@ -70,30 +71,41 @@ impl TextObjectOps for PdfCanvas<'_, B> { } } -impl TextStateOps for PdfCanvas<'_, B> { - type ErrorType = PdfCanvasError; - fn set_character_spacing(&mut self, spacing: f32) -> Result<(), Self::ErrorType> { +impl PdfCanvas<'_, B> { + /// Handles `Tc`: sets the character spacing. + pub(crate) fn set_character_spacing(&mut self, spacing: f32) -> Result<(), PdfCanvasError> { self.current_state_mut()?.text_state.style.character_spacing = spacing; Ok(()) } - fn set_word_spacing(&mut self, spacing: f32) -> Result<(), Self::ErrorType> { + /// Handles `Tw`: sets the word spacing. + pub(crate) fn set_word_spacing(&mut self, spacing: f32) -> Result<(), PdfCanvasError> { self.current_state_mut()?.text_state.style.word_spacing = spacing; Ok(()) } - fn set_horizontal_text_scaling(&mut self, scale_percent: f32) -> Result<(), Self::ErrorType> { + /// Handles `Tz`: sets the horizontal scaling from a percentage. + pub(crate) fn set_horizontal_text_scaling( + &mut self, + scale_percent: f32, + ) -> Result<(), PdfCanvasError> { self.current_state_mut()?.text_state.style.horizontal_scale = scale_percent / 100.0; Ok(()) } - fn set_text_leading(&mut self, leading: f32) -> Result<(), Self::ErrorType> { + /// Handles `TL`: sets the text leading. + pub(crate) fn set_text_leading(&mut self, leading: f32) -> Result<(), PdfCanvasError> { // TL sets the text leading parameter self.current_state_mut()?.text_state.leading = leading; Ok(()) } - fn set_font_and_size(&mut self, font_name: &[u8], size: f32) -> Result<(), Self::ErrorType> { + /// Handles `Tf`: selects a font resource and size. + pub(crate) fn set_font_and_size( + &mut self, + font_name: &[u8], + size: f32, + ) -> Result<(), PdfCanvasError> { let (font, nested_resources) = self .current_state()? .resources @@ -111,44 +123,52 @@ impl TextStateOps for PdfCanvas<'_, B> { Ok(()) } - fn set_text_rendering_mode(&mut self, mode: TextRenderingMode) -> Result<(), Self::ErrorType> { + /// Handles `Tr`: sets the text rendering mode. + pub(crate) fn set_text_rendering_mode( + &mut self, + mode: TextRenderingMode, + ) -> Result<(), PdfCanvasError> { self.current_state_mut()?.paint.rendering_mode = mode; Ok(()) } - fn set_text_rise(&mut self, rise: f32) -> Result<(), Self::ErrorType> { + /// Handles `Ts`: sets the text rise. + pub(crate) fn set_text_rise(&mut self, rise: f32) -> Result<(), PdfCanvasError> { self.current_state_mut()?.text_state.style.rise = rise; Ok(()) } } -impl TextShowingOps for PdfCanvas<'_, B> { - type ErrorType = PdfCanvasError; - fn show_text(&mut self, text: &PdfTextItem) -> Result<(), Self::ErrorType> { +impl PdfCanvas<'_, B> { + /// Handles `Tj`: shows a text string. + pub(crate) fn show_text(&mut self, text: &PdfTextItem) -> Result<(), PdfCanvasError> { self.render_text_items(std::slice::from_ref(text)) } - fn show_text_with_glyph_positioning( + /// Handles `TJ`: shows strings with individual position adjustments. + pub(crate) fn show_text_with_glyph_positioning( &mut self, elements: &[PdfTextItem], - ) -> Result<(), Self::ErrorType> { + ) -> Result<(), PdfCanvasError> { self.render_text_items(elements) } - fn move_to_next_line_and_show_text( + /// Handles `'`: moves to the next line and shows text. + pub(crate) fn move_to_next_line_and_show_text( &mut self, text: &PdfTextItem, - ) -> Result<(), Self::ErrorType> { + ) -> Result<(), PdfCanvasError> { self.move_to_start_of_next_line()?; self.show_text(text) } - fn set_spacing_and_show_text( + /// Handles `"`: sets word and character spacing, then behaves as `'`. + pub(crate) fn set_spacing_and_show_text( &mut self, word_spacing: f32, char_spacing: f32, text: &PdfTextItem, - ) -> Result<(), Self::ErrorType> { + ) -> Result<(), PdfCanvasError> { self.set_word_spacing(word_spacing)?; self.set_character_spacing(char_spacing)?; self.move_to_next_line_and_show_text(text) diff --git a/crates/pdf-canvas/src/lib.rs b/crates/pdf-canvas/src/lib.rs index f16b764a..5b292b9b 100644 --- a/crates/pdf-canvas/src/lib.rs +++ b/crates/pdf-canvas/src/lib.rs @@ -1,7 +1,4 @@ //! PDF drawing interpretation, recording, and portable backend contracts. -use canvas_backend::CanvasBackend; -use pdf_canvas::PdfCanvas; -use pdf_content_stream_operators::pdf_operator_backend::PdfOperatorBackend; /// Drawing operations implemented by concrete rendering backends. pub mod canvas_backend; @@ -15,6 +12,8 @@ mod canvas_external_object_ops; mod canvas_graphics_state_ops; /// Marked-content operator handling. mod canvas_marked_content_ops; +/// Dispatch of parsed content-stream operators to their handlers. +mod canvas_operator_dispatch; /// PDF path construction and painting operators. mod canvas_path_ops; /// PDF graphics-state values retained across saved states. @@ -49,5 +48,3 @@ pub mod text; mod text_state; /// Tiling shader. pub mod tiling_shader; - -impl PdfOperatorBackend for PdfCanvas<'_, B> {} diff --git a/crates/pdf-canvas/src/pdf_canvas.rs b/crates/pdf-canvas/src/pdf_canvas.rs index e68934ab..b620de80 100644 --- a/crates/pdf-canvas/src/pdf_canvas.rs +++ b/crates/pdf-canvas/src/pdf_canvas.rs @@ -771,7 +771,7 @@ impl<'a, B: CanvasBackend> PdfCanvas<'a, B> { if filter.as_mut().is_some_and(|filter| filter(op)) { continue; } - op.call(self)?; + self.apply_operator(op)?; } Ok(()) @@ -901,7 +901,6 @@ impl<'a, B: CanvasBackend> PdfCanvas<'a, B> { #[cfg(test)] mod tests { - use pdf_content_stream_operators::pdf_operator_backend::GraphicsStateOps; use pdf_document::page::PdfPage; use pdf_graphics::rect::Rect; use pdf_text_engine::bundled_font_system; diff --git a/crates/pdf-canvas/src/shading.rs b/crates/pdf-canvas/src/shading.rs index 525d2341..cadb1924 100644 --- a/crates/pdf-canvas/src/shading.rs +++ b/crates/pdf-canvas/src/shading.rs @@ -1,15 +1,13 @@ //! Shading operators and shared paint preparation. use crate::CanvasPath; -use pdf_content_stream_operators::pdf_operator_backend::ShadingOps; use pdf_graphics::{PathFillType, pdf_path::PdfPath, rect::Rect, transform::Transform}; use pdf_shading::model::Shading; use crate::{canvas_backend::CanvasBackend, error::PdfCanvasError, pdf_canvas::PdfCanvas}; -impl ShadingOps for PdfCanvas<'_, B> { - type ErrorType = PdfCanvasError; +impl PdfCanvas<'_, B> { /// Prepares a shading paint and fills the current clip through the selected soft mask. - fn paint_shading(&mut self, shading_name: &[u8]) -> Result<(), Self::ErrorType> { + pub(crate) fn paint_shading(&mut self, shading_name: &[u8]) -> Result<(), PdfCanvasError> { let state = self.current_state()?; let Some(shading) = state diff --git a/crates/pdf-content-stream-operators/src/lib.rs b/crates/pdf-content-stream-operators/src/lib.rs index 7b8e6f12..c7c4bd2b 100644 --- a/crates/pdf-content-stream-operators/src/lib.rs +++ b/crates/pdf-content-stream-operators/src/lib.rs @@ -3,9 +3,6 @@ pub mod error; pub mod marked_content_operators; pub mod variants; -pub mod pdf_operator_backend; -pub mod recording_pdf_operator_backend; - 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 5a565284..4f8f7dfc 100644 --- a/crates/pdf-content-stream-operators/src/marked_content_operators.rs +++ b/crates/pdf-content-stream-operators/src/marked_content_operators.rs @@ -2,10 +2,7 @@ use std::sync::Arc; use pdf_object_reader::{ReadResult, object_variant::ObjectVariant, pdf_string::PdfString}; -use crate::{ - OperandContext, - pdf_operator_backend::{BackendError, PdfOperatorBackend}, -}; +use crate::OperandContext; /// 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 @@ -34,11 +31,14 @@ impl BeginMarkedContentWithProps { Ok(Self::new(tag, properties)) } - /// Dispatches the operator to `backend`. - pub(crate) fn call( - &self, - backend: &mut T, - ) -> Result<(), BackendError> { - backend.begin_marked_content_with_properties(&self.tag, self.properties.as_deref()) + /// The tag indicating the role or nature of the marked-content sequence. + pub fn tag(&self) -> &[u8] { + &self.tag + } + + /// The name of the property list in the `/Properties` resource subdictionary, or `None` + /// when the property list is an inline dictionary. + pub fn properties(&self) -> Option<&[u8]> { + self.properties.as_deref() } } diff --git a/crates/pdf-content-stream-operators/src/pdf_operator_backend.rs b/crates/pdf-content-stream-operators/src/pdf_operator_backend.rs deleted file mode 100644 index 0ae455bd..00000000 --- a/crates/pdf-content-stream-operators/src/pdf_operator_backend.rs +++ /dev/null @@ -1,849 +0,0 @@ -//! Defines traits for processing categorized PDF content stream operators. -//! Implementors of these traits can define how to handle specific groups of -//! PDF drawing, text, and state commands, allowing for different backends -//! (e.g., renderers, text extractors) to selectively implement functionality. - -use pdf_graphics::{LineCap, LineJoin, TextRenderingMode, transform::Transform}; - -use crate::PdfTextItem; - -/// Defines methods for handling PDF path construction operators. -/// -/// These operators are used to define shapes and paths before they are painted. -pub trait PathConstructionOps { - /// The error type that can be returned by operator handling methods. - type ErrorType: std::fmt::Debug + std::fmt::Display; - /// Moves the current point to the specified coordinates (x, y), starting a new subpath. - /// - /// # Parameters - /// - /// - `x`: The x-coordinate of the new current point. - /// - `y`: The y-coordinate of the new current point. - /// - /// # Returns - /// - /// A `Result` indicating success or an `ErrorType` on failure. - fn move_to(&mut self, x: f32, y: f32) -> Result<(), Self::ErrorType>; - - /// Appends a straight line segment from the current point to (x, y). - /// The new current point becomes (x, y). - /// - /// # Parameters - /// - /// - `x`: The x-coordinate of the line's end point. - /// - `y`: The y-coordinate of the line's end point. - /// - /// # Returns - /// - /// A `Result` indicating success or an `ErrorType` on failure. - fn line_to(&mut self, x: f32, y: f32) -> Result<(), Self::ErrorType>; - - /// 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 control points. - /// The new current point becomes (x3, y3). - /// - /// # Parameters - /// - /// - `x1`: The x-coordinate of the first control point. - /// - `y1`: The y-coordinate of the first control point. - /// - `x2`: The x-coordinate of the second control point. - /// - `y2`: The y-coordinate of the second control point. - /// - `x3`: The x-coordinate of the curve's end point. - /// - `y3`: The y-coordinate of the curve's end point. - /// - /// # Returns - /// - /// A `Result` indicating success or an `ErrorType` on failure. - fn curve_to( - &mut self, - x1: f32, - y1: f32, - x2: f32, - y2: f32, - x3: f32, - y3: f32, - ) -> Result<(), Self::ErrorType>; - - /// Appends a cubic Bézier curve to the current path, where the current point is the first control point. - /// (x2, y2) is the second control point, and (x3, y3) is the end point. - /// The new current point becomes (x3, y3). - /// - /// # Parameters - /// - /// - `x2`: The x-coordinate of the second control point. - /// - `y2`: The y-coordinate of the second control point. - /// - `x3`: The x-coordinate of the curve's end point. - /// - `y3`: The y-coordinate of the curve's end point. - /// - /// # Returns - /// - /// A `Result` indicating success or an `ErrorType` on failure. - fn curve_to_v(&mut self, x2: f32, y2: f32, x3: f32, y3: f32) -> Result<(), Self::ErrorType>; - - /// Appends a cubic Bézier curve to the current path, where the endpoint (x3, y3) is also the second control point. - /// (x1, y1) is the first control point. - /// The new current point becomes (x3, y3). - /// - /// # Parameters - /// - /// - `x1`: The x-coordinate of the first control point. - /// - `y1`: The y-coordinate of the first control point. - /// - `x3`: The x-coordinate of the curve's end point (and second control point). - /// - `y3`: The y-coordinate of the curve's end point (and second control point). - /// - /// # Returns - /// - /// A `Result` indicating success or an `ErrorType` on failure. - fn curve_to_y(&mut self, x1: f32, y1: f32, x3: f32, y3: f32) -> Result<(), Self::ErrorType>; - - /// Closes the current subpath by appending a line segment from the current point to the subpath's starting point. - /// - /// # Returns - /// - /// A `Result` indicating success or an `ErrorType` on failure. - fn close_path(&mut self) -> Result<(), Self::ErrorType>; - - /// Appends a rectangle to the current path as a complete subpath. - /// The rectangle is defined by its bottom-left corner (x, y), width, and height. - /// - /// # Parameters - /// - /// - `x`: The x-coordinate of the rectangle's bottom-left corner. - /// - `y`: The y-coordinate of the rectangle's bottom-left corner. - /// - `width`: The width of the rectangle. - /// - `height`: The height of the rectangle. - /// - /// # Returns - /// - /// A `Result` indicating success or an `ErrorType` on failure. - fn rectangle(&mut self, x: f32, y: f32, width: f32, height: f32) - -> Result<(), Self::ErrorType>; -} - -/// Defines methods to handle PDF Path Painting operators. -pub trait PathPaintingOps { - /// The error type that can be returned by operator handling methods. - type ErrorType: std::fmt::Debug + std::fmt::Display; - /// Strokes the current path using the current color and line style. - /// - /// # Returns - /// - /// A `Result` indicating success or an `ErrorType` on failure. - fn stroke_path(&mut self) -> Result<(), Self::ErrorType>; - - /// Closes the current subpath and then strokes it. - /// - /// # Returns - /// - /// A `Result` indicating success or an `ErrorType` on failure. - fn close_and_stroke_path(&mut self) -> Result<(), Self::ErrorType>; - - /// Fills the current path using the non-zero winding number rule. - /// - /// # Returns - /// - /// A `Result` indicating success or an `ErrorType` on failure. - fn fill_path_nonzero_winding(&mut self) -> Result<(), Self::ErrorType>; - - /// Fills the current path using the even-odd rule. - /// - /// # Returns - /// - /// A `Result` indicating success or an `ErrorType` on failure. - fn fill_path_even_odd(&mut self) -> Result<(), Self::ErrorType>; - - /// Fills and then strokes the current path, using the non-zero winding number rule for filling. - /// - /// # Returns - /// - /// A `Result` indicating success or an `ErrorType` on failure. - fn fill_and_stroke_path_nonzero_winding(&mut self) -> Result<(), Self::ErrorType>; - - /// Fills and then strokes the current path, using the even-odd rule for filling. - /// - /// # Returns - /// - /// A `Result` indicating success or an `ErrorType` on failure. - fn fill_and_stroke_path_even_odd(&mut self) -> Result<(), Self::ErrorType>; - - /// Closes, fills, and then strokes the current path, using the non-zero winding number rule for filling. - /// - /// # Returns - /// - /// A `Result` indicating success or an `ErrorType` on failure. - fn close_fill_and_stroke_path_nonzero_winding(&mut self) -> Result<(), Self::ErrorType>; - - /// Closes, fills, and then strokes the current path, using the even-odd rule for filling. - /// - /// # Returns - /// - /// A `Result` indicating success or an `ErrorType` on failure. - fn close_fill_and_stroke_path_even_odd(&mut self) -> Result<(), Self::ErrorType>; - - /// Ends the current path without filling or stroking it, effectively discarding it. - /// - /// # Returns - /// - /// A `Result` indicating success or an `ErrorType` on failure. - fn end_path_no_op(&mut self) -> Result<(), Self::ErrorType>; -} - -/// Defines methods to handle PDF Clipping Path operators. -pub trait ClippingPathOps { - /// The error type that can be returned by operator handling methods. - type ErrorType: std::fmt::Debug + std::fmt::Display; - /// Modifies the current clipping path by intersecting it with the current path, - /// using the non-zero winding number rule. - /// - /// # Returns - /// - /// A `Result` indicating success or an `ErrorType` on failure. - fn clip_path_nonzero_winding(&mut self) -> Result<(), Self::ErrorType>; - - /// Modifies the current clipping path by intersecting it with the current path, - /// using the even-odd rule. - /// - /// # Returns - /// - /// A `Result` indicating success or an `ErrorType` on failure. - fn clip_path_even_odd(&mut self) -> Result<(), Self::ErrorType>; -} - -/// Defines methods to handle PDF Graphics State operators. -pub trait GraphicsStateOps { - /// The error type that can be returned by operator handling methods. - type ErrorType: std::fmt::Debug + std::fmt::Display; - /// Saves the current graphics state onto the graphics state stack. - /// - /// # Returns - /// - /// A `Result` indicating success or an `ErrorType` on failure. - fn save_graphics_state(&mut self) -> Result<(), Self::ErrorType>; - - /// Restores the graphics state by removing the most recently saved state from the stack. - /// - /// # Returns - /// - /// A `Result` indicating success or an `ErrorType` on failure. - fn restore_graphics_state(&mut self) -> Result<(), Self::ErrorType>; - - /// Modifies the current transformation matrix (CTM) by concatenating it with the specified matrix. - /// The matrix is `[a b c d e f]`. - /// - /// # Parameters - /// - /// - `transform`: The transformation matrix to concatenate with the current CTM. - /// - /// # Returns - /// - /// A `Result` indicating success or an `ErrorType` on failure. - fn concat_matrix(&mut self, transform: &Transform) -> Result<(), Self::ErrorType>; - - /// Sets the line width for path stroking. - /// - /// # Parameters - /// - /// - `width`: The new line width in user space units. - /// - /// # Returns - /// - /// A `Result` indicating success or an `ErrorType` on failure. - fn set_line_width(&mut self, width: f32) -> Result<(), Self::ErrorType>; - - /// Sets the line cap style for path stroking. - /// - /// # Parameters - /// - /// - `cap_style`: An integer representing the cap style (e.g., 0 for butt, 1 for - /// round, 2 for projecting square). - /// - /// # Returns - /// - /// A `Result` indicating success or an `ErrorType` on failure. - fn set_line_cap(&mut self, cap_style: LineCap) -> Result<(), Self::ErrorType>; - - /// Sets the line join style for path stroking. - /// - /// # Parameters - /// - /// - `join_style`: An integer representing the join style (e.g., 0 for miter, 1 for round, 2 for bevel). - /// - /// # Returns - /// - /// A `Result` indicating success or an `ErrorType` on failure. - fn set_line_join(&mut self, join_style: LineJoin) -> Result<(), Self::ErrorType>; - - /// Sets the miter limit for path stroking. - /// - /// # Parameters - /// - /// - `limit`: The miter limit. - /// - /// # Returns - /// - /// A `Result` indicating success or an `ErrorType` on failure. - fn set_miter_limit(&mut self, limit: f32) -> Result<(), Self::ErrorType>; - - /// Sets the dash pattern for path stroking. - /// - /// # Parameters - /// - /// - `dash_array`: A slice representing the lengths of alternating dashes and gaps. - /// - `dash_phase`: The distance into the dash pattern at which to start. - /// - /// # Returns - /// - /// A `Result` indicating success or an `ErrorType` on failure. - fn set_dash_pattern( - &mut self, - dash_array: &[f32], - dash_phase: f32, - ) -> Result<(), Self::ErrorType>; - - /// Sets the rendering intent for color reproduction. - /// - /// # Parameters - /// - /// - `intent`: A string representing the rendering intent. - /// - /// # Returns - /// - /// A `Result` indicating success or an `ErrorType` on failure. - fn set_rendering_intent(&mut self, intent: &[u8]) -> Result<(), Self::ErrorType>; - - /// Sets the flatness tolerance, controlling the accuracy of curve rendering. - /// - /// # Parameters - /// - /// - `tolerance`: The flatness tolerance value. - /// - /// # Returns - /// - /// A `Result` indicating success or an `ErrorType` on failure. - fn set_flatness_tolerance(&mut self, tolerance: f32) -> Result<(), Self::ErrorType>; - - /// Sets multiple graphics state parameters from a named graphics state parameter dictionary. - /// The dictionary is expected to be in the resource dictionary. - /// - /// # Parameters - /// - /// - `dict_name`: The name of the graphics state parameter dictionary. - /// - /// # Returns - /// - /// A `Result` indicating success or an `ErrorType` on failure. - fn set_graphics_state_from_dict(&mut self, dict_name: &[u8]) -> Result<(), Self::ErrorType>; -} - -/// Defines methods to handle PDF Color operators. -pub trait ColorOps { - /// The error type that can be returned by operator handling methods. - type ErrorType: std::fmt::Debug + std::fmt::Display; - /// Sets the color space for subsequent stroking operations. - /// - /// # Parameters - /// - /// - `name`: The name of the color space (e.g., "DeviceGray", "DeviceRGB", "DeviceCMYK"). - /// - /// # Returns - /// - /// A `Result` indicating success or an `ErrorType` on failure. - fn set_stroking_color_space(&mut self, name: &[u8]) -> Result<(), Self::ErrorType>; - - /// Sets the color space for subsequent non-stroking (fill) operations. - /// - /// # Parameters - /// - /// - `name`: The name of the color space (e.g., "DeviceGray", "DeviceRGB", "DeviceCMYK"). - /// - /// # Returns - /// - /// A `Result` indicating success or an `ErrorType` on failure. - fn set_non_stroking_color_space(&mut self, name: &[u8]) -> Result<(), Self::ErrorType>; - - /// Sets the color for subsequent stroking operations, using the current stroking color space. - /// The number of components depends on the active color space. - /// - /// # Parameters - /// - /// - `components`: A slice of color components. - /// - /// # Returns - /// - /// A `Result` indicating success or an `ErrorType` on failure. - fn set_stroking_color(&mut self, components: &[f32]) -> Result<(), Self::ErrorType>; - - /// Sets the color for stroking operations, potentially with a pattern name. - /// Used for color spaces like ICCBased, Separation, DeviceN, or Pattern. - /// - /// # Parameters - /// - /// - `components`: A slice of color components. - /// - `pattern_name`: A name of a pattern,. - /// - /// # Returns - /// - /// A `Result` indicating success or an `ErrorType` on failure. - fn set_stroking_color_extended( - &mut self, - components: &[f32], - pattern_name: &[u8], - ) -> Result<(), Self::ErrorType>; - - /// Sets the color for subsequent non-stroking (fill) operations, using the current non-stroking color space. - /// The number of components depends on the active color space. - /// - /// # Parameters - /// - /// - `components`: A slice of color components. - /// - /// # Returns - /// - /// A `Result` indicating success or an `ErrorType` on failure. - fn set_non_stroking_color(&mut self, components: &[f32]) -> Result<(), Self::ErrorType>; - - /// Sets the color for non-stroking (fill) operations, potentially with a pattern name. - /// Used for color spaces like ICCBased, Separation, DeviceN, or Pattern. - /// - /// # Parameters - /// - /// - `components`: A slice of color components. - /// - `pattern_name`: A name of a pattern. - /// - /// # Returns - /// - /// A `Result` indicating success or an `ErrorType` on failure. - fn set_non_stroking_color_extended( - &mut self, - components: &[f32], - pattern_name: &[u8], - ) -> Result<(), Self::ErrorType>; - - /// Sets the stroking color to a grayscale value. - /// Assumes DeviceGray color space or similar. - /// - /// # Parameters - /// - /// - `gray`: The gray level (0.0 for black, 1.0 for white). - /// - /// # Returns - /// - /// A `Result` indicating success or an `ErrorType` on failure. - fn set_stroking_gray(&mut self, gray: f32) -> Result<(), Self::ErrorType>; - - /// Sets the non-stroking (fill) color to a grayscale value. - /// Assumes DeviceGray color space or similar. - /// - /// # Parameters - /// - /// - `gray`: The gray level (0.0 for black, 1.0 for white). - /// - /// # Returns - /// - /// A `Result` indicating success or an `ErrorType` on failure. - fn set_non_stroking_gray(&mut self, gray: f32) -> Result<(), Self::ErrorType>; - - /// Sets the stroking color to an RGB value. - /// Assumes DeviceRGB color space or similar. - /// - /// # Parameters - /// - /// - `r`: Red component (0.0 to 1.0). - /// - `g`: Green component (0.0 to 1.0). - /// - `b`: Blue component (0.0 to 1.0). - /// - /// # Returns - /// - /// A `Result` indicating success or an `ErrorType` on failure. - fn set_stroking_rgb(&mut self, r: f32, g: f32, b: f32) -> Result<(), Self::ErrorType>; - - /// Sets the non-stroking (fill) color to an RGB value. - /// Assumes DeviceRGB color space or similar. - /// - /// # Parameters - /// - /// - `r`: Red component (0.0 to 1.0). - /// - `g`: Green component (0.0 to 1.0). - /// - `b`: Blue component (0.0 to 1.0). - /// - /// # Returns - /// - /// A `Result` indicating success or an `ErrorType` on failure. - fn set_non_stroking_rgb(&mut self, r: f32, g: f32, b: f32) -> Result<(), Self::ErrorType>; - - /// Sets the stroking color to a CMYK value. - /// Assumes DeviceCMYK color space or similar. - /// - /// # Parameters - /// - /// - `c`: Cyan component (0.0 to 1.0). - /// - `m`: Magenta component (0.0 to 1.0). - /// - `y`: Yellow component (0.0 to 1.0). - /// - `k`: Black (Key) component (0.0 to 1.0). - /// - /// # Returns - /// - /// A `Result` indicating success or an `ErrorType` on failure. - fn set_stroking_cmyk(&mut self, c: f32, m: f32, y: f32, k: f32) -> Result<(), Self::ErrorType>; - - /// Sets the non-stroking (fill) color to a CMYK value. - /// Assumes DeviceCMYK color space or similar. - /// - /// # Parameters - /// - /// - `c`: Cyan component (0.0 to 1.0). - /// - `m`: Magenta component (0.0 to 1.0). - /// - `y`: Yellow component (0.0 to 1.0). - /// - `k`: Black (Key) component (0.0 to 1.0). - /// - /// # Returns - /// - /// A `Result` indicating success or an `ErrorType` on failure. - fn set_non_stroking_cmyk( - &mut self, - c: f32, - m: f32, - y: f32, - k: f32, - ) -> Result<(), Self::ErrorType>; -} - -/// Defines methods to handle PDF Text Object operators. -pub trait TextObjectOps { - /// The error type that can be returned by operator handling methods. - type ErrorType: std::fmt::Debug + std::fmt::Display; - /// Begins a text object, initializing the text transformation matrices. - /// - /// # Returns - /// - /// A `Result` indicating success or an `ErrorType` on failure. - fn begin_text_object(&mut self) -> Result<(), Self::ErrorType>; - - /// Ends a text object, discarding the text transformation matrices. - /// - /// # Returns - /// - /// A `Result` indicating success or an `ErrorType` on failure. - fn end_text_object(&mut self) -> Result<(), Self::ErrorType>; -} - -/// Defines methods to handle PDF Text State operators. -pub trait TextStateOps { - /// The error type that can be returned by operator handling methods. - type ErrorType: std::fmt::Debug + std::fmt::Display; - /// Sets the character spacing. - /// - /// # Parameters - /// - /// - `spacing`: The character spacing in unscaled text space units. - /// - /// # Returns - /// - /// A `Result` indicating success or an `ErrorType` on failure. - fn set_character_spacing(&mut self, spacing: f32) -> Result<(), Self::ErrorType>; - - /// Sets the word spacing. - /// - /// # Parameters - /// - /// - `spacing`: The word spacing in unscaled text space units. - /// - /// # Returns - /// - /// A `Result` indicating success or an `ErrorType` on failure. - fn set_word_spacing(&mut self, spacing: f32) -> Result<(), Self::ErrorType>; - - /// Sets the horizontal scaling for text. - /// - /// # Parameters - /// - /// - `scale_percent`: The horizontal scaling factor as a percentage (e.g., 100.0 for 100%). - /// - /// # Returns - /// - /// A `Result` indicating success or an `ErrorType` on failure. - fn set_horizontal_text_scaling(&mut self, scale_percent: f32) -> Result<(), Self::ErrorType>; - - /// Sets the text leading (vertical distance between baselines). - /// - /// # Parameters - /// - /// - `leading`: The text leading in unscaled text space units. - /// - /// # Returns - /// - /// A `Result` indicating success or an `ErrorType` on failure. - fn set_text_leading(&mut self, leading: f32) -> Result<(), Self::ErrorType>; - - /// Sets the text font and size. - /// - /// # Parameters - /// - /// - `font_name`: The name of the font resource. - /// - `size`: The font size in unscaled text space units. - /// - /// # Returns - /// - /// A `Result` indicating success or an `ErrorType` on failure. - fn set_font_and_size(&mut self, font_name: &[u8], size: f32) -> Result<(), Self::ErrorType>; - - /// Sets the text rendering mode (e.g., fill, stroke, clip). - /// - /// # Parameters - /// - /// - `mode`: An integer representing the rendering mode. - /// - /// # Returns - /// - /// A `Result` indicating success or an `ErrorType` on failure. - fn set_text_rendering_mode(&mut self, mode: TextRenderingMode) -> Result<(), Self::ErrorType>; - - /// Sets the text rise (vertical baseline offset). - /// - /// # Parameters - /// - /// - `rise`: The text rise in unscaled text space units. - /// - /// # Returns - /// - /// A `Result` indicating success or an `ErrorType` on failure. - fn set_text_rise(&mut self, rise: f32) -> Result<(), Self::ErrorType>; -} - -/// Defines methods to handle PDF Text Positioning operators. -pub trait TextPositioningOps { - /// The error type that can be returned by operator handling methods. - type ErrorType: std::fmt::Debug + std::fmt::Display; - /// Moves the text position to the start of the next line, offset by (tx, ty). - /// - /// # Parameters - /// - /// - `tx`: The horizontal offset in unscaled text space units. - /// - `ty`: The vertical offset in unscaled text space units. - /// - /// # Returns - /// - /// A `Result` indicating success or an `ErrorType` on failure. - fn move_text_position(&mut self, tx: f32, ty: f32) -> Result<(), Self::ErrorType>; - - /// Moves the text position and sets the text leading. - /// The leading is set to -ty. - /// - /// # Parameters - /// - /// - `tx`: The horizontal offset in unscaled text space units. - /// - `ty`: The vertical offset in unscaled text space units; leading is set to -ty. - /// - /// # Returns - /// - /// A `Result` indicating success or an `ErrorType` on failure. - fn move_text_position_and_set_leading( - &mut self, - tx: f32, - ty: f32, - ) -> Result<(), Self::ErrorType>; - - /// Sets the text matrix and text line matrix. - /// - /// # Parameters - /// - /// - `transform`: The transformation matrix to set as the text matrix and text line matrix. - /// - /// # Returns - /// - /// A `Result` indicating success or an `ErrorType` on failure. - fn set_text_matrix(&mut self, transform: &Transform) -> Result<(), Self::ErrorType>; - - /// Moves to the start of the next text line, using the current text leading. - /// - /// # Returns - /// - /// A `Result` indicating success or an `ErrorType` on failure. - fn move_to_start_of_next_line(&mut self) -> Result<(), Self::ErrorType>; -} - -/// Defines methods to handle PDF Text Showing operators. -pub trait TextShowingOps { - /// The error type that can be returned by operator handling methods. - type ErrorType: std::fmt::Debug + std::fmt::Display; - /// Shows a text string at the current text position. - /// - /// # Parameters - /// - /// - `text`: The text item to show, typically encoded according to the current font. - /// - /// # Returns - /// - /// A `Result` indicating success or an `ErrorType` on failure. - fn show_text(&mut self, text: &PdfTextItem) -> Result<(), Self::ErrorType>; - - /// Shows text, allowing individual adjustments to glyph positions. - /// - /// # Parameters - /// - /// - `elements`: A slice of `PdfTextItem`s, which can be strings or numeric adjustments. - /// - /// # Returns - /// - /// A `Result` indicating success or an `ErrorType` on failure. - fn show_text_with_glyph_positioning( - &mut self, - elements: &[PdfTextItem], - ) -> Result<(), Self::ErrorType>; - - /// Moves to the next line and shows a text string. - /// - /// # Parameters - /// - /// - `text`: The text item to show. - /// - /// # Returns - /// - /// A `Result` indicating success or an `ErrorType` on failure. - fn move_to_next_line_and_show_text( - &mut self, - text: &PdfTextItem, - ) -> Result<(), Self::ErrorType>; - - /// Sets word and character spacing, moves to the next line, and shows a text string. - /// - /// # Parameters - /// - /// - `word_spacing`: The word spacing to set. - /// - `char_spacing`: The character spacing to set. - /// - `text`: The text item to show. - /// - /// # Returns - /// - /// A `Result` indicating success or an `ErrorType` on failure. - fn set_spacing_and_show_text( - &mut self, - word_spacing: f32, - char_spacing: f32, - text: &PdfTextItem, - ) -> Result<(), Self::ErrorType>; -} - -/// Defines methods to handle PDF XObject operators. -pub trait XObjectOps { - /// The error type that can be returned by operator handling methods. - type ErrorType: std::fmt::Debug + std::fmt::Display; - /// Invokes a named external object (XObject), such as an image or a form. - /// - /// # Parameters - /// - /// - `xobject_name`: The name of the XObject resource. - /// - /// # Returns - /// - /// A `Result` indicating success or an `ErrorType` on failure. - fn invoke_xobject(&mut self, xobject_name: &[u8]) -> Result<(), Self::ErrorType>; - - /// Paints an inline image. - fn paint_inline_image(&mut self, image: &pdf_image::PdfImage) -> Result<(), Self::ErrorType>; -} - -/// Defines methods to handle PDF Shading operators. -pub trait ShadingOps { - /// The error type that can be returned by operator handling methods. - type ErrorType: std::fmt::Debug + std::fmt::Display; - /// Paints an area defined by a named shading pattern. - /// - /// # Parameters - /// - /// - `shading_name`: The name of the shading resource. - /// - /// # Returns - /// - /// A `Result` indicating success or an `ErrorType` on failure. - fn paint_shading(&mut self, shading_name: &[u8]) -> Result<(), Self::ErrorType>; -} - -/// Defines methods to handle PDF Marked Content operators. -pub trait MarkedContentOps { - /// The error type that can be returned by operator handling methods. - type ErrorType: std::fmt::Debug + std::fmt::Display; - /// Defines a marked-content point, associating it with a tag. - /// - /// # Parameters - /// - /// - `tag`: The tag for the marked-content point. - /// - /// # Returns - /// - /// A `Result` indicating success or an `ErrorType` on failure. - fn mark_point(&mut self, tag: &[u8]) -> Result<(), Self::ErrorType>; - - /// Defines a marked-content point, associating it with a tag and a property list. - /// - /// # Parameters - /// - /// - `tag`: The tag for the marked-content point. - /// - `properties_name_or_dict`: The name of a property list in the resource dictionary or an inline dictionary. - /// - /// # Returns - /// - /// A `Result` indicating success or an `ErrorType` on failure. - fn mark_point_with_properties( - &mut self, - tag: &[u8], - properties_name_or_dict: &[u8], - ) -> Result<(), Self::ErrorType>; - - /// Begins a marked-content sequence with a tag. - /// - /// # Parameters - /// - /// - `tag`: The tag for the marked-content sequence. - /// - /// # Returns - /// - /// A `Result` indicating success or an `ErrorType` on failure. - fn begin_marked_content(&mut self, tag: &[u8]) -> Result<(), Self::ErrorType>; - - /// Begins a marked-content sequence with a tag and associated properties. - /// - /// # Parameters - /// - /// - `tag`: The tag for the marked-content sequence. - /// - `properties`: The name of a property list in the `/Properties` resource - /// subdictionary, or `None` when the property list is an inline dictionary. - /// - /// # Returns - /// - /// A `Result` indicating success or an `ErrorType` on failure. - fn begin_marked_content_with_properties( - &mut self, - tag: &[u8], - properties: Option<&[u8]>, - ) -> Result<(), Self::ErrorType>; - - /// Ends a marked-content sequence. - /// - /// # Returns - /// - /// A `Result` indicating success or an `ErrorType` on failure. - fn end_marked_content(&mut self) -> Result<(), Self::ErrorType>; -} - -/// The error type produced by a [`PdfOperatorBackend`] implementor. -pub type BackendError = ::ErrorType; - -/// A comprehensive backend that implements all operator categories. -/// This can be used as a blanket implementation if a backend supports everything, -/// or as a way to group all the specialized traits. -pub trait PdfOperatorBackend: - PathConstructionOps - + PathPaintingOps::ErrorType> - + ClippingPathOps::ErrorType> - + GraphicsStateOps::ErrorType> - + ColorOps::ErrorType> - + TextObjectOps::ErrorType> - + TextStateOps::ErrorType> - + TextPositioningOps::ErrorType> - + TextShowingOps::ErrorType> - + XObjectOps::ErrorType> - + ShadingOps::ErrorType> - + MarkedContentOps::ErrorType> -{ -} diff --git a/crates/pdf-content-stream-operators/src/recording_pdf_operator_backend.rs b/crates/pdf-content-stream-operators/src/recording_pdf_operator_backend.rs deleted file mode 100644 index 9eea69be..00000000 --- a/crates/pdf-content-stream-operators/src/recording_pdf_operator_backend.rs +++ /dev/null @@ -1,752 +0,0 @@ -use bytes::Bytes; -use pdf_graphics::LineCap; -use pdf_graphics::LineJoin; -use pdf_graphics::TextRenderingMode; -use pdf_graphics::transform::Transform; -use pdf_image::PdfImage; -use thiserror::Error; - -use crate::PdfTextItem; -use crate::pdf_operator_backend::*; - -/// Represents a recorded operation with its parameters. -#[derive(Debug, Clone, PartialEq)] -pub enum RecordedOperation { - MoveTo { - x: f32, - y: f32, - }, - LineTo { - x: f32, - y: f32, - }, - CurveTo { - x1: f32, - y1: f32, - x2: f32, - y2: f32, - x3: f32, - y3: f32, - }, - CurveToV { - x2: f32, - y2: f32, - x3: f32, - y3: f32, - }, - CurveToY { - x1: f32, - y1: f32, - x3: f32, - y3: f32, - }, - ClosePath, - Rectangle { - x: f32, - y: f32, - width: f32, - height: f32, - }, - StrokePath, - CloseAndStrokePath, - FillPathNonZeroWinding, - FillPathEvenOdd, - FillAndStrokePathNonZeroWinding, - FillAndStrokePathEvenOdd, - CloseFillAndStrokePathNonZeroWinding, - CloseFillAndStrokePathEvenOdd, - EndPathNoOp, - ClipPathNonZeroWinding, - ClipPathEvenOdd, - SaveGraphicsState, - RestoreGraphicsState, - ConcatMatrix { - transform: Transform, - }, - SetLineWidth { - width: f32, - }, - SetLineCap { - cap_style: LineCap, - }, - SetLineJoin { - join_style: LineJoin, - }, - SetMiterLimit { - limit: f32, - }, - SetDashPattern { - dash_array: Vec, - dash_phase: f32, - }, - SetRenderingIntent { - intent: Vec, - }, - SetFlatnessTolerance { - tolerance: f32, - }, - SetGraphicsStateFromDict { - dict_name: Vec, - }, - SetStrokingColorSpace { - name: Vec, - }, - SetNonStrokingColorSpace { - name: Vec, - }, - SetStrokingColor { - components: Vec, - }, - SetStrokingColorExtended { - components: Vec, - pattern_name: Vec, - }, - SetNonStrokingColor { - components: Vec, - }, - SetNonStrokingColorExtended { - components: Vec, - pattern_name: Vec, - }, - SetStrokingGray { - gray: f32, - }, - SetNonStrokingGray { - gray: f32, - }, - SetStrokingRgb { - r: f32, - g: f32, - b: f32, - }, - SetNonStrokingRgb { - r: f32, - g: f32, - b: f32, - }, - SetStrokingCmyk { - c: f32, - m: f32, - y: f32, - k: f32, - }, - SetNonStrokingCmyk { - c: f32, - m: f32, - y: f32, - k: f32, - }, - BeginTextObject, - EndTextObject, - SetCharacterSpacing { - spacing: f32, - }, - SetWordSpacing { - spacing: f32, - }, - SetHorizontalTextScaling { - scale_percent: f32, - }, - SetTextLeading { - leading: f32, - }, - SetFontAndSize { - font_name: Vec, - size: f32, - }, - SetTextRenderingMode { - mode: TextRenderingMode, - }, - SetTextRise { - rise: f32, - }, - MoveTextPosition { - tx: f32, - ty: f32, - }, - MoveTextPositionAndSetLeading { - tx: f32, - ty: f32, - }, - SetTextMatrix { - transform: Transform, - }, - MoveToStartOfNextLine, - ShowText { - text: PdfTextItem, - }, - ShowTextWithGlyphPositioning { - elements: Vec, - }, - MoveToNextLineAndShowText { - text: PdfTextItem, - }, - SetSpacingAndShowText { - word_spacing: f32, - char_spacing: f32, - text: PdfTextItem, - }, - InvokeXObject { - xobject_name: Vec, - }, - PaintInlineImage { - data: Bytes, - }, - PaintShading { - shading_name: Vec, - }, - MarkPoint { - tag: Vec, - }, - MarkPointWithProperties { - tag: Vec, - properties_name_or_dict: Vec, - }, - BeginMarkedContent { - tag: Vec, - }, - BeginMarkedContentWithProperties { - tag: Vec, - properties: Option>, - }, - EndMarkedContent, -} - -/// A [`PdfOperatorBackend`] implementation that records all operations called on it. -#[derive(Debug, Default, Clone, PartialEq)] -pub struct RecordingBackend { - pub operations: Vec, -} - -impl RecordingBackend { - pub fn new() -> Self { - Default::default() - } -} - -#[derive(Debug, Error)] -pub enum PdfRecordingCanvasError {} - -impl PathConstructionOps for RecordingBackend { - type ErrorType = PdfRecordingCanvasError; - fn move_to(&mut self, x: f32, y: f32) -> Result<(), Self::ErrorType> { - self.operations.push(RecordedOperation::MoveTo { x, y }); - Ok(()) - } - - fn line_to(&mut self, x: f32, y: f32) -> Result<(), Self::ErrorType> { - self.operations.push(RecordedOperation::LineTo { x, y }); - Ok(()) - } - - fn curve_to( - &mut self, - x1: f32, - y1: f32, - x2: f32, - y2: f32, - x3: f32, - y3: f32, - ) -> Result<(), Self::ErrorType> { - self.operations.push(RecordedOperation::CurveTo { - x1, - y1, - x2, - y2, - x3, - y3, - }); - Ok(()) - } - - fn curve_to_v(&mut self, x2: f32, y2: f32, x3: f32, y3: f32) -> Result<(), Self::ErrorType> { - self.operations - .push(RecordedOperation::CurveToV { x2, y2, x3, y3 }); - Ok(()) - } - - fn curve_to_y(&mut self, x1: f32, y1: f32, x3: f32, y3: f32) -> Result<(), Self::ErrorType> { - self.operations - .push(RecordedOperation::CurveToY { x1, y1, x3, y3 }); - Ok(()) - } - - fn close_path(&mut self) -> Result<(), Self::ErrorType> { - self.operations.push(RecordedOperation::ClosePath); - Ok(()) - } - - fn rectangle( - &mut self, - x: f32, - y: f32, - width: f32, - height: f32, - ) -> Result<(), Self::ErrorType> { - self.operations.push(RecordedOperation::Rectangle { - x, - y, - width, - height, - }); - Ok(()) - } -} - -impl PathPaintingOps for RecordingBackend { - type ErrorType = PdfRecordingCanvasError; - fn stroke_path(&mut self) -> Result<(), Self::ErrorType> { - self.operations.push(RecordedOperation::StrokePath); - Ok(()) - } - - fn close_and_stroke_path(&mut self) -> Result<(), Self::ErrorType> { - self.operations.push(RecordedOperation::CloseAndStrokePath); - Ok(()) - } - - fn fill_path_nonzero_winding(&mut self) -> Result<(), Self::ErrorType> { - self.operations - .push(RecordedOperation::FillPathNonZeroWinding); - Ok(()) - } - - fn fill_path_even_odd(&mut self) -> Result<(), Self::ErrorType> { - self.operations.push(RecordedOperation::FillPathEvenOdd); - Ok(()) - } - - fn fill_and_stroke_path_nonzero_winding(&mut self) -> Result<(), Self::ErrorType> { - self.operations - .push(RecordedOperation::FillAndStrokePathNonZeroWinding); - Ok(()) - } - - fn fill_and_stroke_path_even_odd(&mut self) -> Result<(), Self::ErrorType> { - self.operations - .push(RecordedOperation::FillAndStrokePathEvenOdd); - Ok(()) - } - - fn close_fill_and_stroke_path_nonzero_winding(&mut self) -> Result<(), Self::ErrorType> { - self.operations - .push(RecordedOperation::CloseFillAndStrokePathNonZeroWinding); - Ok(()) - } - - fn close_fill_and_stroke_path_even_odd(&mut self) -> Result<(), Self::ErrorType> { - self.operations - .push(RecordedOperation::CloseFillAndStrokePathEvenOdd); - Ok(()) - } - - fn end_path_no_op(&mut self) -> Result<(), Self::ErrorType> { - self.operations.push(RecordedOperation::EndPathNoOp); - Ok(()) - } -} - -impl ClippingPathOps for RecordingBackend { - type ErrorType = PdfRecordingCanvasError; - fn clip_path_nonzero_winding(&mut self) -> Result<(), Self::ErrorType> { - self.operations - .push(RecordedOperation::ClipPathNonZeroWinding); - Ok(()) - } - - fn clip_path_even_odd(&mut self) -> Result<(), Self::ErrorType> { - self.operations.push(RecordedOperation::ClipPathEvenOdd); - Ok(()) - } -} - -impl GraphicsStateOps for RecordingBackend { - type ErrorType = PdfRecordingCanvasError; - fn save_graphics_state(&mut self) -> Result<(), Self::ErrorType> { - self.operations.push(RecordedOperation::SaveGraphicsState); - Ok(()) - } - - fn restore_graphics_state(&mut self) -> Result<(), Self::ErrorType> { - self.operations - .push(RecordedOperation::RestoreGraphicsState); - Ok(()) - } - - fn concat_matrix(&mut self, transform: &Transform) -> Result<(), Self::ErrorType> { - self.operations.push(RecordedOperation::ConcatMatrix { - transform: *transform, - }); - Ok(()) - } - - fn set_line_width(&mut self, width: f32) -> Result<(), Self::ErrorType> { - self.operations - .push(RecordedOperation::SetLineWidth { width }); - Ok(()) - } - - fn set_line_cap(&mut self, cap_style: LineCap) -> Result<(), Self::ErrorType> { - self.operations - .push(RecordedOperation::SetLineCap { cap_style }); - Ok(()) - } - - fn set_line_join(&mut self, join_style: LineJoin) -> Result<(), Self::ErrorType> { - self.operations - .push(RecordedOperation::SetLineJoin { join_style }); - Ok(()) - } - - fn set_miter_limit(&mut self, limit: f32) -> Result<(), Self::ErrorType> { - self.operations - .push(RecordedOperation::SetMiterLimit { limit }); - Ok(()) - } - - fn set_dash_pattern( - &mut self, - dash_array: &[f32], - dash_phase: f32, - ) -> Result<(), Self::ErrorType> { - self.operations.push(RecordedOperation::SetDashPattern { - dash_array: dash_array.to_vec(), - dash_phase, - }); - Ok(()) - } - - fn set_rendering_intent(&mut self, intent: &[u8]) -> Result<(), Self::ErrorType> { - self.operations.push(RecordedOperation::SetRenderingIntent { - intent: intent.to_vec(), - }); - Ok(()) - } - - fn set_flatness_tolerance(&mut self, tolerance: f32) -> Result<(), Self::ErrorType> { - self.operations - .push(RecordedOperation::SetFlatnessTolerance { tolerance }); - Ok(()) - } - - fn set_graphics_state_from_dict(&mut self, dict_name: &[u8]) -> Result<(), Self::ErrorType> { - self.operations - .push(RecordedOperation::SetGraphicsStateFromDict { - dict_name: dict_name.to_vec(), - }); - Ok(()) - } -} - -impl ColorOps for RecordingBackend { - type ErrorType = PdfRecordingCanvasError; - fn set_stroking_color_space(&mut self, name: &[u8]) -> Result<(), Self::ErrorType> { - self.operations - .push(RecordedOperation::SetStrokingColorSpace { - name: name.to_vec(), - }); - Ok(()) - } - - fn set_non_stroking_color_space(&mut self, name: &[u8]) -> Result<(), Self::ErrorType> { - self.operations - .push(RecordedOperation::SetNonStrokingColorSpace { - name: name.to_vec(), - }); - Ok(()) - } - - fn set_stroking_color(&mut self, components: &[f32]) -> Result<(), Self::ErrorType> { - self.operations.push(RecordedOperation::SetStrokingColor { - components: components.to_vec(), - }); - Ok(()) - } - - fn set_stroking_color_extended( - &mut self, - components: &[f32], - pattern_name: &[u8], - ) -> Result<(), Self::ErrorType> { - self.operations - .push(RecordedOperation::SetStrokingColorExtended { - components: components.to_vec(), - pattern_name: pattern_name.to_vec(), - }); - Ok(()) - } - - fn set_non_stroking_color(&mut self, components: &[f32]) -> Result<(), Self::ErrorType> { - self.operations - .push(RecordedOperation::SetNonStrokingColor { - components: components.to_vec(), - }); - Ok(()) - } - - fn set_non_stroking_color_extended( - &mut self, - components: &[f32], - pattern_name: &[u8], - ) -> Result<(), Self::ErrorType> { - self.operations - .push(RecordedOperation::SetNonStrokingColorExtended { - components: components.to_vec(), - pattern_name: pattern_name.to_vec(), - }); - Ok(()) - } - - fn set_stroking_gray(&mut self, gray: f32) -> Result<(), Self::ErrorType> { - self.operations - .push(RecordedOperation::SetStrokingGray { gray }); - Ok(()) - } - - fn set_non_stroking_gray(&mut self, gray: f32) -> Result<(), Self::ErrorType> { - self.operations - .push(RecordedOperation::SetNonStrokingGray { gray }); - Ok(()) - } - - fn set_stroking_rgb(&mut self, r: f32, g: f32, b: f32) -> Result<(), Self::ErrorType> { - self.operations - .push(RecordedOperation::SetStrokingRgb { r, g, b }); - Ok(()) - } - - fn set_non_stroking_rgb(&mut self, r: f32, g: f32, b: f32) -> Result<(), Self::ErrorType> { - self.operations - .push(RecordedOperation::SetNonStrokingRgb { r, g, b }); - Ok(()) - } - - fn set_stroking_cmyk(&mut self, c: f32, m: f32, y: f32, k: f32) -> Result<(), Self::ErrorType> { - self.operations - .push(RecordedOperation::SetStrokingCmyk { c, m, y, k }); - Ok(()) - } - - fn set_non_stroking_cmyk( - &mut self, - c: f32, - m: f32, - y: f32, - k: f32, - ) -> Result<(), Self::ErrorType> { - self.operations - .push(RecordedOperation::SetNonStrokingCmyk { c, m, y, k }); - Ok(()) - } -} - -impl TextObjectOps for RecordingBackend { - type ErrorType = PdfRecordingCanvasError; - fn begin_text_object(&mut self) -> Result<(), Self::ErrorType> { - self.operations.push(RecordedOperation::BeginTextObject); - Ok(()) - } - - fn end_text_object(&mut self) -> Result<(), Self::ErrorType> { - self.operations.push(RecordedOperation::EndTextObject); - Ok(()) - } -} - -impl TextStateOps for RecordingBackend { - type ErrorType = PdfRecordingCanvasError; - fn set_character_spacing(&mut self, spacing: f32) -> Result<(), Self::ErrorType> { - self.operations - .push(RecordedOperation::SetCharacterSpacing { spacing }); - Ok(()) - } - - fn set_word_spacing(&mut self, spacing: f32) -> Result<(), Self::ErrorType> { - self.operations - .push(RecordedOperation::SetWordSpacing { spacing }); - Ok(()) - } - - fn set_horizontal_text_scaling(&mut self, scale_percent: f32) -> Result<(), Self::ErrorType> { - self.operations - .push(RecordedOperation::SetHorizontalTextScaling { scale_percent }); - Ok(()) - } - - fn set_text_leading(&mut self, leading: f32) -> Result<(), Self::ErrorType> { - self.operations - .push(RecordedOperation::SetTextLeading { leading }); - Ok(()) - } - - fn set_font_and_size(&mut self, font_name: &[u8], size: f32) -> Result<(), Self::ErrorType> { - self.operations.push(RecordedOperation::SetFontAndSize { - font_name: font_name.to_vec(), - size, - }); - Ok(()) - } - - fn set_text_rendering_mode(&mut self, mode: TextRenderingMode) -> Result<(), Self::ErrorType> { - self.operations - .push(RecordedOperation::SetTextRenderingMode { mode }); - Ok(()) - } - - fn set_text_rise(&mut self, rise: f32) -> Result<(), Self::ErrorType> { - self.operations - .push(RecordedOperation::SetTextRise { rise }); - Ok(()) - } -} - -impl TextPositioningOps for RecordingBackend { - type ErrorType = PdfRecordingCanvasError; - fn move_text_position(&mut self, tx: f32, ty: f32) -> Result<(), Self::ErrorType> { - self.operations - .push(RecordedOperation::MoveTextPosition { tx, ty }); - Ok(()) - } - - fn move_text_position_and_set_leading( - &mut self, - tx: f32, - ty: f32, - ) -> Result<(), Self::ErrorType> { - self.operations - .push(RecordedOperation::MoveTextPositionAndSetLeading { tx, ty }); - Ok(()) - } - - fn set_text_matrix(&mut self, transform: &Transform) -> Result<(), Self::ErrorType> { - self.operations.push(RecordedOperation::SetTextMatrix { - transform: *transform, - }); - Ok(()) - } - - fn move_to_start_of_next_line(&mut self) -> Result<(), Self::ErrorType> { - self.operations - .push(RecordedOperation::MoveToStartOfNextLine); - Ok(()) - } -} - -impl TextShowingOps for RecordingBackend { - type ErrorType = PdfRecordingCanvasError; - fn show_text(&mut self, text: &PdfTextItem) -> Result<(), Self::ErrorType> { - self.operations - .push(RecordedOperation::ShowText { text: text.clone() }); - Ok(()) - } - - fn show_text_with_glyph_positioning( - &mut self, - elements: &[PdfTextItem], - ) -> Result<(), Self::ErrorType> { - self.operations - .push(RecordedOperation::ShowTextWithGlyphPositioning { - elements: elements.to_vec(), - }); - Ok(()) - } - - fn move_to_next_line_and_show_text( - &mut self, - text: &PdfTextItem, - ) -> Result<(), Self::ErrorType> { - self.operations - .push(RecordedOperation::MoveToNextLineAndShowText { text: text.clone() }); - Ok(()) - } - - fn set_spacing_and_show_text( - &mut self, - word_spacing: f32, - char_spacing: f32, - text: &PdfTextItem, - ) -> Result<(), Self::ErrorType> { - self.operations - .push(RecordedOperation::SetSpacingAndShowText { - word_spacing, - char_spacing, - text: text.clone(), - }); - Ok(()) - } -} - -impl XObjectOps for RecordingBackend { - type ErrorType = PdfRecordingCanvasError; - fn invoke_xobject(&mut self, xobject_name: &[u8]) -> Result<(), Self::ErrorType> { - self.operations.push(RecordedOperation::InvokeXObject { - xobject_name: xobject_name.to_vec(), - }); - Ok(()) - } - - fn paint_inline_image(&mut self, image: &PdfImage) -> Result<(), Self::ErrorType> { - self.operations.push(RecordedOperation::PaintInlineImage { - data: image.shared_data(), - }); - Ok(()) - } -} - -impl ShadingOps for RecordingBackend { - type ErrorType = PdfRecordingCanvasError; - fn paint_shading(&mut self, shading_name: &[u8]) -> Result<(), Self::ErrorType> { - self.operations.push(RecordedOperation::PaintShading { - shading_name: shading_name.to_vec(), - }); - Ok(()) - } -} - -impl MarkedContentOps for RecordingBackend { - type ErrorType = PdfRecordingCanvasError; - fn mark_point(&mut self, tag: &[u8]) -> Result<(), Self::ErrorType> { - self.operations - .push(RecordedOperation::MarkPoint { tag: tag.to_vec() }); - Ok(()) - } - - fn mark_point_with_properties( - &mut self, - tag: &[u8], - properties_name_or_dict: &[u8], - ) -> Result<(), Self::ErrorType> { - self.operations - .push(RecordedOperation::MarkPointWithProperties { - tag: tag.to_vec(), - properties_name_or_dict: properties_name_or_dict.to_vec(), - }); - Ok(()) - } - - fn begin_marked_content(&mut self, tag: &[u8]) -> Result<(), Self::ErrorType> { - self.operations - .push(RecordedOperation::BeginMarkedContent { tag: tag.to_vec() }); - Ok(()) - } - - fn begin_marked_content_with_properties( - &mut self, - tag: &[u8], - properties: Option<&[u8]>, - ) -> Result<(), Self::ErrorType> { - self.operations - .push(RecordedOperation::BeginMarkedContentWithProperties { - tag: tag.to_vec(), - properties: properties.map(<[u8]>::to_vec), - }); - Ok(()) - } - - fn end_marked_content(&mut self) -> Result<(), Self::ErrorType> { - self.operations.push(RecordedOperation::EndMarkedContent); - Ok(()) - } -} - -impl PdfOperatorBackend for RecordingBackend {} diff --git a/crates/pdf-content-stream-operators/src/variants.rs b/crates/pdf-content-stream-operators/src/variants.rs index 8c741644..e8630d4f 100644 --- a/crates/pdf-content-stream-operators/src/variants.rs +++ b/crates/pdf-content-stream-operators/src/variants.rs @@ -5,10 +5,8 @@ use pdf_image::PdfImage; use pdf_object_reader::{ReadResult, pdf_string::PdfString}; use crate::{ - OperandContext, PdfTextItem, - color_operators::ColorOperands, + OperandContext, PdfTextItem, color_operators::ColorOperands, marked_content_operators::BeginMarkedContentWithProps, - pdf_operator_backend::{BackendError, PdfOperatorBackend}, }; /// One PDF content-stream operation with its decoded operands. @@ -595,131 +593,18 @@ impl PdfOperatorVariant { }; Ok(Some(operator)) } - - /// Dispatches the operation to `backend`. - pub fn call(&self, backend: &mut T) -> Result<(), BackendError> { - match self { - 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 { - 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(), - Self::FillPathEvenOdd => backend.fill_path_even_odd(), - Self::FillAndStrokePathNonZero => backend.fill_and_stroke_path_nonzero_winding(), - Self::FillAndStrokePathEvenOdd => backend.fill_and_stroke_path_even_odd(), - Self::CloseFillAndStrokePathNonZero => { - backend.close_fill_and_stroke_path_nonzero_winding() - } - Self::CloseFillAndStrokePathEvenOdd => backend.close_fill_and_stroke_path_even_odd(), - Self::EndPath => backend.end_path_no_op(), - Self::ClipNonZero => backend.clip_path_nonzero_winding(), - Self::ClipEvenOdd => backend.clip_path_even_odd(), - Self::SetGrayFill { 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 { 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 { 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 { 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 { 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, + ArrayContext, ObjectReader, 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()); @@ -776,38 +661,4 @@ mod tests { 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 = PdfImage::inline( - 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 ef9a2cc1..aed745e4 100644 --- a/crates/pdf-content-stream/src/content_stream.rs +++ b/crates/pdf-content-stream/src/content_stream.rs @@ -74,8 +74,7 @@ mod tests { use pdf_content_stream_operators::{ PdfTextItem, - recording_pdf_operator_backend::{RecordedOperation, RecordingBackend}, - variants::PdfOperatorVariant, + variants::PdfOperatorVariant::{self, *}, }; use pdf_object_reader::{ dictionary::Dictionary, object_error::ObjectError, object_resolver::ObjectResolver, @@ -106,16 +105,6 @@ mod tests { ) } - fn recorded_operations(operators: &[PdfOperatorVariant]) -> Vec { - let mut backend = RecordingBackend::default(); - for operator in operators { - operator - .call(&mut backend) - .expect("operator should dispatch"); - } - backend.operations - } - struct MapResolver { objects: BTreeMap, } @@ -164,20 +153,14 @@ mod tests { assert_eq!(parsed.id, 0); assert!(matches!( - parsed.operators.first(), - Some(PdfOperatorVariant::BeginCompatibility) - )); - assert!(matches!( - parsed.operators.get(1), - Some(PdfOperatorVariant::EndCompatibility) - )); - assert_eq!( - recorded_operations(&parsed.operators), - vec![ - RecordedOperation::MoveTo { x: 10.0, y: 20.0 }, - RecordedOperation::LineTo { x: 30.0, y: 40.0 }, + parsed.operators.as_slice(), + [ + BeginCompatibility, + EndCompatibility, + MoveTo { x: 10.0, y: 20.0 }, + LineTo { x: 30.0, y: 40.0 } ] - ); + )); } #[test] @@ -222,7 +205,7 @@ mod tests { } #[test] - fn parsed_inline_image_can_be_dispatched() { + fn content_stream_read_parses_inline_image() { let reader = pdf_object_reader::ObjectReader::new(&PassthroughResolver); let parsed = reader .read::(&ObjectVariant::Stream(stream_object( @@ -230,29 +213,8 @@ mod tests { b"BI /W 1 /H 1 /BPC 8 /CS /G ID \x00 EI", ))) .expect("inline image should parse"); - let inline_image = parsed - .operators - .first() - .and_then(|operator| match operator { - PdfOperatorVariant::InlineImage(image) => Some(image), - _ => None, - }) - .expect("expected inline image"); - - let mut backend = RecordingBackend::default(); - parsed - .operators - .first() - .expect("inline image operator") - .call(&mut backend) - .expect("dispatch should succeed"); - assert_eq!( - backend.operations, - vec![RecordedOperation::PaintInlineImage { - data: inline_image.shared_data(), - }] - ); + assert!(matches!(parsed.operators.as_slice(), [InlineImage(_)])); } #[test] @@ -262,10 +224,7 @@ mod tests { .read::(&ObjectVariant::Stream(stream_object(1, b"@ q"))) .expect("stream should parse"); - assert_eq!( - recorded_operations(&parsed.operators), - vec![RecordedOperation::SaveGraphicsState] - ); + assert!(matches!(parsed.operators.as_slice(), [SaveGraphicsState])); } #[test] @@ -290,10 +249,10 @@ mod tests { .expect("contents array should parse"); assert_eq!(parsed.id, 0); - assert_eq!( - recorded_operations(&parsed.operators), - vec![RecordedOperation::MoveTo { x: 1.0, y: 2.0 }] - ); + assert!(matches!( + parsed.operators.as_slice(), + [MoveTo { x: 1.0, y: 2.0 }] + )); } #[test] @@ -336,10 +295,10 @@ mod tests { .expect("malformed inline image should be skipped"); assert_eq!(parsed.id, 0); - assert_eq!( - recorded_operations(&parsed.operators), - vec![RecordedOperation::RestoreGraphicsState] - ); + assert!(matches!( + parsed.operators.as_slice(), + [RestoreGraphicsState] + )); assert_eq!( reader .content_stream_ids() @@ -394,13 +353,10 @@ mod tests { .expect("page should have a content stream"); assert_eq!(content_stream.id, 0); - assert_eq!( - recorded_operations(&content_stream.operators), - vec![ - RecordedOperation::SaveGraphicsState, - RecordedOperation::RestoreGraphicsState, - ] - ); + assert!(matches!( + content_stream.operators.as_slice(), + [SaveGraphicsState, RestoreGraphicsState] + )); let next = reader .read::(&ObjectVariant::Stream(stream_object(3, b"q"))) diff --git a/crates/pdf-content-stream/tests/parser_api.rs b/crates/pdf-content-stream/tests/parser_api.rs index 27d4dab1..262dc01a 100644 --- a/crates/pdf-content-stream/tests/parser_api.rs +++ b/crates/pdf-content-stream/tests/parser_api.rs @@ -4,8 +4,7 @@ use std::sync::Arc; use pdf_content_stream::ContentStream; use pdf_content_stream_operators::{ PdfTextItem, - recording_pdf_operator_backend::{RecordedOperation, RecordingBackend}, - variants::PdfOperatorVariant, + variants::PdfOperatorVariant::{self, *}, }; use pdf_object_reader::{ dictionary::Dictionary, object_error::ObjectError, object_resolver::ObjectResolver, @@ -34,16 +33,6 @@ fn stream_object(object_number: usize, data: &[u8]) -> StreamObject { ) } -fn recorded_operations(operators: &[PdfOperatorVariant]) -> Vec { - let mut backend = RecordingBackend::default(); - for operator in operators { - operator - .call(&mut backend) - .expect("operator should dispatch"); - } - backend.operations -} - struct MapResolver { objects: BTreeMap, } @@ -92,20 +81,14 @@ fn content_stream_read_returns_expected_operators_and_assigns_ids() { assert_eq!(parsed.id, 0); assert!(matches!( - parsed.operators.first(), - Some(PdfOperatorVariant::BeginCompatibility) - )); - assert!(matches!( - parsed.operators.get(1), - Some(PdfOperatorVariant::EndCompatibility) - )); - assert_eq!( - recorded_operations(&parsed.operators), - vec![ - RecordedOperation::MoveTo { x: 10.0, y: 20.0 }, - RecordedOperation::LineTo { x: 30.0, y: 40.0 }, + parsed.operators.as_slice(), + [ + BeginCompatibility, + EndCompatibility, + MoveTo { x: 10.0, y: 20.0 }, + LineTo { x: 30.0, y: 40.0 } ] - ); + )); } #[test] @@ -156,17 +139,13 @@ fn content_stream_preserves_non_utf8_font_name() { .read::(&ObjectVariant::Stream(stream_object(1, b"/#FF 12 Tf"))) .expect("non-UTF-8 resource names should parse"); - assert_eq!( - recorded_operations(&parsed.operators), - vec![RecordedOperation::SetFontAndSize { - font_name: vec![0xFF], - size: 12.0, - }] + assert!( + matches!(parsed.operators.as_slice(), [SetFont { name, size: 12.0 }] if **name == [0xFF]) ); } #[test] -fn parsed_inline_image_can_be_dispatched() { +fn content_stream_read_parses_inline_image() { let reader = pdf_object_reader::ObjectReader::new(&PassthroughResolver); let parsed = reader .read::(&ObjectVariant::Stream(stream_object( @@ -174,29 +153,8 @@ fn parsed_inline_image_can_be_dispatched() { b"BI /W 1 /H 1 /BPC 8 /CS /G ID \x00 EI", ))) .expect("inline image should parse"); - let inline_image = parsed - .operators - .first() - .and_then(|operator| match operator { - PdfOperatorVariant::InlineImage(image) => Some(image), - _ => None, - }) - .expect("expected inline image"); - - let mut backend = RecordingBackend::default(); - parsed - .operators - .first() - .expect("inline image operator") - .call(&mut backend) - .expect("dispatch should succeed"); - assert_eq!( - backend.operations, - vec![RecordedOperation::PaintInlineImage { - data: inline_image.shared_data(), - }] - ); + assert!(matches!(parsed.operators.as_slice(), [InlineImage(_)])); } #[test] @@ -206,10 +164,7 @@ fn content_stream_read_skips_unknown_operator_and_recovers() { .read::(&ObjectVariant::Stream(stream_object(1, b"@ q"))) .expect("stream should parse"); - assert_eq!( - recorded_operations(&parsed.operators), - vec![RecordedOperation::SaveGraphicsState] - ); + assert!(matches!(parsed.operators.as_slice(), [SaveGraphicsState])); } #[test] @@ -257,10 +212,10 @@ fn optional_contents_parses_stream_arrays_and_allocates_monotonically() { .expect("page should have a content stream"); assert_eq!(content_stream.id, 0); - assert_eq!( - recorded_operations(&content_stream.operators), - vec![RecordedOperation::MoveTo { x: 1.0, y: 2.0 }] - ); + assert!(matches!( + content_stream.operators.as_slice(), + [MoveTo { x: 1.0, y: 2.0 }] + )); let next = reader .read::(&ObjectVariant::Stream(stream_object(3, b"q"))) @@ -308,10 +263,10 @@ fn content_stream_read_skips_malformed_inline_image_and_consumes_an_id() { .expect("malformed inline image should be skipped"); assert_eq!(parsed.id, 0); - assert_eq!( - recorded_operations(&parsed.operators), - vec![RecordedOperation::RestoreGraphicsState] - ); + assert!(matches!( + parsed.operators.as_slice(), + [RestoreGraphicsState] + )); assert_eq!( reader .content_stream_ids()