hayro_interpret/
pattern.rs

1//! PDF patterns.
2
3use crate::ClipPath;
4use crate::cache::Cache;
5use crate::color::{Color, ColorSpace};
6use crate::context::Context;
7use crate::device::Device;
8use crate::font::Glyph;
9use crate::interpret::state::State;
10use crate::shading::Shading;
11use crate::soft_mask::SoftMask;
12use crate::{
13    FillProps, FillRule, InterpreterSettings, LumaData, Paint, PaintType, RgbData, StrokeProps,
14    interpret,
15};
16use hayro_syntax::content::TypedIter;
17use hayro_syntax::object::Dict;
18use hayro_syntax::object::Rect;
19use hayro_syntax::object::Stream;
20use hayro_syntax::object::dict::keys::{
21    BBOX, EXT_G_STATE, MATRIX, PAINT_TYPE, RESOURCES, SHADING, X_STEP, Y_STEP,
22};
23use hayro_syntax::object::{Object, dict_or_stream};
24use hayro_syntax::page::Resources;
25use hayro_syntax::xref::XRef;
26use kurbo::{Affine, BezPath, Shape};
27use log::warn;
28use std::fmt::{Debug, Formatter};
29use std::sync::Arc;
30
31/// A PDF pattern.
32#[derive(Debug, Clone)]
33pub enum Pattern<'a> {
34    /// A shading pattern.
35    Shading(ShadingPattern),
36    /// A tiling pattern.
37    Tiling(Box<TilingPattern<'a>>),
38}
39
40impl<'a> Pattern<'a> {
41    pub(crate) fn new(
42        object: Object<'a>,
43        ctx: &Context<'a>,
44        resources: &Resources<'a>,
45    ) -> Option<Self> {
46        if let Some(dict) = object.clone().into_dict() {
47            Some(Self::Shading(ShadingPattern::new(&dict)?))
48        } else if let Some(stream) = object.clone().into_stream() {
49            Some(Self::Tiling(Box::new(TilingPattern::new(
50                stream, ctx, resources,
51            )?)))
52        } else {
53            None
54        }
55    }
56}
57
58/// A shading pattern.
59#[derive(Clone, Debug)]
60pub struct ShadingPattern {
61    /// The underlying shading of the pattern.
62    pub shading: Arc<Shading>,
63    /// A transformation matrix to apply prior to rendering.
64    pub matrix: Affine,
65}
66
67impl ShadingPattern {
68    pub(crate) fn new(dict: &Dict) -> Option<Self> {
69        let shading = dict.get::<Object>(SHADING).and_then(|o| {
70            let (dict, stream) = dict_or_stream(&o)?;
71
72            Shading::new(&dict, stream.as_ref())
73        })?;
74        let matrix = dict
75            .get::<[f64; 6]>(MATRIX)
76            .map(Affine::new)
77            .unwrap_or_default();
78
79        if dict.contains_key(EXT_G_STATE) {
80            warn!("shading patterns with ext_g_state are not supported yet");
81        }
82
83        Some(Self {
84            shading: Arc::new(shading),
85            matrix,
86        })
87    }
88}
89
90/// A tiling pattern.
91#[derive(Clone)]
92pub struct TilingPattern<'a> {
93    /// The bbox of the tiling pattern.
94    pub bbox: Rect,
95    /// The step in the x direction.
96    pub x_step: f32,
97    /// The step in the y direction.
98    pub y_step: f32,
99    /// A transformation to apply prior to rendering.
100    pub matrix: Affine,
101    stream: Stream<'a>,
102    is_color: bool,
103    pub(crate) stroke_paint: Color,
104    pub(crate) non_stroking_paint: Color,
105    pub(crate) state: Box<State<'a>>,
106    pub(crate) parent_resources: Resources<'a>,
107    pub(crate) cache: Cache,
108    pub(crate) settings: InterpreterSettings,
109    pub(crate) xref: &'a XRef,
110}
111
112impl Debug for TilingPattern<'_> {
113    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
114        f.write_str("TilingPattern")
115    }
116}
117
118impl<'a> TilingPattern<'a> {
119    pub(crate) fn new(
120        stream: Stream<'a>,
121        ctx: &Context<'a>,
122        resources: &Resources<'a>,
123    ) -> Option<Self> {
124        let dict = stream.dict();
125
126        let bbox = dict.get::<Rect>(BBOX)?;
127        let x_step = dict.get::<f32>(X_STEP)?;
128        let y_step = dict.get::<f32>(Y_STEP)?;
129        let is_color = dict.get::<u8>(PAINT_TYPE)? == 1;
130        let matrix = dict
131            .get::<[f64; 6]>(MATRIX)
132            .map(Affine::new)
133            .unwrap_or_default();
134
135        let state = ctx.get();
136
137        let fill_cs = ctx
138            .get()
139            .none_stroke_cs
140            .pattern_cs()
141            .unwrap_or(ColorSpace::device_gray());
142        let stroke_cs = ctx
143            .get()
144            .stroke_cs
145            .pattern_cs()
146            .unwrap_or(ColorSpace::device_gray());
147
148        let non_stroking_paint = Color::new(
149            fill_cs,
150            state.non_stroke_color.clone(),
151            state.non_stroke_alpha,
152        );
153        let stroke_paint = Color::new(stroke_cs, state.stroke_color.clone(), state.stroke_alpha);
154
155        Some(Self {
156            bbox,
157            x_step,
158            y_step,
159            matrix,
160            is_color,
161            stream,
162            stroke_paint,
163            non_stroking_paint,
164            state: Box::new(ctx.get().clone()),
165            settings: ctx.settings.clone(),
166            parent_resources: resources.clone(),
167            cache: ctx.object_cache.clone(),
168            xref: ctx.xref,
169        })
170    }
171
172    /// Interpret the contents of the pattern into the given device.
173    pub fn interpret(
174        &self,
175        device: &mut impl Device,
176        initial_transform: Affine,
177        is_stroke: bool,
178    ) -> Option<()> {
179        let mut state = (*self.state).clone();
180        state.ctm = initial_transform;
181
182        let mut context = Context::new_with(
183            state.ctm,
184            // TODO: bbox?
185            kurbo::Rect::new(0.0, 0.0, 1.0, 1.0),
186            self.cache.clone(),
187            self.xref,
188            self.settings.clone(),
189            state,
190        );
191
192        let decoded = self.stream.decoded().ok()?;
193        let resources = Resources::from_parent(
194            self.stream.dict().get(RESOURCES).unwrap_or_default(),
195            self.parent_resources.clone(),
196        );
197        let iter = TypedIter::new(decoded.as_ref());
198
199        let clip_path = ClipPath {
200            path: initial_transform * self.bbox.to_path(0.1),
201            fill: FillRule::NonZero,
202        };
203        device.push_clip_path(&clip_path);
204
205        if self.is_color {
206            interpret(iter, &resources, &mut context, device);
207        } else {
208            let paint = if !is_stroke {
209                Paint {
210                    paint_transform: Default::default(),
211                    paint_type: PaintType::Color(self.non_stroking_paint.clone()),
212                }
213            } else {
214                Paint {
215                    paint_transform: Default::default(),
216                    paint_type: PaintType::Color(self.stroke_paint.clone()),
217                }
218            };
219
220            let mut device = StencilPatternDevice::new(device, &paint);
221            interpret(iter, &resources, &mut context, &mut device);
222        }
223
224        device.pop_clip_path();
225
226        Some(())
227    }
228}
229
230struct StencilPatternDevice<'a, T: Device> {
231    inner: &'a mut T,
232    paint: &'a Paint<'a>,
233}
234
235impl<'a, T: Device> StencilPatternDevice<'a, T> {
236    pub fn new(device: &'a mut T, paint: &'a Paint<'a>) -> Self {
237        Self {
238            inner: device,
239            paint,
240        }
241    }
242}
243
244// Only filling, stroking of paths and stencil masks are allowed.
245impl<T: Device> Device for StencilPatternDevice<'_, T> {
246    fn set_transform(&mut self, affine: Affine) {
247        self.inner.set_transform(affine);
248    }
249
250    fn stroke_path(&mut self, path: &BezPath, _: &Paint) {
251        self.inner.stroke_path(path, self.paint)
252    }
253
254    fn set_stroke_properties(&mut self, stroke_props: &StrokeProps) {
255        self.inner.set_stroke_properties(stroke_props)
256    }
257
258    fn set_soft_mask(&mut self, _: Option<SoftMask>) {}
259
260    fn fill_path(&mut self, path: &BezPath, _: &Paint) {
261        self.inner.fill_path(path, self.paint)
262    }
263
264    fn set_fill_properties(&mut self, fill_props: &FillProps) {
265        self.inner.set_fill_properties(fill_props)
266    }
267
268    fn push_clip_path(&mut self, clip_path: &ClipPath) {
269        self.inner.push_clip_path(clip_path)
270    }
271
272    fn push_transparency_group(&mut self, _: f32, _: Option<SoftMask>) {}
273
274    fn fill_glyph(&mut self, glyph: &Glyph<'_>, _: &Paint) {
275        self.inner.fill_glyph(glyph, self.paint)
276    }
277
278    fn stroke_glyph(&mut self, glyph: &Glyph<'_>, _: &Paint) {
279        self.inner.stroke_glyph(glyph, self.paint)
280    }
281
282    fn draw_rgba_image(&mut self, _: RgbData, _: Option<LumaData>) {}
283
284    fn draw_stencil_image(&mut self, stencil: LumaData, _: &Paint) {
285        self.inner.draw_stencil_image(stencil, self.paint);
286    }
287
288    fn pop_clip_path(&mut self) {
289        self.inner.pop_clip_path();
290    }
291
292    fn pop_transparency_group(&mut self) {}
293}