Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions crates/pdf-canvas/src/canvas_graphics_state_ops.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::sync::Arc;

use pdf_content_stream_operators::pdf_operator_backend::GraphicsStateOps;
use pdf_graphics::{DashPattern, LineCap, LineJoin, transform::Transform};
use pdf_graphics::{DashPattern, LineCap, LineJoin, MaskMode, transform::Transform};
use pdf_resources::{
external_graphics_state::ExternalGraphicsStateKey, resource::Resource, xobject::XObject,
};
Expand Down Expand Up @@ -135,6 +135,10 @@ impl<B: CanvasBackend> GraphicsStateOps for PdfCanvas<'_, B> {
ExternalGraphicsStateKey::SoftMask(smask) => {
// Handle the `/SMask` entry from an `ExtGState` dictionary.
if let Some(smask) = smask.as_ref() {
if matches!(&smask.mask_type, MaskMode::Unknown(_)) {
continue;
}

if let XObject::Image(_) = &smask.shape {
return Err(PdfCanvasError::UnsupportedFeature(
"SoftMask with Image shape".into(),
Expand Down Expand Up @@ -166,11 +170,15 @@ impl<B: CanvasBackend> GraphicsStateOps for PdfCanvas<'_, B> {

// Enable the mask on the main canvas. Subsequent drawing operations
// will be modulated by this mask.
self.canvas
.begin_mask_layer(&arc, &transform, smask.mask_type)?;
self.canvas.begin_mask_layer(
&arc,
&transform,
smask.mask_type.clone(),
)?;

// Store the mask in the current canvas state to be used until it's finished.
self.mask = Some((Arc::clone(&arc), smask.mask_type, transform));
self.mask =
Some((Arc::clone(&arc), smask.mask_type.clone(), transform));
}
} else if let Some((mask, mask_type, transform)) = self.mask.take() {
// This branch handles the case where `/SMask` is set to `/None` in the `ExtGState`,
Expand Down
4 changes: 2 additions & 2 deletions crates/pdf-canvas/src/recording_canvas.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,14 +163,14 @@ impl RecordingCanvas {
mask_mode,
mask,
} => {
backend.begin_mask_layer(mask, transform, *mask_mode)?;
backend.begin_mask_layer(mask, transform, mask_mode.clone())?;
}
EndMaskLayer {
mask,
transform,
mask_mode,
} => {
backend.end_mask_layer(mask, transform, *mask_mode)?;
backend.end_mask_layer(mask, transform, mask_mode.clone())?;
}
}
}
Expand Down
13 changes: 11 additions & 2 deletions crates/pdf-graphics-skia/src/skia_canvas_backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -519,8 +519,12 @@ impl CanvasBackend for SkiaCanvasBackend<'_> {
&mut self,
mask: &Arc<RecordingCanvas>,
transform: &Transform,
_mask_mode: MaskMode,
mask_mode: MaskMode,
) -> Result<(), PdfCanvasError> {
if matches!(mask_mode, MaskMode::Unknown(_)) {
return Ok(());
}

self.surface.canvas().save();
let mat = to_skia_matrix(transform);
let rect = skia_safe::Rect::from_xywh(0.0, 0.0, mask.width(), mask.height());
Expand All @@ -540,6 +544,10 @@ impl CanvasBackend for SkiaCanvasBackend<'_> {
transform: &Transform,
mask_mode: MaskMode,
) -> Result<(), PdfCanvasError> {
if matches!(&mask_mode, MaskMode::Unknown(_)) {
return Ok(());
}

// Render mask into a temporary surface depending on the requested mask mode.
// - Alpha: render directly into an A8 mask surface.
// - Luminosity: render into RGBA and then convert RGB luminance into an A8 mask.
Expand All @@ -555,7 +563,7 @@ impl CanvasBackend for SkiaCanvasBackend<'_> {
};

// Create appropriate surface
let mut surface = match mask_mode {
let mut surface = match &mask_mode {
MaskMode::Alpha => {
let info =
skia_safe::ImageInfo::new_a8((mask.width() as i32, mask.height() as i32));
Expand All @@ -571,6 +579,7 @@ impl CanvasBackend for SkiaCanvasBackend<'_> {
);
make_surface(info)?
}
MaskMode::Unknown(_) => return Ok(()),
};

// Replay the recorded mask drawing operations into the temporary surface.
Expand Down
11 changes: 2 additions & 9 deletions crates/pdf-graphics/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pub mod blend_mode;
mod bounds_accumulator;
pub mod color;
pub mod dash_pattern;
pub mod mask_mode;
pub mod pdf_path;
pub mod point;
pub mod rect;
Expand All @@ -11,6 +12,7 @@ pub mod transform;
pub use blend_mode::BlendMode;
pub use bounds_accumulator::BoundsAccumulator;
pub use dash_pattern::DashPattern;
pub use mask_mode::MaskMode;
use num_derive::FromPrimitive;

/// Specifies the shape to be used at the end of open subpaths when they are stroked.
Expand Down Expand Up @@ -70,15 +72,6 @@ pub enum TextRenderingMode {
Clip,
}

/// Specifies the mode for applying a soft mask in PDF graphics.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MaskMode {
/// The soft mask is applied to the alpha channel only.
Alpha,
/// The soft mask is applied to the luminosity channel.
Luminosity,
}

/// Represents the pixel format of image data in PDF graphics.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PixelFormat {
Expand Down
20 changes: 20 additions & 0 deletions crates/pdf-graphics/src/mask_mode.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/// Specifies the mode for applying a soft mask in PDF graphics.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MaskMode {
/// The soft mask is applied to the alpha channel only.
Alpha,
/// The soft mask is applied to the luminosity channel.
Luminosity,
/// An unrecognized soft mask mode.
Unknown(String),
}

impl From<&str> for MaskMode {
fn from(value: &str) -> Self {
match value {
"Alpha" => Self::Alpha,
"Luminosity" => Self::Luminosity,
other => Self::Unknown(other.to_owned()),
}
}
}
13 changes: 1 addition & 12 deletions crates/pdf-resources/src/external_graphics_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,17 +149,6 @@ fn invalid_ext_gstate_entry_value(entry: &str, reason: impl Into<String>) -> Pdf
}
}

fn parse_mask_mode(value: &str) -> Result<MaskMode, PdfPagesError> {
match value {
"Luminosity" => Ok(MaskMode::Luminosity),
"Alpha" => Ok(MaskMode::Alpha),
other => Err(invalid_ext_gstate_entry_value(
"SMask/S",
format!("unsupported soft mask mode '{other}' (expected 'Alpha' or 'Luminosity')"),
)),
}
}

fn parse_dash_pattern(
key_name: &str,
value: &ObjectVariant,
Expand Down Expand Up @@ -239,7 +228,7 @@ fn parse_soft_mask(
) -> Result<ExternalGraphicsStateKey, PdfPagesError> {
let smask = match value {
ObjectVariant::Dictionary(dict) => {
let mask_type = parse_mask_mode(dict.required_str("S", objects)?)?;
let mask_type = MaskMode::from(dict.required_str("S", objects)?);

// Parse the "G" key for the `XObject`
let content = dict.get_or_err("G")?;
Expand Down
Loading