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