hayro_interpret/
pattern.rs

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