Skip to main content

hayro_interpret/
pattern.rs

1//! PDF patterns.
2
3use crate::cache::Cache;
4use crate::color::{Color, ColorSpace};
5use crate::context::{Context, InterpreterCache};
6use crate::device::Device;
7use crate::font::GlyphRun;
8use crate::interpret::state::{ActiveTransferFunction, State};
9use crate::shading::Shading;
10use crate::util::{Float32Ext, RectExt, hash128};
11use crate::x_object::soft_mask::SoftMask;
12use crate::{BlendMode, CacheKey, ClipPath, DrawMode, DrawProps, Image, ImageDrawProps};
13use crate::{FillRule, InterpreterSettings, Paint, interpret};
14use hayro_syntax::content::TypedIter;
15use hayro_syntax::object::Dict;
16use hayro_syntax::object::Stream;
17use hayro_syntax::object::dict::keys::{
18    BBOX, EXT_G_STATE, MATRIX, PAINT_TYPE, RESOURCES, SHADING, X_STEP, Y_STEP,
19};
20use hayro_syntax::object::{Object, dict_or_stream};
21use hayro_syntax::page::Resources;
22use hayro_syntax::xref::XRef;
23use kurbo::{Affine, BezPath, Rect, Shape};
24use std::fmt::{Debug, Formatter};
25use std::sync::Arc;
26
27/// A PDF pattern.
28#[derive(Debug, Clone)]
29pub enum Pattern<'a> {
30    /// A shading pattern.
31    Shading(ShadingPattern),
32    /// A tiling pattern.
33    Tiling(Box<TilingPattern<'a>>),
34}
35
36impl<'a> Pattern<'a> {
37    pub(crate) fn new(object: Object<'a>, ctx: &Context<'a>) -> Option<Self> {
38        match object {
39            Object::Dict(dict) => Some(Self::Shading(ShadingPattern::new(
40                &dict,
41                &ctx.interpreter_cache.object_cache,
42                ctx.get().graphics_state.non_stroke_alpha,
43            )?)),
44            Object::Stream(stream) => {
45                Some(Self::Tiling(Box::new(TilingPattern::new(stream, ctx)?)))
46            }
47            _ => None,
48        }
49    }
50
51    pub(crate) fn pre_concat_transform(&mut self, transform: Affine) {
52        match self {
53            Self::Shading(p) => {
54                p.matrix = transform * p.matrix;
55                let transformed_clip_path = p.shading.clip_path.clone().map(|r| p.matrix * r);
56                Arc::make_mut(&mut p.shading).clip_path = transformed_clip_path;
57            }
58            Self::Tiling(p) => p.matrix = transform * p.matrix,
59        }
60    }
61
62    pub(crate) fn set_transfer_function(&mut self, tf: ActiveTransferFunction) {
63        if let Self::Shading(p) = self {
64            p.transfer_function = Some(tf);
65        }
66    }
67}
68
69impl CacheKey for Pattern<'_> {
70    fn cache_key(&self) -> u128 {
71        match self {
72            Self::Shading(p) => p.cache_key(),
73            Self::Tiling(p) => p.cache_key(),
74        }
75    }
76}
77
78/// A shading pattern.
79#[derive(Clone, Debug)]
80pub struct ShadingPattern {
81    /// The underlying shading of the pattern.
82    pub shading: Arc<Shading>,
83    /// A transformation matrix to apply prior to rendering.
84    pub matrix: Affine,
85    /// An additional opacity to apply to the shading pattern.
86    pub opacity: f32,
87    /// An optional transfer function to apply to the shading's output colors.
88    pub transfer_function: Option<ActiveTransferFunction>,
89}
90
91impl ShadingPattern {
92    pub(crate) fn new(dict: &Dict<'_>, cache: &Cache, opacity: f32) -> Option<Self> {
93        let shading = dict.get::<Object<'_>>(SHADING).and_then(|o| {
94            let (dict, stream) = dict_or_stream(&o)?;
95
96            Shading::new(dict, stream, cache)
97        })?;
98        let matrix = dict
99            .get::<[f64; 6]>(MATRIX)
100            .map(Affine::new)
101            .unwrap_or_default();
102
103        if dict.contains_key(EXT_G_STATE) {
104            warn!("shading patterns with ext_g_state are not supported yet");
105        }
106
107        Some(Self {
108            shading: Arc::new(shading),
109            opacity,
110            matrix,
111            transfer_function: None,
112        })
113    }
114}
115
116impl CacheKey for ShadingPattern {
117    fn cache_key(&self) -> u128 {
118        hash128(&(self.shading.cache_key(), self.matrix.cache_key()))
119    }
120}
121
122/// A tiling pattern.
123#[derive(Clone)]
124pub struct TilingPattern<'a> {
125    cache_key: u128,
126    ctx_bbox: Rect,
127    /// The bbox of the tiling pattern.
128    pub bbox: Rect,
129    /// The step in the x direction.
130    pub x_step: f32,
131    /// The step in the y direction.
132    pub y_step: f32,
133    /// A transformation to apply prior to rendering.
134    pub matrix: Affine,
135    stream: Stream<'a>,
136    is_color: bool,
137    pub(crate) stroke_paint: Color,
138    pub(crate) non_stroking_paint: Color,
139    resources: Resources<'a>,
140    pub(crate) cache: InterpreterCache<'a>,
141    pub(crate) settings: InterpreterSettings,
142    pub(crate) xref: &'a XRef,
143    nesting_depth: u32,
144}
145
146impl Debug for TilingPattern<'_> {
147    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
148        f.write_str("TilingPattern")
149    }
150}
151
152impl<'a> TilingPattern<'a> {
153    pub(crate) fn new(stream: Stream<'a>, ctx: &Context<'a>) -> Option<Self> {
154        let cache_key = stream.cache_key();
155        let dict = stream.dict();
156
157        let bbox = dict.get::<hayro_syntax::object::Rect>(BBOX)?.to_kurbo();
158        let x_step = dict.get::<f32>(X_STEP)?;
159        let y_step = dict.get::<f32>(Y_STEP)?;
160
161        if x_step.is_nearly_zero() || y_step.is_nearly_zero() || bbox.is_zero_area() {
162            return None;
163        }
164
165        let is_color = dict.get::<u8>(PAINT_TYPE)? == 1;
166        let matrix = dict
167            .get::<[f64; 6]>(MATRIX)
168            .map(Affine::new)
169            .unwrap_or_default();
170
171        let state = ctx.get().clone();
172        let ctx_bbox = ctx.bbox();
173
174        let fill_cs = state
175            .graphics_state
176            .none_stroke_cs
177            .pattern_cs()
178            .unwrap_or(ColorSpace::device_gray());
179        let stroke_cs = state
180            .graphics_state
181            .stroke_cs
182            .pattern_cs()
183            .unwrap_or(ColorSpace::device_gray());
184
185        let non_stroking_paint = Color::new(
186            fill_cs,
187            state.graphics_state.non_stroke_color.clone(),
188            state.graphics_state.non_stroke_alpha,
189        );
190        let stroke_paint = Color::new(
191            stroke_cs,
192            state.graphics_state.stroke_color.clone(),
193            state.graphics_state.stroke_alpha,
194        );
195        let nesting_depth = ctx.nesting_depth() + 1;
196        let resources = Resources::new(dict.get(RESOURCES).unwrap_or_default());
197
198        Some(Self {
199            cache_key,
200            bbox,
201            x_step,
202            y_step,
203            matrix,
204            ctx_bbox,
205            is_color,
206            stream,
207            stroke_paint,
208            non_stroking_paint,
209            settings: ctx.settings.clone(),
210            resources,
211            cache: ctx.interpreter_cache.clone(),
212            xref: ctx.xref,
213            nesting_depth,
214        })
215    }
216
217    /// Interpret the contents of the pattern into the given device.
218    pub fn interpret(
219        &self,
220        device: &mut impl Device<'a>,
221        initial_transform: Affine,
222        is_stroke: bool,
223    ) -> Option<()> {
224        let state = State::new(initial_transform);
225
226        let mut context = Context::new_with(
227            state.ctm,
228            // TODO: bbox?
229            (initial_transform * self.ctx_bbox.to_path(0.1)).bounding_box(),
230            &self.cache,
231            self.xref,
232            self.settings.clone(),
233            state,
234            self.nesting_depth,
235        );
236
237        let decoded = self.stream.decoded().ok()?;
238        let iter = TypedIter::new(decoded.as_ref());
239
240        let clip_path = ClipPath {
241            path: initial_transform * self.bbox.to_path(0.1),
242            fill: FillRule::NonZero,
243        };
244        device.push_clip_path(&clip_path);
245
246        if self.is_color {
247            interpret(iter, &self.resources, &mut context, device);
248        } else {
249            let paint = if !is_stroke {
250                Paint::Color(self.non_stroking_paint.clone())
251            } else {
252                Paint::Color(self.stroke_paint.clone())
253            };
254
255            let mut device = StencilPatternDevice::new(device, paint.clone());
256            interpret(iter, &self.resources, &mut context, &mut device);
257        }
258
259        device.pop_clip();
260
261        Some(())
262    }
263}
264
265impl CacheKey for TilingPattern<'_> {
266    fn cache_key(&self) -> u128 {
267        self.cache_key
268    }
269}
270
271struct StencilPatternDevice<'a, 'b, T: Device<'a>> {
272    inner: &'b mut T,
273    paint: Paint<'a>,
274}
275
276impl<'a, 'b, T: Device<'a>> StencilPatternDevice<'a, 'b, T> {
277    pub(crate) fn new(device: &'b mut T, paint: Paint<'a>) -> Self {
278        Self {
279            inner: device,
280            paint,
281        }
282    }
283}
284
285// Only filling, stroking of paths and stencil masks are allowed.
286impl<'a, T: Device<'a>> Device<'a> for StencilPatternDevice<'a, '_, T> {
287    fn draw_path(&mut self, path: &BezPath, props: DrawProps<'a>, draw_mode: &DrawMode) {
288        let props = DrawProps {
289            paint: self.paint.clone(),
290            ..props
291        };
292        self.inner.draw_path(path, props, draw_mode);
293    }
294
295    fn push_clip_path(&mut self, clip_path: &ClipPath) {
296        self.inner.push_clip_path(clip_path);
297    }
298
299    fn push_transparency_group(&mut self, _: f32, _: Option<SoftMask<'_>>, _: BlendMode) {}
300
301    fn draw_glyph_run(
302        &mut self,
303        glyph_run: &GlyphRun<'_, 'a>,
304        props: DrawProps<'a>,
305        draw_mode: &DrawMode,
306    ) {
307        self.inner.draw_glyph_run(glyph_run, props, draw_mode);
308    }
309
310    fn draw_image(&mut self, image: Image<'a, '_>, props: ImageDrawProps<'a>) {
311        if let Image::Stencil(mut s) = image {
312            s.paint = self.paint.clone();
313            self.inner.draw_image(Image::Stencil(s), props);
314        }
315    }
316
317    fn pop_clip(&mut self) {
318        self.inner.pop_clip();
319    }
320
321    fn pop_transparency_group(&mut self) {}
322}