Skip to main content

pdf_interpret/
soft_mask.rs

1use crate::cache::Cache;
2use crate::color::{Color, ColorComponents, ColorSpace};
3use crate::context::Context;
4use crate::device::Device;
5use crate::function::Function;
6use crate::interpret::state::State;
7use crate::util::hash128;
8use crate::x_object::{FormXObject, draw_form_xobject};
9use crate::{CacheKey, InterpreterSettings};
10use kurbo::Affine;
11use pdf_syntax::object::Name;
12use pdf_syntax::object::ObjectIdentifier;
13use pdf_syntax::object::Stream;
14use pdf_syntax::object::dict::keys::*;
15use pdf_syntax::object::{Dict, Object};
16use pdf_syntax::page::Resources;
17use pdf_syntax::xref::XRef;
18use smallvec::smallvec;
19use std::fmt::Debug;
20use std::hash::{Hash, Hasher};
21use std::ops::Deref;
22use std::sync::Arc;
23
24/// Type type of mask.
25#[derive(Clone, Copy, Debug, PartialEq, Eq)]
26pub enum MaskType {
27    /// A luminosity mask.
28    Luminosity,
29    /// An alpha mask.
30    Alpha,
31}
32
33/// A transfer function to apply to the opacity values of a mask.
34pub struct TransferFunction(Function);
35
36impl TransferFunction {
37    /// Apply the transfer function to the given value.
38    ///
39    /// The input value needs to be between 0 and 1 and the return value is
40    /// guaranteed to be between 0 and 1.
41    #[inline]
42    pub fn apply(&self, val: f32) -> f32 {
43        self.0
44            .eval(smallvec![val])
45            .and_then(|v| v.first().copied())
46            .unwrap_or(0.0)
47            .clamp(0.0, 1.0)
48    }
49}
50
51struct Repr<'a> {
52    /// The depth the parent interpretation sat at (#318): a fresh Context
53    /// would otherwise restart the bound at zero, and a cycle alternating
54    /// between constructs would never reach it.
55    nesting_depth: u32,
56    obj_id: ObjectIdentifier,
57    group: FormXObject<'a>,
58    mask_type: MaskType,
59    parent_resources: Resources<'a>,
60    root_transform: Affine,
61    bbox: kurbo::Rect,
62    object_cache: Cache,
63    transfer_function: Option<TransferFunction>,
64    settings: InterpreterSettings,
65    background: Color,
66    xref: &'a XRef,
67}
68
69impl Hash for Repr<'_> {
70    fn hash<H: Hasher>(&self, state: &mut H) {
71        self.obj_id.hash(state);
72        self.root_transform.cache_key().hash(state);
73    }
74}
75
76/// A soft mask.
77#[derive(Clone, Hash)]
78pub struct SoftMask<'a>(Arc<Repr<'a>>);
79
80impl Debug for SoftMask<'_> {
81    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
82        write!(f, "SoftMask({:?})", self.0.obj_id)
83    }
84}
85
86impl PartialEq for SoftMask<'_> {
87    fn eq(&self, other: &Self) -> bool {
88        self.0.obj_id == other.0.obj_id
89    }
90}
91
92impl Eq for SoftMask<'_> {}
93
94impl CacheKey for SoftMask<'_> {
95    fn cache_key(&self) -> u128 {
96        hash128(self)
97    }
98}
99
100impl<'a> SoftMask<'a> {
101    pub(crate) fn new(
102        dict: &Dict<'a>,
103        context: &Context<'a>,
104        parent_resources: Resources<'a>,
105    ) -> Option<Self> {
106        // TODO: With this setup, if there is a luminosity mask and alpha mask pointing to the
107        // same xobject, the ID will be the same.
108        let obj_id = dict.get_ref(G)?.into();
109        let group_stream = dict.get::<Stream<'_>>(G)?;
110        let group = FormXObject::new(&group_stream, &context.settings.warning_sink)?;
111        let cs = ColorSpace::new(
112            group.dict.get::<Dict<'_>>(GROUP)?.get::<Object<'_>>(CS)?,
113            &context.object_cache,
114            &context.settings.warning_sink,
115        )?;
116        let transfer_function = dict
117            .get::<Object<'_>>(TR)
118            .and_then(|o| Function::new(&o))
119            .map(TransferFunction);
120        let (mask_type, background) = match dict.get::<Name>(S)?.deref() {
121            LUMINOSITY => {
122                let color = dict
123                    .get::<ColorComponents>(BC)
124                    .map(|c| Color::new(cs, c, 1.0))
125                    .unwrap_or(Color::new(ColorSpace::device_gray(), smallvec![0.0], 1.0));
126
127                (MaskType::Luminosity, color)
128            }
129            ALPHA => (
130                MaskType::Alpha,
131                // Background color attribute should only be used with luminosity masks.
132                Color::new(ColorSpace::device_gray(), smallvec![0.0], 1.0),
133            ),
134            _ => return None,
135        };
136
137        Some(Self(Arc::new(Repr {
138            nesting_depth: context.nesting_depth(),
139            obj_id,
140            group,
141            mask_type,
142            root_transform: context.get().ctm,
143            transfer_function,
144            bbox: context.bbox(),
145            object_cache: context.object_cache.clone(),
146            settings: context.settings.clone(),
147            xref: context.xref,
148            background,
149            parent_resources,
150        })))
151    }
152
153    /// Interpret the contents of the mask into the given device.
154    pub fn interpret(&self, device: &mut impl Device<'a>) {
155        let state = State::new(self.0.root_transform);
156        let mut ctx = Context::new_with(
157            self.0.root_transform,
158            self.0.bbox,
159            self.0.object_cache.clone(),
160            self.0.xref,
161            self.0.settings.clone(),
162            state,
163            // The parent's depth, not zero: a fresh Context would restart the
164            // bound and a cycle alternating between constructs would never reach
165            // it. Measured before this: rc=134 on a 1082-byte pattern/soft-mask
166            // cycle (#318).
167            self.0.nesting_depth + 1,
168        );
169        draw_form_xobject(&self.0.parent_resources, &self.0.group, &mut ctx, device);
170    }
171
172    /// Return the object identifier of the mask.
173    ///
174    /// This can be used as a unique identifier for caching purposes.
175    pub fn id(&self) -> ObjectIdentifier {
176        self.0.obj_id
177    }
178
179    /// Return the underlying mask type.
180    pub fn mask_type(&self) -> MaskType {
181        self.0.mask_type
182    }
183
184    /// The background color against which the mask should be composited.
185    pub fn background_color(&self) -> Color {
186        self.0.background.clone()
187    }
188
189    /// Return the transfer function that should be used for the mask.
190    pub fn transfer_function(&self) -> Option<&TransferFunction> {
191        self.0.transfer_function.as_ref()
192    }
193}