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