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::hash128;
12use crate::{CacheKey, ClipPath};
13use crate::{FillRule, InterpreterSettings, LumaData, Paint, RgbData, StrokeProps, 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| (transform * 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
70/// A shading pattern.
71#[derive(Clone, Debug)]
72pub struct ShadingPattern {
73    /// The underlying shading of the pattern.
74    pub shading: Arc<Shading>,
75    /// A transformation matrix to apply prior to rendering.
76    pub matrix: Affine,
77}
78
79impl ShadingPattern {
80    pub(crate) fn new(dict: &Dict, cache: &Cache) -> Option<Self> {
81        let shading = dict.get::<Object>(SHADING).and_then(|o| {
82            let (dict, stream) = dict_or_stream(&o)?;
83
84            Shading::new(&dict, stream.as_ref(), cache)
85        })?;
86        let matrix = dict
87            .get::<[f64; 6]>(MATRIX)
88            .map(Affine::new)
89            .unwrap_or_default();
90
91        if dict.contains_key(EXT_G_STATE) {
92            warn!("shading patterns with ext_g_state are not supported yet");
93        }
94
95        Some(Self {
96            shading: Arc::new(shading),
97            matrix,
98        })
99    }
100}
101
102impl CacheKey for ShadingPattern {
103    fn cache_key(&self) -> u128 {
104        hash128(&(self.shading.cache_key(), self.matrix.cache_key()))
105    }
106}
107
108/// A tiling pattern.
109#[derive(Clone)]
110pub struct TilingPattern<'a> {
111    cache_key: u128,
112    /// The bbox of the tiling pattern.
113    pub bbox: Rect,
114    /// The step in the x direction.
115    pub x_step: f32,
116    /// The step in the y direction.
117    pub y_step: f32,
118    /// A transformation to apply prior to rendering.
119    pub matrix: Affine,
120    stream: Stream<'a>,
121    is_color: bool,
122    pub(crate) stroke_paint: Color,
123    pub(crate) non_stroking_paint: Color,
124    pub(crate) state: Box<State<'a>>,
125    pub(crate) parent_resources: Resources<'a>,
126    pub(crate) cache: Cache,
127    pub(crate) settings: InterpreterSettings,
128    pub(crate) xref: &'a XRef,
129}
130
131impl Debug for TilingPattern<'_> {
132    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
133        f.write_str("TilingPattern")
134    }
135}
136
137impl<'a> TilingPattern<'a> {
138    pub(crate) fn new(
139        stream: Stream<'a>,
140        ctx: &Context<'a>,
141        resources: &Resources<'a>,
142    ) -> Option<Self> {
143        let cache_key = stream.cache_key();
144        let dict = stream.dict();
145
146        let bbox = dict.get::<Rect>(BBOX)?;
147        let x_step = dict.get::<f32>(X_STEP)?;
148        let y_step = dict.get::<f32>(Y_STEP)?;
149        let is_color = dict.get::<u8>(PAINT_TYPE)? == 1;
150        let matrix = dict
151            .get::<[f64; 6]>(MATRIX)
152            .map(Affine::new)
153            .unwrap_or_default();
154
155        let state = ctx.get();
156
157        let fill_cs = ctx
158            .get()
159            .none_stroke_cs
160            .pattern_cs()
161            .unwrap_or(ColorSpace::device_gray());
162        let stroke_cs = ctx
163            .get()
164            .stroke_cs
165            .pattern_cs()
166            .unwrap_or(ColorSpace::device_gray());
167
168        let non_stroking_paint = Color::new(
169            fill_cs,
170            state.non_stroke_color.clone(),
171            state.non_stroke_alpha,
172        );
173        let stroke_paint = Color::new(stroke_cs, state.stroke_color.clone(), state.stroke_alpha);
174
175        Some(Self {
176            cache_key,
177            bbox,
178            x_step,
179            y_step,
180            matrix,
181            is_color,
182            stream,
183            stroke_paint,
184            non_stroking_paint,
185            state: Box::new(ctx.get().clone()),
186            settings: ctx.settings.clone(),
187            parent_resources: resources.clone(),
188            cache: ctx.object_cache.clone(),
189            xref: ctx.xref,
190        })
191    }
192
193    /// Interpret the contents of the pattern into the given device.
194    pub fn interpret(
195        &self,
196        device: &mut impl Device<'a>,
197        initial_transform: Affine,
198        is_stroke: bool,
199    ) -> Option<()> {
200        let mut state = (*self.state).clone();
201        state.ctm = initial_transform;
202
203        let mut context = Context::new_with(
204            state.ctm,
205            // TODO: bbox?
206            kurbo::Rect::new(0.0, 0.0, 1.0, 1.0),
207            self.cache.clone(),
208            self.xref,
209            self.settings.clone(),
210            state,
211        );
212
213        let decoded = self.stream.decoded().ok()?;
214        let resources = Resources::from_parent(
215            self.stream.dict().get(RESOURCES).unwrap_or_default(),
216            self.parent_resources.clone(),
217        );
218        let iter = TypedIter::new(decoded.as_ref());
219
220        let clip_path = ClipPath {
221            path: initial_transform * self.bbox.to_path(0.1),
222            fill: FillRule::NonZero,
223        };
224        device.push_clip_path(&clip_path);
225
226        if self.is_color {
227            interpret(iter, &resources, &mut context, device);
228        } else {
229            let paint = if !is_stroke {
230                Paint::Color(self.non_stroking_paint.clone())
231            } else {
232                Paint::Color(self.stroke_paint.clone())
233            };
234
235            let mut device = StencilPatternDevice::new(device, paint.clone());
236            interpret(iter, &resources, &mut context, &mut device);
237        }
238
239        device.pop_clip_path();
240
241        Some(())
242    }
243}
244
245impl CacheKey for TilingPattern<'_> {
246    fn cache_key(&self) -> u128 {
247        self.cache_key
248    }
249}
250
251struct StencilPatternDevice<'a, 'b, T: Device<'a>> {
252    inner: &'b mut T,
253    paint: Paint<'a>,
254}
255
256impl<'a, 'b, T: Device<'a>> StencilPatternDevice<'a, 'b, T> {
257    pub fn new(device: &'b mut T, paint: Paint<'a>) -> Self {
258        Self {
259            inner: device,
260            paint,
261        }
262    }
263}
264
265// Only filling, stroking of paths and stencil masks are allowed.
266impl<'a, T: Device<'a>> Device<'a> for StencilPatternDevice<'a, '_, T> {
267    fn stroke_path(
268        &mut self,
269        path: &BezPath,
270        transform: Affine,
271        _: &Paint,
272        stroke_props: &StrokeProps,
273    ) {
274        self.inner
275            .stroke_path(path, transform, &self.paint, stroke_props)
276    }
277
278    fn set_soft_mask(&mut self, _: Option<SoftMask>) {}
279
280    fn fill_path(&mut self, path: &BezPath, transform: Affine, _: &Paint, fill_rule: FillRule) {
281        self.inner
282            .fill_path(path, transform, &self.paint, fill_rule)
283    }
284
285    fn push_clip_path(&mut self, clip_path: &ClipPath) {
286        self.inner.push_clip_path(clip_path)
287    }
288
289    fn push_transparency_group(&mut self, _: f32, _: Option<SoftMask>) {}
290
291    fn fill_glyph(
292        &mut self,
293        glyph: &Glyph<'a>,
294        transform: Affine,
295        glyph_transform: Affine,
296        _: &Paint,
297    ) {
298        self.inner
299            .fill_glyph(glyph, transform, glyph_transform, &self.paint)
300    }
301
302    fn stroke_glyph(
303        &mut self,
304        glyph: &Glyph<'a>,
305        transform: Affine,
306        glyph_transform: Affine,
307        _: &Paint,
308        stroke_props: &StrokeProps,
309    ) {
310        self.inner
311            .stroke_glyph(glyph, transform, glyph_transform, &self.paint, stroke_props)
312    }
313
314    fn draw_rgba_image(&mut self, _: RgbData, _: Affine, _: Option<LumaData>) {}
315
316    fn draw_stencil_image(&mut self, stencil: LumaData, transform: Affine, _: &Paint) {
317        self.inner
318            .draw_stencil_image(stencil, transform, &self.paint);
319    }
320
321    fn pop_clip_path(&mut self) {
322        self.inner.pop_clip_path();
323    }
324
325    fn pop_transparency_group(&mut self) {}
326}