Skip to main content

pdf_interpret/
pattern.rs

1//! PDF patterns.
2
3use crate::WarningSinkFn;
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::{ActiveTransferFunction, State};
10use crate::shading::Shading;
11use crate::soft_mask::SoftMask;
12use crate::util::{Float32Ext, RectExt, decode_or_warn, hash128};
13use crate::{BlendMode, CacheKey, ClipPath, GlyphDrawMode, Image, PathDrawMode};
14use crate::{FillRule, InterpreterSettings, Paint, interpret};
15use kurbo::{Affine, BezPath, Rect, Shape};
16use log::warn;
17use pdf_syntax::content::TypedIter;
18use pdf_syntax::object::Dict;
19use pdf_syntax::object::Stream;
20use pdf_syntax::object::dict::keys::{
21    BBOX, EXT_G_STATE, MATRIX, PAINT_TYPE, RESOURCES, SHADING, X_STEP, Y_STEP,
22};
23use pdf_syntax::object::{Object, dict_or_stream};
24use pdf_syntax::page::Resources;
25use pdf_syntax::xref::XRef;
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                ctx.get().graphics_state.non_stroke_alpha,
49                &ctx.settings.warning_sink,
50            )?))
51        } else if let Some(stream) = object.clone().into_stream() {
52            Some(Self::Tiling(Box::new(TilingPattern::new(
53                stream, ctx, resources,
54            )?)))
55        } else {
56            None
57        }
58    }
59
60    pub(crate) fn pre_concat_transform(&mut self, transform: Affine) {
61        match self {
62            Self::Shading(p) => {
63                p.matrix = transform * p.matrix;
64                let transformed_clip_path = p.shading.clip_path.clone().map(|r| p.matrix * r);
65                Arc::make_mut(&mut p.shading).clip_path = transformed_clip_path;
66            }
67            Self::Tiling(p) => p.matrix = transform * p.matrix,
68        }
69    }
70
71    pub(crate) fn set_transfer_function(&mut self, tf: ActiveTransferFunction) {
72        if let Self::Shading(p) = self {
73            p.transfer_function = Some(tf);
74        }
75    }
76}
77
78impl CacheKey for Pattern<'_> {
79    fn cache_key(&self) -> u128 {
80        match self {
81            Self::Shading(p) => p.cache_key(),
82            Self::Tiling(p) => p.cache_key(),
83        }
84    }
85}
86
87/// A shading pattern.
88#[derive(Clone, Debug)]
89pub struct ShadingPattern {
90    /// The underlying shading of the pattern.
91    pub shading: Arc<Shading>,
92    /// A transformation matrix to apply prior to rendering.
93    pub matrix: Affine,
94    /// An additional opacity to apply to the shading pattern.
95    pub opacity: f32,
96    /// An optional transfer function to apply to the shading's output colors.
97    pub transfer_function: Option<ActiveTransferFunction>,
98}
99
100impl ShadingPattern {
101    pub(crate) fn new(
102        dict: &Dict<'_>,
103        cache: &Cache,
104        opacity: f32,
105        warning_sink: &WarningSinkFn,
106    ) -> Option<Self> {
107        let shading = dict.get::<Object<'_>>(SHADING).and_then(|o| {
108            let (dict, stream) = dict_or_stream(&o)?;
109
110            Shading::new(&dict, stream.as_ref(), cache, warning_sink)
111        })?;
112        let matrix = dict
113            .get::<[f64; 6]>(MATRIX)
114            .map(Affine::new)
115            .unwrap_or_default();
116
117        if dict.contains_key(EXT_G_STATE) {
118            warn!("shading patterns with ext_g_state are not supported yet");
119        }
120
121        Some(Self {
122            shading: Arc::new(shading),
123            opacity,
124            matrix,
125            transfer_function: None,
126        })
127    }
128}
129
130impl CacheKey for ShadingPattern {
131    fn cache_key(&self) -> u128 {
132        hash128(&(self.shading.cache_key(), self.matrix.cache_key()))
133    }
134}
135
136/// A tiling pattern.
137#[derive(Clone)]
138pub struct TilingPattern<'a> {
139    /// The depth the parent interpretation sat at (#318): a fresh Context
140    /// would otherwise restart the bound at zero, and a cycle alternating
141    /// between constructs would never reach it.
142    nesting_depth: u32,
143    cache_key: u128,
144    ctx_bbox: Rect,
145    /// The bbox of the tiling pattern.
146    pub bbox: Rect,
147    /// The step in the x direction.
148    pub x_step: f32,
149    /// The step in the y direction.
150    pub y_step: f32,
151    /// A transformation to apply prior to rendering.
152    pub matrix: Affine,
153    stream: Stream<'a>,
154    /// Whether this is a colored (`PaintType 1`) tiling pattern.
155    pub is_color: bool,
156    /// For colored tiling patterns: the non-stroking opacity from the parent
157    /// graphics state at the time the pattern was set. PDF ยง8.7.3.1 states
158    /// that the current graphics state (including opacity) applies when a
159    /// tiling pattern is painted, so we capture it here for later use.
160    pub opacity: f32,
161    pub(crate) stroke_paint: Color,
162    pub(crate) non_stroking_paint: Color,
163    pub(crate) parent_resources: Resources<'a>,
164    pub(crate) cache: Cache,
165    pub(crate) settings: InterpreterSettings,
166    pub(crate) xref: &'a XRef,
167}
168
169impl Debug for TilingPattern<'_> {
170    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
171        f.write_str("TilingPattern")
172    }
173}
174
175impl<'a> TilingPattern<'a> {
176    pub(crate) fn new(
177        stream: Stream<'a>,
178        ctx: &Context<'a>,
179        resources: &Resources<'a>,
180    ) -> Option<Self> {
181        let cache_key = stream.cache_key();
182        let dict = stream.dict();
183
184        let bbox = dict.get::<pdf_syntax::object::Rect>(BBOX)?.to_kurbo();
185        let x_step = dict.get::<f32>(X_STEP)?;
186        let y_step = dict.get::<f32>(Y_STEP)?;
187
188        if x_step.is_nearly_zero() || y_step.is_nearly_zero() || bbox.is_zero_area() {
189            return None;
190        }
191
192        let is_color = dict.get::<u8>(PAINT_TYPE)? == 1;
193        let matrix = dict
194            .get::<[f64; 6]>(MATRIX)
195            .map(Affine::new)
196            .unwrap_or_default();
197
198        let state = ctx.get().clone();
199        let ctx_bbox = ctx.bbox();
200
201        let fill_cs = state
202            .graphics_state
203            .none_stroke_cs
204            .pattern_cs()
205            .unwrap_or(ColorSpace::device_gray());
206        let stroke_cs = state
207            .graphics_state
208            .stroke_cs
209            .pattern_cs()
210            .unwrap_or(ColorSpace::device_gray());
211
212        let non_stroke_alpha = state.graphics_state.non_stroke_alpha;
213        let stroke_alpha = state.graphics_state.stroke_alpha;
214
215        let non_stroking_paint = Color::new(
216            fill_cs,
217            state.graphics_state.non_stroke_color.clone(),
218            non_stroke_alpha,
219        );
220        let stroke_paint = Color::new(
221            stroke_cs,
222            state.graphics_state.stroke_color.clone(),
223            stroke_alpha,
224        );
225
226        // For colored tiling patterns the fill opacity controls transparency
227        // of the whole tile; for uncolored patterns it is already embedded in
228        // the paint colors above, so we record it but use it conditionally.
229        let opacity = non_stroke_alpha;
230
231        Some(Self {
232            nesting_depth: ctx.nesting_depth(),
233            cache_key,
234            bbox,
235            x_step,
236            y_step,
237            matrix,
238            ctx_bbox,
239            is_color,
240            stream,
241            opacity,
242            stroke_paint,
243            non_stroking_paint,
244            settings: ctx.settings.clone(),
245            parent_resources: resources.clone(),
246            cache: ctx.object_cache.clone(),
247            xref: ctx.xref,
248        })
249    }
250
251    /// Interpret the contents of the pattern into the given device.
252    pub fn interpret(
253        &self,
254        device: &mut impl Device<'a>,
255        initial_transform: Affine,
256        is_stroke: bool,
257    ) -> Option<()> {
258        let state = State::new(initial_transform);
259
260        let mut context = Context::new_with(
261            state.ctm,
262            // TODO: bbox?
263            (initial_transform * self.ctx_bbox.to_path(0.1)).bounding_box(),
264            self.cache.clone(),
265            self.xref,
266            self.settings.clone(),
267            state,
268            // The parent's depth, not zero: a fresh Context would restart the
269            // bound and a cycle alternating between constructs would never reach
270            // it. Measured before this: rc=134 on a 1082-byte pattern/soft-mask
271            // cycle (#318).
272            self.nesting_depth + 1,
273        );
274
275        let decoded = decode_or_warn(&self.stream, &self.settings.warning_sink)?;
276        let resources = Resources::from_parent(
277            self.stream.dict().get(RESOURCES).unwrap_or_default(),
278            self.parent_resources.clone(),
279        );
280        let iter = TypedIter::new(decoded.as_ref());
281
282        let clip_path = ClipPath {
283            path: initial_transform * self.bbox.to_path(0.1),
284            fill: FillRule::NonZero,
285        };
286        device.push_clip_path(&clip_path);
287
288        if self.is_color {
289            interpret(iter, &resources, &mut context, device);
290        } else {
291            let paint = if !is_stroke {
292                Paint::Color(self.non_stroking_paint.clone())
293            } else {
294                Paint::Color(self.stroke_paint.clone())
295            };
296
297            let mut device = StencilPatternDevice::new(device, paint.clone());
298            interpret(iter, &resources, &mut context, &mut device);
299        }
300
301        device.pop_clip_path();
302
303        Some(())
304    }
305}
306
307impl CacheKey for TilingPattern<'_> {
308    fn cache_key(&self) -> u128 {
309        self.cache_key
310    }
311}
312
313struct StencilPatternDevice<'a, 'b, T: Device<'a>> {
314    inner: &'b mut T,
315    paint: Paint<'a>,
316}
317
318impl<'a, 'b, T: Device<'a>> StencilPatternDevice<'a, 'b, T> {
319    pub(crate) fn new(device: &'b mut T, paint: Paint<'a>) -> Self {
320        Self {
321            inner: device,
322            paint,
323        }
324    }
325}
326
327// Only filling, stroking of paths and stencil masks are allowed.
328impl<'a, T: Device<'a>> Device<'a> for StencilPatternDevice<'a, '_, T> {
329    fn draw_path(
330        &mut self,
331        path: &BezPath,
332        transform: Affine,
333        _: &Paint<'_>,
334        draw_mode: &PathDrawMode,
335    ) {
336        self.inner
337            .draw_path(path, transform, &self.paint, draw_mode);
338    }
339
340    fn set_soft_mask(&mut self, _: Option<SoftMask<'_>>) {}
341
342    fn push_clip_path(&mut self, clip_path: &ClipPath) {
343        self.inner.push_clip_path(clip_path);
344    }
345
346    fn push_transparency_group(&mut self, _: f32, _: Option<SoftMask<'_>>, _: BlendMode) {}
347
348    fn draw_glyph(
349        &mut self,
350        g: &Glyph<'a>,
351        transform: Affine,
352        glyph_transform: Affine,
353        p: &Paint<'a>,
354        draw_mode: &GlyphDrawMode,
355    ) {
356        self.inner
357            .draw_glyph(g, transform, glyph_transform, p, draw_mode);
358    }
359
360    fn draw_image(&mut self, image: Image<'a, '_>, transform: Affine) {
361        if let Image::Stencil(mut s) = image {
362            s.paint = self.paint.clone();
363            self.inner.draw_image(Image::Stencil(s), transform);
364        }
365    }
366
367    fn pop_clip_path(&mut self) {
368        self.inner.pop_clip_path();
369    }
370
371    fn pop_transparency_group(&mut self) {}
372
373    fn set_blend_mode(&mut self, _: BlendMode) {}
374}