Skip to main content

hayro_interpret/x_object/
soft_mask.rs

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