Skip to main content

hayro_interpret/
soft_mask.rs

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