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, 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| 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
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    /// The bbox of the tiling pattern.
122    pub bbox: Rect,
123    /// The step in the x direction.
124    pub x_step: f32,
125    /// The step in the y direction.
126    pub y_step: f32,
127    /// A transformation to apply prior to rendering.
128    pub matrix: Affine,
129    stream: Stream<'a>,
130    is_color: bool,
131    pub(crate) stroke_paint: Color,
132    pub(crate) non_stroking_paint: Color,
133    pub(crate) state: Box<State<'a>>,
134    pub(crate) parent_resources: Resources<'a>,
135    pub(crate) cache: Cache,
136    pub(crate) settings: InterpreterSettings,
137    pub(crate) xref: &'a XRef,
138}
139
140impl Debug for TilingPattern<'_> {
141    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
142        f.write_str("TilingPattern")
143    }
144}
145
146impl<'a> TilingPattern<'a> {
147    pub(crate) fn new(
148        stream: Stream<'a>,
149        ctx: &Context<'a>,
150        resources: &Resources<'a>,
151    ) -> Option<Self> {
152        let cache_key = stream.cache_key();
153        let dict = stream.dict();
154
155        let bbox = dict.get::<Rect>(BBOX)?;
156        let x_step = dict.get::<f32>(X_STEP)?;
157        let y_step = dict.get::<f32>(Y_STEP)?;
158        let is_color = dict.get::<u8>(PAINT_TYPE)? == 1;
159        let matrix = dict
160            .get::<[f64; 6]>(MATRIX)
161            .map(Affine::new)
162            .unwrap_or_default();
163
164        let state = ctx.get();
165
166        let fill_cs = ctx
167            .get()
168            .none_stroke_cs
169            .pattern_cs()
170            .unwrap_or(ColorSpace::device_gray());
171        let stroke_cs = ctx
172            .get()
173            .stroke_cs
174            .pattern_cs()
175            .unwrap_or(ColorSpace::device_gray());
176
177        let non_stroking_paint = Color::new(
178            fill_cs,
179            state.non_stroke_color.clone(),
180            state.non_stroke_alpha,
181        );
182        let stroke_paint = Color::new(stroke_cs, state.stroke_color.clone(), state.stroke_alpha);
183
184        Some(Self {
185            cache_key,
186            bbox,
187            x_step,
188            y_step,
189            matrix,
190            is_color,
191            stream,
192            stroke_paint,
193            non_stroking_paint,
194            state: Box::new(ctx.get().clone()),
195            settings: ctx.settings.clone(),
196            parent_resources: resources.clone(),
197            cache: ctx.object_cache.clone(),
198            xref: ctx.xref,
199        })
200    }
201
202    /// Interpret the contents of the pattern into the given device.
203    pub fn interpret(
204        &self,
205        device: &mut impl Device<'a>,
206        initial_transform: Affine,
207        is_stroke: bool,
208    ) -> Option<()> {
209        let mut state = (*self.state).clone();
210        state.ctm = initial_transform;
211
212        let mut context = Context::new_with(
213            state.ctm,
214            // TODO: bbox?
215            kurbo::Rect::new(0.0, 0.0, 1.0, 1.0),
216            self.cache.clone(),
217            self.xref,
218            self.settings.clone(),
219            state,
220        );
221
222        let decoded = self.stream.decoded().ok()?;
223        let resources = Resources::from_parent(
224            self.stream.dict().get(RESOURCES).unwrap_or_default(),
225            self.parent_resources.clone(),
226        );
227        let iter = TypedIter::new(decoded.as_ref());
228
229        let clip_path = ClipPath {
230            path: initial_transform * self.bbox.to_path(0.1),
231            fill: FillRule::NonZero,
232        };
233        device.push_clip_path(&clip_path);
234
235        if self.is_color {
236            interpret(iter, &resources, &mut context, device);
237        } else {
238            let paint = if !is_stroke {
239                Paint::Color(self.non_stroking_paint.clone())
240            } else {
241                Paint::Color(self.stroke_paint.clone())
242            };
243
244            let mut device = StencilPatternDevice::new(device, paint.clone());
245            interpret(iter, &resources, &mut context, &mut device);
246        }
247
248        device.pop_clip_path();
249
250        Some(())
251    }
252}
253
254impl CacheKey for TilingPattern<'_> {
255    fn cache_key(&self) -> u128 {
256        self.cache_key
257    }
258}
259
260struct StencilPatternDevice<'a, 'b, T: Device<'a>> {
261    inner: &'b mut T,
262    paint: Paint<'a>,
263}
264
265impl<'a, 'b, T: Device<'a>> StencilPatternDevice<'a, 'b, T> {
266    pub fn new(device: &'b mut T, paint: Paint<'a>) -> Self {
267        Self {
268            inner: device,
269            paint,
270        }
271    }
272}
273
274// Only filling, stroking of paths and stencil masks are allowed.
275impl<'a, T: Device<'a>> Device<'a> for StencilPatternDevice<'a, '_, T> {
276    fn draw_path(
277        &mut self,
278        path: &BezPath,
279        transform: Affine,
280        _: &Paint,
281        draw_mode: &PathDrawMode,
282    ) {
283        self.inner
284            .draw_path(path, transform, &self.paint, draw_mode)
285    }
286
287    fn set_soft_mask(&mut self, _: Option<SoftMask>) {}
288
289    fn push_clip_path(&mut self, clip_path: &ClipPath) {
290        self.inner.push_clip_path(clip_path)
291    }
292
293    fn push_transparency_group(&mut self, _: f32, _: Option<SoftMask>) {}
294
295    fn draw_glyph(
296        &mut self,
297        g: &Glyph<'a>,
298        transform: Affine,
299        glyph_transform: Affine,
300        p: &Paint<'a>,
301        draw_mode: &GlyphDrawMode,
302    ) {
303        self.inner
304            .draw_glyph(g, transform, glyph_transform, p, draw_mode);
305    }
306
307    fn draw_image(&mut self, image: Image<'a, '_>, transform: Affine) {
308        if let Image::Stencil(mut s) = image {
309            s.paint = self.paint.clone();
310            self.inner.draw_image(Image::Stencil(s), transform)
311        }
312    }
313
314    fn pop_clip_path(&mut self) {
315        self.inner.pop_clip_path();
316    }
317
318    fn pop_transparency_group(&mut self) {}
319}